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
868struct ChangeLocation {
869 current: Option<Vec<Anchor>>,
870 original: Vec<Anchor>,
871}
872impl ChangeLocation {
873 fn locations(&self) -> &[Anchor] {
874 self.current.as_ref().unwrap_or(&self.original)
875 }
876}
877
878/// A set of caret positions, registered when the editor was edited.
879pub struct ChangeList {
880 changes: Vec<ChangeLocation>,
881 /// Currently "selected" change.
882 position: Option<usize>,
883}
884
885impl ChangeList {
886 pub fn new() -> Self {
887 Self {
888 changes: Vec::new(),
889 position: None,
890 }
891 }
892
893 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
894 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
895 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
896 if self.changes.is_empty() {
897 return None;
898 }
899
900 let prev = self.position.unwrap_or(self.changes.len());
901 let next = if direction == Direction::Prev {
902 prev.saturating_sub(count)
903 } else {
904 (prev + count).min(self.changes.len() - 1)
905 };
906 self.position = Some(next);
907 self.changes.get(next).map(|change| change.locations())
908 }
909
910 /// Adds a new change to the list, resetting the change list position.
911 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
912 self.position.take();
913 if let Some(last) = self.changes.last_mut()
914 && group
915 {
916 last.current = Some(new_positions)
917 } else {
918 self.changes.push(ChangeLocation {
919 original: new_positions,
920 current: None,
921 });
922 }
923 }
924
925 pub fn last(&self) -> Option<&[Anchor]> {
926 self.changes.last().map(|change| change.locations())
927 }
928
929 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
930 self.changes.last().map(|change| change.original.as_slice())
931 }
932
933 pub fn invert_last_group(&mut self) {
934 if let Some(last) = self.changes.last_mut() {
935 if let Some(current) = last.current.as_mut() {
936 mem::swap(&mut last.original, current);
937 }
938 }
939 }
940}
941
942#[derive(Clone)]
943struct InlineBlamePopoverState {
944 scroll_handle: ScrollHandle,
945 commit_message: Option<ParsedCommitMessage>,
946 markdown: Entity<Markdown>,
947}
948
949struct InlineBlamePopover {
950 position: gpui::Point<Pixels>,
951 hide_task: Option<Task<()>>,
952 popover_bounds: Option<Bounds<Pixels>>,
953 popover_state: InlineBlamePopoverState,
954}
955
956enum SelectionDragState {
957 /// State when no drag related activity is detected.
958 None,
959 /// State when the mouse is down on a selection that is about to be dragged.
960 ReadyToDrag {
961 selection: Selection<Anchor>,
962 click_position: gpui::Point<Pixels>,
963 mouse_down_time: Instant,
964 },
965 /// State when the mouse is dragging the selection in the editor.
966 Dragging {
967 selection: Selection<Anchor>,
968 drop_cursor: Selection<Anchor>,
969 hide_drop_cursor: bool,
970 },
971}
972
973enum ColumnarSelectionState {
974 FromMouse {
975 selection_tail: Anchor,
976 display_point: Option<DisplayPoint>,
977 },
978 FromSelection {
979 selection_tail: Anchor,
980 },
981}
982
983/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
984/// a breakpoint on them.
985#[derive(Clone, Copy, Debug, PartialEq, Eq)]
986struct PhantomBreakpointIndicator {
987 display_row: DisplayRow,
988 /// There's a small debounce between hovering over the line and showing the indicator.
989 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
990 is_active: bool,
991 collides_with_existing_breakpoint: bool,
992}
993
994/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
995///
996/// See the [module level documentation](self) for more information.
997pub struct Editor {
998 focus_handle: FocusHandle,
999 last_focused_descendant: Option<WeakFocusHandle>,
1000 /// The text buffer being edited
1001 buffer: Entity<MultiBuffer>,
1002 /// Map of how text in the buffer should be displayed.
1003 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1004 pub display_map: Entity<DisplayMap>,
1005 pub selections: SelectionsCollection,
1006 pub scroll_manager: ScrollManager,
1007 /// When inline assist editors are linked, they all render cursors because
1008 /// typing enters text into each of them, even the ones that aren't focused.
1009 pub(crate) show_cursor_when_unfocused: bool,
1010 columnar_selection_state: Option<ColumnarSelectionState>,
1011 add_selections_state: Option<AddSelectionsState>,
1012 select_next_state: Option<SelectNextState>,
1013 select_prev_state: Option<SelectNextState>,
1014 selection_history: SelectionHistory,
1015 defer_selection_effects: bool,
1016 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1017 autoclose_regions: Vec<AutocloseRegion>,
1018 snippet_stack: InvalidationStack<SnippetState>,
1019 select_syntax_node_history: SelectSyntaxNodeHistory,
1020 ime_transaction: Option<TransactionId>,
1021 pub diagnostics_max_severity: DiagnosticSeverity,
1022 active_diagnostics: ActiveDiagnostic,
1023 show_inline_diagnostics: bool,
1024 inline_diagnostics_update: Task<()>,
1025 inline_diagnostics_enabled: bool,
1026 diagnostics_enabled: bool,
1027 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1028 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1029 hard_wrap: Option<usize>,
1030
1031 // TODO: make this a access method
1032 pub project: Option<Entity<Project>>,
1033 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1034 completion_provider: Option<Rc<dyn CompletionProvider>>,
1035 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1036 blink_manager: Entity<BlinkManager>,
1037 show_cursor_names: bool,
1038 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1039 pub show_local_selections: bool,
1040 mode: EditorMode,
1041 show_breadcrumbs: bool,
1042 show_gutter: bool,
1043 show_scrollbars: ScrollbarAxes,
1044 minimap_visibility: MinimapVisibility,
1045 offset_content: bool,
1046 disable_expand_excerpt_buttons: bool,
1047 show_line_numbers: Option<bool>,
1048 use_relative_line_numbers: Option<bool>,
1049 show_git_diff_gutter: Option<bool>,
1050 show_code_actions: Option<bool>,
1051 show_runnables: Option<bool>,
1052 show_breakpoints: Option<bool>,
1053 show_wrap_guides: Option<bool>,
1054 show_indent_guides: Option<bool>,
1055 placeholder_text: Option<Arc<str>>,
1056 highlight_order: usize,
1057 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1058 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1059 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1060 scrollbar_marker_state: ScrollbarMarkerState,
1061 active_indent_guides_state: ActiveIndentGuidesState,
1062 nav_history: Option<ItemNavHistory>,
1063 context_menu: RefCell<Option<CodeContextMenu>>,
1064 context_menu_options: Option<ContextMenuOptions>,
1065 mouse_context_menu: Option<MouseContextMenu>,
1066 completion_tasks: Vec<(CompletionId, Task<()>)>,
1067 inline_blame_popover: Option<InlineBlamePopover>,
1068 inline_blame_popover_show_task: Option<Task<()>>,
1069 signature_help_state: SignatureHelpState,
1070 auto_signature_help: Option<bool>,
1071 find_all_references_task_sources: Vec<Anchor>,
1072 next_completion_id: CompletionId,
1073 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1074 code_actions_task: Option<Task<Result<()>>>,
1075 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1076 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1077 document_highlights_task: Option<Task<()>>,
1078 linked_editing_range_task: Option<Task<Option<()>>>,
1079 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1080 pending_rename: Option<RenameState>,
1081 searchable: bool,
1082 cursor_shape: CursorShape,
1083 current_line_highlight: Option<CurrentLineHighlight>,
1084 collapse_matches: bool,
1085 autoindent_mode: Option<AutoindentMode>,
1086 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1087 input_enabled: bool,
1088 use_modal_editing: bool,
1089 read_only: bool,
1090 leader_id: Option<CollaboratorId>,
1091 remote_id: Option<ViewId>,
1092 pub hover_state: HoverState,
1093 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1094 gutter_hovered: bool,
1095 hovered_link_state: Option<HoveredLinkState>,
1096 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1097 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1098 active_inline_completion: Option<InlineCompletionState>,
1099 /// Used to prevent flickering as the user types while the menu is open
1100 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1101 edit_prediction_settings: EditPredictionSettings,
1102 inline_completions_hidden_for_vim_mode: bool,
1103 show_inline_completions_override: Option<bool>,
1104 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1105 edit_prediction_preview: EditPredictionPreview,
1106 edit_prediction_indent_conflict: bool,
1107 edit_prediction_requires_modifier_in_indent_conflict: bool,
1108 inlay_hint_cache: InlayHintCache,
1109 next_inlay_id: usize,
1110 _subscriptions: Vec<Subscription>,
1111 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1112 gutter_dimensions: GutterDimensions,
1113 style: Option<EditorStyle>,
1114 text_style_refinement: Option<TextStyleRefinement>,
1115 next_editor_action_id: EditorActionId,
1116 editor_actions: Rc<
1117 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1118 >,
1119 use_autoclose: bool,
1120 use_auto_surround: bool,
1121 auto_replace_emoji_shortcode: bool,
1122 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1123 show_git_blame_gutter: bool,
1124 show_git_blame_inline: bool,
1125 show_git_blame_inline_delay_task: Option<Task<()>>,
1126 git_blame_inline_enabled: bool,
1127 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1128 serialize_dirty_buffers: bool,
1129 show_selection_menu: Option<bool>,
1130 blame: Option<Entity<GitBlame>>,
1131 blame_subscription: Option<Subscription>,
1132 custom_context_menu: Option<
1133 Box<
1134 dyn 'static
1135 + Fn(
1136 &mut Self,
1137 DisplayPoint,
1138 &mut Window,
1139 &mut Context<Self>,
1140 ) -> Option<Entity<ui::ContextMenu>>,
1141 >,
1142 >,
1143 last_bounds: Option<Bounds<Pixels>>,
1144 last_position_map: Option<Rc<PositionMap>>,
1145 expect_bounds_change: Option<Bounds<Pixels>>,
1146 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1147 tasks_update_task: Option<Task<()>>,
1148 breakpoint_store: Option<Entity<BreakpointStore>>,
1149 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1150 hovered_diff_hunk_row: Option<DisplayRow>,
1151 pull_diagnostics_task: Task<()>,
1152 in_project_search: bool,
1153 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1154 breadcrumb_header: Option<String>,
1155 focused_block: Option<FocusedBlock>,
1156 next_scroll_position: NextScrollCursorCenterTopBottom,
1157 addons: HashMap<TypeId, Box<dyn Addon>>,
1158 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1159 load_diff_task: Option<Shared<Task<()>>>,
1160 /// Whether we are temporarily displaying a diff other than git's
1161 temporary_diff_override: bool,
1162 selection_mark_mode: bool,
1163 toggle_fold_multiple_buffers: Task<()>,
1164 _scroll_cursor_center_top_bottom_task: Task<()>,
1165 serialize_selections: Task<()>,
1166 serialize_folds: Task<()>,
1167 mouse_cursor_hidden: bool,
1168 minimap: Option<Entity<Self>>,
1169 hide_mouse_mode: HideMouseMode,
1170 pub change_list: ChangeList,
1171 inline_value_cache: InlineValueCache,
1172 selection_drag_state: SelectionDragState,
1173 next_color_inlay_id: usize,
1174 colors: Option<LspColorData>,
1175 folding_newlines: Task<()>,
1176}
1177
1178#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1179enum NextScrollCursorCenterTopBottom {
1180 #[default]
1181 Center,
1182 Top,
1183 Bottom,
1184}
1185
1186impl NextScrollCursorCenterTopBottom {
1187 fn next(&self) -> Self {
1188 match self {
1189 Self::Center => Self::Top,
1190 Self::Top => Self::Bottom,
1191 Self::Bottom => Self::Center,
1192 }
1193 }
1194}
1195
1196#[derive(Clone)]
1197pub struct EditorSnapshot {
1198 pub mode: EditorMode,
1199 show_gutter: bool,
1200 show_line_numbers: Option<bool>,
1201 show_git_diff_gutter: Option<bool>,
1202 show_code_actions: Option<bool>,
1203 show_runnables: Option<bool>,
1204 show_breakpoints: Option<bool>,
1205 git_blame_gutter_max_author_length: Option<usize>,
1206 pub display_snapshot: DisplaySnapshot,
1207 pub placeholder_text: Option<Arc<str>>,
1208 is_focused: bool,
1209 scroll_anchor: ScrollAnchor,
1210 ongoing_scroll: OngoingScroll,
1211 current_line_highlight: CurrentLineHighlight,
1212 gutter_hovered: bool,
1213}
1214
1215#[derive(Default, Debug, Clone, Copy)]
1216pub struct GutterDimensions {
1217 pub left_padding: Pixels,
1218 pub right_padding: Pixels,
1219 pub width: Pixels,
1220 pub margin: Pixels,
1221 pub git_blame_entries_width: Option<Pixels>,
1222}
1223
1224impl GutterDimensions {
1225 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1226 Self {
1227 margin: Self::default_gutter_margin(font_id, font_size, cx),
1228 ..Default::default()
1229 }
1230 }
1231
1232 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1233 -cx.text_system().descent(font_id, font_size)
1234 }
1235 /// The full width of the space taken up by the gutter.
1236 pub fn full_width(&self) -> Pixels {
1237 self.margin + self.width
1238 }
1239
1240 /// The width of the space reserved for the fold indicators,
1241 /// use alongside 'justify_end' and `gutter_width` to
1242 /// right align content with the line numbers
1243 pub fn fold_area_width(&self) -> Pixels {
1244 self.margin + self.right_padding
1245 }
1246}
1247
1248struct CharacterDimensions {
1249 em_width: Pixels,
1250 em_advance: Pixels,
1251 line_height: Pixels,
1252}
1253
1254#[derive(Debug)]
1255pub struct RemoteSelection {
1256 pub replica_id: ReplicaId,
1257 pub selection: Selection<Anchor>,
1258 pub cursor_shape: CursorShape,
1259 pub collaborator_id: CollaboratorId,
1260 pub line_mode: bool,
1261 pub user_name: Option<SharedString>,
1262 pub color: PlayerColor,
1263}
1264
1265#[derive(Clone, Debug)]
1266struct SelectionHistoryEntry {
1267 selections: Arc<[Selection<Anchor>]>,
1268 select_next_state: Option<SelectNextState>,
1269 select_prev_state: Option<SelectNextState>,
1270 add_selections_state: Option<AddSelectionsState>,
1271}
1272
1273#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1274enum SelectionHistoryMode {
1275 Normal,
1276 Undoing,
1277 Redoing,
1278 Skipping,
1279}
1280
1281#[derive(Clone, PartialEq, Eq, Hash)]
1282struct HoveredCursor {
1283 replica_id: u16,
1284 selection_id: usize,
1285}
1286
1287impl Default for SelectionHistoryMode {
1288 fn default() -> Self {
1289 Self::Normal
1290 }
1291}
1292
1293#[derive(Debug)]
1294/// SelectionEffects controls the side-effects of updating the selection.
1295///
1296/// The default behaviour does "what you mostly want":
1297/// - it pushes to the nav history if the cursor moved by >10 lines
1298/// - it re-triggers completion requests
1299/// - it scrolls to fit
1300///
1301/// You might want to modify these behaviours. For example when doing a "jump"
1302/// like go to definition, we always want to add to nav history; but when scrolling
1303/// in vim mode we never do.
1304///
1305/// Similarly, you might want to disable scrolling if you don't want the viewport to
1306/// move.
1307pub struct SelectionEffects {
1308 nav_history: Option<bool>,
1309 completions: bool,
1310 scroll: Option<Autoscroll>,
1311}
1312
1313impl Default for SelectionEffects {
1314 fn default() -> Self {
1315 Self {
1316 nav_history: None,
1317 completions: true,
1318 scroll: Some(Autoscroll::fit()),
1319 }
1320 }
1321}
1322impl SelectionEffects {
1323 pub fn scroll(scroll: Autoscroll) -> Self {
1324 Self {
1325 scroll: Some(scroll),
1326 ..Default::default()
1327 }
1328 }
1329
1330 pub fn no_scroll() -> Self {
1331 Self {
1332 scroll: None,
1333 ..Default::default()
1334 }
1335 }
1336
1337 pub fn completions(self, completions: bool) -> Self {
1338 Self {
1339 completions,
1340 ..self
1341 }
1342 }
1343
1344 pub fn nav_history(self, nav_history: bool) -> Self {
1345 Self {
1346 nav_history: Some(nav_history),
1347 ..self
1348 }
1349 }
1350}
1351
1352struct DeferredSelectionEffectsState {
1353 changed: bool,
1354 effects: SelectionEffects,
1355 old_cursor_position: Anchor,
1356 history_entry: SelectionHistoryEntry,
1357}
1358
1359#[derive(Default)]
1360struct SelectionHistory {
1361 #[allow(clippy::type_complexity)]
1362 selections_by_transaction:
1363 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1364 mode: SelectionHistoryMode,
1365 undo_stack: VecDeque<SelectionHistoryEntry>,
1366 redo_stack: VecDeque<SelectionHistoryEntry>,
1367}
1368
1369impl SelectionHistory {
1370 #[track_caller]
1371 fn insert_transaction(
1372 &mut self,
1373 transaction_id: TransactionId,
1374 selections: Arc<[Selection<Anchor>]>,
1375 ) {
1376 if selections.is_empty() {
1377 log::error!(
1378 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1379 std::panic::Location::caller()
1380 );
1381 return;
1382 }
1383 self.selections_by_transaction
1384 .insert(transaction_id, (selections, None));
1385 }
1386
1387 #[allow(clippy::type_complexity)]
1388 fn transaction(
1389 &self,
1390 transaction_id: TransactionId,
1391 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1392 self.selections_by_transaction.get(&transaction_id)
1393 }
1394
1395 #[allow(clippy::type_complexity)]
1396 fn transaction_mut(
1397 &mut self,
1398 transaction_id: TransactionId,
1399 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1400 self.selections_by_transaction.get_mut(&transaction_id)
1401 }
1402
1403 fn push(&mut self, entry: SelectionHistoryEntry) {
1404 if !entry.selections.is_empty() {
1405 match self.mode {
1406 SelectionHistoryMode::Normal => {
1407 self.push_undo(entry);
1408 self.redo_stack.clear();
1409 }
1410 SelectionHistoryMode::Undoing => self.push_redo(entry),
1411 SelectionHistoryMode::Redoing => self.push_undo(entry),
1412 SelectionHistoryMode::Skipping => {}
1413 }
1414 }
1415 }
1416
1417 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1418 if self
1419 .undo_stack
1420 .back()
1421 .map_or(true, |e| e.selections != entry.selections)
1422 {
1423 self.undo_stack.push_back(entry);
1424 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1425 self.undo_stack.pop_front();
1426 }
1427 }
1428 }
1429
1430 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1431 if self
1432 .redo_stack
1433 .back()
1434 .map_or(true, |e| e.selections != entry.selections)
1435 {
1436 self.redo_stack.push_back(entry);
1437 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1438 self.redo_stack.pop_front();
1439 }
1440 }
1441 }
1442}
1443
1444#[derive(Clone, Copy)]
1445pub struct RowHighlightOptions {
1446 pub autoscroll: bool,
1447 pub include_gutter: bool,
1448}
1449
1450impl Default for RowHighlightOptions {
1451 fn default() -> Self {
1452 Self {
1453 autoscroll: Default::default(),
1454 include_gutter: true,
1455 }
1456 }
1457}
1458
1459struct RowHighlight {
1460 index: usize,
1461 range: Range<Anchor>,
1462 color: Hsla,
1463 options: RowHighlightOptions,
1464 type_id: TypeId,
1465}
1466
1467#[derive(Clone, Debug)]
1468struct AddSelectionsState {
1469 groups: Vec<AddSelectionsGroup>,
1470}
1471
1472#[derive(Clone, Debug)]
1473struct AddSelectionsGroup {
1474 above: bool,
1475 stack: Vec<usize>,
1476}
1477
1478#[derive(Clone)]
1479struct SelectNextState {
1480 query: AhoCorasick,
1481 wordwise: bool,
1482 done: bool,
1483}
1484
1485impl std::fmt::Debug for SelectNextState {
1486 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1487 f.debug_struct(std::any::type_name::<Self>())
1488 .field("wordwise", &self.wordwise)
1489 .field("done", &self.done)
1490 .finish()
1491 }
1492}
1493
1494#[derive(Debug)]
1495struct AutocloseRegion {
1496 selection_id: usize,
1497 range: Range<Anchor>,
1498 pair: BracketPair,
1499}
1500
1501#[derive(Debug)]
1502struct SnippetState {
1503 ranges: Vec<Vec<Range<Anchor>>>,
1504 active_index: usize,
1505 choices: Vec<Option<Vec<String>>>,
1506}
1507
1508#[doc(hidden)]
1509pub struct RenameState {
1510 pub range: Range<Anchor>,
1511 pub old_name: Arc<str>,
1512 pub editor: Entity<Editor>,
1513 block_id: CustomBlockId,
1514}
1515
1516struct InvalidationStack<T>(Vec<T>);
1517
1518struct RegisteredInlineCompletionProvider {
1519 provider: Arc<dyn InlineCompletionProviderHandle>,
1520 _subscription: Subscription,
1521}
1522
1523#[derive(Debug, PartialEq, Eq)]
1524pub struct ActiveDiagnosticGroup {
1525 pub active_range: Range<Anchor>,
1526 pub active_message: String,
1527 pub group_id: usize,
1528 pub blocks: HashSet<CustomBlockId>,
1529}
1530
1531#[derive(Debug, PartialEq, Eq)]
1532
1533pub(crate) enum ActiveDiagnostic {
1534 None,
1535 All,
1536 Group(ActiveDiagnosticGroup),
1537}
1538
1539#[derive(Serialize, Deserialize, Clone, Debug)]
1540pub struct ClipboardSelection {
1541 /// The number of bytes in this selection.
1542 pub len: usize,
1543 /// Whether this was a full-line selection.
1544 pub is_entire_line: bool,
1545 /// The indentation of the first line when this content was originally copied.
1546 pub first_line_indent: u32,
1547}
1548
1549// selections, scroll behavior, was newest selection reversed
1550type SelectSyntaxNodeHistoryState = (
1551 Box<[Selection<usize>]>,
1552 SelectSyntaxNodeScrollBehavior,
1553 bool,
1554);
1555
1556#[derive(Default)]
1557struct SelectSyntaxNodeHistory {
1558 stack: Vec<SelectSyntaxNodeHistoryState>,
1559 // disable temporarily to allow changing selections without losing the stack
1560 pub disable_clearing: bool,
1561}
1562
1563impl SelectSyntaxNodeHistory {
1564 pub fn try_clear(&mut self) {
1565 if !self.disable_clearing {
1566 self.stack.clear();
1567 }
1568 }
1569
1570 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1571 self.stack.push(selection);
1572 }
1573
1574 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1575 self.stack.pop()
1576 }
1577}
1578
1579enum SelectSyntaxNodeScrollBehavior {
1580 CursorTop,
1581 FitSelection,
1582 CursorBottom,
1583}
1584
1585#[derive(Debug)]
1586pub(crate) struct NavigationData {
1587 cursor_anchor: Anchor,
1588 cursor_position: Point,
1589 scroll_anchor: ScrollAnchor,
1590 scroll_top_row: u32,
1591}
1592
1593#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1594pub enum GotoDefinitionKind {
1595 Symbol,
1596 Declaration,
1597 Type,
1598 Implementation,
1599}
1600
1601#[derive(Debug, Clone)]
1602enum InlayHintRefreshReason {
1603 ModifiersChanged(bool),
1604 Toggle(bool),
1605 SettingsChange(InlayHintSettings),
1606 NewLinesShown,
1607 BufferEdited(HashSet<Arc<Language>>),
1608 RefreshRequested,
1609 ExcerptsRemoved(Vec<ExcerptId>),
1610}
1611
1612impl InlayHintRefreshReason {
1613 fn description(&self) -> &'static str {
1614 match self {
1615 Self::ModifiersChanged(_) => "modifiers changed",
1616 Self::Toggle(_) => "toggle",
1617 Self::SettingsChange(_) => "settings change",
1618 Self::NewLinesShown => "new lines shown",
1619 Self::BufferEdited(_) => "buffer edited",
1620 Self::RefreshRequested => "refresh requested",
1621 Self::ExcerptsRemoved(_) => "excerpts removed",
1622 }
1623 }
1624}
1625
1626pub enum FormatTarget {
1627 Buffers(HashSet<Entity<Buffer>>),
1628 Ranges(Vec<Range<MultiBufferPoint>>),
1629}
1630
1631pub(crate) struct FocusedBlock {
1632 id: BlockId,
1633 focus_handle: WeakFocusHandle,
1634}
1635
1636#[derive(Clone)]
1637enum JumpData {
1638 MultiBufferRow {
1639 row: MultiBufferRow,
1640 line_offset_from_top: u32,
1641 },
1642 MultiBufferPoint {
1643 excerpt_id: ExcerptId,
1644 position: Point,
1645 anchor: text::Anchor,
1646 line_offset_from_top: u32,
1647 },
1648}
1649
1650pub enum MultibufferSelectionMode {
1651 First,
1652 All,
1653}
1654
1655#[derive(Clone, Copy, Debug, Default)]
1656pub struct RewrapOptions {
1657 pub override_language_settings: bool,
1658 pub preserve_existing_whitespace: bool,
1659}
1660
1661impl Editor {
1662 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1663 let buffer = cx.new(|cx| Buffer::local("", cx));
1664 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1665 Self::new(
1666 EditorMode::SingleLine { auto_width: false },
1667 buffer,
1668 None,
1669 window,
1670 cx,
1671 )
1672 }
1673
1674 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1675 let buffer = cx.new(|cx| Buffer::local("", cx));
1676 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1677 Self::new(EditorMode::full(), buffer, None, window, cx)
1678 }
1679
1680 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1681 let buffer = cx.new(|cx| Buffer::local("", cx));
1682 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1683 Self::new(
1684 EditorMode::SingleLine { auto_width: true },
1685 buffer,
1686 None,
1687 window,
1688 cx,
1689 )
1690 }
1691
1692 pub fn auto_height(
1693 min_lines: usize,
1694 max_lines: usize,
1695 window: &mut Window,
1696 cx: &mut Context<Self>,
1697 ) -> Self {
1698 let buffer = cx.new(|cx| Buffer::local("", cx));
1699 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1700 Self::new(
1701 EditorMode::AutoHeight {
1702 min_lines,
1703 max_lines: Some(max_lines),
1704 },
1705 buffer,
1706 None,
1707 window,
1708 cx,
1709 )
1710 }
1711
1712 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1713 /// The editor grows as tall as needed to fit its content.
1714 pub fn auto_height_unbounded(
1715 min_lines: usize,
1716 window: &mut Window,
1717 cx: &mut Context<Self>,
1718 ) -> Self {
1719 let buffer = cx.new(|cx| Buffer::local("", cx));
1720 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1721 Self::new(
1722 EditorMode::AutoHeight {
1723 min_lines,
1724 max_lines: None,
1725 },
1726 buffer,
1727 None,
1728 window,
1729 cx,
1730 )
1731 }
1732
1733 pub fn for_buffer(
1734 buffer: Entity<Buffer>,
1735 project: Option<Entity<Project>>,
1736 window: &mut Window,
1737 cx: &mut Context<Self>,
1738 ) -> Self {
1739 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1740 Self::new(EditorMode::full(), buffer, project, window, cx)
1741 }
1742
1743 pub fn for_multibuffer(
1744 buffer: Entity<MultiBuffer>,
1745 project: Option<Entity<Project>>,
1746 window: &mut Window,
1747 cx: &mut Context<Self>,
1748 ) -> Self {
1749 Self::new(EditorMode::full(), buffer, project, window, cx)
1750 }
1751
1752 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1753 let mut clone = Self::new(
1754 self.mode.clone(),
1755 self.buffer.clone(),
1756 self.project.clone(),
1757 window,
1758 cx,
1759 );
1760 self.display_map.update(cx, |display_map, cx| {
1761 let snapshot = display_map.snapshot(cx);
1762 clone.display_map.update(cx, |display_map, cx| {
1763 display_map.set_state(&snapshot, cx);
1764 });
1765 });
1766 clone.folds_did_change(cx);
1767 clone.selections.clone_state(&self.selections);
1768 clone.scroll_manager.clone_state(&self.scroll_manager);
1769 clone.searchable = self.searchable;
1770 clone.read_only = self.read_only;
1771 clone
1772 }
1773
1774 pub fn new(
1775 mode: EditorMode,
1776 buffer: Entity<MultiBuffer>,
1777 project: Option<Entity<Project>>,
1778 window: &mut Window,
1779 cx: &mut Context<Self>,
1780 ) -> Self {
1781 Editor::new_internal(mode, buffer, project, None, window, cx)
1782 }
1783
1784 fn new_internal(
1785 mode: EditorMode,
1786 buffer: Entity<MultiBuffer>,
1787 project: Option<Entity<Project>>,
1788 display_map: Option<Entity<DisplayMap>>,
1789 window: &mut Window,
1790 cx: &mut Context<Self>,
1791 ) -> Self {
1792 debug_assert!(
1793 display_map.is_none() || mode.is_minimap(),
1794 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1795 );
1796
1797 let full_mode = mode.is_full();
1798 let diagnostics_max_severity = if full_mode {
1799 EditorSettings::get_global(cx)
1800 .diagnostics_max_severity
1801 .unwrap_or(DiagnosticSeverity::Hint)
1802 } else {
1803 DiagnosticSeverity::Off
1804 };
1805 let style = window.text_style();
1806 let font_size = style.font_size.to_pixels(window.rem_size());
1807 let editor = cx.entity().downgrade();
1808 let fold_placeholder = FoldPlaceholder {
1809 constrain_width: true,
1810 render: Arc::new(move |fold_id, fold_range, cx| {
1811 let editor = editor.clone();
1812 div()
1813 .id(fold_id)
1814 .bg(cx.theme().colors().ghost_element_background)
1815 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1816 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1817 .rounded_xs()
1818 .size_full()
1819 .cursor_pointer()
1820 .child("⋯")
1821 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1822 .on_click(move |_, _window, cx| {
1823 editor
1824 .update(cx, |editor, cx| {
1825 editor.unfold_ranges(
1826 &[fold_range.start..fold_range.end],
1827 true,
1828 false,
1829 cx,
1830 );
1831 cx.stop_propagation();
1832 })
1833 .ok();
1834 })
1835 .into_any()
1836 }),
1837 merge_adjacent: true,
1838 ..FoldPlaceholder::default()
1839 };
1840 let display_map = display_map.unwrap_or_else(|| {
1841 cx.new(|cx| {
1842 DisplayMap::new(
1843 buffer.clone(),
1844 style.font(),
1845 font_size,
1846 None,
1847 FILE_HEADER_HEIGHT,
1848 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1849 fold_placeholder,
1850 diagnostics_max_severity,
1851 cx,
1852 )
1853 })
1854 });
1855
1856 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1857
1858 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1859
1860 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1861 .then(|| language_settings::SoftWrap::None);
1862
1863 let mut project_subscriptions = Vec::new();
1864 if mode.is_full() {
1865 if let Some(project) = project.as_ref() {
1866 project_subscriptions.push(cx.subscribe_in(
1867 project,
1868 window,
1869 |editor, _, event, window, cx| match event {
1870 project::Event::RefreshCodeLens => {
1871 // we always query lens with actions, without storing them, always refreshing them
1872 }
1873 project::Event::RefreshInlayHints => {
1874 editor
1875 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1876 }
1877 project::Event::LanguageServerAdded(..)
1878 | project::Event::LanguageServerRemoved(..) => {
1879 if editor.tasks_update_task.is_none() {
1880 editor.tasks_update_task =
1881 Some(editor.refresh_runnables(window, cx));
1882 }
1883 editor.update_lsp_data(true, None, window, cx);
1884 }
1885 project::Event::SnippetEdit(id, snippet_edits) => {
1886 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1887 let focus_handle = editor.focus_handle(cx);
1888 if focus_handle.is_focused(window) {
1889 let snapshot = buffer.read(cx).snapshot();
1890 for (range, snippet) in snippet_edits {
1891 let editor_range =
1892 language::range_from_lsp(*range).to_offset(&snapshot);
1893 editor
1894 .insert_snippet(
1895 &[editor_range],
1896 snippet.clone(),
1897 window,
1898 cx,
1899 )
1900 .ok();
1901 }
1902 }
1903 }
1904 }
1905 _ => {}
1906 },
1907 ));
1908 if let Some(task_inventory) = project
1909 .read(cx)
1910 .task_store()
1911 .read(cx)
1912 .task_inventory()
1913 .cloned()
1914 {
1915 project_subscriptions.push(cx.observe_in(
1916 &task_inventory,
1917 window,
1918 |editor, _, window, cx| {
1919 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1920 },
1921 ));
1922 };
1923
1924 project_subscriptions.push(cx.subscribe_in(
1925 &project.read(cx).breakpoint_store(),
1926 window,
1927 |editor, _, event, window, cx| match event {
1928 BreakpointStoreEvent::ClearDebugLines => {
1929 editor.clear_row_highlights::<ActiveDebugLine>();
1930 editor.refresh_inline_values(cx);
1931 }
1932 BreakpointStoreEvent::SetDebugLine => {
1933 if editor.go_to_active_debug_line(window, cx) {
1934 cx.stop_propagation();
1935 }
1936
1937 editor.refresh_inline_values(cx);
1938 }
1939 _ => {}
1940 },
1941 ));
1942 let git_store = project.read(cx).git_store().clone();
1943 let project = project.clone();
1944 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1945 match event {
1946 GitStoreEvent::RepositoryUpdated(
1947 _,
1948 RepositoryEvent::Updated {
1949 new_instance: true, ..
1950 },
1951 _,
1952 ) => {
1953 this.load_diff_task = Some(
1954 update_uncommitted_diff_for_buffer(
1955 cx.entity(),
1956 &project,
1957 this.buffer.read(cx).all_buffers(),
1958 this.buffer.clone(),
1959 cx,
1960 )
1961 .shared(),
1962 );
1963 }
1964 _ => {}
1965 }
1966 }));
1967 }
1968 }
1969
1970 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1971
1972 let inlay_hint_settings =
1973 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1974 let focus_handle = cx.focus_handle();
1975 cx.on_focus(&focus_handle, window, Self::handle_focus)
1976 .detach();
1977 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1978 .detach();
1979 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1980 .detach();
1981 cx.on_blur(&focus_handle, window, Self::handle_blur)
1982 .detach();
1983 cx.observe_pending_input(window, Self::observe_pending_input)
1984 .detach();
1985
1986 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1987 Some(false)
1988 } else {
1989 None
1990 };
1991
1992 let breakpoint_store = match (&mode, project.as_ref()) {
1993 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1994 _ => None,
1995 };
1996
1997 let mut code_action_providers = Vec::new();
1998 let mut load_uncommitted_diff = None;
1999 if let Some(project) = project.clone() {
2000 load_uncommitted_diff = Some(
2001 update_uncommitted_diff_for_buffer(
2002 cx.entity(),
2003 &project,
2004 buffer.read(cx).all_buffers(),
2005 buffer.clone(),
2006 cx,
2007 )
2008 .shared(),
2009 );
2010 code_action_providers.push(Rc::new(project) as Rc<_>);
2011 }
2012
2013 let mut editor = Self {
2014 focus_handle,
2015 show_cursor_when_unfocused: false,
2016 last_focused_descendant: None,
2017 buffer: buffer.clone(),
2018 display_map: display_map.clone(),
2019 selections,
2020 scroll_manager: ScrollManager::new(cx),
2021 columnar_selection_state: None,
2022 add_selections_state: None,
2023 select_next_state: None,
2024 select_prev_state: None,
2025 selection_history: SelectionHistory::default(),
2026 defer_selection_effects: false,
2027 deferred_selection_effects_state: None,
2028 autoclose_regions: Vec::new(),
2029 snippet_stack: InvalidationStack::default(),
2030 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2031 ime_transaction: None,
2032 active_diagnostics: ActiveDiagnostic::None,
2033 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2034 inline_diagnostics_update: Task::ready(()),
2035 inline_diagnostics: Vec::new(),
2036 soft_wrap_mode_override,
2037 diagnostics_max_severity,
2038 hard_wrap: None,
2039 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2040 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2041 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2042 project,
2043 blink_manager: blink_manager.clone(),
2044 show_local_selections: true,
2045 show_scrollbars: ScrollbarAxes {
2046 horizontal: full_mode,
2047 vertical: full_mode,
2048 },
2049 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2050 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
2051 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2052 show_gutter: mode.is_full(),
2053 show_line_numbers: None,
2054 use_relative_line_numbers: None,
2055 disable_expand_excerpt_buttons: false,
2056 show_git_diff_gutter: None,
2057 show_code_actions: None,
2058 show_runnables: None,
2059 show_breakpoints: None,
2060 show_wrap_guides: None,
2061 show_indent_guides,
2062 placeholder_text: None,
2063 highlight_order: 0,
2064 highlighted_rows: HashMap::default(),
2065 background_highlights: TreeMap::default(),
2066 gutter_highlights: TreeMap::default(),
2067 scrollbar_marker_state: ScrollbarMarkerState::default(),
2068 active_indent_guides_state: ActiveIndentGuidesState::default(),
2069 nav_history: None,
2070 context_menu: RefCell::new(None),
2071 context_menu_options: None,
2072 mouse_context_menu: None,
2073 completion_tasks: Vec::new(),
2074 inline_blame_popover: None,
2075 inline_blame_popover_show_task: None,
2076 signature_help_state: SignatureHelpState::default(),
2077 auto_signature_help: None,
2078 find_all_references_task_sources: Vec::new(),
2079 next_completion_id: 0,
2080 next_inlay_id: 0,
2081 code_action_providers,
2082 available_code_actions: None,
2083 code_actions_task: None,
2084 quick_selection_highlight_task: None,
2085 debounced_selection_highlight_task: None,
2086 document_highlights_task: None,
2087 linked_editing_range_task: None,
2088 pending_rename: None,
2089 searchable: true,
2090 cursor_shape: EditorSettings::get_global(cx)
2091 .cursor_shape
2092 .unwrap_or_default(),
2093 current_line_highlight: None,
2094 autoindent_mode: Some(AutoindentMode::EachLine),
2095 collapse_matches: false,
2096 workspace: None,
2097 input_enabled: true,
2098 use_modal_editing: mode.is_full(),
2099 read_only: mode.is_minimap(),
2100 use_autoclose: true,
2101 use_auto_surround: true,
2102 auto_replace_emoji_shortcode: false,
2103 jsx_tag_auto_close_enabled_in_any_buffer: false,
2104 leader_id: None,
2105 remote_id: None,
2106 hover_state: HoverState::default(),
2107 pending_mouse_down: None,
2108 hovered_link_state: None,
2109 edit_prediction_provider: None,
2110 active_inline_completion: None,
2111 stale_inline_completion_in_menu: None,
2112 edit_prediction_preview: EditPredictionPreview::Inactive {
2113 released_too_fast: false,
2114 },
2115 inline_diagnostics_enabled: mode.is_full(),
2116 diagnostics_enabled: mode.is_full(),
2117 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2118 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2119
2120 gutter_hovered: false,
2121 pixel_position_of_newest_cursor: None,
2122 last_bounds: None,
2123 last_position_map: None,
2124 expect_bounds_change: None,
2125 gutter_dimensions: GutterDimensions::default(),
2126 style: None,
2127 show_cursor_names: false,
2128 hovered_cursors: HashMap::default(),
2129 next_editor_action_id: EditorActionId::default(),
2130 editor_actions: Rc::default(),
2131 inline_completions_hidden_for_vim_mode: false,
2132 show_inline_completions_override: None,
2133 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
2134 edit_prediction_settings: EditPredictionSettings::Disabled,
2135 edit_prediction_indent_conflict: false,
2136 edit_prediction_requires_modifier_in_indent_conflict: true,
2137 custom_context_menu: None,
2138 show_git_blame_gutter: false,
2139 show_git_blame_inline: false,
2140 show_selection_menu: None,
2141 show_git_blame_inline_delay_task: None,
2142 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2143 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2144 serialize_dirty_buffers: !mode.is_minimap()
2145 && ProjectSettings::get_global(cx)
2146 .session
2147 .restore_unsaved_buffers,
2148 blame: None,
2149 blame_subscription: None,
2150 tasks: BTreeMap::default(),
2151
2152 breakpoint_store,
2153 gutter_breakpoint_indicator: (None, None),
2154 hovered_diff_hunk_row: None,
2155 _subscriptions: vec![
2156 cx.observe(&buffer, Self::on_buffer_changed),
2157 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2158 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2159 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2160 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2161 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2162 cx.observe_window_activation(window, |editor, window, cx| {
2163 let active = window.is_window_active();
2164 editor.blink_manager.update(cx, |blink_manager, cx| {
2165 if active {
2166 blink_manager.enable(cx);
2167 } else {
2168 blink_manager.disable(cx);
2169 }
2170 });
2171 if active {
2172 editor.show_mouse_cursor(cx);
2173 }
2174 }),
2175 ],
2176 tasks_update_task: None,
2177 pull_diagnostics_task: Task::ready(()),
2178 colors: None,
2179 next_color_inlay_id: 0,
2180 linked_edit_ranges: Default::default(),
2181 in_project_search: false,
2182 previous_search_ranges: None,
2183 breadcrumb_header: None,
2184 focused_block: None,
2185 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2186 addons: HashMap::default(),
2187 registered_buffers: HashMap::default(),
2188 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2189 selection_mark_mode: false,
2190 toggle_fold_multiple_buffers: Task::ready(()),
2191 serialize_selections: Task::ready(()),
2192 serialize_folds: Task::ready(()),
2193 text_style_refinement: None,
2194 load_diff_task: load_uncommitted_diff,
2195 temporary_diff_override: false,
2196 mouse_cursor_hidden: false,
2197 minimap: None,
2198 hide_mouse_mode: EditorSettings::get_global(cx)
2199 .hide_mouse
2200 .unwrap_or_default(),
2201 change_list: ChangeList::new(),
2202 mode,
2203 selection_drag_state: SelectionDragState::None,
2204 folding_newlines: Task::ready(()),
2205 };
2206 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2207 editor
2208 ._subscriptions
2209 .push(cx.observe(breakpoints, |_, _, cx| {
2210 cx.notify();
2211 }));
2212 }
2213 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2214 editor._subscriptions.extend(project_subscriptions);
2215
2216 editor._subscriptions.push(cx.subscribe_in(
2217 &cx.entity(),
2218 window,
2219 |editor, _, e: &EditorEvent, window, cx| match e {
2220 EditorEvent::ScrollPositionChanged { local, .. } => {
2221 if *local {
2222 let new_anchor = editor.scroll_manager.anchor();
2223 let snapshot = editor.snapshot(window, cx);
2224 editor.update_restoration_data(cx, move |data| {
2225 data.scroll_position = (
2226 new_anchor.top_row(&snapshot.buffer_snapshot),
2227 new_anchor.offset,
2228 );
2229 });
2230 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2231 editor.inline_blame_popover.take();
2232 }
2233 }
2234 EditorEvent::Edited { .. } => {
2235 if !vim_enabled(cx) {
2236 let (map, selections) = editor.selections.all_adjusted_display(cx);
2237 let pop_state = editor
2238 .change_list
2239 .last()
2240 .map(|previous| {
2241 previous.len() == selections.len()
2242 && previous.iter().enumerate().all(|(ix, p)| {
2243 p.to_display_point(&map).row()
2244 == selections[ix].head().row()
2245 })
2246 })
2247 .unwrap_or(false);
2248 let new_positions = selections
2249 .into_iter()
2250 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2251 .collect();
2252 editor
2253 .change_list
2254 .push_to_change_list(pop_state, new_positions);
2255 }
2256 }
2257 _ => (),
2258 },
2259 ));
2260
2261 if let Some(dap_store) = editor
2262 .project
2263 .as_ref()
2264 .map(|project| project.read(cx).dap_store())
2265 {
2266 let weak_editor = cx.weak_entity();
2267
2268 editor
2269 ._subscriptions
2270 .push(
2271 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2272 let session_entity = cx.entity();
2273 weak_editor
2274 .update(cx, |editor, cx| {
2275 editor._subscriptions.push(
2276 cx.subscribe(&session_entity, Self::on_debug_session_event),
2277 );
2278 })
2279 .ok();
2280 }),
2281 );
2282
2283 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2284 editor
2285 ._subscriptions
2286 .push(cx.subscribe(&session, Self::on_debug_session_event));
2287 }
2288 }
2289
2290 // skip adding the initial selection to selection history
2291 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2292 editor.end_selection(window, cx);
2293 editor.selection_history.mode = SelectionHistoryMode::Normal;
2294
2295 editor.scroll_manager.show_scrollbars(window, cx);
2296 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2297
2298 if full_mode {
2299 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2300 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2301
2302 if editor.git_blame_inline_enabled {
2303 editor.start_git_blame_inline(false, window, cx);
2304 }
2305
2306 editor.go_to_active_debug_line(window, cx);
2307
2308 if let Some(buffer) = buffer.read(cx).as_singleton() {
2309 if let Some(project) = editor.project.as_ref() {
2310 let handle = project.update(cx, |project, cx| {
2311 project.register_buffer_with_language_servers(&buffer, cx)
2312 });
2313 editor
2314 .registered_buffers
2315 .insert(buffer.read(cx).remote_id(), handle);
2316 }
2317 }
2318
2319 editor.minimap =
2320 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2321 editor.colors = Some(LspColorData::new(cx));
2322 editor.update_lsp_data(false, None, window, cx);
2323 }
2324
2325 if editor.mode.is_full() {
2326 editor.report_editor_event("Editor Opened", None, cx);
2327 }
2328
2329 editor
2330 }
2331
2332 pub fn deploy_mouse_context_menu(
2333 &mut self,
2334 position: gpui::Point<Pixels>,
2335 context_menu: Entity<ContextMenu>,
2336 window: &mut Window,
2337 cx: &mut Context<Self>,
2338 ) {
2339 self.mouse_context_menu = Some(MouseContextMenu::new(
2340 self,
2341 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2342 context_menu,
2343 window,
2344 cx,
2345 ));
2346 }
2347
2348 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2349 self.mouse_context_menu
2350 .as_ref()
2351 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2352 }
2353
2354 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2355 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2356 }
2357
2358 fn key_context_internal(
2359 &self,
2360 has_active_edit_prediction: bool,
2361 window: &Window,
2362 cx: &App,
2363 ) -> KeyContext {
2364 let mut key_context = KeyContext::new_with_defaults();
2365 key_context.add("Editor");
2366 let mode = match self.mode {
2367 EditorMode::SingleLine { .. } => "single_line",
2368 EditorMode::AutoHeight { .. } => "auto_height",
2369 EditorMode::Minimap { .. } => "minimap",
2370 EditorMode::Full { .. } => "full",
2371 };
2372
2373 if EditorSettings::jupyter_enabled(cx) {
2374 key_context.add("jupyter");
2375 }
2376
2377 key_context.set("mode", mode);
2378 if self.pending_rename.is_some() {
2379 key_context.add("renaming");
2380 }
2381
2382 match self.context_menu.borrow().as_ref() {
2383 Some(CodeContextMenu::Completions(_)) => {
2384 key_context.add("menu");
2385 key_context.add("showing_completions");
2386 }
2387 Some(CodeContextMenu::CodeActions(_)) => {
2388 key_context.add("menu");
2389 key_context.add("showing_code_actions")
2390 }
2391 None => {}
2392 }
2393
2394 if self.signature_help_state.has_multiple_signatures() {
2395 key_context.add("showing_signature_help");
2396 }
2397
2398 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2399 if !self.focus_handle(cx).contains_focused(window, cx)
2400 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2401 {
2402 for addon in self.addons.values() {
2403 addon.extend_key_context(&mut key_context, cx)
2404 }
2405 }
2406
2407 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2408 if let Some(extension) = singleton_buffer
2409 .read(cx)
2410 .file()
2411 .and_then(|file| file.path().extension()?.to_str())
2412 {
2413 key_context.set("extension", extension.to_string());
2414 }
2415 } else {
2416 key_context.add("multibuffer");
2417 }
2418
2419 if has_active_edit_prediction {
2420 if self.edit_prediction_in_conflict() {
2421 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2422 } else {
2423 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2424 key_context.add("copilot_suggestion");
2425 }
2426 }
2427
2428 if self.selection_mark_mode {
2429 key_context.add("selection_mode");
2430 }
2431
2432 key_context
2433 }
2434
2435 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2436 if self.mouse_cursor_hidden {
2437 self.mouse_cursor_hidden = false;
2438 cx.notify();
2439 }
2440 }
2441
2442 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2443 let hide_mouse_cursor = match origin {
2444 HideMouseCursorOrigin::TypingAction => {
2445 matches!(
2446 self.hide_mouse_mode,
2447 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2448 )
2449 }
2450 HideMouseCursorOrigin::MovementAction => {
2451 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2452 }
2453 };
2454 if self.mouse_cursor_hidden != hide_mouse_cursor {
2455 self.mouse_cursor_hidden = hide_mouse_cursor;
2456 cx.notify();
2457 }
2458 }
2459
2460 pub fn edit_prediction_in_conflict(&self) -> bool {
2461 if !self.show_edit_predictions_in_menu() {
2462 return false;
2463 }
2464
2465 let showing_completions = self
2466 .context_menu
2467 .borrow()
2468 .as_ref()
2469 .map_or(false, |context| {
2470 matches!(context, CodeContextMenu::Completions(_))
2471 });
2472
2473 showing_completions
2474 || self.edit_prediction_requires_modifier()
2475 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2476 // bindings to insert tab characters.
2477 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2478 }
2479
2480 pub fn accept_edit_prediction_keybind(
2481 &self,
2482 accept_partial: bool,
2483 window: &Window,
2484 cx: &App,
2485 ) -> AcceptEditPredictionBinding {
2486 let key_context = self.key_context_internal(true, window, cx);
2487 let in_conflict = self.edit_prediction_in_conflict();
2488
2489 let bindings = if accept_partial {
2490 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2491 } else {
2492 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2493 };
2494
2495 // TODO: if the binding contains multiple keystrokes, display all of them, not
2496 // just the first one.
2497 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2498 !in_conflict
2499 || binding
2500 .keystrokes()
2501 .first()
2502 .map_or(false, |keystroke| keystroke.modifiers.modified())
2503 }))
2504 }
2505
2506 pub fn new_file(
2507 workspace: &mut Workspace,
2508 _: &workspace::NewFile,
2509 window: &mut Window,
2510 cx: &mut Context<Workspace>,
2511 ) {
2512 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2513 "Failed to create buffer",
2514 window,
2515 cx,
2516 |e, _, _| match e.error_code() {
2517 ErrorCode::RemoteUpgradeRequired => Some(format!(
2518 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2519 e.error_tag("required").unwrap_or("the latest version")
2520 )),
2521 _ => None,
2522 },
2523 );
2524 }
2525
2526 pub fn new_in_workspace(
2527 workspace: &mut Workspace,
2528 window: &mut Window,
2529 cx: &mut Context<Workspace>,
2530 ) -> Task<Result<Entity<Editor>>> {
2531 let project = workspace.project().clone();
2532 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2533
2534 cx.spawn_in(window, async move |workspace, cx| {
2535 let buffer = create.await?;
2536 workspace.update_in(cx, |workspace, window, cx| {
2537 let editor =
2538 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2539 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2540 editor
2541 })
2542 })
2543 }
2544
2545 fn new_file_vertical(
2546 workspace: &mut Workspace,
2547 _: &workspace::NewFileSplitVertical,
2548 window: &mut Window,
2549 cx: &mut Context<Workspace>,
2550 ) {
2551 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2552 }
2553
2554 fn new_file_horizontal(
2555 workspace: &mut Workspace,
2556 _: &workspace::NewFileSplitHorizontal,
2557 window: &mut Window,
2558 cx: &mut Context<Workspace>,
2559 ) {
2560 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2561 }
2562
2563 fn new_file_in_direction(
2564 workspace: &mut Workspace,
2565 direction: SplitDirection,
2566 window: &mut Window,
2567 cx: &mut Context<Workspace>,
2568 ) {
2569 let project = workspace.project().clone();
2570 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2571
2572 cx.spawn_in(window, async move |workspace, cx| {
2573 let buffer = create.await?;
2574 workspace.update_in(cx, move |workspace, window, cx| {
2575 workspace.split_item(
2576 direction,
2577 Box::new(
2578 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2579 ),
2580 window,
2581 cx,
2582 )
2583 })?;
2584 anyhow::Ok(())
2585 })
2586 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2587 match e.error_code() {
2588 ErrorCode::RemoteUpgradeRequired => Some(format!(
2589 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2590 e.error_tag("required").unwrap_or("the latest version")
2591 )),
2592 _ => None,
2593 }
2594 });
2595 }
2596
2597 pub fn leader_id(&self) -> Option<CollaboratorId> {
2598 self.leader_id
2599 }
2600
2601 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2602 &self.buffer
2603 }
2604
2605 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2606 self.workspace.as_ref()?.0.upgrade()
2607 }
2608
2609 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2610 self.buffer().read(cx).title(cx)
2611 }
2612
2613 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2614 let git_blame_gutter_max_author_length = self
2615 .render_git_blame_gutter(cx)
2616 .then(|| {
2617 if let Some(blame) = self.blame.as_ref() {
2618 let max_author_length =
2619 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2620 Some(max_author_length)
2621 } else {
2622 None
2623 }
2624 })
2625 .flatten();
2626
2627 EditorSnapshot {
2628 mode: self.mode.clone(),
2629 show_gutter: self.show_gutter,
2630 show_line_numbers: self.show_line_numbers,
2631 show_git_diff_gutter: self.show_git_diff_gutter,
2632 show_code_actions: self.show_code_actions,
2633 show_runnables: self.show_runnables,
2634 show_breakpoints: self.show_breakpoints,
2635 git_blame_gutter_max_author_length,
2636 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2637 scroll_anchor: self.scroll_manager.anchor(),
2638 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2639 placeholder_text: self.placeholder_text.clone(),
2640 is_focused: self.focus_handle.is_focused(window),
2641 current_line_highlight: self
2642 .current_line_highlight
2643 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2644 gutter_hovered: self.gutter_hovered,
2645 }
2646 }
2647
2648 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2649 self.buffer.read(cx).language_at(point, cx)
2650 }
2651
2652 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2653 self.buffer.read(cx).read(cx).file_at(point).cloned()
2654 }
2655
2656 pub fn active_excerpt(
2657 &self,
2658 cx: &App,
2659 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2660 self.buffer
2661 .read(cx)
2662 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2663 }
2664
2665 pub fn mode(&self) -> &EditorMode {
2666 &self.mode
2667 }
2668
2669 pub fn set_mode(&mut self, mode: EditorMode) {
2670 self.mode = mode;
2671 }
2672
2673 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2674 self.collaboration_hub.as_deref()
2675 }
2676
2677 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2678 self.collaboration_hub = Some(hub);
2679 }
2680
2681 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2682 self.in_project_search = in_project_search;
2683 }
2684
2685 pub fn set_custom_context_menu(
2686 &mut self,
2687 f: impl 'static
2688 + Fn(
2689 &mut Self,
2690 DisplayPoint,
2691 &mut Window,
2692 &mut Context<Self>,
2693 ) -> Option<Entity<ui::ContextMenu>>,
2694 ) {
2695 self.custom_context_menu = Some(Box::new(f))
2696 }
2697
2698 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2699 self.completion_provider = provider;
2700 }
2701
2702 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2703 self.semantics_provider.clone()
2704 }
2705
2706 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2707 self.semantics_provider = provider;
2708 }
2709
2710 pub fn set_edit_prediction_provider<T>(
2711 &mut self,
2712 provider: Option<Entity<T>>,
2713 window: &mut Window,
2714 cx: &mut Context<Self>,
2715 ) where
2716 T: EditPredictionProvider,
2717 {
2718 self.edit_prediction_provider =
2719 provider.map(|provider| RegisteredInlineCompletionProvider {
2720 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2721 if this.focus_handle.is_focused(window) {
2722 this.update_visible_inline_completion(window, cx);
2723 }
2724 }),
2725 provider: Arc::new(provider),
2726 });
2727 self.update_edit_prediction_settings(cx);
2728 self.refresh_inline_completion(false, false, window, cx);
2729 }
2730
2731 pub fn placeholder_text(&self) -> Option<&str> {
2732 self.placeholder_text.as_deref()
2733 }
2734
2735 pub fn set_placeholder_text(
2736 &mut self,
2737 placeholder_text: impl Into<Arc<str>>,
2738 cx: &mut Context<Self>,
2739 ) {
2740 let placeholder_text = Some(placeholder_text.into());
2741 if self.placeholder_text != placeholder_text {
2742 self.placeholder_text = placeholder_text;
2743 cx.notify();
2744 }
2745 }
2746
2747 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2748 self.cursor_shape = cursor_shape;
2749
2750 // Disrupt blink for immediate user feedback that the cursor shape has changed
2751 self.blink_manager.update(cx, BlinkManager::show_cursor);
2752
2753 cx.notify();
2754 }
2755
2756 pub fn set_current_line_highlight(
2757 &mut self,
2758 current_line_highlight: Option<CurrentLineHighlight>,
2759 ) {
2760 self.current_line_highlight = current_line_highlight;
2761 }
2762
2763 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2764 self.collapse_matches = collapse_matches;
2765 }
2766
2767 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2768 let buffers = self.buffer.read(cx).all_buffers();
2769 let Some(project) = self.project.as_ref() else {
2770 return;
2771 };
2772 project.update(cx, |project, cx| {
2773 for buffer in buffers {
2774 self.registered_buffers
2775 .entry(buffer.read(cx).remote_id())
2776 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2777 }
2778 })
2779 }
2780
2781 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2782 if self.collapse_matches {
2783 return range.start..range.start;
2784 }
2785 range.clone()
2786 }
2787
2788 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2789 if self.display_map.read(cx).clip_at_line_ends != clip {
2790 self.display_map
2791 .update(cx, |map, _| map.clip_at_line_ends = clip);
2792 }
2793 }
2794
2795 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2796 self.input_enabled = input_enabled;
2797 }
2798
2799 pub fn set_inline_completions_hidden_for_vim_mode(
2800 &mut self,
2801 hidden: bool,
2802 window: &mut Window,
2803 cx: &mut Context<Self>,
2804 ) {
2805 if hidden != self.inline_completions_hidden_for_vim_mode {
2806 self.inline_completions_hidden_for_vim_mode = hidden;
2807 if hidden {
2808 self.update_visible_inline_completion(window, cx);
2809 } else {
2810 self.refresh_inline_completion(true, false, window, cx);
2811 }
2812 }
2813 }
2814
2815 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2816 self.menu_inline_completions_policy = value;
2817 }
2818
2819 pub fn set_autoindent(&mut self, autoindent: bool) {
2820 if autoindent {
2821 self.autoindent_mode = Some(AutoindentMode::EachLine);
2822 } else {
2823 self.autoindent_mode = None;
2824 }
2825 }
2826
2827 pub fn read_only(&self, cx: &App) -> bool {
2828 self.read_only || self.buffer.read(cx).read_only()
2829 }
2830
2831 pub fn set_read_only(&mut self, read_only: bool) {
2832 self.read_only = read_only;
2833 }
2834
2835 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2836 self.use_autoclose = autoclose;
2837 }
2838
2839 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2840 self.use_auto_surround = auto_surround;
2841 }
2842
2843 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2844 self.auto_replace_emoji_shortcode = auto_replace;
2845 }
2846
2847 pub fn toggle_edit_predictions(
2848 &mut self,
2849 _: &ToggleEditPrediction,
2850 window: &mut Window,
2851 cx: &mut Context<Self>,
2852 ) {
2853 if self.show_inline_completions_override.is_some() {
2854 self.set_show_edit_predictions(None, window, cx);
2855 } else {
2856 let show_edit_predictions = !self.edit_predictions_enabled();
2857 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2858 }
2859 }
2860
2861 pub fn set_show_edit_predictions(
2862 &mut self,
2863 show_edit_predictions: Option<bool>,
2864 window: &mut Window,
2865 cx: &mut Context<Self>,
2866 ) {
2867 self.show_inline_completions_override = show_edit_predictions;
2868 self.update_edit_prediction_settings(cx);
2869
2870 if let Some(false) = show_edit_predictions {
2871 self.discard_inline_completion(false, cx);
2872 } else {
2873 self.refresh_inline_completion(false, true, window, cx);
2874 }
2875 }
2876
2877 fn inline_completions_disabled_in_scope(
2878 &self,
2879 buffer: &Entity<Buffer>,
2880 buffer_position: language::Anchor,
2881 cx: &App,
2882 ) -> bool {
2883 let snapshot = buffer.read(cx).snapshot();
2884 let settings = snapshot.settings_at(buffer_position, cx);
2885
2886 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2887 return false;
2888 };
2889
2890 scope.override_name().map_or(false, |scope_name| {
2891 settings
2892 .edit_predictions_disabled_in
2893 .iter()
2894 .any(|s| s == scope_name)
2895 })
2896 }
2897
2898 pub fn set_use_modal_editing(&mut self, to: bool) {
2899 self.use_modal_editing = to;
2900 }
2901
2902 pub fn use_modal_editing(&self) -> bool {
2903 self.use_modal_editing
2904 }
2905
2906 fn selections_did_change(
2907 &mut self,
2908 local: bool,
2909 old_cursor_position: &Anchor,
2910 effects: SelectionEffects,
2911 window: &mut Window,
2912 cx: &mut Context<Self>,
2913 ) {
2914 window.invalidate_character_coordinates();
2915
2916 // Copy selections to primary selection buffer
2917 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2918 if local {
2919 let selections = self.selections.all::<usize>(cx);
2920 let buffer_handle = self.buffer.read(cx).read(cx);
2921
2922 let mut text = String::new();
2923 for (index, selection) in selections.iter().enumerate() {
2924 let text_for_selection = buffer_handle
2925 .text_for_range(selection.start..selection.end)
2926 .collect::<String>();
2927
2928 text.push_str(&text_for_selection);
2929 if index != selections.len() - 1 {
2930 text.push('\n');
2931 }
2932 }
2933
2934 if !text.is_empty() {
2935 cx.write_to_primary(ClipboardItem::new_string(text));
2936 }
2937 }
2938
2939 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2940 self.buffer.update(cx, |buffer, cx| {
2941 buffer.set_active_selections(
2942 &self.selections.disjoint_anchors(),
2943 self.selections.line_mode,
2944 self.cursor_shape,
2945 cx,
2946 )
2947 });
2948 }
2949 let display_map = self
2950 .display_map
2951 .update(cx, |display_map, cx| display_map.snapshot(cx));
2952 let buffer = &display_map.buffer_snapshot;
2953 if self.selections.count() == 1 {
2954 self.add_selections_state = None;
2955 }
2956 self.select_next_state = None;
2957 self.select_prev_state = None;
2958 self.select_syntax_node_history.try_clear();
2959 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2960 self.snippet_stack
2961 .invalidate(&self.selections.disjoint_anchors(), buffer);
2962 self.take_rename(false, window, cx);
2963
2964 let newest_selection = self.selections.newest_anchor();
2965 let new_cursor_position = newest_selection.head();
2966 let selection_start = newest_selection.start;
2967
2968 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
2969 self.push_to_nav_history(
2970 *old_cursor_position,
2971 Some(new_cursor_position.to_point(buffer)),
2972 false,
2973 effects.nav_history == Some(true),
2974 cx,
2975 );
2976 }
2977
2978 if local {
2979 if let Some(buffer_id) = new_cursor_position.buffer_id {
2980 if !self.registered_buffers.contains_key(&buffer_id) {
2981 if let Some(project) = self.project.as_ref() {
2982 project.update(cx, |project, cx| {
2983 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2984 return;
2985 };
2986 self.registered_buffers.insert(
2987 buffer_id,
2988 project.register_buffer_with_language_servers(&buffer, cx),
2989 );
2990 })
2991 }
2992 }
2993 }
2994
2995 let mut context_menu = self.context_menu.borrow_mut();
2996 let completion_menu = match context_menu.as_ref() {
2997 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2998 Some(CodeContextMenu::CodeActions(_)) => {
2999 *context_menu = None;
3000 None
3001 }
3002 None => None,
3003 };
3004 let completion_position = completion_menu.map(|menu| menu.initial_position);
3005 drop(context_menu);
3006
3007 if effects.completions {
3008 if let Some(completion_position) = completion_position {
3009 let start_offset = selection_start.to_offset(buffer);
3010 let position_matches = start_offset == completion_position.to_offset(buffer);
3011 let continue_showing = if position_matches {
3012 if self.snippet_stack.is_empty() {
3013 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3014 } else {
3015 // Snippet choices can be shown even when the cursor is in whitespace.
3016 // Dismissing the menu with actions like backspace is handled by
3017 // invalidation regions.
3018 true
3019 }
3020 } else {
3021 false
3022 };
3023
3024 if continue_showing {
3025 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3026 } else {
3027 self.hide_context_menu(window, cx);
3028 }
3029 }
3030 }
3031
3032 hide_hover(self, cx);
3033
3034 if old_cursor_position.to_display_point(&display_map).row()
3035 != new_cursor_position.to_display_point(&display_map).row()
3036 {
3037 self.available_code_actions.take();
3038 }
3039 self.refresh_code_actions(window, cx);
3040 self.refresh_document_highlights(cx);
3041 self.refresh_selected_text_highlights(false, window, cx);
3042 refresh_matching_bracket_highlights(self, window, cx);
3043 self.update_visible_inline_completion(window, cx);
3044 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3045 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3046 self.inline_blame_popover.take();
3047 if self.git_blame_inline_enabled {
3048 self.start_inline_blame_timer(window, cx);
3049 }
3050 }
3051
3052 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3053 cx.emit(EditorEvent::SelectionsChanged { local });
3054
3055 let selections = &self.selections.disjoint;
3056 if selections.len() == 1 {
3057 cx.emit(SearchEvent::ActiveMatchChanged)
3058 }
3059 if local {
3060 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3061 let inmemory_selections = selections
3062 .iter()
3063 .map(|s| {
3064 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3065 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3066 })
3067 .collect();
3068 self.update_restoration_data(cx, |data| {
3069 data.selections = inmemory_selections;
3070 });
3071
3072 if WorkspaceSettings::get(None, cx).restore_on_startup
3073 != RestoreOnStartupBehavior::None
3074 {
3075 if let Some(workspace_id) =
3076 self.workspace.as_ref().and_then(|workspace| workspace.1)
3077 {
3078 let snapshot = self.buffer().read(cx).snapshot(cx);
3079 let selections = selections.clone();
3080 let background_executor = cx.background_executor().clone();
3081 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3082 self.serialize_selections = cx.background_spawn(async move {
3083 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3084 let db_selections = selections
3085 .iter()
3086 .map(|selection| {
3087 (
3088 selection.start.to_offset(&snapshot),
3089 selection.end.to_offset(&snapshot),
3090 )
3091 })
3092 .collect();
3093
3094 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3095 .await
3096 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3097 .log_err();
3098 });
3099 }
3100 }
3101 }
3102 }
3103
3104 cx.notify();
3105 }
3106
3107 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3108 use text::ToOffset as _;
3109 use text::ToPoint as _;
3110
3111 if self.mode.is_minimap()
3112 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3113 {
3114 return;
3115 }
3116
3117 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3118 return;
3119 };
3120
3121 let snapshot = singleton.read(cx).snapshot();
3122 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3123 let display_snapshot = display_map.snapshot(cx);
3124
3125 display_snapshot
3126 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3127 .map(|fold| {
3128 fold.range.start.text_anchor.to_point(&snapshot)
3129 ..fold.range.end.text_anchor.to_point(&snapshot)
3130 })
3131 .collect()
3132 });
3133 self.update_restoration_data(cx, |data| {
3134 data.folds = inmemory_folds;
3135 });
3136
3137 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3138 return;
3139 };
3140 let background_executor = cx.background_executor().clone();
3141 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3142 let db_folds = self.display_map.update(cx, |display_map, cx| {
3143 display_map
3144 .snapshot(cx)
3145 .folds_in_range(0..snapshot.len())
3146 .map(|fold| {
3147 (
3148 fold.range.start.text_anchor.to_offset(&snapshot),
3149 fold.range.end.text_anchor.to_offset(&snapshot),
3150 )
3151 })
3152 .collect()
3153 });
3154 self.serialize_folds = cx.background_spawn(async move {
3155 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3156 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3157 .await
3158 .with_context(|| {
3159 format!(
3160 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3161 )
3162 })
3163 .log_err();
3164 });
3165 }
3166
3167 pub fn sync_selections(
3168 &mut self,
3169 other: Entity<Editor>,
3170 cx: &mut Context<Self>,
3171 ) -> gpui::Subscription {
3172 let other_selections = other.read(cx).selections.disjoint.to_vec();
3173 self.selections.change_with(cx, |selections| {
3174 selections.select_anchors(other_selections);
3175 });
3176
3177 let other_subscription =
3178 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3179 EditorEvent::SelectionsChanged { local: true } => {
3180 let other_selections = other.read(cx).selections.disjoint.to_vec();
3181 if other_selections.is_empty() {
3182 return;
3183 }
3184 this.selections.change_with(cx, |selections| {
3185 selections.select_anchors(other_selections);
3186 });
3187 }
3188 _ => {}
3189 });
3190
3191 let this_subscription =
3192 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3193 EditorEvent::SelectionsChanged { local: true } => {
3194 let these_selections = this.selections.disjoint.to_vec();
3195 if these_selections.is_empty() {
3196 return;
3197 }
3198 other.update(cx, |other_editor, cx| {
3199 other_editor.selections.change_with(cx, |selections| {
3200 selections.select_anchors(these_selections);
3201 })
3202 });
3203 }
3204 _ => {}
3205 });
3206
3207 Subscription::join(other_subscription, this_subscription)
3208 }
3209
3210 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3211 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3212 /// effects of selection change occur at the end of the transaction.
3213 pub fn change_selections<R>(
3214 &mut self,
3215 effects: SelectionEffects,
3216 window: &mut Window,
3217 cx: &mut Context<Self>,
3218 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3219 ) -> R {
3220 if let Some(state) = &mut self.deferred_selection_effects_state {
3221 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3222 state.effects.completions = effects.completions;
3223 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3224 let (changed, result) = self.selections.change_with(cx, change);
3225 state.changed |= changed;
3226 return result;
3227 }
3228 let mut state = DeferredSelectionEffectsState {
3229 changed: false,
3230 effects,
3231 old_cursor_position: self.selections.newest_anchor().head(),
3232 history_entry: SelectionHistoryEntry {
3233 selections: self.selections.disjoint_anchors(),
3234 select_next_state: self.select_next_state.clone(),
3235 select_prev_state: self.select_prev_state.clone(),
3236 add_selections_state: self.add_selections_state.clone(),
3237 },
3238 };
3239 let (changed, result) = self.selections.change_with(cx, change);
3240 state.changed = state.changed || changed;
3241 if self.defer_selection_effects {
3242 self.deferred_selection_effects_state = Some(state);
3243 } else {
3244 self.apply_selection_effects(state, window, cx);
3245 }
3246 result
3247 }
3248
3249 /// Defers the effects of selection change, so that the effects of multiple calls to
3250 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3251 /// to selection history and the state of popovers based on selection position aren't
3252 /// erroneously updated.
3253 pub fn with_selection_effects_deferred<R>(
3254 &mut self,
3255 window: &mut Window,
3256 cx: &mut Context<Self>,
3257 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3258 ) -> R {
3259 let already_deferred = self.defer_selection_effects;
3260 self.defer_selection_effects = true;
3261 let result = update(self, window, cx);
3262 if !already_deferred {
3263 self.defer_selection_effects = false;
3264 if let Some(state) = self.deferred_selection_effects_state.take() {
3265 self.apply_selection_effects(state, window, cx);
3266 }
3267 }
3268 result
3269 }
3270
3271 fn apply_selection_effects(
3272 &mut self,
3273 state: DeferredSelectionEffectsState,
3274 window: &mut Window,
3275 cx: &mut Context<Self>,
3276 ) {
3277 if state.changed {
3278 self.selection_history.push(state.history_entry);
3279
3280 if let Some(autoscroll) = state.effects.scroll {
3281 self.request_autoscroll(autoscroll, cx);
3282 }
3283
3284 let old_cursor_position = &state.old_cursor_position;
3285
3286 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3287
3288 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3289 self.show_signature_help(&ShowSignatureHelp, window, cx);
3290 }
3291 }
3292 }
3293
3294 pub fn edit<I, S, T>(&mut self, edits: I, 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
3305 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3306 }
3307
3308 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3309 where
3310 I: IntoIterator<Item = (Range<S>, T)>,
3311 S: ToOffset,
3312 T: Into<Arc<str>>,
3313 {
3314 if self.read_only(cx) {
3315 return;
3316 }
3317
3318 self.buffer.update(cx, |buffer, cx| {
3319 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3320 });
3321 }
3322
3323 pub fn edit_with_block_indent<I, S, T>(
3324 &mut self,
3325 edits: I,
3326 original_indent_columns: Vec<Option<u32>>,
3327 cx: &mut Context<Self>,
3328 ) where
3329 I: IntoIterator<Item = (Range<S>, T)>,
3330 S: ToOffset,
3331 T: Into<Arc<str>>,
3332 {
3333 if self.read_only(cx) {
3334 return;
3335 }
3336
3337 self.buffer.update(cx, |buffer, cx| {
3338 buffer.edit(
3339 edits,
3340 Some(AutoindentMode::Block {
3341 original_indent_columns,
3342 }),
3343 cx,
3344 )
3345 });
3346 }
3347
3348 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3349 self.hide_context_menu(window, cx);
3350
3351 match phase {
3352 SelectPhase::Begin {
3353 position,
3354 add,
3355 click_count,
3356 } => self.begin_selection(position, add, click_count, window, cx),
3357 SelectPhase::BeginColumnar {
3358 position,
3359 goal_column,
3360 reset,
3361 mode,
3362 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3363 SelectPhase::Extend {
3364 position,
3365 click_count,
3366 } => self.extend_selection(position, click_count, window, cx),
3367 SelectPhase::Update {
3368 position,
3369 goal_column,
3370 scroll_delta,
3371 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3372 SelectPhase::End => self.end_selection(window, cx),
3373 }
3374 }
3375
3376 fn extend_selection(
3377 &mut self,
3378 position: DisplayPoint,
3379 click_count: usize,
3380 window: &mut Window,
3381 cx: &mut Context<Self>,
3382 ) {
3383 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3384 let tail = self.selections.newest::<usize>(cx).tail();
3385 self.begin_selection(position, false, click_count, window, cx);
3386
3387 let position = position.to_offset(&display_map, Bias::Left);
3388 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3389
3390 let mut pending_selection = self
3391 .selections
3392 .pending_anchor()
3393 .expect("extend_selection not called with pending selection");
3394 if position >= tail {
3395 pending_selection.start = tail_anchor;
3396 } else {
3397 pending_selection.end = tail_anchor;
3398 pending_selection.reversed = true;
3399 }
3400
3401 let mut pending_mode = self.selections.pending_mode().unwrap();
3402 match &mut pending_mode {
3403 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3404 _ => {}
3405 }
3406
3407 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3408 SelectionEffects::scroll(Autoscroll::fit())
3409 } else {
3410 SelectionEffects::no_scroll()
3411 };
3412
3413 self.change_selections(effects, window, cx, |s| {
3414 s.set_pending(pending_selection, pending_mode)
3415 });
3416 }
3417
3418 fn begin_selection(
3419 &mut self,
3420 position: DisplayPoint,
3421 add: bool,
3422 click_count: usize,
3423 window: &mut Window,
3424 cx: &mut Context<Self>,
3425 ) {
3426 if !self.focus_handle.is_focused(window) {
3427 self.last_focused_descendant = None;
3428 window.focus(&self.focus_handle);
3429 }
3430
3431 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3432 let buffer = &display_map.buffer_snapshot;
3433 let position = display_map.clip_point(position, Bias::Left);
3434
3435 let start;
3436 let end;
3437 let mode;
3438 let mut auto_scroll;
3439 match click_count {
3440 1 => {
3441 start = buffer.anchor_before(position.to_point(&display_map));
3442 end = start;
3443 mode = SelectMode::Character;
3444 auto_scroll = true;
3445 }
3446 2 => {
3447 let position = display_map
3448 .clip_point(position, Bias::Left)
3449 .to_offset(&display_map, Bias::Left);
3450 let (range, _) = buffer.surrounding_word(position, false);
3451 start = buffer.anchor_before(range.start);
3452 end = buffer.anchor_before(range.end);
3453 mode = SelectMode::Word(start..end);
3454 auto_scroll = true;
3455 }
3456 3 => {
3457 let position = display_map
3458 .clip_point(position, Bias::Left)
3459 .to_point(&display_map);
3460 let line_start = display_map.prev_line_boundary(position).0;
3461 let next_line_start = buffer.clip_point(
3462 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3463 Bias::Left,
3464 );
3465 start = buffer.anchor_before(line_start);
3466 end = buffer.anchor_before(next_line_start);
3467 mode = SelectMode::Line(start..end);
3468 auto_scroll = true;
3469 }
3470 _ => {
3471 start = buffer.anchor_before(0);
3472 end = buffer.anchor_before(buffer.len());
3473 mode = SelectMode::All;
3474 auto_scroll = false;
3475 }
3476 }
3477 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3478
3479 let point_to_delete: Option<usize> = {
3480 let selected_points: Vec<Selection<Point>> =
3481 self.selections.disjoint_in_range(start..end, cx);
3482
3483 if !add || click_count > 1 {
3484 None
3485 } else if !selected_points.is_empty() {
3486 Some(selected_points[0].id)
3487 } else {
3488 let clicked_point_already_selected =
3489 self.selections.disjoint.iter().find(|selection| {
3490 selection.start.to_point(buffer) == start.to_point(buffer)
3491 || selection.end.to_point(buffer) == end.to_point(buffer)
3492 });
3493
3494 clicked_point_already_selected.map(|selection| selection.id)
3495 }
3496 };
3497
3498 let selections_count = self.selections.count();
3499 let effects = if auto_scroll {
3500 SelectionEffects::default()
3501 } else {
3502 SelectionEffects::no_scroll()
3503 };
3504
3505 self.change_selections(effects, window, cx, |s| {
3506 if let Some(point_to_delete) = point_to_delete {
3507 s.delete(point_to_delete);
3508
3509 if selections_count == 1 {
3510 s.set_pending_anchor_range(start..end, mode);
3511 }
3512 } else {
3513 if !add {
3514 s.clear_disjoint();
3515 }
3516
3517 s.set_pending_anchor_range(start..end, mode);
3518 }
3519 });
3520 }
3521
3522 fn begin_columnar_selection(
3523 &mut self,
3524 position: DisplayPoint,
3525 goal_column: u32,
3526 reset: bool,
3527 mode: ColumnarMode,
3528 window: &mut Window,
3529 cx: &mut Context<Self>,
3530 ) {
3531 if !self.focus_handle.is_focused(window) {
3532 self.last_focused_descendant = None;
3533 window.focus(&self.focus_handle);
3534 }
3535
3536 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3537
3538 if reset {
3539 let pointer_position = display_map
3540 .buffer_snapshot
3541 .anchor_before(position.to_point(&display_map));
3542
3543 self.change_selections(
3544 SelectionEffects::scroll(Autoscroll::newest()),
3545 window,
3546 cx,
3547 |s| {
3548 s.clear_disjoint();
3549 s.set_pending_anchor_range(
3550 pointer_position..pointer_position,
3551 SelectMode::Character,
3552 );
3553 },
3554 );
3555 };
3556
3557 let tail = self.selections.newest::<Point>(cx).tail();
3558 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3559 self.columnar_selection_state = match mode {
3560 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3561 selection_tail: selection_anchor,
3562 display_point: if reset {
3563 if position.column() != goal_column {
3564 Some(DisplayPoint::new(position.row(), goal_column))
3565 } else {
3566 None
3567 }
3568 } else {
3569 None
3570 },
3571 }),
3572 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3573 selection_tail: selection_anchor,
3574 }),
3575 };
3576
3577 if !reset {
3578 self.select_columns(position, goal_column, &display_map, window, cx);
3579 }
3580 }
3581
3582 fn update_selection(
3583 &mut self,
3584 position: DisplayPoint,
3585 goal_column: u32,
3586 scroll_delta: gpui::Point<f32>,
3587 window: &mut Window,
3588 cx: &mut Context<Self>,
3589 ) {
3590 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3591
3592 if self.columnar_selection_state.is_some() {
3593 self.select_columns(position, goal_column, &display_map, window, cx);
3594 } else if let Some(mut pending) = self.selections.pending_anchor() {
3595 let buffer = &display_map.buffer_snapshot;
3596 let head;
3597 let tail;
3598 let mode = self.selections.pending_mode().unwrap();
3599 match &mode {
3600 SelectMode::Character => {
3601 head = position.to_point(&display_map);
3602 tail = pending.tail().to_point(buffer);
3603 }
3604 SelectMode::Word(original_range) => {
3605 let offset = display_map
3606 .clip_point(position, Bias::Left)
3607 .to_offset(&display_map, Bias::Left);
3608 let original_range = original_range.to_offset(buffer);
3609
3610 let head_offset = if buffer.is_inside_word(offset, false)
3611 || original_range.contains(&offset)
3612 {
3613 let (word_range, _) = buffer.surrounding_word(offset, false);
3614 if word_range.start < original_range.start {
3615 word_range.start
3616 } else {
3617 word_range.end
3618 }
3619 } else {
3620 offset
3621 };
3622
3623 head = head_offset.to_point(buffer);
3624 if head_offset <= original_range.start {
3625 tail = original_range.end.to_point(buffer);
3626 } else {
3627 tail = original_range.start.to_point(buffer);
3628 }
3629 }
3630 SelectMode::Line(original_range) => {
3631 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3632
3633 let position = display_map
3634 .clip_point(position, Bias::Left)
3635 .to_point(&display_map);
3636 let line_start = display_map.prev_line_boundary(position).0;
3637 let next_line_start = buffer.clip_point(
3638 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3639 Bias::Left,
3640 );
3641
3642 if line_start < original_range.start {
3643 head = line_start
3644 } else {
3645 head = next_line_start
3646 }
3647
3648 if head <= original_range.start {
3649 tail = original_range.end;
3650 } else {
3651 tail = original_range.start;
3652 }
3653 }
3654 SelectMode::All => {
3655 return;
3656 }
3657 };
3658
3659 if head < tail {
3660 pending.start = buffer.anchor_before(head);
3661 pending.end = buffer.anchor_before(tail);
3662 pending.reversed = true;
3663 } else {
3664 pending.start = buffer.anchor_before(tail);
3665 pending.end = buffer.anchor_before(head);
3666 pending.reversed = false;
3667 }
3668
3669 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3670 s.set_pending(pending, mode);
3671 });
3672 } else {
3673 log::error!("update_selection dispatched with no pending selection");
3674 return;
3675 }
3676
3677 self.apply_scroll_delta(scroll_delta, window, cx);
3678 cx.notify();
3679 }
3680
3681 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3682 self.columnar_selection_state.take();
3683 if self.selections.pending_anchor().is_some() {
3684 let selections = self.selections.all::<usize>(cx);
3685 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3686 s.select(selections);
3687 s.clear_pending();
3688 });
3689 }
3690 }
3691
3692 fn select_columns(
3693 &mut self,
3694 head: DisplayPoint,
3695 goal_column: u32,
3696 display_map: &DisplaySnapshot,
3697 window: &mut Window,
3698 cx: &mut Context<Self>,
3699 ) {
3700 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3701 return;
3702 };
3703
3704 let tail = match columnar_state {
3705 ColumnarSelectionState::FromMouse {
3706 selection_tail,
3707 display_point,
3708 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3709 ColumnarSelectionState::FromSelection { selection_tail } => {
3710 selection_tail.to_display_point(&display_map)
3711 }
3712 };
3713
3714 let start_row = cmp::min(tail.row(), head.row());
3715 let end_row = cmp::max(tail.row(), head.row());
3716 let start_column = cmp::min(tail.column(), goal_column);
3717 let end_column = cmp::max(tail.column(), goal_column);
3718 let reversed = start_column < tail.column();
3719
3720 let selection_ranges = (start_row.0..=end_row.0)
3721 .map(DisplayRow)
3722 .filter_map(|row| {
3723 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3724 || start_column <= display_map.line_len(row))
3725 && !display_map.is_block_line(row)
3726 {
3727 let start = display_map
3728 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3729 .to_point(display_map);
3730 let end = display_map
3731 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3732 .to_point(display_map);
3733 if reversed {
3734 Some(end..start)
3735 } else {
3736 Some(start..end)
3737 }
3738 } else {
3739 None
3740 }
3741 })
3742 .collect::<Vec<_>>();
3743
3744 let ranges = match columnar_state {
3745 ColumnarSelectionState::FromMouse { .. } => {
3746 let mut non_empty_ranges = selection_ranges
3747 .iter()
3748 .filter(|selection_range| selection_range.start != selection_range.end)
3749 .peekable();
3750 if non_empty_ranges.peek().is_some() {
3751 non_empty_ranges.cloned().collect()
3752 } else {
3753 selection_ranges
3754 }
3755 }
3756 _ => selection_ranges,
3757 };
3758
3759 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3760 s.select_ranges(ranges);
3761 });
3762 cx.notify();
3763 }
3764
3765 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3766 self.selections
3767 .all_adjusted(cx)
3768 .iter()
3769 .any(|selection| !selection.is_empty())
3770 }
3771
3772 pub fn has_pending_nonempty_selection(&self) -> bool {
3773 let pending_nonempty_selection = match self.selections.pending_anchor() {
3774 Some(Selection { start, end, .. }) => start != end,
3775 None => false,
3776 };
3777
3778 pending_nonempty_selection
3779 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3780 }
3781
3782 pub fn has_pending_selection(&self) -> bool {
3783 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3784 }
3785
3786 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3787 self.selection_mark_mode = false;
3788 self.selection_drag_state = SelectionDragState::None;
3789
3790 if self.clear_expanded_diff_hunks(cx) {
3791 cx.notify();
3792 return;
3793 }
3794 if self.dismiss_menus_and_popups(true, window, cx) {
3795 return;
3796 }
3797
3798 if self.mode.is_full()
3799 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3800 {
3801 return;
3802 }
3803
3804 cx.propagate();
3805 }
3806
3807 pub fn dismiss_menus_and_popups(
3808 &mut self,
3809 is_user_requested: bool,
3810 window: &mut Window,
3811 cx: &mut Context<Self>,
3812 ) -> bool {
3813 if self.take_rename(false, window, cx).is_some() {
3814 return true;
3815 }
3816
3817 if hide_hover(self, cx) {
3818 return true;
3819 }
3820
3821 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3822 return true;
3823 }
3824
3825 if self.hide_context_menu(window, cx).is_some() {
3826 return true;
3827 }
3828
3829 if self.mouse_context_menu.take().is_some() {
3830 return true;
3831 }
3832
3833 if is_user_requested && self.discard_inline_completion(true, cx) {
3834 return true;
3835 }
3836
3837 if self.snippet_stack.pop().is_some() {
3838 return true;
3839 }
3840
3841 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3842 self.dismiss_diagnostics(cx);
3843 return true;
3844 }
3845
3846 false
3847 }
3848
3849 fn linked_editing_ranges_for(
3850 &self,
3851 selection: Range<text::Anchor>,
3852 cx: &App,
3853 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3854 if self.linked_edit_ranges.is_empty() {
3855 return None;
3856 }
3857 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3858 selection.end.buffer_id.and_then(|end_buffer_id| {
3859 if selection.start.buffer_id != Some(end_buffer_id) {
3860 return None;
3861 }
3862 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3863 let snapshot = buffer.read(cx).snapshot();
3864 self.linked_edit_ranges
3865 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3866 .map(|ranges| (ranges, snapshot, buffer))
3867 })?;
3868 use text::ToOffset as TO;
3869 // find offset from the start of current range to current cursor position
3870 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3871
3872 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3873 let start_difference = start_offset - start_byte_offset;
3874 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3875 let end_difference = end_offset - start_byte_offset;
3876 // Current range has associated linked ranges.
3877 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3878 for range in linked_ranges.iter() {
3879 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3880 let end_offset = start_offset + end_difference;
3881 let start_offset = start_offset + start_difference;
3882 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3883 continue;
3884 }
3885 if self.selections.disjoint_anchor_ranges().any(|s| {
3886 if s.start.buffer_id != selection.start.buffer_id
3887 || s.end.buffer_id != selection.end.buffer_id
3888 {
3889 return false;
3890 }
3891 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3892 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3893 }) {
3894 continue;
3895 }
3896 let start = buffer_snapshot.anchor_after(start_offset);
3897 let end = buffer_snapshot.anchor_after(end_offset);
3898 linked_edits
3899 .entry(buffer.clone())
3900 .or_default()
3901 .push(start..end);
3902 }
3903 Some(linked_edits)
3904 }
3905
3906 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3907 let text: Arc<str> = text.into();
3908
3909 if self.read_only(cx) {
3910 return;
3911 }
3912
3913 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3914
3915 let selections = self.selections.all_adjusted(cx);
3916 let mut bracket_inserted = false;
3917 let mut edits = Vec::new();
3918 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3919 let mut new_selections = Vec::with_capacity(selections.len());
3920 let mut new_autoclose_regions = Vec::new();
3921 let snapshot = self.buffer.read(cx).read(cx);
3922 let mut clear_linked_edit_ranges = false;
3923
3924 for (selection, autoclose_region) in
3925 self.selections_with_autoclose_regions(selections, &snapshot)
3926 {
3927 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3928 // Determine if the inserted text matches the opening or closing
3929 // bracket of any of this language's bracket pairs.
3930 let mut bracket_pair = None;
3931 let mut is_bracket_pair_start = false;
3932 let mut is_bracket_pair_end = false;
3933 if !text.is_empty() {
3934 let mut bracket_pair_matching_end = None;
3935 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3936 // and they are removing the character that triggered IME popup.
3937 for (pair, enabled) in scope.brackets() {
3938 if !pair.close && !pair.surround {
3939 continue;
3940 }
3941
3942 if enabled && pair.start.ends_with(text.as_ref()) {
3943 let prefix_len = pair.start.len() - text.len();
3944 let preceding_text_matches_prefix = prefix_len == 0
3945 || (selection.start.column >= (prefix_len as u32)
3946 && snapshot.contains_str_at(
3947 Point::new(
3948 selection.start.row,
3949 selection.start.column - (prefix_len as u32),
3950 ),
3951 &pair.start[..prefix_len],
3952 ));
3953 if preceding_text_matches_prefix {
3954 bracket_pair = Some(pair.clone());
3955 is_bracket_pair_start = true;
3956 break;
3957 }
3958 }
3959 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3960 {
3961 // take first bracket pair matching end, but don't break in case a later bracket
3962 // pair matches start
3963 bracket_pair_matching_end = Some(pair.clone());
3964 }
3965 }
3966 if let Some(end) = bracket_pair_matching_end
3967 && bracket_pair.is_none()
3968 {
3969 bracket_pair = Some(end);
3970 is_bracket_pair_end = true;
3971 }
3972 }
3973
3974 if let Some(bracket_pair) = bracket_pair {
3975 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3976 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3977 let auto_surround =
3978 self.use_auto_surround && snapshot_settings.use_auto_surround;
3979 if selection.is_empty() {
3980 if is_bracket_pair_start {
3981 // If the inserted text is a suffix of an opening bracket and the
3982 // selection is preceded by the rest of the opening bracket, then
3983 // insert the closing bracket.
3984 let following_text_allows_autoclose = snapshot
3985 .chars_at(selection.start)
3986 .next()
3987 .map_or(true, |c| scope.should_autoclose_before(c));
3988
3989 let preceding_text_allows_autoclose = selection.start.column == 0
3990 || snapshot.reversed_chars_at(selection.start).next().map_or(
3991 true,
3992 |c| {
3993 bracket_pair.start != bracket_pair.end
3994 || !snapshot
3995 .char_classifier_at(selection.start)
3996 .is_word(c)
3997 },
3998 );
3999
4000 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4001 && bracket_pair.start.len() == 1
4002 {
4003 let target = bracket_pair.start.chars().next().unwrap();
4004 let current_line_count = snapshot
4005 .reversed_chars_at(selection.start)
4006 .take_while(|&c| c != '\n')
4007 .filter(|&c| c == target)
4008 .count();
4009 current_line_count % 2 == 1
4010 } else {
4011 false
4012 };
4013
4014 if autoclose
4015 && bracket_pair.close
4016 && following_text_allows_autoclose
4017 && preceding_text_allows_autoclose
4018 && !is_closing_quote
4019 {
4020 let anchor = snapshot.anchor_before(selection.end);
4021 new_selections.push((selection.map(|_| anchor), text.len()));
4022 new_autoclose_regions.push((
4023 anchor,
4024 text.len(),
4025 selection.id,
4026 bracket_pair.clone(),
4027 ));
4028 edits.push((
4029 selection.range(),
4030 format!("{}{}", text, bracket_pair.end).into(),
4031 ));
4032 bracket_inserted = true;
4033 continue;
4034 }
4035 }
4036
4037 if let Some(region) = autoclose_region {
4038 // If the selection is followed by an auto-inserted closing bracket,
4039 // then don't insert that closing bracket again; just move the selection
4040 // past the closing bracket.
4041 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4042 && text.as_ref() == region.pair.end.as_str();
4043 if should_skip {
4044 let anchor = snapshot.anchor_after(selection.end);
4045 new_selections
4046 .push((selection.map(|_| anchor), region.pair.end.len()));
4047 continue;
4048 }
4049 }
4050
4051 let always_treat_brackets_as_autoclosed = snapshot
4052 .language_settings_at(selection.start, cx)
4053 .always_treat_brackets_as_autoclosed;
4054 if always_treat_brackets_as_autoclosed
4055 && is_bracket_pair_end
4056 && snapshot.contains_str_at(selection.end, text.as_ref())
4057 {
4058 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4059 // and the inserted text is a closing bracket and the selection is followed
4060 // by the closing bracket then move the selection past the closing bracket.
4061 let anchor = snapshot.anchor_after(selection.end);
4062 new_selections.push((selection.map(|_| anchor), text.len()));
4063 continue;
4064 }
4065 }
4066 // If an opening bracket is 1 character long and is typed while
4067 // text is selected, then surround that text with the bracket pair.
4068 else if auto_surround
4069 && bracket_pair.surround
4070 && is_bracket_pair_start
4071 && bracket_pair.start.chars().count() == 1
4072 {
4073 edits.push((selection.start..selection.start, text.clone()));
4074 edits.push((
4075 selection.end..selection.end,
4076 bracket_pair.end.as_str().into(),
4077 ));
4078 bracket_inserted = true;
4079 new_selections.push((
4080 Selection {
4081 id: selection.id,
4082 start: snapshot.anchor_after(selection.start),
4083 end: snapshot.anchor_before(selection.end),
4084 reversed: selection.reversed,
4085 goal: selection.goal,
4086 },
4087 0,
4088 ));
4089 continue;
4090 }
4091 }
4092 }
4093
4094 if self.auto_replace_emoji_shortcode
4095 && selection.is_empty()
4096 && text.as_ref().ends_with(':')
4097 {
4098 if let Some(possible_emoji_short_code) =
4099 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4100 {
4101 if !possible_emoji_short_code.is_empty() {
4102 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
4103 let emoji_shortcode_start = Point::new(
4104 selection.start.row,
4105 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4106 );
4107
4108 // Remove shortcode from buffer
4109 edits.push((
4110 emoji_shortcode_start..selection.start,
4111 "".to_string().into(),
4112 ));
4113 new_selections.push((
4114 Selection {
4115 id: selection.id,
4116 start: snapshot.anchor_after(emoji_shortcode_start),
4117 end: snapshot.anchor_before(selection.start),
4118 reversed: selection.reversed,
4119 goal: selection.goal,
4120 },
4121 0,
4122 ));
4123
4124 // Insert emoji
4125 let selection_start_anchor = snapshot.anchor_after(selection.start);
4126 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4127 edits.push((selection.start..selection.end, emoji.to_string().into()));
4128
4129 continue;
4130 }
4131 }
4132 }
4133 }
4134
4135 // If not handling any auto-close operation, then just replace the selected
4136 // text with the given input and move the selection to the end of the
4137 // newly inserted text.
4138 let anchor = snapshot.anchor_after(selection.end);
4139 if !self.linked_edit_ranges.is_empty() {
4140 let start_anchor = snapshot.anchor_before(selection.start);
4141
4142 let is_word_char = text.chars().next().map_or(true, |char| {
4143 let classifier = snapshot
4144 .char_classifier_at(start_anchor.to_offset(&snapshot))
4145 .ignore_punctuation(true);
4146 classifier.is_word(char)
4147 });
4148
4149 if is_word_char {
4150 if let Some(ranges) = self
4151 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4152 {
4153 for (buffer, edits) in ranges {
4154 linked_edits
4155 .entry(buffer.clone())
4156 .or_default()
4157 .extend(edits.into_iter().map(|range| (range, text.clone())));
4158 }
4159 }
4160 } else {
4161 clear_linked_edit_ranges = true;
4162 }
4163 }
4164
4165 new_selections.push((selection.map(|_| anchor), 0));
4166 edits.push((selection.start..selection.end, text.clone()));
4167 }
4168
4169 drop(snapshot);
4170
4171 self.transact(window, cx, |this, window, cx| {
4172 if clear_linked_edit_ranges {
4173 this.linked_edit_ranges.clear();
4174 }
4175 let initial_buffer_versions =
4176 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4177
4178 this.buffer.update(cx, |buffer, cx| {
4179 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4180 });
4181 for (buffer, edits) in linked_edits {
4182 buffer.update(cx, |buffer, cx| {
4183 let snapshot = buffer.snapshot();
4184 let edits = edits
4185 .into_iter()
4186 .map(|(range, text)| {
4187 use text::ToPoint as TP;
4188 let end_point = TP::to_point(&range.end, &snapshot);
4189 let start_point = TP::to_point(&range.start, &snapshot);
4190 (start_point..end_point, text)
4191 })
4192 .sorted_by_key(|(range, _)| range.start);
4193 buffer.edit(edits, None, cx);
4194 })
4195 }
4196 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4197 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4198 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4199 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4200 .zip(new_selection_deltas)
4201 .map(|(selection, delta)| Selection {
4202 id: selection.id,
4203 start: selection.start + delta,
4204 end: selection.end + delta,
4205 reversed: selection.reversed,
4206 goal: SelectionGoal::None,
4207 })
4208 .collect::<Vec<_>>();
4209
4210 let mut i = 0;
4211 for (position, delta, selection_id, pair) in new_autoclose_regions {
4212 let position = position.to_offset(&map.buffer_snapshot) + delta;
4213 let start = map.buffer_snapshot.anchor_before(position);
4214 let end = map.buffer_snapshot.anchor_after(position);
4215 while let Some(existing_state) = this.autoclose_regions.get(i) {
4216 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4217 Ordering::Less => i += 1,
4218 Ordering::Greater => break,
4219 Ordering::Equal => {
4220 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4221 Ordering::Less => i += 1,
4222 Ordering::Equal => break,
4223 Ordering::Greater => break,
4224 }
4225 }
4226 }
4227 }
4228 this.autoclose_regions.insert(
4229 i,
4230 AutocloseRegion {
4231 selection_id,
4232 range: start..end,
4233 pair,
4234 },
4235 );
4236 }
4237
4238 let had_active_inline_completion = this.has_active_inline_completion();
4239 this.change_selections(
4240 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4241 window,
4242 cx,
4243 |s| s.select(new_selections),
4244 );
4245
4246 if !bracket_inserted {
4247 if let Some(on_type_format_task) =
4248 this.trigger_on_type_formatting(text.to_string(), window, cx)
4249 {
4250 on_type_format_task.detach_and_log_err(cx);
4251 }
4252 }
4253
4254 let editor_settings = EditorSettings::get_global(cx);
4255 if bracket_inserted
4256 && (editor_settings.auto_signature_help
4257 || editor_settings.show_signature_help_after_edits)
4258 {
4259 this.show_signature_help(&ShowSignatureHelp, window, cx);
4260 }
4261
4262 let trigger_in_words =
4263 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4264 if this.hard_wrap.is_some() {
4265 let latest: Range<Point> = this.selections.newest(cx).range();
4266 if latest.is_empty()
4267 && this
4268 .buffer()
4269 .read(cx)
4270 .snapshot(cx)
4271 .line_len(MultiBufferRow(latest.start.row))
4272 == latest.start.column
4273 {
4274 this.rewrap_impl(
4275 RewrapOptions {
4276 override_language_settings: true,
4277 preserve_existing_whitespace: true,
4278 },
4279 cx,
4280 )
4281 }
4282 }
4283 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4284 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4285 this.refresh_inline_completion(true, false, window, cx);
4286 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4287 });
4288 }
4289
4290 fn find_possible_emoji_shortcode_at_position(
4291 snapshot: &MultiBufferSnapshot,
4292 position: Point,
4293 ) -> Option<String> {
4294 let mut chars = Vec::new();
4295 let mut found_colon = false;
4296 for char in snapshot.reversed_chars_at(position).take(100) {
4297 // Found a possible emoji shortcode in the middle of the buffer
4298 if found_colon {
4299 if char.is_whitespace() {
4300 chars.reverse();
4301 return Some(chars.iter().collect());
4302 }
4303 // If the previous character is not a whitespace, we are in the middle of a word
4304 // and we only want to complete the shortcode if the word is made up of other emojis
4305 let mut containing_word = String::new();
4306 for ch in snapshot
4307 .reversed_chars_at(position)
4308 .skip(chars.len() + 1)
4309 .take(100)
4310 {
4311 if ch.is_whitespace() {
4312 break;
4313 }
4314 containing_word.push(ch);
4315 }
4316 let containing_word = containing_word.chars().rev().collect::<String>();
4317 if util::word_consists_of_emojis(containing_word.as_str()) {
4318 chars.reverse();
4319 return Some(chars.iter().collect());
4320 }
4321 }
4322
4323 if char.is_whitespace() || !char.is_ascii() {
4324 return None;
4325 }
4326 if char == ':' {
4327 found_colon = true;
4328 } else {
4329 chars.push(char);
4330 }
4331 }
4332 // Found a possible emoji shortcode at the beginning of the buffer
4333 chars.reverse();
4334 Some(chars.iter().collect())
4335 }
4336
4337 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4338 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4339 self.transact(window, cx, |this, window, cx| {
4340 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4341 let selections = this.selections.all::<usize>(cx);
4342 let multi_buffer = this.buffer.read(cx);
4343 let buffer = multi_buffer.snapshot(cx);
4344 selections
4345 .iter()
4346 .map(|selection| {
4347 let start_point = selection.start.to_point(&buffer);
4348 let mut existing_indent =
4349 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4350 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4351 let start = selection.start;
4352 let end = selection.end;
4353 let selection_is_empty = start == end;
4354 let language_scope = buffer.language_scope_at(start);
4355 let (
4356 comment_delimiter,
4357 doc_delimiter,
4358 insert_extra_newline,
4359 indent_on_newline,
4360 indent_on_extra_newline,
4361 ) = if let Some(language) = &language_scope {
4362 let mut insert_extra_newline =
4363 insert_extra_newline_brackets(&buffer, start..end, language)
4364 || insert_extra_newline_tree_sitter(&buffer, start..end);
4365
4366 // Comment extension on newline is allowed only for cursor selections
4367 let comment_delimiter = maybe!({
4368 if !selection_is_empty {
4369 return None;
4370 }
4371
4372 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4373 return None;
4374 }
4375
4376 let delimiters = language.line_comment_prefixes();
4377 let max_len_of_delimiter =
4378 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4379 let (snapshot, range) =
4380 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4381
4382 let num_of_whitespaces = snapshot
4383 .chars_for_range(range.clone())
4384 .take_while(|c| c.is_whitespace())
4385 .count();
4386 let comment_candidate = snapshot
4387 .chars_for_range(range.clone())
4388 .skip(num_of_whitespaces)
4389 .take(max_len_of_delimiter)
4390 .collect::<String>();
4391 let (delimiter, trimmed_len) = delimiters
4392 .iter()
4393 .filter_map(|delimiter| {
4394 let prefix = delimiter.trim_end();
4395 if comment_candidate.starts_with(prefix) {
4396 Some((delimiter, prefix.len()))
4397 } else {
4398 None
4399 }
4400 })
4401 .max_by_key(|(_, len)| *len)?;
4402
4403 if let Some((block_start, _)) = language.block_comment_delimiters()
4404 {
4405 let block_start_trimmed = block_start.trim_end();
4406 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4407 let line_content = snapshot
4408 .chars_for_range(range)
4409 .skip(num_of_whitespaces)
4410 .take(block_start_trimmed.len())
4411 .collect::<String>();
4412
4413 if line_content.starts_with(block_start_trimmed) {
4414 return None;
4415 }
4416 }
4417 }
4418
4419 let cursor_is_placed_after_comment_marker =
4420 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4421 if cursor_is_placed_after_comment_marker {
4422 Some(delimiter.clone())
4423 } else {
4424 None
4425 }
4426 });
4427
4428 let mut indent_on_newline = IndentSize::spaces(0);
4429 let mut indent_on_extra_newline = IndentSize::spaces(0);
4430
4431 let doc_delimiter = maybe!({
4432 if !selection_is_empty {
4433 return None;
4434 }
4435
4436 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4437 return None;
4438 }
4439
4440 let DocumentationConfig {
4441 start: start_tag,
4442 end: end_tag,
4443 prefix: delimiter,
4444 tab_size: len,
4445 } = language.documentation()?;
4446
4447 let is_within_block_comment = buffer
4448 .language_scope_at(start_point)
4449 .is_some_and(|scope| scope.override_name() == Some("comment"));
4450 if !is_within_block_comment {
4451 return None;
4452 }
4453
4454 let (snapshot, range) =
4455 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4456
4457 let num_of_whitespaces = snapshot
4458 .chars_for_range(range.clone())
4459 .take_while(|c| c.is_whitespace())
4460 .count();
4461
4462 // 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.
4463 let column = start_point.column;
4464 let cursor_is_after_start_tag = {
4465 let start_tag_len = start_tag.len();
4466 let start_tag_line = snapshot
4467 .chars_for_range(range.clone())
4468 .skip(num_of_whitespaces)
4469 .take(start_tag_len)
4470 .collect::<String>();
4471 if start_tag_line.starts_with(start_tag.as_ref()) {
4472 num_of_whitespaces + start_tag_len <= column as usize
4473 } else {
4474 false
4475 }
4476 };
4477
4478 let cursor_is_after_delimiter = {
4479 let delimiter_trim = delimiter.trim_end();
4480 let delimiter_line = snapshot
4481 .chars_for_range(range.clone())
4482 .skip(num_of_whitespaces)
4483 .take(delimiter_trim.len())
4484 .collect::<String>();
4485 if delimiter_line.starts_with(delimiter_trim) {
4486 num_of_whitespaces + delimiter_trim.len() <= column as usize
4487 } else {
4488 false
4489 }
4490 };
4491
4492 let cursor_is_before_end_tag_if_exists = {
4493 let mut char_position = 0u32;
4494 let mut end_tag_offset = None;
4495
4496 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4497 if let Some(byte_pos) = chunk.find(&**end_tag) {
4498 let chars_before_match =
4499 chunk[..byte_pos].chars().count() as u32;
4500 end_tag_offset =
4501 Some(char_position + chars_before_match);
4502 break 'outer;
4503 }
4504 char_position += chunk.chars().count() as u32;
4505 }
4506
4507 if let Some(end_tag_offset) = end_tag_offset {
4508 let cursor_is_before_end_tag = column <= end_tag_offset;
4509 if cursor_is_after_start_tag {
4510 if cursor_is_before_end_tag {
4511 insert_extra_newline = true;
4512 }
4513 let cursor_is_at_start_of_end_tag =
4514 column == end_tag_offset;
4515 if cursor_is_at_start_of_end_tag {
4516 indent_on_extra_newline.len = (*len).into();
4517 }
4518 }
4519 cursor_is_before_end_tag
4520 } else {
4521 true
4522 }
4523 };
4524
4525 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4526 && cursor_is_before_end_tag_if_exists
4527 {
4528 if cursor_is_after_start_tag {
4529 indent_on_newline.len = (*len).into();
4530 }
4531 Some(delimiter.clone())
4532 } else {
4533 None
4534 }
4535 });
4536
4537 (
4538 comment_delimiter,
4539 doc_delimiter,
4540 insert_extra_newline,
4541 indent_on_newline,
4542 indent_on_extra_newline,
4543 )
4544 } else {
4545 (
4546 None,
4547 None,
4548 false,
4549 IndentSize::default(),
4550 IndentSize::default(),
4551 )
4552 };
4553
4554 let prevent_auto_indent = doc_delimiter.is_some();
4555 let delimiter = comment_delimiter.or(doc_delimiter);
4556
4557 let capacity_for_delimiter =
4558 delimiter.as_deref().map(str::len).unwrap_or_default();
4559 let mut new_text = String::with_capacity(
4560 1 + capacity_for_delimiter
4561 + existing_indent.len as usize
4562 + indent_on_newline.len as usize
4563 + indent_on_extra_newline.len as usize,
4564 );
4565 new_text.push('\n');
4566 new_text.extend(existing_indent.chars());
4567 new_text.extend(indent_on_newline.chars());
4568
4569 if let Some(delimiter) = &delimiter {
4570 new_text.push_str(delimiter);
4571 }
4572
4573 if insert_extra_newline {
4574 new_text.push('\n');
4575 new_text.extend(existing_indent.chars());
4576 new_text.extend(indent_on_extra_newline.chars());
4577 }
4578
4579 let anchor = buffer.anchor_after(end);
4580 let new_selection = selection.map(|_| anchor);
4581 (
4582 ((start..end, new_text), prevent_auto_indent),
4583 (insert_extra_newline, new_selection),
4584 )
4585 })
4586 .unzip()
4587 };
4588
4589 let mut auto_indent_edits = Vec::new();
4590 let mut edits = Vec::new();
4591 for (edit, prevent_auto_indent) in edits_with_flags {
4592 if prevent_auto_indent {
4593 edits.push(edit);
4594 } else {
4595 auto_indent_edits.push(edit);
4596 }
4597 }
4598 if !edits.is_empty() {
4599 this.edit(edits, cx);
4600 }
4601 if !auto_indent_edits.is_empty() {
4602 this.edit_with_autoindent(auto_indent_edits, cx);
4603 }
4604
4605 let buffer = this.buffer.read(cx).snapshot(cx);
4606 let new_selections = selection_info
4607 .into_iter()
4608 .map(|(extra_newline_inserted, new_selection)| {
4609 let mut cursor = new_selection.end.to_point(&buffer);
4610 if extra_newline_inserted {
4611 cursor.row -= 1;
4612 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4613 }
4614 new_selection.map(|_| cursor)
4615 })
4616 .collect();
4617
4618 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4619 this.refresh_inline_completion(true, false, window, cx);
4620 });
4621 }
4622
4623 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4624 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4625
4626 let buffer = self.buffer.read(cx);
4627 let snapshot = buffer.snapshot(cx);
4628
4629 let mut edits = Vec::new();
4630 let mut rows = Vec::new();
4631
4632 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4633 let cursor = selection.head();
4634 let row = cursor.row;
4635
4636 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4637
4638 let newline = "\n".to_string();
4639 edits.push((start_of_line..start_of_line, newline));
4640
4641 rows.push(row + rows_inserted as u32);
4642 }
4643
4644 self.transact(window, cx, |editor, window, cx| {
4645 editor.edit(edits, cx);
4646
4647 editor.change_selections(Default::default(), window, cx, |s| {
4648 let mut index = 0;
4649 s.move_cursors_with(|map, _, _| {
4650 let row = rows[index];
4651 index += 1;
4652
4653 let point = Point::new(row, 0);
4654 let boundary = map.next_line_boundary(point).1;
4655 let clipped = map.clip_point(boundary, Bias::Left);
4656
4657 (clipped, SelectionGoal::None)
4658 });
4659 });
4660
4661 let mut indent_edits = Vec::new();
4662 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4663 for row in rows {
4664 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4665 for (row, indent) in indents {
4666 if indent.len == 0 {
4667 continue;
4668 }
4669
4670 let text = match indent.kind {
4671 IndentKind::Space => " ".repeat(indent.len as usize),
4672 IndentKind::Tab => "\t".repeat(indent.len as usize),
4673 };
4674 let point = Point::new(row.0, 0);
4675 indent_edits.push((point..point, text));
4676 }
4677 }
4678 editor.edit(indent_edits, cx);
4679 });
4680 }
4681
4682 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4683 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4684
4685 let buffer = self.buffer.read(cx);
4686 let snapshot = buffer.snapshot(cx);
4687
4688 let mut edits = Vec::new();
4689 let mut rows = Vec::new();
4690 let mut rows_inserted = 0;
4691
4692 for selection in self.selections.all_adjusted(cx) {
4693 let cursor = selection.head();
4694 let row = cursor.row;
4695
4696 let point = Point::new(row + 1, 0);
4697 let start_of_line = snapshot.clip_point(point, Bias::Left);
4698
4699 let newline = "\n".to_string();
4700 edits.push((start_of_line..start_of_line, newline));
4701
4702 rows_inserted += 1;
4703 rows.push(row + rows_inserted);
4704 }
4705
4706 self.transact(window, cx, |editor, window, cx| {
4707 editor.edit(edits, cx);
4708
4709 editor.change_selections(Default::default(), window, cx, |s| {
4710 let mut index = 0;
4711 s.move_cursors_with(|map, _, _| {
4712 let row = rows[index];
4713 index += 1;
4714
4715 let point = Point::new(row, 0);
4716 let boundary = map.next_line_boundary(point).1;
4717 let clipped = map.clip_point(boundary, Bias::Left);
4718
4719 (clipped, SelectionGoal::None)
4720 });
4721 });
4722
4723 let mut indent_edits = Vec::new();
4724 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4725 for row in rows {
4726 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4727 for (row, indent) in indents {
4728 if indent.len == 0 {
4729 continue;
4730 }
4731
4732 let text = match indent.kind {
4733 IndentKind::Space => " ".repeat(indent.len as usize),
4734 IndentKind::Tab => "\t".repeat(indent.len as usize),
4735 };
4736 let point = Point::new(row.0, 0);
4737 indent_edits.push((point..point, text));
4738 }
4739 }
4740 editor.edit(indent_edits, cx);
4741 });
4742 }
4743
4744 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4745 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4746 original_indent_columns: Vec::new(),
4747 });
4748 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4749 }
4750
4751 fn insert_with_autoindent_mode(
4752 &mut self,
4753 text: &str,
4754 autoindent_mode: Option<AutoindentMode>,
4755 window: &mut Window,
4756 cx: &mut Context<Self>,
4757 ) {
4758 if self.read_only(cx) {
4759 return;
4760 }
4761
4762 let text: Arc<str> = text.into();
4763 self.transact(window, cx, |this, window, cx| {
4764 let old_selections = this.selections.all_adjusted(cx);
4765 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4766 let anchors = {
4767 let snapshot = buffer.read(cx);
4768 old_selections
4769 .iter()
4770 .map(|s| {
4771 let anchor = snapshot.anchor_after(s.head());
4772 s.map(|_| anchor)
4773 })
4774 .collect::<Vec<_>>()
4775 };
4776 buffer.edit(
4777 old_selections
4778 .iter()
4779 .map(|s| (s.start..s.end, text.clone())),
4780 autoindent_mode,
4781 cx,
4782 );
4783 anchors
4784 });
4785
4786 this.change_selections(Default::default(), window, cx, |s| {
4787 s.select_anchors(selection_anchors);
4788 });
4789
4790 cx.notify();
4791 });
4792 }
4793
4794 fn trigger_completion_on_input(
4795 &mut self,
4796 text: &str,
4797 trigger_in_words: bool,
4798 window: &mut Window,
4799 cx: &mut Context<Self>,
4800 ) {
4801 let completions_source = self
4802 .context_menu
4803 .borrow()
4804 .as_ref()
4805 .and_then(|menu| match menu {
4806 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4807 CodeContextMenu::CodeActions(_) => None,
4808 });
4809
4810 match completions_source {
4811 Some(CompletionsMenuSource::Words) => {
4812 self.show_word_completions(&ShowWordCompletions, window, cx)
4813 }
4814 Some(CompletionsMenuSource::Normal)
4815 | Some(CompletionsMenuSource::SnippetChoices)
4816 | None
4817 if self.is_completion_trigger(
4818 text,
4819 trigger_in_words,
4820 completions_source.is_some(),
4821 cx,
4822 ) =>
4823 {
4824 self.show_completions(
4825 &ShowCompletions {
4826 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4827 },
4828 window,
4829 cx,
4830 )
4831 }
4832 _ => {
4833 self.hide_context_menu(window, cx);
4834 }
4835 }
4836 }
4837
4838 fn is_completion_trigger(
4839 &self,
4840 text: &str,
4841 trigger_in_words: bool,
4842 menu_is_open: bool,
4843 cx: &mut Context<Self>,
4844 ) -> bool {
4845 let position = self.selections.newest_anchor().head();
4846 let multibuffer = self.buffer.read(cx);
4847 let Some(buffer) = position
4848 .buffer_id
4849 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4850 else {
4851 return false;
4852 };
4853
4854 if let Some(completion_provider) = &self.completion_provider {
4855 completion_provider.is_completion_trigger(
4856 &buffer,
4857 position.text_anchor,
4858 text,
4859 trigger_in_words,
4860 menu_is_open,
4861 cx,
4862 )
4863 } else {
4864 false
4865 }
4866 }
4867
4868 /// If any empty selections is touching the start of its innermost containing autoclose
4869 /// region, expand it to select the brackets.
4870 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4871 let selections = self.selections.all::<usize>(cx);
4872 let buffer = self.buffer.read(cx).read(cx);
4873 let new_selections = self
4874 .selections_with_autoclose_regions(selections, &buffer)
4875 .map(|(mut selection, region)| {
4876 if !selection.is_empty() {
4877 return selection;
4878 }
4879
4880 if let Some(region) = region {
4881 let mut range = region.range.to_offset(&buffer);
4882 if selection.start == range.start && range.start >= region.pair.start.len() {
4883 range.start -= region.pair.start.len();
4884 if buffer.contains_str_at(range.start, ®ion.pair.start)
4885 && buffer.contains_str_at(range.end, ®ion.pair.end)
4886 {
4887 range.end += region.pair.end.len();
4888 selection.start = range.start;
4889 selection.end = range.end;
4890
4891 return selection;
4892 }
4893 }
4894 }
4895
4896 let always_treat_brackets_as_autoclosed = buffer
4897 .language_settings_at(selection.start, cx)
4898 .always_treat_brackets_as_autoclosed;
4899
4900 if !always_treat_brackets_as_autoclosed {
4901 return selection;
4902 }
4903
4904 if let Some(scope) = buffer.language_scope_at(selection.start) {
4905 for (pair, enabled) in scope.brackets() {
4906 if !enabled || !pair.close {
4907 continue;
4908 }
4909
4910 if buffer.contains_str_at(selection.start, &pair.end) {
4911 let pair_start_len = pair.start.len();
4912 if buffer.contains_str_at(
4913 selection.start.saturating_sub(pair_start_len),
4914 &pair.start,
4915 ) {
4916 selection.start -= pair_start_len;
4917 selection.end += pair.end.len();
4918
4919 return selection;
4920 }
4921 }
4922 }
4923 }
4924
4925 selection
4926 })
4927 .collect();
4928
4929 drop(buffer);
4930 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
4931 selections.select(new_selections)
4932 });
4933 }
4934
4935 /// Iterate the given selections, and for each one, find the smallest surrounding
4936 /// autoclose region. This uses the ordering of the selections and the autoclose
4937 /// regions to avoid repeated comparisons.
4938 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4939 &'a self,
4940 selections: impl IntoIterator<Item = Selection<D>>,
4941 buffer: &'a MultiBufferSnapshot,
4942 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4943 let mut i = 0;
4944 let mut regions = self.autoclose_regions.as_slice();
4945 selections.into_iter().map(move |selection| {
4946 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4947
4948 let mut enclosing = None;
4949 while let Some(pair_state) = regions.get(i) {
4950 if pair_state.range.end.to_offset(buffer) < range.start {
4951 regions = ®ions[i + 1..];
4952 i = 0;
4953 } else if pair_state.range.start.to_offset(buffer) > range.end {
4954 break;
4955 } else {
4956 if pair_state.selection_id == selection.id {
4957 enclosing = Some(pair_state);
4958 }
4959 i += 1;
4960 }
4961 }
4962
4963 (selection, enclosing)
4964 })
4965 }
4966
4967 /// Remove any autoclose regions that no longer contain their selection.
4968 fn invalidate_autoclose_regions(
4969 &mut self,
4970 mut selections: &[Selection<Anchor>],
4971 buffer: &MultiBufferSnapshot,
4972 ) {
4973 self.autoclose_regions.retain(|state| {
4974 let mut i = 0;
4975 while let Some(selection) = selections.get(i) {
4976 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4977 selections = &selections[1..];
4978 continue;
4979 }
4980 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4981 break;
4982 }
4983 if selection.id == state.selection_id {
4984 return true;
4985 } else {
4986 i += 1;
4987 }
4988 }
4989 false
4990 });
4991 }
4992
4993 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4994 let offset = position.to_offset(buffer);
4995 let (word_range, kind) = buffer.surrounding_word(offset, true);
4996 if offset > word_range.start && kind == Some(CharKind::Word) {
4997 Some(
4998 buffer
4999 .text_for_range(word_range.start..offset)
5000 .collect::<String>(),
5001 )
5002 } else {
5003 None
5004 }
5005 }
5006
5007 pub fn toggle_inline_values(
5008 &mut self,
5009 _: &ToggleInlineValues,
5010 _: &mut Window,
5011 cx: &mut Context<Self>,
5012 ) {
5013 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5014
5015 self.refresh_inline_values(cx);
5016 }
5017
5018 pub fn toggle_inlay_hints(
5019 &mut self,
5020 _: &ToggleInlayHints,
5021 _: &mut Window,
5022 cx: &mut Context<Self>,
5023 ) {
5024 self.refresh_inlay_hints(
5025 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5026 cx,
5027 );
5028 }
5029
5030 pub fn inlay_hints_enabled(&self) -> bool {
5031 self.inlay_hint_cache.enabled
5032 }
5033
5034 pub fn inline_values_enabled(&self) -> bool {
5035 self.inline_value_cache.enabled
5036 }
5037
5038 #[cfg(any(test, feature = "test-support"))]
5039 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5040 self.display_map
5041 .read(cx)
5042 .current_inlays()
5043 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5044 .cloned()
5045 .collect()
5046 }
5047
5048 #[cfg(any(test, feature = "test-support"))]
5049 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5050 self.display_map
5051 .read(cx)
5052 .current_inlays()
5053 .cloned()
5054 .collect()
5055 }
5056
5057 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5058 if self.semantics_provider.is_none() || !self.mode.is_full() {
5059 return;
5060 }
5061
5062 let reason_description = reason.description();
5063 let ignore_debounce = matches!(
5064 reason,
5065 InlayHintRefreshReason::SettingsChange(_)
5066 | InlayHintRefreshReason::Toggle(_)
5067 | InlayHintRefreshReason::ExcerptsRemoved(_)
5068 | InlayHintRefreshReason::ModifiersChanged(_)
5069 );
5070 let (invalidate_cache, required_languages) = match reason {
5071 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5072 match self.inlay_hint_cache.modifiers_override(enabled) {
5073 Some(enabled) => {
5074 if enabled {
5075 (InvalidationStrategy::RefreshRequested, None)
5076 } else {
5077 self.splice_inlays(
5078 &self
5079 .visible_inlay_hints(cx)
5080 .iter()
5081 .map(|inlay| inlay.id)
5082 .collect::<Vec<InlayId>>(),
5083 Vec::new(),
5084 cx,
5085 );
5086 return;
5087 }
5088 }
5089 None => return,
5090 }
5091 }
5092 InlayHintRefreshReason::Toggle(enabled) => {
5093 if self.inlay_hint_cache.toggle(enabled) {
5094 if enabled {
5095 (InvalidationStrategy::RefreshRequested, None)
5096 } else {
5097 self.splice_inlays(
5098 &self
5099 .visible_inlay_hints(cx)
5100 .iter()
5101 .map(|inlay| inlay.id)
5102 .collect::<Vec<InlayId>>(),
5103 Vec::new(),
5104 cx,
5105 );
5106 return;
5107 }
5108 } else {
5109 return;
5110 }
5111 }
5112 InlayHintRefreshReason::SettingsChange(new_settings) => {
5113 match self.inlay_hint_cache.update_settings(
5114 &self.buffer,
5115 new_settings,
5116 self.visible_inlay_hints(cx),
5117 cx,
5118 ) {
5119 ControlFlow::Break(Some(InlaySplice {
5120 to_remove,
5121 to_insert,
5122 })) => {
5123 self.splice_inlays(&to_remove, to_insert, cx);
5124 return;
5125 }
5126 ControlFlow::Break(None) => return,
5127 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5128 }
5129 }
5130 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5131 if let Some(InlaySplice {
5132 to_remove,
5133 to_insert,
5134 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5135 {
5136 self.splice_inlays(&to_remove, to_insert, cx);
5137 }
5138 self.display_map.update(cx, |display_map, _| {
5139 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5140 });
5141 return;
5142 }
5143 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5144 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5145 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5146 }
5147 InlayHintRefreshReason::RefreshRequested => {
5148 (InvalidationStrategy::RefreshRequested, None)
5149 }
5150 };
5151
5152 if let Some(InlaySplice {
5153 to_remove,
5154 to_insert,
5155 }) = self.inlay_hint_cache.spawn_hint_refresh(
5156 reason_description,
5157 self.visible_excerpts(required_languages.as_ref(), cx),
5158 invalidate_cache,
5159 ignore_debounce,
5160 cx,
5161 ) {
5162 self.splice_inlays(&to_remove, to_insert, cx);
5163 }
5164 }
5165
5166 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5167 self.display_map
5168 .read(cx)
5169 .current_inlays()
5170 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5171 .cloned()
5172 .collect()
5173 }
5174
5175 pub fn visible_excerpts(
5176 &self,
5177 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5178 cx: &mut Context<Editor>,
5179 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5180 let Some(project) = self.project.as_ref() else {
5181 return HashMap::default();
5182 };
5183 let project = project.read(cx);
5184 let multi_buffer = self.buffer().read(cx);
5185 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5186 let multi_buffer_visible_start = self
5187 .scroll_manager
5188 .anchor()
5189 .anchor
5190 .to_point(&multi_buffer_snapshot);
5191 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5192 multi_buffer_visible_start
5193 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5194 Bias::Left,
5195 );
5196 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5197 multi_buffer_snapshot
5198 .range_to_buffer_ranges(multi_buffer_visible_range)
5199 .into_iter()
5200 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5201 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5202 let buffer_file = project::File::from_dyn(buffer.file())?;
5203 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5204 let worktree_entry = buffer_worktree
5205 .read(cx)
5206 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5207 if worktree_entry.is_ignored {
5208 return None;
5209 }
5210
5211 let language = buffer.language()?;
5212 if let Some(restrict_to_languages) = restrict_to_languages {
5213 if !restrict_to_languages.contains(language) {
5214 return None;
5215 }
5216 }
5217 Some((
5218 excerpt_id,
5219 (
5220 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5221 buffer.version().clone(),
5222 excerpt_visible_range,
5223 ),
5224 ))
5225 })
5226 .collect()
5227 }
5228
5229 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5230 TextLayoutDetails {
5231 text_system: window.text_system().clone(),
5232 editor_style: self.style.clone().unwrap(),
5233 rem_size: window.rem_size(),
5234 scroll_anchor: self.scroll_manager.anchor(),
5235 visible_rows: self.visible_line_count(),
5236 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5237 }
5238 }
5239
5240 pub fn splice_inlays(
5241 &self,
5242 to_remove: &[InlayId],
5243 to_insert: Vec<Inlay>,
5244 cx: &mut Context<Self>,
5245 ) {
5246 self.display_map.update(cx, |display_map, cx| {
5247 display_map.splice_inlays(to_remove, to_insert, cx)
5248 });
5249 cx.notify();
5250 }
5251
5252 fn trigger_on_type_formatting(
5253 &self,
5254 input: String,
5255 window: &mut Window,
5256 cx: &mut Context<Self>,
5257 ) -> Option<Task<Result<()>>> {
5258 if input.len() != 1 {
5259 return None;
5260 }
5261
5262 let project = self.project.as_ref()?;
5263 let position = self.selections.newest_anchor().head();
5264 let (buffer, buffer_position) = self
5265 .buffer
5266 .read(cx)
5267 .text_anchor_for_position(position, cx)?;
5268
5269 let settings = language_settings::language_settings(
5270 buffer
5271 .read(cx)
5272 .language_at(buffer_position)
5273 .map(|l| l.name()),
5274 buffer.read(cx).file(),
5275 cx,
5276 );
5277 if !settings.use_on_type_format {
5278 return None;
5279 }
5280
5281 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5282 // hence we do LSP request & edit on host side only — add formats to host's history.
5283 let push_to_lsp_host_history = true;
5284 // If this is not the host, append its history with new edits.
5285 let push_to_client_history = project.read(cx).is_via_collab();
5286
5287 let on_type_formatting = project.update(cx, |project, cx| {
5288 project.on_type_format(
5289 buffer.clone(),
5290 buffer_position,
5291 input,
5292 push_to_lsp_host_history,
5293 cx,
5294 )
5295 });
5296 Some(cx.spawn_in(window, async move |editor, cx| {
5297 if let Some(transaction) = on_type_formatting.await? {
5298 if push_to_client_history {
5299 buffer
5300 .update(cx, |buffer, _| {
5301 buffer.push_transaction(transaction, Instant::now());
5302 buffer.finalize_last_transaction();
5303 })
5304 .ok();
5305 }
5306 editor.update(cx, |editor, cx| {
5307 editor.refresh_document_highlights(cx);
5308 })?;
5309 }
5310 Ok(())
5311 }))
5312 }
5313
5314 pub fn show_word_completions(
5315 &mut self,
5316 _: &ShowWordCompletions,
5317 window: &mut Window,
5318 cx: &mut Context<Self>,
5319 ) {
5320 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5321 }
5322
5323 pub fn show_completions(
5324 &mut self,
5325 options: &ShowCompletions,
5326 window: &mut Window,
5327 cx: &mut Context<Self>,
5328 ) {
5329 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5330 }
5331
5332 fn open_or_update_completions_menu(
5333 &mut self,
5334 requested_source: Option<CompletionsMenuSource>,
5335 trigger: Option<&str>,
5336 window: &mut Window,
5337 cx: &mut Context<Self>,
5338 ) {
5339 if self.pending_rename.is_some() {
5340 return;
5341 }
5342
5343 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5344
5345 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5346 // inserted and selected. To handle that case, the start of the selection is used so that
5347 // the menu starts with all choices.
5348 let position = self
5349 .selections
5350 .newest_anchor()
5351 .start
5352 .bias_right(&multibuffer_snapshot);
5353 if position.diff_base_anchor.is_some() {
5354 return;
5355 }
5356 let (buffer, buffer_position) =
5357 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5358 output
5359 } else {
5360 return;
5361 };
5362 let buffer_snapshot = buffer.read(cx).snapshot();
5363
5364 let query: Option<Arc<String>> =
5365 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5366
5367 drop(multibuffer_snapshot);
5368
5369 let provider = match requested_source {
5370 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5371 Some(CompletionsMenuSource::Words) => None,
5372 Some(CompletionsMenuSource::SnippetChoices) => {
5373 log::error!("bug: SnippetChoices requested_source is not handled");
5374 None
5375 }
5376 };
5377
5378 let sort_completions = provider
5379 .as_ref()
5380 .map_or(false, |provider| provider.sort_completions());
5381
5382 let filter_completions = provider
5383 .as_ref()
5384 .map_or(true, |provider| provider.filter_completions());
5385
5386 let trigger_kind = match trigger {
5387 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5388 CompletionTriggerKind::TRIGGER_CHARACTER
5389 }
5390 _ => CompletionTriggerKind::INVOKED,
5391 };
5392 let completion_context = CompletionContext {
5393 trigger_character: trigger.and_then(|trigger| {
5394 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5395 Some(String::from(trigger))
5396 } else {
5397 None
5398 }
5399 }),
5400 trigger_kind,
5401 };
5402
5403 // Hide the current completions menu when a trigger char is typed. Without this, cached
5404 // completions from before the trigger char may be reused (#32774). Snippet choices could
5405 // involve trigger chars, so this is skipped in that case.
5406 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5407 {
5408 let menu_is_open = matches!(
5409 self.context_menu.borrow().as_ref(),
5410 Some(CodeContextMenu::Completions(_))
5411 );
5412 if menu_is_open {
5413 self.hide_context_menu(window, cx);
5414 }
5415 }
5416
5417 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5418 if filter_completions {
5419 menu.filter(query.clone(), provider.clone(), window, cx);
5420 }
5421 // When `is_incomplete` is false, no need to re-query completions when the current query
5422 // is a suffix of the initial query.
5423 if !menu.is_incomplete {
5424 // If the new query is a suffix of the old query (typing more characters) and
5425 // the previous result was complete, the existing completions can be filtered.
5426 //
5427 // Note that this is always true for snippet completions.
5428 let query_matches = match (&menu.initial_query, &query) {
5429 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5430 (None, _) => true,
5431 _ => false,
5432 };
5433 if query_matches {
5434 let position_matches = if menu.initial_position == position {
5435 true
5436 } else {
5437 let snapshot = self.buffer.read(cx).read(cx);
5438 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5439 };
5440 if position_matches {
5441 return;
5442 }
5443 }
5444 }
5445 };
5446
5447 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5448 buffer_snapshot.surrounding_word(buffer_position)
5449 {
5450 let word_to_exclude = buffer_snapshot
5451 .text_for_range(word_range.clone())
5452 .collect::<String>();
5453 (
5454 buffer_snapshot.anchor_before(word_range.start)
5455 ..buffer_snapshot.anchor_after(buffer_position),
5456 Some(word_to_exclude),
5457 )
5458 } else {
5459 (buffer_position..buffer_position, None)
5460 };
5461
5462 let language = buffer_snapshot
5463 .language_at(buffer_position)
5464 .map(|language| language.name());
5465
5466 let completion_settings =
5467 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5468
5469 let show_completion_documentation = buffer_snapshot
5470 .settings_at(buffer_position, cx)
5471 .show_completion_documentation;
5472
5473 // The document can be large, so stay in reasonable bounds when searching for words,
5474 // otherwise completion pop-up might be slow to appear.
5475 const WORD_LOOKUP_ROWS: u32 = 5_000;
5476 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5477 let min_word_search = buffer_snapshot.clip_point(
5478 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5479 Bias::Left,
5480 );
5481 let max_word_search = buffer_snapshot.clip_point(
5482 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5483 Bias::Right,
5484 );
5485 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5486 ..buffer_snapshot.point_to_offset(max_word_search);
5487
5488 let skip_digits = query
5489 .as_ref()
5490 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5491
5492 let (mut words, provider_responses) = match &provider {
5493 Some(provider) => {
5494 let provider_responses = provider.completions(
5495 position.excerpt_id,
5496 &buffer,
5497 buffer_position,
5498 completion_context,
5499 window,
5500 cx,
5501 );
5502
5503 let words = match completion_settings.words {
5504 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5505 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5506 .background_spawn(async move {
5507 buffer_snapshot.words_in_range(WordsQuery {
5508 fuzzy_contents: None,
5509 range: word_search_range,
5510 skip_digits,
5511 })
5512 }),
5513 };
5514
5515 (words, provider_responses)
5516 }
5517 None => (
5518 cx.background_spawn(async move {
5519 buffer_snapshot.words_in_range(WordsQuery {
5520 fuzzy_contents: None,
5521 range: word_search_range,
5522 skip_digits,
5523 })
5524 }),
5525 Task::ready(Ok(Vec::new())),
5526 ),
5527 };
5528
5529 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5530
5531 let id = post_inc(&mut self.next_completion_id);
5532 let task = cx.spawn_in(window, async move |editor, cx| {
5533 let Ok(()) = editor.update(cx, |this, _| {
5534 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5535 }) else {
5536 return;
5537 };
5538
5539 // TODO: Ideally completions from different sources would be selectively re-queried, so
5540 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5541 let mut completions = Vec::new();
5542 let mut is_incomplete = false;
5543 if let Some(provider_responses) = provider_responses.await.log_err() {
5544 if !provider_responses.is_empty() {
5545 for response in provider_responses {
5546 completions.extend(response.completions);
5547 is_incomplete = is_incomplete || response.is_incomplete;
5548 }
5549 if completion_settings.words == WordsCompletionMode::Fallback {
5550 words = Task::ready(BTreeMap::default());
5551 }
5552 }
5553 }
5554
5555 let mut words = words.await;
5556 if let Some(word_to_exclude) = &word_to_exclude {
5557 words.remove(word_to_exclude);
5558 }
5559 for lsp_completion in &completions {
5560 words.remove(&lsp_completion.new_text);
5561 }
5562 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5563 replace_range: word_replace_range.clone(),
5564 new_text: word.clone(),
5565 label: CodeLabel::plain(word, None),
5566 icon_path: None,
5567 documentation: None,
5568 source: CompletionSource::BufferWord {
5569 word_range,
5570 resolved: false,
5571 },
5572 insert_text_mode: Some(InsertTextMode::AS_IS),
5573 confirm: None,
5574 }));
5575
5576 let menu = if completions.is_empty() {
5577 None
5578 } else {
5579 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5580 let languages = editor
5581 .workspace
5582 .as_ref()
5583 .and_then(|(workspace, _)| workspace.upgrade())
5584 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5585 let menu = CompletionsMenu::new(
5586 id,
5587 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5588 sort_completions,
5589 show_completion_documentation,
5590 position,
5591 query.clone(),
5592 is_incomplete,
5593 buffer.clone(),
5594 completions.into(),
5595 snippet_sort_order,
5596 languages,
5597 language,
5598 cx,
5599 );
5600
5601 let query = if filter_completions { query } else { None };
5602 let matches_task = if let Some(query) = query {
5603 menu.do_async_filtering(query, cx)
5604 } else {
5605 Task::ready(menu.unfiltered_matches())
5606 };
5607 (menu, matches_task)
5608 }) else {
5609 return;
5610 };
5611
5612 let matches = matches_task.await;
5613
5614 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5615 // Newer menu already set, so exit.
5616 match editor.context_menu.borrow().as_ref() {
5617 Some(CodeContextMenu::Completions(prev_menu)) => {
5618 if prev_menu.id > id {
5619 return;
5620 }
5621 }
5622 _ => {}
5623 };
5624
5625 // Only valid to take prev_menu because it the new menu is immediately set
5626 // below, or the menu is hidden.
5627 match editor.context_menu.borrow_mut().take() {
5628 Some(CodeContextMenu::Completions(prev_menu)) => {
5629 let position_matches =
5630 if prev_menu.initial_position == menu.initial_position {
5631 true
5632 } else {
5633 let snapshot = editor.buffer.read(cx).read(cx);
5634 prev_menu.initial_position.to_offset(&snapshot)
5635 == menu.initial_position.to_offset(&snapshot)
5636 };
5637 if position_matches {
5638 // Preserve markdown cache before `set_filter_results` because it will
5639 // try to populate the documentation cache.
5640 menu.preserve_markdown_cache(prev_menu);
5641 }
5642 }
5643 _ => {}
5644 };
5645
5646 menu.set_filter_results(matches, provider, window, cx);
5647 }) else {
5648 return;
5649 };
5650
5651 menu.visible().then_some(menu)
5652 };
5653
5654 editor
5655 .update_in(cx, |editor, window, cx| {
5656 if editor.focus_handle.is_focused(window) {
5657 if let Some(menu) = menu {
5658 *editor.context_menu.borrow_mut() =
5659 Some(CodeContextMenu::Completions(menu));
5660
5661 crate::hover_popover::hide_hover(editor, cx);
5662 if editor.show_edit_predictions_in_menu() {
5663 editor.update_visible_inline_completion(window, cx);
5664 } else {
5665 editor.discard_inline_completion(false, cx);
5666 }
5667
5668 cx.notify();
5669 return;
5670 }
5671 }
5672
5673 if editor.completion_tasks.len() <= 1 {
5674 // If there are no more completion tasks and the last menu was empty, we should hide it.
5675 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5676 // If it was already hidden and we don't show inline completions in the menu, we should
5677 // also show the inline-completion when available.
5678 if was_hidden && editor.show_edit_predictions_in_menu() {
5679 editor.update_visible_inline_completion(window, cx);
5680 }
5681 }
5682 })
5683 .ok();
5684 });
5685
5686 self.completion_tasks.push((id, task));
5687 }
5688
5689 #[cfg(feature = "test-support")]
5690 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5691 let menu = self.context_menu.borrow();
5692 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5693 let completions = menu.completions.borrow();
5694 Some(completions.to_vec())
5695 } else {
5696 None
5697 }
5698 }
5699
5700 pub fn with_completions_menu_matching_id<R>(
5701 &self,
5702 id: CompletionId,
5703 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5704 ) -> R {
5705 let mut context_menu = self.context_menu.borrow_mut();
5706 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5707 return f(None);
5708 };
5709 if completions_menu.id != id {
5710 return f(None);
5711 }
5712 f(Some(completions_menu))
5713 }
5714
5715 pub fn confirm_completion(
5716 &mut self,
5717 action: &ConfirmCompletion,
5718 window: &mut Window,
5719 cx: &mut Context<Self>,
5720 ) -> Option<Task<Result<()>>> {
5721 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5722 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5723 }
5724
5725 pub fn confirm_completion_insert(
5726 &mut self,
5727 _: &ConfirmCompletionInsert,
5728 window: &mut Window,
5729 cx: &mut Context<Self>,
5730 ) -> Option<Task<Result<()>>> {
5731 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5732 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5733 }
5734
5735 pub fn confirm_completion_replace(
5736 &mut self,
5737 _: &ConfirmCompletionReplace,
5738 window: &mut Window,
5739 cx: &mut Context<Self>,
5740 ) -> Option<Task<Result<()>>> {
5741 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5742 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5743 }
5744
5745 pub fn compose_completion(
5746 &mut self,
5747 action: &ComposeCompletion,
5748 window: &mut Window,
5749 cx: &mut Context<Self>,
5750 ) -> Option<Task<Result<()>>> {
5751 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5752 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5753 }
5754
5755 fn do_completion(
5756 &mut self,
5757 item_ix: Option<usize>,
5758 intent: CompletionIntent,
5759 window: &mut Window,
5760 cx: &mut Context<Editor>,
5761 ) -> Option<Task<Result<()>>> {
5762 use language::ToOffset as _;
5763
5764 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5765 else {
5766 return None;
5767 };
5768
5769 let candidate_id = {
5770 let entries = completions_menu.entries.borrow();
5771 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5772 if self.show_edit_predictions_in_menu() {
5773 self.discard_inline_completion(true, cx);
5774 }
5775 mat.candidate_id
5776 };
5777
5778 let completion = completions_menu
5779 .completions
5780 .borrow()
5781 .get(candidate_id)?
5782 .clone();
5783 cx.stop_propagation();
5784
5785 let buffer_handle = completions_menu.buffer.clone();
5786
5787 let CompletionEdit {
5788 new_text,
5789 snippet,
5790 replace_range,
5791 } = process_completion_for_edit(
5792 &completion,
5793 intent,
5794 &buffer_handle,
5795 &completions_menu.initial_position.text_anchor,
5796 cx,
5797 );
5798
5799 let buffer = buffer_handle.read(cx);
5800 let snapshot = self.buffer.read(cx).snapshot(cx);
5801 let newest_anchor = self.selections.newest_anchor();
5802 let replace_range_multibuffer = {
5803 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5804 let multibuffer_anchor = snapshot
5805 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5806 .unwrap()
5807 ..snapshot
5808 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5809 .unwrap();
5810 multibuffer_anchor.start.to_offset(&snapshot)
5811 ..multibuffer_anchor.end.to_offset(&snapshot)
5812 };
5813 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5814 return None;
5815 }
5816
5817 let old_text = buffer
5818 .text_for_range(replace_range.clone())
5819 .collect::<String>();
5820 let lookbehind = newest_anchor
5821 .start
5822 .text_anchor
5823 .to_offset(buffer)
5824 .saturating_sub(replace_range.start);
5825 let lookahead = replace_range
5826 .end
5827 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5828 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5829 let suffix = &old_text[lookbehind.min(old_text.len())..];
5830
5831 let selections = self.selections.all::<usize>(cx);
5832 let mut ranges = Vec::new();
5833 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5834
5835 for selection in &selections {
5836 let range = if selection.id == newest_anchor.id {
5837 replace_range_multibuffer.clone()
5838 } else {
5839 let mut range = selection.range();
5840
5841 // if prefix is present, don't duplicate it
5842 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5843 range.start = range.start.saturating_sub(lookbehind);
5844
5845 // if suffix is also present, mimic the newest cursor and replace it
5846 if selection.id != newest_anchor.id
5847 && snapshot.contains_str_at(range.end, suffix)
5848 {
5849 range.end += lookahead;
5850 }
5851 }
5852 range
5853 };
5854
5855 ranges.push(range.clone());
5856
5857 if !self.linked_edit_ranges.is_empty() {
5858 let start_anchor = snapshot.anchor_before(range.start);
5859 let end_anchor = snapshot.anchor_after(range.end);
5860 if let Some(ranges) = self
5861 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5862 {
5863 for (buffer, edits) in ranges {
5864 linked_edits
5865 .entry(buffer.clone())
5866 .or_default()
5867 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5868 }
5869 }
5870 }
5871 }
5872
5873 let common_prefix_len = old_text
5874 .chars()
5875 .zip(new_text.chars())
5876 .take_while(|(a, b)| a == b)
5877 .map(|(a, _)| a.len_utf8())
5878 .sum::<usize>();
5879
5880 cx.emit(EditorEvent::InputHandled {
5881 utf16_range_to_replace: None,
5882 text: new_text[common_prefix_len..].into(),
5883 });
5884
5885 self.transact(window, cx, |this, window, cx| {
5886 if let Some(mut snippet) = snippet {
5887 snippet.text = new_text.to_string();
5888 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5889 } else {
5890 this.buffer.update(cx, |buffer, cx| {
5891 let auto_indent = match completion.insert_text_mode {
5892 Some(InsertTextMode::AS_IS) => None,
5893 _ => this.autoindent_mode.clone(),
5894 };
5895 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5896 buffer.edit(edits, auto_indent, cx);
5897 });
5898 }
5899 for (buffer, edits) in linked_edits {
5900 buffer.update(cx, |buffer, cx| {
5901 let snapshot = buffer.snapshot();
5902 let edits = edits
5903 .into_iter()
5904 .map(|(range, text)| {
5905 use text::ToPoint as TP;
5906 let end_point = TP::to_point(&range.end, &snapshot);
5907 let start_point = TP::to_point(&range.start, &snapshot);
5908 (start_point..end_point, text)
5909 })
5910 .sorted_by_key(|(range, _)| range.start);
5911 buffer.edit(edits, None, cx);
5912 })
5913 }
5914
5915 this.refresh_inline_completion(true, false, window, cx);
5916 });
5917
5918 let show_new_completions_on_confirm = completion
5919 .confirm
5920 .as_ref()
5921 .map_or(false, |confirm| confirm(intent, window, cx));
5922 if show_new_completions_on_confirm {
5923 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5924 }
5925
5926 let provider = self.completion_provider.as_ref()?;
5927 drop(completion);
5928 let apply_edits = provider.apply_additional_edits_for_completion(
5929 buffer_handle,
5930 completions_menu.completions.clone(),
5931 candidate_id,
5932 true,
5933 cx,
5934 );
5935
5936 let editor_settings = EditorSettings::get_global(cx);
5937 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5938 // After the code completion is finished, users often want to know what signatures are needed.
5939 // so we should automatically call signature_help
5940 self.show_signature_help(&ShowSignatureHelp, window, cx);
5941 }
5942
5943 Some(cx.foreground_executor().spawn(async move {
5944 apply_edits.await?;
5945 Ok(())
5946 }))
5947 }
5948
5949 pub fn toggle_code_actions(
5950 &mut self,
5951 action: &ToggleCodeActions,
5952 window: &mut Window,
5953 cx: &mut Context<Self>,
5954 ) {
5955 let quick_launch = action.quick_launch;
5956 let mut context_menu = self.context_menu.borrow_mut();
5957 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5958 if code_actions.deployed_from == action.deployed_from {
5959 // Toggle if we're selecting the same one
5960 *context_menu = None;
5961 cx.notify();
5962 return;
5963 } else {
5964 // Otherwise, clear it and start a new one
5965 *context_menu = None;
5966 cx.notify();
5967 }
5968 }
5969 drop(context_menu);
5970 let snapshot = self.snapshot(window, cx);
5971 let deployed_from = action.deployed_from.clone();
5972 let action = action.clone();
5973 self.completion_tasks.clear();
5974 self.discard_inline_completion(false, cx);
5975
5976 let multibuffer_point = match &action.deployed_from {
5977 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5978 DisplayPoint::new(*row, 0).to_point(&snapshot)
5979 }
5980 _ => self.selections.newest::<Point>(cx).head(),
5981 };
5982 let Some((buffer, buffer_row)) = snapshot
5983 .buffer_snapshot
5984 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5985 .and_then(|(buffer_snapshot, range)| {
5986 self.buffer()
5987 .read(cx)
5988 .buffer(buffer_snapshot.remote_id())
5989 .map(|buffer| (buffer, range.start.row))
5990 })
5991 else {
5992 return;
5993 };
5994 let buffer_id = buffer.read(cx).remote_id();
5995 let tasks = self
5996 .tasks
5997 .get(&(buffer_id, buffer_row))
5998 .map(|t| Arc::new(t.to_owned()));
5999
6000 if !self.focus_handle.is_focused(window) {
6001 return;
6002 }
6003 let project = self.project.clone();
6004
6005 let code_actions_task = match deployed_from {
6006 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6007 _ => self.code_actions(buffer_row, window, cx),
6008 };
6009
6010 let runnable_task = match deployed_from {
6011 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6012 _ => {
6013 let mut task_context_task = Task::ready(None);
6014 if let Some(tasks) = &tasks {
6015 if let Some(project) = project {
6016 task_context_task =
6017 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6018 }
6019 }
6020
6021 cx.spawn_in(window, {
6022 let buffer = buffer.clone();
6023 async move |editor, cx| {
6024 let task_context = task_context_task.await;
6025
6026 let resolved_tasks =
6027 tasks
6028 .zip(task_context.clone())
6029 .map(|(tasks, task_context)| ResolvedTasks {
6030 templates: tasks.resolve(&task_context).collect(),
6031 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6032 multibuffer_point.row,
6033 tasks.column,
6034 )),
6035 });
6036 let debug_scenarios = editor
6037 .update(cx, |editor, cx| {
6038 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6039 })?
6040 .await;
6041 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6042 }
6043 })
6044 }
6045 };
6046
6047 cx.spawn_in(window, async move |editor, cx| {
6048 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6049 let code_actions = code_actions_task.await;
6050 let spawn_straight_away = quick_launch
6051 && resolved_tasks
6052 .as_ref()
6053 .map_or(false, |tasks| tasks.templates.len() == 1)
6054 && code_actions
6055 .as_ref()
6056 .map_or(true, |actions| actions.is_empty())
6057 && debug_scenarios.is_empty();
6058
6059 editor.update_in(cx, |editor, window, cx| {
6060 crate::hover_popover::hide_hover(editor, cx);
6061 let actions = CodeActionContents::new(
6062 resolved_tasks,
6063 code_actions,
6064 debug_scenarios,
6065 task_context.unwrap_or_default(),
6066 );
6067
6068 // Don't show the menu if there are no actions available
6069 if actions.is_empty() {
6070 cx.notify();
6071 return Task::ready(Ok(()));
6072 }
6073
6074 *editor.context_menu.borrow_mut() =
6075 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6076 buffer,
6077 actions,
6078 selected_item: Default::default(),
6079 scroll_handle: UniformListScrollHandle::default(),
6080 deployed_from,
6081 }));
6082 cx.notify();
6083 if spawn_straight_away {
6084 if let Some(task) = editor.confirm_code_action(
6085 &ConfirmCodeAction { item_ix: Some(0) },
6086 window,
6087 cx,
6088 ) {
6089 return task;
6090 }
6091 }
6092
6093 Task::ready(Ok(()))
6094 })
6095 })
6096 .detach_and_log_err(cx);
6097 }
6098
6099 fn debug_scenarios(
6100 &mut self,
6101 resolved_tasks: &Option<ResolvedTasks>,
6102 buffer: &Entity<Buffer>,
6103 cx: &mut App,
6104 ) -> Task<Vec<task::DebugScenario>> {
6105 maybe!({
6106 let project = self.project.as_ref()?;
6107 let dap_store = project.read(cx).dap_store();
6108 let mut scenarios = vec![];
6109 let resolved_tasks = resolved_tasks.as_ref()?;
6110 let buffer = buffer.read(cx);
6111 let language = buffer.language()?;
6112 let file = buffer.file();
6113 let debug_adapter = language_settings(language.name().into(), file, cx)
6114 .debuggers
6115 .first()
6116 .map(SharedString::from)
6117 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6118
6119 dap_store.update(cx, |dap_store, cx| {
6120 for (_, task) in &resolved_tasks.templates {
6121 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6122 task.original_task().clone(),
6123 debug_adapter.clone().into(),
6124 task.display_label().to_owned().into(),
6125 cx,
6126 );
6127 scenarios.push(maybe_scenario);
6128 }
6129 });
6130 Some(cx.background_spawn(async move {
6131 let scenarios = futures::future::join_all(scenarios)
6132 .await
6133 .into_iter()
6134 .flatten()
6135 .collect::<Vec<_>>();
6136 scenarios
6137 }))
6138 })
6139 .unwrap_or_else(|| Task::ready(vec![]))
6140 }
6141
6142 fn code_actions(
6143 &mut self,
6144 buffer_row: u32,
6145 window: &mut Window,
6146 cx: &mut Context<Self>,
6147 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6148 let mut task = self.code_actions_task.take();
6149 cx.spawn_in(window, async move |editor, cx| {
6150 while let Some(prev_task) = task {
6151 prev_task.await.log_err();
6152 task = editor
6153 .update(cx, |this, _| this.code_actions_task.take())
6154 .ok()?;
6155 }
6156
6157 editor
6158 .update(cx, |editor, cx| {
6159 editor
6160 .available_code_actions
6161 .clone()
6162 .and_then(|(location, code_actions)| {
6163 let snapshot = location.buffer.read(cx).snapshot();
6164 let point_range = location.range.to_point(&snapshot);
6165 let point_range = point_range.start.row..=point_range.end.row;
6166 if point_range.contains(&buffer_row) {
6167 Some(code_actions)
6168 } else {
6169 None
6170 }
6171 })
6172 })
6173 .ok()
6174 .flatten()
6175 })
6176 }
6177
6178 pub fn confirm_code_action(
6179 &mut self,
6180 action: &ConfirmCodeAction,
6181 window: &mut Window,
6182 cx: &mut Context<Self>,
6183 ) -> Option<Task<Result<()>>> {
6184 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6185
6186 let actions_menu =
6187 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6188 menu
6189 } else {
6190 return None;
6191 };
6192
6193 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6194 let action = actions_menu.actions.get(action_ix)?;
6195 let title = action.label();
6196 let buffer = actions_menu.buffer;
6197 let workspace = self.workspace()?;
6198
6199 match action {
6200 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6201 workspace.update(cx, |workspace, cx| {
6202 workspace.schedule_resolved_task(
6203 task_source_kind,
6204 resolved_task,
6205 false,
6206 window,
6207 cx,
6208 );
6209
6210 Some(Task::ready(Ok(())))
6211 })
6212 }
6213 CodeActionsItem::CodeAction {
6214 excerpt_id,
6215 action,
6216 provider,
6217 } => {
6218 let apply_code_action =
6219 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6220 let workspace = workspace.downgrade();
6221 Some(cx.spawn_in(window, async move |editor, cx| {
6222 let project_transaction = apply_code_action.await?;
6223 Self::open_project_transaction(
6224 &editor,
6225 workspace,
6226 project_transaction,
6227 title,
6228 cx,
6229 )
6230 .await
6231 }))
6232 }
6233 CodeActionsItem::DebugScenario(scenario) => {
6234 let context = actions_menu.actions.context.clone();
6235
6236 workspace.update(cx, |workspace, cx| {
6237 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6238 workspace.start_debug_session(
6239 scenario,
6240 context,
6241 Some(buffer),
6242 None,
6243 window,
6244 cx,
6245 );
6246 });
6247 Some(Task::ready(Ok(())))
6248 }
6249 }
6250 }
6251
6252 pub async fn open_project_transaction(
6253 this: &WeakEntity<Editor>,
6254 workspace: WeakEntity<Workspace>,
6255 transaction: ProjectTransaction,
6256 title: String,
6257 cx: &mut AsyncWindowContext,
6258 ) -> Result<()> {
6259 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6260 cx.update(|_, cx| {
6261 entries.sort_unstable_by_key(|(buffer, _)| {
6262 buffer.read(cx).file().map(|f| f.path().clone())
6263 });
6264 })?;
6265
6266 // If the project transaction's edits are all contained within this editor, then
6267 // avoid opening a new editor to display them.
6268
6269 if let Some((buffer, transaction)) = entries.first() {
6270 if entries.len() == 1 {
6271 let excerpt = this.update(cx, |editor, cx| {
6272 editor
6273 .buffer()
6274 .read(cx)
6275 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6276 })?;
6277 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6278 if excerpted_buffer == *buffer {
6279 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6280 let excerpt_range = excerpt_range.to_offset(buffer);
6281 buffer
6282 .edited_ranges_for_transaction::<usize>(transaction)
6283 .all(|range| {
6284 excerpt_range.start <= range.start
6285 && excerpt_range.end >= range.end
6286 })
6287 })?;
6288
6289 if all_edits_within_excerpt {
6290 return Ok(());
6291 }
6292 }
6293 }
6294 }
6295 } else {
6296 return Ok(());
6297 }
6298
6299 let mut ranges_to_highlight = Vec::new();
6300 let excerpt_buffer = cx.new(|cx| {
6301 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6302 for (buffer_handle, transaction) in &entries {
6303 let edited_ranges = buffer_handle
6304 .read(cx)
6305 .edited_ranges_for_transaction::<Point>(transaction)
6306 .collect::<Vec<_>>();
6307 let (ranges, _) = multibuffer.set_excerpts_for_path(
6308 PathKey::for_buffer(buffer_handle, cx),
6309 buffer_handle.clone(),
6310 edited_ranges,
6311 DEFAULT_MULTIBUFFER_CONTEXT,
6312 cx,
6313 );
6314
6315 ranges_to_highlight.extend(ranges);
6316 }
6317 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6318 multibuffer
6319 })?;
6320
6321 workspace.update_in(cx, |workspace, window, cx| {
6322 let project = workspace.project().clone();
6323 let editor =
6324 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6325 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6326 editor.update(cx, |editor, cx| {
6327 editor.highlight_background::<Self>(
6328 &ranges_to_highlight,
6329 |theme| theme.colors().editor_highlighted_line_background,
6330 cx,
6331 );
6332 });
6333 })?;
6334
6335 Ok(())
6336 }
6337
6338 pub fn clear_code_action_providers(&mut self) {
6339 self.code_action_providers.clear();
6340 self.available_code_actions.take();
6341 }
6342
6343 pub fn add_code_action_provider(
6344 &mut self,
6345 provider: Rc<dyn CodeActionProvider>,
6346 window: &mut Window,
6347 cx: &mut Context<Self>,
6348 ) {
6349 if self
6350 .code_action_providers
6351 .iter()
6352 .any(|existing_provider| existing_provider.id() == provider.id())
6353 {
6354 return;
6355 }
6356
6357 self.code_action_providers.push(provider);
6358 self.refresh_code_actions(window, cx);
6359 }
6360
6361 pub fn remove_code_action_provider(
6362 &mut self,
6363 id: Arc<str>,
6364 window: &mut Window,
6365 cx: &mut Context<Self>,
6366 ) {
6367 self.code_action_providers
6368 .retain(|provider| provider.id() != id);
6369 self.refresh_code_actions(window, cx);
6370 }
6371
6372 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6373 !self.code_action_providers.is_empty()
6374 && EditorSettings::get_global(cx).toolbar.code_actions
6375 }
6376
6377 pub fn has_available_code_actions(&self) -> bool {
6378 self.available_code_actions
6379 .as_ref()
6380 .is_some_and(|(_, actions)| !actions.is_empty())
6381 }
6382
6383 fn render_inline_code_actions(
6384 &self,
6385 icon_size: ui::IconSize,
6386 display_row: DisplayRow,
6387 is_active: bool,
6388 cx: &mut Context<Self>,
6389 ) -> AnyElement {
6390 let show_tooltip = !self.context_menu_visible();
6391 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6392 .icon_size(icon_size)
6393 .shape(ui::IconButtonShape::Square)
6394 .style(ButtonStyle::Transparent)
6395 .icon_color(ui::Color::Hidden)
6396 .toggle_state(is_active)
6397 .when(show_tooltip, |this| {
6398 this.tooltip({
6399 let focus_handle = self.focus_handle.clone();
6400 move |window, cx| {
6401 Tooltip::for_action_in(
6402 "Toggle Code Actions",
6403 &ToggleCodeActions {
6404 deployed_from: None,
6405 quick_launch: false,
6406 },
6407 &focus_handle,
6408 window,
6409 cx,
6410 )
6411 }
6412 })
6413 })
6414 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6415 window.focus(&editor.focus_handle(cx));
6416 editor.toggle_code_actions(
6417 &crate::actions::ToggleCodeActions {
6418 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6419 display_row,
6420 )),
6421 quick_launch: false,
6422 },
6423 window,
6424 cx,
6425 );
6426 }))
6427 .into_any_element()
6428 }
6429
6430 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6431 &self.context_menu
6432 }
6433
6434 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6435 let newest_selection = self.selections.newest_anchor().clone();
6436 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6437 let buffer = self.buffer.read(cx);
6438 if newest_selection.head().diff_base_anchor.is_some() {
6439 return None;
6440 }
6441 let (start_buffer, start) =
6442 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6443 let (end_buffer, end) =
6444 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6445 if start_buffer != end_buffer {
6446 return None;
6447 }
6448
6449 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6450 cx.background_executor()
6451 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6452 .await;
6453
6454 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6455 let providers = this.code_action_providers.clone();
6456 let tasks = this
6457 .code_action_providers
6458 .iter()
6459 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6460 .collect::<Vec<_>>();
6461 (providers, tasks)
6462 })?;
6463
6464 let mut actions = Vec::new();
6465 for (provider, provider_actions) in
6466 providers.into_iter().zip(future::join_all(tasks).await)
6467 {
6468 if let Some(provider_actions) = provider_actions.log_err() {
6469 actions.extend(provider_actions.into_iter().map(|action| {
6470 AvailableCodeAction {
6471 excerpt_id: newest_selection.start.excerpt_id,
6472 action,
6473 provider: provider.clone(),
6474 }
6475 }));
6476 }
6477 }
6478
6479 this.update(cx, |this, cx| {
6480 this.available_code_actions = if actions.is_empty() {
6481 None
6482 } else {
6483 Some((
6484 Location {
6485 buffer: start_buffer,
6486 range: start..end,
6487 },
6488 actions.into(),
6489 ))
6490 };
6491 cx.notify();
6492 })
6493 }));
6494 None
6495 }
6496
6497 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6498 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6499 self.show_git_blame_inline = false;
6500
6501 self.show_git_blame_inline_delay_task =
6502 Some(cx.spawn_in(window, async move |this, cx| {
6503 cx.background_executor().timer(delay).await;
6504
6505 this.update(cx, |this, cx| {
6506 this.show_git_blame_inline = true;
6507 cx.notify();
6508 })
6509 .log_err();
6510 }));
6511 }
6512 }
6513
6514 fn show_blame_popover(
6515 &mut self,
6516 blame_entry: &BlameEntry,
6517 position: gpui::Point<Pixels>,
6518 cx: &mut Context<Self>,
6519 ) {
6520 if let Some(state) = &mut self.inline_blame_popover {
6521 state.hide_task.take();
6522 } else {
6523 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6524 let blame_entry = blame_entry.clone();
6525 let show_task = cx.spawn(async move |editor, cx| {
6526 cx.background_executor()
6527 .timer(std::time::Duration::from_millis(delay))
6528 .await;
6529 editor
6530 .update(cx, |editor, cx| {
6531 editor.inline_blame_popover_show_task.take();
6532 let Some(blame) = editor.blame.as_ref() else {
6533 return;
6534 };
6535 let blame = blame.read(cx);
6536 let details = blame.details_for_entry(&blame_entry);
6537 let markdown = cx.new(|cx| {
6538 Markdown::new(
6539 details
6540 .as_ref()
6541 .map(|message| message.message.clone())
6542 .unwrap_or_default(),
6543 None,
6544 None,
6545 cx,
6546 )
6547 });
6548 editor.inline_blame_popover = Some(InlineBlamePopover {
6549 position,
6550 hide_task: None,
6551 popover_bounds: None,
6552 popover_state: InlineBlamePopoverState {
6553 scroll_handle: ScrollHandle::new(),
6554 commit_message: details,
6555 markdown,
6556 },
6557 });
6558 cx.notify();
6559 })
6560 .ok();
6561 });
6562 self.inline_blame_popover_show_task = Some(show_task);
6563 }
6564 }
6565
6566 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6567 self.inline_blame_popover_show_task.take();
6568 if let Some(state) = &mut self.inline_blame_popover {
6569 let hide_task = cx.spawn(async move |editor, cx| {
6570 cx.background_executor()
6571 .timer(std::time::Duration::from_millis(100))
6572 .await;
6573 editor
6574 .update(cx, |editor, cx| {
6575 editor.inline_blame_popover.take();
6576 cx.notify();
6577 })
6578 .ok();
6579 });
6580 state.hide_task = Some(hide_task);
6581 }
6582 }
6583
6584 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6585 if self.pending_rename.is_some() {
6586 return None;
6587 }
6588
6589 let provider = self.semantics_provider.clone()?;
6590 let buffer = self.buffer.read(cx);
6591 let newest_selection = self.selections.newest_anchor().clone();
6592 let cursor_position = newest_selection.head();
6593 let (cursor_buffer, cursor_buffer_position) =
6594 buffer.text_anchor_for_position(cursor_position, cx)?;
6595 let (tail_buffer, tail_buffer_position) =
6596 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6597 if cursor_buffer != tail_buffer {
6598 return None;
6599 }
6600
6601 let snapshot = cursor_buffer.read(cx).snapshot();
6602 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6603 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6604 if start_word_range != end_word_range {
6605 self.document_highlights_task.take();
6606 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6607 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6608 return None;
6609 }
6610
6611 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6612 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6613 cx.background_executor()
6614 .timer(Duration::from_millis(debounce))
6615 .await;
6616
6617 let highlights = if let Some(highlights) = cx
6618 .update(|cx| {
6619 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6620 })
6621 .ok()
6622 .flatten()
6623 {
6624 highlights.await.log_err()
6625 } else {
6626 None
6627 };
6628
6629 if let Some(highlights) = highlights {
6630 this.update(cx, |this, cx| {
6631 if this.pending_rename.is_some() {
6632 return;
6633 }
6634
6635 let buffer_id = cursor_position.buffer_id;
6636 let buffer = this.buffer.read(cx);
6637 if !buffer
6638 .text_anchor_for_position(cursor_position, cx)
6639 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6640 {
6641 return;
6642 }
6643
6644 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6645 let mut write_ranges = Vec::new();
6646 let mut read_ranges = Vec::new();
6647 for highlight in highlights {
6648 for (excerpt_id, excerpt_range) in
6649 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6650 {
6651 let start = highlight
6652 .range
6653 .start
6654 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6655 let end = highlight
6656 .range
6657 .end
6658 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6659 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6660 continue;
6661 }
6662
6663 let range = Anchor {
6664 buffer_id,
6665 excerpt_id,
6666 text_anchor: start,
6667 diff_base_anchor: None,
6668 }..Anchor {
6669 buffer_id,
6670 excerpt_id,
6671 text_anchor: end,
6672 diff_base_anchor: None,
6673 };
6674 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6675 write_ranges.push(range);
6676 } else {
6677 read_ranges.push(range);
6678 }
6679 }
6680 }
6681
6682 this.highlight_background::<DocumentHighlightRead>(
6683 &read_ranges,
6684 |theme| theme.colors().editor_document_highlight_read_background,
6685 cx,
6686 );
6687 this.highlight_background::<DocumentHighlightWrite>(
6688 &write_ranges,
6689 |theme| theme.colors().editor_document_highlight_write_background,
6690 cx,
6691 );
6692 cx.notify();
6693 })
6694 .log_err();
6695 }
6696 }));
6697 None
6698 }
6699
6700 fn prepare_highlight_query_from_selection(
6701 &mut self,
6702 cx: &mut Context<Editor>,
6703 ) -> Option<(String, Range<Anchor>)> {
6704 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6705 return None;
6706 }
6707 if !EditorSettings::get_global(cx).selection_highlight {
6708 return None;
6709 }
6710 if self.selections.count() != 1 || self.selections.line_mode {
6711 return None;
6712 }
6713 let selection = self.selections.newest::<Point>(cx);
6714 if selection.is_empty() || selection.start.row != selection.end.row {
6715 return None;
6716 }
6717 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6718 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6719 let query = multi_buffer_snapshot
6720 .text_for_range(selection_anchor_range.clone())
6721 .collect::<String>();
6722 if query.trim().is_empty() {
6723 return None;
6724 }
6725 Some((query, selection_anchor_range))
6726 }
6727
6728 fn update_selection_occurrence_highlights(
6729 &mut self,
6730 query_text: String,
6731 query_range: Range<Anchor>,
6732 multi_buffer_range_to_query: Range<Point>,
6733 use_debounce: bool,
6734 window: &mut Window,
6735 cx: &mut Context<Editor>,
6736 ) -> Task<()> {
6737 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6738 cx.spawn_in(window, async move |editor, cx| {
6739 if use_debounce {
6740 cx.background_executor()
6741 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6742 .await;
6743 }
6744 let match_task = cx.background_spawn(async move {
6745 let buffer_ranges = multi_buffer_snapshot
6746 .range_to_buffer_ranges(multi_buffer_range_to_query)
6747 .into_iter()
6748 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6749 let mut match_ranges = Vec::new();
6750 let Ok(regex) = project::search::SearchQuery::text(
6751 query_text.clone(),
6752 false,
6753 false,
6754 false,
6755 Default::default(),
6756 Default::default(),
6757 false,
6758 None,
6759 ) else {
6760 return Vec::default();
6761 };
6762 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6763 match_ranges.extend(
6764 regex
6765 .search(&buffer_snapshot, Some(search_range.clone()))
6766 .await
6767 .into_iter()
6768 .filter_map(|match_range| {
6769 let match_start = buffer_snapshot
6770 .anchor_after(search_range.start + match_range.start);
6771 let match_end = buffer_snapshot
6772 .anchor_before(search_range.start + match_range.end);
6773 let match_anchor_range = Anchor::range_in_buffer(
6774 excerpt_id,
6775 buffer_snapshot.remote_id(),
6776 match_start..match_end,
6777 );
6778 (match_anchor_range != query_range).then_some(match_anchor_range)
6779 }),
6780 );
6781 }
6782 match_ranges
6783 });
6784 let match_ranges = match_task.await;
6785 editor
6786 .update_in(cx, |editor, _, cx| {
6787 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6788 if !match_ranges.is_empty() {
6789 editor.highlight_background::<SelectedTextHighlight>(
6790 &match_ranges,
6791 |theme| theme.colors().editor_document_highlight_bracket_background,
6792 cx,
6793 )
6794 }
6795 })
6796 .log_err();
6797 })
6798 }
6799
6800 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6801 struct NewlineFold;
6802 let type_id = std::any::TypeId::of::<NewlineFold>();
6803 if !self.mode.is_single_line() {
6804 return;
6805 }
6806 let snapshot = self.snapshot(window, cx);
6807 if snapshot.buffer_snapshot.max_point().row == 0 {
6808 return;
6809 }
6810 let task = cx.background_spawn(async move {
6811 let new_newlines = snapshot
6812 .buffer_chars_at(0)
6813 .filter_map(|(c, i)| {
6814 if c == '\n' {
6815 Some(
6816 snapshot.buffer_snapshot.anchor_after(i)
6817 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6818 )
6819 } else {
6820 None
6821 }
6822 })
6823 .collect::<Vec<_>>();
6824 let existing_newlines = snapshot
6825 .folds_in_range(0..snapshot.buffer_snapshot.len())
6826 .filter_map(|fold| {
6827 if fold.placeholder.type_tag == Some(type_id) {
6828 Some(fold.range.start..fold.range.end)
6829 } else {
6830 None
6831 }
6832 })
6833 .collect::<Vec<_>>();
6834
6835 (new_newlines, existing_newlines)
6836 });
6837 self.folding_newlines = cx.spawn(async move |this, cx| {
6838 let (new_newlines, existing_newlines) = task.await;
6839 if new_newlines == existing_newlines {
6840 return;
6841 }
6842 let placeholder = FoldPlaceholder {
6843 render: Arc::new(move |_, _, cx| {
6844 div()
6845 .bg(cx.theme().status().hint_background)
6846 .border_b_1()
6847 .size_full()
6848 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6849 .border_color(cx.theme().status().hint)
6850 .child("\\n")
6851 .into_any()
6852 }),
6853 constrain_width: false,
6854 merge_adjacent: false,
6855 type_tag: Some(type_id),
6856 };
6857 let creases = new_newlines
6858 .into_iter()
6859 .map(|range| Crease::simple(range, placeholder.clone()))
6860 .collect();
6861 this.update(cx, |this, cx| {
6862 this.display_map.update(cx, |display_map, cx| {
6863 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6864 display_map.fold(creases, cx);
6865 });
6866 })
6867 .ok();
6868 });
6869 }
6870
6871 fn refresh_selected_text_highlights(
6872 &mut self,
6873 on_buffer_edit: bool,
6874 window: &mut Window,
6875 cx: &mut Context<Editor>,
6876 ) {
6877 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6878 else {
6879 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6880 self.quick_selection_highlight_task.take();
6881 self.debounced_selection_highlight_task.take();
6882 return;
6883 };
6884 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6885 if on_buffer_edit
6886 || self
6887 .quick_selection_highlight_task
6888 .as_ref()
6889 .map_or(true, |(prev_anchor_range, _)| {
6890 prev_anchor_range != &query_range
6891 })
6892 {
6893 let multi_buffer_visible_start = self
6894 .scroll_manager
6895 .anchor()
6896 .anchor
6897 .to_point(&multi_buffer_snapshot);
6898 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6899 multi_buffer_visible_start
6900 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6901 Bias::Left,
6902 );
6903 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6904 self.quick_selection_highlight_task = Some((
6905 query_range.clone(),
6906 self.update_selection_occurrence_highlights(
6907 query_text.clone(),
6908 query_range.clone(),
6909 multi_buffer_visible_range,
6910 false,
6911 window,
6912 cx,
6913 ),
6914 ));
6915 }
6916 if on_buffer_edit
6917 || self
6918 .debounced_selection_highlight_task
6919 .as_ref()
6920 .map_or(true, |(prev_anchor_range, _)| {
6921 prev_anchor_range != &query_range
6922 })
6923 {
6924 let multi_buffer_start = multi_buffer_snapshot
6925 .anchor_before(0)
6926 .to_point(&multi_buffer_snapshot);
6927 let multi_buffer_end = multi_buffer_snapshot
6928 .anchor_after(multi_buffer_snapshot.len())
6929 .to_point(&multi_buffer_snapshot);
6930 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6931 self.debounced_selection_highlight_task = Some((
6932 query_range.clone(),
6933 self.update_selection_occurrence_highlights(
6934 query_text,
6935 query_range,
6936 multi_buffer_full_range,
6937 true,
6938 window,
6939 cx,
6940 ),
6941 ));
6942 }
6943 }
6944
6945 pub fn refresh_inline_completion(
6946 &mut self,
6947 debounce: bool,
6948 user_requested: bool,
6949 window: &mut Window,
6950 cx: &mut Context<Self>,
6951 ) -> Option<()> {
6952 let provider = self.edit_prediction_provider()?;
6953 let cursor = self.selections.newest_anchor().head();
6954 let (buffer, cursor_buffer_position) =
6955 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6956
6957 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6958 self.discard_inline_completion(false, cx);
6959 return None;
6960 }
6961
6962 if !user_requested
6963 && (!self.should_show_edit_predictions()
6964 || !self.is_focused(window)
6965 || buffer.read(cx).is_empty())
6966 {
6967 self.discard_inline_completion(false, cx);
6968 return None;
6969 }
6970
6971 self.update_visible_inline_completion(window, cx);
6972 provider.refresh(
6973 self.project.clone(),
6974 buffer,
6975 cursor_buffer_position,
6976 debounce,
6977 cx,
6978 );
6979 Some(())
6980 }
6981
6982 fn show_edit_predictions_in_menu(&self) -> bool {
6983 match self.edit_prediction_settings {
6984 EditPredictionSettings::Disabled => false,
6985 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6986 }
6987 }
6988
6989 pub fn edit_predictions_enabled(&self) -> bool {
6990 match self.edit_prediction_settings {
6991 EditPredictionSettings::Disabled => false,
6992 EditPredictionSettings::Enabled { .. } => true,
6993 }
6994 }
6995
6996 fn edit_prediction_requires_modifier(&self) -> bool {
6997 match self.edit_prediction_settings {
6998 EditPredictionSettings::Disabled => false,
6999 EditPredictionSettings::Enabled {
7000 preview_requires_modifier,
7001 ..
7002 } => preview_requires_modifier,
7003 }
7004 }
7005
7006 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7007 if self.edit_prediction_provider.is_none() {
7008 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7009 } else {
7010 let selection = self.selections.newest_anchor();
7011 let cursor = selection.head();
7012
7013 if let Some((buffer, cursor_buffer_position)) =
7014 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7015 {
7016 self.edit_prediction_settings =
7017 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7018 }
7019 }
7020 }
7021
7022 fn edit_prediction_settings_at_position(
7023 &self,
7024 buffer: &Entity<Buffer>,
7025 buffer_position: language::Anchor,
7026 cx: &App,
7027 ) -> EditPredictionSettings {
7028 if !self.mode.is_full()
7029 || !self.show_inline_completions_override.unwrap_or(true)
7030 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
7031 {
7032 return EditPredictionSettings::Disabled;
7033 }
7034
7035 let buffer = buffer.read(cx);
7036
7037 let file = buffer.file();
7038
7039 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7040 return EditPredictionSettings::Disabled;
7041 };
7042
7043 let by_provider = matches!(
7044 self.menu_inline_completions_policy,
7045 MenuInlineCompletionsPolicy::ByProvider
7046 );
7047
7048 let show_in_menu = by_provider
7049 && self
7050 .edit_prediction_provider
7051 .as_ref()
7052 .map_or(false, |provider| {
7053 provider.provider.show_completions_in_menu()
7054 });
7055
7056 let preview_requires_modifier =
7057 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7058
7059 EditPredictionSettings::Enabled {
7060 show_in_menu,
7061 preview_requires_modifier,
7062 }
7063 }
7064
7065 fn should_show_edit_predictions(&self) -> bool {
7066 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7067 }
7068
7069 pub fn edit_prediction_preview_is_active(&self) -> bool {
7070 matches!(
7071 self.edit_prediction_preview,
7072 EditPredictionPreview::Active { .. }
7073 )
7074 }
7075
7076 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7077 let cursor = self.selections.newest_anchor().head();
7078 if let Some((buffer, cursor_position)) =
7079 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7080 {
7081 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7082 } else {
7083 false
7084 }
7085 }
7086
7087 pub fn supports_minimap(&self, cx: &App) -> bool {
7088 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7089 }
7090
7091 fn edit_predictions_enabled_in_buffer(
7092 &self,
7093 buffer: &Entity<Buffer>,
7094 buffer_position: language::Anchor,
7095 cx: &App,
7096 ) -> bool {
7097 maybe!({
7098 if self.read_only(cx) {
7099 return Some(false);
7100 }
7101 let provider = self.edit_prediction_provider()?;
7102 if !provider.is_enabled(&buffer, buffer_position, cx) {
7103 return Some(false);
7104 }
7105 let buffer = buffer.read(cx);
7106 let Some(file) = buffer.file() else {
7107 return Some(true);
7108 };
7109 let settings = all_language_settings(Some(file), cx);
7110 Some(settings.edit_predictions_enabled_for_file(file, cx))
7111 })
7112 .unwrap_or(false)
7113 }
7114
7115 fn cycle_inline_completion(
7116 &mut self,
7117 direction: Direction,
7118 window: &mut Window,
7119 cx: &mut Context<Self>,
7120 ) -> Option<()> {
7121 let provider = self.edit_prediction_provider()?;
7122 let cursor = self.selections.newest_anchor().head();
7123 let (buffer, cursor_buffer_position) =
7124 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7125 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7126 return None;
7127 }
7128
7129 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7130 self.update_visible_inline_completion(window, cx);
7131
7132 Some(())
7133 }
7134
7135 pub fn show_inline_completion(
7136 &mut self,
7137 _: &ShowEditPrediction,
7138 window: &mut Window,
7139 cx: &mut Context<Self>,
7140 ) {
7141 if !self.has_active_inline_completion() {
7142 self.refresh_inline_completion(false, true, window, cx);
7143 return;
7144 }
7145
7146 self.update_visible_inline_completion(window, cx);
7147 }
7148
7149 pub fn display_cursor_names(
7150 &mut self,
7151 _: &DisplayCursorNames,
7152 window: &mut Window,
7153 cx: &mut Context<Self>,
7154 ) {
7155 self.show_cursor_names(window, cx);
7156 }
7157
7158 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7159 self.show_cursor_names = true;
7160 cx.notify();
7161 cx.spawn_in(window, async move |this, cx| {
7162 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7163 this.update(cx, |this, cx| {
7164 this.show_cursor_names = false;
7165 cx.notify()
7166 })
7167 .ok()
7168 })
7169 .detach();
7170 }
7171
7172 pub fn next_edit_prediction(
7173 &mut self,
7174 _: &NextEditPrediction,
7175 window: &mut Window,
7176 cx: &mut Context<Self>,
7177 ) {
7178 if self.has_active_inline_completion() {
7179 self.cycle_inline_completion(Direction::Next, window, cx);
7180 } else {
7181 let is_copilot_disabled = self
7182 .refresh_inline_completion(false, true, window, cx)
7183 .is_none();
7184 if is_copilot_disabled {
7185 cx.propagate();
7186 }
7187 }
7188 }
7189
7190 pub fn previous_edit_prediction(
7191 &mut self,
7192 _: &PreviousEditPrediction,
7193 window: &mut Window,
7194 cx: &mut Context<Self>,
7195 ) {
7196 if self.has_active_inline_completion() {
7197 self.cycle_inline_completion(Direction::Prev, window, cx);
7198 } else {
7199 let is_copilot_disabled = self
7200 .refresh_inline_completion(false, true, window, cx)
7201 .is_none();
7202 if is_copilot_disabled {
7203 cx.propagate();
7204 }
7205 }
7206 }
7207
7208 pub fn accept_edit_prediction(
7209 &mut self,
7210 _: &AcceptEditPrediction,
7211 window: &mut Window,
7212 cx: &mut Context<Self>,
7213 ) {
7214 if self.show_edit_predictions_in_menu() {
7215 self.hide_context_menu(window, cx);
7216 }
7217
7218 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7219 return;
7220 };
7221
7222 self.report_inline_completion_event(
7223 active_inline_completion.completion_id.clone(),
7224 true,
7225 cx,
7226 );
7227
7228 match &active_inline_completion.completion {
7229 InlineCompletion::Move { target, .. } => {
7230 let target = *target;
7231
7232 if let Some(position_map) = &self.last_position_map {
7233 if position_map
7234 .visible_row_range
7235 .contains(&target.to_display_point(&position_map.snapshot).row())
7236 || !self.edit_prediction_requires_modifier()
7237 {
7238 self.unfold_ranges(&[target..target], true, false, cx);
7239 // Note that this is also done in vim's handler of the Tab action.
7240 self.change_selections(
7241 SelectionEffects::scroll(Autoscroll::newest()),
7242 window,
7243 cx,
7244 |selections| {
7245 selections.select_anchor_ranges([target..target]);
7246 },
7247 );
7248 self.clear_row_highlights::<EditPredictionPreview>();
7249
7250 self.edit_prediction_preview
7251 .set_previous_scroll_position(None);
7252 } else {
7253 self.edit_prediction_preview
7254 .set_previous_scroll_position(Some(
7255 position_map.snapshot.scroll_anchor,
7256 ));
7257
7258 self.highlight_rows::<EditPredictionPreview>(
7259 target..target,
7260 cx.theme().colors().editor_highlighted_line_background,
7261 RowHighlightOptions {
7262 autoscroll: true,
7263 ..Default::default()
7264 },
7265 cx,
7266 );
7267 self.request_autoscroll(Autoscroll::fit(), cx);
7268 }
7269 }
7270 }
7271 InlineCompletion::Edit { edits, .. } => {
7272 if let Some(provider) = self.edit_prediction_provider() {
7273 provider.accept(cx);
7274 }
7275
7276 // Store the transaction ID and selections before applying the edit
7277 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7278
7279 let snapshot = self.buffer.read(cx).snapshot(cx);
7280 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7281
7282 self.buffer.update(cx, |buffer, cx| {
7283 buffer.edit(edits.iter().cloned(), None, cx)
7284 });
7285
7286 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7287 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7288 });
7289
7290 let selections = self.selections.disjoint_anchors();
7291 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7292 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7293 if has_new_transaction {
7294 self.selection_history
7295 .insert_transaction(transaction_id_now, selections);
7296 }
7297 }
7298
7299 self.update_visible_inline_completion(window, cx);
7300 if self.active_inline_completion.is_none() {
7301 self.refresh_inline_completion(true, true, window, cx);
7302 }
7303
7304 cx.notify();
7305 }
7306 }
7307
7308 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7309 }
7310
7311 pub fn accept_partial_inline_completion(
7312 &mut self,
7313 _: &AcceptPartialEditPrediction,
7314 window: &mut Window,
7315 cx: &mut Context<Self>,
7316 ) {
7317 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7318 return;
7319 };
7320 if self.selections.count() != 1 {
7321 return;
7322 }
7323
7324 self.report_inline_completion_event(
7325 active_inline_completion.completion_id.clone(),
7326 true,
7327 cx,
7328 );
7329
7330 match &active_inline_completion.completion {
7331 InlineCompletion::Move { target, .. } => {
7332 let target = *target;
7333 self.change_selections(
7334 SelectionEffects::scroll(Autoscroll::newest()),
7335 window,
7336 cx,
7337 |selections| {
7338 selections.select_anchor_ranges([target..target]);
7339 },
7340 );
7341 }
7342 InlineCompletion::Edit { edits, .. } => {
7343 // Find an insertion that starts at the cursor position.
7344 let snapshot = self.buffer.read(cx).snapshot(cx);
7345 let cursor_offset = self.selections.newest::<usize>(cx).head();
7346 let insertion = edits.iter().find_map(|(range, text)| {
7347 let range = range.to_offset(&snapshot);
7348 if range.is_empty() && range.start == cursor_offset {
7349 Some(text)
7350 } else {
7351 None
7352 }
7353 });
7354
7355 if let Some(text) = insertion {
7356 let mut partial_completion = text
7357 .chars()
7358 .by_ref()
7359 .take_while(|c| c.is_alphabetic())
7360 .collect::<String>();
7361 if partial_completion.is_empty() {
7362 partial_completion = text
7363 .chars()
7364 .by_ref()
7365 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7366 .collect::<String>();
7367 }
7368
7369 cx.emit(EditorEvent::InputHandled {
7370 utf16_range_to_replace: None,
7371 text: partial_completion.clone().into(),
7372 });
7373
7374 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7375
7376 self.refresh_inline_completion(true, true, window, cx);
7377 cx.notify();
7378 } else {
7379 self.accept_edit_prediction(&Default::default(), window, cx);
7380 }
7381 }
7382 }
7383 }
7384
7385 fn discard_inline_completion(
7386 &mut self,
7387 should_report_inline_completion_event: bool,
7388 cx: &mut Context<Self>,
7389 ) -> bool {
7390 if should_report_inline_completion_event {
7391 let completion_id = self
7392 .active_inline_completion
7393 .as_ref()
7394 .and_then(|active_completion| active_completion.completion_id.clone());
7395
7396 self.report_inline_completion_event(completion_id, false, cx);
7397 }
7398
7399 if let Some(provider) = self.edit_prediction_provider() {
7400 provider.discard(cx);
7401 }
7402
7403 self.take_active_inline_completion(cx)
7404 }
7405
7406 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7407 let Some(provider) = self.edit_prediction_provider() else {
7408 return;
7409 };
7410
7411 let Some((_, buffer, _)) = self
7412 .buffer
7413 .read(cx)
7414 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7415 else {
7416 return;
7417 };
7418
7419 let extension = buffer
7420 .read(cx)
7421 .file()
7422 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7423
7424 let event_type = match accepted {
7425 true => "Edit Prediction Accepted",
7426 false => "Edit Prediction Discarded",
7427 };
7428 telemetry::event!(
7429 event_type,
7430 provider = provider.name(),
7431 prediction_id = id,
7432 suggestion_accepted = accepted,
7433 file_extension = extension,
7434 );
7435 }
7436
7437 pub fn has_active_inline_completion(&self) -> bool {
7438 self.active_inline_completion.is_some()
7439 }
7440
7441 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7442 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7443 return false;
7444 };
7445
7446 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7447 self.clear_highlights::<InlineCompletionHighlight>(cx);
7448 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7449 true
7450 }
7451
7452 /// Returns true when we're displaying the edit prediction popover below the cursor
7453 /// like we are not previewing and the LSP autocomplete menu is visible
7454 /// or we are in `when_holding_modifier` mode.
7455 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7456 if self.edit_prediction_preview_is_active()
7457 || !self.show_edit_predictions_in_menu()
7458 || !self.edit_predictions_enabled()
7459 {
7460 return false;
7461 }
7462
7463 if self.has_visible_completions_menu() {
7464 return true;
7465 }
7466
7467 has_completion && self.edit_prediction_requires_modifier()
7468 }
7469
7470 fn handle_modifiers_changed(
7471 &mut self,
7472 modifiers: Modifiers,
7473 position_map: &PositionMap,
7474 window: &mut Window,
7475 cx: &mut Context<Self>,
7476 ) {
7477 if self.show_edit_predictions_in_menu() {
7478 self.update_edit_prediction_preview(&modifiers, window, cx);
7479 }
7480
7481 self.update_selection_mode(&modifiers, position_map, window, cx);
7482
7483 let mouse_position = window.mouse_position();
7484 if !position_map.text_hitbox.is_hovered(window) {
7485 return;
7486 }
7487
7488 self.update_hovered_link(
7489 position_map.point_for_position(mouse_position),
7490 &position_map.snapshot,
7491 modifiers,
7492 window,
7493 cx,
7494 )
7495 }
7496
7497 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7498 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7499 if invert {
7500 match multi_cursor_setting {
7501 MultiCursorModifier::Alt => modifiers.alt,
7502 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7503 }
7504 } else {
7505 match multi_cursor_setting {
7506 MultiCursorModifier::Alt => modifiers.secondary(),
7507 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7508 }
7509 }
7510 }
7511
7512 fn columnar_selection_mode(
7513 modifiers: &Modifiers,
7514 cx: &mut Context<Self>,
7515 ) -> Option<ColumnarMode> {
7516 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7517 if Self::multi_cursor_modifier(false, modifiers, cx) {
7518 Some(ColumnarMode::FromMouse)
7519 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7520 Some(ColumnarMode::FromSelection)
7521 } else {
7522 None
7523 }
7524 } else {
7525 None
7526 }
7527 }
7528
7529 fn update_selection_mode(
7530 &mut self,
7531 modifiers: &Modifiers,
7532 position_map: &PositionMap,
7533 window: &mut Window,
7534 cx: &mut Context<Self>,
7535 ) {
7536 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7537 return;
7538 };
7539 if self.selections.pending.is_none() {
7540 return;
7541 }
7542
7543 let mouse_position = window.mouse_position();
7544 let point_for_position = position_map.point_for_position(mouse_position);
7545 let position = point_for_position.previous_valid;
7546
7547 self.select(
7548 SelectPhase::BeginColumnar {
7549 position,
7550 reset: false,
7551 mode,
7552 goal_column: point_for_position.exact_unclipped.column(),
7553 },
7554 window,
7555 cx,
7556 );
7557 }
7558
7559 fn update_edit_prediction_preview(
7560 &mut self,
7561 modifiers: &Modifiers,
7562 window: &mut Window,
7563 cx: &mut Context<Self>,
7564 ) {
7565 let mut modifiers_held = false;
7566 if let Some(accept_keystroke) = self
7567 .accept_edit_prediction_keybind(false, window, cx)
7568 .keystroke()
7569 {
7570 modifiers_held = modifiers_held
7571 || (&accept_keystroke.modifiers == modifiers
7572 && accept_keystroke.modifiers.modified());
7573 };
7574 if let Some(accept_partial_keystroke) = self
7575 .accept_edit_prediction_keybind(true, window, cx)
7576 .keystroke()
7577 {
7578 modifiers_held = modifiers_held
7579 || (&accept_partial_keystroke.modifiers == modifiers
7580 && accept_partial_keystroke.modifiers.modified());
7581 }
7582
7583 if modifiers_held {
7584 if matches!(
7585 self.edit_prediction_preview,
7586 EditPredictionPreview::Inactive { .. }
7587 ) {
7588 self.edit_prediction_preview = EditPredictionPreview::Active {
7589 previous_scroll_position: None,
7590 since: Instant::now(),
7591 };
7592
7593 self.update_visible_inline_completion(window, cx);
7594 cx.notify();
7595 }
7596 } else if let EditPredictionPreview::Active {
7597 previous_scroll_position,
7598 since,
7599 } = self.edit_prediction_preview
7600 {
7601 if let (Some(previous_scroll_position), Some(position_map)) =
7602 (previous_scroll_position, self.last_position_map.as_ref())
7603 {
7604 self.set_scroll_position(
7605 previous_scroll_position
7606 .scroll_position(&position_map.snapshot.display_snapshot),
7607 window,
7608 cx,
7609 );
7610 }
7611
7612 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7613 released_too_fast: since.elapsed() < Duration::from_millis(200),
7614 };
7615 self.clear_row_highlights::<EditPredictionPreview>();
7616 self.update_visible_inline_completion(window, cx);
7617 cx.notify();
7618 }
7619 }
7620
7621 fn update_visible_inline_completion(
7622 &mut self,
7623 _window: &mut Window,
7624 cx: &mut Context<Self>,
7625 ) -> Option<()> {
7626 let selection = self.selections.newest_anchor();
7627 let cursor = selection.head();
7628 let multibuffer = self.buffer.read(cx).snapshot(cx);
7629 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7630 let excerpt_id = cursor.excerpt_id;
7631
7632 let show_in_menu = self.show_edit_predictions_in_menu();
7633 let completions_menu_has_precedence = !show_in_menu
7634 && (self.context_menu.borrow().is_some()
7635 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7636
7637 if completions_menu_has_precedence
7638 || !offset_selection.is_empty()
7639 || self
7640 .active_inline_completion
7641 .as_ref()
7642 .map_or(false, |completion| {
7643 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7644 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7645 !invalidation_range.contains(&offset_selection.head())
7646 })
7647 {
7648 self.discard_inline_completion(false, cx);
7649 return None;
7650 }
7651
7652 self.take_active_inline_completion(cx);
7653 let Some(provider) = self.edit_prediction_provider() else {
7654 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7655 return None;
7656 };
7657
7658 let (buffer, cursor_buffer_position) =
7659 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7660
7661 self.edit_prediction_settings =
7662 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7663
7664 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7665
7666 if self.edit_prediction_indent_conflict {
7667 let cursor_point = cursor.to_point(&multibuffer);
7668
7669 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7670
7671 if let Some((_, indent)) = indents.iter().next() {
7672 if indent.len == cursor_point.column {
7673 self.edit_prediction_indent_conflict = false;
7674 }
7675 }
7676 }
7677
7678 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7679 let edits = inline_completion
7680 .edits
7681 .into_iter()
7682 .flat_map(|(range, new_text)| {
7683 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7684 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7685 Some((start..end, new_text))
7686 })
7687 .collect::<Vec<_>>();
7688 if edits.is_empty() {
7689 return None;
7690 }
7691
7692 let first_edit_start = edits.first().unwrap().0.start;
7693 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7694 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7695
7696 let last_edit_end = edits.last().unwrap().0.end;
7697 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7698 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7699
7700 let cursor_row = cursor.to_point(&multibuffer).row;
7701
7702 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7703
7704 let mut inlay_ids = Vec::new();
7705 let invalidation_row_range;
7706 let move_invalidation_row_range = if cursor_row < edit_start_row {
7707 Some(cursor_row..edit_end_row)
7708 } else if cursor_row > edit_end_row {
7709 Some(edit_start_row..cursor_row)
7710 } else {
7711 None
7712 };
7713 let is_move =
7714 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7715 let completion = if is_move {
7716 invalidation_row_range =
7717 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7718 let target = first_edit_start;
7719 InlineCompletion::Move { target, snapshot }
7720 } else {
7721 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7722 && !self.inline_completions_hidden_for_vim_mode;
7723
7724 if show_completions_in_buffer {
7725 if edits
7726 .iter()
7727 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7728 {
7729 let mut inlays = Vec::new();
7730 for (range, new_text) in &edits {
7731 let inlay = Inlay::inline_completion(
7732 post_inc(&mut self.next_inlay_id),
7733 range.start,
7734 new_text.as_str(),
7735 );
7736 inlay_ids.push(inlay.id);
7737 inlays.push(inlay);
7738 }
7739
7740 self.splice_inlays(&[], inlays, cx);
7741 } else {
7742 let background_color = cx.theme().status().deleted_background;
7743 self.highlight_text::<InlineCompletionHighlight>(
7744 edits.iter().map(|(range, _)| range.clone()).collect(),
7745 HighlightStyle {
7746 background_color: Some(background_color),
7747 ..Default::default()
7748 },
7749 cx,
7750 );
7751 }
7752 }
7753
7754 invalidation_row_range = edit_start_row..edit_end_row;
7755
7756 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7757 if provider.show_tab_accept_marker() {
7758 EditDisplayMode::TabAccept
7759 } else {
7760 EditDisplayMode::Inline
7761 }
7762 } else {
7763 EditDisplayMode::DiffPopover
7764 };
7765
7766 InlineCompletion::Edit {
7767 edits,
7768 edit_preview: inline_completion.edit_preview,
7769 display_mode,
7770 snapshot,
7771 }
7772 };
7773
7774 let invalidation_range = multibuffer
7775 .anchor_before(Point::new(invalidation_row_range.start, 0))
7776 ..multibuffer.anchor_after(Point::new(
7777 invalidation_row_range.end,
7778 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7779 ));
7780
7781 self.stale_inline_completion_in_menu = None;
7782 self.active_inline_completion = Some(InlineCompletionState {
7783 inlay_ids,
7784 completion,
7785 completion_id: inline_completion.id,
7786 invalidation_range,
7787 });
7788
7789 cx.notify();
7790
7791 Some(())
7792 }
7793
7794 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7795 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7796 }
7797
7798 fn clear_tasks(&mut self) {
7799 self.tasks.clear()
7800 }
7801
7802 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7803 if self.tasks.insert(key, value).is_some() {
7804 // This case should hopefully be rare, but just in case...
7805 log::error!(
7806 "multiple different run targets found on a single line, only the last target will be rendered"
7807 )
7808 }
7809 }
7810
7811 /// Get all display points of breakpoints that will be rendered within editor
7812 ///
7813 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7814 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7815 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7816 fn active_breakpoints(
7817 &self,
7818 range: Range<DisplayRow>,
7819 window: &mut Window,
7820 cx: &mut Context<Self>,
7821 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7822 let mut breakpoint_display_points = HashMap::default();
7823
7824 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7825 return breakpoint_display_points;
7826 };
7827
7828 let snapshot = self.snapshot(window, cx);
7829
7830 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7831 let Some(project) = self.project.as_ref() else {
7832 return breakpoint_display_points;
7833 };
7834
7835 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7836 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7837
7838 for (buffer_snapshot, range, excerpt_id) in
7839 multi_buffer_snapshot.range_to_buffer_ranges(range)
7840 {
7841 let Some(buffer) = project
7842 .read(cx)
7843 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7844 else {
7845 continue;
7846 };
7847 let breakpoints = breakpoint_store.read(cx).breakpoints(
7848 &buffer,
7849 Some(
7850 buffer_snapshot.anchor_before(range.start)
7851 ..buffer_snapshot.anchor_after(range.end),
7852 ),
7853 buffer_snapshot,
7854 cx,
7855 );
7856 for (breakpoint, state) in breakpoints {
7857 let multi_buffer_anchor =
7858 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7859 let position = multi_buffer_anchor
7860 .to_point(&multi_buffer_snapshot)
7861 .to_display_point(&snapshot);
7862
7863 breakpoint_display_points.insert(
7864 position.row(),
7865 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7866 );
7867 }
7868 }
7869
7870 breakpoint_display_points
7871 }
7872
7873 fn breakpoint_context_menu(
7874 &self,
7875 anchor: Anchor,
7876 window: &mut Window,
7877 cx: &mut Context<Self>,
7878 ) -> Entity<ui::ContextMenu> {
7879 let weak_editor = cx.weak_entity();
7880 let focus_handle = self.focus_handle(cx);
7881
7882 let row = self
7883 .buffer
7884 .read(cx)
7885 .snapshot(cx)
7886 .summary_for_anchor::<Point>(&anchor)
7887 .row;
7888
7889 let breakpoint = self
7890 .breakpoint_at_row(row, window, cx)
7891 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7892
7893 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7894 "Edit Log Breakpoint"
7895 } else {
7896 "Set Log Breakpoint"
7897 };
7898
7899 let condition_breakpoint_msg = if breakpoint
7900 .as_ref()
7901 .is_some_and(|bp| bp.1.condition.is_some())
7902 {
7903 "Edit Condition Breakpoint"
7904 } else {
7905 "Set Condition Breakpoint"
7906 };
7907
7908 let hit_condition_breakpoint_msg = if breakpoint
7909 .as_ref()
7910 .is_some_and(|bp| bp.1.hit_condition.is_some())
7911 {
7912 "Edit Hit Condition Breakpoint"
7913 } else {
7914 "Set Hit Condition Breakpoint"
7915 };
7916
7917 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7918 "Unset Breakpoint"
7919 } else {
7920 "Set Breakpoint"
7921 };
7922
7923 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7924
7925 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7926 BreakpointState::Enabled => Some("Disable"),
7927 BreakpointState::Disabled => Some("Enable"),
7928 });
7929
7930 let (anchor, breakpoint) =
7931 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7932
7933 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7934 menu.on_blur_subscription(Subscription::new(|| {}))
7935 .context(focus_handle)
7936 .when(run_to_cursor, |this| {
7937 let weak_editor = weak_editor.clone();
7938 this.entry("Run to cursor", None, move |window, cx| {
7939 weak_editor
7940 .update(cx, |editor, cx| {
7941 editor.change_selections(
7942 SelectionEffects::no_scroll(),
7943 window,
7944 cx,
7945 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
7946 );
7947 })
7948 .ok();
7949
7950 window.dispatch_action(Box::new(RunToCursor), cx);
7951 })
7952 .separator()
7953 })
7954 .when_some(toggle_state_msg, |this, msg| {
7955 this.entry(msg, None, {
7956 let weak_editor = weak_editor.clone();
7957 let breakpoint = breakpoint.clone();
7958 move |_window, cx| {
7959 weak_editor
7960 .update(cx, |this, cx| {
7961 this.edit_breakpoint_at_anchor(
7962 anchor,
7963 breakpoint.as_ref().clone(),
7964 BreakpointEditAction::InvertState,
7965 cx,
7966 );
7967 })
7968 .log_err();
7969 }
7970 })
7971 })
7972 .entry(set_breakpoint_msg, None, {
7973 let weak_editor = weak_editor.clone();
7974 let breakpoint = breakpoint.clone();
7975 move |_window, cx| {
7976 weak_editor
7977 .update(cx, |this, cx| {
7978 this.edit_breakpoint_at_anchor(
7979 anchor,
7980 breakpoint.as_ref().clone(),
7981 BreakpointEditAction::Toggle,
7982 cx,
7983 );
7984 })
7985 .log_err();
7986 }
7987 })
7988 .entry(log_breakpoint_msg, None, {
7989 let breakpoint = breakpoint.clone();
7990 let weak_editor = weak_editor.clone();
7991 move |window, cx| {
7992 weak_editor
7993 .update(cx, |this, cx| {
7994 this.add_edit_breakpoint_block(
7995 anchor,
7996 breakpoint.as_ref(),
7997 BreakpointPromptEditAction::Log,
7998 window,
7999 cx,
8000 );
8001 })
8002 .log_err();
8003 }
8004 })
8005 .entry(condition_breakpoint_msg, None, {
8006 let breakpoint = breakpoint.clone();
8007 let weak_editor = weak_editor.clone();
8008 move |window, cx| {
8009 weak_editor
8010 .update(cx, |this, cx| {
8011 this.add_edit_breakpoint_block(
8012 anchor,
8013 breakpoint.as_ref(),
8014 BreakpointPromptEditAction::Condition,
8015 window,
8016 cx,
8017 );
8018 })
8019 .log_err();
8020 }
8021 })
8022 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8023 weak_editor
8024 .update(cx, |this, cx| {
8025 this.add_edit_breakpoint_block(
8026 anchor,
8027 breakpoint.as_ref(),
8028 BreakpointPromptEditAction::HitCondition,
8029 window,
8030 cx,
8031 );
8032 })
8033 .log_err();
8034 })
8035 })
8036 }
8037
8038 fn render_breakpoint(
8039 &self,
8040 position: Anchor,
8041 row: DisplayRow,
8042 breakpoint: &Breakpoint,
8043 state: Option<BreakpointSessionState>,
8044 cx: &mut Context<Self>,
8045 ) -> IconButton {
8046 let is_rejected = state.is_some_and(|s| !s.verified);
8047 // Is it a breakpoint that shows up when hovering over gutter?
8048 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8049 (false, false),
8050 |PhantomBreakpointIndicator {
8051 is_active,
8052 display_row,
8053 collides_with_existing_breakpoint,
8054 }| {
8055 (
8056 is_active && display_row == row,
8057 collides_with_existing_breakpoint,
8058 )
8059 },
8060 );
8061
8062 let (color, icon) = {
8063 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8064 (false, false) => ui::IconName::DebugBreakpoint,
8065 (true, false) => ui::IconName::DebugLogBreakpoint,
8066 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8067 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8068 };
8069
8070 let color = if is_phantom {
8071 Color::Hint
8072 } else if is_rejected {
8073 Color::Disabled
8074 } else {
8075 Color::Debugger
8076 };
8077
8078 (color, icon)
8079 };
8080
8081 let breakpoint = Arc::from(breakpoint.clone());
8082
8083 let alt_as_text = gpui::Keystroke {
8084 modifiers: Modifiers::secondary_key(),
8085 ..Default::default()
8086 };
8087 let primary_action_text = if breakpoint.is_disabled() {
8088 "Enable breakpoint"
8089 } else if is_phantom && !collides_with_existing {
8090 "Set breakpoint"
8091 } else {
8092 "Unset breakpoint"
8093 };
8094 let focus_handle = self.focus_handle.clone();
8095
8096 let meta = if is_rejected {
8097 SharedString::from("No executable code is associated with this line.")
8098 } else if collides_with_existing && !breakpoint.is_disabled() {
8099 SharedString::from(format!(
8100 "{alt_as_text}-click to disable,\nright-click for more options."
8101 ))
8102 } else {
8103 SharedString::from("Right-click for more options.")
8104 };
8105 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8106 .icon_size(IconSize::XSmall)
8107 .size(ui::ButtonSize::None)
8108 .when(is_rejected, |this| {
8109 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8110 })
8111 .icon_color(color)
8112 .style(ButtonStyle::Transparent)
8113 .on_click(cx.listener({
8114 let breakpoint = breakpoint.clone();
8115
8116 move |editor, event: &ClickEvent, window, cx| {
8117 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8118 BreakpointEditAction::InvertState
8119 } else {
8120 BreakpointEditAction::Toggle
8121 };
8122
8123 window.focus(&editor.focus_handle(cx));
8124 editor.edit_breakpoint_at_anchor(
8125 position,
8126 breakpoint.as_ref().clone(),
8127 edit_action,
8128 cx,
8129 );
8130 }
8131 }))
8132 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8133 editor.set_breakpoint_context_menu(
8134 row,
8135 Some(position),
8136 event.down.position,
8137 window,
8138 cx,
8139 );
8140 }))
8141 .tooltip(move |window, cx| {
8142 Tooltip::with_meta_in(
8143 primary_action_text,
8144 Some(&ToggleBreakpoint),
8145 meta.clone(),
8146 &focus_handle,
8147 window,
8148 cx,
8149 )
8150 })
8151 }
8152
8153 fn build_tasks_context(
8154 project: &Entity<Project>,
8155 buffer: &Entity<Buffer>,
8156 buffer_row: u32,
8157 tasks: &Arc<RunnableTasks>,
8158 cx: &mut Context<Self>,
8159 ) -> Task<Option<task::TaskContext>> {
8160 let position = Point::new(buffer_row, tasks.column);
8161 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8162 let location = Location {
8163 buffer: buffer.clone(),
8164 range: range_start..range_start,
8165 };
8166 // Fill in the environmental variables from the tree-sitter captures
8167 let mut captured_task_variables = TaskVariables::default();
8168 for (capture_name, value) in tasks.extra_variables.clone() {
8169 captured_task_variables.insert(
8170 task::VariableName::Custom(capture_name.into()),
8171 value.clone(),
8172 );
8173 }
8174 project.update(cx, |project, cx| {
8175 project.task_store().update(cx, |task_store, cx| {
8176 task_store.task_context_for_location(captured_task_variables, location, cx)
8177 })
8178 })
8179 }
8180
8181 pub fn spawn_nearest_task(
8182 &mut self,
8183 action: &SpawnNearestTask,
8184 window: &mut Window,
8185 cx: &mut Context<Self>,
8186 ) {
8187 let Some((workspace, _)) = self.workspace.clone() else {
8188 return;
8189 };
8190 let Some(project) = self.project.clone() else {
8191 return;
8192 };
8193
8194 // Try to find a closest, enclosing node using tree-sitter that has a
8195 // task
8196 let Some((buffer, buffer_row, tasks)) = self
8197 .find_enclosing_node_task(cx)
8198 // Or find the task that's closest in row-distance.
8199 .or_else(|| self.find_closest_task(cx))
8200 else {
8201 return;
8202 };
8203
8204 let reveal_strategy = action.reveal;
8205 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8206 cx.spawn_in(window, async move |_, cx| {
8207 let context = task_context.await?;
8208 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8209
8210 let resolved = &mut resolved_task.resolved;
8211 resolved.reveal = reveal_strategy;
8212
8213 workspace
8214 .update_in(cx, |workspace, window, cx| {
8215 workspace.schedule_resolved_task(
8216 task_source_kind,
8217 resolved_task,
8218 false,
8219 window,
8220 cx,
8221 );
8222 })
8223 .ok()
8224 })
8225 .detach();
8226 }
8227
8228 fn find_closest_task(
8229 &mut self,
8230 cx: &mut Context<Self>,
8231 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8232 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8233
8234 let ((buffer_id, row), tasks) = self
8235 .tasks
8236 .iter()
8237 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8238
8239 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8240 let tasks = Arc::new(tasks.to_owned());
8241 Some((buffer, *row, tasks))
8242 }
8243
8244 fn find_enclosing_node_task(
8245 &mut self,
8246 cx: &mut Context<Self>,
8247 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8248 let snapshot = self.buffer.read(cx).snapshot(cx);
8249 let offset = self.selections.newest::<usize>(cx).head();
8250 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8251 let buffer_id = excerpt.buffer().remote_id();
8252
8253 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8254 let mut cursor = layer.node().walk();
8255
8256 while cursor.goto_first_child_for_byte(offset).is_some() {
8257 if cursor.node().end_byte() == offset {
8258 cursor.goto_next_sibling();
8259 }
8260 }
8261
8262 // Ascend to the smallest ancestor that contains the range and has a task.
8263 loop {
8264 let node = cursor.node();
8265 let node_range = node.byte_range();
8266 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8267
8268 // Check if this node contains our offset
8269 if node_range.start <= offset && node_range.end >= offset {
8270 // If it contains offset, check for task
8271 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8272 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8273 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8274 }
8275 }
8276
8277 if !cursor.goto_parent() {
8278 break;
8279 }
8280 }
8281 None
8282 }
8283
8284 fn render_run_indicator(
8285 &self,
8286 _style: &EditorStyle,
8287 is_active: bool,
8288 row: DisplayRow,
8289 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8290 cx: &mut Context<Self>,
8291 ) -> IconButton {
8292 let color = Color::Muted;
8293 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8294
8295 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
8296 .shape(ui::IconButtonShape::Square)
8297 .icon_size(IconSize::XSmall)
8298 .icon_color(color)
8299 .toggle_state(is_active)
8300 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8301 let quick_launch = e.down.button == MouseButton::Left;
8302 window.focus(&editor.focus_handle(cx));
8303 editor.toggle_code_actions(
8304 &ToggleCodeActions {
8305 deployed_from: Some(CodeActionSource::RunMenu(row)),
8306 quick_launch,
8307 },
8308 window,
8309 cx,
8310 );
8311 }))
8312 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8313 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
8314 }))
8315 }
8316
8317 pub fn context_menu_visible(&self) -> bool {
8318 !self.edit_prediction_preview_is_active()
8319 && self
8320 .context_menu
8321 .borrow()
8322 .as_ref()
8323 .map_or(false, |menu| menu.visible())
8324 }
8325
8326 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8327 self.context_menu
8328 .borrow()
8329 .as_ref()
8330 .map(|menu| menu.origin())
8331 }
8332
8333 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8334 self.context_menu_options = Some(options);
8335 }
8336
8337 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8338 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8339
8340 fn render_edit_prediction_popover(
8341 &mut self,
8342 text_bounds: &Bounds<Pixels>,
8343 content_origin: gpui::Point<Pixels>,
8344 right_margin: Pixels,
8345 editor_snapshot: &EditorSnapshot,
8346 visible_row_range: Range<DisplayRow>,
8347 scroll_top: f32,
8348 scroll_bottom: f32,
8349 line_layouts: &[LineWithInvisibles],
8350 line_height: Pixels,
8351 scroll_pixel_position: gpui::Point<Pixels>,
8352 newest_selection_head: Option<DisplayPoint>,
8353 editor_width: Pixels,
8354 style: &EditorStyle,
8355 window: &mut Window,
8356 cx: &mut App,
8357 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8358 if self.mode().is_minimap() {
8359 return None;
8360 }
8361 let active_inline_completion = self.active_inline_completion.as_ref()?;
8362
8363 if self.edit_prediction_visible_in_cursor_popover(true) {
8364 return None;
8365 }
8366
8367 match &active_inline_completion.completion {
8368 InlineCompletion::Move { target, .. } => {
8369 let target_display_point = target.to_display_point(editor_snapshot);
8370
8371 if self.edit_prediction_requires_modifier() {
8372 if !self.edit_prediction_preview_is_active() {
8373 return None;
8374 }
8375
8376 self.render_edit_prediction_modifier_jump_popover(
8377 text_bounds,
8378 content_origin,
8379 visible_row_range,
8380 line_layouts,
8381 line_height,
8382 scroll_pixel_position,
8383 newest_selection_head,
8384 target_display_point,
8385 window,
8386 cx,
8387 )
8388 } else {
8389 self.render_edit_prediction_eager_jump_popover(
8390 text_bounds,
8391 content_origin,
8392 editor_snapshot,
8393 visible_row_range,
8394 scroll_top,
8395 scroll_bottom,
8396 line_height,
8397 scroll_pixel_position,
8398 target_display_point,
8399 editor_width,
8400 window,
8401 cx,
8402 )
8403 }
8404 }
8405 InlineCompletion::Edit {
8406 display_mode: EditDisplayMode::Inline,
8407 ..
8408 } => None,
8409 InlineCompletion::Edit {
8410 display_mode: EditDisplayMode::TabAccept,
8411 edits,
8412 ..
8413 } => {
8414 let range = &edits.first()?.0;
8415 let target_display_point = range.end.to_display_point(editor_snapshot);
8416
8417 self.render_edit_prediction_end_of_line_popover(
8418 "Accept",
8419 editor_snapshot,
8420 visible_row_range,
8421 target_display_point,
8422 line_height,
8423 scroll_pixel_position,
8424 content_origin,
8425 editor_width,
8426 window,
8427 cx,
8428 )
8429 }
8430 InlineCompletion::Edit {
8431 edits,
8432 edit_preview,
8433 display_mode: EditDisplayMode::DiffPopover,
8434 snapshot,
8435 } => self.render_edit_prediction_diff_popover(
8436 text_bounds,
8437 content_origin,
8438 right_margin,
8439 editor_snapshot,
8440 visible_row_range,
8441 line_layouts,
8442 line_height,
8443 scroll_pixel_position,
8444 newest_selection_head,
8445 editor_width,
8446 style,
8447 edits,
8448 edit_preview,
8449 snapshot,
8450 window,
8451 cx,
8452 ),
8453 }
8454 }
8455
8456 fn render_edit_prediction_modifier_jump_popover(
8457 &mut self,
8458 text_bounds: &Bounds<Pixels>,
8459 content_origin: gpui::Point<Pixels>,
8460 visible_row_range: Range<DisplayRow>,
8461 line_layouts: &[LineWithInvisibles],
8462 line_height: Pixels,
8463 scroll_pixel_position: gpui::Point<Pixels>,
8464 newest_selection_head: Option<DisplayPoint>,
8465 target_display_point: DisplayPoint,
8466 window: &mut Window,
8467 cx: &mut App,
8468 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8469 let scrolled_content_origin =
8470 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8471
8472 const SCROLL_PADDING_Y: Pixels = px(12.);
8473
8474 if target_display_point.row() < visible_row_range.start {
8475 return self.render_edit_prediction_scroll_popover(
8476 |_| SCROLL_PADDING_Y,
8477 IconName::ArrowUp,
8478 visible_row_range,
8479 line_layouts,
8480 newest_selection_head,
8481 scrolled_content_origin,
8482 window,
8483 cx,
8484 );
8485 } else if target_display_point.row() >= visible_row_range.end {
8486 return self.render_edit_prediction_scroll_popover(
8487 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8488 IconName::ArrowDown,
8489 visible_row_range,
8490 line_layouts,
8491 newest_selection_head,
8492 scrolled_content_origin,
8493 window,
8494 cx,
8495 );
8496 }
8497
8498 const POLE_WIDTH: Pixels = px(2.);
8499
8500 let line_layout =
8501 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8502 let target_column = target_display_point.column() as usize;
8503
8504 let target_x = line_layout.x_for_index(target_column);
8505 let target_y =
8506 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8507
8508 let flag_on_right = target_x < text_bounds.size.width / 2.;
8509
8510 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8511 border_color.l += 0.001;
8512
8513 let mut element = v_flex()
8514 .items_end()
8515 .when(flag_on_right, |el| el.items_start())
8516 .child(if flag_on_right {
8517 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8518 .rounded_bl(px(0.))
8519 .rounded_tl(px(0.))
8520 .border_l_2()
8521 .border_color(border_color)
8522 } else {
8523 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8524 .rounded_br(px(0.))
8525 .rounded_tr(px(0.))
8526 .border_r_2()
8527 .border_color(border_color)
8528 })
8529 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8530 .into_any();
8531
8532 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8533
8534 let mut origin = scrolled_content_origin + point(target_x, target_y)
8535 - point(
8536 if flag_on_right {
8537 POLE_WIDTH
8538 } else {
8539 size.width - POLE_WIDTH
8540 },
8541 size.height - line_height,
8542 );
8543
8544 origin.x = origin.x.max(content_origin.x);
8545
8546 element.prepaint_at(origin, window, cx);
8547
8548 Some((element, origin))
8549 }
8550
8551 fn render_edit_prediction_scroll_popover(
8552 &mut self,
8553 to_y: impl Fn(Size<Pixels>) -> Pixels,
8554 scroll_icon: IconName,
8555 visible_row_range: Range<DisplayRow>,
8556 line_layouts: &[LineWithInvisibles],
8557 newest_selection_head: Option<DisplayPoint>,
8558 scrolled_content_origin: gpui::Point<Pixels>,
8559 window: &mut Window,
8560 cx: &mut App,
8561 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8562 let mut element = self
8563 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8564 .into_any();
8565
8566 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8567
8568 let cursor = newest_selection_head?;
8569 let cursor_row_layout =
8570 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8571 let cursor_column = cursor.column() as usize;
8572
8573 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8574
8575 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8576
8577 element.prepaint_at(origin, window, cx);
8578 Some((element, origin))
8579 }
8580
8581 fn render_edit_prediction_eager_jump_popover(
8582 &mut self,
8583 text_bounds: &Bounds<Pixels>,
8584 content_origin: gpui::Point<Pixels>,
8585 editor_snapshot: &EditorSnapshot,
8586 visible_row_range: Range<DisplayRow>,
8587 scroll_top: f32,
8588 scroll_bottom: f32,
8589 line_height: Pixels,
8590 scroll_pixel_position: gpui::Point<Pixels>,
8591 target_display_point: DisplayPoint,
8592 editor_width: Pixels,
8593 window: &mut Window,
8594 cx: &mut App,
8595 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8596 if target_display_point.row().as_f32() < scroll_top {
8597 let mut element = self
8598 .render_edit_prediction_line_popover(
8599 "Jump to Edit",
8600 Some(IconName::ArrowUp),
8601 window,
8602 cx,
8603 )?
8604 .into_any();
8605
8606 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8607 let offset = point(
8608 (text_bounds.size.width - size.width) / 2.,
8609 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8610 );
8611
8612 let origin = text_bounds.origin + offset;
8613 element.prepaint_at(origin, window, cx);
8614 Some((element, origin))
8615 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8616 let mut element = self
8617 .render_edit_prediction_line_popover(
8618 "Jump to Edit",
8619 Some(IconName::ArrowDown),
8620 window,
8621 cx,
8622 )?
8623 .into_any();
8624
8625 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8626 let offset = point(
8627 (text_bounds.size.width - size.width) / 2.,
8628 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8629 );
8630
8631 let origin = text_bounds.origin + offset;
8632 element.prepaint_at(origin, window, cx);
8633 Some((element, origin))
8634 } else {
8635 self.render_edit_prediction_end_of_line_popover(
8636 "Jump to Edit",
8637 editor_snapshot,
8638 visible_row_range,
8639 target_display_point,
8640 line_height,
8641 scroll_pixel_position,
8642 content_origin,
8643 editor_width,
8644 window,
8645 cx,
8646 )
8647 }
8648 }
8649
8650 fn render_edit_prediction_end_of_line_popover(
8651 self: &mut Editor,
8652 label: &'static str,
8653 editor_snapshot: &EditorSnapshot,
8654 visible_row_range: Range<DisplayRow>,
8655 target_display_point: DisplayPoint,
8656 line_height: Pixels,
8657 scroll_pixel_position: gpui::Point<Pixels>,
8658 content_origin: gpui::Point<Pixels>,
8659 editor_width: Pixels,
8660 window: &mut Window,
8661 cx: &mut App,
8662 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8663 let target_line_end = DisplayPoint::new(
8664 target_display_point.row(),
8665 editor_snapshot.line_len(target_display_point.row()),
8666 );
8667
8668 let mut element = self
8669 .render_edit_prediction_line_popover(label, None, window, cx)?
8670 .into_any();
8671
8672 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8673
8674 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8675
8676 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8677 let mut origin = start_point
8678 + line_origin
8679 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8680 origin.x = origin.x.max(content_origin.x);
8681
8682 let max_x = content_origin.x + editor_width - size.width;
8683
8684 if origin.x > max_x {
8685 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8686
8687 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8688 origin.y += offset;
8689 IconName::ArrowUp
8690 } else {
8691 origin.y -= offset;
8692 IconName::ArrowDown
8693 };
8694
8695 element = self
8696 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8697 .into_any();
8698
8699 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8700
8701 origin.x = content_origin.x + editor_width - size.width - px(2.);
8702 }
8703
8704 element.prepaint_at(origin, window, cx);
8705 Some((element, origin))
8706 }
8707
8708 fn render_edit_prediction_diff_popover(
8709 self: &Editor,
8710 text_bounds: &Bounds<Pixels>,
8711 content_origin: gpui::Point<Pixels>,
8712 right_margin: Pixels,
8713 editor_snapshot: &EditorSnapshot,
8714 visible_row_range: Range<DisplayRow>,
8715 line_layouts: &[LineWithInvisibles],
8716 line_height: Pixels,
8717 scroll_pixel_position: gpui::Point<Pixels>,
8718 newest_selection_head: Option<DisplayPoint>,
8719 editor_width: Pixels,
8720 style: &EditorStyle,
8721 edits: &Vec<(Range<Anchor>, String)>,
8722 edit_preview: &Option<language::EditPreview>,
8723 snapshot: &language::BufferSnapshot,
8724 window: &mut Window,
8725 cx: &mut App,
8726 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8727 let edit_start = edits
8728 .first()
8729 .unwrap()
8730 .0
8731 .start
8732 .to_display_point(editor_snapshot);
8733 let edit_end = edits
8734 .last()
8735 .unwrap()
8736 .0
8737 .end
8738 .to_display_point(editor_snapshot);
8739
8740 let is_visible = visible_row_range.contains(&edit_start.row())
8741 || visible_row_range.contains(&edit_end.row());
8742 if !is_visible {
8743 return None;
8744 }
8745
8746 let highlighted_edits =
8747 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8748
8749 let styled_text = highlighted_edits.to_styled_text(&style.text);
8750 let line_count = highlighted_edits.text.lines().count();
8751
8752 const BORDER_WIDTH: Pixels = px(1.);
8753
8754 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8755 let has_keybind = keybind.is_some();
8756
8757 let mut element = h_flex()
8758 .items_start()
8759 .child(
8760 h_flex()
8761 .bg(cx.theme().colors().editor_background)
8762 .border(BORDER_WIDTH)
8763 .shadow_xs()
8764 .border_color(cx.theme().colors().border)
8765 .rounded_l_lg()
8766 .when(line_count > 1, |el| el.rounded_br_lg())
8767 .pr_1()
8768 .child(styled_text),
8769 )
8770 .child(
8771 h_flex()
8772 .h(line_height + BORDER_WIDTH * 2.)
8773 .px_1p5()
8774 .gap_1()
8775 // Workaround: For some reason, there's a gap if we don't do this
8776 .ml(-BORDER_WIDTH)
8777 .shadow(vec![gpui::BoxShadow {
8778 color: gpui::black().opacity(0.05),
8779 offset: point(px(1.), px(1.)),
8780 blur_radius: px(2.),
8781 spread_radius: px(0.),
8782 }])
8783 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8784 .border(BORDER_WIDTH)
8785 .border_color(cx.theme().colors().border)
8786 .rounded_r_lg()
8787 .id("edit_prediction_diff_popover_keybind")
8788 .when(!has_keybind, |el| {
8789 let status_colors = cx.theme().status();
8790
8791 el.bg(status_colors.error_background)
8792 .border_color(status_colors.error.opacity(0.6))
8793 .child(Icon::new(IconName::Info).color(Color::Error))
8794 .cursor_default()
8795 .hoverable_tooltip(move |_window, cx| {
8796 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8797 })
8798 })
8799 .children(keybind),
8800 )
8801 .into_any();
8802
8803 let longest_row =
8804 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8805 let longest_line_width = if visible_row_range.contains(&longest_row) {
8806 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8807 } else {
8808 layout_line(
8809 longest_row,
8810 editor_snapshot,
8811 style,
8812 editor_width,
8813 |_| false,
8814 window,
8815 cx,
8816 )
8817 .width
8818 };
8819
8820 let viewport_bounds =
8821 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8822 right: -right_margin,
8823 ..Default::default()
8824 });
8825
8826 let x_after_longest =
8827 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8828 - scroll_pixel_position.x;
8829
8830 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8831
8832 // Fully visible if it can be displayed within the window (allow overlapping other
8833 // panes). However, this is only allowed if the popover starts within text_bounds.
8834 let can_position_to_the_right = x_after_longest < text_bounds.right()
8835 && x_after_longest + element_bounds.width < viewport_bounds.right();
8836
8837 let mut origin = if can_position_to_the_right {
8838 point(
8839 x_after_longest,
8840 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8841 - scroll_pixel_position.y,
8842 )
8843 } else {
8844 let cursor_row = newest_selection_head.map(|head| head.row());
8845 let above_edit = edit_start
8846 .row()
8847 .0
8848 .checked_sub(line_count as u32)
8849 .map(DisplayRow);
8850 let below_edit = Some(edit_end.row() + 1);
8851 let above_cursor =
8852 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8853 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8854
8855 // Place the edit popover adjacent to the edit if there is a location
8856 // available that is onscreen and does not obscure the cursor. Otherwise,
8857 // place it adjacent to the cursor.
8858 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8859 .into_iter()
8860 .flatten()
8861 .find(|&start_row| {
8862 let end_row = start_row + line_count as u32;
8863 visible_row_range.contains(&start_row)
8864 && visible_row_range.contains(&end_row)
8865 && cursor_row.map_or(true, |cursor_row| {
8866 !((start_row..end_row).contains(&cursor_row))
8867 })
8868 })?;
8869
8870 content_origin
8871 + point(
8872 -scroll_pixel_position.x,
8873 row_target.as_f32() * line_height - scroll_pixel_position.y,
8874 )
8875 };
8876
8877 origin.x -= BORDER_WIDTH;
8878
8879 window.defer_draw(element, origin, 1);
8880
8881 // Do not return an element, since it will already be drawn due to defer_draw.
8882 None
8883 }
8884
8885 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8886 px(30.)
8887 }
8888
8889 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8890 if self.read_only(cx) {
8891 cx.theme().players().read_only()
8892 } else {
8893 self.style.as_ref().unwrap().local_player
8894 }
8895 }
8896
8897 fn render_edit_prediction_accept_keybind(
8898 &self,
8899 window: &mut Window,
8900 cx: &App,
8901 ) -> Option<AnyElement> {
8902 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8903 let accept_keystroke = accept_binding.keystroke()?;
8904
8905 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8906
8907 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8908 Color::Accent
8909 } else {
8910 Color::Muted
8911 };
8912
8913 h_flex()
8914 .px_0p5()
8915 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8916 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8917 .text_size(TextSize::XSmall.rems(cx))
8918 .child(h_flex().children(ui::render_modifiers(
8919 &accept_keystroke.modifiers,
8920 PlatformStyle::platform(),
8921 Some(modifiers_color),
8922 Some(IconSize::XSmall.rems().into()),
8923 true,
8924 )))
8925 .when(is_platform_style_mac, |parent| {
8926 parent.child(accept_keystroke.key.clone())
8927 })
8928 .when(!is_platform_style_mac, |parent| {
8929 parent.child(
8930 Key::new(
8931 util::capitalize(&accept_keystroke.key),
8932 Some(Color::Default),
8933 )
8934 .size(Some(IconSize::XSmall.rems().into())),
8935 )
8936 })
8937 .into_any()
8938 .into()
8939 }
8940
8941 fn render_edit_prediction_line_popover(
8942 &self,
8943 label: impl Into<SharedString>,
8944 icon: Option<IconName>,
8945 window: &mut Window,
8946 cx: &App,
8947 ) -> Option<Stateful<Div>> {
8948 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8949
8950 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8951 let has_keybind = keybind.is_some();
8952
8953 let result = h_flex()
8954 .id("ep-line-popover")
8955 .py_0p5()
8956 .pl_1()
8957 .pr(padding_right)
8958 .gap_1()
8959 .rounded_md()
8960 .border_1()
8961 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8962 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8963 .shadow_xs()
8964 .when(!has_keybind, |el| {
8965 let status_colors = cx.theme().status();
8966
8967 el.bg(status_colors.error_background)
8968 .border_color(status_colors.error.opacity(0.6))
8969 .pl_2()
8970 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8971 .cursor_default()
8972 .hoverable_tooltip(move |_window, cx| {
8973 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8974 })
8975 })
8976 .children(keybind)
8977 .child(
8978 Label::new(label)
8979 .size(LabelSize::Small)
8980 .when(!has_keybind, |el| {
8981 el.color(cx.theme().status().error.into()).strikethrough()
8982 }),
8983 )
8984 .when(!has_keybind, |el| {
8985 el.child(
8986 h_flex().ml_1().child(
8987 Icon::new(IconName::Info)
8988 .size(IconSize::Small)
8989 .color(cx.theme().status().error.into()),
8990 ),
8991 )
8992 })
8993 .when_some(icon, |element, icon| {
8994 element.child(
8995 div()
8996 .mt(px(1.5))
8997 .child(Icon::new(icon).size(IconSize::Small)),
8998 )
8999 });
9000
9001 Some(result)
9002 }
9003
9004 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9005 let accent_color = cx.theme().colors().text_accent;
9006 let editor_bg_color = cx.theme().colors().editor_background;
9007 editor_bg_color.blend(accent_color.opacity(0.1))
9008 }
9009
9010 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9011 let accent_color = cx.theme().colors().text_accent;
9012 let editor_bg_color = cx.theme().colors().editor_background;
9013 editor_bg_color.blend(accent_color.opacity(0.6))
9014 }
9015
9016 fn render_edit_prediction_cursor_popover(
9017 &self,
9018 min_width: Pixels,
9019 max_width: Pixels,
9020 cursor_point: Point,
9021 style: &EditorStyle,
9022 accept_keystroke: Option<&gpui::Keystroke>,
9023 _window: &Window,
9024 cx: &mut Context<Editor>,
9025 ) -> Option<AnyElement> {
9026 let provider = self.edit_prediction_provider.as_ref()?;
9027
9028 if provider.provider.needs_terms_acceptance(cx) {
9029 return Some(
9030 h_flex()
9031 .min_w(min_width)
9032 .flex_1()
9033 .px_2()
9034 .py_1()
9035 .gap_3()
9036 .elevation_2(cx)
9037 .hover(|style| style.bg(cx.theme().colors().element_hover))
9038 .id("accept-terms")
9039 .cursor_pointer()
9040 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
9041 .on_click(cx.listener(|this, _event, window, cx| {
9042 cx.stop_propagation();
9043 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
9044 window.dispatch_action(
9045 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
9046 cx,
9047 );
9048 }))
9049 .child(
9050 h_flex()
9051 .flex_1()
9052 .gap_2()
9053 .child(Icon::new(IconName::ZedPredict))
9054 .child(Label::new("Accept Terms of Service"))
9055 .child(div().w_full())
9056 .child(
9057 Icon::new(IconName::ArrowUpRight)
9058 .color(Color::Muted)
9059 .size(IconSize::Small),
9060 )
9061 .into_any_element(),
9062 )
9063 .into_any(),
9064 );
9065 }
9066
9067 let is_refreshing = provider.provider.is_refreshing(cx);
9068
9069 fn pending_completion_container() -> Div {
9070 h_flex()
9071 .h_full()
9072 .flex_1()
9073 .gap_2()
9074 .child(Icon::new(IconName::ZedPredict))
9075 }
9076
9077 let completion = match &self.active_inline_completion {
9078 Some(prediction) => {
9079 if !self.has_visible_completions_menu() {
9080 const RADIUS: Pixels = px(6.);
9081 const BORDER_WIDTH: Pixels = px(1.);
9082
9083 return Some(
9084 h_flex()
9085 .elevation_2(cx)
9086 .border(BORDER_WIDTH)
9087 .border_color(cx.theme().colors().border)
9088 .when(accept_keystroke.is_none(), |el| {
9089 el.border_color(cx.theme().status().error)
9090 })
9091 .rounded(RADIUS)
9092 .rounded_tl(px(0.))
9093 .overflow_hidden()
9094 .child(div().px_1p5().child(match &prediction.completion {
9095 InlineCompletion::Move { target, snapshot } => {
9096 use text::ToPoint as _;
9097 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
9098 {
9099 Icon::new(IconName::ZedPredictDown)
9100 } else {
9101 Icon::new(IconName::ZedPredictUp)
9102 }
9103 }
9104 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
9105 }))
9106 .child(
9107 h_flex()
9108 .gap_1()
9109 .py_1()
9110 .px_2()
9111 .rounded_r(RADIUS - BORDER_WIDTH)
9112 .border_l_1()
9113 .border_color(cx.theme().colors().border)
9114 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9115 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9116 el.child(
9117 Label::new("Hold")
9118 .size(LabelSize::Small)
9119 .when(accept_keystroke.is_none(), |el| {
9120 el.strikethrough()
9121 })
9122 .line_height_style(LineHeightStyle::UiLabel),
9123 )
9124 })
9125 .id("edit_prediction_cursor_popover_keybind")
9126 .when(accept_keystroke.is_none(), |el| {
9127 let status_colors = cx.theme().status();
9128
9129 el.bg(status_colors.error_background)
9130 .border_color(status_colors.error.opacity(0.6))
9131 .child(Icon::new(IconName::Info).color(Color::Error))
9132 .cursor_default()
9133 .hoverable_tooltip(move |_window, cx| {
9134 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9135 .into()
9136 })
9137 })
9138 .when_some(
9139 accept_keystroke.as_ref(),
9140 |el, accept_keystroke| {
9141 el.child(h_flex().children(ui::render_modifiers(
9142 &accept_keystroke.modifiers,
9143 PlatformStyle::platform(),
9144 Some(Color::Default),
9145 Some(IconSize::XSmall.rems().into()),
9146 false,
9147 )))
9148 },
9149 ),
9150 )
9151 .into_any(),
9152 );
9153 }
9154
9155 self.render_edit_prediction_cursor_popover_preview(
9156 prediction,
9157 cursor_point,
9158 style,
9159 cx,
9160 )?
9161 }
9162
9163 None if is_refreshing => match &self.stale_inline_completion_in_menu {
9164 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9165 stale_completion,
9166 cursor_point,
9167 style,
9168 cx,
9169 )?,
9170
9171 None => {
9172 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
9173 }
9174 },
9175
9176 None => pending_completion_container().child(Label::new("No Prediction")),
9177 };
9178
9179 let completion = if is_refreshing {
9180 completion
9181 .with_animation(
9182 "loading-completion",
9183 Animation::new(Duration::from_secs(2))
9184 .repeat()
9185 .with_easing(pulsating_between(0.4, 0.8)),
9186 |label, delta| label.opacity(delta),
9187 )
9188 .into_any_element()
9189 } else {
9190 completion.into_any_element()
9191 };
9192
9193 let has_completion = self.active_inline_completion.is_some();
9194
9195 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9196 Some(
9197 h_flex()
9198 .min_w(min_width)
9199 .max_w(max_width)
9200 .flex_1()
9201 .elevation_2(cx)
9202 .border_color(cx.theme().colors().border)
9203 .child(
9204 div()
9205 .flex_1()
9206 .py_1()
9207 .px_2()
9208 .overflow_hidden()
9209 .child(completion),
9210 )
9211 .when_some(accept_keystroke, |el, accept_keystroke| {
9212 if !accept_keystroke.modifiers.modified() {
9213 return el;
9214 }
9215
9216 el.child(
9217 h_flex()
9218 .h_full()
9219 .border_l_1()
9220 .rounded_r_lg()
9221 .border_color(cx.theme().colors().border)
9222 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9223 .gap_1()
9224 .py_1()
9225 .px_2()
9226 .child(
9227 h_flex()
9228 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9229 .when(is_platform_style_mac, |parent| parent.gap_1())
9230 .child(h_flex().children(ui::render_modifiers(
9231 &accept_keystroke.modifiers,
9232 PlatformStyle::platform(),
9233 Some(if !has_completion {
9234 Color::Muted
9235 } else {
9236 Color::Default
9237 }),
9238 None,
9239 false,
9240 ))),
9241 )
9242 .child(Label::new("Preview").into_any_element())
9243 .opacity(if has_completion { 1.0 } else { 0.4 }),
9244 )
9245 })
9246 .into_any(),
9247 )
9248 }
9249
9250 fn render_edit_prediction_cursor_popover_preview(
9251 &self,
9252 completion: &InlineCompletionState,
9253 cursor_point: Point,
9254 style: &EditorStyle,
9255 cx: &mut Context<Editor>,
9256 ) -> Option<Div> {
9257 use text::ToPoint as _;
9258
9259 fn render_relative_row_jump(
9260 prefix: impl Into<String>,
9261 current_row: u32,
9262 target_row: u32,
9263 ) -> Div {
9264 let (row_diff, arrow) = if target_row < current_row {
9265 (current_row - target_row, IconName::ArrowUp)
9266 } else {
9267 (target_row - current_row, IconName::ArrowDown)
9268 };
9269
9270 h_flex()
9271 .child(
9272 Label::new(format!("{}{}", prefix.into(), row_diff))
9273 .color(Color::Muted)
9274 .size(LabelSize::Small),
9275 )
9276 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9277 }
9278
9279 match &completion.completion {
9280 InlineCompletion::Move {
9281 target, snapshot, ..
9282 } => Some(
9283 h_flex()
9284 .px_2()
9285 .gap_2()
9286 .flex_1()
9287 .child(
9288 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9289 Icon::new(IconName::ZedPredictDown)
9290 } else {
9291 Icon::new(IconName::ZedPredictUp)
9292 },
9293 )
9294 .child(Label::new("Jump to Edit")),
9295 ),
9296
9297 InlineCompletion::Edit {
9298 edits,
9299 edit_preview,
9300 snapshot,
9301 display_mode: _,
9302 } => {
9303 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9304
9305 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
9306 &snapshot,
9307 &edits,
9308 edit_preview.as_ref()?,
9309 true,
9310 cx,
9311 )
9312 .first_line_preview();
9313
9314 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9315 .with_default_highlights(&style.text, highlighted_edits.highlights);
9316
9317 let preview = h_flex()
9318 .gap_1()
9319 .min_w_16()
9320 .child(styled_text)
9321 .when(has_more_lines, |parent| parent.child("…"));
9322
9323 let left = if first_edit_row != cursor_point.row {
9324 render_relative_row_jump("", cursor_point.row, first_edit_row)
9325 .into_any_element()
9326 } else {
9327 Icon::new(IconName::ZedPredict).into_any_element()
9328 };
9329
9330 Some(
9331 h_flex()
9332 .h_full()
9333 .flex_1()
9334 .gap_2()
9335 .pr_1()
9336 .overflow_x_hidden()
9337 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9338 .child(left)
9339 .child(preview),
9340 )
9341 }
9342 }
9343 }
9344
9345 pub fn render_context_menu(
9346 &self,
9347 style: &EditorStyle,
9348 max_height_in_lines: u32,
9349 window: &mut Window,
9350 cx: &mut Context<Editor>,
9351 ) -> Option<AnyElement> {
9352 let menu = self.context_menu.borrow();
9353 let menu = menu.as_ref()?;
9354 if !menu.visible() {
9355 return None;
9356 };
9357 Some(menu.render(style, max_height_in_lines, window, cx))
9358 }
9359
9360 fn render_context_menu_aside(
9361 &mut self,
9362 max_size: Size<Pixels>,
9363 window: &mut Window,
9364 cx: &mut Context<Editor>,
9365 ) -> Option<AnyElement> {
9366 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9367 if menu.visible() {
9368 menu.render_aside(max_size, window, cx)
9369 } else {
9370 None
9371 }
9372 })
9373 }
9374
9375 fn hide_context_menu(
9376 &mut self,
9377 window: &mut Window,
9378 cx: &mut Context<Self>,
9379 ) -> Option<CodeContextMenu> {
9380 cx.notify();
9381 self.completion_tasks.clear();
9382 let context_menu = self.context_menu.borrow_mut().take();
9383 self.stale_inline_completion_in_menu.take();
9384 self.update_visible_inline_completion(window, cx);
9385 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9386 if let Some(completion_provider) = &self.completion_provider {
9387 completion_provider.selection_changed(None, window, cx);
9388 }
9389 }
9390 context_menu
9391 }
9392
9393 fn show_snippet_choices(
9394 &mut self,
9395 choices: &Vec<String>,
9396 selection: Range<Anchor>,
9397 cx: &mut Context<Self>,
9398 ) {
9399 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9400 (Some(a), Some(b)) if a == b => a,
9401 _ => {
9402 log::error!("expected anchor range to have matching buffer IDs");
9403 return;
9404 }
9405 };
9406 let multi_buffer = self.buffer().read(cx);
9407 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9408 return;
9409 };
9410
9411 let id = post_inc(&mut self.next_completion_id);
9412 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9413 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9414 CompletionsMenu::new_snippet_choices(
9415 id,
9416 true,
9417 choices,
9418 selection,
9419 buffer,
9420 snippet_sort_order,
9421 ),
9422 ));
9423 }
9424
9425 pub fn insert_snippet(
9426 &mut self,
9427 insertion_ranges: &[Range<usize>],
9428 snippet: Snippet,
9429 window: &mut Window,
9430 cx: &mut Context<Self>,
9431 ) -> Result<()> {
9432 struct Tabstop<T> {
9433 is_end_tabstop: bool,
9434 ranges: Vec<Range<T>>,
9435 choices: Option<Vec<String>>,
9436 }
9437
9438 let tabstops = self.buffer.update(cx, |buffer, cx| {
9439 let snippet_text: Arc<str> = snippet.text.clone().into();
9440 let edits = insertion_ranges
9441 .iter()
9442 .cloned()
9443 .map(|range| (range, snippet_text.clone()));
9444 let autoindent_mode = AutoindentMode::Block {
9445 original_indent_columns: Vec::new(),
9446 };
9447 buffer.edit(edits, Some(autoindent_mode), cx);
9448
9449 let snapshot = &*buffer.read(cx);
9450 let snippet = &snippet;
9451 snippet
9452 .tabstops
9453 .iter()
9454 .map(|tabstop| {
9455 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9456 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9457 });
9458 let mut tabstop_ranges = tabstop
9459 .ranges
9460 .iter()
9461 .flat_map(|tabstop_range| {
9462 let mut delta = 0_isize;
9463 insertion_ranges.iter().map(move |insertion_range| {
9464 let insertion_start = insertion_range.start as isize + delta;
9465 delta +=
9466 snippet.text.len() as isize - insertion_range.len() as isize;
9467
9468 let start = ((insertion_start + tabstop_range.start) as usize)
9469 .min(snapshot.len());
9470 let end = ((insertion_start + tabstop_range.end) as usize)
9471 .min(snapshot.len());
9472 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9473 })
9474 })
9475 .collect::<Vec<_>>();
9476 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9477
9478 Tabstop {
9479 is_end_tabstop,
9480 ranges: tabstop_ranges,
9481 choices: tabstop.choices.clone(),
9482 }
9483 })
9484 .collect::<Vec<_>>()
9485 });
9486 if let Some(tabstop) = tabstops.first() {
9487 self.change_selections(Default::default(), window, cx, |s| {
9488 // Reverse order so that the first range is the newest created selection.
9489 // Completions will use it and autoscroll will prioritize it.
9490 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9491 });
9492
9493 if let Some(choices) = &tabstop.choices {
9494 if let Some(selection) = tabstop.ranges.first() {
9495 self.show_snippet_choices(choices, selection.clone(), cx)
9496 }
9497 }
9498
9499 // If we're already at the last tabstop and it's at the end of the snippet,
9500 // we're done, we don't need to keep the state around.
9501 if !tabstop.is_end_tabstop {
9502 let choices = tabstops
9503 .iter()
9504 .map(|tabstop| tabstop.choices.clone())
9505 .collect();
9506
9507 let ranges = tabstops
9508 .into_iter()
9509 .map(|tabstop| tabstop.ranges)
9510 .collect::<Vec<_>>();
9511
9512 self.snippet_stack.push(SnippetState {
9513 active_index: 0,
9514 ranges,
9515 choices,
9516 });
9517 }
9518
9519 // Check whether the just-entered snippet ends with an auto-closable bracket.
9520 if self.autoclose_regions.is_empty() {
9521 let snapshot = self.buffer.read(cx).snapshot(cx);
9522 for selection in &mut self.selections.all::<Point>(cx) {
9523 let selection_head = selection.head();
9524 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9525 continue;
9526 };
9527
9528 let mut bracket_pair = None;
9529 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9530 let prev_chars = snapshot
9531 .reversed_chars_at(selection_head)
9532 .collect::<String>();
9533 for (pair, enabled) in scope.brackets() {
9534 if enabled
9535 && pair.close
9536 && prev_chars.starts_with(pair.start.as_str())
9537 && next_chars.starts_with(pair.end.as_str())
9538 {
9539 bracket_pair = Some(pair.clone());
9540 break;
9541 }
9542 }
9543 if let Some(pair) = bracket_pair {
9544 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9545 let autoclose_enabled =
9546 self.use_autoclose && snapshot_settings.use_autoclose;
9547 if autoclose_enabled {
9548 let start = snapshot.anchor_after(selection_head);
9549 let end = snapshot.anchor_after(selection_head);
9550 self.autoclose_regions.push(AutocloseRegion {
9551 selection_id: selection.id,
9552 range: start..end,
9553 pair,
9554 });
9555 }
9556 }
9557 }
9558 }
9559 }
9560 Ok(())
9561 }
9562
9563 pub fn move_to_next_snippet_tabstop(
9564 &mut self,
9565 window: &mut Window,
9566 cx: &mut Context<Self>,
9567 ) -> bool {
9568 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9569 }
9570
9571 pub fn move_to_prev_snippet_tabstop(
9572 &mut self,
9573 window: &mut Window,
9574 cx: &mut Context<Self>,
9575 ) -> bool {
9576 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9577 }
9578
9579 pub fn move_to_snippet_tabstop(
9580 &mut self,
9581 bias: Bias,
9582 window: &mut Window,
9583 cx: &mut Context<Self>,
9584 ) -> bool {
9585 if let Some(mut snippet) = self.snippet_stack.pop() {
9586 match bias {
9587 Bias::Left => {
9588 if snippet.active_index > 0 {
9589 snippet.active_index -= 1;
9590 } else {
9591 self.snippet_stack.push(snippet);
9592 return false;
9593 }
9594 }
9595 Bias::Right => {
9596 if snippet.active_index + 1 < snippet.ranges.len() {
9597 snippet.active_index += 1;
9598 } else {
9599 self.snippet_stack.push(snippet);
9600 return false;
9601 }
9602 }
9603 }
9604 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9605 self.change_selections(Default::default(), window, cx, |s| {
9606 // Reverse order so that the first range is the newest created selection.
9607 // Completions will use it and autoscroll will prioritize it.
9608 s.select_ranges(current_ranges.iter().rev().cloned())
9609 });
9610
9611 if let Some(choices) = &snippet.choices[snippet.active_index] {
9612 if let Some(selection) = current_ranges.first() {
9613 self.show_snippet_choices(&choices, selection.clone(), cx);
9614 }
9615 }
9616
9617 // If snippet state is not at the last tabstop, push it back on the stack
9618 if snippet.active_index + 1 < snippet.ranges.len() {
9619 self.snippet_stack.push(snippet);
9620 }
9621 return true;
9622 }
9623 }
9624
9625 false
9626 }
9627
9628 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9629 self.transact(window, cx, |this, window, cx| {
9630 this.select_all(&SelectAll, window, cx);
9631 this.insert("", window, cx);
9632 });
9633 }
9634
9635 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9636 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9637 self.transact(window, cx, |this, window, cx| {
9638 this.select_autoclose_pair(window, cx);
9639 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9640 if !this.linked_edit_ranges.is_empty() {
9641 let selections = this.selections.all::<MultiBufferPoint>(cx);
9642 let snapshot = this.buffer.read(cx).snapshot(cx);
9643
9644 for selection in selections.iter() {
9645 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9646 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9647 if selection_start.buffer_id != selection_end.buffer_id {
9648 continue;
9649 }
9650 if let Some(ranges) =
9651 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9652 {
9653 for (buffer, entries) in ranges {
9654 linked_ranges.entry(buffer).or_default().extend(entries);
9655 }
9656 }
9657 }
9658 }
9659
9660 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9661 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9662 for selection in &mut selections {
9663 if selection.is_empty() {
9664 let old_head = selection.head();
9665 let mut new_head =
9666 movement::left(&display_map, old_head.to_display_point(&display_map))
9667 .to_point(&display_map);
9668 if let Some((buffer, line_buffer_range)) = display_map
9669 .buffer_snapshot
9670 .buffer_line_for_row(MultiBufferRow(old_head.row))
9671 {
9672 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9673 let indent_len = match indent_size.kind {
9674 IndentKind::Space => {
9675 buffer.settings_at(line_buffer_range.start, cx).tab_size
9676 }
9677 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9678 };
9679 if old_head.column <= indent_size.len && old_head.column > 0 {
9680 let indent_len = indent_len.get();
9681 new_head = cmp::min(
9682 new_head,
9683 MultiBufferPoint::new(
9684 old_head.row,
9685 ((old_head.column - 1) / indent_len) * indent_len,
9686 ),
9687 );
9688 }
9689 }
9690
9691 selection.set_head(new_head, SelectionGoal::None);
9692 }
9693 }
9694
9695 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9696 this.insert("", window, cx);
9697 let empty_str: Arc<str> = Arc::from("");
9698 for (buffer, edits) in linked_ranges {
9699 let snapshot = buffer.read(cx).snapshot();
9700 use text::ToPoint as TP;
9701
9702 let edits = edits
9703 .into_iter()
9704 .map(|range| {
9705 let end_point = TP::to_point(&range.end, &snapshot);
9706 let mut start_point = TP::to_point(&range.start, &snapshot);
9707
9708 if end_point == start_point {
9709 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9710 .saturating_sub(1);
9711 start_point =
9712 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9713 };
9714
9715 (start_point..end_point, empty_str.clone())
9716 })
9717 .sorted_by_key(|(range, _)| range.start)
9718 .collect::<Vec<_>>();
9719 buffer.update(cx, |this, cx| {
9720 this.edit(edits, None, cx);
9721 })
9722 }
9723 this.refresh_inline_completion(true, false, window, cx);
9724 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9725 });
9726 }
9727
9728 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9729 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9730 self.transact(window, cx, |this, window, cx| {
9731 this.change_selections(Default::default(), window, cx, |s| {
9732 s.move_with(|map, selection| {
9733 if selection.is_empty() {
9734 let cursor = movement::right(map, selection.head());
9735 selection.end = cursor;
9736 selection.reversed = true;
9737 selection.goal = SelectionGoal::None;
9738 }
9739 })
9740 });
9741 this.insert("", window, cx);
9742 this.refresh_inline_completion(true, false, window, cx);
9743 });
9744 }
9745
9746 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9747 if self.mode.is_single_line() {
9748 cx.propagate();
9749 return;
9750 }
9751
9752 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9753 if self.move_to_prev_snippet_tabstop(window, cx) {
9754 return;
9755 }
9756 self.outdent(&Outdent, window, cx);
9757 }
9758
9759 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9760 if self.mode.is_single_line() {
9761 cx.propagate();
9762 return;
9763 }
9764
9765 if self.move_to_next_snippet_tabstop(window, cx) {
9766 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9767 return;
9768 }
9769 if self.read_only(cx) {
9770 return;
9771 }
9772 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9773 let mut selections = self.selections.all_adjusted(cx);
9774 let buffer = self.buffer.read(cx);
9775 let snapshot = buffer.snapshot(cx);
9776 let rows_iter = selections.iter().map(|s| s.head().row);
9777 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9778
9779 let has_some_cursor_in_whitespace = selections
9780 .iter()
9781 .filter(|selection| selection.is_empty())
9782 .any(|selection| {
9783 let cursor = selection.head();
9784 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9785 cursor.column < current_indent.len
9786 });
9787
9788 let mut edits = Vec::new();
9789 let mut prev_edited_row = 0;
9790 let mut row_delta = 0;
9791 for selection in &mut selections {
9792 if selection.start.row != prev_edited_row {
9793 row_delta = 0;
9794 }
9795 prev_edited_row = selection.end.row;
9796
9797 // If the selection is non-empty, then increase the indentation of the selected lines.
9798 if !selection.is_empty() {
9799 row_delta =
9800 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9801 continue;
9802 }
9803
9804 let cursor = selection.head();
9805 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9806 if let Some(suggested_indent) =
9807 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9808 {
9809 // Don't do anything if already at suggested indent
9810 // and there is any other cursor which is not
9811 if has_some_cursor_in_whitespace
9812 && cursor.column == current_indent.len
9813 && current_indent.len == suggested_indent.len
9814 {
9815 continue;
9816 }
9817
9818 // Adjust line and move cursor to suggested indent
9819 // if cursor is not at suggested indent
9820 if cursor.column < suggested_indent.len
9821 && cursor.column <= current_indent.len
9822 && current_indent.len <= suggested_indent.len
9823 {
9824 selection.start = Point::new(cursor.row, suggested_indent.len);
9825 selection.end = selection.start;
9826 if row_delta == 0 {
9827 edits.extend(Buffer::edit_for_indent_size_adjustment(
9828 cursor.row,
9829 current_indent,
9830 suggested_indent,
9831 ));
9832 row_delta = suggested_indent.len - current_indent.len;
9833 }
9834 continue;
9835 }
9836
9837 // If current indent is more than suggested indent
9838 // only move cursor to current indent and skip indent
9839 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9840 selection.start = Point::new(cursor.row, current_indent.len);
9841 selection.end = selection.start;
9842 continue;
9843 }
9844 }
9845
9846 // Otherwise, insert a hard or soft tab.
9847 let settings = buffer.language_settings_at(cursor, cx);
9848 let tab_size = if settings.hard_tabs {
9849 IndentSize::tab()
9850 } else {
9851 let tab_size = settings.tab_size.get();
9852 let indent_remainder = snapshot
9853 .text_for_range(Point::new(cursor.row, 0)..cursor)
9854 .flat_map(str::chars)
9855 .fold(row_delta % tab_size, |counter: u32, c| {
9856 if c == '\t' {
9857 0
9858 } else {
9859 (counter + 1) % tab_size
9860 }
9861 });
9862
9863 let chars_to_next_tab_stop = tab_size - indent_remainder;
9864 IndentSize::spaces(chars_to_next_tab_stop)
9865 };
9866 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9867 selection.end = selection.start;
9868 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9869 row_delta += tab_size.len;
9870 }
9871
9872 self.transact(window, cx, |this, window, cx| {
9873 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9874 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9875 this.refresh_inline_completion(true, false, window, cx);
9876 });
9877 }
9878
9879 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9880 if self.read_only(cx) {
9881 return;
9882 }
9883 if self.mode.is_single_line() {
9884 cx.propagate();
9885 return;
9886 }
9887
9888 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9889 let mut selections = self.selections.all::<Point>(cx);
9890 let mut prev_edited_row = 0;
9891 let mut row_delta = 0;
9892 let mut edits = Vec::new();
9893 let buffer = self.buffer.read(cx);
9894 let snapshot = buffer.snapshot(cx);
9895 for selection in &mut selections {
9896 if selection.start.row != prev_edited_row {
9897 row_delta = 0;
9898 }
9899 prev_edited_row = selection.end.row;
9900
9901 row_delta =
9902 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9903 }
9904
9905 self.transact(window, cx, |this, window, cx| {
9906 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9907 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9908 });
9909 }
9910
9911 fn indent_selection(
9912 buffer: &MultiBuffer,
9913 snapshot: &MultiBufferSnapshot,
9914 selection: &mut Selection<Point>,
9915 edits: &mut Vec<(Range<Point>, String)>,
9916 delta_for_start_row: u32,
9917 cx: &App,
9918 ) -> u32 {
9919 let settings = buffer.language_settings_at(selection.start, cx);
9920 let tab_size = settings.tab_size.get();
9921 let indent_kind = if settings.hard_tabs {
9922 IndentKind::Tab
9923 } else {
9924 IndentKind::Space
9925 };
9926 let mut start_row = selection.start.row;
9927 let mut end_row = selection.end.row + 1;
9928
9929 // If a selection ends at the beginning of a line, don't indent
9930 // that last line.
9931 if selection.end.column == 0 && selection.end.row > selection.start.row {
9932 end_row -= 1;
9933 }
9934
9935 // Avoid re-indenting a row that has already been indented by a
9936 // previous selection, but still update this selection's column
9937 // to reflect that indentation.
9938 if delta_for_start_row > 0 {
9939 start_row += 1;
9940 selection.start.column += delta_for_start_row;
9941 if selection.end.row == selection.start.row {
9942 selection.end.column += delta_for_start_row;
9943 }
9944 }
9945
9946 let mut delta_for_end_row = 0;
9947 let has_multiple_rows = start_row + 1 != end_row;
9948 for row in start_row..end_row {
9949 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9950 let indent_delta = match (current_indent.kind, indent_kind) {
9951 (IndentKind::Space, IndentKind::Space) => {
9952 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9953 IndentSize::spaces(columns_to_next_tab_stop)
9954 }
9955 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9956 (_, IndentKind::Tab) => IndentSize::tab(),
9957 };
9958
9959 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9960 0
9961 } else {
9962 selection.start.column
9963 };
9964 let row_start = Point::new(row, start);
9965 edits.push((
9966 row_start..row_start,
9967 indent_delta.chars().collect::<String>(),
9968 ));
9969
9970 // Update this selection's endpoints to reflect the indentation.
9971 if row == selection.start.row {
9972 selection.start.column += indent_delta.len;
9973 }
9974 if row == selection.end.row {
9975 selection.end.column += indent_delta.len;
9976 delta_for_end_row = indent_delta.len;
9977 }
9978 }
9979
9980 if selection.start.row == selection.end.row {
9981 delta_for_start_row + delta_for_end_row
9982 } else {
9983 delta_for_end_row
9984 }
9985 }
9986
9987 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9988 if self.read_only(cx) {
9989 return;
9990 }
9991 if self.mode.is_single_line() {
9992 cx.propagate();
9993 return;
9994 }
9995
9996 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9997 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9998 let selections = self.selections.all::<Point>(cx);
9999 let mut deletion_ranges = Vec::new();
10000 let mut last_outdent = None;
10001 {
10002 let buffer = self.buffer.read(cx);
10003 let snapshot = buffer.snapshot(cx);
10004 for selection in &selections {
10005 let settings = buffer.language_settings_at(selection.start, cx);
10006 let tab_size = settings.tab_size.get();
10007 let mut rows = selection.spanned_rows(false, &display_map);
10008
10009 // Avoid re-outdenting a row that has already been outdented by a
10010 // previous selection.
10011 if let Some(last_row) = last_outdent {
10012 if last_row == rows.start {
10013 rows.start = rows.start.next_row();
10014 }
10015 }
10016 let has_multiple_rows = rows.len() > 1;
10017 for row in rows.iter_rows() {
10018 let indent_size = snapshot.indent_size_for_line(row);
10019 if indent_size.len > 0 {
10020 let deletion_len = match indent_size.kind {
10021 IndentKind::Space => {
10022 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10023 if columns_to_prev_tab_stop == 0 {
10024 tab_size
10025 } else {
10026 columns_to_prev_tab_stop
10027 }
10028 }
10029 IndentKind::Tab => 1,
10030 };
10031 let start = if has_multiple_rows
10032 || deletion_len > selection.start.column
10033 || indent_size.len < selection.start.column
10034 {
10035 0
10036 } else {
10037 selection.start.column - deletion_len
10038 };
10039 deletion_ranges.push(
10040 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10041 );
10042 last_outdent = Some(row);
10043 }
10044 }
10045 }
10046 }
10047
10048 self.transact(window, cx, |this, window, cx| {
10049 this.buffer.update(cx, |buffer, cx| {
10050 let empty_str: Arc<str> = Arc::default();
10051 buffer.edit(
10052 deletion_ranges
10053 .into_iter()
10054 .map(|range| (range, empty_str.clone())),
10055 None,
10056 cx,
10057 );
10058 });
10059 let selections = this.selections.all::<usize>(cx);
10060 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10061 });
10062 }
10063
10064 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10065 if self.read_only(cx) {
10066 return;
10067 }
10068 if self.mode.is_single_line() {
10069 cx.propagate();
10070 return;
10071 }
10072
10073 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10074 let selections = self
10075 .selections
10076 .all::<usize>(cx)
10077 .into_iter()
10078 .map(|s| s.range());
10079
10080 self.transact(window, cx, |this, window, cx| {
10081 this.buffer.update(cx, |buffer, cx| {
10082 buffer.autoindent_ranges(selections, cx);
10083 });
10084 let selections = this.selections.all::<usize>(cx);
10085 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10086 });
10087 }
10088
10089 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10090 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10091 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10092 let selections = self.selections.all::<Point>(cx);
10093
10094 let mut new_cursors = Vec::new();
10095 let mut edit_ranges = Vec::new();
10096 let mut selections = selections.iter().peekable();
10097 while let Some(selection) = selections.next() {
10098 let mut rows = selection.spanned_rows(false, &display_map);
10099 let goal_display_column = selection.head().to_display_point(&display_map).column();
10100
10101 // Accumulate contiguous regions of rows that we want to delete.
10102 while let Some(next_selection) = selections.peek() {
10103 let next_rows = next_selection.spanned_rows(false, &display_map);
10104 if next_rows.start <= rows.end {
10105 rows.end = next_rows.end;
10106 selections.next().unwrap();
10107 } else {
10108 break;
10109 }
10110 }
10111
10112 let buffer = &display_map.buffer_snapshot;
10113 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10114 let edit_end;
10115 let cursor_buffer_row;
10116 if buffer.max_point().row >= rows.end.0 {
10117 // If there's a line after the range, delete the \n from the end of the row range
10118 // and position the cursor on the next line.
10119 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10120 cursor_buffer_row = rows.end;
10121 } else {
10122 // If there isn't a line after the range, delete the \n from the line before the
10123 // start of the row range and position the cursor there.
10124 edit_start = edit_start.saturating_sub(1);
10125 edit_end = buffer.len();
10126 cursor_buffer_row = rows.start.previous_row();
10127 }
10128
10129 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10130 *cursor.column_mut() =
10131 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10132
10133 new_cursors.push((
10134 selection.id,
10135 buffer.anchor_after(cursor.to_point(&display_map)),
10136 ));
10137 edit_ranges.push(edit_start..edit_end);
10138 }
10139
10140 self.transact(window, cx, |this, window, cx| {
10141 let buffer = this.buffer.update(cx, |buffer, cx| {
10142 let empty_str: Arc<str> = Arc::default();
10143 buffer.edit(
10144 edit_ranges
10145 .into_iter()
10146 .map(|range| (range, empty_str.clone())),
10147 None,
10148 cx,
10149 );
10150 buffer.snapshot(cx)
10151 });
10152 let new_selections = new_cursors
10153 .into_iter()
10154 .map(|(id, cursor)| {
10155 let cursor = cursor.to_point(&buffer);
10156 Selection {
10157 id,
10158 start: cursor,
10159 end: cursor,
10160 reversed: false,
10161 goal: SelectionGoal::None,
10162 }
10163 })
10164 .collect();
10165
10166 this.change_selections(Default::default(), window, cx, |s| {
10167 s.select(new_selections);
10168 });
10169 });
10170 }
10171
10172 pub fn join_lines_impl(
10173 &mut self,
10174 insert_whitespace: bool,
10175 window: &mut Window,
10176 cx: &mut Context<Self>,
10177 ) {
10178 if self.read_only(cx) {
10179 return;
10180 }
10181 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10182 for selection in self.selections.all::<Point>(cx) {
10183 let start = MultiBufferRow(selection.start.row);
10184 // Treat single line selections as if they include the next line. Otherwise this action
10185 // would do nothing for single line selections individual cursors.
10186 let end = if selection.start.row == selection.end.row {
10187 MultiBufferRow(selection.start.row + 1)
10188 } else {
10189 MultiBufferRow(selection.end.row)
10190 };
10191
10192 if let Some(last_row_range) = row_ranges.last_mut() {
10193 if start <= last_row_range.end {
10194 last_row_range.end = end;
10195 continue;
10196 }
10197 }
10198 row_ranges.push(start..end);
10199 }
10200
10201 let snapshot = self.buffer.read(cx).snapshot(cx);
10202 let mut cursor_positions = Vec::new();
10203 for row_range in &row_ranges {
10204 let anchor = snapshot.anchor_before(Point::new(
10205 row_range.end.previous_row().0,
10206 snapshot.line_len(row_range.end.previous_row()),
10207 ));
10208 cursor_positions.push(anchor..anchor);
10209 }
10210
10211 self.transact(window, cx, |this, window, cx| {
10212 for row_range in row_ranges.into_iter().rev() {
10213 for row in row_range.iter_rows().rev() {
10214 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10215 let next_line_row = row.next_row();
10216 let indent = snapshot.indent_size_for_line(next_line_row);
10217 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10218
10219 let replace =
10220 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10221 " "
10222 } else {
10223 ""
10224 };
10225
10226 this.buffer.update(cx, |buffer, cx| {
10227 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10228 });
10229 }
10230 }
10231
10232 this.change_selections(Default::default(), window, cx, |s| {
10233 s.select_anchor_ranges(cursor_positions)
10234 });
10235 });
10236 }
10237
10238 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10239 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10240 self.join_lines_impl(true, window, cx);
10241 }
10242
10243 pub fn sort_lines_case_sensitive(
10244 &mut self,
10245 _: &SortLinesCaseSensitive,
10246 window: &mut Window,
10247 cx: &mut Context<Self>,
10248 ) {
10249 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10250 }
10251
10252 pub fn sort_lines_by_length(
10253 &mut self,
10254 _: &SortLinesByLength,
10255 window: &mut Window,
10256 cx: &mut Context<Self>,
10257 ) {
10258 self.manipulate_immutable_lines(window, cx, |lines| {
10259 lines.sort_by_key(|&line| line.chars().count())
10260 })
10261 }
10262
10263 pub fn sort_lines_case_insensitive(
10264 &mut self,
10265 _: &SortLinesCaseInsensitive,
10266 window: &mut Window,
10267 cx: &mut Context<Self>,
10268 ) {
10269 self.manipulate_immutable_lines(window, cx, |lines| {
10270 lines.sort_by_key(|line| line.to_lowercase())
10271 })
10272 }
10273
10274 pub fn unique_lines_case_insensitive(
10275 &mut self,
10276 _: &UniqueLinesCaseInsensitive,
10277 window: &mut Window,
10278 cx: &mut Context<Self>,
10279 ) {
10280 self.manipulate_immutable_lines(window, cx, |lines| {
10281 let mut seen = HashSet::default();
10282 lines.retain(|line| seen.insert(line.to_lowercase()));
10283 })
10284 }
10285
10286 pub fn unique_lines_case_sensitive(
10287 &mut self,
10288 _: &UniqueLinesCaseSensitive,
10289 window: &mut Window,
10290 cx: &mut Context<Self>,
10291 ) {
10292 self.manipulate_immutable_lines(window, cx, |lines| {
10293 let mut seen = HashSet::default();
10294 lines.retain(|line| seen.insert(*line));
10295 })
10296 }
10297
10298 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10299 let Some(project) = self.project.clone() else {
10300 return;
10301 };
10302 self.reload(project, window, cx)
10303 .detach_and_notify_err(window, cx);
10304 }
10305
10306 pub fn restore_file(
10307 &mut self,
10308 _: &::git::RestoreFile,
10309 window: &mut Window,
10310 cx: &mut Context<Self>,
10311 ) {
10312 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10313 let mut buffer_ids = HashSet::default();
10314 let snapshot = self.buffer().read(cx).snapshot(cx);
10315 for selection in self.selections.all::<usize>(cx) {
10316 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10317 }
10318
10319 let buffer = self.buffer().read(cx);
10320 let ranges = buffer_ids
10321 .into_iter()
10322 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10323 .collect::<Vec<_>>();
10324
10325 self.restore_hunks_in_ranges(ranges, window, cx);
10326 }
10327
10328 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10329 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10330 let selections = self
10331 .selections
10332 .all(cx)
10333 .into_iter()
10334 .map(|s| s.range())
10335 .collect();
10336 self.restore_hunks_in_ranges(selections, window, cx);
10337 }
10338
10339 pub fn restore_hunks_in_ranges(
10340 &mut self,
10341 ranges: Vec<Range<Point>>,
10342 window: &mut Window,
10343 cx: &mut Context<Editor>,
10344 ) {
10345 let mut revert_changes = HashMap::default();
10346 let chunk_by = self
10347 .snapshot(window, cx)
10348 .hunks_for_ranges(ranges)
10349 .into_iter()
10350 .chunk_by(|hunk| hunk.buffer_id);
10351 for (buffer_id, hunks) in &chunk_by {
10352 let hunks = hunks.collect::<Vec<_>>();
10353 for hunk in &hunks {
10354 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10355 }
10356 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10357 }
10358 drop(chunk_by);
10359 if !revert_changes.is_empty() {
10360 self.transact(window, cx, |editor, window, cx| {
10361 editor.restore(revert_changes, window, cx);
10362 });
10363 }
10364 }
10365
10366 pub fn open_active_item_in_terminal(
10367 &mut self,
10368 _: &OpenInTerminal,
10369 window: &mut Window,
10370 cx: &mut Context<Self>,
10371 ) {
10372 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10373 let project_path = buffer.read(cx).project_path(cx)?;
10374 let project = self.project.as_ref()?.read(cx);
10375 let entry = project.entry_for_path(&project_path, cx)?;
10376 let parent = match &entry.canonical_path {
10377 Some(canonical_path) => canonical_path.to_path_buf(),
10378 None => project.absolute_path(&project_path, cx)?,
10379 }
10380 .parent()?
10381 .to_path_buf();
10382 Some(parent)
10383 }) {
10384 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10385 }
10386 }
10387
10388 fn set_breakpoint_context_menu(
10389 &mut self,
10390 display_row: DisplayRow,
10391 position: Option<Anchor>,
10392 clicked_point: gpui::Point<Pixels>,
10393 window: &mut Window,
10394 cx: &mut Context<Self>,
10395 ) {
10396 let source = self
10397 .buffer
10398 .read(cx)
10399 .snapshot(cx)
10400 .anchor_before(Point::new(display_row.0, 0u32));
10401
10402 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10403
10404 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10405 self,
10406 source,
10407 clicked_point,
10408 context_menu,
10409 window,
10410 cx,
10411 );
10412 }
10413
10414 fn add_edit_breakpoint_block(
10415 &mut self,
10416 anchor: Anchor,
10417 breakpoint: &Breakpoint,
10418 edit_action: BreakpointPromptEditAction,
10419 window: &mut Window,
10420 cx: &mut Context<Self>,
10421 ) {
10422 let weak_editor = cx.weak_entity();
10423 let bp_prompt = cx.new(|cx| {
10424 BreakpointPromptEditor::new(
10425 weak_editor,
10426 anchor,
10427 breakpoint.clone(),
10428 edit_action,
10429 window,
10430 cx,
10431 )
10432 });
10433
10434 let height = bp_prompt.update(cx, |this, cx| {
10435 this.prompt
10436 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10437 });
10438 let cloned_prompt = bp_prompt.clone();
10439 let blocks = vec![BlockProperties {
10440 style: BlockStyle::Sticky,
10441 placement: BlockPlacement::Above(anchor),
10442 height: Some(height),
10443 render: Arc::new(move |cx| {
10444 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10445 cloned_prompt.clone().into_any_element()
10446 }),
10447 priority: 0,
10448 render_in_minimap: true,
10449 }];
10450
10451 let focus_handle = bp_prompt.focus_handle(cx);
10452 window.focus(&focus_handle);
10453
10454 let block_ids = self.insert_blocks(blocks, None, cx);
10455 bp_prompt.update(cx, |prompt, _| {
10456 prompt.add_block_ids(block_ids);
10457 });
10458 }
10459
10460 pub(crate) fn breakpoint_at_row(
10461 &self,
10462 row: u32,
10463 window: &mut Window,
10464 cx: &mut Context<Self>,
10465 ) -> Option<(Anchor, Breakpoint)> {
10466 let snapshot = self.snapshot(window, cx);
10467 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10468
10469 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10470 }
10471
10472 pub(crate) fn breakpoint_at_anchor(
10473 &self,
10474 breakpoint_position: Anchor,
10475 snapshot: &EditorSnapshot,
10476 cx: &mut Context<Self>,
10477 ) -> Option<(Anchor, Breakpoint)> {
10478 let project = self.project.clone()?;
10479
10480 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10481 snapshot
10482 .buffer_snapshot
10483 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10484 })?;
10485
10486 let enclosing_excerpt = breakpoint_position.excerpt_id;
10487 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10488 let buffer_snapshot = buffer.read(cx).snapshot();
10489
10490 let row = buffer_snapshot
10491 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10492 .row;
10493
10494 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10495 let anchor_end = snapshot
10496 .buffer_snapshot
10497 .anchor_after(Point::new(row, line_len));
10498
10499 let bp = self
10500 .breakpoint_store
10501 .as_ref()?
10502 .read_with(cx, |breakpoint_store, cx| {
10503 breakpoint_store
10504 .breakpoints(
10505 &buffer,
10506 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10507 &buffer_snapshot,
10508 cx,
10509 )
10510 .next()
10511 .and_then(|(bp, _)| {
10512 let breakpoint_row = buffer_snapshot
10513 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10514 .row;
10515
10516 if breakpoint_row == row {
10517 snapshot
10518 .buffer_snapshot
10519 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10520 .map(|position| (position, bp.bp.clone()))
10521 } else {
10522 None
10523 }
10524 })
10525 });
10526 bp
10527 }
10528
10529 pub fn edit_log_breakpoint(
10530 &mut self,
10531 _: &EditLogBreakpoint,
10532 window: &mut Window,
10533 cx: &mut Context<Self>,
10534 ) {
10535 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10536 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10537 message: None,
10538 state: BreakpointState::Enabled,
10539 condition: None,
10540 hit_condition: None,
10541 });
10542
10543 self.add_edit_breakpoint_block(
10544 anchor,
10545 &breakpoint,
10546 BreakpointPromptEditAction::Log,
10547 window,
10548 cx,
10549 );
10550 }
10551 }
10552
10553 fn breakpoints_at_cursors(
10554 &self,
10555 window: &mut Window,
10556 cx: &mut Context<Self>,
10557 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10558 let snapshot = self.snapshot(window, cx);
10559 let cursors = self
10560 .selections
10561 .disjoint_anchors()
10562 .into_iter()
10563 .map(|selection| {
10564 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10565
10566 let breakpoint_position = self
10567 .breakpoint_at_row(cursor_position.row, window, cx)
10568 .map(|bp| bp.0)
10569 .unwrap_or_else(|| {
10570 snapshot
10571 .display_snapshot
10572 .buffer_snapshot
10573 .anchor_after(Point::new(cursor_position.row, 0))
10574 });
10575
10576 let breakpoint = self
10577 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10578 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10579
10580 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10581 })
10582 // 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.
10583 .collect::<HashMap<Anchor, _>>();
10584
10585 cursors.into_iter().collect()
10586 }
10587
10588 pub fn enable_breakpoint(
10589 &mut self,
10590 _: &crate::actions::EnableBreakpoint,
10591 window: &mut Window,
10592 cx: &mut Context<Self>,
10593 ) {
10594 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10595 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10596 continue;
10597 };
10598 self.edit_breakpoint_at_anchor(
10599 anchor,
10600 breakpoint,
10601 BreakpointEditAction::InvertState,
10602 cx,
10603 );
10604 }
10605 }
10606
10607 pub fn disable_breakpoint(
10608 &mut self,
10609 _: &crate::actions::DisableBreakpoint,
10610 window: &mut Window,
10611 cx: &mut Context<Self>,
10612 ) {
10613 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10614 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10615 continue;
10616 };
10617 self.edit_breakpoint_at_anchor(
10618 anchor,
10619 breakpoint,
10620 BreakpointEditAction::InvertState,
10621 cx,
10622 );
10623 }
10624 }
10625
10626 pub fn toggle_breakpoint(
10627 &mut self,
10628 _: &crate::actions::ToggleBreakpoint,
10629 window: &mut Window,
10630 cx: &mut Context<Self>,
10631 ) {
10632 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10633 if let Some(breakpoint) = breakpoint {
10634 self.edit_breakpoint_at_anchor(
10635 anchor,
10636 breakpoint,
10637 BreakpointEditAction::Toggle,
10638 cx,
10639 );
10640 } else {
10641 self.edit_breakpoint_at_anchor(
10642 anchor,
10643 Breakpoint::new_standard(),
10644 BreakpointEditAction::Toggle,
10645 cx,
10646 );
10647 }
10648 }
10649 }
10650
10651 pub fn edit_breakpoint_at_anchor(
10652 &mut self,
10653 breakpoint_position: Anchor,
10654 breakpoint: Breakpoint,
10655 edit_action: BreakpointEditAction,
10656 cx: &mut Context<Self>,
10657 ) {
10658 let Some(breakpoint_store) = &self.breakpoint_store else {
10659 return;
10660 };
10661
10662 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10663 if breakpoint_position == Anchor::min() {
10664 self.buffer()
10665 .read(cx)
10666 .excerpt_buffer_ids()
10667 .into_iter()
10668 .next()
10669 } else {
10670 None
10671 }
10672 }) else {
10673 return;
10674 };
10675
10676 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10677 return;
10678 };
10679
10680 breakpoint_store.update(cx, |breakpoint_store, cx| {
10681 breakpoint_store.toggle_breakpoint(
10682 buffer,
10683 BreakpointWithPosition {
10684 position: breakpoint_position.text_anchor,
10685 bp: breakpoint,
10686 },
10687 edit_action,
10688 cx,
10689 );
10690 });
10691
10692 cx.notify();
10693 }
10694
10695 #[cfg(any(test, feature = "test-support"))]
10696 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10697 self.breakpoint_store.clone()
10698 }
10699
10700 pub fn prepare_restore_change(
10701 &self,
10702 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10703 hunk: &MultiBufferDiffHunk,
10704 cx: &mut App,
10705 ) -> Option<()> {
10706 if hunk.is_created_file() {
10707 return None;
10708 }
10709 let buffer = self.buffer.read(cx);
10710 let diff = buffer.diff_for(hunk.buffer_id)?;
10711 let buffer = buffer.buffer(hunk.buffer_id)?;
10712 let buffer = buffer.read(cx);
10713 let original_text = diff
10714 .read(cx)
10715 .base_text()
10716 .as_rope()
10717 .slice(hunk.diff_base_byte_range.clone());
10718 let buffer_snapshot = buffer.snapshot();
10719 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10720 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10721 probe
10722 .0
10723 .start
10724 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10725 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10726 }) {
10727 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10728 Some(())
10729 } else {
10730 None
10731 }
10732 }
10733
10734 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10735 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10736 }
10737
10738 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10739 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10740 }
10741
10742 fn manipulate_lines<M>(
10743 &mut self,
10744 window: &mut Window,
10745 cx: &mut Context<Self>,
10746 mut manipulate: M,
10747 ) where
10748 M: FnMut(&str) -> LineManipulationResult,
10749 {
10750 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10751
10752 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10753 let buffer = self.buffer.read(cx).snapshot(cx);
10754
10755 let mut edits = Vec::new();
10756
10757 let selections = self.selections.all::<Point>(cx);
10758 let mut selections = selections.iter().peekable();
10759 let mut contiguous_row_selections = Vec::new();
10760 let mut new_selections = Vec::new();
10761 let mut added_lines = 0;
10762 let mut removed_lines = 0;
10763
10764 while let Some(selection) = selections.next() {
10765 let (start_row, end_row) = consume_contiguous_rows(
10766 &mut contiguous_row_selections,
10767 selection,
10768 &display_map,
10769 &mut selections,
10770 );
10771
10772 let start_point = Point::new(start_row.0, 0);
10773 let end_point = Point::new(
10774 end_row.previous_row().0,
10775 buffer.line_len(end_row.previous_row()),
10776 );
10777 let text = buffer
10778 .text_for_range(start_point..end_point)
10779 .collect::<String>();
10780
10781 let LineManipulationResult {
10782 new_text,
10783 line_count_before,
10784 line_count_after,
10785 } = manipulate(&text);
10786
10787 edits.push((start_point..end_point, new_text));
10788
10789 // Selections must change based on added and removed line count
10790 let start_row =
10791 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10792 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10793 new_selections.push(Selection {
10794 id: selection.id,
10795 start: start_row,
10796 end: end_row,
10797 goal: SelectionGoal::None,
10798 reversed: selection.reversed,
10799 });
10800
10801 if line_count_after > line_count_before {
10802 added_lines += line_count_after - line_count_before;
10803 } else if line_count_before > line_count_after {
10804 removed_lines += line_count_before - line_count_after;
10805 }
10806 }
10807
10808 self.transact(window, cx, |this, window, cx| {
10809 let buffer = this.buffer.update(cx, |buffer, cx| {
10810 buffer.edit(edits, None, cx);
10811 buffer.snapshot(cx)
10812 });
10813
10814 // Recalculate offsets on newly edited buffer
10815 let new_selections = new_selections
10816 .iter()
10817 .map(|s| {
10818 let start_point = Point::new(s.start.0, 0);
10819 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10820 Selection {
10821 id: s.id,
10822 start: buffer.point_to_offset(start_point),
10823 end: buffer.point_to_offset(end_point),
10824 goal: s.goal,
10825 reversed: s.reversed,
10826 }
10827 })
10828 .collect();
10829
10830 this.change_selections(Default::default(), window, cx, |s| {
10831 s.select(new_selections);
10832 });
10833
10834 this.request_autoscroll(Autoscroll::fit(), cx);
10835 });
10836 }
10837
10838 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10839 self.manipulate_text(window, cx, |text| {
10840 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10841 if has_upper_case_characters {
10842 text.to_lowercase()
10843 } else {
10844 text.to_uppercase()
10845 }
10846 })
10847 }
10848
10849 fn manipulate_immutable_lines<Fn>(
10850 &mut self,
10851 window: &mut Window,
10852 cx: &mut Context<Self>,
10853 mut callback: Fn,
10854 ) where
10855 Fn: FnMut(&mut Vec<&str>),
10856 {
10857 self.manipulate_lines(window, cx, |text| {
10858 let mut lines: Vec<&str> = text.split('\n').collect();
10859 let line_count_before = lines.len();
10860
10861 callback(&mut lines);
10862
10863 LineManipulationResult {
10864 new_text: lines.join("\n"),
10865 line_count_before,
10866 line_count_after: lines.len(),
10867 }
10868 });
10869 }
10870
10871 fn manipulate_mutable_lines<Fn>(
10872 &mut self,
10873 window: &mut Window,
10874 cx: &mut Context<Self>,
10875 mut callback: Fn,
10876 ) where
10877 Fn: FnMut(&mut Vec<Cow<'_, str>>),
10878 {
10879 self.manipulate_lines(window, cx, |text| {
10880 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
10881 let line_count_before = lines.len();
10882
10883 callback(&mut lines);
10884
10885 LineManipulationResult {
10886 new_text: lines.join("\n"),
10887 line_count_before,
10888 line_count_after: lines.len(),
10889 }
10890 });
10891 }
10892
10893 pub fn convert_indentation_to_spaces(
10894 &mut self,
10895 _: &ConvertIndentationToSpaces,
10896 window: &mut Window,
10897 cx: &mut Context<Self>,
10898 ) {
10899 let settings = self.buffer.read(cx).language_settings(cx);
10900 let tab_size = settings.tab_size.get() as usize;
10901
10902 self.manipulate_mutable_lines(window, cx, |lines| {
10903 // Allocates a reasonably sized scratch buffer once for the whole loop
10904 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10905 // Avoids recomputing spaces that could be inserted many times
10906 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10907 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10908 .collect();
10909
10910 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10911 let mut chars = line.as_ref().chars();
10912 let mut col = 0;
10913 let mut changed = false;
10914
10915 while let Some(ch) = chars.next() {
10916 match ch {
10917 ' ' => {
10918 reindented_line.push(' ');
10919 col += 1;
10920 }
10921 '\t' => {
10922 // \t are converted to spaces depending on the current column
10923 let spaces_len = tab_size - (col % tab_size);
10924 reindented_line.extend(&space_cache[spaces_len - 1]);
10925 col += spaces_len;
10926 changed = true;
10927 }
10928 _ => {
10929 // If we dont append before break, the character is consumed
10930 reindented_line.push(ch);
10931 break;
10932 }
10933 }
10934 }
10935
10936 if !changed {
10937 reindented_line.clear();
10938 continue;
10939 }
10940 // Append the rest of the line and replace old reference with new one
10941 reindented_line.extend(chars);
10942 *line = Cow::Owned(reindented_line.clone());
10943 reindented_line.clear();
10944 }
10945 });
10946 }
10947
10948 pub fn convert_indentation_to_tabs(
10949 &mut self,
10950 _: &ConvertIndentationToTabs,
10951 window: &mut Window,
10952 cx: &mut Context<Self>,
10953 ) {
10954 let settings = self.buffer.read(cx).language_settings(cx);
10955 let tab_size = settings.tab_size.get() as usize;
10956
10957 self.manipulate_mutable_lines(window, cx, |lines| {
10958 // Allocates a reasonably sized buffer once for the whole loop
10959 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10960 // Avoids recomputing spaces that could be inserted many times
10961 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10962 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10963 .collect();
10964
10965 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10966 let mut chars = line.chars();
10967 let mut spaces_count = 0;
10968 let mut first_non_indent_char = None;
10969 let mut changed = false;
10970
10971 while let Some(ch) = chars.next() {
10972 match ch {
10973 ' ' => {
10974 // Keep track of spaces. Append \t when we reach tab_size
10975 spaces_count += 1;
10976 changed = true;
10977 if spaces_count == tab_size {
10978 reindented_line.push('\t');
10979 spaces_count = 0;
10980 }
10981 }
10982 '\t' => {
10983 reindented_line.push('\t');
10984 spaces_count = 0;
10985 }
10986 _ => {
10987 // Dont append it yet, we might have remaining spaces
10988 first_non_indent_char = Some(ch);
10989 break;
10990 }
10991 }
10992 }
10993
10994 if !changed {
10995 reindented_line.clear();
10996 continue;
10997 }
10998 // Remaining spaces that didn't make a full tab stop
10999 if spaces_count > 0 {
11000 reindented_line.extend(&space_cache[spaces_count - 1]);
11001 }
11002 // If we consume an extra character that was not indentation, add it back
11003 if let Some(extra_char) = first_non_indent_char {
11004 reindented_line.push(extra_char);
11005 }
11006 // Append the rest of the line and replace old reference with new one
11007 reindented_line.extend(chars);
11008 *line = Cow::Owned(reindented_line.clone());
11009 reindented_line.clear();
11010 }
11011 });
11012 }
11013
11014 pub fn convert_to_upper_case(
11015 &mut self,
11016 _: &ConvertToUpperCase,
11017 window: &mut Window,
11018 cx: &mut Context<Self>,
11019 ) {
11020 self.manipulate_text(window, cx, |text| text.to_uppercase())
11021 }
11022
11023 pub fn convert_to_lower_case(
11024 &mut self,
11025 _: &ConvertToLowerCase,
11026 window: &mut Window,
11027 cx: &mut Context<Self>,
11028 ) {
11029 self.manipulate_text(window, cx, |text| text.to_lowercase())
11030 }
11031
11032 pub fn convert_to_title_case(
11033 &mut self,
11034 _: &ConvertToTitleCase,
11035 window: &mut Window,
11036 cx: &mut Context<Self>,
11037 ) {
11038 self.manipulate_text(window, cx, |text| {
11039 text.split('\n')
11040 .map(|line| line.to_case(Case::Title))
11041 .join("\n")
11042 })
11043 }
11044
11045 pub fn convert_to_snake_case(
11046 &mut self,
11047 _: &ConvertToSnakeCase,
11048 window: &mut Window,
11049 cx: &mut Context<Self>,
11050 ) {
11051 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11052 }
11053
11054 pub fn convert_to_kebab_case(
11055 &mut self,
11056 _: &ConvertToKebabCase,
11057 window: &mut Window,
11058 cx: &mut Context<Self>,
11059 ) {
11060 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11061 }
11062
11063 pub fn convert_to_upper_camel_case(
11064 &mut self,
11065 _: &ConvertToUpperCamelCase,
11066 window: &mut Window,
11067 cx: &mut Context<Self>,
11068 ) {
11069 self.manipulate_text(window, cx, |text| {
11070 text.split('\n')
11071 .map(|line| line.to_case(Case::UpperCamel))
11072 .join("\n")
11073 })
11074 }
11075
11076 pub fn convert_to_lower_camel_case(
11077 &mut self,
11078 _: &ConvertToLowerCamelCase,
11079 window: &mut Window,
11080 cx: &mut Context<Self>,
11081 ) {
11082 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11083 }
11084
11085 pub fn convert_to_opposite_case(
11086 &mut self,
11087 _: &ConvertToOppositeCase,
11088 window: &mut Window,
11089 cx: &mut Context<Self>,
11090 ) {
11091 self.manipulate_text(window, cx, |text| {
11092 text.chars()
11093 .fold(String::with_capacity(text.len()), |mut t, c| {
11094 if c.is_uppercase() {
11095 t.extend(c.to_lowercase());
11096 } else {
11097 t.extend(c.to_uppercase());
11098 }
11099 t
11100 })
11101 })
11102 }
11103
11104 pub fn convert_to_rot13(
11105 &mut self,
11106 _: &ConvertToRot13,
11107 window: &mut Window,
11108 cx: &mut Context<Self>,
11109 ) {
11110 self.manipulate_text(window, cx, |text| {
11111 text.chars()
11112 .map(|c| match c {
11113 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11114 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11115 _ => c,
11116 })
11117 .collect()
11118 })
11119 }
11120
11121 pub fn convert_to_rot47(
11122 &mut self,
11123 _: &ConvertToRot47,
11124 window: &mut Window,
11125 cx: &mut Context<Self>,
11126 ) {
11127 self.manipulate_text(window, cx, |text| {
11128 text.chars()
11129 .map(|c| {
11130 let code_point = c as u32;
11131 if code_point >= 33 && code_point <= 126 {
11132 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11133 }
11134 c
11135 })
11136 .collect()
11137 })
11138 }
11139
11140 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11141 where
11142 Fn: FnMut(&str) -> String,
11143 {
11144 let buffer = self.buffer.read(cx).snapshot(cx);
11145
11146 let mut new_selections = Vec::new();
11147 let mut edits = Vec::new();
11148 let mut selection_adjustment = 0i32;
11149
11150 for selection in self.selections.all::<usize>(cx) {
11151 let selection_is_empty = selection.is_empty();
11152
11153 let (start, end) = if selection_is_empty {
11154 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11155 (word_range.start, word_range.end)
11156 } else {
11157 (selection.start, selection.end)
11158 };
11159
11160 let text = buffer.text_for_range(start..end).collect::<String>();
11161 let old_length = text.len() as i32;
11162 let text = callback(&text);
11163
11164 new_selections.push(Selection {
11165 start: (start as i32 - selection_adjustment) as usize,
11166 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11167 goal: SelectionGoal::None,
11168 ..selection
11169 });
11170
11171 selection_adjustment += old_length - text.len() as i32;
11172
11173 edits.push((start..end, text));
11174 }
11175
11176 self.transact(window, cx, |this, window, cx| {
11177 this.buffer.update(cx, |buffer, cx| {
11178 buffer.edit(edits, None, cx);
11179 });
11180
11181 this.change_selections(Default::default(), window, cx, |s| {
11182 s.select(new_selections);
11183 });
11184
11185 this.request_autoscroll(Autoscroll::fit(), cx);
11186 });
11187 }
11188
11189 pub fn move_selection_on_drop(
11190 &mut self,
11191 selection: &Selection<Anchor>,
11192 target: DisplayPoint,
11193 is_cut: bool,
11194 window: &mut Window,
11195 cx: &mut Context<Self>,
11196 ) {
11197 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11198 let buffer = &display_map.buffer_snapshot;
11199 let mut edits = Vec::new();
11200 let insert_point = display_map
11201 .clip_point(target, Bias::Left)
11202 .to_point(&display_map);
11203 let text = buffer
11204 .text_for_range(selection.start..selection.end)
11205 .collect::<String>();
11206 if is_cut {
11207 edits.push(((selection.start..selection.end), String::new()));
11208 }
11209 let insert_anchor = buffer.anchor_before(insert_point);
11210 edits.push(((insert_anchor..insert_anchor), text));
11211 let last_edit_start = insert_anchor.bias_left(buffer);
11212 let last_edit_end = insert_anchor.bias_right(buffer);
11213 self.transact(window, cx, |this, window, cx| {
11214 this.buffer.update(cx, |buffer, cx| {
11215 buffer.edit(edits, None, cx);
11216 });
11217 this.change_selections(Default::default(), window, cx, |s| {
11218 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11219 });
11220 });
11221 }
11222
11223 pub fn clear_selection_drag_state(&mut self) {
11224 self.selection_drag_state = SelectionDragState::None;
11225 }
11226
11227 pub fn duplicate(
11228 &mut self,
11229 upwards: bool,
11230 whole_lines: bool,
11231 window: &mut Window,
11232 cx: &mut Context<Self>,
11233 ) {
11234 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11235
11236 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11237 let buffer = &display_map.buffer_snapshot;
11238 let selections = self.selections.all::<Point>(cx);
11239
11240 let mut edits = Vec::new();
11241 let mut selections_iter = selections.iter().peekable();
11242 while let Some(selection) = selections_iter.next() {
11243 let mut rows = selection.spanned_rows(false, &display_map);
11244 // duplicate line-wise
11245 if whole_lines || selection.start == selection.end {
11246 // Avoid duplicating the same lines twice.
11247 while let Some(next_selection) = selections_iter.peek() {
11248 let next_rows = next_selection.spanned_rows(false, &display_map);
11249 if next_rows.start < rows.end {
11250 rows.end = next_rows.end;
11251 selections_iter.next().unwrap();
11252 } else {
11253 break;
11254 }
11255 }
11256
11257 // Copy the text from the selected row region and splice it either at the start
11258 // or end of the region.
11259 let start = Point::new(rows.start.0, 0);
11260 let end = Point::new(
11261 rows.end.previous_row().0,
11262 buffer.line_len(rows.end.previous_row()),
11263 );
11264 let text = buffer
11265 .text_for_range(start..end)
11266 .chain(Some("\n"))
11267 .collect::<String>();
11268 let insert_location = if upwards {
11269 Point::new(rows.end.0, 0)
11270 } else {
11271 start
11272 };
11273 edits.push((insert_location..insert_location, text));
11274 } else {
11275 // duplicate character-wise
11276 let start = selection.start;
11277 let end = selection.end;
11278 let text = buffer.text_for_range(start..end).collect::<String>();
11279 edits.push((selection.end..selection.end, text));
11280 }
11281 }
11282
11283 self.transact(window, cx, |this, _, cx| {
11284 this.buffer.update(cx, |buffer, cx| {
11285 buffer.edit(edits, None, cx);
11286 });
11287
11288 this.request_autoscroll(Autoscroll::fit(), cx);
11289 });
11290 }
11291
11292 pub fn duplicate_line_up(
11293 &mut self,
11294 _: &DuplicateLineUp,
11295 window: &mut Window,
11296 cx: &mut Context<Self>,
11297 ) {
11298 self.duplicate(true, true, window, cx);
11299 }
11300
11301 pub fn duplicate_line_down(
11302 &mut self,
11303 _: &DuplicateLineDown,
11304 window: &mut Window,
11305 cx: &mut Context<Self>,
11306 ) {
11307 self.duplicate(false, true, window, cx);
11308 }
11309
11310 pub fn duplicate_selection(
11311 &mut self,
11312 _: &DuplicateSelection,
11313 window: &mut Window,
11314 cx: &mut Context<Self>,
11315 ) {
11316 self.duplicate(false, false, window, cx);
11317 }
11318
11319 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11320 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11321 if self.mode.is_single_line() {
11322 cx.propagate();
11323 return;
11324 }
11325
11326 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11327 let buffer = self.buffer.read(cx).snapshot(cx);
11328
11329 let mut edits = Vec::new();
11330 let mut unfold_ranges = Vec::new();
11331 let mut refold_creases = Vec::new();
11332
11333 let selections = self.selections.all::<Point>(cx);
11334 let mut selections = selections.iter().peekable();
11335 let mut contiguous_row_selections = Vec::new();
11336 let mut new_selections = Vec::new();
11337
11338 while let Some(selection) = selections.next() {
11339 // Find all the selections that span a contiguous row range
11340 let (start_row, end_row) = consume_contiguous_rows(
11341 &mut contiguous_row_selections,
11342 selection,
11343 &display_map,
11344 &mut selections,
11345 );
11346
11347 // Move the text spanned by the row range to be before the line preceding the row range
11348 if start_row.0 > 0 {
11349 let range_to_move = Point::new(
11350 start_row.previous_row().0,
11351 buffer.line_len(start_row.previous_row()),
11352 )
11353 ..Point::new(
11354 end_row.previous_row().0,
11355 buffer.line_len(end_row.previous_row()),
11356 );
11357 let insertion_point = display_map
11358 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11359 .0;
11360
11361 // Don't move lines across excerpts
11362 if buffer
11363 .excerpt_containing(insertion_point..range_to_move.end)
11364 .is_some()
11365 {
11366 let text = buffer
11367 .text_for_range(range_to_move.clone())
11368 .flat_map(|s| s.chars())
11369 .skip(1)
11370 .chain(['\n'])
11371 .collect::<String>();
11372
11373 edits.push((
11374 buffer.anchor_after(range_to_move.start)
11375 ..buffer.anchor_before(range_to_move.end),
11376 String::new(),
11377 ));
11378 let insertion_anchor = buffer.anchor_after(insertion_point);
11379 edits.push((insertion_anchor..insertion_anchor, text));
11380
11381 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11382
11383 // Move selections up
11384 new_selections.extend(contiguous_row_selections.drain(..).map(
11385 |mut selection| {
11386 selection.start.row -= row_delta;
11387 selection.end.row -= row_delta;
11388 selection
11389 },
11390 ));
11391
11392 // Move folds up
11393 unfold_ranges.push(range_to_move.clone());
11394 for fold in display_map.folds_in_range(
11395 buffer.anchor_before(range_to_move.start)
11396 ..buffer.anchor_after(range_to_move.end),
11397 ) {
11398 let mut start = fold.range.start.to_point(&buffer);
11399 let mut end = fold.range.end.to_point(&buffer);
11400 start.row -= row_delta;
11401 end.row -= row_delta;
11402 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11403 }
11404 }
11405 }
11406
11407 // If we didn't move line(s), preserve the existing selections
11408 new_selections.append(&mut contiguous_row_selections);
11409 }
11410
11411 self.transact(window, cx, |this, window, cx| {
11412 this.unfold_ranges(&unfold_ranges, true, true, cx);
11413 this.buffer.update(cx, |buffer, cx| {
11414 for (range, text) in edits {
11415 buffer.edit([(range, text)], None, cx);
11416 }
11417 });
11418 this.fold_creases(refold_creases, true, window, cx);
11419 this.change_selections(Default::default(), window, cx, |s| {
11420 s.select(new_selections);
11421 })
11422 });
11423 }
11424
11425 pub fn move_line_down(
11426 &mut self,
11427 _: &MoveLineDown,
11428 window: &mut Window,
11429 cx: &mut Context<Self>,
11430 ) {
11431 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11432 if self.mode.is_single_line() {
11433 cx.propagate();
11434 return;
11435 }
11436
11437 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11438 let buffer = self.buffer.read(cx).snapshot(cx);
11439
11440 let mut edits = Vec::new();
11441 let mut unfold_ranges = Vec::new();
11442 let mut refold_creases = Vec::new();
11443
11444 let selections = self.selections.all::<Point>(cx);
11445 let mut selections = selections.iter().peekable();
11446 let mut contiguous_row_selections = Vec::new();
11447 let mut new_selections = Vec::new();
11448
11449 while let Some(selection) = selections.next() {
11450 // Find all the selections that span a contiguous row range
11451 let (start_row, end_row) = consume_contiguous_rows(
11452 &mut contiguous_row_selections,
11453 selection,
11454 &display_map,
11455 &mut selections,
11456 );
11457
11458 // Move the text spanned by the row range to be after the last line of the row range
11459 if end_row.0 <= buffer.max_point().row {
11460 let range_to_move =
11461 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11462 let insertion_point = display_map
11463 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11464 .0;
11465
11466 // Don't move lines across excerpt boundaries
11467 if buffer
11468 .excerpt_containing(range_to_move.start..insertion_point)
11469 .is_some()
11470 {
11471 let mut text = String::from("\n");
11472 text.extend(buffer.text_for_range(range_to_move.clone()));
11473 text.pop(); // Drop trailing newline
11474 edits.push((
11475 buffer.anchor_after(range_to_move.start)
11476 ..buffer.anchor_before(range_to_move.end),
11477 String::new(),
11478 ));
11479 let insertion_anchor = buffer.anchor_after(insertion_point);
11480 edits.push((insertion_anchor..insertion_anchor, text));
11481
11482 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11483
11484 // Move selections down
11485 new_selections.extend(contiguous_row_selections.drain(..).map(
11486 |mut selection| {
11487 selection.start.row += row_delta;
11488 selection.end.row += row_delta;
11489 selection
11490 },
11491 ));
11492
11493 // Move folds down
11494 unfold_ranges.push(range_to_move.clone());
11495 for fold in display_map.folds_in_range(
11496 buffer.anchor_before(range_to_move.start)
11497 ..buffer.anchor_after(range_to_move.end),
11498 ) {
11499 let mut start = fold.range.start.to_point(&buffer);
11500 let mut end = fold.range.end.to_point(&buffer);
11501 start.row += row_delta;
11502 end.row += row_delta;
11503 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11504 }
11505 }
11506 }
11507
11508 // If we didn't move line(s), preserve the existing selections
11509 new_selections.append(&mut contiguous_row_selections);
11510 }
11511
11512 self.transact(window, cx, |this, window, cx| {
11513 this.unfold_ranges(&unfold_ranges, true, true, cx);
11514 this.buffer.update(cx, |buffer, cx| {
11515 for (range, text) in edits {
11516 buffer.edit([(range, text)], None, cx);
11517 }
11518 });
11519 this.fold_creases(refold_creases, true, window, cx);
11520 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11521 });
11522 }
11523
11524 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11525 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11526 let text_layout_details = &self.text_layout_details(window);
11527 self.transact(window, cx, |this, window, cx| {
11528 let edits = this.change_selections(Default::default(), window, cx, |s| {
11529 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11530 s.move_with(|display_map, selection| {
11531 if !selection.is_empty() {
11532 return;
11533 }
11534
11535 let mut head = selection.head();
11536 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11537 if head.column() == display_map.line_len(head.row()) {
11538 transpose_offset = display_map
11539 .buffer_snapshot
11540 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11541 }
11542
11543 if transpose_offset == 0 {
11544 return;
11545 }
11546
11547 *head.column_mut() += 1;
11548 head = display_map.clip_point(head, Bias::Right);
11549 let goal = SelectionGoal::HorizontalPosition(
11550 display_map
11551 .x_for_display_point(head, text_layout_details)
11552 .into(),
11553 );
11554 selection.collapse_to(head, goal);
11555
11556 let transpose_start = display_map
11557 .buffer_snapshot
11558 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11559 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11560 let transpose_end = display_map
11561 .buffer_snapshot
11562 .clip_offset(transpose_offset + 1, Bias::Right);
11563 if let Some(ch) =
11564 display_map.buffer_snapshot.chars_at(transpose_start).next()
11565 {
11566 edits.push((transpose_start..transpose_offset, String::new()));
11567 edits.push((transpose_end..transpose_end, ch.to_string()));
11568 }
11569 }
11570 });
11571 edits
11572 });
11573 this.buffer
11574 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11575 let selections = this.selections.all::<usize>(cx);
11576 this.change_selections(Default::default(), window, cx, |s| {
11577 s.select(selections);
11578 });
11579 });
11580 }
11581
11582 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11583 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11584 if self.mode.is_single_line() {
11585 cx.propagate();
11586 return;
11587 }
11588
11589 self.rewrap_impl(RewrapOptions::default(), cx)
11590 }
11591
11592 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11593 let buffer = self.buffer.read(cx).snapshot(cx);
11594 let selections = self.selections.all::<Point>(cx);
11595
11596 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11597 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11598 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11599 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11600 .peekable();
11601
11602 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11603 row
11604 } else {
11605 return Vec::new();
11606 };
11607
11608 let language_settings = buffer.language_settings_at(selection.head(), cx);
11609 let language_scope = buffer.language_scope_at(selection.head());
11610
11611 let indent_and_prefix_for_row =
11612 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11613 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11614 let (comment_prefix, rewrap_prefix) =
11615 if let Some(language_scope) = &language_scope {
11616 let indent_end = Point::new(row, indent.len);
11617 let comment_prefix = language_scope
11618 .line_comment_prefixes()
11619 .iter()
11620 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11621 .map(|prefix| prefix.to_string());
11622 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11623 let line_text_after_indent = buffer
11624 .text_for_range(indent_end..line_end)
11625 .collect::<String>();
11626 let rewrap_prefix = language_scope
11627 .rewrap_prefixes()
11628 .iter()
11629 .find_map(|prefix_regex| {
11630 prefix_regex.find(&line_text_after_indent).map(|mat| {
11631 if mat.start() == 0 {
11632 Some(mat.as_str().to_string())
11633 } else {
11634 None
11635 }
11636 })
11637 })
11638 .flatten();
11639 (comment_prefix, rewrap_prefix)
11640 } else {
11641 (None, None)
11642 };
11643 (indent, comment_prefix, rewrap_prefix)
11644 };
11645
11646 let mut ranges = Vec::new();
11647 let from_empty_selection = selection.is_empty();
11648
11649 let mut current_range_start = first_row;
11650 let mut prev_row = first_row;
11651 let (
11652 mut current_range_indent,
11653 mut current_range_comment_prefix,
11654 mut current_range_rewrap_prefix,
11655 ) = indent_and_prefix_for_row(first_row);
11656
11657 for row in non_blank_rows_iter.skip(1) {
11658 let has_paragraph_break = row > prev_row + 1;
11659
11660 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11661 indent_and_prefix_for_row(row);
11662
11663 let has_indent_change = row_indent != current_range_indent;
11664 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11665
11666 let has_boundary_change = has_comment_change
11667 || row_rewrap_prefix.is_some()
11668 || (has_indent_change && current_range_comment_prefix.is_some());
11669
11670 if has_paragraph_break || has_boundary_change {
11671 ranges.push((
11672 language_settings.clone(),
11673 Point::new(current_range_start, 0)
11674 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11675 current_range_indent,
11676 current_range_comment_prefix.clone(),
11677 current_range_rewrap_prefix.clone(),
11678 from_empty_selection,
11679 ));
11680 current_range_start = row;
11681 current_range_indent = row_indent;
11682 current_range_comment_prefix = row_comment_prefix;
11683 current_range_rewrap_prefix = row_rewrap_prefix;
11684 }
11685 prev_row = row;
11686 }
11687
11688 ranges.push((
11689 language_settings.clone(),
11690 Point::new(current_range_start, 0)
11691 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11692 current_range_indent,
11693 current_range_comment_prefix,
11694 current_range_rewrap_prefix,
11695 from_empty_selection,
11696 ));
11697
11698 ranges
11699 });
11700
11701 let mut edits = Vec::new();
11702 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11703
11704 for (
11705 language_settings,
11706 wrap_range,
11707 indent_size,
11708 comment_prefix,
11709 rewrap_prefix,
11710 from_empty_selection,
11711 ) in wrap_ranges
11712 {
11713 let mut start_row = wrap_range.start.row;
11714 let mut end_row = wrap_range.end.row;
11715
11716 // Skip selections that overlap with a range that has already been rewrapped.
11717 let selection_range = start_row..end_row;
11718 if rewrapped_row_ranges
11719 .iter()
11720 .any(|range| range.overlaps(&selection_range))
11721 {
11722 continue;
11723 }
11724
11725 let tab_size = language_settings.tab_size;
11726
11727 let indent_prefix = indent_size.chars().collect::<String>();
11728 let mut line_prefix = indent_prefix.clone();
11729 let mut inside_comment = false;
11730 if let Some(prefix) = &comment_prefix {
11731 line_prefix.push_str(prefix);
11732 inside_comment = true;
11733 }
11734 if let Some(prefix) = &rewrap_prefix {
11735 line_prefix.push_str(prefix);
11736 }
11737
11738 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11739 RewrapBehavior::InComments => inside_comment,
11740 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11741 RewrapBehavior::Anywhere => true,
11742 };
11743
11744 let should_rewrap = options.override_language_settings
11745 || allow_rewrap_based_on_language
11746 || self.hard_wrap.is_some();
11747 if !should_rewrap {
11748 continue;
11749 }
11750
11751 if from_empty_selection {
11752 'expand_upwards: while start_row > 0 {
11753 let prev_row = start_row - 1;
11754 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11755 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11756 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11757 {
11758 start_row = prev_row;
11759 } else {
11760 break 'expand_upwards;
11761 }
11762 }
11763
11764 'expand_downwards: while end_row < buffer.max_point().row {
11765 let next_row = end_row + 1;
11766 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11767 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11768 && !buffer.is_line_blank(MultiBufferRow(next_row))
11769 {
11770 end_row = next_row;
11771 } else {
11772 break 'expand_downwards;
11773 }
11774 }
11775 }
11776
11777 let start = Point::new(start_row, 0);
11778 let start_offset = start.to_offset(&buffer);
11779 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11780 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11781 let Some(lines_without_prefixes) = selection_text
11782 .lines()
11783 .enumerate()
11784 .map(|(ix, line)| {
11785 let line_trimmed = line.trim_start();
11786 if rewrap_prefix.is_some() && ix > 0 {
11787 Ok(line_trimmed)
11788 } else {
11789 line_trimmed
11790 .strip_prefix(&line_prefix.trim_start())
11791 .with_context(|| {
11792 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11793 })
11794 }
11795 })
11796 .collect::<Result<Vec<_>, _>>()
11797 .log_err()
11798 else {
11799 continue;
11800 };
11801
11802 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11803 buffer
11804 .language_settings_at(Point::new(start_row, 0), cx)
11805 .preferred_line_length as usize
11806 });
11807
11808 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11809 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11810 } else {
11811 line_prefix.clone()
11812 };
11813
11814 let wrapped_text = wrap_with_prefix(
11815 line_prefix,
11816 subsequent_lines_prefix,
11817 lines_without_prefixes.join("\n"),
11818 wrap_column,
11819 tab_size,
11820 options.preserve_existing_whitespace,
11821 );
11822
11823 // TODO: should always use char-based diff while still supporting cursor behavior that
11824 // matches vim.
11825 let mut diff_options = DiffOptions::default();
11826 if options.override_language_settings {
11827 diff_options.max_word_diff_len = 0;
11828 diff_options.max_word_diff_line_count = 0;
11829 } else {
11830 diff_options.max_word_diff_len = usize::MAX;
11831 diff_options.max_word_diff_line_count = usize::MAX;
11832 }
11833
11834 for (old_range, new_text) in
11835 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11836 {
11837 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11838 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11839 edits.push((edit_start..edit_end, new_text));
11840 }
11841
11842 rewrapped_row_ranges.push(start_row..=end_row);
11843 }
11844
11845 self.buffer
11846 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11847 }
11848
11849 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11850 let mut text = String::new();
11851 let buffer = self.buffer.read(cx).snapshot(cx);
11852 let mut selections = self.selections.all::<Point>(cx);
11853 let mut clipboard_selections = Vec::with_capacity(selections.len());
11854 {
11855 let max_point = buffer.max_point();
11856 let mut is_first = true;
11857 for selection in &mut selections {
11858 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11859 if is_entire_line {
11860 selection.start = Point::new(selection.start.row, 0);
11861 if !selection.is_empty() && selection.end.column == 0 {
11862 selection.end = cmp::min(max_point, selection.end);
11863 } else {
11864 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11865 }
11866 selection.goal = SelectionGoal::None;
11867 }
11868 if is_first {
11869 is_first = false;
11870 } else {
11871 text += "\n";
11872 }
11873 let mut len = 0;
11874 for chunk in buffer.text_for_range(selection.start..selection.end) {
11875 text.push_str(chunk);
11876 len += chunk.len();
11877 }
11878 clipboard_selections.push(ClipboardSelection {
11879 len,
11880 is_entire_line,
11881 first_line_indent: buffer
11882 .indent_size_for_line(MultiBufferRow(selection.start.row))
11883 .len,
11884 });
11885 }
11886 }
11887
11888 self.transact(window, cx, |this, window, cx| {
11889 this.change_selections(Default::default(), window, cx, |s| {
11890 s.select(selections);
11891 });
11892 this.insert("", window, cx);
11893 });
11894 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11895 }
11896
11897 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11898 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11899 let item = self.cut_common(window, cx);
11900 cx.write_to_clipboard(item);
11901 }
11902
11903 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11904 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11905 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11906 s.move_with(|snapshot, sel| {
11907 if sel.is_empty() {
11908 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11909 }
11910 });
11911 });
11912 let item = self.cut_common(window, cx);
11913 cx.set_global(KillRing(item))
11914 }
11915
11916 pub fn kill_ring_yank(
11917 &mut self,
11918 _: &KillRingYank,
11919 window: &mut Window,
11920 cx: &mut Context<Self>,
11921 ) {
11922 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11923 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11924 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11925 (kill_ring.text().to_string(), kill_ring.metadata_json())
11926 } else {
11927 return;
11928 }
11929 } else {
11930 return;
11931 };
11932 self.do_paste(&text, metadata, false, window, cx);
11933 }
11934
11935 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11936 self.do_copy(true, cx);
11937 }
11938
11939 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11940 self.do_copy(false, cx);
11941 }
11942
11943 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11944 let selections = self.selections.all::<Point>(cx);
11945 let buffer = self.buffer.read(cx).read(cx);
11946 let mut text = String::new();
11947
11948 let mut clipboard_selections = Vec::with_capacity(selections.len());
11949 {
11950 let max_point = buffer.max_point();
11951 let mut is_first = true;
11952 for selection in &selections {
11953 let mut start = selection.start;
11954 let mut end = selection.end;
11955 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11956 if is_entire_line {
11957 start = Point::new(start.row, 0);
11958 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11959 }
11960
11961 let mut trimmed_selections = Vec::new();
11962 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11963 let row = MultiBufferRow(start.row);
11964 let first_indent = buffer.indent_size_for_line(row);
11965 if first_indent.len == 0 || start.column > first_indent.len {
11966 trimmed_selections.push(start..end);
11967 } else {
11968 trimmed_selections.push(
11969 Point::new(row.0, first_indent.len)
11970 ..Point::new(row.0, buffer.line_len(row)),
11971 );
11972 for row in start.row + 1..=end.row {
11973 let mut line_len = buffer.line_len(MultiBufferRow(row));
11974 if row == end.row {
11975 line_len = end.column;
11976 }
11977 if line_len == 0 {
11978 trimmed_selections
11979 .push(Point::new(row, 0)..Point::new(row, line_len));
11980 continue;
11981 }
11982 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11983 if row_indent_size.len >= first_indent.len {
11984 trimmed_selections.push(
11985 Point::new(row, first_indent.len)..Point::new(row, line_len),
11986 );
11987 } else {
11988 trimmed_selections.clear();
11989 trimmed_selections.push(start..end);
11990 break;
11991 }
11992 }
11993 }
11994 } else {
11995 trimmed_selections.push(start..end);
11996 }
11997
11998 for trimmed_range in trimmed_selections {
11999 if is_first {
12000 is_first = false;
12001 } else {
12002 text += "\n";
12003 }
12004 let mut len = 0;
12005 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12006 text.push_str(chunk);
12007 len += chunk.len();
12008 }
12009 clipboard_selections.push(ClipboardSelection {
12010 len,
12011 is_entire_line,
12012 first_line_indent: buffer
12013 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12014 .len,
12015 });
12016 }
12017 }
12018 }
12019
12020 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12021 text,
12022 clipboard_selections,
12023 ));
12024 }
12025
12026 pub fn do_paste(
12027 &mut self,
12028 text: &String,
12029 clipboard_selections: Option<Vec<ClipboardSelection>>,
12030 handle_entire_lines: bool,
12031 window: &mut Window,
12032 cx: &mut Context<Self>,
12033 ) {
12034 if self.read_only(cx) {
12035 return;
12036 }
12037
12038 let clipboard_text = Cow::Borrowed(text);
12039
12040 self.transact(window, cx, |this, window, cx| {
12041 if let Some(mut clipboard_selections) = clipboard_selections {
12042 let old_selections = this.selections.all::<usize>(cx);
12043 let all_selections_were_entire_line =
12044 clipboard_selections.iter().all(|s| s.is_entire_line);
12045 let first_selection_indent_column =
12046 clipboard_selections.first().map(|s| s.first_line_indent);
12047 if clipboard_selections.len() != old_selections.len() {
12048 clipboard_selections.drain(..);
12049 }
12050 let cursor_offset = this.selections.last::<usize>(cx).head();
12051 let mut auto_indent_on_paste = true;
12052
12053 this.buffer.update(cx, |buffer, cx| {
12054 let snapshot = buffer.read(cx);
12055 auto_indent_on_paste = snapshot
12056 .language_settings_at(cursor_offset, cx)
12057 .auto_indent_on_paste;
12058
12059 let mut start_offset = 0;
12060 let mut edits = Vec::new();
12061 let mut original_indent_columns = Vec::new();
12062 for (ix, selection) in old_selections.iter().enumerate() {
12063 let to_insert;
12064 let entire_line;
12065 let original_indent_column;
12066 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12067 let end_offset = start_offset + clipboard_selection.len;
12068 to_insert = &clipboard_text[start_offset..end_offset];
12069 entire_line = clipboard_selection.is_entire_line;
12070 start_offset = end_offset + 1;
12071 original_indent_column = Some(clipboard_selection.first_line_indent);
12072 } else {
12073 to_insert = clipboard_text.as_str();
12074 entire_line = all_selections_were_entire_line;
12075 original_indent_column = first_selection_indent_column
12076 }
12077
12078 // If the corresponding selection was empty when this slice of the
12079 // clipboard text was written, then the entire line containing the
12080 // selection was copied. If this selection is also currently empty,
12081 // then paste the line before the current line of the buffer.
12082 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12083 let column = selection.start.to_point(&snapshot).column as usize;
12084 let line_start = selection.start - column;
12085 line_start..line_start
12086 } else {
12087 selection.range()
12088 };
12089
12090 edits.push((range, to_insert));
12091 original_indent_columns.push(original_indent_column);
12092 }
12093 drop(snapshot);
12094
12095 buffer.edit(
12096 edits,
12097 if auto_indent_on_paste {
12098 Some(AutoindentMode::Block {
12099 original_indent_columns,
12100 })
12101 } else {
12102 None
12103 },
12104 cx,
12105 );
12106 });
12107
12108 let selections = this.selections.all::<usize>(cx);
12109 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12110 } else {
12111 this.insert(&clipboard_text, window, cx);
12112 }
12113 });
12114 }
12115
12116 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12117 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12118 if let Some(item) = cx.read_from_clipboard() {
12119 let entries = item.entries();
12120
12121 match entries.first() {
12122 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12123 // of all the pasted entries.
12124 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12125 .do_paste(
12126 clipboard_string.text(),
12127 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12128 true,
12129 window,
12130 cx,
12131 ),
12132 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12133 }
12134 }
12135 }
12136
12137 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12138 if self.read_only(cx) {
12139 return;
12140 }
12141
12142 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12143
12144 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12145 if let Some((selections, _)) =
12146 self.selection_history.transaction(transaction_id).cloned()
12147 {
12148 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12149 s.select_anchors(selections.to_vec());
12150 });
12151 } else {
12152 log::error!(
12153 "No entry in selection_history found for undo. \
12154 This may correspond to a bug where undo does not update the selection. \
12155 If this is occurring, please add details to \
12156 https://github.com/zed-industries/zed/issues/22692"
12157 );
12158 }
12159 self.request_autoscroll(Autoscroll::fit(), cx);
12160 self.unmark_text(window, cx);
12161 self.refresh_inline_completion(true, false, window, cx);
12162 cx.emit(EditorEvent::Edited { transaction_id });
12163 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12164 }
12165 }
12166
12167 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12168 if self.read_only(cx) {
12169 return;
12170 }
12171
12172 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12173
12174 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12175 if let Some((_, Some(selections))) =
12176 self.selection_history.transaction(transaction_id).cloned()
12177 {
12178 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12179 s.select_anchors(selections.to_vec());
12180 });
12181 } else {
12182 log::error!(
12183 "No entry in selection_history found for redo. \
12184 This may correspond to a bug where undo does not update the selection. \
12185 If this is occurring, please add details to \
12186 https://github.com/zed-industries/zed/issues/22692"
12187 );
12188 }
12189 self.request_autoscroll(Autoscroll::fit(), cx);
12190 self.unmark_text(window, cx);
12191 self.refresh_inline_completion(true, false, window, cx);
12192 cx.emit(EditorEvent::Edited { transaction_id });
12193 }
12194 }
12195
12196 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12197 self.buffer
12198 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12199 }
12200
12201 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12202 self.buffer
12203 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12204 }
12205
12206 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12207 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12208 self.change_selections(Default::default(), window, cx, |s| {
12209 s.move_with(|map, selection| {
12210 let cursor = if selection.is_empty() {
12211 movement::left(map, selection.start)
12212 } else {
12213 selection.start
12214 };
12215 selection.collapse_to(cursor, SelectionGoal::None);
12216 });
12217 })
12218 }
12219
12220 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12221 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12222 self.change_selections(Default::default(), window, cx, |s| {
12223 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12224 })
12225 }
12226
12227 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12228 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12229 self.change_selections(Default::default(), window, cx, |s| {
12230 s.move_with(|map, selection| {
12231 let cursor = if selection.is_empty() {
12232 movement::right(map, selection.end)
12233 } else {
12234 selection.end
12235 };
12236 selection.collapse_to(cursor, SelectionGoal::None)
12237 });
12238 })
12239 }
12240
12241 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12242 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12243 self.change_selections(Default::default(), window, cx, |s| {
12244 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12245 })
12246 }
12247
12248 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12249 if self.take_rename(true, window, cx).is_some() {
12250 return;
12251 }
12252
12253 if self.mode.is_single_line() {
12254 cx.propagate();
12255 return;
12256 }
12257
12258 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12259
12260 let text_layout_details = &self.text_layout_details(window);
12261 let selection_count = self.selections.count();
12262 let first_selection = self.selections.first_anchor();
12263
12264 self.change_selections(Default::default(), window, cx, |s| {
12265 s.move_with(|map, selection| {
12266 if !selection.is_empty() {
12267 selection.goal = SelectionGoal::None;
12268 }
12269 let (cursor, goal) = movement::up(
12270 map,
12271 selection.start,
12272 selection.goal,
12273 false,
12274 text_layout_details,
12275 );
12276 selection.collapse_to(cursor, goal);
12277 });
12278 });
12279
12280 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12281 {
12282 cx.propagate();
12283 }
12284 }
12285
12286 pub fn move_up_by_lines(
12287 &mut self,
12288 action: &MoveUpByLines,
12289 window: &mut Window,
12290 cx: &mut Context<Self>,
12291 ) {
12292 if self.take_rename(true, window, cx).is_some() {
12293 return;
12294 }
12295
12296 if self.mode.is_single_line() {
12297 cx.propagate();
12298 return;
12299 }
12300
12301 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12302
12303 let text_layout_details = &self.text_layout_details(window);
12304
12305 self.change_selections(Default::default(), window, cx, |s| {
12306 s.move_with(|map, selection| {
12307 if !selection.is_empty() {
12308 selection.goal = SelectionGoal::None;
12309 }
12310 let (cursor, goal) = movement::up_by_rows(
12311 map,
12312 selection.start,
12313 action.lines,
12314 selection.goal,
12315 false,
12316 text_layout_details,
12317 );
12318 selection.collapse_to(cursor, goal);
12319 });
12320 })
12321 }
12322
12323 pub fn move_down_by_lines(
12324 &mut self,
12325 action: &MoveDownByLines,
12326 window: &mut Window,
12327 cx: &mut Context<Self>,
12328 ) {
12329 if self.take_rename(true, window, cx).is_some() {
12330 return;
12331 }
12332
12333 if self.mode.is_single_line() {
12334 cx.propagate();
12335 return;
12336 }
12337
12338 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12339
12340 let text_layout_details = &self.text_layout_details(window);
12341
12342 self.change_selections(Default::default(), window, cx, |s| {
12343 s.move_with(|map, selection| {
12344 if !selection.is_empty() {
12345 selection.goal = SelectionGoal::None;
12346 }
12347 let (cursor, goal) = movement::down_by_rows(
12348 map,
12349 selection.start,
12350 action.lines,
12351 selection.goal,
12352 false,
12353 text_layout_details,
12354 );
12355 selection.collapse_to(cursor, goal);
12356 });
12357 })
12358 }
12359
12360 pub fn select_down_by_lines(
12361 &mut self,
12362 action: &SelectDownByLines,
12363 window: &mut Window,
12364 cx: &mut Context<Self>,
12365 ) {
12366 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12367 let text_layout_details = &self.text_layout_details(window);
12368 self.change_selections(Default::default(), window, cx, |s| {
12369 s.move_heads_with(|map, head, goal| {
12370 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12371 })
12372 })
12373 }
12374
12375 pub fn select_up_by_lines(
12376 &mut self,
12377 action: &SelectUpByLines,
12378 window: &mut Window,
12379 cx: &mut Context<Self>,
12380 ) {
12381 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12382 let text_layout_details = &self.text_layout_details(window);
12383 self.change_selections(Default::default(), window, cx, |s| {
12384 s.move_heads_with(|map, head, goal| {
12385 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12386 })
12387 })
12388 }
12389
12390 pub fn select_page_up(
12391 &mut self,
12392 _: &SelectPageUp,
12393 window: &mut Window,
12394 cx: &mut Context<Self>,
12395 ) {
12396 let Some(row_count) = self.visible_row_count() else {
12397 return;
12398 };
12399
12400 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12401
12402 let text_layout_details = &self.text_layout_details(window);
12403
12404 self.change_selections(Default::default(), window, cx, |s| {
12405 s.move_heads_with(|map, head, goal| {
12406 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12407 })
12408 })
12409 }
12410
12411 pub fn move_page_up(
12412 &mut self,
12413 action: &MovePageUp,
12414 window: &mut Window,
12415 cx: &mut Context<Self>,
12416 ) {
12417 if self.take_rename(true, window, cx).is_some() {
12418 return;
12419 }
12420
12421 if self
12422 .context_menu
12423 .borrow_mut()
12424 .as_mut()
12425 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12426 .unwrap_or(false)
12427 {
12428 return;
12429 }
12430
12431 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12432 cx.propagate();
12433 return;
12434 }
12435
12436 let Some(row_count) = self.visible_row_count() else {
12437 return;
12438 };
12439
12440 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12441
12442 let effects = if action.center_cursor {
12443 SelectionEffects::scroll(Autoscroll::center())
12444 } else {
12445 SelectionEffects::default()
12446 };
12447
12448 let text_layout_details = &self.text_layout_details(window);
12449
12450 self.change_selections(effects, window, cx, |s| {
12451 s.move_with(|map, selection| {
12452 if !selection.is_empty() {
12453 selection.goal = SelectionGoal::None;
12454 }
12455 let (cursor, goal) = movement::up_by_rows(
12456 map,
12457 selection.end,
12458 row_count,
12459 selection.goal,
12460 false,
12461 text_layout_details,
12462 );
12463 selection.collapse_to(cursor, goal);
12464 });
12465 });
12466 }
12467
12468 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12469 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12470 let text_layout_details = &self.text_layout_details(window);
12471 self.change_selections(Default::default(), window, cx, |s| {
12472 s.move_heads_with(|map, head, goal| {
12473 movement::up(map, head, goal, false, text_layout_details)
12474 })
12475 })
12476 }
12477
12478 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12479 self.take_rename(true, window, cx);
12480
12481 if self.mode.is_single_line() {
12482 cx.propagate();
12483 return;
12484 }
12485
12486 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12487
12488 let text_layout_details = &self.text_layout_details(window);
12489 let selection_count = self.selections.count();
12490 let first_selection = self.selections.first_anchor();
12491
12492 self.change_selections(Default::default(), window, cx, |s| {
12493 s.move_with(|map, selection| {
12494 if !selection.is_empty() {
12495 selection.goal = SelectionGoal::None;
12496 }
12497 let (cursor, goal) = movement::down(
12498 map,
12499 selection.end,
12500 selection.goal,
12501 false,
12502 text_layout_details,
12503 );
12504 selection.collapse_to(cursor, goal);
12505 });
12506 });
12507
12508 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12509 {
12510 cx.propagate();
12511 }
12512 }
12513
12514 pub fn select_page_down(
12515 &mut self,
12516 _: &SelectPageDown,
12517 window: &mut Window,
12518 cx: &mut Context<Self>,
12519 ) {
12520 let Some(row_count) = self.visible_row_count() else {
12521 return;
12522 };
12523
12524 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12525
12526 let text_layout_details = &self.text_layout_details(window);
12527
12528 self.change_selections(Default::default(), window, cx, |s| {
12529 s.move_heads_with(|map, head, goal| {
12530 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12531 })
12532 })
12533 }
12534
12535 pub fn move_page_down(
12536 &mut self,
12537 action: &MovePageDown,
12538 window: &mut Window,
12539 cx: &mut Context<Self>,
12540 ) {
12541 if self.take_rename(true, window, cx).is_some() {
12542 return;
12543 }
12544
12545 if self
12546 .context_menu
12547 .borrow_mut()
12548 .as_mut()
12549 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12550 .unwrap_or(false)
12551 {
12552 return;
12553 }
12554
12555 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12556 cx.propagate();
12557 return;
12558 }
12559
12560 let Some(row_count) = self.visible_row_count() else {
12561 return;
12562 };
12563
12564 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12565
12566 let effects = if action.center_cursor {
12567 SelectionEffects::scroll(Autoscroll::center())
12568 } else {
12569 SelectionEffects::default()
12570 };
12571
12572 let text_layout_details = &self.text_layout_details(window);
12573 self.change_selections(effects, window, cx, |s| {
12574 s.move_with(|map, selection| {
12575 if !selection.is_empty() {
12576 selection.goal = SelectionGoal::None;
12577 }
12578 let (cursor, goal) = movement::down_by_rows(
12579 map,
12580 selection.end,
12581 row_count,
12582 selection.goal,
12583 false,
12584 text_layout_details,
12585 );
12586 selection.collapse_to(cursor, goal);
12587 });
12588 });
12589 }
12590
12591 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12592 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12593 let text_layout_details = &self.text_layout_details(window);
12594 self.change_selections(Default::default(), window, cx, |s| {
12595 s.move_heads_with(|map, head, goal| {
12596 movement::down(map, head, goal, false, text_layout_details)
12597 })
12598 });
12599 }
12600
12601 pub fn context_menu_first(
12602 &mut self,
12603 _: &ContextMenuFirst,
12604 window: &mut Window,
12605 cx: &mut Context<Self>,
12606 ) {
12607 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12608 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12609 }
12610 }
12611
12612 pub fn context_menu_prev(
12613 &mut self,
12614 _: &ContextMenuPrevious,
12615 window: &mut Window,
12616 cx: &mut Context<Self>,
12617 ) {
12618 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12619 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12620 }
12621 }
12622
12623 pub fn context_menu_next(
12624 &mut self,
12625 _: &ContextMenuNext,
12626 window: &mut Window,
12627 cx: &mut Context<Self>,
12628 ) {
12629 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12630 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12631 }
12632 }
12633
12634 pub fn context_menu_last(
12635 &mut self,
12636 _: &ContextMenuLast,
12637 window: &mut Window,
12638 cx: &mut Context<Self>,
12639 ) {
12640 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12641 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12642 }
12643 }
12644
12645 pub fn signature_help_prev(
12646 &mut self,
12647 _: &SignatureHelpPrevious,
12648 _: &mut Window,
12649 cx: &mut Context<Self>,
12650 ) {
12651 if let Some(popover) = self.signature_help_state.popover_mut() {
12652 if popover.current_signature == 0 {
12653 popover.current_signature = popover.signatures.len() - 1;
12654 } else {
12655 popover.current_signature -= 1;
12656 }
12657 cx.notify();
12658 }
12659 }
12660
12661 pub fn signature_help_next(
12662 &mut self,
12663 _: &SignatureHelpNext,
12664 _: &mut Window,
12665 cx: &mut Context<Self>,
12666 ) {
12667 if let Some(popover) = self.signature_help_state.popover_mut() {
12668 if popover.current_signature + 1 == popover.signatures.len() {
12669 popover.current_signature = 0;
12670 } else {
12671 popover.current_signature += 1;
12672 }
12673 cx.notify();
12674 }
12675 }
12676
12677 pub fn move_to_previous_word_start(
12678 &mut self,
12679 _: &MoveToPreviousWordStart,
12680 window: &mut Window,
12681 cx: &mut Context<Self>,
12682 ) {
12683 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12684 self.change_selections(Default::default(), window, cx, |s| {
12685 s.move_cursors_with(|map, head, _| {
12686 (
12687 movement::previous_word_start(map, head),
12688 SelectionGoal::None,
12689 )
12690 });
12691 })
12692 }
12693
12694 pub fn move_to_previous_subword_start(
12695 &mut self,
12696 _: &MoveToPreviousSubwordStart,
12697 window: &mut Window,
12698 cx: &mut Context<Self>,
12699 ) {
12700 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12701 self.change_selections(Default::default(), window, cx, |s| {
12702 s.move_cursors_with(|map, head, _| {
12703 (
12704 movement::previous_subword_start(map, head),
12705 SelectionGoal::None,
12706 )
12707 });
12708 })
12709 }
12710
12711 pub fn select_to_previous_word_start(
12712 &mut self,
12713 _: &SelectToPreviousWordStart,
12714 window: &mut Window,
12715 cx: &mut Context<Self>,
12716 ) {
12717 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12718 self.change_selections(Default::default(), window, cx, |s| {
12719 s.move_heads_with(|map, head, _| {
12720 (
12721 movement::previous_word_start(map, head),
12722 SelectionGoal::None,
12723 )
12724 });
12725 })
12726 }
12727
12728 pub fn select_to_previous_subword_start(
12729 &mut self,
12730 _: &SelectToPreviousSubwordStart,
12731 window: &mut Window,
12732 cx: &mut Context<Self>,
12733 ) {
12734 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12735 self.change_selections(Default::default(), window, cx, |s| {
12736 s.move_heads_with(|map, head, _| {
12737 (
12738 movement::previous_subword_start(map, head),
12739 SelectionGoal::None,
12740 )
12741 });
12742 })
12743 }
12744
12745 pub fn delete_to_previous_word_start(
12746 &mut self,
12747 action: &DeleteToPreviousWordStart,
12748 window: &mut Window,
12749 cx: &mut Context<Self>,
12750 ) {
12751 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12752 self.transact(window, cx, |this, window, cx| {
12753 this.select_autoclose_pair(window, cx);
12754 this.change_selections(Default::default(), window, cx, |s| {
12755 s.move_with(|map, selection| {
12756 if selection.is_empty() {
12757 let cursor = if action.ignore_newlines {
12758 movement::previous_word_start(map, selection.head())
12759 } else {
12760 movement::previous_word_start_or_newline(map, selection.head())
12761 };
12762 selection.set_head(cursor, SelectionGoal::None);
12763 }
12764 });
12765 });
12766 this.insert("", window, cx);
12767 });
12768 }
12769
12770 pub fn delete_to_previous_subword_start(
12771 &mut self,
12772 _: &DeleteToPreviousSubwordStart,
12773 window: &mut Window,
12774 cx: &mut Context<Self>,
12775 ) {
12776 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12777 self.transact(window, cx, |this, window, cx| {
12778 this.select_autoclose_pair(window, cx);
12779 this.change_selections(Default::default(), window, cx, |s| {
12780 s.move_with(|map, selection| {
12781 if selection.is_empty() {
12782 let cursor = movement::previous_subword_start(map, selection.head());
12783 selection.set_head(cursor, SelectionGoal::None);
12784 }
12785 });
12786 });
12787 this.insert("", window, cx);
12788 });
12789 }
12790
12791 pub fn move_to_next_word_end(
12792 &mut self,
12793 _: &MoveToNextWordEnd,
12794 window: &mut Window,
12795 cx: &mut Context<Self>,
12796 ) {
12797 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12798 self.change_selections(Default::default(), window, cx, |s| {
12799 s.move_cursors_with(|map, head, _| {
12800 (movement::next_word_end(map, head), SelectionGoal::None)
12801 });
12802 })
12803 }
12804
12805 pub fn move_to_next_subword_end(
12806 &mut self,
12807 _: &MoveToNextSubwordEnd,
12808 window: &mut Window,
12809 cx: &mut Context<Self>,
12810 ) {
12811 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12812 self.change_selections(Default::default(), window, cx, |s| {
12813 s.move_cursors_with(|map, head, _| {
12814 (movement::next_subword_end(map, head), SelectionGoal::None)
12815 });
12816 })
12817 }
12818
12819 pub fn select_to_next_word_end(
12820 &mut self,
12821 _: &SelectToNextWordEnd,
12822 window: &mut Window,
12823 cx: &mut Context<Self>,
12824 ) {
12825 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12826 self.change_selections(Default::default(), window, cx, |s| {
12827 s.move_heads_with(|map, head, _| {
12828 (movement::next_word_end(map, head), SelectionGoal::None)
12829 });
12830 })
12831 }
12832
12833 pub fn select_to_next_subword_end(
12834 &mut self,
12835 _: &SelectToNextSubwordEnd,
12836 window: &mut Window,
12837 cx: &mut Context<Self>,
12838 ) {
12839 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12840 self.change_selections(Default::default(), window, cx, |s| {
12841 s.move_heads_with(|map, head, _| {
12842 (movement::next_subword_end(map, head), SelectionGoal::None)
12843 });
12844 })
12845 }
12846
12847 pub fn delete_to_next_word_end(
12848 &mut self,
12849 action: &DeleteToNextWordEnd,
12850 window: &mut Window,
12851 cx: &mut Context<Self>,
12852 ) {
12853 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12854 self.transact(window, cx, |this, window, cx| {
12855 this.change_selections(Default::default(), window, cx, |s| {
12856 s.move_with(|map, selection| {
12857 if selection.is_empty() {
12858 let cursor = if action.ignore_newlines {
12859 movement::next_word_end(map, selection.head())
12860 } else {
12861 movement::next_word_end_or_newline(map, selection.head())
12862 };
12863 selection.set_head(cursor, SelectionGoal::None);
12864 }
12865 });
12866 });
12867 this.insert("", window, cx);
12868 });
12869 }
12870
12871 pub fn delete_to_next_subword_end(
12872 &mut self,
12873 _: &DeleteToNextSubwordEnd,
12874 window: &mut Window,
12875 cx: &mut Context<Self>,
12876 ) {
12877 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12878 self.transact(window, cx, |this, window, cx| {
12879 this.change_selections(Default::default(), window, cx, |s| {
12880 s.move_with(|map, selection| {
12881 if selection.is_empty() {
12882 let cursor = movement::next_subword_end(map, selection.head());
12883 selection.set_head(cursor, SelectionGoal::None);
12884 }
12885 });
12886 });
12887 this.insert("", window, cx);
12888 });
12889 }
12890
12891 pub fn move_to_beginning_of_line(
12892 &mut self,
12893 action: &MoveToBeginningOfLine,
12894 window: &mut Window,
12895 cx: &mut Context<Self>,
12896 ) {
12897 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12898 self.change_selections(Default::default(), window, cx, |s| {
12899 s.move_cursors_with(|map, head, _| {
12900 (
12901 movement::indented_line_beginning(
12902 map,
12903 head,
12904 action.stop_at_soft_wraps,
12905 action.stop_at_indent,
12906 ),
12907 SelectionGoal::None,
12908 )
12909 });
12910 })
12911 }
12912
12913 pub fn select_to_beginning_of_line(
12914 &mut self,
12915 action: &SelectToBeginningOfLine,
12916 window: &mut Window,
12917 cx: &mut Context<Self>,
12918 ) {
12919 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12920 self.change_selections(Default::default(), window, cx, |s| {
12921 s.move_heads_with(|map, head, _| {
12922 (
12923 movement::indented_line_beginning(
12924 map,
12925 head,
12926 action.stop_at_soft_wraps,
12927 action.stop_at_indent,
12928 ),
12929 SelectionGoal::None,
12930 )
12931 });
12932 });
12933 }
12934
12935 pub fn delete_to_beginning_of_line(
12936 &mut self,
12937 action: &DeleteToBeginningOfLine,
12938 window: &mut Window,
12939 cx: &mut Context<Self>,
12940 ) {
12941 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12942 self.transact(window, cx, |this, window, cx| {
12943 this.change_selections(Default::default(), window, cx, |s| {
12944 s.move_with(|_, selection| {
12945 selection.reversed = true;
12946 });
12947 });
12948
12949 this.select_to_beginning_of_line(
12950 &SelectToBeginningOfLine {
12951 stop_at_soft_wraps: false,
12952 stop_at_indent: action.stop_at_indent,
12953 },
12954 window,
12955 cx,
12956 );
12957 this.backspace(&Backspace, window, cx);
12958 });
12959 }
12960
12961 pub fn move_to_end_of_line(
12962 &mut self,
12963 action: &MoveToEndOfLine,
12964 window: &mut Window,
12965 cx: &mut Context<Self>,
12966 ) {
12967 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12968 self.change_selections(Default::default(), window, cx, |s| {
12969 s.move_cursors_with(|map, head, _| {
12970 (
12971 movement::line_end(map, head, action.stop_at_soft_wraps),
12972 SelectionGoal::None,
12973 )
12974 });
12975 })
12976 }
12977
12978 pub fn select_to_end_of_line(
12979 &mut self,
12980 action: &SelectToEndOfLine,
12981 window: &mut Window,
12982 cx: &mut Context<Self>,
12983 ) {
12984 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12985 self.change_selections(Default::default(), window, cx, |s| {
12986 s.move_heads_with(|map, head, _| {
12987 (
12988 movement::line_end(map, head, action.stop_at_soft_wraps),
12989 SelectionGoal::None,
12990 )
12991 });
12992 })
12993 }
12994
12995 pub fn delete_to_end_of_line(
12996 &mut self,
12997 _: &DeleteToEndOfLine,
12998 window: &mut Window,
12999 cx: &mut Context<Self>,
13000 ) {
13001 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13002 self.transact(window, cx, |this, window, cx| {
13003 this.select_to_end_of_line(
13004 &SelectToEndOfLine {
13005 stop_at_soft_wraps: false,
13006 },
13007 window,
13008 cx,
13009 );
13010 this.delete(&Delete, window, cx);
13011 });
13012 }
13013
13014 pub fn cut_to_end_of_line(
13015 &mut self,
13016 _: &CutToEndOfLine,
13017 window: &mut Window,
13018 cx: &mut Context<Self>,
13019 ) {
13020 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13021 self.transact(window, cx, |this, window, cx| {
13022 this.select_to_end_of_line(
13023 &SelectToEndOfLine {
13024 stop_at_soft_wraps: false,
13025 },
13026 window,
13027 cx,
13028 );
13029 this.cut(&Cut, window, cx);
13030 });
13031 }
13032
13033 pub fn move_to_start_of_paragraph(
13034 &mut self,
13035 _: &MoveToStartOfParagraph,
13036 window: &mut Window,
13037 cx: &mut Context<Self>,
13038 ) {
13039 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13040 cx.propagate();
13041 return;
13042 }
13043 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13044 self.change_selections(Default::default(), window, cx, |s| {
13045 s.move_with(|map, selection| {
13046 selection.collapse_to(
13047 movement::start_of_paragraph(map, selection.head(), 1),
13048 SelectionGoal::None,
13049 )
13050 });
13051 })
13052 }
13053
13054 pub fn move_to_end_of_paragraph(
13055 &mut self,
13056 _: &MoveToEndOfParagraph,
13057 window: &mut Window,
13058 cx: &mut Context<Self>,
13059 ) {
13060 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13061 cx.propagate();
13062 return;
13063 }
13064 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13065 self.change_selections(Default::default(), window, cx, |s| {
13066 s.move_with(|map, selection| {
13067 selection.collapse_to(
13068 movement::end_of_paragraph(map, selection.head(), 1),
13069 SelectionGoal::None,
13070 )
13071 });
13072 })
13073 }
13074
13075 pub fn select_to_start_of_paragraph(
13076 &mut self,
13077 _: &SelectToStartOfParagraph,
13078 window: &mut Window,
13079 cx: &mut Context<Self>,
13080 ) {
13081 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13082 cx.propagate();
13083 return;
13084 }
13085 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13086 self.change_selections(Default::default(), window, cx, |s| {
13087 s.move_heads_with(|map, head, _| {
13088 (
13089 movement::start_of_paragraph(map, head, 1),
13090 SelectionGoal::None,
13091 )
13092 });
13093 })
13094 }
13095
13096 pub fn select_to_end_of_paragraph(
13097 &mut self,
13098 _: &SelectToEndOfParagraph,
13099 window: &mut Window,
13100 cx: &mut Context<Self>,
13101 ) {
13102 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13103 cx.propagate();
13104 return;
13105 }
13106 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13107 self.change_selections(Default::default(), window, cx, |s| {
13108 s.move_heads_with(|map, head, _| {
13109 (
13110 movement::end_of_paragraph(map, head, 1),
13111 SelectionGoal::None,
13112 )
13113 });
13114 })
13115 }
13116
13117 pub fn move_to_start_of_excerpt(
13118 &mut self,
13119 _: &MoveToStartOfExcerpt,
13120 window: &mut Window,
13121 cx: &mut Context<Self>,
13122 ) {
13123 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13124 cx.propagate();
13125 return;
13126 }
13127 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13128 self.change_selections(Default::default(), window, cx, |s| {
13129 s.move_with(|map, selection| {
13130 selection.collapse_to(
13131 movement::start_of_excerpt(
13132 map,
13133 selection.head(),
13134 workspace::searchable::Direction::Prev,
13135 ),
13136 SelectionGoal::None,
13137 )
13138 });
13139 })
13140 }
13141
13142 pub fn move_to_start_of_next_excerpt(
13143 &mut self,
13144 _: &MoveToStartOfNextExcerpt,
13145 window: &mut Window,
13146 cx: &mut Context<Self>,
13147 ) {
13148 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13149 cx.propagate();
13150 return;
13151 }
13152
13153 self.change_selections(Default::default(), window, cx, |s| {
13154 s.move_with(|map, selection| {
13155 selection.collapse_to(
13156 movement::start_of_excerpt(
13157 map,
13158 selection.head(),
13159 workspace::searchable::Direction::Next,
13160 ),
13161 SelectionGoal::None,
13162 )
13163 });
13164 })
13165 }
13166
13167 pub fn move_to_end_of_excerpt(
13168 &mut self,
13169 _: &MoveToEndOfExcerpt,
13170 window: &mut Window,
13171 cx: &mut Context<Self>,
13172 ) {
13173 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13174 cx.propagate();
13175 return;
13176 }
13177 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13178 self.change_selections(Default::default(), window, cx, |s| {
13179 s.move_with(|map, selection| {
13180 selection.collapse_to(
13181 movement::end_of_excerpt(
13182 map,
13183 selection.head(),
13184 workspace::searchable::Direction::Next,
13185 ),
13186 SelectionGoal::None,
13187 )
13188 });
13189 })
13190 }
13191
13192 pub fn move_to_end_of_previous_excerpt(
13193 &mut self,
13194 _: &MoveToEndOfPreviousExcerpt,
13195 window: &mut Window,
13196 cx: &mut Context<Self>,
13197 ) {
13198 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13199 cx.propagate();
13200 return;
13201 }
13202 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13203 self.change_selections(Default::default(), window, cx, |s| {
13204 s.move_with(|map, selection| {
13205 selection.collapse_to(
13206 movement::end_of_excerpt(
13207 map,
13208 selection.head(),
13209 workspace::searchable::Direction::Prev,
13210 ),
13211 SelectionGoal::None,
13212 )
13213 });
13214 })
13215 }
13216
13217 pub fn select_to_start_of_excerpt(
13218 &mut self,
13219 _: &SelectToStartOfExcerpt,
13220 window: &mut Window,
13221 cx: &mut Context<Self>,
13222 ) {
13223 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13224 cx.propagate();
13225 return;
13226 }
13227 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13228 self.change_selections(Default::default(), window, cx, |s| {
13229 s.move_heads_with(|map, head, _| {
13230 (
13231 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13232 SelectionGoal::None,
13233 )
13234 });
13235 })
13236 }
13237
13238 pub fn select_to_start_of_next_excerpt(
13239 &mut self,
13240 _: &SelectToStartOfNextExcerpt,
13241 window: &mut Window,
13242 cx: &mut Context<Self>,
13243 ) {
13244 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13245 cx.propagate();
13246 return;
13247 }
13248 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13249 self.change_selections(Default::default(), window, cx, |s| {
13250 s.move_heads_with(|map, head, _| {
13251 (
13252 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13253 SelectionGoal::None,
13254 )
13255 });
13256 })
13257 }
13258
13259 pub fn select_to_end_of_excerpt(
13260 &mut self,
13261 _: &SelectToEndOfExcerpt,
13262 window: &mut Window,
13263 cx: &mut Context<Self>,
13264 ) {
13265 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13266 cx.propagate();
13267 return;
13268 }
13269 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13270 self.change_selections(Default::default(), window, cx, |s| {
13271 s.move_heads_with(|map, head, _| {
13272 (
13273 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13274 SelectionGoal::None,
13275 )
13276 });
13277 })
13278 }
13279
13280 pub fn select_to_end_of_previous_excerpt(
13281 &mut self,
13282 _: &SelectToEndOfPreviousExcerpt,
13283 window: &mut Window,
13284 cx: &mut Context<Self>,
13285 ) {
13286 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13287 cx.propagate();
13288 return;
13289 }
13290 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13291 self.change_selections(Default::default(), window, cx, |s| {
13292 s.move_heads_with(|map, head, _| {
13293 (
13294 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13295 SelectionGoal::None,
13296 )
13297 });
13298 })
13299 }
13300
13301 pub fn move_to_beginning(
13302 &mut self,
13303 _: &MoveToBeginning,
13304 window: &mut Window,
13305 cx: &mut Context<Self>,
13306 ) {
13307 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13308 cx.propagate();
13309 return;
13310 }
13311 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13312 self.change_selections(Default::default(), window, cx, |s| {
13313 s.select_ranges(vec![0..0]);
13314 });
13315 }
13316
13317 pub fn select_to_beginning(
13318 &mut self,
13319 _: &SelectToBeginning,
13320 window: &mut Window,
13321 cx: &mut Context<Self>,
13322 ) {
13323 let mut selection = self.selections.last::<Point>(cx);
13324 selection.set_head(Point::zero(), SelectionGoal::None);
13325 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13326 self.change_selections(Default::default(), window, cx, |s| {
13327 s.select(vec![selection]);
13328 });
13329 }
13330
13331 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13332 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13333 cx.propagate();
13334 return;
13335 }
13336 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13337 let cursor = self.buffer.read(cx).read(cx).len();
13338 self.change_selections(Default::default(), window, cx, |s| {
13339 s.select_ranges(vec![cursor..cursor])
13340 });
13341 }
13342
13343 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13344 self.nav_history = nav_history;
13345 }
13346
13347 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13348 self.nav_history.as_ref()
13349 }
13350
13351 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13352 self.push_to_nav_history(
13353 self.selections.newest_anchor().head(),
13354 None,
13355 false,
13356 true,
13357 cx,
13358 );
13359 }
13360
13361 fn push_to_nav_history(
13362 &mut self,
13363 cursor_anchor: Anchor,
13364 new_position: Option<Point>,
13365 is_deactivate: bool,
13366 always: bool,
13367 cx: &mut Context<Self>,
13368 ) {
13369 if let Some(nav_history) = self.nav_history.as_mut() {
13370 let buffer = self.buffer.read(cx).read(cx);
13371 let cursor_position = cursor_anchor.to_point(&buffer);
13372 let scroll_state = self.scroll_manager.anchor();
13373 let scroll_top_row = scroll_state.top_row(&buffer);
13374 drop(buffer);
13375
13376 if let Some(new_position) = new_position {
13377 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13378 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13379 return;
13380 }
13381 }
13382
13383 nav_history.push(
13384 Some(NavigationData {
13385 cursor_anchor,
13386 cursor_position,
13387 scroll_anchor: scroll_state,
13388 scroll_top_row,
13389 }),
13390 cx,
13391 );
13392 cx.emit(EditorEvent::PushedToNavHistory {
13393 anchor: cursor_anchor,
13394 is_deactivate,
13395 })
13396 }
13397 }
13398
13399 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13400 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13401 let buffer = self.buffer.read(cx).snapshot(cx);
13402 let mut selection = self.selections.first::<usize>(cx);
13403 selection.set_head(buffer.len(), SelectionGoal::None);
13404 self.change_selections(Default::default(), window, cx, |s| {
13405 s.select(vec![selection]);
13406 });
13407 }
13408
13409 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13410 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13411 let end = self.buffer.read(cx).read(cx).len();
13412 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13413 s.select_ranges(vec![0..end]);
13414 });
13415 }
13416
13417 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13418 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13419 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13420 let mut selections = self.selections.all::<Point>(cx);
13421 let max_point = display_map.buffer_snapshot.max_point();
13422 for selection in &mut selections {
13423 let rows = selection.spanned_rows(true, &display_map);
13424 selection.start = Point::new(rows.start.0, 0);
13425 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13426 selection.reversed = false;
13427 }
13428 self.change_selections(Default::default(), window, cx, |s| {
13429 s.select(selections);
13430 });
13431 }
13432
13433 pub fn split_selection_into_lines(
13434 &mut self,
13435 _: &SplitSelectionIntoLines,
13436 window: &mut Window,
13437 cx: &mut Context<Self>,
13438 ) {
13439 let selections = self
13440 .selections
13441 .all::<Point>(cx)
13442 .into_iter()
13443 .map(|selection| selection.start..selection.end)
13444 .collect::<Vec<_>>();
13445 self.unfold_ranges(&selections, true, true, cx);
13446
13447 let mut new_selection_ranges = Vec::new();
13448 {
13449 let buffer = self.buffer.read(cx).read(cx);
13450 for selection in selections {
13451 for row in selection.start.row..selection.end.row {
13452 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13453 new_selection_ranges.push(cursor..cursor);
13454 }
13455
13456 let is_multiline_selection = selection.start.row != selection.end.row;
13457 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13458 // so this action feels more ergonomic when paired with other selection operations
13459 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13460 if !should_skip_last {
13461 new_selection_ranges.push(selection.end..selection.end);
13462 }
13463 }
13464 }
13465 self.change_selections(Default::default(), window, cx, |s| {
13466 s.select_ranges(new_selection_ranges);
13467 });
13468 }
13469
13470 pub fn add_selection_above(
13471 &mut self,
13472 _: &AddSelectionAbove,
13473 window: &mut Window,
13474 cx: &mut Context<Self>,
13475 ) {
13476 self.add_selection(true, window, cx);
13477 }
13478
13479 pub fn add_selection_below(
13480 &mut self,
13481 _: &AddSelectionBelow,
13482 window: &mut Window,
13483 cx: &mut Context<Self>,
13484 ) {
13485 self.add_selection(false, window, cx);
13486 }
13487
13488 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13489 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13490
13491 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13492 let all_selections = self.selections.all::<Point>(cx);
13493 let text_layout_details = self.text_layout_details(window);
13494
13495 let (mut columnar_selections, new_selections_to_columnarize) = {
13496 if let Some(state) = self.add_selections_state.as_ref() {
13497 let columnar_selection_ids: HashSet<_> = state
13498 .groups
13499 .iter()
13500 .flat_map(|group| group.stack.iter())
13501 .copied()
13502 .collect();
13503
13504 all_selections
13505 .into_iter()
13506 .partition(|s| columnar_selection_ids.contains(&s.id))
13507 } else {
13508 (Vec::new(), all_selections)
13509 }
13510 };
13511
13512 let mut state = self
13513 .add_selections_state
13514 .take()
13515 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13516
13517 for selection in new_selections_to_columnarize {
13518 let range = selection.display_range(&display_map).sorted();
13519 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13520 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13521 let positions = start_x.min(end_x)..start_x.max(end_x);
13522 let mut stack = Vec::new();
13523 for row in range.start.row().0..=range.end.row().0 {
13524 if let Some(selection) = self.selections.build_columnar_selection(
13525 &display_map,
13526 DisplayRow(row),
13527 &positions,
13528 selection.reversed,
13529 &text_layout_details,
13530 ) {
13531 stack.push(selection.id);
13532 columnar_selections.push(selection);
13533 }
13534 }
13535 if !stack.is_empty() {
13536 if above {
13537 stack.reverse();
13538 }
13539 state.groups.push(AddSelectionsGroup { above, stack });
13540 }
13541 }
13542
13543 let mut final_selections = Vec::new();
13544 let end_row = if above {
13545 DisplayRow(0)
13546 } else {
13547 display_map.max_point().row()
13548 };
13549
13550 let mut last_added_item_per_group = HashMap::default();
13551 for group in state.groups.iter_mut() {
13552 if let Some(last_id) = group.stack.last() {
13553 last_added_item_per_group.insert(*last_id, group);
13554 }
13555 }
13556
13557 for selection in columnar_selections {
13558 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13559 if above == group.above {
13560 let range = selection.display_range(&display_map).sorted();
13561 debug_assert_eq!(range.start.row(), range.end.row());
13562 let mut row = range.start.row();
13563 let positions =
13564 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13565 px(start)..px(end)
13566 } else {
13567 let start_x =
13568 display_map.x_for_display_point(range.start, &text_layout_details);
13569 let end_x =
13570 display_map.x_for_display_point(range.end, &text_layout_details);
13571 start_x.min(end_x)..start_x.max(end_x)
13572 };
13573
13574 let mut maybe_new_selection = None;
13575 while row != end_row {
13576 if above {
13577 row.0 -= 1;
13578 } else {
13579 row.0 += 1;
13580 }
13581 if let Some(new_selection) = self.selections.build_columnar_selection(
13582 &display_map,
13583 row,
13584 &positions,
13585 selection.reversed,
13586 &text_layout_details,
13587 ) {
13588 maybe_new_selection = Some(new_selection);
13589 break;
13590 }
13591 }
13592
13593 if let Some(new_selection) = maybe_new_selection {
13594 group.stack.push(new_selection.id);
13595 if above {
13596 final_selections.push(new_selection);
13597 final_selections.push(selection);
13598 } else {
13599 final_selections.push(selection);
13600 final_selections.push(new_selection);
13601 }
13602 } else {
13603 final_selections.push(selection);
13604 }
13605 } else {
13606 group.stack.pop();
13607 }
13608 } else {
13609 final_selections.push(selection);
13610 }
13611 }
13612
13613 self.change_selections(Default::default(), window, cx, |s| {
13614 s.select(final_selections);
13615 });
13616
13617 let final_selection_ids: HashSet<_> = self
13618 .selections
13619 .all::<Point>(cx)
13620 .iter()
13621 .map(|s| s.id)
13622 .collect();
13623 state.groups.retain_mut(|group| {
13624 // selections might get merged above so we remove invalid items from stacks
13625 group.stack.retain(|id| final_selection_ids.contains(id));
13626
13627 // single selection in stack can be treated as initial state
13628 group.stack.len() > 1
13629 });
13630
13631 if !state.groups.is_empty() {
13632 self.add_selections_state = Some(state);
13633 }
13634 }
13635
13636 fn select_match_ranges(
13637 &mut self,
13638 range: Range<usize>,
13639 reversed: bool,
13640 replace_newest: bool,
13641 auto_scroll: Option<Autoscroll>,
13642 window: &mut Window,
13643 cx: &mut Context<Editor>,
13644 ) {
13645 self.unfold_ranges(
13646 std::slice::from_ref(&range),
13647 false,
13648 auto_scroll.is_some(),
13649 cx,
13650 );
13651 let effects = if let Some(scroll) = auto_scroll {
13652 SelectionEffects::scroll(scroll)
13653 } else {
13654 SelectionEffects::no_scroll()
13655 };
13656 self.change_selections(effects, window, cx, |s| {
13657 if replace_newest {
13658 s.delete(s.newest_anchor().id);
13659 }
13660 if reversed {
13661 s.insert_range(range.end..range.start);
13662 } else {
13663 s.insert_range(range);
13664 }
13665 });
13666 }
13667
13668 pub fn select_next_match_internal(
13669 &mut self,
13670 display_map: &DisplaySnapshot,
13671 replace_newest: bool,
13672 autoscroll: Option<Autoscroll>,
13673 window: &mut Window,
13674 cx: &mut Context<Self>,
13675 ) -> Result<()> {
13676 let buffer = &display_map.buffer_snapshot;
13677 let mut selections = self.selections.all::<usize>(cx);
13678 if let Some(mut select_next_state) = self.select_next_state.take() {
13679 let query = &select_next_state.query;
13680 if !select_next_state.done {
13681 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13682 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13683 let mut next_selected_range = None;
13684
13685 let bytes_after_last_selection =
13686 buffer.bytes_in_range(last_selection.end..buffer.len());
13687 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13688 let query_matches = query
13689 .stream_find_iter(bytes_after_last_selection)
13690 .map(|result| (last_selection.end, result))
13691 .chain(
13692 query
13693 .stream_find_iter(bytes_before_first_selection)
13694 .map(|result| (0, result)),
13695 );
13696
13697 for (start_offset, query_match) in query_matches {
13698 let query_match = query_match.unwrap(); // can only fail due to I/O
13699 let offset_range =
13700 start_offset + query_match.start()..start_offset + query_match.end();
13701
13702 if !select_next_state.wordwise
13703 || (!buffer.is_inside_word(offset_range.start, false)
13704 && !buffer.is_inside_word(offset_range.end, false))
13705 {
13706 // TODO: This is n^2, because we might check all the selections
13707 if !selections
13708 .iter()
13709 .any(|selection| selection.range().overlaps(&offset_range))
13710 {
13711 next_selected_range = Some(offset_range);
13712 break;
13713 }
13714 }
13715 }
13716
13717 if let Some(next_selected_range) = next_selected_range {
13718 self.select_match_ranges(
13719 next_selected_range,
13720 last_selection.reversed,
13721 replace_newest,
13722 autoscroll,
13723 window,
13724 cx,
13725 );
13726 } else {
13727 select_next_state.done = true;
13728 }
13729 }
13730
13731 self.select_next_state = Some(select_next_state);
13732 } else {
13733 let mut only_carets = true;
13734 let mut same_text_selected = true;
13735 let mut selected_text = None;
13736
13737 let mut selections_iter = selections.iter().peekable();
13738 while let Some(selection) = selections_iter.next() {
13739 if selection.start != selection.end {
13740 only_carets = false;
13741 }
13742
13743 if same_text_selected {
13744 if selected_text.is_none() {
13745 selected_text =
13746 Some(buffer.text_for_range(selection.range()).collect::<String>());
13747 }
13748
13749 if let Some(next_selection) = selections_iter.peek() {
13750 if next_selection.range().len() == selection.range().len() {
13751 let next_selected_text = buffer
13752 .text_for_range(next_selection.range())
13753 .collect::<String>();
13754 if Some(next_selected_text) != selected_text {
13755 same_text_selected = false;
13756 selected_text = None;
13757 }
13758 } else {
13759 same_text_selected = false;
13760 selected_text = None;
13761 }
13762 }
13763 }
13764 }
13765
13766 if only_carets {
13767 for selection in &mut selections {
13768 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13769 selection.start = word_range.start;
13770 selection.end = word_range.end;
13771 selection.goal = SelectionGoal::None;
13772 selection.reversed = false;
13773 self.select_match_ranges(
13774 selection.start..selection.end,
13775 selection.reversed,
13776 replace_newest,
13777 autoscroll,
13778 window,
13779 cx,
13780 );
13781 }
13782
13783 if selections.len() == 1 {
13784 let selection = selections
13785 .last()
13786 .expect("ensured that there's only one selection");
13787 let query = buffer
13788 .text_for_range(selection.start..selection.end)
13789 .collect::<String>();
13790 let is_empty = query.is_empty();
13791 let select_state = SelectNextState {
13792 query: AhoCorasick::new(&[query])?,
13793 wordwise: true,
13794 done: is_empty,
13795 };
13796 self.select_next_state = Some(select_state);
13797 } else {
13798 self.select_next_state = None;
13799 }
13800 } else if let Some(selected_text) = selected_text {
13801 self.select_next_state = Some(SelectNextState {
13802 query: AhoCorasick::new(&[selected_text])?,
13803 wordwise: false,
13804 done: false,
13805 });
13806 self.select_next_match_internal(
13807 display_map,
13808 replace_newest,
13809 autoscroll,
13810 window,
13811 cx,
13812 )?;
13813 }
13814 }
13815 Ok(())
13816 }
13817
13818 pub fn select_all_matches(
13819 &mut self,
13820 _action: &SelectAllMatches,
13821 window: &mut Window,
13822 cx: &mut Context<Self>,
13823 ) -> Result<()> {
13824 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13825
13826 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13827
13828 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13829 let Some(select_next_state) = self.select_next_state.as_mut() else {
13830 return Ok(());
13831 };
13832 if select_next_state.done {
13833 return Ok(());
13834 }
13835
13836 let mut new_selections = Vec::new();
13837
13838 let reversed = self.selections.oldest::<usize>(cx).reversed;
13839 let buffer = &display_map.buffer_snapshot;
13840 let query_matches = select_next_state
13841 .query
13842 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13843
13844 for query_match in query_matches.into_iter() {
13845 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13846 let offset_range = if reversed {
13847 query_match.end()..query_match.start()
13848 } else {
13849 query_match.start()..query_match.end()
13850 };
13851
13852 if !select_next_state.wordwise
13853 || (!buffer.is_inside_word(offset_range.start, false)
13854 && !buffer.is_inside_word(offset_range.end, false))
13855 {
13856 new_selections.push(offset_range.start..offset_range.end);
13857 }
13858 }
13859
13860 select_next_state.done = true;
13861
13862 if new_selections.is_empty() {
13863 log::error!("bug: new_selections is empty in select_all_matches");
13864 return Ok(());
13865 }
13866
13867 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13868 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
13869 selections.select_ranges(new_selections)
13870 });
13871
13872 Ok(())
13873 }
13874
13875 pub fn select_next(
13876 &mut self,
13877 action: &SelectNext,
13878 window: &mut Window,
13879 cx: &mut Context<Self>,
13880 ) -> Result<()> {
13881 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13882 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13883 self.select_next_match_internal(
13884 &display_map,
13885 action.replace_newest,
13886 Some(Autoscroll::newest()),
13887 window,
13888 cx,
13889 )?;
13890 Ok(())
13891 }
13892
13893 pub fn select_previous(
13894 &mut self,
13895 action: &SelectPrevious,
13896 window: &mut Window,
13897 cx: &mut Context<Self>,
13898 ) -> Result<()> {
13899 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13900 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13901 let buffer = &display_map.buffer_snapshot;
13902 let mut selections = self.selections.all::<usize>(cx);
13903 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13904 let query = &select_prev_state.query;
13905 if !select_prev_state.done {
13906 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13907 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13908 let mut next_selected_range = None;
13909 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13910 let bytes_before_last_selection =
13911 buffer.reversed_bytes_in_range(0..last_selection.start);
13912 let bytes_after_first_selection =
13913 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13914 let query_matches = query
13915 .stream_find_iter(bytes_before_last_selection)
13916 .map(|result| (last_selection.start, result))
13917 .chain(
13918 query
13919 .stream_find_iter(bytes_after_first_selection)
13920 .map(|result| (buffer.len(), result)),
13921 );
13922 for (end_offset, query_match) in query_matches {
13923 let query_match = query_match.unwrap(); // can only fail due to I/O
13924 let offset_range =
13925 end_offset - query_match.end()..end_offset - query_match.start();
13926
13927 if !select_prev_state.wordwise
13928 || (!buffer.is_inside_word(offset_range.start, false)
13929 && !buffer.is_inside_word(offset_range.end, false))
13930 {
13931 next_selected_range = Some(offset_range);
13932 break;
13933 }
13934 }
13935
13936 if let Some(next_selected_range) = next_selected_range {
13937 self.select_match_ranges(
13938 next_selected_range,
13939 last_selection.reversed,
13940 action.replace_newest,
13941 Some(Autoscroll::newest()),
13942 window,
13943 cx,
13944 );
13945 } else {
13946 select_prev_state.done = true;
13947 }
13948 }
13949
13950 self.select_prev_state = Some(select_prev_state);
13951 } else {
13952 let mut only_carets = true;
13953 let mut same_text_selected = true;
13954 let mut selected_text = None;
13955
13956 let mut selections_iter = selections.iter().peekable();
13957 while let Some(selection) = selections_iter.next() {
13958 if selection.start != selection.end {
13959 only_carets = false;
13960 }
13961
13962 if same_text_selected {
13963 if selected_text.is_none() {
13964 selected_text =
13965 Some(buffer.text_for_range(selection.range()).collect::<String>());
13966 }
13967
13968 if let Some(next_selection) = selections_iter.peek() {
13969 if next_selection.range().len() == selection.range().len() {
13970 let next_selected_text = buffer
13971 .text_for_range(next_selection.range())
13972 .collect::<String>();
13973 if Some(next_selected_text) != selected_text {
13974 same_text_selected = false;
13975 selected_text = None;
13976 }
13977 } else {
13978 same_text_selected = false;
13979 selected_text = None;
13980 }
13981 }
13982 }
13983 }
13984
13985 if only_carets {
13986 for selection in &mut selections {
13987 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13988 selection.start = word_range.start;
13989 selection.end = word_range.end;
13990 selection.goal = SelectionGoal::None;
13991 selection.reversed = false;
13992 self.select_match_ranges(
13993 selection.start..selection.end,
13994 selection.reversed,
13995 action.replace_newest,
13996 Some(Autoscroll::newest()),
13997 window,
13998 cx,
13999 );
14000 }
14001 if selections.len() == 1 {
14002 let selection = selections
14003 .last()
14004 .expect("ensured that there's only one selection");
14005 let query = buffer
14006 .text_for_range(selection.start..selection.end)
14007 .collect::<String>();
14008 let is_empty = query.is_empty();
14009 let select_state = SelectNextState {
14010 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14011 wordwise: true,
14012 done: is_empty,
14013 };
14014 self.select_prev_state = Some(select_state);
14015 } else {
14016 self.select_prev_state = None;
14017 }
14018 } else if let Some(selected_text) = selected_text {
14019 self.select_prev_state = Some(SelectNextState {
14020 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14021 wordwise: false,
14022 done: false,
14023 });
14024 self.select_previous(action, window, cx)?;
14025 }
14026 }
14027 Ok(())
14028 }
14029
14030 pub fn find_next_match(
14031 &mut self,
14032 _: &FindNextMatch,
14033 window: &mut Window,
14034 cx: &mut Context<Self>,
14035 ) -> Result<()> {
14036 let selections = self.selections.disjoint_anchors();
14037 match selections.first() {
14038 Some(first) if selections.len() >= 2 => {
14039 self.change_selections(Default::default(), window, cx, |s| {
14040 s.select_ranges([first.range()]);
14041 });
14042 }
14043 _ => self.select_next(
14044 &SelectNext {
14045 replace_newest: true,
14046 },
14047 window,
14048 cx,
14049 )?,
14050 }
14051 Ok(())
14052 }
14053
14054 pub fn find_previous_match(
14055 &mut self,
14056 _: &FindPreviousMatch,
14057 window: &mut Window,
14058 cx: &mut Context<Self>,
14059 ) -> Result<()> {
14060 let selections = self.selections.disjoint_anchors();
14061 match selections.last() {
14062 Some(last) if selections.len() >= 2 => {
14063 self.change_selections(Default::default(), window, cx, |s| {
14064 s.select_ranges([last.range()]);
14065 });
14066 }
14067 _ => self.select_previous(
14068 &SelectPrevious {
14069 replace_newest: true,
14070 },
14071 window,
14072 cx,
14073 )?,
14074 }
14075 Ok(())
14076 }
14077
14078 pub fn toggle_comments(
14079 &mut self,
14080 action: &ToggleComments,
14081 window: &mut Window,
14082 cx: &mut Context<Self>,
14083 ) {
14084 if self.read_only(cx) {
14085 return;
14086 }
14087 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14088 let text_layout_details = &self.text_layout_details(window);
14089 self.transact(window, cx, |this, window, cx| {
14090 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14091 let mut edits = Vec::new();
14092 let mut selection_edit_ranges = Vec::new();
14093 let mut last_toggled_row = None;
14094 let snapshot = this.buffer.read(cx).read(cx);
14095 let empty_str: Arc<str> = Arc::default();
14096 let mut suffixes_inserted = Vec::new();
14097 let ignore_indent = action.ignore_indent;
14098
14099 fn comment_prefix_range(
14100 snapshot: &MultiBufferSnapshot,
14101 row: MultiBufferRow,
14102 comment_prefix: &str,
14103 comment_prefix_whitespace: &str,
14104 ignore_indent: bool,
14105 ) -> Range<Point> {
14106 let indent_size = if ignore_indent {
14107 0
14108 } else {
14109 snapshot.indent_size_for_line(row).len
14110 };
14111
14112 let start = Point::new(row.0, indent_size);
14113
14114 let mut line_bytes = snapshot
14115 .bytes_in_range(start..snapshot.max_point())
14116 .flatten()
14117 .copied();
14118
14119 // If this line currently begins with the line comment prefix, then record
14120 // the range containing the prefix.
14121 if line_bytes
14122 .by_ref()
14123 .take(comment_prefix.len())
14124 .eq(comment_prefix.bytes())
14125 {
14126 // Include any whitespace that matches the comment prefix.
14127 let matching_whitespace_len = line_bytes
14128 .zip(comment_prefix_whitespace.bytes())
14129 .take_while(|(a, b)| a == b)
14130 .count() as u32;
14131 let end = Point::new(
14132 start.row,
14133 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14134 );
14135 start..end
14136 } else {
14137 start..start
14138 }
14139 }
14140
14141 fn comment_suffix_range(
14142 snapshot: &MultiBufferSnapshot,
14143 row: MultiBufferRow,
14144 comment_suffix: &str,
14145 comment_suffix_has_leading_space: bool,
14146 ) -> Range<Point> {
14147 let end = Point::new(row.0, snapshot.line_len(row));
14148 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14149
14150 let mut line_end_bytes = snapshot
14151 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14152 .flatten()
14153 .copied();
14154
14155 let leading_space_len = if suffix_start_column > 0
14156 && line_end_bytes.next() == Some(b' ')
14157 && comment_suffix_has_leading_space
14158 {
14159 1
14160 } else {
14161 0
14162 };
14163
14164 // If this line currently begins with the line comment prefix, then record
14165 // the range containing the prefix.
14166 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14167 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14168 start..end
14169 } else {
14170 end..end
14171 }
14172 }
14173
14174 // TODO: Handle selections that cross excerpts
14175 for selection in &mut selections {
14176 let start_column = snapshot
14177 .indent_size_for_line(MultiBufferRow(selection.start.row))
14178 .len;
14179 let language = if let Some(language) =
14180 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14181 {
14182 language
14183 } else {
14184 continue;
14185 };
14186
14187 selection_edit_ranges.clear();
14188
14189 // If multiple selections contain a given row, avoid processing that
14190 // row more than once.
14191 let mut start_row = MultiBufferRow(selection.start.row);
14192 if last_toggled_row == Some(start_row) {
14193 start_row = start_row.next_row();
14194 }
14195 let end_row =
14196 if selection.end.row > selection.start.row && selection.end.column == 0 {
14197 MultiBufferRow(selection.end.row - 1)
14198 } else {
14199 MultiBufferRow(selection.end.row)
14200 };
14201 last_toggled_row = Some(end_row);
14202
14203 if start_row > end_row {
14204 continue;
14205 }
14206
14207 // If the language has line comments, toggle those.
14208 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14209
14210 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14211 if ignore_indent {
14212 full_comment_prefixes = full_comment_prefixes
14213 .into_iter()
14214 .map(|s| Arc::from(s.trim_end()))
14215 .collect();
14216 }
14217
14218 if !full_comment_prefixes.is_empty() {
14219 let first_prefix = full_comment_prefixes
14220 .first()
14221 .expect("prefixes is non-empty");
14222 let prefix_trimmed_lengths = full_comment_prefixes
14223 .iter()
14224 .map(|p| p.trim_end_matches(' ').len())
14225 .collect::<SmallVec<[usize; 4]>>();
14226
14227 let mut all_selection_lines_are_comments = true;
14228
14229 for row in start_row.0..=end_row.0 {
14230 let row = MultiBufferRow(row);
14231 if start_row < end_row && snapshot.is_line_blank(row) {
14232 continue;
14233 }
14234
14235 let prefix_range = full_comment_prefixes
14236 .iter()
14237 .zip(prefix_trimmed_lengths.iter().copied())
14238 .map(|(prefix, trimmed_prefix_len)| {
14239 comment_prefix_range(
14240 snapshot.deref(),
14241 row,
14242 &prefix[..trimmed_prefix_len],
14243 &prefix[trimmed_prefix_len..],
14244 ignore_indent,
14245 )
14246 })
14247 .max_by_key(|range| range.end.column - range.start.column)
14248 .expect("prefixes is non-empty");
14249
14250 if prefix_range.is_empty() {
14251 all_selection_lines_are_comments = false;
14252 }
14253
14254 selection_edit_ranges.push(prefix_range);
14255 }
14256
14257 if all_selection_lines_are_comments {
14258 edits.extend(
14259 selection_edit_ranges
14260 .iter()
14261 .cloned()
14262 .map(|range| (range, empty_str.clone())),
14263 );
14264 } else {
14265 let min_column = selection_edit_ranges
14266 .iter()
14267 .map(|range| range.start.column)
14268 .min()
14269 .unwrap_or(0);
14270 edits.extend(selection_edit_ranges.iter().map(|range| {
14271 let position = Point::new(range.start.row, min_column);
14272 (position..position, first_prefix.clone())
14273 }));
14274 }
14275 } else if let Some((full_comment_prefix, comment_suffix)) =
14276 language.block_comment_delimiters()
14277 {
14278 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14279 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14280 let prefix_range = comment_prefix_range(
14281 snapshot.deref(),
14282 start_row,
14283 comment_prefix,
14284 comment_prefix_whitespace,
14285 ignore_indent,
14286 );
14287 let suffix_range = comment_suffix_range(
14288 snapshot.deref(),
14289 end_row,
14290 comment_suffix.trim_start_matches(' '),
14291 comment_suffix.starts_with(' '),
14292 );
14293
14294 if prefix_range.is_empty() || suffix_range.is_empty() {
14295 edits.push((
14296 prefix_range.start..prefix_range.start,
14297 full_comment_prefix.clone(),
14298 ));
14299 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14300 suffixes_inserted.push((end_row, comment_suffix.len()));
14301 } else {
14302 edits.push((prefix_range, empty_str.clone()));
14303 edits.push((suffix_range, empty_str.clone()));
14304 }
14305 } else {
14306 continue;
14307 }
14308 }
14309
14310 drop(snapshot);
14311 this.buffer.update(cx, |buffer, cx| {
14312 buffer.edit(edits, None, cx);
14313 });
14314
14315 // Adjust selections so that they end before any comment suffixes that
14316 // were inserted.
14317 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14318 let mut selections = this.selections.all::<Point>(cx);
14319 let snapshot = this.buffer.read(cx).read(cx);
14320 for selection in &mut selections {
14321 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14322 match row.cmp(&MultiBufferRow(selection.end.row)) {
14323 Ordering::Less => {
14324 suffixes_inserted.next();
14325 continue;
14326 }
14327 Ordering::Greater => break,
14328 Ordering::Equal => {
14329 if selection.end.column == snapshot.line_len(row) {
14330 if selection.is_empty() {
14331 selection.start.column -= suffix_len as u32;
14332 }
14333 selection.end.column -= suffix_len as u32;
14334 }
14335 break;
14336 }
14337 }
14338 }
14339 }
14340
14341 drop(snapshot);
14342 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14343
14344 let selections = this.selections.all::<Point>(cx);
14345 let selections_on_single_row = selections.windows(2).all(|selections| {
14346 selections[0].start.row == selections[1].start.row
14347 && selections[0].end.row == selections[1].end.row
14348 && selections[0].start.row == selections[0].end.row
14349 });
14350 let selections_selecting = selections
14351 .iter()
14352 .any(|selection| selection.start != selection.end);
14353 let advance_downwards = action.advance_downwards
14354 && selections_on_single_row
14355 && !selections_selecting
14356 && !matches!(this.mode, EditorMode::SingleLine { .. });
14357
14358 if advance_downwards {
14359 let snapshot = this.buffer.read(cx).snapshot(cx);
14360
14361 this.change_selections(Default::default(), window, cx, |s| {
14362 s.move_cursors_with(|display_snapshot, display_point, _| {
14363 let mut point = display_point.to_point(display_snapshot);
14364 point.row += 1;
14365 point = snapshot.clip_point(point, Bias::Left);
14366 let display_point = point.to_display_point(display_snapshot);
14367 let goal = SelectionGoal::HorizontalPosition(
14368 display_snapshot
14369 .x_for_display_point(display_point, text_layout_details)
14370 .into(),
14371 );
14372 (display_point, goal)
14373 })
14374 });
14375 }
14376 });
14377 }
14378
14379 pub fn select_enclosing_symbol(
14380 &mut self,
14381 _: &SelectEnclosingSymbol,
14382 window: &mut Window,
14383 cx: &mut Context<Self>,
14384 ) {
14385 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14386
14387 let buffer = self.buffer.read(cx).snapshot(cx);
14388 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14389
14390 fn update_selection(
14391 selection: &Selection<usize>,
14392 buffer_snap: &MultiBufferSnapshot,
14393 ) -> Option<Selection<usize>> {
14394 let cursor = selection.head();
14395 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14396 for symbol in symbols.iter().rev() {
14397 let start = symbol.range.start.to_offset(buffer_snap);
14398 let end = symbol.range.end.to_offset(buffer_snap);
14399 let new_range = start..end;
14400 if start < selection.start || end > selection.end {
14401 return Some(Selection {
14402 id: selection.id,
14403 start: new_range.start,
14404 end: new_range.end,
14405 goal: SelectionGoal::None,
14406 reversed: selection.reversed,
14407 });
14408 }
14409 }
14410 None
14411 }
14412
14413 let mut selected_larger_symbol = false;
14414 let new_selections = old_selections
14415 .iter()
14416 .map(|selection| match update_selection(selection, &buffer) {
14417 Some(new_selection) => {
14418 if new_selection.range() != selection.range() {
14419 selected_larger_symbol = true;
14420 }
14421 new_selection
14422 }
14423 None => selection.clone(),
14424 })
14425 .collect::<Vec<_>>();
14426
14427 if selected_larger_symbol {
14428 self.change_selections(Default::default(), window, cx, |s| {
14429 s.select(new_selections);
14430 });
14431 }
14432 }
14433
14434 pub fn select_larger_syntax_node(
14435 &mut self,
14436 _: &SelectLargerSyntaxNode,
14437 window: &mut Window,
14438 cx: &mut Context<Self>,
14439 ) {
14440 let Some(visible_row_count) = self.visible_row_count() else {
14441 return;
14442 };
14443 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14444 if old_selections.is_empty() {
14445 return;
14446 }
14447
14448 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14449
14450 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14451 let buffer = self.buffer.read(cx).snapshot(cx);
14452
14453 let mut selected_larger_node = false;
14454 let mut new_selections = old_selections
14455 .iter()
14456 .map(|selection| {
14457 let old_range = selection.start..selection.end;
14458
14459 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14460 // manually select word at selection
14461 if ["string_content", "inline"].contains(&node.kind()) {
14462 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14463 // ignore if word is already selected
14464 if !word_range.is_empty() && old_range != word_range {
14465 let (last_word_range, _) =
14466 buffer.surrounding_word(old_range.end, false);
14467 // only select word if start and end point belongs to same word
14468 if word_range == last_word_range {
14469 selected_larger_node = true;
14470 return Selection {
14471 id: selection.id,
14472 start: word_range.start,
14473 end: word_range.end,
14474 goal: SelectionGoal::None,
14475 reversed: selection.reversed,
14476 };
14477 }
14478 }
14479 }
14480 }
14481
14482 let mut new_range = old_range.clone();
14483 while let Some((_node, containing_range)) =
14484 buffer.syntax_ancestor(new_range.clone())
14485 {
14486 new_range = match containing_range {
14487 MultiOrSingleBufferOffsetRange::Single(_) => break,
14488 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14489 };
14490 if !display_map.intersects_fold(new_range.start)
14491 && !display_map.intersects_fold(new_range.end)
14492 {
14493 break;
14494 }
14495 }
14496
14497 selected_larger_node |= new_range != old_range;
14498 Selection {
14499 id: selection.id,
14500 start: new_range.start,
14501 end: new_range.end,
14502 goal: SelectionGoal::None,
14503 reversed: selection.reversed,
14504 }
14505 })
14506 .collect::<Vec<_>>();
14507
14508 if !selected_larger_node {
14509 return; // don't put this call in the history
14510 }
14511
14512 // scroll based on transformation done to the last selection created by the user
14513 let (last_old, last_new) = old_selections
14514 .last()
14515 .zip(new_selections.last().cloned())
14516 .expect("old_selections isn't empty");
14517
14518 // revert selection
14519 let is_selection_reversed = {
14520 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14521 new_selections.last_mut().expect("checked above").reversed =
14522 should_newest_selection_be_reversed;
14523 should_newest_selection_be_reversed
14524 };
14525
14526 if selected_larger_node {
14527 self.select_syntax_node_history.disable_clearing = true;
14528 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14529 s.select(new_selections.clone());
14530 });
14531 self.select_syntax_node_history.disable_clearing = false;
14532 }
14533
14534 let start_row = last_new.start.to_display_point(&display_map).row().0;
14535 let end_row = last_new.end.to_display_point(&display_map).row().0;
14536 let selection_height = end_row - start_row + 1;
14537 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14538
14539 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14540 let scroll_behavior = if fits_on_the_screen {
14541 self.request_autoscroll(Autoscroll::fit(), cx);
14542 SelectSyntaxNodeScrollBehavior::FitSelection
14543 } else if is_selection_reversed {
14544 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14545 SelectSyntaxNodeScrollBehavior::CursorTop
14546 } else {
14547 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14548 SelectSyntaxNodeScrollBehavior::CursorBottom
14549 };
14550
14551 self.select_syntax_node_history.push((
14552 old_selections,
14553 scroll_behavior,
14554 is_selection_reversed,
14555 ));
14556 }
14557
14558 pub fn select_smaller_syntax_node(
14559 &mut self,
14560 _: &SelectSmallerSyntaxNode,
14561 window: &mut Window,
14562 cx: &mut Context<Self>,
14563 ) {
14564 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14565
14566 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14567 self.select_syntax_node_history.pop()
14568 {
14569 if let Some(selection) = selections.last_mut() {
14570 selection.reversed = is_selection_reversed;
14571 }
14572
14573 self.select_syntax_node_history.disable_clearing = true;
14574 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14575 s.select(selections.to_vec());
14576 });
14577 self.select_syntax_node_history.disable_clearing = false;
14578
14579 match scroll_behavior {
14580 SelectSyntaxNodeScrollBehavior::CursorTop => {
14581 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14582 }
14583 SelectSyntaxNodeScrollBehavior::FitSelection => {
14584 self.request_autoscroll(Autoscroll::fit(), cx);
14585 }
14586 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14587 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14588 }
14589 }
14590 }
14591 }
14592
14593 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14594 if !EditorSettings::get_global(cx).gutter.runnables {
14595 self.clear_tasks();
14596 return Task::ready(());
14597 }
14598 let project = self.project.as_ref().map(Entity::downgrade);
14599 let task_sources = self.lsp_task_sources(cx);
14600 let multi_buffer = self.buffer.downgrade();
14601 cx.spawn_in(window, async move |editor, cx| {
14602 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14603 let Some(project) = project.and_then(|p| p.upgrade()) else {
14604 return;
14605 };
14606 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14607 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14608 }) else {
14609 return;
14610 };
14611
14612 let hide_runnables = project
14613 .update(cx, |project, cx| {
14614 // Do not display any test indicators in non-dev server remote projects.
14615 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14616 })
14617 .unwrap_or(true);
14618 if hide_runnables {
14619 return;
14620 }
14621 let new_rows =
14622 cx.background_spawn({
14623 let snapshot = display_snapshot.clone();
14624 async move {
14625 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14626 }
14627 })
14628 .await;
14629 let Ok(lsp_tasks) =
14630 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14631 else {
14632 return;
14633 };
14634 let lsp_tasks = lsp_tasks.await;
14635
14636 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14637 lsp_tasks
14638 .into_iter()
14639 .flat_map(|(kind, tasks)| {
14640 tasks.into_iter().filter_map(move |(location, task)| {
14641 Some((kind.clone(), location?, task))
14642 })
14643 })
14644 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14645 let buffer = location.target.buffer;
14646 let buffer_snapshot = buffer.read(cx).snapshot();
14647 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14648 |(excerpt_id, snapshot, _)| {
14649 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14650 display_snapshot
14651 .buffer_snapshot
14652 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14653 } else {
14654 None
14655 }
14656 },
14657 );
14658 if let Some(offset) = offset {
14659 let task_buffer_range =
14660 location.target.range.to_point(&buffer_snapshot);
14661 let context_buffer_range =
14662 task_buffer_range.to_offset(&buffer_snapshot);
14663 let context_range = BufferOffset(context_buffer_range.start)
14664 ..BufferOffset(context_buffer_range.end);
14665
14666 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14667 .or_insert_with(|| RunnableTasks {
14668 templates: Vec::new(),
14669 offset,
14670 column: task_buffer_range.start.column,
14671 extra_variables: HashMap::default(),
14672 context_range,
14673 })
14674 .templates
14675 .push((kind, task.original_task().clone()));
14676 }
14677
14678 acc
14679 })
14680 }) else {
14681 return;
14682 };
14683
14684 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14685 buffer.language_settings(cx).tasks.prefer_lsp
14686 }) else {
14687 return;
14688 };
14689
14690 let rows = Self::runnable_rows(
14691 project,
14692 display_snapshot,
14693 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14694 new_rows,
14695 cx.clone(),
14696 )
14697 .await;
14698 editor
14699 .update(cx, |editor, _| {
14700 editor.clear_tasks();
14701 for (key, mut value) in rows {
14702 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14703 value.templates.extend(lsp_tasks.templates);
14704 }
14705
14706 editor.insert_tasks(key, value);
14707 }
14708 for (key, value) in lsp_tasks_by_rows {
14709 editor.insert_tasks(key, value);
14710 }
14711 })
14712 .ok();
14713 })
14714 }
14715 fn fetch_runnable_ranges(
14716 snapshot: &DisplaySnapshot,
14717 range: Range<Anchor>,
14718 ) -> Vec<language::RunnableRange> {
14719 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14720 }
14721
14722 fn runnable_rows(
14723 project: Entity<Project>,
14724 snapshot: DisplaySnapshot,
14725 prefer_lsp: bool,
14726 runnable_ranges: Vec<RunnableRange>,
14727 cx: AsyncWindowContext,
14728 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14729 cx.spawn(async move |cx| {
14730 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14731 for mut runnable in runnable_ranges {
14732 let Some(tasks) = cx
14733 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14734 .ok()
14735 else {
14736 continue;
14737 };
14738 let mut tasks = tasks.await;
14739
14740 if prefer_lsp {
14741 tasks.retain(|(task_kind, _)| {
14742 !matches!(task_kind, TaskSourceKind::Language { .. })
14743 });
14744 }
14745 if tasks.is_empty() {
14746 continue;
14747 }
14748
14749 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14750 let Some(row) = snapshot
14751 .buffer_snapshot
14752 .buffer_line_for_row(MultiBufferRow(point.row))
14753 .map(|(_, range)| range.start.row)
14754 else {
14755 continue;
14756 };
14757
14758 let context_range =
14759 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14760 runnable_rows.push((
14761 (runnable.buffer_id, row),
14762 RunnableTasks {
14763 templates: tasks,
14764 offset: snapshot
14765 .buffer_snapshot
14766 .anchor_before(runnable.run_range.start),
14767 context_range,
14768 column: point.column,
14769 extra_variables: runnable.extra_captures,
14770 },
14771 ));
14772 }
14773 runnable_rows
14774 })
14775 }
14776
14777 fn templates_with_tags(
14778 project: &Entity<Project>,
14779 runnable: &mut Runnable,
14780 cx: &mut App,
14781 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14782 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14783 let (worktree_id, file) = project
14784 .buffer_for_id(runnable.buffer, cx)
14785 .and_then(|buffer| buffer.read(cx).file())
14786 .map(|file| (file.worktree_id(cx), file.clone()))
14787 .unzip();
14788
14789 (
14790 project.task_store().read(cx).task_inventory().cloned(),
14791 worktree_id,
14792 file,
14793 )
14794 });
14795
14796 let tags = mem::take(&mut runnable.tags);
14797 let language = runnable.language.clone();
14798 cx.spawn(async move |cx| {
14799 let mut templates_with_tags = Vec::new();
14800 if let Some(inventory) = inventory {
14801 for RunnableTag(tag) in tags {
14802 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14803 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14804 }) else {
14805 return templates_with_tags;
14806 };
14807 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14808 move |(_, template)| {
14809 template.tags.iter().any(|source_tag| source_tag == &tag)
14810 },
14811 ));
14812 }
14813 }
14814 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14815
14816 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14817 // Strongest source wins; if we have worktree tag binding, prefer that to
14818 // global and language bindings;
14819 // if we have a global binding, prefer that to language binding.
14820 let first_mismatch = templates_with_tags
14821 .iter()
14822 .position(|(tag_source, _)| tag_source != leading_tag_source);
14823 if let Some(index) = first_mismatch {
14824 templates_with_tags.truncate(index);
14825 }
14826 }
14827
14828 templates_with_tags
14829 })
14830 }
14831
14832 pub fn move_to_enclosing_bracket(
14833 &mut self,
14834 _: &MoveToEnclosingBracket,
14835 window: &mut Window,
14836 cx: &mut Context<Self>,
14837 ) {
14838 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14839 self.change_selections(Default::default(), window, cx, |s| {
14840 s.move_offsets_with(|snapshot, selection| {
14841 let Some(enclosing_bracket_ranges) =
14842 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14843 else {
14844 return;
14845 };
14846
14847 let mut best_length = usize::MAX;
14848 let mut best_inside = false;
14849 let mut best_in_bracket_range = false;
14850 let mut best_destination = None;
14851 for (open, close) in enclosing_bracket_ranges {
14852 let close = close.to_inclusive();
14853 let length = close.end() - open.start;
14854 let inside = selection.start >= open.end && selection.end <= *close.start();
14855 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14856 || close.contains(&selection.head());
14857
14858 // If best is next to a bracket and current isn't, skip
14859 if !in_bracket_range && best_in_bracket_range {
14860 continue;
14861 }
14862
14863 // Prefer smaller lengths unless best is inside and current isn't
14864 if length > best_length && (best_inside || !inside) {
14865 continue;
14866 }
14867
14868 best_length = length;
14869 best_inside = inside;
14870 best_in_bracket_range = in_bracket_range;
14871 best_destination = Some(
14872 if close.contains(&selection.start) && close.contains(&selection.end) {
14873 if inside { open.end } else { open.start }
14874 } else if inside {
14875 *close.start()
14876 } else {
14877 *close.end()
14878 },
14879 );
14880 }
14881
14882 if let Some(destination) = best_destination {
14883 selection.collapse_to(destination, SelectionGoal::None);
14884 }
14885 })
14886 });
14887 }
14888
14889 pub fn undo_selection(
14890 &mut self,
14891 _: &UndoSelection,
14892 window: &mut Window,
14893 cx: &mut Context<Self>,
14894 ) {
14895 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14896 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14897 self.selection_history.mode = SelectionHistoryMode::Undoing;
14898 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14899 this.end_selection(window, cx);
14900 this.change_selections(
14901 SelectionEffects::scroll(Autoscroll::newest()),
14902 window,
14903 cx,
14904 |s| s.select_anchors(entry.selections.to_vec()),
14905 );
14906 });
14907 self.selection_history.mode = SelectionHistoryMode::Normal;
14908
14909 self.select_next_state = entry.select_next_state;
14910 self.select_prev_state = entry.select_prev_state;
14911 self.add_selections_state = entry.add_selections_state;
14912 }
14913 }
14914
14915 pub fn redo_selection(
14916 &mut self,
14917 _: &RedoSelection,
14918 window: &mut Window,
14919 cx: &mut Context<Self>,
14920 ) {
14921 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14922 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14923 self.selection_history.mode = SelectionHistoryMode::Redoing;
14924 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14925 this.end_selection(window, cx);
14926 this.change_selections(
14927 SelectionEffects::scroll(Autoscroll::newest()),
14928 window,
14929 cx,
14930 |s| s.select_anchors(entry.selections.to_vec()),
14931 );
14932 });
14933 self.selection_history.mode = SelectionHistoryMode::Normal;
14934
14935 self.select_next_state = entry.select_next_state;
14936 self.select_prev_state = entry.select_prev_state;
14937 self.add_selections_state = entry.add_selections_state;
14938 }
14939 }
14940
14941 pub fn expand_excerpts(
14942 &mut self,
14943 action: &ExpandExcerpts,
14944 _: &mut Window,
14945 cx: &mut Context<Self>,
14946 ) {
14947 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14948 }
14949
14950 pub fn expand_excerpts_down(
14951 &mut self,
14952 action: &ExpandExcerptsDown,
14953 _: &mut Window,
14954 cx: &mut Context<Self>,
14955 ) {
14956 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14957 }
14958
14959 pub fn expand_excerpts_up(
14960 &mut self,
14961 action: &ExpandExcerptsUp,
14962 _: &mut Window,
14963 cx: &mut Context<Self>,
14964 ) {
14965 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14966 }
14967
14968 pub fn expand_excerpts_for_direction(
14969 &mut self,
14970 lines: u32,
14971 direction: ExpandExcerptDirection,
14972
14973 cx: &mut Context<Self>,
14974 ) {
14975 let selections = self.selections.disjoint_anchors();
14976
14977 let lines = if lines == 0 {
14978 EditorSettings::get_global(cx).expand_excerpt_lines
14979 } else {
14980 lines
14981 };
14982
14983 self.buffer.update(cx, |buffer, cx| {
14984 let snapshot = buffer.snapshot(cx);
14985 let mut excerpt_ids = selections
14986 .iter()
14987 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14988 .collect::<Vec<_>>();
14989 excerpt_ids.sort();
14990 excerpt_ids.dedup();
14991 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14992 })
14993 }
14994
14995 pub fn expand_excerpt(
14996 &mut self,
14997 excerpt: ExcerptId,
14998 direction: ExpandExcerptDirection,
14999 window: &mut Window,
15000 cx: &mut Context<Self>,
15001 ) {
15002 let current_scroll_position = self.scroll_position(cx);
15003 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15004 let mut should_scroll_up = false;
15005
15006 if direction == ExpandExcerptDirection::Down {
15007 let multi_buffer = self.buffer.read(cx);
15008 let snapshot = multi_buffer.snapshot(cx);
15009 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
15010 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
15011 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
15012 let buffer_snapshot = buffer.read(cx).snapshot();
15013 let excerpt_end_row =
15014 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15015 let last_row = buffer_snapshot.max_point().row;
15016 let lines_below = last_row.saturating_sub(excerpt_end_row);
15017 should_scroll_up = lines_below >= lines_to_expand;
15018 }
15019 }
15020 }
15021 }
15022
15023 self.buffer.update(cx, |buffer, cx| {
15024 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15025 });
15026
15027 if should_scroll_up {
15028 let new_scroll_position =
15029 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15030 self.set_scroll_position(new_scroll_position, window, cx);
15031 }
15032 }
15033
15034 pub fn go_to_singleton_buffer_point(
15035 &mut self,
15036 point: Point,
15037 window: &mut Window,
15038 cx: &mut Context<Self>,
15039 ) {
15040 self.go_to_singleton_buffer_range(point..point, window, cx);
15041 }
15042
15043 pub fn go_to_singleton_buffer_range(
15044 &mut self,
15045 range: Range<Point>,
15046 window: &mut Window,
15047 cx: &mut Context<Self>,
15048 ) {
15049 let multibuffer = self.buffer().read(cx);
15050 let Some(buffer) = multibuffer.as_singleton() else {
15051 return;
15052 };
15053 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15054 return;
15055 };
15056 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15057 return;
15058 };
15059 self.change_selections(
15060 SelectionEffects::default().nav_history(true),
15061 window,
15062 cx,
15063 |s| s.select_anchor_ranges([start..end]),
15064 );
15065 }
15066
15067 pub fn go_to_diagnostic(
15068 &mut self,
15069 _: &GoToDiagnostic,
15070 window: &mut Window,
15071 cx: &mut Context<Self>,
15072 ) {
15073 if !self.diagnostics_enabled() {
15074 return;
15075 }
15076 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15077 self.go_to_diagnostic_impl(Direction::Next, window, cx)
15078 }
15079
15080 pub fn go_to_prev_diagnostic(
15081 &mut self,
15082 _: &GoToPreviousDiagnostic,
15083 window: &mut Window,
15084 cx: &mut Context<Self>,
15085 ) {
15086 if !self.diagnostics_enabled() {
15087 return;
15088 }
15089 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15090 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
15091 }
15092
15093 pub fn go_to_diagnostic_impl(
15094 &mut self,
15095 direction: Direction,
15096 window: &mut Window,
15097 cx: &mut Context<Self>,
15098 ) {
15099 let buffer = self.buffer.read(cx).snapshot(cx);
15100 let selection = self.selections.newest::<usize>(cx);
15101
15102 let mut active_group_id = None;
15103 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
15104 if active_group.active_range.start.to_offset(&buffer) == selection.start {
15105 active_group_id = Some(active_group.group_id);
15106 }
15107 }
15108
15109 fn filtered(
15110 snapshot: EditorSnapshot,
15111 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15112 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15113 diagnostics
15114 .filter(|entry| entry.range.start != entry.range.end)
15115 .filter(|entry| !entry.diagnostic.is_unnecessary)
15116 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15117 }
15118
15119 let snapshot = self.snapshot(window, cx);
15120 let before = filtered(
15121 snapshot.clone(),
15122 buffer
15123 .diagnostics_in_range(0..selection.start)
15124 .filter(|entry| entry.range.start <= selection.start),
15125 );
15126 let after = filtered(
15127 snapshot,
15128 buffer
15129 .diagnostics_in_range(selection.start..buffer.len())
15130 .filter(|entry| entry.range.start >= selection.start),
15131 );
15132
15133 let mut found: Option<DiagnosticEntry<usize>> = None;
15134 if direction == Direction::Prev {
15135 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15136 {
15137 for diagnostic in prev_diagnostics.into_iter().rev() {
15138 if diagnostic.range.start != selection.start
15139 || active_group_id
15140 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15141 {
15142 found = Some(diagnostic);
15143 break 'outer;
15144 }
15145 }
15146 }
15147 } else {
15148 for diagnostic in after.chain(before) {
15149 if diagnostic.range.start != selection.start
15150 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15151 {
15152 found = Some(diagnostic);
15153 break;
15154 }
15155 }
15156 }
15157 let Some(next_diagnostic) = found else {
15158 return;
15159 };
15160
15161 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15162 return;
15163 };
15164 self.change_selections(Default::default(), window, cx, |s| {
15165 s.select_ranges(vec![
15166 next_diagnostic.range.start..next_diagnostic.range.start,
15167 ])
15168 });
15169 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15170 self.refresh_inline_completion(false, true, window, cx);
15171 }
15172
15173 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15174 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15175 let snapshot = self.snapshot(window, cx);
15176 let selection = self.selections.newest::<Point>(cx);
15177 self.go_to_hunk_before_or_after_position(
15178 &snapshot,
15179 selection.head(),
15180 Direction::Next,
15181 window,
15182 cx,
15183 );
15184 }
15185
15186 pub fn go_to_hunk_before_or_after_position(
15187 &mut self,
15188 snapshot: &EditorSnapshot,
15189 position: Point,
15190 direction: Direction,
15191 window: &mut Window,
15192 cx: &mut Context<Editor>,
15193 ) {
15194 let row = if direction == Direction::Next {
15195 self.hunk_after_position(snapshot, position)
15196 .map(|hunk| hunk.row_range.start)
15197 } else {
15198 self.hunk_before_position(snapshot, position)
15199 };
15200
15201 if let Some(row) = row {
15202 let destination = Point::new(row.0, 0);
15203 let autoscroll = Autoscroll::center();
15204
15205 self.unfold_ranges(&[destination..destination], false, false, cx);
15206 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15207 s.select_ranges([destination..destination]);
15208 });
15209 }
15210 }
15211
15212 fn hunk_after_position(
15213 &mut self,
15214 snapshot: &EditorSnapshot,
15215 position: Point,
15216 ) -> Option<MultiBufferDiffHunk> {
15217 snapshot
15218 .buffer_snapshot
15219 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15220 .find(|hunk| hunk.row_range.start.0 > position.row)
15221 .or_else(|| {
15222 snapshot
15223 .buffer_snapshot
15224 .diff_hunks_in_range(Point::zero()..position)
15225 .find(|hunk| hunk.row_range.end.0 < position.row)
15226 })
15227 }
15228
15229 fn go_to_prev_hunk(
15230 &mut self,
15231 _: &GoToPreviousHunk,
15232 window: &mut Window,
15233 cx: &mut Context<Self>,
15234 ) {
15235 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15236 let snapshot = self.snapshot(window, cx);
15237 let selection = self.selections.newest::<Point>(cx);
15238 self.go_to_hunk_before_or_after_position(
15239 &snapshot,
15240 selection.head(),
15241 Direction::Prev,
15242 window,
15243 cx,
15244 );
15245 }
15246
15247 fn hunk_before_position(
15248 &mut self,
15249 snapshot: &EditorSnapshot,
15250 position: Point,
15251 ) -> Option<MultiBufferRow> {
15252 snapshot
15253 .buffer_snapshot
15254 .diff_hunk_before(position)
15255 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15256 }
15257
15258 fn go_to_next_change(
15259 &mut self,
15260 _: &GoToNextChange,
15261 window: &mut Window,
15262 cx: &mut Context<Self>,
15263 ) {
15264 if let Some(selections) = self
15265 .change_list
15266 .next_change(1, Direction::Next)
15267 .map(|s| s.to_vec())
15268 {
15269 self.change_selections(Default::default(), window, cx, |s| {
15270 let map = s.display_map();
15271 s.select_display_ranges(selections.iter().map(|a| {
15272 let point = a.to_display_point(&map);
15273 point..point
15274 }))
15275 })
15276 }
15277 }
15278
15279 fn go_to_previous_change(
15280 &mut self,
15281 _: &GoToPreviousChange,
15282 window: &mut Window,
15283 cx: &mut Context<Self>,
15284 ) {
15285 if let Some(selections) = self
15286 .change_list
15287 .next_change(1, Direction::Prev)
15288 .map(|s| s.to_vec())
15289 {
15290 self.change_selections(Default::default(), window, cx, |s| {
15291 let map = s.display_map();
15292 s.select_display_ranges(selections.iter().map(|a| {
15293 let point = a.to_display_point(&map);
15294 point..point
15295 }))
15296 })
15297 }
15298 }
15299
15300 fn go_to_line<T: 'static>(
15301 &mut self,
15302 position: Anchor,
15303 highlight_color: Option<Hsla>,
15304 window: &mut Window,
15305 cx: &mut Context<Self>,
15306 ) {
15307 let snapshot = self.snapshot(window, cx).display_snapshot;
15308 let position = position.to_point(&snapshot.buffer_snapshot);
15309 let start = snapshot
15310 .buffer_snapshot
15311 .clip_point(Point::new(position.row, 0), Bias::Left);
15312 let end = start + Point::new(1, 0);
15313 let start = snapshot.buffer_snapshot.anchor_before(start);
15314 let end = snapshot.buffer_snapshot.anchor_before(end);
15315
15316 self.highlight_rows::<T>(
15317 start..end,
15318 highlight_color
15319 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15320 Default::default(),
15321 cx,
15322 );
15323
15324 if self.buffer.read(cx).is_singleton() {
15325 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15326 }
15327 }
15328
15329 pub fn go_to_definition(
15330 &mut self,
15331 _: &GoToDefinition,
15332 window: &mut Window,
15333 cx: &mut Context<Self>,
15334 ) -> Task<Result<Navigated>> {
15335 let definition =
15336 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15337 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15338 cx.spawn_in(window, async move |editor, cx| {
15339 if definition.await? == Navigated::Yes {
15340 return Ok(Navigated::Yes);
15341 }
15342 match fallback_strategy {
15343 GoToDefinitionFallback::None => Ok(Navigated::No),
15344 GoToDefinitionFallback::FindAllReferences => {
15345 match editor.update_in(cx, |editor, window, cx| {
15346 editor.find_all_references(&FindAllReferences, window, cx)
15347 })? {
15348 Some(references) => references.await,
15349 None => Ok(Navigated::No),
15350 }
15351 }
15352 }
15353 })
15354 }
15355
15356 pub fn go_to_declaration(
15357 &mut self,
15358 _: &GoToDeclaration,
15359 window: &mut Window,
15360 cx: &mut Context<Self>,
15361 ) -> Task<Result<Navigated>> {
15362 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15363 }
15364
15365 pub fn go_to_declaration_split(
15366 &mut self,
15367 _: &GoToDeclaration,
15368 window: &mut Window,
15369 cx: &mut Context<Self>,
15370 ) -> Task<Result<Navigated>> {
15371 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15372 }
15373
15374 pub fn go_to_implementation(
15375 &mut self,
15376 _: &GoToImplementation,
15377 window: &mut Window,
15378 cx: &mut Context<Self>,
15379 ) -> Task<Result<Navigated>> {
15380 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15381 }
15382
15383 pub fn go_to_implementation_split(
15384 &mut self,
15385 _: &GoToImplementationSplit,
15386 window: &mut Window,
15387 cx: &mut Context<Self>,
15388 ) -> Task<Result<Navigated>> {
15389 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15390 }
15391
15392 pub fn go_to_type_definition(
15393 &mut self,
15394 _: &GoToTypeDefinition,
15395 window: &mut Window,
15396 cx: &mut Context<Self>,
15397 ) -> Task<Result<Navigated>> {
15398 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15399 }
15400
15401 pub fn go_to_definition_split(
15402 &mut self,
15403 _: &GoToDefinitionSplit,
15404 window: &mut Window,
15405 cx: &mut Context<Self>,
15406 ) -> Task<Result<Navigated>> {
15407 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15408 }
15409
15410 pub fn go_to_type_definition_split(
15411 &mut self,
15412 _: &GoToTypeDefinitionSplit,
15413 window: &mut Window,
15414 cx: &mut Context<Self>,
15415 ) -> Task<Result<Navigated>> {
15416 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15417 }
15418
15419 fn go_to_definition_of_kind(
15420 &mut self,
15421 kind: GotoDefinitionKind,
15422 split: bool,
15423 window: &mut Window,
15424 cx: &mut Context<Self>,
15425 ) -> Task<Result<Navigated>> {
15426 let Some(provider) = self.semantics_provider.clone() else {
15427 return Task::ready(Ok(Navigated::No));
15428 };
15429 let head = self.selections.newest::<usize>(cx).head();
15430 let buffer = self.buffer.read(cx);
15431 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
15432 text_anchor
15433 } else {
15434 return Task::ready(Ok(Navigated::No));
15435 };
15436
15437 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15438 return Task::ready(Ok(Navigated::No));
15439 };
15440
15441 cx.spawn_in(window, async move |editor, cx| {
15442 let definitions = definitions.await?;
15443 let navigated = editor
15444 .update_in(cx, |editor, window, cx| {
15445 editor.navigate_to_hover_links(
15446 Some(kind),
15447 definitions
15448 .into_iter()
15449 .filter(|location| {
15450 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15451 })
15452 .map(HoverLink::Text)
15453 .collect::<Vec<_>>(),
15454 split,
15455 window,
15456 cx,
15457 )
15458 })?
15459 .await?;
15460 anyhow::Ok(navigated)
15461 })
15462 }
15463
15464 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15465 let selection = self.selections.newest_anchor();
15466 let head = selection.head();
15467 let tail = selection.tail();
15468
15469 let Some((buffer, start_position)) =
15470 self.buffer.read(cx).text_anchor_for_position(head, cx)
15471 else {
15472 return;
15473 };
15474
15475 let end_position = if head != tail {
15476 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15477 return;
15478 };
15479 Some(pos)
15480 } else {
15481 None
15482 };
15483
15484 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15485 let url = if let Some(end_pos) = end_position {
15486 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15487 } else {
15488 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15489 };
15490
15491 if let Some(url) = url {
15492 editor.update(cx, |_, cx| {
15493 cx.open_url(&url);
15494 })
15495 } else {
15496 Ok(())
15497 }
15498 });
15499
15500 url_finder.detach();
15501 }
15502
15503 pub fn open_selected_filename(
15504 &mut self,
15505 _: &OpenSelectedFilename,
15506 window: &mut Window,
15507 cx: &mut Context<Self>,
15508 ) {
15509 let Some(workspace) = self.workspace() else {
15510 return;
15511 };
15512
15513 let position = self.selections.newest_anchor().head();
15514
15515 let Some((buffer, buffer_position)) =
15516 self.buffer.read(cx).text_anchor_for_position(position, cx)
15517 else {
15518 return;
15519 };
15520
15521 let project = self.project.clone();
15522
15523 cx.spawn_in(window, async move |_, cx| {
15524 let result = find_file(&buffer, project, buffer_position, cx).await;
15525
15526 if let Some((_, path)) = result {
15527 workspace
15528 .update_in(cx, |workspace, window, cx| {
15529 workspace.open_resolved_path(path, window, cx)
15530 })?
15531 .await?;
15532 }
15533 anyhow::Ok(())
15534 })
15535 .detach();
15536 }
15537
15538 pub(crate) fn navigate_to_hover_links(
15539 &mut self,
15540 kind: Option<GotoDefinitionKind>,
15541 mut definitions: Vec<HoverLink>,
15542 split: bool,
15543 window: &mut Window,
15544 cx: &mut Context<Editor>,
15545 ) -> Task<Result<Navigated>> {
15546 // If there is one definition, just open it directly
15547 if definitions.len() == 1 {
15548 let definition = definitions.pop().unwrap();
15549
15550 enum TargetTaskResult {
15551 Location(Option<Location>),
15552 AlreadyNavigated,
15553 }
15554
15555 let target_task = match definition {
15556 HoverLink::Text(link) => {
15557 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15558 }
15559 HoverLink::InlayHint(lsp_location, server_id) => {
15560 let computation =
15561 self.compute_target_location(lsp_location, server_id, window, cx);
15562 cx.background_spawn(async move {
15563 let location = computation.await?;
15564 Ok(TargetTaskResult::Location(location))
15565 })
15566 }
15567 HoverLink::Url(url) => {
15568 cx.open_url(&url);
15569 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15570 }
15571 HoverLink::File(path) => {
15572 if let Some(workspace) = self.workspace() {
15573 cx.spawn_in(window, async move |_, cx| {
15574 workspace
15575 .update_in(cx, |workspace, window, cx| {
15576 workspace.open_resolved_path(path, window, cx)
15577 })?
15578 .await
15579 .map(|_| TargetTaskResult::AlreadyNavigated)
15580 })
15581 } else {
15582 Task::ready(Ok(TargetTaskResult::Location(None)))
15583 }
15584 }
15585 };
15586 cx.spawn_in(window, async move |editor, cx| {
15587 let target = match target_task.await.context("target resolution task")? {
15588 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15589 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15590 TargetTaskResult::Location(Some(target)) => target,
15591 };
15592
15593 editor.update_in(cx, |editor, window, cx| {
15594 let Some(workspace) = editor.workspace() else {
15595 return Navigated::No;
15596 };
15597 let pane = workspace.read(cx).active_pane().clone();
15598
15599 let range = target.range.to_point(target.buffer.read(cx));
15600 let range = editor.range_for_match(&range);
15601 let range = collapse_multiline_range(range);
15602
15603 if !split
15604 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15605 {
15606 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15607 } else {
15608 window.defer(cx, move |window, cx| {
15609 let target_editor: Entity<Self> =
15610 workspace.update(cx, |workspace, cx| {
15611 let pane = if split {
15612 workspace.adjacent_pane(window, cx)
15613 } else {
15614 workspace.active_pane().clone()
15615 };
15616
15617 workspace.open_project_item(
15618 pane,
15619 target.buffer.clone(),
15620 true,
15621 true,
15622 window,
15623 cx,
15624 )
15625 });
15626 target_editor.update(cx, |target_editor, cx| {
15627 // When selecting a definition in a different buffer, disable the nav history
15628 // to avoid creating a history entry at the previous cursor location.
15629 pane.update(cx, |pane, _| pane.disable_history());
15630 target_editor.go_to_singleton_buffer_range(range, window, cx);
15631 pane.update(cx, |pane, _| pane.enable_history());
15632 });
15633 });
15634 }
15635 Navigated::Yes
15636 })
15637 })
15638 } else if !definitions.is_empty() {
15639 cx.spawn_in(window, async move |editor, cx| {
15640 let (title, location_tasks, workspace) = editor
15641 .update_in(cx, |editor, window, cx| {
15642 let tab_kind = match kind {
15643 Some(GotoDefinitionKind::Implementation) => "Implementations",
15644 _ => "Definitions",
15645 };
15646 let title = definitions
15647 .iter()
15648 .find_map(|definition| match definition {
15649 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15650 let buffer = origin.buffer.read(cx);
15651 format!(
15652 "{} for {}",
15653 tab_kind,
15654 buffer
15655 .text_for_range(origin.range.clone())
15656 .collect::<String>()
15657 )
15658 }),
15659 HoverLink::InlayHint(_, _) => None,
15660 HoverLink::Url(_) => None,
15661 HoverLink::File(_) => None,
15662 })
15663 .unwrap_or(tab_kind.to_string());
15664 let location_tasks = definitions
15665 .into_iter()
15666 .map(|definition| match definition {
15667 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15668 HoverLink::InlayHint(lsp_location, server_id) => editor
15669 .compute_target_location(lsp_location, server_id, window, cx),
15670 HoverLink::Url(_) => Task::ready(Ok(None)),
15671 HoverLink::File(_) => Task::ready(Ok(None)),
15672 })
15673 .collect::<Vec<_>>();
15674 (title, location_tasks, editor.workspace().clone())
15675 })
15676 .context("location tasks preparation")?;
15677
15678 let locations: Vec<Location> = future::join_all(location_tasks)
15679 .await
15680 .into_iter()
15681 .filter_map(|location| location.transpose())
15682 .collect::<Result<_>>()
15683 .context("location tasks")?;
15684
15685 if locations.is_empty() {
15686 return Ok(Navigated::No);
15687 }
15688
15689 let Some(workspace) = workspace else {
15690 return Ok(Navigated::No);
15691 };
15692
15693 let opened = workspace
15694 .update_in(cx, |workspace, window, cx| {
15695 Self::open_locations_in_multibuffer(
15696 workspace,
15697 locations,
15698 title,
15699 split,
15700 MultibufferSelectionMode::First,
15701 window,
15702 cx,
15703 )
15704 })
15705 .ok();
15706
15707 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15708 })
15709 } else {
15710 Task::ready(Ok(Navigated::No))
15711 }
15712 }
15713
15714 fn compute_target_location(
15715 &self,
15716 lsp_location: lsp::Location,
15717 server_id: LanguageServerId,
15718 window: &mut Window,
15719 cx: &mut Context<Self>,
15720 ) -> Task<anyhow::Result<Option<Location>>> {
15721 let Some(project) = self.project.clone() else {
15722 return Task::ready(Ok(None));
15723 };
15724
15725 cx.spawn_in(window, async move |editor, cx| {
15726 let location_task = editor.update(cx, |_, cx| {
15727 project.update(cx, |project, cx| {
15728 let language_server_name = project
15729 .language_server_statuses(cx)
15730 .find(|(id, _)| server_id == *id)
15731 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15732 language_server_name.map(|language_server_name| {
15733 project.open_local_buffer_via_lsp(
15734 lsp_location.uri.clone(),
15735 server_id,
15736 language_server_name,
15737 cx,
15738 )
15739 })
15740 })
15741 })?;
15742 let location = match location_task {
15743 Some(task) => Some({
15744 let target_buffer_handle = task.await.context("open local buffer")?;
15745 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15746 let target_start = target_buffer
15747 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15748 let target_end = target_buffer
15749 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15750 target_buffer.anchor_after(target_start)
15751 ..target_buffer.anchor_before(target_end)
15752 })?;
15753 Location {
15754 buffer: target_buffer_handle,
15755 range,
15756 }
15757 }),
15758 None => None,
15759 };
15760 Ok(location)
15761 })
15762 }
15763
15764 pub fn find_all_references(
15765 &mut self,
15766 _: &FindAllReferences,
15767 window: &mut Window,
15768 cx: &mut Context<Self>,
15769 ) -> Option<Task<Result<Navigated>>> {
15770 let selection = self.selections.newest::<usize>(cx);
15771 let multi_buffer = self.buffer.read(cx);
15772 let head = selection.head();
15773
15774 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15775 let head_anchor = multi_buffer_snapshot.anchor_at(
15776 head,
15777 if head < selection.tail() {
15778 Bias::Right
15779 } else {
15780 Bias::Left
15781 },
15782 );
15783
15784 match self
15785 .find_all_references_task_sources
15786 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15787 {
15788 Ok(_) => {
15789 log::info!(
15790 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15791 );
15792 return None;
15793 }
15794 Err(i) => {
15795 self.find_all_references_task_sources.insert(i, head_anchor);
15796 }
15797 }
15798
15799 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15800 let workspace = self.workspace()?;
15801 let project = workspace.read(cx).project().clone();
15802 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15803 Some(cx.spawn_in(window, async move |editor, cx| {
15804 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15805 if let Ok(i) = editor
15806 .find_all_references_task_sources
15807 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15808 {
15809 editor.find_all_references_task_sources.remove(i);
15810 }
15811 });
15812
15813 let locations = references.await?;
15814 if locations.is_empty() {
15815 return anyhow::Ok(Navigated::No);
15816 }
15817
15818 workspace.update_in(cx, |workspace, window, cx| {
15819 let title = locations
15820 .first()
15821 .as_ref()
15822 .map(|location| {
15823 let buffer = location.buffer.read(cx);
15824 format!(
15825 "References to `{}`",
15826 buffer
15827 .text_for_range(location.range.clone())
15828 .collect::<String>()
15829 )
15830 })
15831 .unwrap();
15832 Self::open_locations_in_multibuffer(
15833 workspace,
15834 locations,
15835 title,
15836 false,
15837 MultibufferSelectionMode::First,
15838 window,
15839 cx,
15840 );
15841 Navigated::Yes
15842 })
15843 }))
15844 }
15845
15846 /// Opens a multibuffer with the given project locations in it
15847 pub fn open_locations_in_multibuffer(
15848 workspace: &mut Workspace,
15849 mut locations: Vec<Location>,
15850 title: String,
15851 split: bool,
15852 multibuffer_selection_mode: MultibufferSelectionMode,
15853 window: &mut Window,
15854 cx: &mut Context<Workspace>,
15855 ) {
15856 if locations.is_empty() {
15857 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
15858 return;
15859 }
15860
15861 // If there are multiple definitions, open them in a multibuffer
15862 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15863 let mut locations = locations.into_iter().peekable();
15864 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15865 let capability = workspace.project().read(cx).capability();
15866
15867 let excerpt_buffer = cx.new(|cx| {
15868 let mut multibuffer = MultiBuffer::new(capability);
15869 while let Some(location) = locations.next() {
15870 let buffer = location.buffer.read(cx);
15871 let mut ranges_for_buffer = Vec::new();
15872 let range = location.range.to_point(buffer);
15873 ranges_for_buffer.push(range.clone());
15874
15875 while let Some(next_location) = locations.peek() {
15876 if next_location.buffer == location.buffer {
15877 ranges_for_buffer.push(next_location.range.to_point(buffer));
15878 locations.next();
15879 } else {
15880 break;
15881 }
15882 }
15883
15884 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15885 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15886 PathKey::for_buffer(&location.buffer, cx),
15887 location.buffer.clone(),
15888 ranges_for_buffer,
15889 DEFAULT_MULTIBUFFER_CONTEXT,
15890 cx,
15891 );
15892 ranges.extend(new_ranges)
15893 }
15894
15895 multibuffer.with_title(title)
15896 });
15897
15898 let editor = cx.new(|cx| {
15899 Editor::for_multibuffer(
15900 excerpt_buffer,
15901 Some(workspace.project().clone()),
15902 window,
15903 cx,
15904 )
15905 });
15906 editor.update(cx, |editor, cx| {
15907 match multibuffer_selection_mode {
15908 MultibufferSelectionMode::First => {
15909 if let Some(first_range) = ranges.first() {
15910 editor.change_selections(
15911 SelectionEffects::no_scroll(),
15912 window,
15913 cx,
15914 |selections| {
15915 selections.clear_disjoint();
15916 selections
15917 .select_anchor_ranges(std::iter::once(first_range.clone()));
15918 },
15919 );
15920 }
15921 editor.highlight_background::<Self>(
15922 &ranges,
15923 |theme| theme.colors().editor_highlighted_line_background,
15924 cx,
15925 );
15926 }
15927 MultibufferSelectionMode::All => {
15928 editor.change_selections(
15929 SelectionEffects::no_scroll(),
15930 window,
15931 cx,
15932 |selections| {
15933 selections.clear_disjoint();
15934 selections.select_anchor_ranges(ranges);
15935 },
15936 );
15937 }
15938 }
15939 editor.register_buffers_with_language_servers(cx);
15940 });
15941
15942 let item = Box::new(editor);
15943 let item_id = item.item_id();
15944
15945 if split {
15946 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15947 } else {
15948 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15949 let (preview_item_id, preview_item_idx) =
15950 workspace.active_pane().read_with(cx, |pane, _| {
15951 (pane.preview_item_id(), pane.preview_item_idx())
15952 });
15953
15954 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15955
15956 if let Some(preview_item_id) = preview_item_id {
15957 workspace.active_pane().update(cx, |pane, cx| {
15958 pane.remove_item(preview_item_id, false, false, window, cx);
15959 });
15960 }
15961 } else {
15962 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15963 }
15964 }
15965 workspace.active_pane().update(cx, |pane, cx| {
15966 pane.set_preview_item_id(Some(item_id), cx);
15967 });
15968 }
15969
15970 pub fn rename(
15971 &mut self,
15972 _: &Rename,
15973 window: &mut Window,
15974 cx: &mut Context<Self>,
15975 ) -> Option<Task<Result<()>>> {
15976 use language::ToOffset as _;
15977
15978 let provider = self.semantics_provider.clone()?;
15979 let selection = self.selections.newest_anchor().clone();
15980 let (cursor_buffer, cursor_buffer_position) = self
15981 .buffer
15982 .read(cx)
15983 .text_anchor_for_position(selection.head(), cx)?;
15984 let (tail_buffer, cursor_buffer_position_end) = self
15985 .buffer
15986 .read(cx)
15987 .text_anchor_for_position(selection.tail(), cx)?;
15988 if tail_buffer != cursor_buffer {
15989 return None;
15990 }
15991
15992 let snapshot = cursor_buffer.read(cx).snapshot();
15993 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15994 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15995 let prepare_rename = provider
15996 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15997 .unwrap_or_else(|| Task::ready(Ok(None)));
15998 drop(snapshot);
15999
16000 Some(cx.spawn_in(window, async move |this, cx| {
16001 let rename_range = if let Some(range) = prepare_rename.await? {
16002 Some(range)
16003 } else {
16004 this.update(cx, |this, cx| {
16005 let buffer = this.buffer.read(cx).snapshot(cx);
16006 let mut buffer_highlights = this
16007 .document_highlights_for_position(selection.head(), &buffer)
16008 .filter(|highlight| {
16009 highlight.start.excerpt_id == selection.head().excerpt_id
16010 && highlight.end.excerpt_id == selection.head().excerpt_id
16011 });
16012 buffer_highlights
16013 .next()
16014 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16015 })?
16016 };
16017 if let Some(rename_range) = rename_range {
16018 this.update_in(cx, |this, window, cx| {
16019 let snapshot = cursor_buffer.read(cx).snapshot();
16020 let rename_buffer_range = rename_range.to_offset(&snapshot);
16021 let cursor_offset_in_rename_range =
16022 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16023 let cursor_offset_in_rename_range_end =
16024 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16025
16026 this.take_rename(false, window, cx);
16027 let buffer = this.buffer.read(cx).read(cx);
16028 let cursor_offset = selection.head().to_offset(&buffer);
16029 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16030 let rename_end = rename_start + rename_buffer_range.len();
16031 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16032 let mut old_highlight_id = None;
16033 let old_name: Arc<str> = buffer
16034 .chunks(rename_start..rename_end, true)
16035 .map(|chunk| {
16036 if old_highlight_id.is_none() {
16037 old_highlight_id = chunk.syntax_highlight_id;
16038 }
16039 chunk.text
16040 })
16041 .collect::<String>()
16042 .into();
16043
16044 drop(buffer);
16045
16046 // Position the selection in the rename editor so that it matches the current selection.
16047 this.show_local_selections = false;
16048 let rename_editor = cx.new(|cx| {
16049 let mut editor = Editor::single_line(window, cx);
16050 editor.buffer.update(cx, |buffer, cx| {
16051 buffer.edit([(0..0, old_name.clone())], None, cx)
16052 });
16053 let rename_selection_range = match cursor_offset_in_rename_range
16054 .cmp(&cursor_offset_in_rename_range_end)
16055 {
16056 Ordering::Equal => {
16057 editor.select_all(&SelectAll, window, cx);
16058 return editor;
16059 }
16060 Ordering::Less => {
16061 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16062 }
16063 Ordering::Greater => {
16064 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16065 }
16066 };
16067 if rename_selection_range.end > old_name.len() {
16068 editor.select_all(&SelectAll, window, cx);
16069 } else {
16070 editor.change_selections(Default::default(), window, cx, |s| {
16071 s.select_ranges([rename_selection_range]);
16072 });
16073 }
16074 editor
16075 });
16076 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16077 if e == &EditorEvent::Focused {
16078 cx.emit(EditorEvent::FocusedIn)
16079 }
16080 })
16081 .detach();
16082
16083 let write_highlights =
16084 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16085 let read_highlights =
16086 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16087 let ranges = write_highlights
16088 .iter()
16089 .flat_map(|(_, ranges)| ranges.iter())
16090 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16091 .cloned()
16092 .collect();
16093
16094 this.highlight_text::<Rename>(
16095 ranges,
16096 HighlightStyle {
16097 fade_out: Some(0.6),
16098 ..Default::default()
16099 },
16100 cx,
16101 );
16102 let rename_focus_handle = rename_editor.focus_handle(cx);
16103 window.focus(&rename_focus_handle);
16104 let block_id = this.insert_blocks(
16105 [BlockProperties {
16106 style: BlockStyle::Flex,
16107 placement: BlockPlacement::Below(range.start),
16108 height: Some(1),
16109 render: Arc::new({
16110 let rename_editor = rename_editor.clone();
16111 move |cx: &mut BlockContext| {
16112 let mut text_style = cx.editor_style.text.clone();
16113 if let Some(highlight_style) = old_highlight_id
16114 .and_then(|h| h.style(&cx.editor_style.syntax))
16115 {
16116 text_style = text_style.highlight(highlight_style);
16117 }
16118 div()
16119 .block_mouse_except_scroll()
16120 .pl(cx.anchor_x)
16121 .child(EditorElement::new(
16122 &rename_editor,
16123 EditorStyle {
16124 background: cx.theme().system().transparent,
16125 local_player: cx.editor_style.local_player,
16126 text: text_style,
16127 scrollbar_width: cx.editor_style.scrollbar_width,
16128 syntax: cx.editor_style.syntax.clone(),
16129 status: cx.editor_style.status.clone(),
16130 inlay_hints_style: HighlightStyle {
16131 font_weight: Some(FontWeight::BOLD),
16132 ..make_inlay_hints_style(cx.app)
16133 },
16134 inline_completion_styles: make_suggestion_styles(
16135 cx.app,
16136 ),
16137 ..EditorStyle::default()
16138 },
16139 ))
16140 .into_any_element()
16141 }
16142 }),
16143 priority: 0,
16144 render_in_minimap: true,
16145 }],
16146 Some(Autoscroll::fit()),
16147 cx,
16148 )[0];
16149 this.pending_rename = Some(RenameState {
16150 range,
16151 old_name,
16152 editor: rename_editor,
16153 block_id,
16154 });
16155 })?;
16156 }
16157
16158 Ok(())
16159 }))
16160 }
16161
16162 pub fn confirm_rename(
16163 &mut self,
16164 _: &ConfirmRename,
16165 window: &mut Window,
16166 cx: &mut Context<Self>,
16167 ) -> Option<Task<Result<()>>> {
16168 let rename = self.take_rename(false, window, cx)?;
16169 let workspace = self.workspace()?.downgrade();
16170 let (buffer, start) = self
16171 .buffer
16172 .read(cx)
16173 .text_anchor_for_position(rename.range.start, cx)?;
16174 let (end_buffer, _) = self
16175 .buffer
16176 .read(cx)
16177 .text_anchor_for_position(rename.range.end, cx)?;
16178 if buffer != end_buffer {
16179 return None;
16180 }
16181
16182 let old_name = rename.old_name;
16183 let new_name = rename.editor.read(cx).text(cx);
16184
16185 let rename = self.semantics_provider.as_ref()?.perform_rename(
16186 &buffer,
16187 start,
16188 new_name.clone(),
16189 cx,
16190 )?;
16191
16192 Some(cx.spawn_in(window, async move |editor, cx| {
16193 let project_transaction = rename.await?;
16194 Self::open_project_transaction(
16195 &editor,
16196 workspace,
16197 project_transaction,
16198 format!("Rename: {} → {}", old_name, new_name),
16199 cx,
16200 )
16201 .await?;
16202
16203 editor.update(cx, |editor, cx| {
16204 editor.refresh_document_highlights(cx);
16205 })?;
16206 Ok(())
16207 }))
16208 }
16209
16210 fn take_rename(
16211 &mut self,
16212 moving_cursor: bool,
16213 window: &mut Window,
16214 cx: &mut Context<Self>,
16215 ) -> Option<RenameState> {
16216 let rename = self.pending_rename.take()?;
16217 if rename.editor.focus_handle(cx).is_focused(window) {
16218 window.focus(&self.focus_handle);
16219 }
16220
16221 self.remove_blocks(
16222 [rename.block_id].into_iter().collect(),
16223 Some(Autoscroll::fit()),
16224 cx,
16225 );
16226 self.clear_highlights::<Rename>(cx);
16227 self.show_local_selections = true;
16228
16229 if moving_cursor {
16230 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16231 editor.selections.newest::<usize>(cx).head()
16232 });
16233
16234 // Update the selection to match the position of the selection inside
16235 // the rename editor.
16236 let snapshot = self.buffer.read(cx).read(cx);
16237 let rename_range = rename.range.to_offset(&snapshot);
16238 let cursor_in_editor = snapshot
16239 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16240 .min(rename_range.end);
16241 drop(snapshot);
16242
16243 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16244 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16245 });
16246 } else {
16247 self.refresh_document_highlights(cx);
16248 }
16249
16250 Some(rename)
16251 }
16252
16253 pub fn pending_rename(&self) -> Option<&RenameState> {
16254 self.pending_rename.as_ref()
16255 }
16256
16257 fn format(
16258 &mut self,
16259 _: &Format,
16260 window: &mut Window,
16261 cx: &mut Context<Self>,
16262 ) -> Option<Task<Result<()>>> {
16263 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16264
16265 let project = match &self.project {
16266 Some(project) => project.clone(),
16267 None => return None,
16268 };
16269
16270 Some(self.perform_format(
16271 project,
16272 FormatTrigger::Manual,
16273 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16274 window,
16275 cx,
16276 ))
16277 }
16278
16279 fn format_selections(
16280 &mut self,
16281 _: &FormatSelections,
16282 window: &mut Window,
16283 cx: &mut Context<Self>,
16284 ) -> Option<Task<Result<()>>> {
16285 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16286
16287 let project = match &self.project {
16288 Some(project) => project.clone(),
16289 None => return None,
16290 };
16291
16292 let ranges = self
16293 .selections
16294 .all_adjusted(cx)
16295 .into_iter()
16296 .map(|selection| selection.range())
16297 .collect_vec();
16298
16299 Some(self.perform_format(
16300 project,
16301 FormatTrigger::Manual,
16302 FormatTarget::Ranges(ranges),
16303 window,
16304 cx,
16305 ))
16306 }
16307
16308 fn perform_format(
16309 &mut self,
16310 project: Entity<Project>,
16311 trigger: FormatTrigger,
16312 target: FormatTarget,
16313 window: &mut Window,
16314 cx: &mut Context<Self>,
16315 ) -> Task<Result<()>> {
16316 let buffer = self.buffer.clone();
16317 let (buffers, target) = match target {
16318 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16319 FormatTarget::Ranges(selection_ranges) => {
16320 let multi_buffer = buffer.read(cx);
16321 let snapshot = multi_buffer.read(cx);
16322 let mut buffers = HashSet::default();
16323 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16324 BTreeMap::new();
16325 for selection_range in selection_ranges {
16326 for (buffer, buffer_range, _) in
16327 snapshot.range_to_buffer_ranges(selection_range)
16328 {
16329 let buffer_id = buffer.remote_id();
16330 let start = buffer.anchor_before(buffer_range.start);
16331 let end = buffer.anchor_after(buffer_range.end);
16332 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16333 buffer_id_to_ranges
16334 .entry(buffer_id)
16335 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16336 .or_insert_with(|| vec![start..end]);
16337 }
16338 }
16339 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16340 }
16341 };
16342
16343 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16344 let selections_prev = transaction_id_prev
16345 .and_then(|transaction_id_prev| {
16346 // default to selections as they were after the last edit, if we have them,
16347 // instead of how they are now.
16348 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16349 // will take you back to where you made the last edit, instead of staying where you scrolled
16350 self.selection_history
16351 .transaction(transaction_id_prev)
16352 .map(|t| t.0.clone())
16353 })
16354 .unwrap_or_else(|| {
16355 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16356 self.selections.disjoint_anchors()
16357 });
16358
16359 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16360 let format = project.update(cx, |project, cx| {
16361 project.format(buffers, target, true, trigger, cx)
16362 });
16363
16364 cx.spawn_in(window, async move |editor, cx| {
16365 let transaction = futures::select_biased! {
16366 transaction = format.log_err().fuse() => transaction,
16367 () = timeout => {
16368 log::warn!("timed out waiting for formatting");
16369 None
16370 }
16371 };
16372
16373 buffer
16374 .update(cx, |buffer, cx| {
16375 if let Some(transaction) = transaction {
16376 if !buffer.is_singleton() {
16377 buffer.push_transaction(&transaction.0, cx);
16378 }
16379 }
16380 cx.notify();
16381 })
16382 .ok();
16383
16384 if let Some(transaction_id_now) =
16385 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16386 {
16387 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16388 if has_new_transaction {
16389 _ = editor.update(cx, |editor, _| {
16390 editor
16391 .selection_history
16392 .insert_transaction(transaction_id_now, selections_prev);
16393 });
16394 }
16395 }
16396
16397 Ok(())
16398 })
16399 }
16400
16401 fn organize_imports(
16402 &mut self,
16403 _: &OrganizeImports,
16404 window: &mut Window,
16405 cx: &mut Context<Self>,
16406 ) -> Option<Task<Result<()>>> {
16407 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16408 let project = match &self.project {
16409 Some(project) => project.clone(),
16410 None => return None,
16411 };
16412 Some(self.perform_code_action_kind(
16413 project,
16414 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16415 window,
16416 cx,
16417 ))
16418 }
16419
16420 fn perform_code_action_kind(
16421 &mut self,
16422 project: Entity<Project>,
16423 kind: CodeActionKind,
16424 window: &mut Window,
16425 cx: &mut Context<Self>,
16426 ) -> Task<Result<()>> {
16427 let buffer = self.buffer.clone();
16428 let buffers = buffer.read(cx).all_buffers();
16429 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16430 let apply_action = project.update(cx, |project, cx| {
16431 project.apply_code_action_kind(buffers, kind, true, cx)
16432 });
16433 cx.spawn_in(window, async move |_, cx| {
16434 let transaction = futures::select_biased! {
16435 () = timeout => {
16436 log::warn!("timed out waiting for executing code action");
16437 None
16438 }
16439 transaction = apply_action.log_err().fuse() => transaction,
16440 };
16441 buffer
16442 .update(cx, |buffer, cx| {
16443 // check if we need this
16444 if let Some(transaction) = transaction {
16445 if !buffer.is_singleton() {
16446 buffer.push_transaction(&transaction.0, cx);
16447 }
16448 }
16449 cx.notify();
16450 })
16451 .ok();
16452 Ok(())
16453 })
16454 }
16455
16456 pub fn restart_language_server(
16457 &mut self,
16458 _: &RestartLanguageServer,
16459 _: &mut Window,
16460 cx: &mut Context<Self>,
16461 ) {
16462 if let Some(project) = self.project.clone() {
16463 self.buffer.update(cx, |multi_buffer, cx| {
16464 project.update(cx, |project, cx| {
16465 project.restart_language_servers_for_buffers(
16466 multi_buffer.all_buffers().into_iter().collect(),
16467 HashSet::default(),
16468 cx,
16469 );
16470 });
16471 })
16472 }
16473 }
16474
16475 pub fn stop_language_server(
16476 &mut self,
16477 _: &StopLanguageServer,
16478 _: &mut Window,
16479 cx: &mut Context<Self>,
16480 ) {
16481 if let Some(project) = self.project.clone() {
16482 self.buffer.update(cx, |multi_buffer, cx| {
16483 project.update(cx, |project, cx| {
16484 project.stop_language_servers_for_buffers(
16485 multi_buffer.all_buffers().into_iter().collect(),
16486 HashSet::default(),
16487 cx,
16488 );
16489 cx.emit(project::Event::RefreshInlayHints);
16490 });
16491 });
16492 }
16493 }
16494
16495 fn cancel_language_server_work(
16496 workspace: &mut Workspace,
16497 _: &actions::CancelLanguageServerWork,
16498 _: &mut Window,
16499 cx: &mut Context<Workspace>,
16500 ) {
16501 let project = workspace.project();
16502 let buffers = workspace
16503 .active_item(cx)
16504 .and_then(|item| item.act_as::<Editor>(cx))
16505 .map_or(HashSet::default(), |editor| {
16506 editor.read(cx).buffer.read(cx).all_buffers()
16507 });
16508 project.update(cx, |project, cx| {
16509 project.cancel_language_server_work_for_buffers(buffers, cx);
16510 });
16511 }
16512
16513 fn show_character_palette(
16514 &mut self,
16515 _: &ShowCharacterPalette,
16516 window: &mut Window,
16517 _: &mut Context<Self>,
16518 ) {
16519 window.show_character_palette();
16520 }
16521
16522 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16523 if !self.diagnostics_enabled() {
16524 return;
16525 }
16526
16527 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16528 let buffer = self.buffer.read(cx).snapshot(cx);
16529 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16530 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16531 let is_valid = buffer
16532 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16533 .any(|entry| {
16534 entry.diagnostic.is_primary
16535 && !entry.range.is_empty()
16536 && entry.range.start == primary_range_start
16537 && entry.diagnostic.message == active_diagnostics.active_message
16538 });
16539
16540 if !is_valid {
16541 self.dismiss_diagnostics(cx);
16542 }
16543 }
16544 }
16545
16546 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16547 match &self.active_diagnostics {
16548 ActiveDiagnostic::Group(group) => Some(group),
16549 _ => None,
16550 }
16551 }
16552
16553 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16554 if !self.diagnostics_enabled() {
16555 return;
16556 }
16557 self.dismiss_diagnostics(cx);
16558 self.active_diagnostics = ActiveDiagnostic::All;
16559 }
16560
16561 fn activate_diagnostics(
16562 &mut self,
16563 buffer_id: BufferId,
16564 diagnostic: DiagnosticEntry<usize>,
16565 window: &mut Window,
16566 cx: &mut Context<Self>,
16567 ) {
16568 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16569 return;
16570 }
16571 self.dismiss_diagnostics(cx);
16572 let snapshot = self.snapshot(window, cx);
16573 let buffer = self.buffer.read(cx).snapshot(cx);
16574 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16575 return;
16576 };
16577
16578 let diagnostic_group = buffer
16579 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16580 .collect::<Vec<_>>();
16581
16582 let blocks =
16583 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16584
16585 let blocks = self.display_map.update(cx, |display_map, cx| {
16586 display_map.insert_blocks(blocks, cx).into_iter().collect()
16587 });
16588 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16589 active_range: buffer.anchor_before(diagnostic.range.start)
16590 ..buffer.anchor_after(diagnostic.range.end),
16591 active_message: diagnostic.diagnostic.message.clone(),
16592 group_id: diagnostic.diagnostic.group_id,
16593 blocks,
16594 });
16595 cx.notify();
16596 }
16597
16598 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16599 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16600 return;
16601 };
16602
16603 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16604 if let ActiveDiagnostic::Group(group) = prev {
16605 self.display_map.update(cx, |display_map, cx| {
16606 display_map.remove_blocks(group.blocks, cx);
16607 });
16608 cx.notify();
16609 }
16610 }
16611
16612 /// Disable inline diagnostics rendering for this editor.
16613 pub fn disable_inline_diagnostics(&mut self) {
16614 self.inline_diagnostics_enabled = false;
16615 self.inline_diagnostics_update = Task::ready(());
16616 self.inline_diagnostics.clear();
16617 }
16618
16619 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16620 self.diagnostics_enabled = false;
16621 self.dismiss_diagnostics(cx);
16622 self.inline_diagnostics_update = Task::ready(());
16623 self.inline_diagnostics.clear();
16624 }
16625
16626 pub fn diagnostics_enabled(&self) -> bool {
16627 self.diagnostics_enabled && self.mode.is_full()
16628 }
16629
16630 pub fn inline_diagnostics_enabled(&self) -> bool {
16631 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16632 }
16633
16634 pub fn show_inline_diagnostics(&self) -> bool {
16635 self.show_inline_diagnostics
16636 }
16637
16638 pub fn toggle_inline_diagnostics(
16639 &mut self,
16640 _: &ToggleInlineDiagnostics,
16641 window: &mut Window,
16642 cx: &mut Context<Editor>,
16643 ) {
16644 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16645 self.refresh_inline_diagnostics(false, window, cx);
16646 }
16647
16648 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16649 self.diagnostics_max_severity = severity;
16650 self.display_map.update(cx, |display_map, _| {
16651 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16652 });
16653 }
16654
16655 pub fn toggle_diagnostics(
16656 &mut self,
16657 _: &ToggleDiagnostics,
16658 window: &mut Window,
16659 cx: &mut Context<Editor>,
16660 ) {
16661 if !self.diagnostics_enabled() {
16662 return;
16663 }
16664
16665 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16666 EditorSettings::get_global(cx)
16667 .diagnostics_max_severity
16668 .filter(|severity| severity != &DiagnosticSeverity::Off)
16669 .unwrap_or(DiagnosticSeverity::Hint)
16670 } else {
16671 DiagnosticSeverity::Off
16672 };
16673 self.set_max_diagnostics_severity(new_severity, cx);
16674 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16675 self.active_diagnostics = ActiveDiagnostic::None;
16676 self.inline_diagnostics_update = Task::ready(());
16677 self.inline_diagnostics.clear();
16678 } else {
16679 self.refresh_inline_diagnostics(false, window, cx);
16680 }
16681
16682 cx.notify();
16683 }
16684
16685 pub fn toggle_minimap(
16686 &mut self,
16687 _: &ToggleMinimap,
16688 window: &mut Window,
16689 cx: &mut Context<Editor>,
16690 ) {
16691 if self.supports_minimap(cx) {
16692 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16693 }
16694 }
16695
16696 fn refresh_inline_diagnostics(
16697 &mut self,
16698 debounce: bool,
16699 window: &mut Window,
16700 cx: &mut Context<Self>,
16701 ) {
16702 let max_severity = ProjectSettings::get_global(cx)
16703 .diagnostics
16704 .inline
16705 .max_severity
16706 .unwrap_or(self.diagnostics_max_severity);
16707
16708 if !self.inline_diagnostics_enabled()
16709 || !self.show_inline_diagnostics
16710 || max_severity == DiagnosticSeverity::Off
16711 {
16712 self.inline_diagnostics_update = Task::ready(());
16713 self.inline_diagnostics.clear();
16714 return;
16715 }
16716
16717 let debounce_ms = ProjectSettings::get_global(cx)
16718 .diagnostics
16719 .inline
16720 .update_debounce_ms;
16721 let debounce = if debounce && debounce_ms > 0 {
16722 Some(Duration::from_millis(debounce_ms))
16723 } else {
16724 None
16725 };
16726 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16727 if let Some(debounce) = debounce {
16728 cx.background_executor().timer(debounce).await;
16729 }
16730 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16731 editor
16732 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16733 .ok()
16734 }) else {
16735 return;
16736 };
16737
16738 let new_inline_diagnostics = cx
16739 .background_spawn(async move {
16740 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16741 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16742 let message = diagnostic_entry
16743 .diagnostic
16744 .message
16745 .split_once('\n')
16746 .map(|(line, _)| line)
16747 .map(SharedString::new)
16748 .unwrap_or_else(|| {
16749 SharedString::from(diagnostic_entry.diagnostic.message)
16750 });
16751 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16752 let (Ok(i) | Err(i)) = inline_diagnostics
16753 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16754 inline_diagnostics.insert(
16755 i,
16756 (
16757 start_anchor,
16758 InlineDiagnostic {
16759 message,
16760 group_id: diagnostic_entry.diagnostic.group_id,
16761 start: diagnostic_entry.range.start.to_point(&snapshot),
16762 is_primary: diagnostic_entry.diagnostic.is_primary,
16763 severity: diagnostic_entry.diagnostic.severity,
16764 },
16765 ),
16766 );
16767 }
16768 inline_diagnostics
16769 })
16770 .await;
16771
16772 editor
16773 .update(cx, |editor, cx| {
16774 editor.inline_diagnostics = new_inline_diagnostics;
16775 cx.notify();
16776 })
16777 .ok();
16778 });
16779 }
16780
16781 fn pull_diagnostics(
16782 &mut self,
16783 buffer_id: Option<BufferId>,
16784 window: &Window,
16785 cx: &mut Context<Self>,
16786 ) -> Option<()> {
16787 if !self.mode().is_full() {
16788 return None;
16789 }
16790 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16791 .diagnostics
16792 .lsp_pull_diagnostics;
16793 if !pull_diagnostics_settings.enabled {
16794 return None;
16795 }
16796 let project = self.project.as_ref()?.downgrade();
16797 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16798 let mut buffers = self.buffer.read(cx).all_buffers();
16799 if let Some(buffer_id) = buffer_id {
16800 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16801 }
16802
16803 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16804 cx.background_executor().timer(debounce).await;
16805
16806 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16807 buffers
16808 .into_iter()
16809 .filter_map(|buffer| {
16810 project
16811 .update(cx, |project, cx| {
16812 project.lsp_store().update(cx, |lsp_store, cx| {
16813 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
16814 })
16815 })
16816 .ok()
16817 })
16818 .collect::<FuturesUnordered<_>>()
16819 }) else {
16820 return;
16821 };
16822
16823 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16824 match pull_task {
16825 Ok(()) => {
16826 if editor
16827 .update_in(cx, |editor, window, cx| {
16828 editor.update_diagnostics_state(window, cx);
16829 })
16830 .is_err()
16831 {
16832 return;
16833 }
16834 }
16835 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16836 }
16837 }
16838 });
16839
16840 Some(())
16841 }
16842
16843 pub fn set_selections_from_remote(
16844 &mut self,
16845 selections: Vec<Selection<Anchor>>,
16846 pending_selection: Option<Selection<Anchor>>,
16847 window: &mut Window,
16848 cx: &mut Context<Self>,
16849 ) {
16850 let old_cursor_position = self.selections.newest_anchor().head();
16851 self.selections.change_with(cx, |s| {
16852 s.select_anchors(selections);
16853 if let Some(pending_selection) = pending_selection {
16854 s.set_pending(pending_selection, SelectMode::Character);
16855 } else {
16856 s.clear_pending();
16857 }
16858 });
16859 self.selections_did_change(
16860 false,
16861 &old_cursor_position,
16862 SelectionEffects::default(),
16863 window,
16864 cx,
16865 );
16866 }
16867
16868 pub fn transact(
16869 &mut self,
16870 window: &mut Window,
16871 cx: &mut Context<Self>,
16872 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16873 ) -> Option<TransactionId> {
16874 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16875 this.start_transaction_at(Instant::now(), window, cx);
16876 update(this, window, cx);
16877 this.end_transaction_at(Instant::now(), cx)
16878 })
16879 }
16880
16881 pub fn start_transaction_at(
16882 &mut self,
16883 now: Instant,
16884 window: &mut Window,
16885 cx: &mut Context<Self>,
16886 ) {
16887 self.end_selection(window, cx);
16888 if let Some(tx_id) = self
16889 .buffer
16890 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16891 {
16892 self.selection_history
16893 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16894 cx.emit(EditorEvent::TransactionBegun {
16895 transaction_id: tx_id,
16896 })
16897 }
16898 }
16899
16900 pub fn end_transaction_at(
16901 &mut self,
16902 now: Instant,
16903 cx: &mut Context<Self>,
16904 ) -> Option<TransactionId> {
16905 if let Some(transaction_id) = self
16906 .buffer
16907 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16908 {
16909 if let Some((_, end_selections)) =
16910 self.selection_history.transaction_mut(transaction_id)
16911 {
16912 *end_selections = Some(self.selections.disjoint_anchors());
16913 } else {
16914 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16915 }
16916
16917 cx.emit(EditorEvent::Edited { transaction_id });
16918 Some(transaction_id)
16919 } else {
16920 None
16921 }
16922 }
16923
16924 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16925 if self.selection_mark_mode {
16926 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16927 s.move_with(|_, sel| {
16928 sel.collapse_to(sel.head(), SelectionGoal::None);
16929 });
16930 })
16931 }
16932 self.selection_mark_mode = true;
16933 cx.notify();
16934 }
16935
16936 pub fn swap_selection_ends(
16937 &mut self,
16938 _: &actions::SwapSelectionEnds,
16939 window: &mut Window,
16940 cx: &mut Context<Self>,
16941 ) {
16942 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16943 s.move_with(|_, sel| {
16944 if sel.start != sel.end {
16945 sel.reversed = !sel.reversed
16946 }
16947 });
16948 });
16949 self.request_autoscroll(Autoscroll::newest(), cx);
16950 cx.notify();
16951 }
16952
16953 pub fn toggle_fold(
16954 &mut self,
16955 _: &actions::ToggleFold,
16956 window: &mut Window,
16957 cx: &mut Context<Self>,
16958 ) {
16959 if self.is_singleton(cx) {
16960 let selection = self.selections.newest::<Point>(cx);
16961
16962 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16963 let range = if selection.is_empty() {
16964 let point = selection.head().to_display_point(&display_map);
16965 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16966 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16967 .to_point(&display_map);
16968 start..end
16969 } else {
16970 selection.range()
16971 };
16972 if display_map.folds_in_range(range).next().is_some() {
16973 self.unfold_lines(&Default::default(), window, cx)
16974 } else {
16975 self.fold(&Default::default(), window, cx)
16976 }
16977 } else {
16978 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16979 let buffer_ids: HashSet<_> = self
16980 .selections
16981 .disjoint_anchor_ranges()
16982 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16983 .collect();
16984
16985 let should_unfold = buffer_ids
16986 .iter()
16987 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16988
16989 for buffer_id in buffer_ids {
16990 if should_unfold {
16991 self.unfold_buffer(buffer_id, cx);
16992 } else {
16993 self.fold_buffer(buffer_id, cx);
16994 }
16995 }
16996 }
16997 }
16998
16999 pub fn toggle_fold_recursive(
17000 &mut self,
17001 _: &actions::ToggleFoldRecursive,
17002 window: &mut Window,
17003 cx: &mut Context<Self>,
17004 ) {
17005 let selection = self.selections.newest::<Point>(cx);
17006
17007 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17008 let range = if selection.is_empty() {
17009 let point = selection.head().to_display_point(&display_map);
17010 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17011 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17012 .to_point(&display_map);
17013 start..end
17014 } else {
17015 selection.range()
17016 };
17017 if display_map.folds_in_range(range).next().is_some() {
17018 self.unfold_recursive(&Default::default(), window, cx)
17019 } else {
17020 self.fold_recursive(&Default::default(), window, cx)
17021 }
17022 }
17023
17024 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17025 if self.is_singleton(cx) {
17026 let mut to_fold = Vec::new();
17027 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17028 let selections = self.selections.all_adjusted(cx);
17029
17030 for selection in selections {
17031 let range = selection.range().sorted();
17032 let buffer_start_row = range.start.row;
17033
17034 if range.start.row != range.end.row {
17035 let mut found = false;
17036 let mut row = range.start.row;
17037 while row <= range.end.row {
17038 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17039 {
17040 found = true;
17041 row = crease.range().end.row + 1;
17042 to_fold.push(crease);
17043 } else {
17044 row += 1
17045 }
17046 }
17047 if found {
17048 continue;
17049 }
17050 }
17051
17052 for row in (0..=range.start.row).rev() {
17053 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17054 if crease.range().end.row >= buffer_start_row {
17055 to_fold.push(crease);
17056 if row <= range.start.row {
17057 break;
17058 }
17059 }
17060 }
17061 }
17062 }
17063
17064 self.fold_creases(to_fold, true, window, cx);
17065 } else {
17066 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17067 let buffer_ids = self
17068 .selections
17069 .disjoint_anchor_ranges()
17070 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17071 .collect::<HashSet<_>>();
17072 for buffer_id in buffer_ids {
17073 self.fold_buffer(buffer_id, cx);
17074 }
17075 }
17076 }
17077
17078 fn fold_at_level(
17079 &mut self,
17080 fold_at: &FoldAtLevel,
17081 window: &mut Window,
17082 cx: &mut Context<Self>,
17083 ) {
17084 if !self.buffer.read(cx).is_singleton() {
17085 return;
17086 }
17087
17088 let fold_at_level = fold_at.0;
17089 let snapshot = self.buffer.read(cx).snapshot(cx);
17090 let mut to_fold = Vec::new();
17091 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17092
17093 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17094 while start_row < end_row {
17095 match self
17096 .snapshot(window, cx)
17097 .crease_for_buffer_row(MultiBufferRow(start_row))
17098 {
17099 Some(crease) => {
17100 let nested_start_row = crease.range().start.row + 1;
17101 let nested_end_row = crease.range().end.row;
17102
17103 if current_level < fold_at_level {
17104 stack.push((nested_start_row, nested_end_row, current_level + 1));
17105 } else if current_level == fold_at_level {
17106 to_fold.push(crease);
17107 }
17108
17109 start_row = nested_end_row + 1;
17110 }
17111 None => start_row += 1,
17112 }
17113 }
17114 }
17115
17116 self.fold_creases(to_fold, true, window, cx);
17117 }
17118
17119 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17120 if self.buffer.read(cx).is_singleton() {
17121 let mut fold_ranges = Vec::new();
17122 let snapshot = self.buffer.read(cx).snapshot(cx);
17123
17124 for row in 0..snapshot.max_row().0 {
17125 if let Some(foldable_range) = self
17126 .snapshot(window, cx)
17127 .crease_for_buffer_row(MultiBufferRow(row))
17128 {
17129 fold_ranges.push(foldable_range);
17130 }
17131 }
17132
17133 self.fold_creases(fold_ranges, true, window, cx);
17134 } else {
17135 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17136 editor
17137 .update_in(cx, |editor, _, cx| {
17138 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17139 editor.fold_buffer(buffer_id, cx);
17140 }
17141 })
17142 .ok();
17143 });
17144 }
17145 }
17146
17147 pub fn fold_function_bodies(
17148 &mut self,
17149 _: &actions::FoldFunctionBodies,
17150 window: &mut Window,
17151 cx: &mut Context<Self>,
17152 ) {
17153 let snapshot = self.buffer.read(cx).snapshot(cx);
17154
17155 let ranges = snapshot
17156 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17157 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17158 .collect::<Vec<_>>();
17159
17160 let creases = ranges
17161 .into_iter()
17162 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17163 .collect();
17164
17165 self.fold_creases(creases, true, window, cx);
17166 }
17167
17168 pub fn fold_recursive(
17169 &mut self,
17170 _: &actions::FoldRecursive,
17171 window: &mut Window,
17172 cx: &mut Context<Self>,
17173 ) {
17174 let mut to_fold = Vec::new();
17175 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17176 let selections = self.selections.all_adjusted(cx);
17177
17178 for selection in selections {
17179 let range = selection.range().sorted();
17180 let buffer_start_row = range.start.row;
17181
17182 if range.start.row != range.end.row {
17183 let mut found = false;
17184 for row in range.start.row..=range.end.row {
17185 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17186 found = true;
17187 to_fold.push(crease);
17188 }
17189 }
17190 if found {
17191 continue;
17192 }
17193 }
17194
17195 for row in (0..=range.start.row).rev() {
17196 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17197 if crease.range().end.row >= buffer_start_row {
17198 to_fold.push(crease);
17199 } else {
17200 break;
17201 }
17202 }
17203 }
17204 }
17205
17206 self.fold_creases(to_fold, true, window, cx);
17207 }
17208
17209 pub fn fold_at(
17210 &mut self,
17211 buffer_row: MultiBufferRow,
17212 window: &mut Window,
17213 cx: &mut Context<Self>,
17214 ) {
17215 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17216
17217 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17218 let autoscroll = self
17219 .selections
17220 .all::<Point>(cx)
17221 .iter()
17222 .any(|selection| crease.range().overlaps(&selection.range()));
17223
17224 self.fold_creases(vec![crease], autoscroll, window, cx);
17225 }
17226 }
17227
17228 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17229 if self.is_singleton(cx) {
17230 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17231 let buffer = &display_map.buffer_snapshot;
17232 let selections = self.selections.all::<Point>(cx);
17233 let ranges = selections
17234 .iter()
17235 .map(|s| {
17236 let range = s.display_range(&display_map).sorted();
17237 let mut start = range.start.to_point(&display_map);
17238 let mut end = range.end.to_point(&display_map);
17239 start.column = 0;
17240 end.column = buffer.line_len(MultiBufferRow(end.row));
17241 start..end
17242 })
17243 .collect::<Vec<_>>();
17244
17245 self.unfold_ranges(&ranges, true, true, cx);
17246 } else {
17247 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17248 let buffer_ids = self
17249 .selections
17250 .disjoint_anchor_ranges()
17251 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17252 .collect::<HashSet<_>>();
17253 for buffer_id in buffer_ids {
17254 self.unfold_buffer(buffer_id, cx);
17255 }
17256 }
17257 }
17258
17259 pub fn unfold_recursive(
17260 &mut self,
17261 _: &UnfoldRecursive,
17262 _window: &mut Window,
17263 cx: &mut Context<Self>,
17264 ) {
17265 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17266 let selections = self.selections.all::<Point>(cx);
17267 let ranges = selections
17268 .iter()
17269 .map(|s| {
17270 let mut range = s.display_range(&display_map).sorted();
17271 *range.start.column_mut() = 0;
17272 *range.end.column_mut() = display_map.line_len(range.end.row());
17273 let start = range.start.to_point(&display_map);
17274 let end = range.end.to_point(&display_map);
17275 start..end
17276 })
17277 .collect::<Vec<_>>();
17278
17279 self.unfold_ranges(&ranges, true, true, cx);
17280 }
17281
17282 pub fn unfold_at(
17283 &mut self,
17284 buffer_row: MultiBufferRow,
17285 _window: &mut Window,
17286 cx: &mut Context<Self>,
17287 ) {
17288 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17289
17290 let intersection_range = Point::new(buffer_row.0, 0)
17291 ..Point::new(
17292 buffer_row.0,
17293 display_map.buffer_snapshot.line_len(buffer_row),
17294 );
17295
17296 let autoscroll = self
17297 .selections
17298 .all::<Point>(cx)
17299 .iter()
17300 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17301
17302 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17303 }
17304
17305 pub fn unfold_all(
17306 &mut self,
17307 _: &actions::UnfoldAll,
17308 _window: &mut Window,
17309 cx: &mut Context<Self>,
17310 ) {
17311 if self.buffer.read(cx).is_singleton() {
17312 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17313 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17314 } else {
17315 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17316 editor
17317 .update(cx, |editor, cx| {
17318 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17319 editor.unfold_buffer(buffer_id, cx);
17320 }
17321 })
17322 .ok();
17323 });
17324 }
17325 }
17326
17327 pub fn fold_selected_ranges(
17328 &mut self,
17329 _: &FoldSelectedRanges,
17330 window: &mut Window,
17331 cx: &mut Context<Self>,
17332 ) {
17333 let selections = self.selections.all_adjusted(cx);
17334 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17335 let ranges = selections
17336 .into_iter()
17337 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17338 .collect::<Vec<_>>();
17339 self.fold_creases(ranges, true, window, cx);
17340 }
17341
17342 pub fn fold_ranges<T: ToOffset + Clone>(
17343 &mut self,
17344 ranges: Vec<Range<T>>,
17345 auto_scroll: bool,
17346 window: &mut Window,
17347 cx: &mut Context<Self>,
17348 ) {
17349 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17350 let ranges = ranges
17351 .into_iter()
17352 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17353 .collect::<Vec<_>>();
17354 self.fold_creases(ranges, auto_scroll, window, cx);
17355 }
17356
17357 pub fn fold_creases<T: ToOffset + Clone>(
17358 &mut self,
17359 creases: Vec<Crease<T>>,
17360 auto_scroll: bool,
17361 _window: &mut Window,
17362 cx: &mut Context<Self>,
17363 ) {
17364 if creases.is_empty() {
17365 return;
17366 }
17367
17368 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17369
17370 if auto_scroll {
17371 self.request_autoscroll(Autoscroll::fit(), cx);
17372 }
17373
17374 cx.notify();
17375
17376 self.scrollbar_marker_state.dirty = true;
17377 self.folds_did_change(cx);
17378 }
17379
17380 /// Removes any folds whose ranges intersect any of the given ranges.
17381 pub fn unfold_ranges<T: ToOffset + Clone>(
17382 &mut self,
17383 ranges: &[Range<T>],
17384 inclusive: bool,
17385 auto_scroll: bool,
17386 cx: &mut Context<Self>,
17387 ) {
17388 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17389 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17390 });
17391 self.folds_did_change(cx);
17392 }
17393
17394 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17395 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17396 return;
17397 }
17398 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17399 self.display_map.update(cx, |display_map, cx| {
17400 display_map.fold_buffers([buffer_id], cx)
17401 });
17402 cx.emit(EditorEvent::BufferFoldToggled {
17403 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17404 folded: true,
17405 });
17406 cx.notify();
17407 }
17408
17409 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17410 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17411 return;
17412 }
17413 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17414 self.display_map.update(cx, |display_map, cx| {
17415 display_map.unfold_buffers([buffer_id], cx);
17416 });
17417 cx.emit(EditorEvent::BufferFoldToggled {
17418 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17419 folded: false,
17420 });
17421 cx.notify();
17422 }
17423
17424 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17425 self.display_map.read(cx).is_buffer_folded(buffer)
17426 }
17427
17428 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17429 self.display_map.read(cx).folded_buffers()
17430 }
17431
17432 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17433 self.display_map.update(cx, |display_map, cx| {
17434 display_map.disable_header_for_buffer(buffer_id, cx);
17435 });
17436 cx.notify();
17437 }
17438
17439 /// Removes any folds with the given ranges.
17440 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17441 &mut self,
17442 ranges: &[Range<T>],
17443 type_id: TypeId,
17444 auto_scroll: bool,
17445 cx: &mut Context<Self>,
17446 ) {
17447 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17448 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17449 });
17450 self.folds_did_change(cx);
17451 }
17452
17453 fn remove_folds_with<T: ToOffset + Clone>(
17454 &mut self,
17455 ranges: &[Range<T>],
17456 auto_scroll: bool,
17457 cx: &mut Context<Self>,
17458 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17459 ) {
17460 if ranges.is_empty() {
17461 return;
17462 }
17463
17464 let mut buffers_affected = HashSet::default();
17465 let multi_buffer = self.buffer().read(cx);
17466 for range in ranges {
17467 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17468 buffers_affected.insert(buffer.read(cx).remote_id());
17469 };
17470 }
17471
17472 self.display_map.update(cx, update);
17473
17474 if auto_scroll {
17475 self.request_autoscroll(Autoscroll::fit(), cx);
17476 }
17477
17478 cx.notify();
17479 self.scrollbar_marker_state.dirty = true;
17480 self.active_indent_guides_state.dirty = true;
17481 }
17482
17483 pub fn update_renderer_widths(
17484 &mut self,
17485 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17486 cx: &mut Context<Self>,
17487 ) -> bool {
17488 self.display_map
17489 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17490 }
17491
17492 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17493 self.display_map.read(cx).fold_placeholder.clone()
17494 }
17495
17496 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17497 self.buffer.update(cx, |buffer, cx| {
17498 buffer.set_all_diff_hunks_expanded(cx);
17499 });
17500 }
17501
17502 pub fn expand_all_diff_hunks(
17503 &mut self,
17504 _: &ExpandAllDiffHunks,
17505 _window: &mut Window,
17506 cx: &mut Context<Self>,
17507 ) {
17508 self.buffer.update(cx, |buffer, cx| {
17509 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17510 });
17511 }
17512
17513 pub fn toggle_selected_diff_hunks(
17514 &mut self,
17515 _: &ToggleSelectedDiffHunks,
17516 _window: &mut Window,
17517 cx: &mut Context<Self>,
17518 ) {
17519 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17520 self.toggle_diff_hunks_in_ranges(ranges, cx);
17521 }
17522
17523 pub fn diff_hunks_in_ranges<'a>(
17524 &'a self,
17525 ranges: &'a [Range<Anchor>],
17526 buffer: &'a MultiBufferSnapshot,
17527 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17528 ranges.iter().flat_map(move |range| {
17529 let end_excerpt_id = range.end.excerpt_id;
17530 let range = range.to_point(buffer);
17531 let mut peek_end = range.end;
17532 if range.end.row < buffer.max_row().0 {
17533 peek_end = Point::new(range.end.row + 1, 0);
17534 }
17535 buffer
17536 .diff_hunks_in_range(range.start..peek_end)
17537 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17538 })
17539 }
17540
17541 pub fn has_stageable_diff_hunks_in_ranges(
17542 &self,
17543 ranges: &[Range<Anchor>],
17544 snapshot: &MultiBufferSnapshot,
17545 ) -> bool {
17546 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17547 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17548 }
17549
17550 pub fn toggle_staged_selected_diff_hunks(
17551 &mut self,
17552 _: &::git::ToggleStaged,
17553 _: &mut Window,
17554 cx: &mut Context<Self>,
17555 ) {
17556 let snapshot = self.buffer.read(cx).snapshot(cx);
17557 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17558 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17559 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17560 }
17561
17562 pub fn set_render_diff_hunk_controls(
17563 &mut self,
17564 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17565 cx: &mut Context<Self>,
17566 ) {
17567 self.render_diff_hunk_controls = render_diff_hunk_controls;
17568 cx.notify();
17569 }
17570
17571 pub fn stage_and_next(
17572 &mut self,
17573 _: &::git::StageAndNext,
17574 window: &mut Window,
17575 cx: &mut Context<Self>,
17576 ) {
17577 self.do_stage_or_unstage_and_next(true, window, cx);
17578 }
17579
17580 pub fn unstage_and_next(
17581 &mut self,
17582 _: &::git::UnstageAndNext,
17583 window: &mut Window,
17584 cx: &mut Context<Self>,
17585 ) {
17586 self.do_stage_or_unstage_and_next(false, window, cx);
17587 }
17588
17589 pub fn stage_or_unstage_diff_hunks(
17590 &mut self,
17591 stage: bool,
17592 ranges: Vec<Range<Anchor>>,
17593 cx: &mut Context<Self>,
17594 ) {
17595 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17596 cx.spawn(async move |this, cx| {
17597 task.await?;
17598 this.update(cx, |this, cx| {
17599 let snapshot = this.buffer.read(cx).snapshot(cx);
17600 let chunk_by = this
17601 .diff_hunks_in_ranges(&ranges, &snapshot)
17602 .chunk_by(|hunk| hunk.buffer_id);
17603 for (buffer_id, hunks) in &chunk_by {
17604 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17605 }
17606 })
17607 })
17608 .detach_and_log_err(cx);
17609 }
17610
17611 fn save_buffers_for_ranges_if_needed(
17612 &mut self,
17613 ranges: &[Range<Anchor>],
17614 cx: &mut Context<Editor>,
17615 ) -> Task<Result<()>> {
17616 let multibuffer = self.buffer.read(cx);
17617 let snapshot = multibuffer.read(cx);
17618 let buffer_ids: HashSet<_> = ranges
17619 .iter()
17620 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17621 .collect();
17622 drop(snapshot);
17623
17624 let mut buffers = HashSet::default();
17625 for buffer_id in buffer_ids {
17626 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17627 let buffer = buffer_entity.read(cx);
17628 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17629 {
17630 buffers.insert(buffer_entity);
17631 }
17632 }
17633 }
17634
17635 if let Some(project) = &self.project {
17636 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17637 } else {
17638 Task::ready(Ok(()))
17639 }
17640 }
17641
17642 fn do_stage_or_unstage_and_next(
17643 &mut self,
17644 stage: bool,
17645 window: &mut Window,
17646 cx: &mut Context<Self>,
17647 ) {
17648 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17649
17650 if ranges.iter().any(|range| range.start != range.end) {
17651 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17652 return;
17653 }
17654
17655 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17656 let snapshot = self.snapshot(window, cx);
17657 let position = self.selections.newest::<Point>(cx).head();
17658 let mut row = snapshot
17659 .buffer_snapshot
17660 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17661 .find(|hunk| hunk.row_range.start.0 > position.row)
17662 .map(|hunk| hunk.row_range.start);
17663
17664 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17665 // Outside of the project diff editor, wrap around to the beginning.
17666 if !all_diff_hunks_expanded {
17667 row = row.or_else(|| {
17668 snapshot
17669 .buffer_snapshot
17670 .diff_hunks_in_range(Point::zero()..position)
17671 .find(|hunk| hunk.row_range.end.0 < position.row)
17672 .map(|hunk| hunk.row_range.start)
17673 });
17674 }
17675
17676 if let Some(row) = row {
17677 let destination = Point::new(row.0, 0);
17678 let autoscroll = Autoscroll::center();
17679
17680 self.unfold_ranges(&[destination..destination], false, false, cx);
17681 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17682 s.select_ranges([destination..destination]);
17683 });
17684 }
17685 }
17686
17687 fn do_stage_or_unstage(
17688 &self,
17689 stage: bool,
17690 buffer_id: BufferId,
17691 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17692 cx: &mut App,
17693 ) -> Option<()> {
17694 let project = self.project.as_ref()?;
17695 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17696 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17697 let buffer_snapshot = buffer.read(cx).snapshot();
17698 let file_exists = buffer_snapshot
17699 .file()
17700 .is_some_and(|file| file.disk_state().exists());
17701 diff.update(cx, |diff, cx| {
17702 diff.stage_or_unstage_hunks(
17703 stage,
17704 &hunks
17705 .map(|hunk| buffer_diff::DiffHunk {
17706 buffer_range: hunk.buffer_range,
17707 diff_base_byte_range: hunk.diff_base_byte_range,
17708 secondary_status: hunk.secondary_status,
17709 range: Point::zero()..Point::zero(), // unused
17710 })
17711 .collect::<Vec<_>>(),
17712 &buffer_snapshot,
17713 file_exists,
17714 cx,
17715 )
17716 });
17717 None
17718 }
17719
17720 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17721 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17722 self.buffer
17723 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17724 }
17725
17726 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17727 self.buffer.update(cx, |buffer, cx| {
17728 let ranges = vec![Anchor::min()..Anchor::max()];
17729 if !buffer.all_diff_hunks_expanded()
17730 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17731 {
17732 buffer.collapse_diff_hunks(ranges, cx);
17733 true
17734 } else {
17735 false
17736 }
17737 })
17738 }
17739
17740 fn toggle_diff_hunks_in_ranges(
17741 &mut self,
17742 ranges: Vec<Range<Anchor>>,
17743 cx: &mut Context<Editor>,
17744 ) {
17745 self.buffer.update(cx, |buffer, cx| {
17746 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17747 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17748 })
17749 }
17750
17751 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17752 self.buffer.update(cx, |buffer, cx| {
17753 let snapshot = buffer.snapshot(cx);
17754 let excerpt_id = range.end.excerpt_id;
17755 let point_range = range.to_point(&snapshot);
17756 let expand = !buffer.single_hunk_is_expanded(range, cx);
17757 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17758 })
17759 }
17760
17761 pub(crate) fn apply_all_diff_hunks(
17762 &mut self,
17763 _: &ApplyAllDiffHunks,
17764 window: &mut Window,
17765 cx: &mut Context<Self>,
17766 ) {
17767 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17768
17769 let buffers = self.buffer.read(cx).all_buffers();
17770 for branch_buffer in buffers {
17771 branch_buffer.update(cx, |branch_buffer, cx| {
17772 branch_buffer.merge_into_base(Vec::new(), cx);
17773 });
17774 }
17775
17776 if let Some(project) = self.project.clone() {
17777 self.save(
17778 SaveOptions {
17779 format: true,
17780 autosave: false,
17781 },
17782 project,
17783 window,
17784 cx,
17785 )
17786 .detach_and_log_err(cx);
17787 }
17788 }
17789
17790 pub(crate) fn apply_selected_diff_hunks(
17791 &mut self,
17792 _: &ApplyDiffHunk,
17793 window: &mut Window,
17794 cx: &mut Context<Self>,
17795 ) {
17796 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17797 let snapshot = self.snapshot(window, cx);
17798 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17799 let mut ranges_by_buffer = HashMap::default();
17800 self.transact(window, cx, |editor, _window, cx| {
17801 for hunk in hunks {
17802 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17803 ranges_by_buffer
17804 .entry(buffer.clone())
17805 .or_insert_with(Vec::new)
17806 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17807 }
17808 }
17809
17810 for (buffer, ranges) in ranges_by_buffer {
17811 buffer.update(cx, |buffer, cx| {
17812 buffer.merge_into_base(ranges, cx);
17813 });
17814 }
17815 });
17816
17817 if let Some(project) = self.project.clone() {
17818 self.save(
17819 SaveOptions {
17820 format: true,
17821 autosave: false,
17822 },
17823 project,
17824 window,
17825 cx,
17826 )
17827 .detach_and_log_err(cx);
17828 }
17829 }
17830
17831 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17832 if hovered != self.gutter_hovered {
17833 self.gutter_hovered = hovered;
17834 cx.notify();
17835 }
17836 }
17837
17838 pub fn insert_blocks(
17839 &mut self,
17840 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17841 autoscroll: Option<Autoscroll>,
17842 cx: &mut Context<Self>,
17843 ) -> Vec<CustomBlockId> {
17844 let blocks = self
17845 .display_map
17846 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17847 if let Some(autoscroll) = autoscroll {
17848 self.request_autoscroll(autoscroll, cx);
17849 }
17850 cx.notify();
17851 blocks
17852 }
17853
17854 pub fn resize_blocks(
17855 &mut self,
17856 heights: HashMap<CustomBlockId, u32>,
17857 autoscroll: Option<Autoscroll>,
17858 cx: &mut Context<Self>,
17859 ) {
17860 self.display_map
17861 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17862 if let Some(autoscroll) = autoscroll {
17863 self.request_autoscroll(autoscroll, cx);
17864 }
17865 cx.notify();
17866 }
17867
17868 pub fn replace_blocks(
17869 &mut self,
17870 renderers: HashMap<CustomBlockId, RenderBlock>,
17871 autoscroll: Option<Autoscroll>,
17872 cx: &mut Context<Self>,
17873 ) {
17874 self.display_map
17875 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17876 if let Some(autoscroll) = autoscroll {
17877 self.request_autoscroll(autoscroll, cx);
17878 }
17879 cx.notify();
17880 }
17881
17882 pub fn remove_blocks(
17883 &mut self,
17884 block_ids: HashSet<CustomBlockId>,
17885 autoscroll: Option<Autoscroll>,
17886 cx: &mut Context<Self>,
17887 ) {
17888 self.display_map.update(cx, |display_map, cx| {
17889 display_map.remove_blocks(block_ids, cx)
17890 });
17891 if let Some(autoscroll) = autoscroll {
17892 self.request_autoscroll(autoscroll, cx);
17893 }
17894 cx.notify();
17895 }
17896
17897 pub fn row_for_block(
17898 &self,
17899 block_id: CustomBlockId,
17900 cx: &mut Context<Self>,
17901 ) -> Option<DisplayRow> {
17902 self.display_map
17903 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17904 }
17905
17906 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17907 self.focused_block = Some(focused_block);
17908 }
17909
17910 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17911 self.focused_block.take()
17912 }
17913
17914 pub fn insert_creases(
17915 &mut self,
17916 creases: impl IntoIterator<Item = Crease<Anchor>>,
17917 cx: &mut Context<Self>,
17918 ) -> Vec<CreaseId> {
17919 self.display_map
17920 .update(cx, |map, cx| map.insert_creases(creases, cx))
17921 }
17922
17923 pub fn remove_creases(
17924 &mut self,
17925 ids: impl IntoIterator<Item = CreaseId>,
17926 cx: &mut Context<Self>,
17927 ) -> Vec<(CreaseId, Range<Anchor>)> {
17928 self.display_map
17929 .update(cx, |map, cx| map.remove_creases(ids, cx))
17930 }
17931
17932 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17933 self.display_map
17934 .update(cx, |map, cx| map.snapshot(cx))
17935 .longest_row()
17936 }
17937
17938 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17939 self.display_map
17940 .update(cx, |map, cx| map.snapshot(cx))
17941 .max_point()
17942 }
17943
17944 pub fn text(&self, cx: &App) -> String {
17945 self.buffer.read(cx).read(cx).text()
17946 }
17947
17948 pub fn is_empty(&self, cx: &App) -> bool {
17949 self.buffer.read(cx).read(cx).is_empty()
17950 }
17951
17952 pub fn text_option(&self, cx: &App) -> Option<String> {
17953 let text = self.text(cx);
17954 let text = text.trim();
17955
17956 if text.is_empty() {
17957 return None;
17958 }
17959
17960 Some(text.to_string())
17961 }
17962
17963 pub fn set_text(
17964 &mut self,
17965 text: impl Into<Arc<str>>,
17966 window: &mut Window,
17967 cx: &mut Context<Self>,
17968 ) {
17969 self.transact(window, cx, |this, _, cx| {
17970 this.buffer
17971 .read(cx)
17972 .as_singleton()
17973 .expect("you can only call set_text on editors for singleton buffers")
17974 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17975 });
17976 }
17977
17978 pub fn display_text(&self, cx: &mut App) -> String {
17979 self.display_map
17980 .update(cx, |map, cx| map.snapshot(cx))
17981 .text()
17982 }
17983
17984 fn create_minimap(
17985 &self,
17986 minimap_settings: MinimapSettings,
17987 window: &mut Window,
17988 cx: &mut Context<Self>,
17989 ) -> Option<Entity<Self>> {
17990 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17991 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17992 }
17993
17994 fn initialize_new_minimap(
17995 &self,
17996 minimap_settings: MinimapSettings,
17997 window: &mut Window,
17998 cx: &mut Context<Self>,
17999 ) -> Entity<Self> {
18000 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18001
18002 let mut minimap = Editor::new_internal(
18003 EditorMode::Minimap {
18004 parent: cx.weak_entity(),
18005 },
18006 self.buffer.clone(),
18007 self.project.clone(),
18008 Some(self.display_map.clone()),
18009 window,
18010 cx,
18011 );
18012 minimap.scroll_manager.clone_state(&self.scroll_manager);
18013 minimap.set_text_style_refinement(TextStyleRefinement {
18014 font_size: Some(MINIMAP_FONT_SIZE),
18015 font_weight: Some(MINIMAP_FONT_WEIGHT),
18016 ..Default::default()
18017 });
18018 minimap.update_minimap_configuration(minimap_settings, cx);
18019 cx.new(|_| minimap)
18020 }
18021
18022 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18023 let current_line_highlight = minimap_settings
18024 .current_line_highlight
18025 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18026 self.set_current_line_highlight(Some(current_line_highlight));
18027 }
18028
18029 pub fn minimap(&self) -> Option<&Entity<Self>> {
18030 self.minimap
18031 .as_ref()
18032 .filter(|_| self.minimap_visibility.visible())
18033 }
18034
18035 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18036 let mut wrap_guides = smallvec![];
18037
18038 if self.show_wrap_guides == Some(false) {
18039 return wrap_guides;
18040 }
18041
18042 let settings = self.buffer.read(cx).language_settings(cx);
18043 if settings.show_wrap_guides {
18044 match self.soft_wrap_mode(cx) {
18045 SoftWrap::Column(soft_wrap) => {
18046 wrap_guides.push((soft_wrap as usize, true));
18047 }
18048 SoftWrap::Bounded(soft_wrap) => {
18049 wrap_guides.push((soft_wrap as usize, true));
18050 }
18051 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18052 }
18053 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18054 }
18055
18056 wrap_guides
18057 }
18058
18059 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18060 let settings = self.buffer.read(cx).language_settings(cx);
18061 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18062 match mode {
18063 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18064 SoftWrap::None
18065 }
18066 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18067 language_settings::SoftWrap::PreferredLineLength => {
18068 SoftWrap::Column(settings.preferred_line_length)
18069 }
18070 language_settings::SoftWrap::Bounded => {
18071 SoftWrap::Bounded(settings.preferred_line_length)
18072 }
18073 }
18074 }
18075
18076 pub fn set_soft_wrap_mode(
18077 &mut self,
18078 mode: language_settings::SoftWrap,
18079
18080 cx: &mut Context<Self>,
18081 ) {
18082 self.soft_wrap_mode_override = Some(mode);
18083 cx.notify();
18084 }
18085
18086 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18087 self.hard_wrap = hard_wrap;
18088 cx.notify();
18089 }
18090
18091 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18092 self.text_style_refinement = Some(style);
18093 }
18094
18095 /// called by the Element so we know what style we were most recently rendered with.
18096 pub(crate) fn set_style(
18097 &mut self,
18098 style: EditorStyle,
18099 window: &mut Window,
18100 cx: &mut Context<Self>,
18101 ) {
18102 // We intentionally do not inform the display map about the minimap style
18103 // so that wrapping is not recalculated and stays consistent for the editor
18104 // and its linked minimap.
18105 if !self.mode.is_minimap() {
18106 let rem_size = window.rem_size();
18107 self.display_map.update(cx, |map, cx| {
18108 map.set_font(
18109 style.text.font(),
18110 style.text.font_size.to_pixels(rem_size),
18111 cx,
18112 )
18113 });
18114 }
18115 self.style = Some(style);
18116 }
18117
18118 pub fn style(&self) -> Option<&EditorStyle> {
18119 self.style.as_ref()
18120 }
18121
18122 // Called by the element. This method is not designed to be called outside of the editor
18123 // element's layout code because it does not notify when rewrapping is computed synchronously.
18124 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18125 self.display_map
18126 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18127 }
18128
18129 pub fn set_soft_wrap(&mut self) {
18130 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18131 }
18132
18133 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18134 if self.soft_wrap_mode_override.is_some() {
18135 self.soft_wrap_mode_override.take();
18136 } else {
18137 let soft_wrap = match self.soft_wrap_mode(cx) {
18138 SoftWrap::GitDiff => return,
18139 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18140 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18141 language_settings::SoftWrap::None
18142 }
18143 };
18144 self.soft_wrap_mode_override = Some(soft_wrap);
18145 }
18146 cx.notify();
18147 }
18148
18149 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18150 let Some(workspace) = self.workspace() else {
18151 return;
18152 };
18153 let fs = workspace.read(cx).app_state().fs.clone();
18154 let current_show = TabBarSettings::get_global(cx).show;
18155 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18156 setting.show = Some(!current_show);
18157 });
18158 }
18159
18160 pub fn toggle_indent_guides(
18161 &mut self,
18162 _: &ToggleIndentGuides,
18163 _: &mut Window,
18164 cx: &mut Context<Self>,
18165 ) {
18166 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18167 self.buffer
18168 .read(cx)
18169 .language_settings(cx)
18170 .indent_guides
18171 .enabled
18172 });
18173 self.show_indent_guides = Some(!currently_enabled);
18174 cx.notify();
18175 }
18176
18177 fn should_show_indent_guides(&self) -> Option<bool> {
18178 self.show_indent_guides
18179 }
18180
18181 pub fn toggle_line_numbers(
18182 &mut self,
18183 _: &ToggleLineNumbers,
18184 _: &mut Window,
18185 cx: &mut Context<Self>,
18186 ) {
18187 let mut editor_settings = EditorSettings::get_global(cx).clone();
18188 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18189 EditorSettings::override_global(editor_settings, cx);
18190 }
18191
18192 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18193 if let Some(show_line_numbers) = self.show_line_numbers {
18194 return show_line_numbers;
18195 }
18196 EditorSettings::get_global(cx).gutter.line_numbers
18197 }
18198
18199 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18200 self.use_relative_line_numbers
18201 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18202 }
18203
18204 pub fn toggle_relative_line_numbers(
18205 &mut self,
18206 _: &ToggleRelativeLineNumbers,
18207 _: &mut Window,
18208 cx: &mut Context<Self>,
18209 ) {
18210 let is_relative = self.should_use_relative_line_numbers(cx);
18211 self.set_relative_line_number(Some(!is_relative), cx)
18212 }
18213
18214 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18215 self.use_relative_line_numbers = is_relative;
18216 cx.notify();
18217 }
18218
18219 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18220 self.show_gutter = show_gutter;
18221 cx.notify();
18222 }
18223
18224 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18225 self.show_scrollbars = ScrollbarAxes {
18226 horizontal: show,
18227 vertical: show,
18228 };
18229 cx.notify();
18230 }
18231
18232 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18233 self.show_scrollbars.vertical = show;
18234 cx.notify();
18235 }
18236
18237 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18238 self.show_scrollbars.horizontal = show;
18239 cx.notify();
18240 }
18241
18242 pub fn set_minimap_visibility(
18243 &mut self,
18244 minimap_visibility: MinimapVisibility,
18245 window: &mut Window,
18246 cx: &mut Context<Self>,
18247 ) {
18248 if self.minimap_visibility != minimap_visibility {
18249 if minimap_visibility.visible() && self.minimap.is_none() {
18250 let minimap_settings = EditorSettings::get_global(cx).minimap;
18251 self.minimap =
18252 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18253 }
18254 self.minimap_visibility = minimap_visibility;
18255 cx.notify();
18256 }
18257 }
18258
18259 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18260 self.set_show_scrollbars(false, cx);
18261 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18262 }
18263
18264 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18265 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18266 }
18267
18268 /// Normally the text in full mode and auto height editors is padded on the
18269 /// left side by roughly half a character width for improved hit testing.
18270 ///
18271 /// Use this method to disable this for cases where this is not wanted (e.g.
18272 /// if you want to align the editor text with some other text above or below)
18273 /// or if you want to add this padding to single-line editors.
18274 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18275 self.offset_content = offset_content;
18276 cx.notify();
18277 }
18278
18279 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18280 self.show_line_numbers = Some(show_line_numbers);
18281 cx.notify();
18282 }
18283
18284 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18285 self.disable_expand_excerpt_buttons = true;
18286 cx.notify();
18287 }
18288
18289 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18290 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18291 cx.notify();
18292 }
18293
18294 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18295 self.show_code_actions = Some(show_code_actions);
18296 cx.notify();
18297 }
18298
18299 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18300 self.show_runnables = Some(show_runnables);
18301 cx.notify();
18302 }
18303
18304 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18305 self.show_breakpoints = Some(show_breakpoints);
18306 cx.notify();
18307 }
18308
18309 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18310 if self.display_map.read(cx).masked != masked {
18311 self.display_map.update(cx, |map, _| map.masked = masked);
18312 }
18313 cx.notify()
18314 }
18315
18316 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18317 self.show_wrap_guides = Some(show_wrap_guides);
18318 cx.notify();
18319 }
18320
18321 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18322 self.show_indent_guides = Some(show_indent_guides);
18323 cx.notify();
18324 }
18325
18326 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18327 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18328 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18329 if let Some(dir) = file.abs_path(cx).parent() {
18330 return Some(dir.to_owned());
18331 }
18332 }
18333
18334 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18335 return Some(project_path.path.to_path_buf());
18336 }
18337 }
18338
18339 None
18340 }
18341
18342 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18343 self.active_excerpt(cx)?
18344 .1
18345 .read(cx)
18346 .file()
18347 .and_then(|f| f.as_local())
18348 }
18349
18350 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18351 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18352 let buffer = buffer.read(cx);
18353 if let Some(project_path) = buffer.project_path(cx) {
18354 let project = self.project.as_ref()?.read(cx);
18355 project.absolute_path(&project_path, cx)
18356 } else {
18357 buffer
18358 .file()
18359 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18360 }
18361 })
18362 }
18363
18364 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18365 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18366 let project_path = buffer.read(cx).project_path(cx)?;
18367 let project = self.project.as_ref()?.read(cx);
18368 let entry = project.entry_for_path(&project_path, cx)?;
18369 let path = entry.path.to_path_buf();
18370 Some(path)
18371 })
18372 }
18373
18374 pub fn reveal_in_finder(
18375 &mut self,
18376 _: &RevealInFileManager,
18377 _window: &mut Window,
18378 cx: &mut Context<Self>,
18379 ) {
18380 if let Some(target) = self.target_file(cx) {
18381 cx.reveal_path(&target.abs_path(cx));
18382 }
18383 }
18384
18385 pub fn copy_path(
18386 &mut self,
18387 _: &zed_actions::workspace::CopyPath,
18388 _window: &mut Window,
18389 cx: &mut Context<Self>,
18390 ) {
18391 if let Some(path) = self.target_file_abs_path(cx) {
18392 if let Some(path) = path.to_str() {
18393 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18394 }
18395 }
18396 }
18397
18398 pub fn copy_relative_path(
18399 &mut self,
18400 _: &zed_actions::workspace::CopyRelativePath,
18401 _window: &mut Window,
18402 cx: &mut Context<Self>,
18403 ) {
18404 if let Some(path) = self.target_file_path(cx) {
18405 if let Some(path) = path.to_str() {
18406 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18407 }
18408 }
18409 }
18410
18411 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18412 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18413 buffer.read(cx).project_path(cx)
18414 } else {
18415 None
18416 }
18417 }
18418
18419 // Returns true if the editor handled a go-to-line request
18420 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18421 maybe!({
18422 let breakpoint_store = self.breakpoint_store.as_ref()?;
18423
18424 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18425 else {
18426 self.clear_row_highlights::<ActiveDebugLine>();
18427 return None;
18428 };
18429
18430 let position = active_stack_frame.position;
18431 let buffer_id = position.buffer_id?;
18432 let snapshot = self
18433 .project
18434 .as_ref()?
18435 .read(cx)
18436 .buffer_for_id(buffer_id, cx)?
18437 .read(cx)
18438 .snapshot();
18439
18440 let mut handled = false;
18441 for (id, ExcerptRange { context, .. }) in
18442 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18443 {
18444 if context.start.cmp(&position, &snapshot).is_ge()
18445 || context.end.cmp(&position, &snapshot).is_lt()
18446 {
18447 continue;
18448 }
18449 let snapshot = self.buffer.read(cx).snapshot(cx);
18450 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18451
18452 handled = true;
18453 self.clear_row_highlights::<ActiveDebugLine>();
18454
18455 self.go_to_line::<ActiveDebugLine>(
18456 multibuffer_anchor,
18457 Some(cx.theme().colors().editor_debugger_active_line_background),
18458 window,
18459 cx,
18460 );
18461
18462 cx.notify();
18463 }
18464
18465 handled.then_some(())
18466 })
18467 .is_some()
18468 }
18469
18470 pub fn copy_file_name_without_extension(
18471 &mut self,
18472 _: &CopyFileNameWithoutExtension,
18473 _: &mut Window,
18474 cx: &mut Context<Self>,
18475 ) {
18476 if let Some(file) = self.target_file(cx) {
18477 if let Some(file_stem) = file.path().file_stem() {
18478 if let Some(name) = file_stem.to_str() {
18479 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18480 }
18481 }
18482 }
18483 }
18484
18485 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18486 if let Some(file) = self.target_file(cx) {
18487 if let Some(file_name) = file.path().file_name() {
18488 if let Some(name) = file_name.to_str() {
18489 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18490 }
18491 }
18492 }
18493 }
18494
18495 pub fn toggle_git_blame(
18496 &mut self,
18497 _: &::git::Blame,
18498 window: &mut Window,
18499 cx: &mut Context<Self>,
18500 ) {
18501 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18502
18503 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18504 self.start_git_blame(true, window, cx);
18505 }
18506
18507 cx.notify();
18508 }
18509
18510 pub fn toggle_git_blame_inline(
18511 &mut self,
18512 _: &ToggleGitBlameInline,
18513 window: &mut Window,
18514 cx: &mut Context<Self>,
18515 ) {
18516 self.toggle_git_blame_inline_internal(true, window, cx);
18517 cx.notify();
18518 }
18519
18520 pub fn open_git_blame_commit(
18521 &mut self,
18522 _: &OpenGitBlameCommit,
18523 window: &mut Window,
18524 cx: &mut Context<Self>,
18525 ) {
18526 self.open_git_blame_commit_internal(window, cx);
18527 }
18528
18529 fn open_git_blame_commit_internal(
18530 &mut self,
18531 window: &mut Window,
18532 cx: &mut Context<Self>,
18533 ) -> Option<()> {
18534 let blame = self.blame.as_ref()?;
18535 let snapshot = self.snapshot(window, cx);
18536 let cursor = self.selections.newest::<Point>(cx).head();
18537 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18538 let blame_entry = blame
18539 .update(cx, |blame, cx| {
18540 blame
18541 .blame_for_rows(
18542 &[RowInfo {
18543 buffer_id: Some(buffer.remote_id()),
18544 buffer_row: Some(point.row),
18545 ..Default::default()
18546 }],
18547 cx,
18548 )
18549 .next()
18550 })
18551 .flatten()?;
18552 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18553 let repo = blame.read(cx).repository(cx)?;
18554 let workspace = self.workspace()?.downgrade();
18555 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18556 None
18557 }
18558
18559 pub fn git_blame_inline_enabled(&self) -> bool {
18560 self.git_blame_inline_enabled
18561 }
18562
18563 pub fn toggle_selection_menu(
18564 &mut self,
18565 _: &ToggleSelectionMenu,
18566 _: &mut Window,
18567 cx: &mut Context<Self>,
18568 ) {
18569 self.show_selection_menu = self
18570 .show_selection_menu
18571 .map(|show_selections_menu| !show_selections_menu)
18572 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18573
18574 cx.notify();
18575 }
18576
18577 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18578 self.show_selection_menu
18579 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18580 }
18581
18582 fn start_git_blame(
18583 &mut self,
18584 user_triggered: bool,
18585 window: &mut Window,
18586 cx: &mut Context<Self>,
18587 ) {
18588 if let Some(project) = self.project.as_ref() {
18589 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18590 return;
18591 };
18592
18593 if buffer.read(cx).file().is_none() {
18594 return;
18595 }
18596
18597 let focused = self.focus_handle(cx).contains_focused(window, cx);
18598
18599 let project = project.clone();
18600 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18601 self.blame_subscription =
18602 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18603 self.blame = Some(blame);
18604 }
18605 }
18606
18607 fn toggle_git_blame_inline_internal(
18608 &mut self,
18609 user_triggered: bool,
18610 window: &mut Window,
18611 cx: &mut Context<Self>,
18612 ) {
18613 if self.git_blame_inline_enabled {
18614 self.git_blame_inline_enabled = false;
18615 self.show_git_blame_inline = false;
18616 self.show_git_blame_inline_delay_task.take();
18617 } else {
18618 self.git_blame_inline_enabled = true;
18619 self.start_git_blame_inline(user_triggered, window, cx);
18620 }
18621
18622 cx.notify();
18623 }
18624
18625 fn start_git_blame_inline(
18626 &mut self,
18627 user_triggered: bool,
18628 window: &mut Window,
18629 cx: &mut Context<Self>,
18630 ) {
18631 self.start_git_blame(user_triggered, window, cx);
18632
18633 if ProjectSettings::get_global(cx)
18634 .git
18635 .inline_blame_delay()
18636 .is_some()
18637 {
18638 self.start_inline_blame_timer(window, cx);
18639 } else {
18640 self.show_git_blame_inline = true
18641 }
18642 }
18643
18644 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18645 self.blame.as_ref()
18646 }
18647
18648 pub fn show_git_blame_gutter(&self) -> bool {
18649 self.show_git_blame_gutter
18650 }
18651
18652 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18653 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18654 }
18655
18656 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18657 self.show_git_blame_inline
18658 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18659 && !self.newest_selection_head_on_empty_line(cx)
18660 && self.has_blame_entries(cx)
18661 }
18662
18663 fn has_blame_entries(&self, cx: &App) -> bool {
18664 self.blame()
18665 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18666 }
18667
18668 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18669 let cursor_anchor = self.selections.newest_anchor().head();
18670
18671 let snapshot = self.buffer.read(cx).snapshot(cx);
18672 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18673
18674 snapshot.line_len(buffer_row) == 0
18675 }
18676
18677 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18678 let buffer_and_selection = maybe!({
18679 let selection = self.selections.newest::<Point>(cx);
18680 let selection_range = selection.range();
18681
18682 let multi_buffer = self.buffer().read(cx);
18683 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18684 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18685
18686 let (buffer, range, _) = if selection.reversed {
18687 buffer_ranges.first()
18688 } else {
18689 buffer_ranges.last()
18690 }?;
18691
18692 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18693 ..text::ToPoint::to_point(&range.end, &buffer).row;
18694 Some((
18695 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18696 selection,
18697 ))
18698 });
18699
18700 let Some((buffer, selection)) = buffer_and_selection else {
18701 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18702 };
18703
18704 let Some(project) = self.project.as_ref() else {
18705 return Task::ready(Err(anyhow!("editor does not have project")));
18706 };
18707
18708 project.update(cx, |project, cx| {
18709 project.get_permalink_to_line(&buffer, selection, cx)
18710 })
18711 }
18712
18713 pub fn copy_permalink_to_line(
18714 &mut self,
18715 _: &CopyPermalinkToLine,
18716 window: &mut Window,
18717 cx: &mut Context<Self>,
18718 ) {
18719 let permalink_task = self.get_permalink_to_line(cx);
18720 let workspace = self.workspace();
18721
18722 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18723 Ok(permalink) => {
18724 cx.update(|_, cx| {
18725 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18726 })
18727 .ok();
18728 }
18729 Err(err) => {
18730 let message = format!("Failed to copy permalink: {err}");
18731
18732 anyhow::Result::<()>::Err(err).log_err();
18733
18734 if let Some(workspace) = workspace {
18735 workspace
18736 .update_in(cx, |workspace, _, cx| {
18737 struct CopyPermalinkToLine;
18738
18739 workspace.show_toast(
18740 Toast::new(
18741 NotificationId::unique::<CopyPermalinkToLine>(),
18742 message,
18743 ),
18744 cx,
18745 )
18746 })
18747 .ok();
18748 }
18749 }
18750 })
18751 .detach();
18752 }
18753
18754 pub fn copy_file_location(
18755 &mut self,
18756 _: &CopyFileLocation,
18757 _: &mut Window,
18758 cx: &mut Context<Self>,
18759 ) {
18760 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18761 if let Some(file) = self.target_file(cx) {
18762 if let Some(path) = file.path().to_str() {
18763 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18764 }
18765 }
18766 }
18767
18768 pub fn open_permalink_to_line(
18769 &mut self,
18770 _: &OpenPermalinkToLine,
18771 window: &mut Window,
18772 cx: &mut Context<Self>,
18773 ) {
18774 let permalink_task = self.get_permalink_to_line(cx);
18775 let workspace = self.workspace();
18776
18777 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18778 Ok(permalink) => {
18779 cx.update(|_, cx| {
18780 cx.open_url(permalink.as_ref());
18781 })
18782 .ok();
18783 }
18784 Err(err) => {
18785 let message = format!("Failed to open permalink: {err}");
18786
18787 anyhow::Result::<()>::Err(err).log_err();
18788
18789 if let Some(workspace) = workspace {
18790 workspace
18791 .update(cx, |workspace, cx| {
18792 struct OpenPermalinkToLine;
18793
18794 workspace.show_toast(
18795 Toast::new(
18796 NotificationId::unique::<OpenPermalinkToLine>(),
18797 message,
18798 ),
18799 cx,
18800 )
18801 })
18802 .ok();
18803 }
18804 }
18805 })
18806 .detach();
18807 }
18808
18809 pub fn insert_uuid_v4(
18810 &mut self,
18811 _: &InsertUuidV4,
18812 window: &mut Window,
18813 cx: &mut Context<Self>,
18814 ) {
18815 self.insert_uuid(UuidVersion::V4, window, cx);
18816 }
18817
18818 pub fn insert_uuid_v7(
18819 &mut self,
18820 _: &InsertUuidV7,
18821 window: &mut Window,
18822 cx: &mut Context<Self>,
18823 ) {
18824 self.insert_uuid(UuidVersion::V7, window, cx);
18825 }
18826
18827 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18828 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18829 self.transact(window, cx, |this, window, cx| {
18830 let edits = this
18831 .selections
18832 .all::<Point>(cx)
18833 .into_iter()
18834 .map(|selection| {
18835 let uuid = match version {
18836 UuidVersion::V4 => uuid::Uuid::new_v4(),
18837 UuidVersion::V7 => uuid::Uuid::now_v7(),
18838 };
18839
18840 (selection.range(), uuid.to_string())
18841 });
18842 this.edit(edits, cx);
18843 this.refresh_inline_completion(true, false, window, cx);
18844 });
18845 }
18846
18847 pub fn open_selections_in_multibuffer(
18848 &mut self,
18849 _: &OpenSelectionsInMultibuffer,
18850 window: &mut Window,
18851 cx: &mut Context<Self>,
18852 ) {
18853 let multibuffer = self.buffer.read(cx);
18854
18855 let Some(buffer) = multibuffer.as_singleton() else {
18856 return;
18857 };
18858
18859 let Some(workspace) = self.workspace() else {
18860 return;
18861 };
18862
18863 let title = multibuffer.title(cx).to_string();
18864
18865 let locations = self
18866 .selections
18867 .all_anchors(cx)
18868 .into_iter()
18869 .map(|selection| Location {
18870 buffer: buffer.clone(),
18871 range: selection.start.text_anchor..selection.end.text_anchor,
18872 })
18873 .collect::<Vec<_>>();
18874
18875 cx.spawn_in(window, async move |_, cx| {
18876 workspace.update_in(cx, |workspace, window, cx| {
18877 Self::open_locations_in_multibuffer(
18878 workspace,
18879 locations,
18880 format!("Selections for '{title}'"),
18881 false,
18882 MultibufferSelectionMode::All,
18883 window,
18884 cx,
18885 );
18886 })
18887 })
18888 .detach();
18889 }
18890
18891 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18892 /// last highlight added will be used.
18893 ///
18894 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18895 pub fn highlight_rows<T: 'static>(
18896 &mut self,
18897 range: Range<Anchor>,
18898 color: Hsla,
18899 options: RowHighlightOptions,
18900 cx: &mut Context<Self>,
18901 ) {
18902 let snapshot = self.buffer().read(cx).snapshot(cx);
18903 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18904 let ix = row_highlights.binary_search_by(|highlight| {
18905 Ordering::Equal
18906 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18907 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18908 });
18909
18910 if let Err(mut ix) = ix {
18911 let index = post_inc(&mut self.highlight_order);
18912
18913 // If this range intersects with the preceding highlight, then merge it with
18914 // the preceding highlight. Otherwise insert a new highlight.
18915 let mut merged = false;
18916 if ix > 0 {
18917 let prev_highlight = &mut row_highlights[ix - 1];
18918 if prev_highlight
18919 .range
18920 .end
18921 .cmp(&range.start, &snapshot)
18922 .is_ge()
18923 {
18924 ix -= 1;
18925 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18926 prev_highlight.range.end = range.end;
18927 }
18928 merged = true;
18929 prev_highlight.index = index;
18930 prev_highlight.color = color;
18931 prev_highlight.options = options;
18932 }
18933 }
18934
18935 if !merged {
18936 row_highlights.insert(
18937 ix,
18938 RowHighlight {
18939 range: range.clone(),
18940 index,
18941 color,
18942 options,
18943 type_id: TypeId::of::<T>(),
18944 },
18945 );
18946 }
18947
18948 // If any of the following highlights intersect with this one, merge them.
18949 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18950 let highlight = &row_highlights[ix];
18951 if next_highlight
18952 .range
18953 .start
18954 .cmp(&highlight.range.end, &snapshot)
18955 .is_le()
18956 {
18957 if next_highlight
18958 .range
18959 .end
18960 .cmp(&highlight.range.end, &snapshot)
18961 .is_gt()
18962 {
18963 row_highlights[ix].range.end = next_highlight.range.end;
18964 }
18965 row_highlights.remove(ix + 1);
18966 } else {
18967 break;
18968 }
18969 }
18970 }
18971 }
18972
18973 /// Remove any highlighted row ranges of the given type that intersect the
18974 /// given ranges.
18975 pub fn remove_highlighted_rows<T: 'static>(
18976 &mut self,
18977 ranges_to_remove: Vec<Range<Anchor>>,
18978 cx: &mut Context<Self>,
18979 ) {
18980 let snapshot = self.buffer().read(cx).snapshot(cx);
18981 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18982 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18983 row_highlights.retain(|highlight| {
18984 while let Some(range_to_remove) = ranges_to_remove.peek() {
18985 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18986 Ordering::Less | Ordering::Equal => {
18987 ranges_to_remove.next();
18988 }
18989 Ordering::Greater => {
18990 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18991 Ordering::Less | Ordering::Equal => {
18992 return false;
18993 }
18994 Ordering::Greater => break,
18995 }
18996 }
18997 }
18998 }
18999
19000 true
19001 })
19002 }
19003
19004 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19005 pub fn clear_row_highlights<T: 'static>(&mut self) {
19006 self.highlighted_rows.remove(&TypeId::of::<T>());
19007 }
19008
19009 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19010 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19011 self.highlighted_rows
19012 .get(&TypeId::of::<T>())
19013 .map_or(&[] as &[_], |vec| vec.as_slice())
19014 .iter()
19015 .map(|highlight| (highlight.range.clone(), highlight.color))
19016 }
19017
19018 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19019 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19020 /// Allows to ignore certain kinds of highlights.
19021 pub fn highlighted_display_rows(
19022 &self,
19023 window: &mut Window,
19024 cx: &mut App,
19025 ) -> BTreeMap<DisplayRow, LineHighlight> {
19026 let snapshot = self.snapshot(window, cx);
19027 let mut used_highlight_orders = HashMap::default();
19028 self.highlighted_rows
19029 .iter()
19030 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19031 .fold(
19032 BTreeMap::<DisplayRow, LineHighlight>::new(),
19033 |mut unique_rows, highlight| {
19034 let start = highlight.range.start.to_display_point(&snapshot);
19035 let end = highlight.range.end.to_display_point(&snapshot);
19036 let start_row = start.row().0;
19037 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19038 && end.column() == 0
19039 {
19040 end.row().0.saturating_sub(1)
19041 } else {
19042 end.row().0
19043 };
19044 for row in start_row..=end_row {
19045 let used_index =
19046 used_highlight_orders.entry(row).or_insert(highlight.index);
19047 if highlight.index >= *used_index {
19048 *used_index = highlight.index;
19049 unique_rows.insert(
19050 DisplayRow(row),
19051 LineHighlight {
19052 include_gutter: highlight.options.include_gutter,
19053 border: None,
19054 background: highlight.color.into(),
19055 type_id: Some(highlight.type_id),
19056 },
19057 );
19058 }
19059 }
19060 unique_rows
19061 },
19062 )
19063 }
19064
19065 pub fn highlighted_display_row_for_autoscroll(
19066 &self,
19067 snapshot: &DisplaySnapshot,
19068 ) -> Option<DisplayRow> {
19069 self.highlighted_rows
19070 .values()
19071 .flat_map(|highlighted_rows| highlighted_rows.iter())
19072 .filter_map(|highlight| {
19073 if highlight.options.autoscroll {
19074 Some(highlight.range.start.to_display_point(snapshot).row())
19075 } else {
19076 None
19077 }
19078 })
19079 .min()
19080 }
19081
19082 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19083 self.highlight_background::<SearchWithinRange>(
19084 ranges,
19085 |colors| colors.colors().editor_document_highlight_read_background,
19086 cx,
19087 )
19088 }
19089
19090 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19091 self.breadcrumb_header = Some(new_header);
19092 }
19093
19094 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19095 self.clear_background_highlights::<SearchWithinRange>(cx);
19096 }
19097
19098 pub fn highlight_background<T: 'static>(
19099 &mut self,
19100 ranges: &[Range<Anchor>],
19101 color_fetcher: fn(&Theme) -> Hsla,
19102 cx: &mut Context<Self>,
19103 ) {
19104 self.background_highlights.insert(
19105 HighlightKey::Type(TypeId::of::<T>()),
19106 (color_fetcher, Arc::from(ranges)),
19107 );
19108 self.scrollbar_marker_state.dirty = true;
19109 cx.notify();
19110 }
19111
19112 pub fn highlight_background_key<T: 'static>(
19113 &mut self,
19114 key: usize,
19115 ranges: &[Range<Anchor>],
19116 color_fetcher: fn(&Theme) -> Hsla,
19117 cx: &mut Context<Self>,
19118 ) {
19119 self.background_highlights.insert(
19120 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19121 (color_fetcher, Arc::from(ranges)),
19122 );
19123 self.scrollbar_marker_state.dirty = true;
19124 cx.notify();
19125 }
19126
19127 pub fn clear_background_highlights<T: 'static>(
19128 &mut self,
19129 cx: &mut Context<Self>,
19130 ) -> Option<BackgroundHighlight> {
19131 let text_highlights = self
19132 .background_highlights
19133 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19134 if !text_highlights.1.is_empty() {
19135 self.scrollbar_marker_state.dirty = true;
19136 cx.notify();
19137 }
19138 Some(text_highlights)
19139 }
19140
19141 pub fn highlight_gutter<T: 'static>(
19142 &mut self,
19143 ranges: impl Into<Vec<Range<Anchor>>>,
19144 color_fetcher: fn(&App) -> Hsla,
19145 cx: &mut Context<Self>,
19146 ) {
19147 self.gutter_highlights
19148 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19149 cx.notify();
19150 }
19151
19152 pub fn clear_gutter_highlights<T: 'static>(
19153 &mut self,
19154 cx: &mut Context<Self>,
19155 ) -> Option<GutterHighlight> {
19156 cx.notify();
19157 self.gutter_highlights.remove(&TypeId::of::<T>())
19158 }
19159
19160 pub fn insert_gutter_highlight<T: 'static>(
19161 &mut self,
19162 range: Range<Anchor>,
19163 color_fetcher: fn(&App) -> Hsla,
19164 cx: &mut Context<Self>,
19165 ) {
19166 let snapshot = self.buffer().read(cx).snapshot(cx);
19167 let mut highlights = self
19168 .gutter_highlights
19169 .remove(&TypeId::of::<T>())
19170 .map(|(_, highlights)| highlights)
19171 .unwrap_or_default();
19172 let ix = highlights.binary_search_by(|highlight| {
19173 Ordering::Equal
19174 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19175 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19176 });
19177 if let Err(ix) = ix {
19178 highlights.insert(ix, range);
19179 }
19180 self.gutter_highlights
19181 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19182 }
19183
19184 pub fn remove_gutter_highlights<T: 'static>(
19185 &mut self,
19186 ranges_to_remove: Vec<Range<Anchor>>,
19187 cx: &mut Context<Self>,
19188 ) {
19189 let snapshot = self.buffer().read(cx).snapshot(cx);
19190 let Some((color_fetcher, mut gutter_highlights)) =
19191 self.gutter_highlights.remove(&TypeId::of::<T>())
19192 else {
19193 return;
19194 };
19195 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19196 gutter_highlights.retain(|highlight| {
19197 while let Some(range_to_remove) = ranges_to_remove.peek() {
19198 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19199 Ordering::Less | Ordering::Equal => {
19200 ranges_to_remove.next();
19201 }
19202 Ordering::Greater => {
19203 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19204 Ordering::Less | Ordering::Equal => {
19205 return false;
19206 }
19207 Ordering::Greater => break,
19208 }
19209 }
19210 }
19211 }
19212
19213 true
19214 });
19215 self.gutter_highlights
19216 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19217 }
19218
19219 #[cfg(feature = "test-support")]
19220 pub fn all_text_highlights(
19221 &self,
19222 window: &mut Window,
19223 cx: &mut Context<Self>,
19224 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19225 let snapshot = self.snapshot(window, cx);
19226 self.display_map.update(cx, |display_map, _| {
19227 display_map
19228 .all_text_highlights()
19229 .map(|highlight| {
19230 let (style, ranges) = highlight.as_ref();
19231 (
19232 *style,
19233 ranges
19234 .iter()
19235 .map(|range| range.clone().to_display_points(&snapshot))
19236 .collect(),
19237 )
19238 })
19239 .collect()
19240 })
19241 }
19242
19243 #[cfg(feature = "test-support")]
19244 pub fn all_text_background_highlights(
19245 &self,
19246 window: &mut Window,
19247 cx: &mut Context<Self>,
19248 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19249 let snapshot = self.snapshot(window, cx);
19250 let buffer = &snapshot.buffer_snapshot;
19251 let start = buffer.anchor_before(0);
19252 let end = buffer.anchor_after(buffer.len());
19253 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19254 }
19255
19256 #[cfg(feature = "test-support")]
19257 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19258 let snapshot = self.buffer().read(cx).snapshot(cx);
19259
19260 let highlights = self
19261 .background_highlights
19262 .get(&HighlightKey::Type(TypeId::of::<
19263 items::BufferSearchHighlights,
19264 >()));
19265
19266 if let Some((_color, ranges)) = highlights {
19267 ranges
19268 .iter()
19269 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19270 .collect_vec()
19271 } else {
19272 vec![]
19273 }
19274 }
19275
19276 fn document_highlights_for_position<'a>(
19277 &'a self,
19278 position: Anchor,
19279 buffer: &'a MultiBufferSnapshot,
19280 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19281 let read_highlights = self
19282 .background_highlights
19283 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19284 .map(|h| &h.1);
19285 let write_highlights = self
19286 .background_highlights
19287 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19288 .map(|h| &h.1);
19289 let left_position = position.bias_left(buffer);
19290 let right_position = position.bias_right(buffer);
19291 read_highlights
19292 .into_iter()
19293 .chain(write_highlights)
19294 .flat_map(move |ranges| {
19295 let start_ix = match ranges.binary_search_by(|probe| {
19296 let cmp = probe.end.cmp(&left_position, buffer);
19297 if cmp.is_ge() {
19298 Ordering::Greater
19299 } else {
19300 Ordering::Less
19301 }
19302 }) {
19303 Ok(i) | Err(i) => i,
19304 };
19305
19306 ranges[start_ix..]
19307 .iter()
19308 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19309 })
19310 }
19311
19312 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19313 self.background_highlights
19314 .get(&HighlightKey::Type(TypeId::of::<T>()))
19315 .map_or(false, |(_, highlights)| !highlights.is_empty())
19316 }
19317
19318 pub fn background_highlights_in_range(
19319 &self,
19320 search_range: Range<Anchor>,
19321 display_snapshot: &DisplaySnapshot,
19322 theme: &Theme,
19323 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19324 let mut results = Vec::new();
19325 for (color_fetcher, ranges) in self.background_highlights.values() {
19326 let color = color_fetcher(theme);
19327 let start_ix = match ranges.binary_search_by(|probe| {
19328 let cmp = probe
19329 .end
19330 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19331 if cmp.is_gt() {
19332 Ordering::Greater
19333 } else {
19334 Ordering::Less
19335 }
19336 }) {
19337 Ok(i) | Err(i) => i,
19338 };
19339 for range in &ranges[start_ix..] {
19340 if range
19341 .start
19342 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19343 .is_ge()
19344 {
19345 break;
19346 }
19347
19348 let start = range.start.to_display_point(display_snapshot);
19349 let end = range.end.to_display_point(display_snapshot);
19350 results.push((start..end, color))
19351 }
19352 }
19353 results
19354 }
19355
19356 pub fn background_highlight_row_ranges<T: 'static>(
19357 &self,
19358 search_range: Range<Anchor>,
19359 display_snapshot: &DisplaySnapshot,
19360 count: usize,
19361 ) -> Vec<RangeInclusive<DisplayPoint>> {
19362 let mut results = Vec::new();
19363 let Some((_, ranges)) = self
19364 .background_highlights
19365 .get(&HighlightKey::Type(TypeId::of::<T>()))
19366 else {
19367 return vec![];
19368 };
19369
19370 let start_ix = match ranges.binary_search_by(|probe| {
19371 let cmp = probe
19372 .end
19373 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19374 if cmp.is_gt() {
19375 Ordering::Greater
19376 } else {
19377 Ordering::Less
19378 }
19379 }) {
19380 Ok(i) | Err(i) => i,
19381 };
19382 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19383 if let (Some(start_display), Some(end_display)) = (start, end) {
19384 results.push(
19385 start_display.to_display_point(display_snapshot)
19386 ..=end_display.to_display_point(display_snapshot),
19387 );
19388 }
19389 };
19390 let mut start_row: Option<Point> = None;
19391 let mut end_row: Option<Point> = None;
19392 if ranges.len() > count {
19393 return Vec::new();
19394 }
19395 for range in &ranges[start_ix..] {
19396 if range
19397 .start
19398 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19399 .is_ge()
19400 {
19401 break;
19402 }
19403 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19404 if let Some(current_row) = &end_row {
19405 if end.row == current_row.row {
19406 continue;
19407 }
19408 }
19409 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19410 if start_row.is_none() {
19411 assert_eq!(end_row, None);
19412 start_row = Some(start);
19413 end_row = Some(end);
19414 continue;
19415 }
19416 if let Some(current_end) = end_row.as_mut() {
19417 if start.row > current_end.row + 1 {
19418 push_region(start_row, end_row);
19419 start_row = Some(start);
19420 end_row = Some(end);
19421 } else {
19422 // Merge two hunks.
19423 *current_end = end;
19424 }
19425 } else {
19426 unreachable!();
19427 }
19428 }
19429 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19430 push_region(start_row, end_row);
19431 results
19432 }
19433
19434 pub fn gutter_highlights_in_range(
19435 &self,
19436 search_range: Range<Anchor>,
19437 display_snapshot: &DisplaySnapshot,
19438 cx: &App,
19439 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19440 let mut results = Vec::new();
19441 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19442 let color = color_fetcher(cx);
19443 let start_ix = match ranges.binary_search_by(|probe| {
19444 let cmp = probe
19445 .end
19446 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19447 if cmp.is_gt() {
19448 Ordering::Greater
19449 } else {
19450 Ordering::Less
19451 }
19452 }) {
19453 Ok(i) | Err(i) => i,
19454 };
19455 for range in &ranges[start_ix..] {
19456 if range
19457 .start
19458 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19459 .is_ge()
19460 {
19461 break;
19462 }
19463
19464 let start = range.start.to_display_point(display_snapshot);
19465 let end = range.end.to_display_point(display_snapshot);
19466 results.push((start..end, color))
19467 }
19468 }
19469 results
19470 }
19471
19472 /// Get the text ranges corresponding to the redaction query
19473 pub fn redacted_ranges(
19474 &self,
19475 search_range: Range<Anchor>,
19476 display_snapshot: &DisplaySnapshot,
19477 cx: &App,
19478 ) -> Vec<Range<DisplayPoint>> {
19479 display_snapshot
19480 .buffer_snapshot
19481 .redacted_ranges(search_range, |file| {
19482 if let Some(file) = file {
19483 file.is_private()
19484 && EditorSettings::get(
19485 Some(SettingsLocation {
19486 worktree_id: file.worktree_id(cx),
19487 path: file.path().as_ref(),
19488 }),
19489 cx,
19490 )
19491 .redact_private_values
19492 } else {
19493 false
19494 }
19495 })
19496 .map(|range| {
19497 range.start.to_display_point(display_snapshot)
19498 ..range.end.to_display_point(display_snapshot)
19499 })
19500 .collect()
19501 }
19502
19503 pub fn highlight_text_key<T: 'static>(
19504 &mut self,
19505 key: usize,
19506 ranges: Vec<Range<Anchor>>,
19507 style: HighlightStyle,
19508 cx: &mut Context<Self>,
19509 ) {
19510 self.display_map.update(cx, |map, _| {
19511 map.highlight_text(
19512 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19513 ranges,
19514 style,
19515 );
19516 });
19517 cx.notify();
19518 }
19519
19520 pub fn highlight_text<T: 'static>(
19521 &mut self,
19522 ranges: Vec<Range<Anchor>>,
19523 style: HighlightStyle,
19524 cx: &mut Context<Self>,
19525 ) {
19526 self.display_map.update(cx, |map, _| {
19527 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19528 });
19529 cx.notify();
19530 }
19531
19532 pub(crate) fn highlight_inlays<T: 'static>(
19533 &mut self,
19534 highlights: Vec<InlayHighlight>,
19535 style: HighlightStyle,
19536 cx: &mut Context<Self>,
19537 ) {
19538 self.display_map.update(cx, |map, _| {
19539 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19540 });
19541 cx.notify();
19542 }
19543
19544 pub fn text_highlights<'a, T: 'static>(
19545 &'a self,
19546 cx: &'a App,
19547 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19548 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19549 }
19550
19551 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19552 let cleared = self
19553 .display_map
19554 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19555 if cleared {
19556 cx.notify();
19557 }
19558 }
19559
19560 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19561 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19562 && self.focus_handle.is_focused(window)
19563 }
19564
19565 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19566 self.show_cursor_when_unfocused = is_enabled;
19567 cx.notify();
19568 }
19569
19570 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19571 cx.notify();
19572 }
19573
19574 fn on_debug_session_event(
19575 &mut self,
19576 _session: Entity<Session>,
19577 event: &SessionEvent,
19578 cx: &mut Context<Self>,
19579 ) {
19580 match event {
19581 SessionEvent::InvalidateInlineValue => {
19582 self.refresh_inline_values(cx);
19583 }
19584 _ => {}
19585 }
19586 }
19587
19588 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19589 let Some(project) = self.project.clone() else {
19590 return;
19591 };
19592
19593 if !self.inline_value_cache.enabled {
19594 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19595 self.splice_inlays(&inlays, Vec::new(), cx);
19596 return;
19597 }
19598
19599 let current_execution_position = self
19600 .highlighted_rows
19601 .get(&TypeId::of::<ActiveDebugLine>())
19602 .and_then(|lines| lines.last().map(|line| line.range.end));
19603
19604 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19605 let inline_values = editor
19606 .update(cx, |editor, cx| {
19607 let Some(current_execution_position) = current_execution_position else {
19608 return Some(Task::ready(Ok(Vec::new())));
19609 };
19610
19611 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19612 let snapshot = buffer.snapshot(cx);
19613
19614 let excerpt = snapshot.excerpt_containing(
19615 current_execution_position..current_execution_position,
19616 )?;
19617
19618 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19619 })?;
19620
19621 let range =
19622 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19623
19624 project.inline_values(buffer, range, cx)
19625 })
19626 .ok()
19627 .flatten()?
19628 .await
19629 .context("refreshing debugger inlays")
19630 .log_err()?;
19631
19632 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19633
19634 for (buffer_id, inline_value) in inline_values
19635 .into_iter()
19636 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19637 {
19638 buffer_inline_values
19639 .entry(buffer_id)
19640 .or_default()
19641 .push(inline_value);
19642 }
19643
19644 editor
19645 .update(cx, |editor, cx| {
19646 let snapshot = editor.buffer.read(cx).snapshot(cx);
19647 let mut new_inlays = Vec::default();
19648
19649 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19650 let buffer_id = buffer_snapshot.remote_id();
19651 buffer_inline_values
19652 .get(&buffer_id)
19653 .into_iter()
19654 .flatten()
19655 .for_each(|hint| {
19656 let inlay = Inlay::debugger(
19657 post_inc(&mut editor.next_inlay_id),
19658 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19659 hint.text(),
19660 );
19661 if !inlay.text.chars().contains(&'\n') {
19662 new_inlays.push(inlay);
19663 }
19664 });
19665 }
19666
19667 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19668 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19669
19670 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19671 })
19672 .ok()?;
19673 Some(())
19674 });
19675 }
19676
19677 fn on_buffer_event(
19678 &mut self,
19679 multibuffer: &Entity<MultiBuffer>,
19680 event: &multi_buffer::Event,
19681 window: &mut Window,
19682 cx: &mut Context<Self>,
19683 ) {
19684 match event {
19685 multi_buffer::Event::Edited {
19686 singleton_buffer_edited,
19687 edited_buffer,
19688 } => {
19689 self.scrollbar_marker_state.dirty = true;
19690 self.active_indent_guides_state.dirty = true;
19691 self.refresh_active_diagnostics(cx);
19692 self.refresh_code_actions(window, cx);
19693 self.refresh_selected_text_highlights(true, window, cx);
19694 self.refresh_single_line_folds(window, cx);
19695 refresh_matching_bracket_highlights(self, window, cx);
19696 if self.has_active_inline_completion() {
19697 self.update_visible_inline_completion(window, cx);
19698 }
19699 if let Some(project) = self.project.as_ref() {
19700 if let Some(edited_buffer) = edited_buffer {
19701 project.update(cx, |project, cx| {
19702 self.registered_buffers
19703 .entry(edited_buffer.read(cx).remote_id())
19704 .or_insert_with(|| {
19705 project
19706 .register_buffer_with_language_servers(&edited_buffer, cx)
19707 });
19708 });
19709 }
19710 }
19711 cx.emit(EditorEvent::BufferEdited);
19712 cx.emit(SearchEvent::MatchesInvalidated);
19713
19714 if let Some(buffer) = edited_buffer {
19715 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
19716 }
19717
19718 if *singleton_buffer_edited {
19719 if let Some(buffer) = edited_buffer {
19720 if buffer.read(cx).file().is_none() {
19721 cx.emit(EditorEvent::TitleChanged);
19722 }
19723 }
19724 if let Some(project) = &self.project {
19725 #[allow(clippy::mutable_key_type)]
19726 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19727 multibuffer
19728 .all_buffers()
19729 .into_iter()
19730 .filter_map(|buffer| {
19731 buffer.update(cx, |buffer, cx| {
19732 let language = buffer.language()?;
19733 let should_discard = project.update(cx, |project, cx| {
19734 project.is_local()
19735 && !project.has_language_servers_for(buffer, cx)
19736 });
19737 should_discard.not().then_some(language.clone())
19738 })
19739 })
19740 .collect::<HashSet<_>>()
19741 });
19742 if !languages_affected.is_empty() {
19743 self.refresh_inlay_hints(
19744 InlayHintRefreshReason::BufferEdited(languages_affected),
19745 cx,
19746 );
19747 }
19748 }
19749 }
19750
19751 let Some(project) = &self.project else { return };
19752 let (telemetry, is_via_ssh) = {
19753 let project = project.read(cx);
19754 let telemetry = project.client().telemetry().clone();
19755 let is_via_ssh = project.is_via_ssh();
19756 (telemetry, is_via_ssh)
19757 };
19758 refresh_linked_ranges(self, window, cx);
19759 telemetry.log_edit_event("editor", is_via_ssh);
19760 }
19761 multi_buffer::Event::ExcerptsAdded {
19762 buffer,
19763 predecessor,
19764 excerpts,
19765 } => {
19766 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19767 let buffer_id = buffer.read(cx).remote_id();
19768 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19769 if let Some(project) = &self.project {
19770 update_uncommitted_diff_for_buffer(
19771 cx.entity(),
19772 project,
19773 [buffer.clone()],
19774 self.buffer.clone(),
19775 cx,
19776 )
19777 .detach();
19778 }
19779 }
19780 self.update_lsp_data(false, Some(buffer_id), window, cx);
19781 cx.emit(EditorEvent::ExcerptsAdded {
19782 buffer: buffer.clone(),
19783 predecessor: *predecessor,
19784 excerpts: excerpts.clone(),
19785 });
19786 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19787 }
19788 multi_buffer::Event::ExcerptsRemoved {
19789 ids,
19790 removed_buffer_ids,
19791 } => {
19792 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19793 let buffer = self.buffer.read(cx);
19794 self.registered_buffers
19795 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19796 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19797 cx.emit(EditorEvent::ExcerptsRemoved {
19798 ids: ids.clone(),
19799 removed_buffer_ids: removed_buffer_ids.clone(),
19800 });
19801 }
19802 multi_buffer::Event::ExcerptsEdited {
19803 excerpt_ids,
19804 buffer_ids,
19805 } => {
19806 self.display_map.update(cx, |map, cx| {
19807 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19808 });
19809 cx.emit(EditorEvent::ExcerptsEdited {
19810 ids: excerpt_ids.clone(),
19811 });
19812 }
19813 multi_buffer::Event::ExcerptsExpanded { ids } => {
19814 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19815 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19816 }
19817 multi_buffer::Event::Reparsed(buffer_id) => {
19818 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19819 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19820
19821 cx.emit(EditorEvent::Reparsed(*buffer_id));
19822 }
19823 multi_buffer::Event::DiffHunksToggled => {
19824 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19825 }
19826 multi_buffer::Event::LanguageChanged(buffer_id) => {
19827 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19828 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19829 cx.emit(EditorEvent::Reparsed(*buffer_id));
19830 cx.notify();
19831 }
19832 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19833 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19834 multi_buffer::Event::FileHandleChanged
19835 | multi_buffer::Event::Reloaded
19836 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19837 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19838 multi_buffer::Event::DiagnosticsUpdated => {
19839 self.update_diagnostics_state(window, cx);
19840 }
19841 _ => {}
19842 };
19843 }
19844
19845 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19846 if !self.diagnostics_enabled() {
19847 return;
19848 }
19849 self.refresh_active_diagnostics(cx);
19850 self.refresh_inline_diagnostics(true, window, cx);
19851 self.scrollbar_marker_state.dirty = true;
19852 cx.notify();
19853 }
19854
19855 pub fn start_temporary_diff_override(&mut self) {
19856 self.load_diff_task.take();
19857 self.temporary_diff_override = true;
19858 }
19859
19860 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19861 self.temporary_diff_override = false;
19862 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19863 self.buffer.update(cx, |buffer, cx| {
19864 buffer.set_all_diff_hunks_collapsed(cx);
19865 });
19866
19867 if let Some(project) = self.project.clone() {
19868 self.load_diff_task = Some(
19869 update_uncommitted_diff_for_buffer(
19870 cx.entity(),
19871 &project,
19872 self.buffer.read(cx).all_buffers(),
19873 self.buffer.clone(),
19874 cx,
19875 )
19876 .shared(),
19877 );
19878 }
19879 }
19880
19881 fn on_display_map_changed(
19882 &mut self,
19883 _: Entity<DisplayMap>,
19884 _: &mut Window,
19885 cx: &mut Context<Self>,
19886 ) {
19887 cx.notify();
19888 }
19889
19890 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19891 let new_severity = if self.diagnostics_enabled() {
19892 EditorSettings::get_global(cx)
19893 .diagnostics_max_severity
19894 .unwrap_or(DiagnosticSeverity::Hint)
19895 } else {
19896 DiagnosticSeverity::Off
19897 };
19898 self.set_max_diagnostics_severity(new_severity, cx);
19899 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19900 self.update_edit_prediction_settings(cx);
19901 self.refresh_inline_completion(true, false, window, cx);
19902 self.refresh_inline_values(cx);
19903 self.refresh_inlay_hints(
19904 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19905 self.selections.newest_anchor().head(),
19906 &self.buffer.read(cx).snapshot(cx),
19907 cx,
19908 )),
19909 cx,
19910 );
19911
19912 let old_cursor_shape = self.cursor_shape;
19913
19914 {
19915 let editor_settings = EditorSettings::get_global(cx);
19916 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19917 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19918 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19919 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19920 }
19921
19922 if old_cursor_shape != self.cursor_shape {
19923 cx.emit(EditorEvent::CursorShapeChanged);
19924 }
19925
19926 let project_settings = ProjectSettings::get_global(cx);
19927 self.serialize_dirty_buffers =
19928 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19929
19930 if self.mode.is_full() {
19931 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19932 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19933 if self.show_inline_diagnostics != show_inline_diagnostics {
19934 self.show_inline_diagnostics = show_inline_diagnostics;
19935 self.refresh_inline_diagnostics(false, window, cx);
19936 }
19937
19938 if self.git_blame_inline_enabled != inline_blame_enabled {
19939 self.toggle_git_blame_inline_internal(false, window, cx);
19940 }
19941
19942 let minimap_settings = EditorSettings::get_global(cx).minimap;
19943 if self.minimap_visibility != MinimapVisibility::Disabled {
19944 if self.minimap_visibility.settings_visibility()
19945 != minimap_settings.minimap_enabled()
19946 {
19947 self.set_minimap_visibility(
19948 MinimapVisibility::for_mode(self.mode(), cx),
19949 window,
19950 cx,
19951 );
19952 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19953 minimap_entity.update(cx, |minimap_editor, cx| {
19954 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19955 })
19956 }
19957 }
19958 }
19959
19960 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
19961 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
19962 }) {
19963 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
19964 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
19965 }
19966 self.refresh_colors(false, None, window, cx);
19967 }
19968
19969 cx.notify();
19970 }
19971
19972 pub fn set_searchable(&mut self, searchable: bool) {
19973 self.searchable = searchable;
19974 }
19975
19976 pub fn searchable(&self) -> bool {
19977 self.searchable
19978 }
19979
19980 fn open_proposed_changes_editor(
19981 &mut self,
19982 _: &OpenProposedChangesEditor,
19983 window: &mut Window,
19984 cx: &mut Context<Self>,
19985 ) {
19986 let Some(workspace) = self.workspace() else {
19987 cx.propagate();
19988 return;
19989 };
19990
19991 let selections = self.selections.all::<usize>(cx);
19992 let multi_buffer = self.buffer.read(cx);
19993 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19994 let mut new_selections_by_buffer = HashMap::default();
19995 for selection in selections {
19996 for (buffer, range, _) in
19997 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19998 {
19999 let mut range = range.to_point(buffer);
20000 range.start.column = 0;
20001 range.end.column = buffer.line_len(range.end.row);
20002 new_selections_by_buffer
20003 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20004 .or_insert(Vec::new())
20005 .push(range)
20006 }
20007 }
20008
20009 let proposed_changes_buffers = new_selections_by_buffer
20010 .into_iter()
20011 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20012 .collect::<Vec<_>>();
20013 let proposed_changes_editor = cx.new(|cx| {
20014 ProposedChangesEditor::new(
20015 "Proposed changes",
20016 proposed_changes_buffers,
20017 self.project.clone(),
20018 window,
20019 cx,
20020 )
20021 });
20022
20023 window.defer(cx, move |window, cx| {
20024 workspace.update(cx, |workspace, cx| {
20025 workspace.active_pane().update(cx, |pane, cx| {
20026 pane.add_item(
20027 Box::new(proposed_changes_editor),
20028 true,
20029 true,
20030 None,
20031 window,
20032 cx,
20033 );
20034 });
20035 });
20036 });
20037 }
20038
20039 pub fn open_excerpts_in_split(
20040 &mut self,
20041 _: &OpenExcerptsSplit,
20042 window: &mut Window,
20043 cx: &mut Context<Self>,
20044 ) {
20045 self.open_excerpts_common(None, true, window, cx)
20046 }
20047
20048 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20049 self.open_excerpts_common(None, false, window, cx)
20050 }
20051
20052 fn open_excerpts_common(
20053 &mut self,
20054 jump_data: Option<JumpData>,
20055 split: bool,
20056 window: &mut Window,
20057 cx: &mut Context<Self>,
20058 ) {
20059 let Some(workspace) = self.workspace() else {
20060 cx.propagate();
20061 return;
20062 };
20063
20064 if self.buffer.read(cx).is_singleton() {
20065 cx.propagate();
20066 return;
20067 }
20068
20069 let mut new_selections_by_buffer = HashMap::default();
20070 match &jump_data {
20071 Some(JumpData::MultiBufferPoint {
20072 excerpt_id,
20073 position,
20074 anchor,
20075 line_offset_from_top,
20076 }) => {
20077 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20078 if let Some(buffer) = multi_buffer_snapshot
20079 .buffer_id_for_excerpt(*excerpt_id)
20080 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20081 {
20082 let buffer_snapshot = buffer.read(cx).snapshot();
20083 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20084 language::ToPoint::to_point(anchor, &buffer_snapshot)
20085 } else {
20086 buffer_snapshot.clip_point(*position, Bias::Left)
20087 };
20088 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20089 new_selections_by_buffer.insert(
20090 buffer,
20091 (
20092 vec![jump_to_offset..jump_to_offset],
20093 Some(*line_offset_from_top),
20094 ),
20095 );
20096 }
20097 }
20098 Some(JumpData::MultiBufferRow {
20099 row,
20100 line_offset_from_top,
20101 }) => {
20102 let point = MultiBufferPoint::new(row.0, 0);
20103 if let Some((buffer, buffer_point, _)) =
20104 self.buffer.read(cx).point_to_buffer_point(point, cx)
20105 {
20106 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20107 new_selections_by_buffer
20108 .entry(buffer)
20109 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20110 .0
20111 .push(buffer_offset..buffer_offset)
20112 }
20113 }
20114 None => {
20115 let selections = self.selections.all::<usize>(cx);
20116 let multi_buffer = self.buffer.read(cx);
20117 for selection in selections {
20118 for (snapshot, range, _, anchor) in multi_buffer
20119 .snapshot(cx)
20120 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20121 {
20122 if let Some(anchor) = anchor {
20123 // selection is in a deleted hunk
20124 let Some(buffer_id) = anchor.buffer_id else {
20125 continue;
20126 };
20127 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20128 continue;
20129 };
20130 let offset = text::ToOffset::to_offset(
20131 &anchor.text_anchor,
20132 &buffer_handle.read(cx).snapshot(),
20133 );
20134 let range = offset..offset;
20135 new_selections_by_buffer
20136 .entry(buffer_handle)
20137 .or_insert((Vec::new(), None))
20138 .0
20139 .push(range)
20140 } else {
20141 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20142 else {
20143 continue;
20144 };
20145 new_selections_by_buffer
20146 .entry(buffer_handle)
20147 .or_insert((Vec::new(), None))
20148 .0
20149 .push(range)
20150 }
20151 }
20152 }
20153 }
20154 }
20155
20156 new_selections_by_buffer
20157 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20158
20159 if new_selections_by_buffer.is_empty() {
20160 return;
20161 }
20162
20163 // We defer the pane interaction because we ourselves are a workspace item
20164 // and activating a new item causes the pane to call a method on us reentrantly,
20165 // which panics if we're on the stack.
20166 window.defer(cx, move |window, cx| {
20167 workspace.update(cx, |workspace, cx| {
20168 let pane = if split {
20169 workspace.adjacent_pane(window, cx)
20170 } else {
20171 workspace.active_pane().clone()
20172 };
20173
20174 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20175 let editor = buffer
20176 .read(cx)
20177 .file()
20178 .is_none()
20179 .then(|| {
20180 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20181 // so `workspace.open_project_item` will never find them, always opening a new editor.
20182 // Instead, we try to activate the existing editor in the pane first.
20183 let (editor, pane_item_index) =
20184 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20185 let editor = item.downcast::<Editor>()?;
20186 let singleton_buffer =
20187 editor.read(cx).buffer().read(cx).as_singleton()?;
20188 if singleton_buffer == buffer {
20189 Some((editor, i))
20190 } else {
20191 None
20192 }
20193 })?;
20194 pane.update(cx, |pane, cx| {
20195 pane.activate_item(pane_item_index, true, true, window, cx)
20196 });
20197 Some(editor)
20198 })
20199 .flatten()
20200 .unwrap_or_else(|| {
20201 workspace.open_project_item::<Self>(
20202 pane.clone(),
20203 buffer,
20204 true,
20205 true,
20206 window,
20207 cx,
20208 )
20209 });
20210
20211 editor.update(cx, |editor, cx| {
20212 let autoscroll = match scroll_offset {
20213 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20214 None => Autoscroll::newest(),
20215 };
20216 let nav_history = editor.nav_history.take();
20217 editor.change_selections(
20218 SelectionEffects::scroll(autoscroll),
20219 window,
20220 cx,
20221 |s| {
20222 s.select_ranges(ranges);
20223 },
20224 );
20225 editor.nav_history = nav_history;
20226 });
20227 }
20228 })
20229 });
20230 }
20231
20232 // For now, don't allow opening excerpts in buffers that aren't backed by
20233 // regular project files.
20234 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20235 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
20236 }
20237
20238 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20239 let snapshot = self.buffer.read(cx).read(cx);
20240 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20241 Some(
20242 ranges
20243 .iter()
20244 .map(move |range| {
20245 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20246 })
20247 .collect(),
20248 )
20249 }
20250
20251 fn selection_replacement_ranges(
20252 &self,
20253 range: Range<OffsetUtf16>,
20254 cx: &mut App,
20255 ) -> Vec<Range<OffsetUtf16>> {
20256 let selections = self.selections.all::<OffsetUtf16>(cx);
20257 let newest_selection = selections
20258 .iter()
20259 .max_by_key(|selection| selection.id)
20260 .unwrap();
20261 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20262 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20263 let snapshot = self.buffer.read(cx).read(cx);
20264 selections
20265 .into_iter()
20266 .map(|mut selection| {
20267 selection.start.0 =
20268 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20269 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20270 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20271 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20272 })
20273 .collect()
20274 }
20275
20276 fn report_editor_event(
20277 &self,
20278 event_type: &'static str,
20279 file_extension: Option<String>,
20280 cx: &App,
20281 ) {
20282 if cfg!(any(test, feature = "test-support")) {
20283 return;
20284 }
20285
20286 let Some(project) = &self.project else { return };
20287
20288 // If None, we are in a file without an extension
20289 let file = self
20290 .buffer
20291 .read(cx)
20292 .as_singleton()
20293 .and_then(|b| b.read(cx).file());
20294 let file_extension = file_extension.or(file
20295 .as_ref()
20296 .and_then(|file| Path::new(file.file_name(cx)).extension())
20297 .and_then(|e| e.to_str())
20298 .map(|a| a.to_string()));
20299
20300 let vim_mode = vim_enabled(cx);
20301
20302 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20303 let copilot_enabled = edit_predictions_provider
20304 == language::language_settings::EditPredictionProvider::Copilot;
20305 let copilot_enabled_for_language = self
20306 .buffer
20307 .read(cx)
20308 .language_settings(cx)
20309 .show_edit_predictions;
20310
20311 let project = project.read(cx);
20312 telemetry::event!(
20313 event_type,
20314 file_extension,
20315 vim_mode,
20316 copilot_enabled,
20317 copilot_enabled_for_language,
20318 edit_predictions_provider,
20319 is_via_ssh = project.is_via_ssh(),
20320 );
20321 }
20322
20323 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20324 /// with each line being an array of {text, highlight} objects.
20325 fn copy_highlight_json(
20326 &mut self,
20327 _: &CopyHighlightJson,
20328 window: &mut Window,
20329 cx: &mut Context<Self>,
20330 ) {
20331 #[derive(Serialize)]
20332 struct Chunk<'a> {
20333 text: String,
20334 highlight: Option<&'a str>,
20335 }
20336
20337 let snapshot = self.buffer.read(cx).snapshot(cx);
20338 let range = self
20339 .selected_text_range(false, window, cx)
20340 .and_then(|selection| {
20341 if selection.range.is_empty() {
20342 None
20343 } else {
20344 Some(selection.range)
20345 }
20346 })
20347 .unwrap_or_else(|| 0..snapshot.len());
20348
20349 let chunks = snapshot.chunks(range, true);
20350 let mut lines = Vec::new();
20351 let mut line: VecDeque<Chunk> = VecDeque::new();
20352
20353 let Some(style) = self.style.as_ref() else {
20354 return;
20355 };
20356
20357 for chunk in chunks {
20358 let highlight = chunk
20359 .syntax_highlight_id
20360 .and_then(|id| id.name(&style.syntax));
20361 let mut chunk_lines = chunk.text.split('\n').peekable();
20362 while let Some(text) = chunk_lines.next() {
20363 let mut merged_with_last_token = false;
20364 if let Some(last_token) = line.back_mut() {
20365 if last_token.highlight == highlight {
20366 last_token.text.push_str(text);
20367 merged_with_last_token = true;
20368 }
20369 }
20370
20371 if !merged_with_last_token {
20372 line.push_back(Chunk {
20373 text: text.into(),
20374 highlight,
20375 });
20376 }
20377
20378 if chunk_lines.peek().is_some() {
20379 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20380 line.pop_front();
20381 }
20382 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20383 line.pop_back();
20384 }
20385
20386 lines.push(mem::take(&mut line));
20387 }
20388 }
20389 }
20390
20391 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20392 return;
20393 };
20394 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20395 }
20396
20397 pub fn open_context_menu(
20398 &mut self,
20399 _: &OpenContextMenu,
20400 window: &mut Window,
20401 cx: &mut Context<Self>,
20402 ) {
20403 self.request_autoscroll(Autoscroll::newest(), cx);
20404 let position = self.selections.newest_display(cx).start;
20405 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20406 }
20407
20408 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20409 &self.inlay_hint_cache
20410 }
20411
20412 pub fn replay_insert_event(
20413 &mut self,
20414 text: &str,
20415 relative_utf16_range: Option<Range<isize>>,
20416 window: &mut Window,
20417 cx: &mut Context<Self>,
20418 ) {
20419 if !self.input_enabled {
20420 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20421 return;
20422 }
20423 if let Some(relative_utf16_range) = relative_utf16_range {
20424 let selections = self.selections.all::<OffsetUtf16>(cx);
20425 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20426 let new_ranges = selections.into_iter().map(|range| {
20427 let start = OffsetUtf16(
20428 range
20429 .head()
20430 .0
20431 .saturating_add_signed(relative_utf16_range.start),
20432 );
20433 let end = OffsetUtf16(
20434 range
20435 .head()
20436 .0
20437 .saturating_add_signed(relative_utf16_range.end),
20438 );
20439 start..end
20440 });
20441 s.select_ranges(new_ranges);
20442 });
20443 }
20444
20445 self.handle_input(text, window, cx);
20446 }
20447
20448 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20449 let Some(provider) = self.semantics_provider.as_ref() else {
20450 return false;
20451 };
20452
20453 let mut supports = false;
20454 self.buffer().update(cx, |this, cx| {
20455 this.for_each_buffer(|buffer| {
20456 supports |= provider.supports_inlay_hints(buffer, cx);
20457 });
20458 });
20459
20460 supports
20461 }
20462
20463 pub fn is_focused(&self, window: &Window) -> bool {
20464 self.focus_handle.is_focused(window)
20465 }
20466
20467 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20468 cx.emit(EditorEvent::Focused);
20469
20470 if let Some(descendant) = self
20471 .last_focused_descendant
20472 .take()
20473 .and_then(|descendant| descendant.upgrade())
20474 {
20475 window.focus(&descendant);
20476 } else {
20477 if let Some(blame) = self.blame.as_ref() {
20478 blame.update(cx, GitBlame::focus)
20479 }
20480
20481 self.blink_manager.update(cx, BlinkManager::enable);
20482 self.show_cursor_names(window, cx);
20483 self.buffer.update(cx, |buffer, cx| {
20484 buffer.finalize_last_transaction(cx);
20485 if self.leader_id.is_none() {
20486 buffer.set_active_selections(
20487 &self.selections.disjoint_anchors(),
20488 self.selections.line_mode,
20489 self.cursor_shape,
20490 cx,
20491 );
20492 }
20493 });
20494 }
20495 }
20496
20497 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20498 cx.emit(EditorEvent::FocusedIn)
20499 }
20500
20501 fn handle_focus_out(
20502 &mut self,
20503 event: FocusOutEvent,
20504 _window: &mut Window,
20505 cx: &mut Context<Self>,
20506 ) {
20507 if event.blurred != self.focus_handle {
20508 self.last_focused_descendant = Some(event.blurred);
20509 }
20510 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20511 }
20512
20513 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20514 self.blink_manager.update(cx, BlinkManager::disable);
20515 self.buffer
20516 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20517
20518 if let Some(blame) = self.blame.as_ref() {
20519 blame.update(cx, GitBlame::blur)
20520 }
20521 if !self.hover_state.focused(window, cx) {
20522 hide_hover(self, cx);
20523 }
20524 if !self
20525 .context_menu
20526 .borrow()
20527 .as_ref()
20528 .is_some_and(|context_menu| context_menu.focused(window, cx))
20529 {
20530 self.hide_context_menu(window, cx);
20531 }
20532 self.discard_inline_completion(false, cx);
20533 cx.emit(EditorEvent::Blurred);
20534 cx.notify();
20535 }
20536
20537 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20538 let mut pending: String = window
20539 .pending_input_keystrokes()
20540 .into_iter()
20541 .flatten()
20542 .filter_map(|keystroke| {
20543 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20544 keystroke.key_char.clone()
20545 } else {
20546 None
20547 }
20548 })
20549 .collect();
20550
20551 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20552 pending = "".to_string();
20553 }
20554
20555 let existing_pending = self
20556 .text_highlights::<PendingInput>(cx)
20557 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20558 if existing_pending.is_none() && pending.is_empty() {
20559 return;
20560 }
20561 let transaction =
20562 self.transact(window, cx, |this, window, cx| {
20563 let selections = this.selections.all::<usize>(cx);
20564 let edits = selections
20565 .iter()
20566 .map(|selection| (selection.end..selection.end, pending.clone()));
20567 this.edit(edits, cx);
20568 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20569 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20570 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20571 }));
20572 });
20573 if let Some(existing_ranges) = existing_pending {
20574 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20575 this.edit(edits, cx);
20576 }
20577 });
20578
20579 let snapshot = self.snapshot(window, cx);
20580 let ranges = self
20581 .selections
20582 .all::<usize>(cx)
20583 .into_iter()
20584 .map(|selection| {
20585 snapshot.buffer_snapshot.anchor_after(selection.end)
20586 ..snapshot
20587 .buffer_snapshot
20588 .anchor_before(selection.end + pending.len())
20589 })
20590 .collect();
20591
20592 if pending.is_empty() {
20593 self.clear_highlights::<PendingInput>(cx);
20594 } else {
20595 self.highlight_text::<PendingInput>(
20596 ranges,
20597 HighlightStyle {
20598 underline: Some(UnderlineStyle {
20599 thickness: px(1.),
20600 color: None,
20601 wavy: false,
20602 }),
20603 ..Default::default()
20604 },
20605 cx,
20606 );
20607 }
20608
20609 self.ime_transaction = self.ime_transaction.or(transaction);
20610 if let Some(transaction) = self.ime_transaction {
20611 self.buffer.update(cx, |buffer, cx| {
20612 buffer.group_until_transaction(transaction, cx);
20613 });
20614 }
20615
20616 if self.text_highlights::<PendingInput>(cx).is_none() {
20617 self.ime_transaction.take();
20618 }
20619 }
20620
20621 pub fn register_action_renderer(
20622 &mut self,
20623 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20624 ) -> Subscription {
20625 let id = self.next_editor_action_id.post_inc();
20626 self.editor_actions
20627 .borrow_mut()
20628 .insert(id, Box::new(listener));
20629
20630 let editor_actions = self.editor_actions.clone();
20631 Subscription::new(move || {
20632 editor_actions.borrow_mut().remove(&id);
20633 })
20634 }
20635
20636 pub fn register_action<A: Action>(
20637 &mut self,
20638 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20639 ) -> Subscription {
20640 let id = self.next_editor_action_id.post_inc();
20641 let listener = Arc::new(listener);
20642 self.editor_actions.borrow_mut().insert(
20643 id,
20644 Box::new(move |_, window, _| {
20645 let listener = listener.clone();
20646 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20647 let action = action.downcast_ref().unwrap();
20648 if phase == DispatchPhase::Bubble {
20649 listener(action, window, cx)
20650 }
20651 })
20652 }),
20653 );
20654
20655 let editor_actions = self.editor_actions.clone();
20656 Subscription::new(move || {
20657 editor_actions.borrow_mut().remove(&id);
20658 })
20659 }
20660
20661 pub fn file_header_size(&self) -> u32 {
20662 FILE_HEADER_HEIGHT
20663 }
20664
20665 pub fn restore(
20666 &mut self,
20667 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20668 window: &mut Window,
20669 cx: &mut Context<Self>,
20670 ) {
20671 let workspace = self.workspace();
20672 let project = self.project.as_ref();
20673 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20674 let mut tasks = Vec::new();
20675 for (buffer_id, changes) in revert_changes {
20676 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20677 buffer.update(cx, |buffer, cx| {
20678 buffer.edit(
20679 changes
20680 .into_iter()
20681 .map(|(range, text)| (range, text.to_string())),
20682 None,
20683 cx,
20684 );
20685 });
20686
20687 if let Some(project) =
20688 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20689 {
20690 project.update(cx, |project, cx| {
20691 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20692 })
20693 }
20694 }
20695 }
20696 tasks
20697 });
20698 cx.spawn_in(window, async move |_, cx| {
20699 for (buffer, task) in save_tasks {
20700 let result = task.await;
20701 if result.is_err() {
20702 let Some(path) = buffer
20703 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20704 .ok()
20705 else {
20706 continue;
20707 };
20708 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20709 let Some(task) = cx
20710 .update_window_entity(&workspace, |workspace, window, cx| {
20711 workspace
20712 .open_path_preview(path, None, false, false, false, window, cx)
20713 })
20714 .ok()
20715 else {
20716 continue;
20717 };
20718 task.await.log_err();
20719 }
20720 }
20721 }
20722 })
20723 .detach();
20724 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20725 selections.refresh()
20726 });
20727 }
20728
20729 pub fn to_pixel_point(
20730 &self,
20731 source: multi_buffer::Anchor,
20732 editor_snapshot: &EditorSnapshot,
20733 window: &mut Window,
20734 ) -> Option<gpui::Point<Pixels>> {
20735 let source_point = source.to_display_point(editor_snapshot);
20736 self.display_to_pixel_point(source_point, editor_snapshot, window)
20737 }
20738
20739 pub fn display_to_pixel_point(
20740 &self,
20741 source: DisplayPoint,
20742 editor_snapshot: &EditorSnapshot,
20743 window: &mut Window,
20744 ) -> Option<gpui::Point<Pixels>> {
20745 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20746 let text_layout_details = self.text_layout_details(window);
20747 let scroll_top = text_layout_details
20748 .scroll_anchor
20749 .scroll_position(editor_snapshot)
20750 .y;
20751
20752 if source.row().as_f32() < scroll_top.floor() {
20753 return None;
20754 }
20755 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20756 let source_y = line_height * (source.row().as_f32() - scroll_top);
20757 Some(gpui::Point::new(source_x, source_y))
20758 }
20759
20760 pub fn has_visible_completions_menu(&self) -> bool {
20761 !self.edit_prediction_preview_is_active()
20762 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20763 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20764 })
20765 }
20766
20767 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20768 if self.mode.is_minimap() {
20769 return;
20770 }
20771 self.addons
20772 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20773 }
20774
20775 pub fn unregister_addon<T: Addon>(&mut self) {
20776 self.addons.remove(&std::any::TypeId::of::<T>());
20777 }
20778
20779 pub fn addon<T: Addon>(&self) -> Option<&T> {
20780 let type_id = std::any::TypeId::of::<T>();
20781 self.addons
20782 .get(&type_id)
20783 .and_then(|item| item.to_any().downcast_ref::<T>())
20784 }
20785
20786 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20787 let type_id = std::any::TypeId::of::<T>();
20788 self.addons
20789 .get_mut(&type_id)
20790 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20791 }
20792
20793 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
20794 let text_layout_details = self.text_layout_details(window);
20795 let style = &text_layout_details.editor_style;
20796 let font_id = window.text_system().resolve_font(&style.text.font());
20797 let font_size = style.text.font_size.to_pixels(window.rem_size());
20798 let line_height = style.text.line_height_in_pixels(window.rem_size());
20799 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20800 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
20801
20802 CharacterDimensions {
20803 em_width,
20804 em_advance,
20805 line_height,
20806 }
20807 }
20808
20809 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20810 self.load_diff_task.clone()
20811 }
20812
20813 fn read_metadata_from_db(
20814 &mut self,
20815 item_id: u64,
20816 workspace_id: WorkspaceId,
20817 window: &mut Window,
20818 cx: &mut Context<Editor>,
20819 ) {
20820 if self.is_singleton(cx)
20821 && !self.mode.is_minimap()
20822 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20823 {
20824 let buffer_snapshot = OnceCell::new();
20825
20826 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20827 if !folds.is_empty() {
20828 let snapshot =
20829 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20830 self.fold_ranges(
20831 folds
20832 .into_iter()
20833 .map(|(start, end)| {
20834 snapshot.clip_offset(start, Bias::Left)
20835 ..snapshot.clip_offset(end, Bias::Right)
20836 })
20837 .collect(),
20838 false,
20839 window,
20840 cx,
20841 );
20842 }
20843 }
20844
20845 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20846 if !selections.is_empty() {
20847 let snapshot =
20848 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20849 // skip adding the initial selection to selection history
20850 self.selection_history.mode = SelectionHistoryMode::Skipping;
20851 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20852 s.select_ranges(selections.into_iter().map(|(start, end)| {
20853 snapshot.clip_offset(start, Bias::Left)
20854 ..snapshot.clip_offset(end, Bias::Right)
20855 }));
20856 });
20857 self.selection_history.mode = SelectionHistoryMode::Normal;
20858 }
20859 };
20860 }
20861
20862 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20863 }
20864
20865 fn update_lsp_data(
20866 &mut self,
20867 ignore_cache: bool,
20868 for_buffer: Option<BufferId>,
20869 window: &mut Window,
20870 cx: &mut Context<'_, Self>,
20871 ) {
20872 self.pull_diagnostics(for_buffer, window, cx);
20873 self.refresh_colors(ignore_cache, for_buffer, window, cx);
20874 }
20875}
20876
20877fn vim_enabled(cx: &App) -> bool {
20878 cx.global::<SettingsStore>()
20879 .raw_user_settings()
20880 .get("vim_mode")
20881 == Some(&serde_json::Value::Bool(true))
20882}
20883
20884fn process_completion_for_edit(
20885 completion: &Completion,
20886 intent: CompletionIntent,
20887 buffer: &Entity<Buffer>,
20888 cursor_position: &text::Anchor,
20889 cx: &mut Context<Editor>,
20890) -> CompletionEdit {
20891 let buffer = buffer.read(cx);
20892 let buffer_snapshot = buffer.snapshot();
20893 let (snippet, new_text) = if completion.is_snippet() {
20894 // Workaround for typescript language server issues so that methods don't expand within
20895 // strings and functions with type expressions. The previous point is used because the query
20896 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20897 let mut snippet_source = completion.new_text.clone();
20898 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20899 previous_point.column = previous_point.column.saturating_sub(1);
20900 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20901 if scope.prefers_label_for_snippet_in_completion() {
20902 if let Some(label) = completion.label() {
20903 if matches!(
20904 completion.kind(),
20905 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20906 ) {
20907 snippet_source = label;
20908 }
20909 }
20910 }
20911 }
20912 match Snippet::parse(&snippet_source).log_err() {
20913 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20914 None => (None, completion.new_text.clone()),
20915 }
20916 } else {
20917 (None, completion.new_text.clone())
20918 };
20919
20920 let mut range_to_replace = {
20921 let replace_range = &completion.replace_range;
20922 if let CompletionSource::Lsp {
20923 insert_range: Some(insert_range),
20924 ..
20925 } = &completion.source
20926 {
20927 debug_assert_eq!(
20928 insert_range.start, replace_range.start,
20929 "insert_range and replace_range should start at the same position"
20930 );
20931 debug_assert!(
20932 insert_range
20933 .start
20934 .cmp(&cursor_position, &buffer_snapshot)
20935 .is_le(),
20936 "insert_range should start before or at cursor position"
20937 );
20938 debug_assert!(
20939 replace_range
20940 .start
20941 .cmp(&cursor_position, &buffer_snapshot)
20942 .is_le(),
20943 "replace_range should start before or at cursor position"
20944 );
20945 debug_assert!(
20946 insert_range
20947 .end
20948 .cmp(&cursor_position, &buffer_snapshot)
20949 .is_le(),
20950 "insert_range should end before or at cursor position"
20951 );
20952
20953 let should_replace = match intent {
20954 CompletionIntent::CompleteWithInsert => false,
20955 CompletionIntent::CompleteWithReplace => true,
20956 CompletionIntent::Complete | CompletionIntent::Compose => {
20957 let insert_mode =
20958 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20959 .completions
20960 .lsp_insert_mode;
20961 match insert_mode {
20962 LspInsertMode::Insert => false,
20963 LspInsertMode::Replace => true,
20964 LspInsertMode::ReplaceSubsequence => {
20965 let mut text_to_replace = buffer.chars_for_range(
20966 buffer.anchor_before(replace_range.start)
20967 ..buffer.anchor_after(replace_range.end),
20968 );
20969 let mut current_needle = text_to_replace.next();
20970 for haystack_ch in completion.label.text.chars() {
20971 if let Some(needle_ch) = current_needle {
20972 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20973 current_needle = text_to_replace.next();
20974 }
20975 }
20976 }
20977 current_needle.is_none()
20978 }
20979 LspInsertMode::ReplaceSuffix => {
20980 if replace_range
20981 .end
20982 .cmp(&cursor_position, &buffer_snapshot)
20983 .is_gt()
20984 {
20985 let range_after_cursor = *cursor_position..replace_range.end;
20986 let text_after_cursor = buffer
20987 .text_for_range(
20988 buffer.anchor_before(range_after_cursor.start)
20989 ..buffer.anchor_after(range_after_cursor.end),
20990 )
20991 .collect::<String>()
20992 .to_ascii_lowercase();
20993 completion
20994 .label
20995 .text
20996 .to_ascii_lowercase()
20997 .ends_with(&text_after_cursor)
20998 } else {
20999 true
21000 }
21001 }
21002 }
21003 }
21004 };
21005
21006 if should_replace {
21007 replace_range.clone()
21008 } else {
21009 insert_range.clone()
21010 }
21011 } else {
21012 replace_range.clone()
21013 }
21014 };
21015
21016 if range_to_replace
21017 .end
21018 .cmp(&cursor_position, &buffer_snapshot)
21019 .is_lt()
21020 {
21021 range_to_replace.end = *cursor_position;
21022 }
21023
21024 CompletionEdit {
21025 new_text,
21026 replace_range: range_to_replace.to_offset(&buffer),
21027 snippet,
21028 }
21029}
21030
21031struct CompletionEdit {
21032 new_text: String,
21033 replace_range: Range<usize>,
21034 snippet: Option<Snippet>,
21035}
21036
21037fn insert_extra_newline_brackets(
21038 buffer: &MultiBufferSnapshot,
21039 range: Range<usize>,
21040 language: &language::LanguageScope,
21041) -> bool {
21042 let leading_whitespace_len = buffer
21043 .reversed_chars_at(range.start)
21044 .take_while(|c| c.is_whitespace() && *c != '\n')
21045 .map(|c| c.len_utf8())
21046 .sum::<usize>();
21047 let trailing_whitespace_len = buffer
21048 .chars_at(range.end)
21049 .take_while(|c| c.is_whitespace() && *c != '\n')
21050 .map(|c| c.len_utf8())
21051 .sum::<usize>();
21052 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21053
21054 language.brackets().any(|(pair, enabled)| {
21055 let pair_start = pair.start.trim_end();
21056 let pair_end = pair.end.trim_start();
21057
21058 enabled
21059 && pair.newline
21060 && buffer.contains_str_at(range.end, pair_end)
21061 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21062 })
21063}
21064
21065fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21066 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21067 [(buffer, range, _)] => (*buffer, range.clone()),
21068 _ => return false,
21069 };
21070 let pair = {
21071 let mut result: Option<BracketMatch> = None;
21072
21073 for pair in buffer
21074 .all_bracket_ranges(range.clone())
21075 .filter(move |pair| {
21076 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21077 })
21078 {
21079 let len = pair.close_range.end - pair.open_range.start;
21080
21081 if let Some(existing) = &result {
21082 let existing_len = existing.close_range.end - existing.open_range.start;
21083 if len > existing_len {
21084 continue;
21085 }
21086 }
21087
21088 result = Some(pair);
21089 }
21090
21091 result
21092 };
21093 let Some(pair) = pair else {
21094 return false;
21095 };
21096 pair.newline_only
21097 && buffer
21098 .chars_for_range(pair.open_range.end..range.start)
21099 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21100 .all(|c| c.is_whitespace() && c != '\n')
21101}
21102
21103fn update_uncommitted_diff_for_buffer(
21104 editor: Entity<Editor>,
21105 project: &Entity<Project>,
21106 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21107 buffer: Entity<MultiBuffer>,
21108 cx: &mut App,
21109) -> Task<()> {
21110 let mut tasks = Vec::new();
21111 project.update(cx, |project, cx| {
21112 for buffer in buffers {
21113 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21114 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21115 }
21116 }
21117 });
21118 cx.spawn(async move |cx| {
21119 let diffs = future::join_all(tasks).await;
21120 if editor
21121 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21122 .unwrap_or(false)
21123 {
21124 return;
21125 }
21126
21127 buffer
21128 .update(cx, |buffer, cx| {
21129 for diff in diffs.into_iter().flatten() {
21130 buffer.add_diff(diff, cx);
21131 }
21132 })
21133 .ok();
21134 })
21135}
21136
21137fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21138 let tab_size = tab_size.get() as usize;
21139 let mut width = offset;
21140
21141 for ch in text.chars() {
21142 width += if ch == '\t' {
21143 tab_size - (width % tab_size)
21144 } else {
21145 1
21146 };
21147 }
21148
21149 width - offset
21150}
21151
21152#[cfg(test)]
21153mod tests {
21154 use super::*;
21155
21156 #[test]
21157 fn test_string_size_with_expanded_tabs() {
21158 let nz = |val| NonZeroU32::new(val).unwrap();
21159 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21160 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21161 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21162 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21163 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21164 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21165 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21166 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21167 }
21168}
21169
21170/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21171struct WordBreakingTokenizer<'a> {
21172 input: &'a str,
21173}
21174
21175impl<'a> WordBreakingTokenizer<'a> {
21176 fn new(input: &'a str) -> Self {
21177 Self { input }
21178 }
21179}
21180
21181fn is_char_ideographic(ch: char) -> bool {
21182 use unicode_script::Script::*;
21183 use unicode_script::UnicodeScript;
21184 matches!(ch.script(), Han | Tangut | Yi)
21185}
21186
21187fn is_grapheme_ideographic(text: &str) -> bool {
21188 text.chars().any(is_char_ideographic)
21189}
21190
21191fn is_grapheme_whitespace(text: &str) -> bool {
21192 text.chars().any(|x| x.is_whitespace())
21193}
21194
21195fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21196 text.chars().next().map_or(false, |ch| {
21197 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
21198 })
21199}
21200
21201#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21202enum WordBreakToken<'a> {
21203 Word { token: &'a str, grapheme_len: usize },
21204 InlineWhitespace { token: &'a str, grapheme_len: usize },
21205 Newline,
21206}
21207
21208impl<'a> Iterator for WordBreakingTokenizer<'a> {
21209 /// Yields a span, the count of graphemes in the token, and whether it was
21210 /// whitespace. Note that it also breaks at word boundaries.
21211 type Item = WordBreakToken<'a>;
21212
21213 fn next(&mut self) -> Option<Self::Item> {
21214 use unicode_segmentation::UnicodeSegmentation;
21215 if self.input.is_empty() {
21216 return None;
21217 }
21218
21219 let mut iter = self.input.graphemes(true).peekable();
21220 let mut offset = 0;
21221 let mut grapheme_len = 0;
21222 if let Some(first_grapheme) = iter.next() {
21223 let is_newline = first_grapheme == "\n";
21224 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21225 offset += first_grapheme.len();
21226 grapheme_len += 1;
21227 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21228 if let Some(grapheme) = iter.peek().copied() {
21229 if should_stay_with_preceding_ideograph(grapheme) {
21230 offset += grapheme.len();
21231 grapheme_len += 1;
21232 }
21233 }
21234 } else {
21235 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21236 let mut next_word_bound = words.peek().copied();
21237 if next_word_bound.map_or(false, |(i, _)| i == 0) {
21238 next_word_bound = words.next();
21239 }
21240 while let Some(grapheme) = iter.peek().copied() {
21241 if next_word_bound.map_or(false, |(i, _)| i == offset) {
21242 break;
21243 };
21244 if is_grapheme_whitespace(grapheme) != is_whitespace
21245 || (grapheme == "\n") != is_newline
21246 {
21247 break;
21248 };
21249 offset += grapheme.len();
21250 grapheme_len += 1;
21251 iter.next();
21252 }
21253 }
21254 let token = &self.input[..offset];
21255 self.input = &self.input[offset..];
21256 if token == "\n" {
21257 Some(WordBreakToken::Newline)
21258 } else if is_whitespace {
21259 Some(WordBreakToken::InlineWhitespace {
21260 token,
21261 grapheme_len,
21262 })
21263 } else {
21264 Some(WordBreakToken::Word {
21265 token,
21266 grapheme_len,
21267 })
21268 }
21269 } else {
21270 None
21271 }
21272 }
21273}
21274
21275#[test]
21276fn test_word_breaking_tokenizer() {
21277 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21278 ("", &[]),
21279 (" ", &[whitespace(" ", 2)]),
21280 ("Ʒ", &[word("Ʒ", 1)]),
21281 ("Ǽ", &[word("Ǽ", 1)]),
21282 ("⋑", &[word("⋑", 1)]),
21283 ("⋑⋑", &[word("⋑⋑", 2)]),
21284 (
21285 "原理,进而",
21286 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21287 ),
21288 (
21289 "hello world",
21290 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21291 ),
21292 (
21293 "hello, world",
21294 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21295 ),
21296 (
21297 " hello world",
21298 &[
21299 whitespace(" ", 2),
21300 word("hello", 5),
21301 whitespace(" ", 1),
21302 word("world", 5),
21303 ],
21304 ),
21305 (
21306 "这是什么 \n 钢笔",
21307 &[
21308 word("这", 1),
21309 word("是", 1),
21310 word("什", 1),
21311 word("么", 1),
21312 whitespace(" ", 1),
21313 newline(),
21314 whitespace(" ", 1),
21315 word("钢", 1),
21316 word("笔", 1),
21317 ],
21318 ),
21319 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21320 ];
21321
21322 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21323 WordBreakToken::Word {
21324 token,
21325 grapheme_len,
21326 }
21327 }
21328
21329 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21330 WordBreakToken::InlineWhitespace {
21331 token,
21332 grapheme_len,
21333 }
21334 }
21335
21336 fn newline() -> WordBreakToken<'static> {
21337 WordBreakToken::Newline
21338 }
21339
21340 for (input, result) in tests {
21341 assert_eq!(
21342 WordBreakingTokenizer::new(input)
21343 .collect::<Vec<_>>()
21344 .as_slice(),
21345 *result,
21346 );
21347 }
21348}
21349
21350fn wrap_with_prefix(
21351 first_line_prefix: String,
21352 subsequent_lines_prefix: String,
21353 unwrapped_text: String,
21354 wrap_column: usize,
21355 tab_size: NonZeroU32,
21356 preserve_existing_whitespace: bool,
21357) -> String {
21358 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21359 let subsequent_lines_prefix_len =
21360 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21361 let mut wrapped_text = String::new();
21362 let mut current_line = first_line_prefix.clone();
21363 let mut is_first_line = true;
21364
21365 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21366 let mut current_line_len = first_line_prefix_len;
21367 let mut in_whitespace = false;
21368 for token in tokenizer {
21369 let have_preceding_whitespace = in_whitespace;
21370 match token {
21371 WordBreakToken::Word {
21372 token,
21373 grapheme_len,
21374 } => {
21375 in_whitespace = false;
21376 let current_prefix_len = if is_first_line {
21377 first_line_prefix_len
21378 } else {
21379 subsequent_lines_prefix_len
21380 };
21381 if current_line_len + grapheme_len > wrap_column
21382 && current_line_len != current_prefix_len
21383 {
21384 wrapped_text.push_str(current_line.trim_end());
21385 wrapped_text.push('\n');
21386 is_first_line = false;
21387 current_line = subsequent_lines_prefix.clone();
21388 current_line_len = subsequent_lines_prefix_len;
21389 }
21390 current_line.push_str(token);
21391 current_line_len += grapheme_len;
21392 }
21393 WordBreakToken::InlineWhitespace {
21394 mut token,
21395 mut grapheme_len,
21396 } => {
21397 in_whitespace = true;
21398 if have_preceding_whitespace && !preserve_existing_whitespace {
21399 continue;
21400 }
21401 if !preserve_existing_whitespace {
21402 token = " ";
21403 grapheme_len = 1;
21404 }
21405 let current_prefix_len = if is_first_line {
21406 first_line_prefix_len
21407 } else {
21408 subsequent_lines_prefix_len
21409 };
21410 if current_line_len + grapheme_len > wrap_column {
21411 wrapped_text.push_str(current_line.trim_end());
21412 wrapped_text.push('\n');
21413 is_first_line = false;
21414 current_line = subsequent_lines_prefix.clone();
21415 current_line_len = subsequent_lines_prefix_len;
21416 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21417 current_line.push_str(token);
21418 current_line_len += grapheme_len;
21419 }
21420 }
21421 WordBreakToken::Newline => {
21422 in_whitespace = true;
21423 let current_prefix_len = if is_first_line {
21424 first_line_prefix_len
21425 } else {
21426 subsequent_lines_prefix_len
21427 };
21428 if preserve_existing_whitespace {
21429 wrapped_text.push_str(current_line.trim_end());
21430 wrapped_text.push('\n');
21431 is_first_line = false;
21432 current_line = subsequent_lines_prefix.clone();
21433 current_line_len = subsequent_lines_prefix_len;
21434 } else if have_preceding_whitespace {
21435 continue;
21436 } else if current_line_len + 1 > wrap_column
21437 && current_line_len != current_prefix_len
21438 {
21439 wrapped_text.push_str(current_line.trim_end());
21440 wrapped_text.push('\n');
21441 is_first_line = false;
21442 current_line = subsequent_lines_prefix.clone();
21443 current_line_len = subsequent_lines_prefix_len;
21444 } else if current_line_len != current_prefix_len {
21445 current_line.push(' ');
21446 current_line_len += 1;
21447 }
21448 }
21449 }
21450 }
21451
21452 if !current_line.is_empty() {
21453 wrapped_text.push_str(¤t_line);
21454 }
21455 wrapped_text
21456}
21457
21458#[test]
21459fn test_wrap_with_prefix() {
21460 assert_eq!(
21461 wrap_with_prefix(
21462 "# ".to_string(),
21463 "# ".to_string(),
21464 "abcdefg".to_string(),
21465 4,
21466 NonZeroU32::new(4).unwrap(),
21467 false,
21468 ),
21469 "# abcdefg"
21470 );
21471 assert_eq!(
21472 wrap_with_prefix(
21473 "".to_string(),
21474 "".to_string(),
21475 "\thello world".to_string(),
21476 8,
21477 NonZeroU32::new(4).unwrap(),
21478 false,
21479 ),
21480 "hello\nworld"
21481 );
21482 assert_eq!(
21483 wrap_with_prefix(
21484 "// ".to_string(),
21485 "// ".to_string(),
21486 "xx \nyy zz aa bb cc".to_string(),
21487 12,
21488 NonZeroU32::new(4).unwrap(),
21489 false,
21490 ),
21491 "// xx yy zz\n// aa bb cc"
21492 );
21493 assert_eq!(
21494 wrap_with_prefix(
21495 String::new(),
21496 String::new(),
21497 "这是什么 \n 钢笔".to_string(),
21498 3,
21499 NonZeroU32::new(4).unwrap(),
21500 false,
21501 ),
21502 "这是什\n么 钢\n笔"
21503 );
21504}
21505
21506pub trait CollaborationHub {
21507 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21508 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21509 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21510}
21511
21512impl CollaborationHub for Entity<Project> {
21513 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21514 self.read(cx).collaborators()
21515 }
21516
21517 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21518 self.read(cx).user_store().read(cx).participant_indices()
21519 }
21520
21521 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21522 let this = self.read(cx);
21523 let user_ids = this.collaborators().values().map(|c| c.user_id);
21524 this.user_store().read(cx).participant_names(user_ids, cx)
21525 }
21526}
21527
21528pub trait SemanticsProvider {
21529 fn hover(
21530 &self,
21531 buffer: &Entity<Buffer>,
21532 position: text::Anchor,
21533 cx: &mut App,
21534 ) -> Option<Task<Vec<project::Hover>>>;
21535
21536 fn inline_values(
21537 &self,
21538 buffer_handle: Entity<Buffer>,
21539 range: Range<text::Anchor>,
21540 cx: &mut App,
21541 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21542
21543 fn inlay_hints(
21544 &self,
21545 buffer_handle: Entity<Buffer>,
21546 range: Range<text::Anchor>,
21547 cx: &mut App,
21548 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21549
21550 fn resolve_inlay_hint(
21551 &self,
21552 hint: InlayHint,
21553 buffer_handle: Entity<Buffer>,
21554 server_id: LanguageServerId,
21555 cx: &mut App,
21556 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21557
21558 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21559
21560 fn document_highlights(
21561 &self,
21562 buffer: &Entity<Buffer>,
21563 position: text::Anchor,
21564 cx: &mut App,
21565 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21566
21567 fn definitions(
21568 &self,
21569 buffer: &Entity<Buffer>,
21570 position: text::Anchor,
21571 kind: GotoDefinitionKind,
21572 cx: &mut App,
21573 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21574
21575 fn range_for_rename(
21576 &self,
21577 buffer: &Entity<Buffer>,
21578 position: text::Anchor,
21579 cx: &mut App,
21580 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21581
21582 fn perform_rename(
21583 &self,
21584 buffer: &Entity<Buffer>,
21585 position: text::Anchor,
21586 new_name: String,
21587 cx: &mut App,
21588 ) -> Option<Task<Result<ProjectTransaction>>>;
21589}
21590
21591pub trait CompletionProvider {
21592 fn completions(
21593 &self,
21594 excerpt_id: ExcerptId,
21595 buffer: &Entity<Buffer>,
21596 buffer_position: text::Anchor,
21597 trigger: CompletionContext,
21598 window: &mut Window,
21599 cx: &mut Context<Editor>,
21600 ) -> Task<Result<Vec<CompletionResponse>>>;
21601
21602 fn resolve_completions(
21603 &self,
21604 _buffer: Entity<Buffer>,
21605 _completion_indices: Vec<usize>,
21606 _completions: Rc<RefCell<Box<[Completion]>>>,
21607 _cx: &mut Context<Editor>,
21608 ) -> Task<Result<bool>> {
21609 Task::ready(Ok(false))
21610 }
21611
21612 fn apply_additional_edits_for_completion(
21613 &self,
21614 _buffer: Entity<Buffer>,
21615 _completions: Rc<RefCell<Box<[Completion]>>>,
21616 _completion_index: usize,
21617 _push_to_history: bool,
21618 _cx: &mut Context<Editor>,
21619 ) -> Task<Result<Option<language::Transaction>>> {
21620 Task::ready(Ok(None))
21621 }
21622
21623 fn is_completion_trigger(
21624 &self,
21625 buffer: &Entity<Buffer>,
21626 position: language::Anchor,
21627 text: &str,
21628 trigger_in_words: bool,
21629 menu_is_open: bool,
21630 cx: &mut Context<Editor>,
21631 ) -> bool;
21632
21633 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21634
21635 fn sort_completions(&self) -> bool {
21636 true
21637 }
21638
21639 fn filter_completions(&self) -> bool {
21640 true
21641 }
21642}
21643
21644pub trait CodeActionProvider {
21645 fn id(&self) -> Arc<str>;
21646
21647 fn code_actions(
21648 &self,
21649 buffer: &Entity<Buffer>,
21650 range: Range<text::Anchor>,
21651 window: &mut Window,
21652 cx: &mut App,
21653 ) -> Task<Result<Vec<CodeAction>>>;
21654
21655 fn apply_code_action(
21656 &self,
21657 buffer_handle: Entity<Buffer>,
21658 action: CodeAction,
21659 excerpt_id: ExcerptId,
21660 push_to_history: bool,
21661 window: &mut Window,
21662 cx: &mut App,
21663 ) -> Task<Result<ProjectTransaction>>;
21664}
21665
21666impl CodeActionProvider for Entity<Project> {
21667 fn id(&self) -> Arc<str> {
21668 "project".into()
21669 }
21670
21671 fn code_actions(
21672 &self,
21673 buffer: &Entity<Buffer>,
21674 range: Range<text::Anchor>,
21675 _window: &mut Window,
21676 cx: &mut App,
21677 ) -> Task<Result<Vec<CodeAction>>> {
21678 self.update(cx, |project, cx| {
21679 let code_lens = project.code_lens(buffer, range.clone(), cx);
21680 let code_actions = project.code_actions(buffer, range, None, cx);
21681 cx.background_spawn(async move {
21682 let (code_lens, code_actions) = join(code_lens, code_actions).await;
21683 Ok(code_lens
21684 .context("code lens fetch")?
21685 .into_iter()
21686 .chain(code_actions.context("code action fetch")?)
21687 .collect())
21688 })
21689 })
21690 }
21691
21692 fn apply_code_action(
21693 &self,
21694 buffer_handle: Entity<Buffer>,
21695 action: CodeAction,
21696 _excerpt_id: ExcerptId,
21697 push_to_history: bool,
21698 _window: &mut Window,
21699 cx: &mut App,
21700 ) -> Task<Result<ProjectTransaction>> {
21701 self.update(cx, |project, cx| {
21702 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21703 })
21704 }
21705}
21706
21707fn snippet_completions(
21708 project: &Project,
21709 buffer: &Entity<Buffer>,
21710 buffer_position: text::Anchor,
21711 cx: &mut App,
21712) -> Task<Result<CompletionResponse>> {
21713 let languages = buffer.read(cx).languages_at(buffer_position);
21714 let snippet_store = project.snippets().read(cx);
21715
21716 let scopes: Vec<_> = languages
21717 .iter()
21718 .filter_map(|language| {
21719 let language_name = language.lsp_id();
21720 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21721
21722 if snippets.is_empty() {
21723 None
21724 } else {
21725 Some((language.default_scope(), snippets))
21726 }
21727 })
21728 .collect();
21729
21730 if scopes.is_empty() {
21731 return Task::ready(Ok(CompletionResponse {
21732 completions: vec![],
21733 is_incomplete: false,
21734 }));
21735 }
21736
21737 let snapshot = buffer.read(cx).text_snapshot();
21738 let chars: String = snapshot
21739 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21740 .collect();
21741 let executor = cx.background_executor().clone();
21742
21743 cx.background_spawn(async move {
21744 let mut is_incomplete = false;
21745 let mut completions: Vec<Completion> = Vec::new();
21746 for (scope, snippets) in scopes.into_iter() {
21747 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21748 let mut last_word = chars
21749 .chars()
21750 .take_while(|c| classifier.is_word(*c))
21751 .collect::<String>();
21752 last_word = last_word.chars().rev().collect();
21753
21754 if last_word.is_empty() {
21755 return Ok(CompletionResponse {
21756 completions: vec![],
21757 is_incomplete: true,
21758 });
21759 }
21760
21761 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21762 let to_lsp = |point: &text::Anchor| {
21763 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21764 point_to_lsp(end)
21765 };
21766 let lsp_end = to_lsp(&buffer_position);
21767
21768 let candidates = snippets
21769 .iter()
21770 .enumerate()
21771 .flat_map(|(ix, snippet)| {
21772 snippet
21773 .prefix
21774 .iter()
21775 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21776 })
21777 .collect::<Vec<StringMatchCandidate>>();
21778
21779 const MAX_RESULTS: usize = 100;
21780 let mut matches = fuzzy::match_strings(
21781 &candidates,
21782 &last_word,
21783 last_word.chars().any(|c| c.is_uppercase()),
21784 true,
21785 MAX_RESULTS,
21786 &Default::default(),
21787 executor.clone(),
21788 )
21789 .await;
21790
21791 if matches.len() >= MAX_RESULTS {
21792 is_incomplete = true;
21793 }
21794
21795 // Remove all candidates where the query's start does not match the start of any word in the candidate
21796 if let Some(query_start) = last_word.chars().next() {
21797 matches.retain(|string_match| {
21798 split_words(&string_match.string).any(|word| {
21799 // Check that the first codepoint of the word as lowercase matches the first
21800 // codepoint of the query as lowercase
21801 word.chars()
21802 .flat_map(|codepoint| codepoint.to_lowercase())
21803 .zip(query_start.to_lowercase())
21804 .all(|(word_cp, query_cp)| word_cp == query_cp)
21805 })
21806 });
21807 }
21808
21809 let matched_strings = matches
21810 .into_iter()
21811 .map(|m| m.string)
21812 .collect::<HashSet<_>>();
21813
21814 completions.extend(snippets.iter().filter_map(|snippet| {
21815 let matching_prefix = snippet
21816 .prefix
21817 .iter()
21818 .find(|prefix| matched_strings.contains(*prefix))?;
21819 let start = as_offset - last_word.len();
21820 let start = snapshot.anchor_before(start);
21821 let range = start..buffer_position;
21822 let lsp_start = to_lsp(&start);
21823 let lsp_range = lsp::Range {
21824 start: lsp_start,
21825 end: lsp_end,
21826 };
21827 Some(Completion {
21828 replace_range: range,
21829 new_text: snippet.body.clone(),
21830 source: CompletionSource::Lsp {
21831 insert_range: None,
21832 server_id: LanguageServerId(usize::MAX),
21833 resolved: true,
21834 lsp_completion: Box::new(lsp::CompletionItem {
21835 label: snippet.prefix.first().unwrap().clone(),
21836 kind: Some(CompletionItemKind::SNIPPET),
21837 label_details: snippet.description.as_ref().map(|description| {
21838 lsp::CompletionItemLabelDetails {
21839 detail: Some(description.clone()),
21840 description: None,
21841 }
21842 }),
21843 insert_text_format: Some(InsertTextFormat::SNIPPET),
21844 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21845 lsp::InsertReplaceEdit {
21846 new_text: snippet.body.clone(),
21847 insert: lsp_range,
21848 replace: lsp_range,
21849 },
21850 )),
21851 filter_text: Some(snippet.body.clone()),
21852 sort_text: Some(char::MAX.to_string()),
21853 ..lsp::CompletionItem::default()
21854 }),
21855 lsp_defaults: None,
21856 },
21857 label: CodeLabel {
21858 text: matching_prefix.clone(),
21859 runs: Vec::new(),
21860 filter_range: 0..matching_prefix.len(),
21861 },
21862 icon_path: None,
21863 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21864 single_line: snippet.name.clone().into(),
21865 plain_text: snippet
21866 .description
21867 .clone()
21868 .map(|description| description.into()),
21869 }),
21870 insert_text_mode: None,
21871 confirm: None,
21872 })
21873 }))
21874 }
21875
21876 Ok(CompletionResponse {
21877 completions,
21878 is_incomplete,
21879 })
21880 })
21881}
21882
21883impl CompletionProvider for Entity<Project> {
21884 fn completions(
21885 &self,
21886 _excerpt_id: ExcerptId,
21887 buffer: &Entity<Buffer>,
21888 buffer_position: text::Anchor,
21889 options: CompletionContext,
21890 _window: &mut Window,
21891 cx: &mut Context<Editor>,
21892 ) -> Task<Result<Vec<CompletionResponse>>> {
21893 self.update(cx, |project, cx| {
21894 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21895 let project_completions = project.completions(buffer, buffer_position, options, cx);
21896 cx.background_spawn(async move {
21897 let mut responses = project_completions.await?;
21898 let snippets = snippets.await?;
21899 if !snippets.completions.is_empty() {
21900 responses.push(snippets);
21901 }
21902 Ok(responses)
21903 })
21904 })
21905 }
21906
21907 fn resolve_completions(
21908 &self,
21909 buffer: Entity<Buffer>,
21910 completion_indices: Vec<usize>,
21911 completions: Rc<RefCell<Box<[Completion]>>>,
21912 cx: &mut Context<Editor>,
21913 ) -> Task<Result<bool>> {
21914 self.update(cx, |project, cx| {
21915 project.lsp_store().update(cx, |lsp_store, cx| {
21916 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21917 })
21918 })
21919 }
21920
21921 fn apply_additional_edits_for_completion(
21922 &self,
21923 buffer: Entity<Buffer>,
21924 completions: Rc<RefCell<Box<[Completion]>>>,
21925 completion_index: usize,
21926 push_to_history: bool,
21927 cx: &mut Context<Editor>,
21928 ) -> Task<Result<Option<language::Transaction>>> {
21929 self.update(cx, |project, cx| {
21930 project.lsp_store().update(cx, |lsp_store, cx| {
21931 lsp_store.apply_additional_edits_for_completion(
21932 buffer,
21933 completions,
21934 completion_index,
21935 push_to_history,
21936 cx,
21937 )
21938 })
21939 })
21940 }
21941
21942 fn is_completion_trigger(
21943 &self,
21944 buffer: &Entity<Buffer>,
21945 position: language::Anchor,
21946 text: &str,
21947 trigger_in_words: bool,
21948 menu_is_open: bool,
21949 cx: &mut Context<Editor>,
21950 ) -> bool {
21951 let mut chars = text.chars();
21952 let char = if let Some(char) = chars.next() {
21953 char
21954 } else {
21955 return false;
21956 };
21957 if chars.next().is_some() {
21958 return false;
21959 }
21960
21961 let buffer = buffer.read(cx);
21962 let snapshot = buffer.snapshot();
21963 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21964 return false;
21965 }
21966 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21967 if trigger_in_words && classifier.is_word(char) {
21968 return true;
21969 }
21970
21971 buffer.completion_triggers().contains(text)
21972 }
21973}
21974
21975impl SemanticsProvider for Entity<Project> {
21976 fn hover(
21977 &self,
21978 buffer: &Entity<Buffer>,
21979 position: text::Anchor,
21980 cx: &mut App,
21981 ) -> Option<Task<Vec<project::Hover>>> {
21982 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21983 }
21984
21985 fn document_highlights(
21986 &self,
21987 buffer: &Entity<Buffer>,
21988 position: text::Anchor,
21989 cx: &mut App,
21990 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21991 Some(self.update(cx, |project, cx| {
21992 project.document_highlights(buffer, position, cx)
21993 }))
21994 }
21995
21996 fn definitions(
21997 &self,
21998 buffer: &Entity<Buffer>,
21999 position: text::Anchor,
22000 kind: GotoDefinitionKind,
22001 cx: &mut App,
22002 ) -> Option<Task<Result<Vec<LocationLink>>>> {
22003 Some(self.update(cx, |project, cx| match kind {
22004 GotoDefinitionKind::Symbol => project.definitions(&buffer, position, cx),
22005 GotoDefinitionKind::Declaration => project.declarations(&buffer, position, cx),
22006 GotoDefinitionKind::Type => project.type_definitions(&buffer, position, cx),
22007 GotoDefinitionKind::Implementation => project.implementations(&buffer, position, cx),
22008 }))
22009 }
22010
22011 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22012 // TODO: make this work for remote projects
22013 self.update(cx, |project, cx| {
22014 if project
22015 .active_debug_session(cx)
22016 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22017 {
22018 return true;
22019 }
22020
22021 buffer.update(cx, |buffer, cx| {
22022 project.any_language_server_supports_inlay_hints(buffer, cx)
22023 })
22024 })
22025 }
22026
22027 fn inline_values(
22028 &self,
22029 buffer_handle: Entity<Buffer>,
22030 range: Range<text::Anchor>,
22031 cx: &mut App,
22032 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22033 self.update(cx, |project, cx| {
22034 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22035
22036 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22037 })
22038 }
22039
22040 fn inlay_hints(
22041 &self,
22042 buffer_handle: Entity<Buffer>,
22043 range: Range<text::Anchor>,
22044 cx: &mut App,
22045 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22046 Some(self.update(cx, |project, cx| {
22047 project.inlay_hints(buffer_handle, range, cx)
22048 }))
22049 }
22050
22051 fn resolve_inlay_hint(
22052 &self,
22053 hint: InlayHint,
22054 buffer_handle: Entity<Buffer>,
22055 server_id: LanguageServerId,
22056 cx: &mut App,
22057 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22058 Some(self.update(cx, |project, cx| {
22059 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22060 }))
22061 }
22062
22063 fn range_for_rename(
22064 &self,
22065 buffer: &Entity<Buffer>,
22066 position: text::Anchor,
22067 cx: &mut App,
22068 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22069 Some(self.update(cx, |project, cx| {
22070 let buffer = buffer.clone();
22071 let task = project.prepare_rename(buffer.clone(), position, cx);
22072 cx.spawn(async move |_, cx| {
22073 Ok(match task.await? {
22074 PrepareRenameResponse::Success(range) => Some(range),
22075 PrepareRenameResponse::InvalidPosition => None,
22076 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22077 // Fallback on using TreeSitter info to determine identifier range
22078 buffer.read_with(cx, |buffer, _| {
22079 let snapshot = buffer.snapshot();
22080 let (range, kind) = snapshot.surrounding_word(position);
22081 if kind != Some(CharKind::Word) {
22082 return None;
22083 }
22084 Some(
22085 snapshot.anchor_before(range.start)
22086 ..snapshot.anchor_after(range.end),
22087 )
22088 })?
22089 }
22090 })
22091 })
22092 }))
22093 }
22094
22095 fn perform_rename(
22096 &self,
22097 buffer: &Entity<Buffer>,
22098 position: text::Anchor,
22099 new_name: String,
22100 cx: &mut App,
22101 ) -> Option<Task<Result<ProjectTransaction>>> {
22102 Some(self.update(cx, |project, cx| {
22103 project.perform_rename(buffer.clone(), position, new_name, cx)
22104 }))
22105 }
22106}
22107
22108fn inlay_hint_settings(
22109 location: Anchor,
22110 snapshot: &MultiBufferSnapshot,
22111 cx: &mut Context<Editor>,
22112) -> InlayHintSettings {
22113 let file = snapshot.file_at(location);
22114 let language = snapshot.language_at(location).map(|l| l.name());
22115 language_settings(language, file, cx).inlay_hints
22116}
22117
22118fn consume_contiguous_rows(
22119 contiguous_row_selections: &mut Vec<Selection<Point>>,
22120 selection: &Selection<Point>,
22121 display_map: &DisplaySnapshot,
22122 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22123) -> (MultiBufferRow, MultiBufferRow) {
22124 contiguous_row_selections.push(selection.clone());
22125 let start_row = MultiBufferRow(selection.start.row);
22126 let mut end_row = ending_row(selection, display_map);
22127
22128 while let Some(next_selection) = selections.peek() {
22129 if next_selection.start.row <= end_row.0 {
22130 end_row = ending_row(next_selection, display_map);
22131 contiguous_row_selections.push(selections.next().unwrap().clone());
22132 } else {
22133 break;
22134 }
22135 }
22136 (start_row, end_row)
22137}
22138
22139fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22140 if next_selection.end.column > 0 || next_selection.is_empty() {
22141 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22142 } else {
22143 MultiBufferRow(next_selection.end.row)
22144 }
22145}
22146
22147impl EditorSnapshot {
22148 pub fn remote_selections_in_range<'a>(
22149 &'a self,
22150 range: &'a Range<Anchor>,
22151 collaboration_hub: &dyn CollaborationHub,
22152 cx: &'a App,
22153 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22154 let participant_names = collaboration_hub.user_names(cx);
22155 let participant_indices = collaboration_hub.user_participant_indices(cx);
22156 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22157 let collaborators_by_replica_id = collaborators_by_peer_id
22158 .values()
22159 .map(|collaborator| (collaborator.replica_id, collaborator))
22160 .collect::<HashMap<_, _>>();
22161 self.buffer_snapshot
22162 .selections_in_range(range, false)
22163 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22164 if replica_id == AGENT_REPLICA_ID {
22165 Some(RemoteSelection {
22166 replica_id,
22167 selection,
22168 cursor_shape,
22169 line_mode,
22170 collaborator_id: CollaboratorId::Agent,
22171 user_name: Some("Agent".into()),
22172 color: cx.theme().players().agent(),
22173 })
22174 } else {
22175 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22176 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22177 let user_name = participant_names.get(&collaborator.user_id).cloned();
22178 Some(RemoteSelection {
22179 replica_id,
22180 selection,
22181 cursor_shape,
22182 line_mode,
22183 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22184 user_name,
22185 color: if let Some(index) = participant_index {
22186 cx.theme().players().color_for_participant(index.0)
22187 } else {
22188 cx.theme().players().absent()
22189 },
22190 })
22191 }
22192 })
22193 }
22194
22195 pub fn hunks_for_ranges(
22196 &self,
22197 ranges: impl IntoIterator<Item = Range<Point>>,
22198 ) -> Vec<MultiBufferDiffHunk> {
22199 let mut hunks = Vec::new();
22200 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22201 HashMap::default();
22202 for query_range in ranges {
22203 let query_rows =
22204 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22205 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22206 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22207 ) {
22208 // Include deleted hunks that are adjacent to the query range, because
22209 // otherwise they would be missed.
22210 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22211 if hunk.status().is_deleted() {
22212 intersects_range |= hunk.row_range.start == query_rows.end;
22213 intersects_range |= hunk.row_range.end == query_rows.start;
22214 }
22215 if intersects_range {
22216 if !processed_buffer_rows
22217 .entry(hunk.buffer_id)
22218 .or_default()
22219 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22220 {
22221 continue;
22222 }
22223 hunks.push(hunk);
22224 }
22225 }
22226 }
22227
22228 hunks
22229 }
22230
22231 fn display_diff_hunks_for_rows<'a>(
22232 &'a self,
22233 display_rows: Range<DisplayRow>,
22234 folded_buffers: &'a HashSet<BufferId>,
22235 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22236 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22237 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22238
22239 self.buffer_snapshot
22240 .diff_hunks_in_range(buffer_start..buffer_end)
22241 .filter_map(|hunk| {
22242 if folded_buffers.contains(&hunk.buffer_id) {
22243 return None;
22244 }
22245
22246 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22247 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22248
22249 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22250 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22251
22252 let display_hunk = if hunk_display_start.column() != 0 {
22253 DisplayDiffHunk::Folded {
22254 display_row: hunk_display_start.row(),
22255 }
22256 } else {
22257 let mut end_row = hunk_display_end.row();
22258 if hunk_display_end.column() > 0 {
22259 end_row.0 += 1;
22260 }
22261 let is_created_file = hunk.is_created_file();
22262 DisplayDiffHunk::Unfolded {
22263 status: hunk.status(),
22264 diff_base_byte_range: hunk.diff_base_byte_range,
22265 display_row_range: hunk_display_start.row()..end_row,
22266 multi_buffer_range: Anchor::range_in_buffer(
22267 hunk.excerpt_id,
22268 hunk.buffer_id,
22269 hunk.buffer_range,
22270 ),
22271 is_created_file,
22272 }
22273 };
22274
22275 Some(display_hunk)
22276 })
22277 }
22278
22279 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22280 self.display_snapshot.buffer_snapshot.language_at(position)
22281 }
22282
22283 pub fn is_focused(&self) -> bool {
22284 self.is_focused
22285 }
22286
22287 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22288 self.placeholder_text.as_ref()
22289 }
22290
22291 pub fn scroll_position(&self) -> gpui::Point<f32> {
22292 self.scroll_anchor.scroll_position(&self.display_snapshot)
22293 }
22294
22295 fn gutter_dimensions(
22296 &self,
22297 font_id: FontId,
22298 font_size: Pixels,
22299 max_line_number_width: Pixels,
22300 cx: &App,
22301 ) -> Option<GutterDimensions> {
22302 if !self.show_gutter {
22303 return None;
22304 }
22305
22306 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22307 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22308
22309 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22310 matches!(
22311 ProjectSettings::get_global(cx).git.git_gutter,
22312 Some(GitGutterSetting::TrackedFiles)
22313 )
22314 });
22315 let gutter_settings = EditorSettings::get_global(cx).gutter;
22316 let show_line_numbers = self
22317 .show_line_numbers
22318 .unwrap_or(gutter_settings.line_numbers);
22319 let line_gutter_width = if show_line_numbers {
22320 // Avoid flicker-like gutter resizes when the line number gains another digit by
22321 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22322 let min_width_for_number_on_gutter =
22323 ch_advance * gutter_settings.min_line_number_digits as f32;
22324 max_line_number_width.max(min_width_for_number_on_gutter)
22325 } else {
22326 0.0.into()
22327 };
22328
22329 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22330 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22331
22332 let git_blame_entries_width =
22333 self.git_blame_gutter_max_author_length
22334 .map(|max_author_length| {
22335 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22336 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22337
22338 /// The number of characters to dedicate to gaps and margins.
22339 const SPACING_WIDTH: usize = 4;
22340
22341 let max_char_count = max_author_length.min(renderer.max_author_length())
22342 + ::git::SHORT_SHA_LENGTH
22343 + MAX_RELATIVE_TIMESTAMP.len()
22344 + SPACING_WIDTH;
22345
22346 ch_advance * max_char_count
22347 });
22348
22349 let is_singleton = self.buffer_snapshot.is_singleton();
22350
22351 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22352 left_padding += if !is_singleton {
22353 ch_width * 4.0
22354 } else if show_runnables || show_breakpoints {
22355 ch_width * 3.0
22356 } else if show_git_gutter && show_line_numbers {
22357 ch_width * 2.0
22358 } else if show_git_gutter || show_line_numbers {
22359 ch_width
22360 } else {
22361 px(0.)
22362 };
22363
22364 let shows_folds = is_singleton && gutter_settings.folds;
22365
22366 let right_padding = if shows_folds && show_line_numbers {
22367 ch_width * 4.0
22368 } else if shows_folds || (!is_singleton && show_line_numbers) {
22369 ch_width * 3.0
22370 } else if show_line_numbers {
22371 ch_width
22372 } else {
22373 px(0.)
22374 };
22375
22376 Some(GutterDimensions {
22377 left_padding,
22378 right_padding,
22379 width: line_gutter_width + left_padding + right_padding,
22380 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22381 git_blame_entries_width,
22382 })
22383 }
22384
22385 pub fn render_crease_toggle(
22386 &self,
22387 buffer_row: MultiBufferRow,
22388 row_contains_cursor: bool,
22389 editor: Entity<Editor>,
22390 window: &mut Window,
22391 cx: &mut App,
22392 ) -> Option<AnyElement> {
22393 let folded = self.is_line_folded(buffer_row);
22394 let mut is_foldable = false;
22395
22396 if let Some(crease) = self
22397 .crease_snapshot
22398 .query_row(buffer_row, &self.buffer_snapshot)
22399 {
22400 is_foldable = true;
22401 match crease {
22402 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22403 if let Some(render_toggle) = render_toggle {
22404 let toggle_callback =
22405 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22406 if folded {
22407 editor.update(cx, |editor, cx| {
22408 editor.fold_at(buffer_row, window, cx)
22409 });
22410 } else {
22411 editor.update(cx, |editor, cx| {
22412 editor.unfold_at(buffer_row, window, cx)
22413 });
22414 }
22415 });
22416 return Some((render_toggle)(
22417 buffer_row,
22418 folded,
22419 toggle_callback,
22420 window,
22421 cx,
22422 ));
22423 }
22424 }
22425 }
22426 }
22427
22428 is_foldable |= self.starts_indent(buffer_row);
22429
22430 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22431 Some(
22432 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22433 .toggle_state(folded)
22434 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22435 if folded {
22436 this.unfold_at(buffer_row, window, cx);
22437 } else {
22438 this.fold_at(buffer_row, window, cx);
22439 }
22440 }))
22441 .into_any_element(),
22442 )
22443 } else {
22444 None
22445 }
22446 }
22447
22448 pub fn render_crease_trailer(
22449 &self,
22450 buffer_row: MultiBufferRow,
22451 window: &mut Window,
22452 cx: &mut App,
22453 ) -> Option<AnyElement> {
22454 let folded = self.is_line_folded(buffer_row);
22455 if let Crease::Inline { render_trailer, .. } = self
22456 .crease_snapshot
22457 .query_row(buffer_row, &self.buffer_snapshot)?
22458 {
22459 let render_trailer = render_trailer.as_ref()?;
22460 Some(render_trailer(buffer_row, folded, window, cx))
22461 } else {
22462 None
22463 }
22464 }
22465}
22466
22467impl Deref for EditorSnapshot {
22468 type Target = DisplaySnapshot;
22469
22470 fn deref(&self) -> &Self::Target {
22471 &self.display_snapshot
22472 }
22473}
22474
22475#[derive(Clone, Debug, PartialEq, Eq)]
22476pub enum EditorEvent {
22477 InputIgnored {
22478 text: Arc<str>,
22479 },
22480 InputHandled {
22481 utf16_range_to_replace: Option<Range<isize>>,
22482 text: Arc<str>,
22483 },
22484 ExcerptsAdded {
22485 buffer: Entity<Buffer>,
22486 predecessor: ExcerptId,
22487 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22488 },
22489 ExcerptsRemoved {
22490 ids: Vec<ExcerptId>,
22491 removed_buffer_ids: Vec<BufferId>,
22492 },
22493 BufferFoldToggled {
22494 ids: Vec<ExcerptId>,
22495 folded: bool,
22496 },
22497 ExcerptsEdited {
22498 ids: Vec<ExcerptId>,
22499 },
22500 ExcerptsExpanded {
22501 ids: Vec<ExcerptId>,
22502 },
22503 BufferEdited,
22504 Edited {
22505 transaction_id: clock::Lamport,
22506 },
22507 Reparsed(BufferId),
22508 Focused,
22509 FocusedIn,
22510 Blurred,
22511 DirtyChanged,
22512 Saved,
22513 TitleChanged,
22514 DiffBaseChanged,
22515 SelectionsChanged {
22516 local: bool,
22517 },
22518 ScrollPositionChanged {
22519 local: bool,
22520 autoscroll: bool,
22521 },
22522 Closed,
22523 TransactionUndone {
22524 transaction_id: clock::Lamport,
22525 },
22526 TransactionBegun {
22527 transaction_id: clock::Lamport,
22528 },
22529 Reloaded,
22530 CursorShapeChanged,
22531 PushedToNavHistory {
22532 anchor: Anchor,
22533 is_deactivate: bool,
22534 },
22535}
22536
22537impl EventEmitter<EditorEvent> for Editor {}
22538
22539impl Focusable for Editor {
22540 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22541 self.focus_handle.clone()
22542 }
22543}
22544
22545impl Render for Editor {
22546 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22547 let settings = ThemeSettings::get_global(cx);
22548
22549 let mut text_style = match self.mode {
22550 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22551 color: cx.theme().colors().editor_foreground,
22552 font_family: settings.ui_font.family.clone(),
22553 font_features: settings.ui_font.features.clone(),
22554 font_fallbacks: settings.ui_font.fallbacks.clone(),
22555 font_size: rems(0.875).into(),
22556 font_weight: settings.ui_font.weight,
22557 line_height: relative(settings.buffer_line_height.value()),
22558 ..Default::default()
22559 },
22560 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22561 color: cx.theme().colors().editor_foreground,
22562 font_family: settings.buffer_font.family.clone(),
22563 font_features: settings.buffer_font.features.clone(),
22564 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22565 font_size: settings.buffer_font_size(cx).into(),
22566 font_weight: settings.buffer_font.weight,
22567 line_height: relative(settings.buffer_line_height.value()),
22568 ..Default::default()
22569 },
22570 };
22571 if let Some(text_style_refinement) = &self.text_style_refinement {
22572 text_style.refine(text_style_refinement)
22573 }
22574
22575 let background = match self.mode {
22576 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22577 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22578 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22579 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22580 };
22581
22582 EditorElement::new(
22583 &cx.entity(),
22584 EditorStyle {
22585 background,
22586 border: cx.theme().colors().border,
22587 local_player: cx.theme().players().local(),
22588 text: text_style,
22589 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22590 syntax: cx.theme().syntax().clone(),
22591 status: cx.theme().status().clone(),
22592 inlay_hints_style: make_inlay_hints_style(cx),
22593 inline_completion_styles: make_suggestion_styles(cx),
22594 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22595 show_underlines: self.diagnostics_enabled(),
22596 },
22597 )
22598 }
22599}
22600
22601impl EntityInputHandler for Editor {
22602 fn text_for_range(
22603 &mut self,
22604 range_utf16: Range<usize>,
22605 adjusted_range: &mut Option<Range<usize>>,
22606 _: &mut Window,
22607 cx: &mut Context<Self>,
22608 ) -> Option<String> {
22609 let snapshot = self.buffer.read(cx).read(cx);
22610 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22611 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22612 if (start.0..end.0) != range_utf16 {
22613 adjusted_range.replace(start.0..end.0);
22614 }
22615 Some(snapshot.text_for_range(start..end).collect())
22616 }
22617
22618 fn selected_text_range(
22619 &mut self,
22620 ignore_disabled_input: bool,
22621 _: &mut Window,
22622 cx: &mut Context<Self>,
22623 ) -> Option<UTF16Selection> {
22624 // Prevent the IME menu from appearing when holding down an alphabetic key
22625 // while input is disabled.
22626 if !ignore_disabled_input && !self.input_enabled {
22627 return None;
22628 }
22629
22630 let selection = self.selections.newest::<OffsetUtf16>(cx);
22631 let range = selection.range();
22632
22633 Some(UTF16Selection {
22634 range: range.start.0..range.end.0,
22635 reversed: selection.reversed,
22636 })
22637 }
22638
22639 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22640 let snapshot = self.buffer.read(cx).read(cx);
22641 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22642 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22643 }
22644
22645 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22646 self.clear_highlights::<InputComposition>(cx);
22647 self.ime_transaction.take();
22648 }
22649
22650 fn replace_text_in_range(
22651 &mut self,
22652 range_utf16: Option<Range<usize>>,
22653 text: &str,
22654 window: &mut Window,
22655 cx: &mut Context<Self>,
22656 ) {
22657 if !self.input_enabled {
22658 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22659 return;
22660 }
22661
22662 self.transact(window, cx, |this, window, cx| {
22663 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22664 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22665 Some(this.selection_replacement_ranges(range_utf16, cx))
22666 } else {
22667 this.marked_text_ranges(cx)
22668 };
22669
22670 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22671 let newest_selection_id = this.selections.newest_anchor().id;
22672 this.selections
22673 .all::<OffsetUtf16>(cx)
22674 .iter()
22675 .zip(ranges_to_replace.iter())
22676 .find_map(|(selection, range)| {
22677 if selection.id == newest_selection_id {
22678 Some(
22679 (range.start.0 as isize - selection.head().0 as isize)
22680 ..(range.end.0 as isize - selection.head().0 as isize),
22681 )
22682 } else {
22683 None
22684 }
22685 })
22686 });
22687
22688 cx.emit(EditorEvent::InputHandled {
22689 utf16_range_to_replace: range_to_replace,
22690 text: text.into(),
22691 });
22692
22693 if let Some(new_selected_ranges) = new_selected_ranges {
22694 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22695 selections.select_ranges(new_selected_ranges)
22696 });
22697 this.backspace(&Default::default(), window, cx);
22698 }
22699
22700 this.handle_input(text, window, cx);
22701 });
22702
22703 if let Some(transaction) = self.ime_transaction {
22704 self.buffer.update(cx, |buffer, cx| {
22705 buffer.group_until_transaction(transaction, cx);
22706 });
22707 }
22708
22709 self.unmark_text(window, cx);
22710 }
22711
22712 fn replace_and_mark_text_in_range(
22713 &mut self,
22714 range_utf16: Option<Range<usize>>,
22715 text: &str,
22716 new_selected_range_utf16: Option<Range<usize>>,
22717 window: &mut Window,
22718 cx: &mut Context<Self>,
22719 ) {
22720 if !self.input_enabled {
22721 return;
22722 }
22723
22724 let transaction = self.transact(window, cx, |this, window, cx| {
22725 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22726 let snapshot = this.buffer.read(cx).read(cx);
22727 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22728 for marked_range in &mut marked_ranges {
22729 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22730 marked_range.start.0 += relative_range_utf16.start;
22731 marked_range.start =
22732 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22733 marked_range.end =
22734 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22735 }
22736 }
22737 Some(marked_ranges)
22738 } else if let Some(range_utf16) = range_utf16 {
22739 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22740 Some(this.selection_replacement_ranges(range_utf16, cx))
22741 } else {
22742 None
22743 };
22744
22745 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22746 let newest_selection_id = this.selections.newest_anchor().id;
22747 this.selections
22748 .all::<OffsetUtf16>(cx)
22749 .iter()
22750 .zip(ranges_to_replace.iter())
22751 .find_map(|(selection, range)| {
22752 if selection.id == newest_selection_id {
22753 Some(
22754 (range.start.0 as isize - selection.head().0 as isize)
22755 ..(range.end.0 as isize - selection.head().0 as isize),
22756 )
22757 } else {
22758 None
22759 }
22760 })
22761 });
22762
22763 cx.emit(EditorEvent::InputHandled {
22764 utf16_range_to_replace: range_to_replace,
22765 text: text.into(),
22766 });
22767
22768 if let Some(ranges) = ranges_to_replace {
22769 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22770 s.select_ranges(ranges)
22771 });
22772 }
22773
22774 let marked_ranges = {
22775 let snapshot = this.buffer.read(cx).read(cx);
22776 this.selections
22777 .disjoint_anchors()
22778 .iter()
22779 .map(|selection| {
22780 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22781 })
22782 .collect::<Vec<_>>()
22783 };
22784
22785 if text.is_empty() {
22786 this.unmark_text(window, cx);
22787 } else {
22788 this.highlight_text::<InputComposition>(
22789 marked_ranges.clone(),
22790 HighlightStyle {
22791 underline: Some(UnderlineStyle {
22792 thickness: px(1.),
22793 color: None,
22794 wavy: false,
22795 }),
22796 ..Default::default()
22797 },
22798 cx,
22799 );
22800 }
22801
22802 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22803 let use_autoclose = this.use_autoclose;
22804 let use_auto_surround = this.use_auto_surround;
22805 this.set_use_autoclose(false);
22806 this.set_use_auto_surround(false);
22807 this.handle_input(text, window, cx);
22808 this.set_use_autoclose(use_autoclose);
22809 this.set_use_auto_surround(use_auto_surround);
22810
22811 if let Some(new_selected_range) = new_selected_range_utf16 {
22812 let snapshot = this.buffer.read(cx).read(cx);
22813 let new_selected_ranges = marked_ranges
22814 .into_iter()
22815 .map(|marked_range| {
22816 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22817 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22818 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22819 snapshot.clip_offset_utf16(new_start, Bias::Left)
22820 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22821 })
22822 .collect::<Vec<_>>();
22823
22824 drop(snapshot);
22825 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22826 selections.select_ranges(new_selected_ranges)
22827 });
22828 }
22829 });
22830
22831 self.ime_transaction = self.ime_transaction.or(transaction);
22832 if let Some(transaction) = self.ime_transaction {
22833 self.buffer.update(cx, |buffer, cx| {
22834 buffer.group_until_transaction(transaction, cx);
22835 });
22836 }
22837
22838 if self.text_highlights::<InputComposition>(cx).is_none() {
22839 self.ime_transaction.take();
22840 }
22841 }
22842
22843 fn bounds_for_range(
22844 &mut self,
22845 range_utf16: Range<usize>,
22846 element_bounds: gpui::Bounds<Pixels>,
22847 window: &mut Window,
22848 cx: &mut Context<Self>,
22849 ) -> Option<gpui::Bounds<Pixels>> {
22850 let text_layout_details = self.text_layout_details(window);
22851 let CharacterDimensions {
22852 em_width,
22853 em_advance,
22854 line_height,
22855 } = self.character_dimensions(window);
22856
22857 let snapshot = self.snapshot(window, cx);
22858 let scroll_position = snapshot.scroll_position();
22859 let scroll_left = scroll_position.x * em_advance;
22860
22861 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22862 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22863 + self.gutter_dimensions.full_width();
22864 let y = line_height * (start.row().as_f32() - scroll_position.y);
22865
22866 Some(Bounds {
22867 origin: element_bounds.origin + point(x, y),
22868 size: size(em_width, line_height),
22869 })
22870 }
22871
22872 fn character_index_for_point(
22873 &mut self,
22874 point: gpui::Point<Pixels>,
22875 _window: &mut Window,
22876 _cx: &mut Context<Self>,
22877 ) -> Option<usize> {
22878 let position_map = self.last_position_map.as_ref()?;
22879 if !position_map.text_hitbox.contains(&point) {
22880 return None;
22881 }
22882 let display_point = position_map.point_for_position(point).previous_valid;
22883 let anchor = position_map
22884 .snapshot
22885 .display_point_to_anchor(display_point, Bias::Left);
22886 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22887 Some(utf16_offset.0)
22888 }
22889}
22890
22891trait SelectionExt {
22892 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22893 fn spanned_rows(
22894 &self,
22895 include_end_if_at_line_start: bool,
22896 map: &DisplaySnapshot,
22897 ) -> Range<MultiBufferRow>;
22898}
22899
22900impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22901 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22902 let start = self
22903 .start
22904 .to_point(&map.buffer_snapshot)
22905 .to_display_point(map);
22906 let end = self
22907 .end
22908 .to_point(&map.buffer_snapshot)
22909 .to_display_point(map);
22910 if self.reversed {
22911 end..start
22912 } else {
22913 start..end
22914 }
22915 }
22916
22917 fn spanned_rows(
22918 &self,
22919 include_end_if_at_line_start: bool,
22920 map: &DisplaySnapshot,
22921 ) -> Range<MultiBufferRow> {
22922 let start = self.start.to_point(&map.buffer_snapshot);
22923 let mut end = self.end.to_point(&map.buffer_snapshot);
22924 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22925 end.row -= 1;
22926 }
22927
22928 let buffer_start = map.prev_line_boundary(start).0;
22929 let buffer_end = map.next_line_boundary(end).0;
22930 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22931 }
22932}
22933
22934impl<T: InvalidationRegion> InvalidationStack<T> {
22935 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22936 where
22937 S: Clone + ToOffset,
22938 {
22939 while let Some(region) = self.last() {
22940 let all_selections_inside_invalidation_ranges =
22941 if selections.len() == region.ranges().len() {
22942 selections
22943 .iter()
22944 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22945 .all(|(selection, invalidation_range)| {
22946 let head = selection.head().to_offset(buffer);
22947 invalidation_range.start <= head && invalidation_range.end >= head
22948 })
22949 } else {
22950 false
22951 };
22952
22953 if all_selections_inside_invalidation_ranges {
22954 break;
22955 } else {
22956 self.pop();
22957 }
22958 }
22959 }
22960}
22961
22962impl<T> Default for InvalidationStack<T> {
22963 fn default() -> Self {
22964 Self(Default::default())
22965 }
22966}
22967
22968impl<T> Deref for InvalidationStack<T> {
22969 type Target = Vec<T>;
22970
22971 fn deref(&self) -> &Self::Target {
22972 &self.0
22973 }
22974}
22975
22976impl<T> DerefMut for InvalidationStack<T> {
22977 fn deref_mut(&mut self) -> &mut Self::Target {
22978 &mut self.0
22979 }
22980}
22981
22982impl InvalidationRegion for SnippetState {
22983 fn ranges(&self) -> &[Range<Anchor>] {
22984 &self.ranges[self.active_index]
22985 }
22986}
22987
22988fn inline_completion_edit_text(
22989 current_snapshot: &BufferSnapshot,
22990 edits: &[(Range<Anchor>, String)],
22991 edit_preview: &EditPreview,
22992 include_deletions: bool,
22993 cx: &App,
22994) -> HighlightedText {
22995 let edits = edits
22996 .iter()
22997 .map(|(anchor, text)| {
22998 (
22999 anchor.start.text_anchor..anchor.end.text_anchor,
23000 text.clone(),
23001 )
23002 })
23003 .collect::<Vec<_>>();
23004
23005 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23006}
23007
23008pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23009 match severity {
23010 lsp::DiagnosticSeverity::ERROR => colors.error,
23011 lsp::DiagnosticSeverity::WARNING => colors.warning,
23012 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23013 lsp::DiagnosticSeverity::HINT => colors.info,
23014 _ => colors.ignored,
23015 }
23016}
23017
23018pub fn styled_runs_for_code_label<'a>(
23019 label: &'a CodeLabel,
23020 syntax_theme: &'a theme::SyntaxTheme,
23021) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23022 let fade_out = HighlightStyle {
23023 fade_out: Some(0.35),
23024 ..Default::default()
23025 };
23026
23027 let mut prev_end = label.filter_range.end;
23028 label
23029 .runs
23030 .iter()
23031 .enumerate()
23032 .flat_map(move |(ix, (range, highlight_id))| {
23033 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23034 style
23035 } else {
23036 return Default::default();
23037 };
23038 let mut muted_style = style;
23039 muted_style.highlight(fade_out);
23040
23041 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23042 if range.start >= label.filter_range.end {
23043 if range.start > prev_end {
23044 runs.push((prev_end..range.start, fade_out));
23045 }
23046 runs.push((range.clone(), muted_style));
23047 } else if range.end <= label.filter_range.end {
23048 runs.push((range.clone(), style));
23049 } else {
23050 runs.push((range.start..label.filter_range.end, style));
23051 runs.push((label.filter_range.end..range.end, muted_style));
23052 }
23053 prev_end = cmp::max(prev_end, range.end);
23054
23055 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23056 runs.push((prev_end..label.text.len(), fade_out));
23057 }
23058
23059 runs
23060 })
23061}
23062
23063pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23064 let mut prev_index = 0;
23065 let mut prev_codepoint: Option<char> = None;
23066 text.char_indices()
23067 .chain([(text.len(), '\0')])
23068 .filter_map(move |(index, codepoint)| {
23069 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23070 let is_boundary = index == text.len()
23071 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23072 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23073 if is_boundary {
23074 let chunk = &text[prev_index..index];
23075 prev_index = index;
23076 Some(chunk)
23077 } else {
23078 None
23079 }
23080 })
23081}
23082
23083pub trait RangeToAnchorExt: Sized {
23084 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23085
23086 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23087 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23088 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23089 }
23090}
23091
23092impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23093 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23094 let start_offset = self.start.to_offset(snapshot);
23095 let end_offset = self.end.to_offset(snapshot);
23096 if start_offset == end_offset {
23097 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23098 } else {
23099 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23100 }
23101 }
23102}
23103
23104pub trait RowExt {
23105 fn as_f32(&self) -> f32;
23106
23107 fn next_row(&self) -> Self;
23108
23109 fn previous_row(&self) -> Self;
23110
23111 fn minus(&self, other: Self) -> u32;
23112}
23113
23114impl RowExt for DisplayRow {
23115 fn as_f32(&self) -> f32 {
23116 self.0 as f32
23117 }
23118
23119 fn next_row(&self) -> Self {
23120 Self(self.0 + 1)
23121 }
23122
23123 fn previous_row(&self) -> Self {
23124 Self(self.0.saturating_sub(1))
23125 }
23126
23127 fn minus(&self, other: Self) -> u32 {
23128 self.0 - other.0
23129 }
23130}
23131
23132impl RowExt for MultiBufferRow {
23133 fn as_f32(&self) -> f32 {
23134 self.0 as f32
23135 }
23136
23137 fn next_row(&self) -> Self {
23138 Self(self.0 + 1)
23139 }
23140
23141 fn previous_row(&self) -> Self {
23142 Self(self.0.saturating_sub(1))
23143 }
23144
23145 fn minus(&self, other: Self) -> u32 {
23146 self.0 - other.0
23147 }
23148}
23149
23150trait RowRangeExt {
23151 type Row;
23152
23153 fn len(&self) -> usize;
23154
23155 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23156}
23157
23158impl RowRangeExt for Range<MultiBufferRow> {
23159 type Row = MultiBufferRow;
23160
23161 fn len(&self) -> usize {
23162 (self.end.0 - self.start.0) as usize
23163 }
23164
23165 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23166 (self.start.0..self.end.0).map(MultiBufferRow)
23167 }
23168}
23169
23170impl RowRangeExt for Range<DisplayRow> {
23171 type Row = DisplayRow;
23172
23173 fn len(&self) -> usize {
23174 (self.end.0 - self.start.0) as usize
23175 }
23176
23177 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23178 (self.start.0..self.end.0).map(DisplayRow)
23179 }
23180}
23181
23182/// If select range has more than one line, we
23183/// just point the cursor to range.start.
23184fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23185 if range.start.row == range.end.row {
23186 range
23187 } else {
23188 range.start..range.start
23189 }
23190}
23191pub struct KillRing(ClipboardItem);
23192impl Global for KillRing {}
23193
23194const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23195
23196enum BreakpointPromptEditAction {
23197 Log,
23198 Condition,
23199 HitCondition,
23200}
23201
23202struct BreakpointPromptEditor {
23203 pub(crate) prompt: Entity<Editor>,
23204 editor: WeakEntity<Editor>,
23205 breakpoint_anchor: Anchor,
23206 breakpoint: Breakpoint,
23207 edit_action: BreakpointPromptEditAction,
23208 block_ids: HashSet<CustomBlockId>,
23209 editor_margins: Arc<Mutex<EditorMargins>>,
23210 _subscriptions: Vec<Subscription>,
23211}
23212
23213impl BreakpointPromptEditor {
23214 const MAX_LINES: u8 = 4;
23215
23216 fn new(
23217 editor: WeakEntity<Editor>,
23218 breakpoint_anchor: Anchor,
23219 breakpoint: Breakpoint,
23220 edit_action: BreakpointPromptEditAction,
23221 window: &mut Window,
23222 cx: &mut Context<Self>,
23223 ) -> Self {
23224 let base_text = match edit_action {
23225 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23226 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23227 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23228 }
23229 .map(|msg| msg.to_string())
23230 .unwrap_or_default();
23231
23232 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23233 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23234
23235 let prompt = cx.new(|cx| {
23236 let mut prompt = Editor::new(
23237 EditorMode::AutoHeight {
23238 min_lines: 1,
23239 max_lines: Some(Self::MAX_LINES as usize),
23240 },
23241 buffer,
23242 None,
23243 window,
23244 cx,
23245 );
23246 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23247 prompt.set_show_cursor_when_unfocused(false, cx);
23248 prompt.set_placeholder_text(
23249 match edit_action {
23250 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23251 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23252 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23253 },
23254 cx,
23255 );
23256
23257 prompt
23258 });
23259
23260 Self {
23261 prompt,
23262 editor,
23263 breakpoint_anchor,
23264 breakpoint,
23265 edit_action,
23266 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23267 block_ids: Default::default(),
23268 _subscriptions: vec![],
23269 }
23270 }
23271
23272 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23273 self.block_ids.extend(block_ids)
23274 }
23275
23276 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23277 if let Some(editor) = self.editor.upgrade() {
23278 let message = self
23279 .prompt
23280 .read(cx)
23281 .buffer
23282 .read(cx)
23283 .as_singleton()
23284 .expect("A multi buffer in breakpoint prompt isn't possible")
23285 .read(cx)
23286 .as_rope()
23287 .to_string();
23288
23289 editor.update(cx, |editor, cx| {
23290 editor.edit_breakpoint_at_anchor(
23291 self.breakpoint_anchor,
23292 self.breakpoint.clone(),
23293 match self.edit_action {
23294 BreakpointPromptEditAction::Log => {
23295 BreakpointEditAction::EditLogMessage(message.into())
23296 }
23297 BreakpointPromptEditAction::Condition => {
23298 BreakpointEditAction::EditCondition(message.into())
23299 }
23300 BreakpointPromptEditAction::HitCondition => {
23301 BreakpointEditAction::EditHitCondition(message.into())
23302 }
23303 },
23304 cx,
23305 );
23306
23307 editor.remove_blocks(self.block_ids.clone(), None, cx);
23308 cx.focus_self(window);
23309 });
23310 }
23311 }
23312
23313 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23314 self.editor
23315 .update(cx, |editor, cx| {
23316 editor.remove_blocks(self.block_ids.clone(), None, cx);
23317 window.focus(&editor.focus_handle);
23318 })
23319 .log_err();
23320 }
23321
23322 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23323 let settings = ThemeSettings::get_global(cx);
23324 let text_style = TextStyle {
23325 color: if self.prompt.read(cx).read_only(cx) {
23326 cx.theme().colors().text_disabled
23327 } else {
23328 cx.theme().colors().text
23329 },
23330 font_family: settings.buffer_font.family.clone(),
23331 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23332 font_size: settings.buffer_font_size(cx).into(),
23333 font_weight: settings.buffer_font.weight,
23334 line_height: relative(settings.buffer_line_height.value()),
23335 ..Default::default()
23336 };
23337 EditorElement::new(
23338 &self.prompt,
23339 EditorStyle {
23340 background: cx.theme().colors().editor_background,
23341 local_player: cx.theme().players().local(),
23342 text: text_style,
23343 ..Default::default()
23344 },
23345 )
23346 }
23347}
23348
23349impl Render for BreakpointPromptEditor {
23350 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23351 let editor_margins = *self.editor_margins.lock();
23352 let gutter_dimensions = editor_margins.gutter;
23353 h_flex()
23354 .key_context("Editor")
23355 .bg(cx.theme().colors().editor_background)
23356 .border_y_1()
23357 .border_color(cx.theme().status().info_border)
23358 .size_full()
23359 .py(window.line_height() / 2.5)
23360 .on_action(cx.listener(Self::confirm))
23361 .on_action(cx.listener(Self::cancel))
23362 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23363 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23364 }
23365}
23366
23367impl Focusable for BreakpointPromptEditor {
23368 fn focus_handle(&self, cx: &App) -> FocusHandle {
23369 self.prompt.focus_handle(cx)
23370 }
23371}
23372
23373fn all_edits_insertions_or_deletions(
23374 edits: &Vec<(Range<Anchor>, String)>,
23375 snapshot: &MultiBufferSnapshot,
23376) -> bool {
23377 let mut all_insertions = true;
23378 let mut all_deletions = true;
23379
23380 for (range, new_text) in edits.iter() {
23381 let range_is_empty = range.to_offset(&snapshot).is_empty();
23382 let text_is_empty = new_text.is_empty();
23383
23384 if range_is_empty != text_is_empty {
23385 if range_is_empty {
23386 all_deletions = false;
23387 } else {
23388 all_insertions = false;
23389 }
23390 } else {
23391 return false;
23392 }
23393
23394 if !all_insertions && !all_deletions {
23395 return false;
23396 }
23397 }
23398 all_insertions || all_deletions
23399}
23400
23401struct MissingEditPredictionKeybindingTooltip;
23402
23403impl Render for MissingEditPredictionKeybindingTooltip {
23404 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23405 ui::tooltip_container(window, cx, |container, _, cx| {
23406 container
23407 .flex_shrink_0()
23408 .max_w_80()
23409 .min_h(rems_from_px(124.))
23410 .justify_between()
23411 .child(
23412 v_flex()
23413 .flex_1()
23414 .text_ui_sm(cx)
23415 .child(Label::new("Conflict with Accept Keybinding"))
23416 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23417 )
23418 .child(
23419 h_flex()
23420 .pb_1()
23421 .gap_1()
23422 .items_end()
23423 .w_full()
23424 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23425 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23426 }))
23427 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23428 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23429 })),
23430 )
23431 })
23432 }
23433}
23434
23435#[derive(Debug, Clone, Copy, PartialEq)]
23436pub struct LineHighlight {
23437 pub background: Background,
23438 pub border: Option<gpui::Hsla>,
23439 pub include_gutter: bool,
23440 pub type_id: Option<TypeId>,
23441}
23442
23443struct LineManipulationResult {
23444 pub new_text: String,
23445 pub line_count_before: usize,
23446 pub line_count_after: usize,
23447}
23448
23449fn render_diff_hunk_controls(
23450 row: u32,
23451 status: &DiffHunkStatus,
23452 hunk_range: Range<Anchor>,
23453 is_created_file: bool,
23454 line_height: Pixels,
23455 editor: &Entity<Editor>,
23456 _window: &mut Window,
23457 cx: &mut App,
23458) -> AnyElement {
23459 h_flex()
23460 .h(line_height)
23461 .mr_1()
23462 .gap_1()
23463 .px_0p5()
23464 .pb_1()
23465 .border_x_1()
23466 .border_b_1()
23467 .border_color(cx.theme().colors().border_variant)
23468 .rounded_b_lg()
23469 .bg(cx.theme().colors().editor_background)
23470 .gap_1()
23471 .block_mouse_except_scroll()
23472 .shadow_md()
23473 .child(if status.has_secondary_hunk() {
23474 Button::new(("stage", row as u64), "Stage")
23475 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23476 .tooltip({
23477 let focus_handle = editor.focus_handle(cx);
23478 move |window, cx| {
23479 Tooltip::for_action_in(
23480 "Stage Hunk",
23481 &::git::ToggleStaged,
23482 &focus_handle,
23483 window,
23484 cx,
23485 )
23486 }
23487 })
23488 .on_click({
23489 let editor = editor.clone();
23490 move |_event, _window, cx| {
23491 editor.update(cx, |editor, cx| {
23492 editor.stage_or_unstage_diff_hunks(
23493 true,
23494 vec![hunk_range.start..hunk_range.start],
23495 cx,
23496 );
23497 });
23498 }
23499 })
23500 } else {
23501 Button::new(("unstage", row as u64), "Unstage")
23502 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23503 .tooltip({
23504 let focus_handle = editor.focus_handle(cx);
23505 move |window, cx| {
23506 Tooltip::for_action_in(
23507 "Unstage Hunk",
23508 &::git::ToggleStaged,
23509 &focus_handle,
23510 window,
23511 cx,
23512 )
23513 }
23514 })
23515 .on_click({
23516 let editor = editor.clone();
23517 move |_event, _window, cx| {
23518 editor.update(cx, |editor, cx| {
23519 editor.stage_or_unstage_diff_hunks(
23520 false,
23521 vec![hunk_range.start..hunk_range.start],
23522 cx,
23523 );
23524 });
23525 }
23526 })
23527 })
23528 .child(
23529 Button::new(("restore", row as u64), "Restore")
23530 .tooltip({
23531 let focus_handle = editor.focus_handle(cx);
23532 move |window, cx| {
23533 Tooltip::for_action_in(
23534 "Restore Hunk",
23535 &::git::Restore,
23536 &focus_handle,
23537 window,
23538 cx,
23539 )
23540 }
23541 })
23542 .on_click({
23543 let editor = editor.clone();
23544 move |_event, window, cx| {
23545 editor.update(cx, |editor, cx| {
23546 let snapshot = editor.snapshot(window, cx);
23547 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23548 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23549 });
23550 }
23551 })
23552 .disabled(is_created_file),
23553 )
23554 .when(
23555 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23556 |el| {
23557 el.child(
23558 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23559 .shape(IconButtonShape::Square)
23560 .icon_size(IconSize::Small)
23561 // .disabled(!has_multiple_hunks)
23562 .tooltip({
23563 let focus_handle = editor.focus_handle(cx);
23564 move |window, cx| {
23565 Tooltip::for_action_in(
23566 "Next Hunk",
23567 &GoToHunk,
23568 &focus_handle,
23569 window,
23570 cx,
23571 )
23572 }
23573 })
23574 .on_click({
23575 let editor = editor.clone();
23576 move |_event, window, cx| {
23577 editor.update(cx, |editor, cx| {
23578 let snapshot = editor.snapshot(window, cx);
23579 let position =
23580 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23581 editor.go_to_hunk_before_or_after_position(
23582 &snapshot,
23583 position,
23584 Direction::Next,
23585 window,
23586 cx,
23587 );
23588 editor.expand_selected_diff_hunks(cx);
23589 });
23590 }
23591 }),
23592 )
23593 .child(
23594 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23595 .shape(IconButtonShape::Square)
23596 .icon_size(IconSize::Small)
23597 // .disabled(!has_multiple_hunks)
23598 .tooltip({
23599 let focus_handle = editor.focus_handle(cx);
23600 move |window, cx| {
23601 Tooltip::for_action_in(
23602 "Previous Hunk",
23603 &GoToPreviousHunk,
23604 &focus_handle,
23605 window,
23606 cx,
23607 )
23608 }
23609 })
23610 .on_click({
23611 let editor = editor.clone();
23612 move |_event, window, cx| {
23613 editor.update(cx, |editor, cx| {
23614 let snapshot = editor.snapshot(window, cx);
23615 let point =
23616 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23617 editor.go_to_hunk_before_or_after_position(
23618 &snapshot,
23619 point,
23620 Direction::Prev,
23621 window,
23622 cx,
23623 );
23624 editor.expand_selected_diff_hunks(cx);
23625 });
23626 }
23627 }),
23628 )
23629 },
23630 )
23631 .into_any_element()
23632}