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;
18mod 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_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod editor_tests;
46#[cfg(test)]
47mod inline_completion_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
54use aho_corasick::AhoCorasick;
55use anyhow::{Context as _, Result, anyhow};
56use blink_manager::BlinkManager;
57use buffer_diff::DiffHunkStatus;
58use client::{Collaborator, ParticipantIndex};
59use clock::{AGENT_REPLICA_ID, ReplicaId};
60use collections::{BTreeMap, HashMap, HashSet, VecDeque};
61use convert_case::{Case, Casing};
62use display_map::*;
63pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
64pub use editor_settings::{
65 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, SearchSettings,
66 ShowScrollbar,
67};
68use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
69pub use editor_settings_controls::*;
70use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
71pub use element::{
72 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
73};
74use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
75use futures::{
76 FutureExt,
77 future::{self, Shared, join},
78};
79use fuzzy::StringMatchCandidate;
80
81use ::git::blame::BlameEntry;
82use ::git::{Restore, blame::ParsedCommitMessage};
83use code_context_menus::{
84 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
85 CompletionsMenu, ContextMenuOrigin,
86};
87use git::blame::{GitBlame, GlobalBlameRenderer};
88use gpui::{
89 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
90 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
91 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
92 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
93 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
94 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
95 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
96 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
97};
98use highlight_matching_bracket::refresh_matching_bracket_highlights;
99use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
100pub use hover_popover::hover_markdown_style;
101use hover_popover::{HoverState, hide_hover};
102use indent_guides::ActiveIndentGuidesState;
103use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
104pub use inline_completion::Direction;
105use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
106pub use items::MAX_TAB_TITLE_LEN;
107use itertools::Itertools;
108use language::{
109 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
110 CursorShape, DiagnosticEntry, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText,
111 IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal, TextObject,
112 TransactionId, TreeSitterOptions, WordsQuery,
113 language_settings::{
114 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
115 all_language_settings, language_settings,
116 },
117 point_from_lsp, text_diff_with_options,
118};
119use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
120use linked_editing_ranges::refresh_linked_ranges;
121use markdown::Markdown;
122use mouse_context_menu::MouseContextMenu;
123use persistence::DB;
124use project::{
125 BreakpointWithPosition, ProjectPath,
126 debugger::{
127 breakpoint_store::{
128 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
129 BreakpointStoreEvent,
130 },
131 session::{Session, SessionEvent},
132 },
133 project_settings::DiagnosticSeverity,
134};
135
136pub use git::blame::BlameRenderer;
137pub use proposed_changes_editor::{
138 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
139};
140use smallvec::smallvec;
141use std::{cell::OnceCell, iter::Peekable, ops::Not};
142use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
143
144pub use lsp::CompletionContext;
145use lsp::{
146 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
147 LanguageServerId, LanguageServerName,
148};
149
150use language::BufferSnapshot;
151pub use lsp_ext::lsp_tasks;
152use movement::TextLayoutDetails;
153pub use multi_buffer::{
154 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
155 RowInfo, ToOffset, ToPoint,
156};
157use multi_buffer::{
158 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
159 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
160};
161use parking_lot::Mutex;
162use project::{
163 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
164 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
165 TaskSourceKind,
166 debugger::breakpoint_store::Breakpoint,
167 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
168 project_settings::{GitGutterSetting, ProjectSettings},
169};
170use rand::prelude::*;
171use rpc::{ErrorExt, proto::*};
172use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
173use selections_collection::{
174 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
175};
176use serde::{Deserialize, Serialize};
177use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
178use smallvec::SmallVec;
179use snippet::Snippet;
180use std::sync::Arc;
181use std::{
182 any::TypeId,
183 borrow::Cow,
184 cell::RefCell,
185 cmp::{self, Ordering, Reverse},
186 mem,
187 num::NonZeroU32,
188 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
189 path::{Path, PathBuf},
190 rc::Rc,
191 time::{Duration, Instant},
192};
193pub use sum_tree::Bias;
194use sum_tree::TreeMap;
195use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
196use theme::{
197 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
198 observe_buffer_font_size_adjustment,
199};
200use ui::{
201 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
202 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
203};
204use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
205use workspace::{
206 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
207 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
208 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
209 item::{ItemHandle, PreviewTabsSettings},
210 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
211 searchable::SearchEvent,
212};
213
214use crate::hover_links::{find_url, find_url_from_range};
215use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
216
217pub const FILE_HEADER_HEIGHT: u32 = 2;
218pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
219pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
220const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
221const MAX_LINE_LEN: usize = 1024;
222const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
223const MAX_SELECTION_HISTORY_LEN: usize = 1024;
224pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
225#[doc(hidden)]
226pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
227const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
228
229pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
230pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
231pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
232
233pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
234pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
235pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
236pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
237
238pub type RenderDiffHunkControlsFn = Arc<
239 dyn Fn(
240 u32,
241 &DiffHunkStatus,
242 Range<Anchor>,
243 bool,
244 Pixels,
245 &Entity<Editor>,
246 &mut Window,
247 &mut App,
248 ) -> AnyElement,
249>;
250
251const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
252 alt: true,
253 shift: true,
254 control: false,
255 platform: false,
256 function: false,
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 Hint(usize),
279 DebuggerValue(usize),
280}
281
282impl InlayId {
283 fn id(&self) -> usize {
284 match self {
285 Self::InlineCompletion(id) => *id,
286 Self::Hint(id) => *id,
287 Self::DebuggerValue(id) => *id,
288 }
289 }
290}
291
292pub enum ActiveDebugLine {}
293pub enum DebugStackFrameLine {}
294enum DocumentHighlightRead {}
295enum DocumentHighlightWrite {}
296enum InputComposition {}
297enum SelectedTextHighlight {}
298
299pub enum ConflictsOuter {}
300pub enum ConflictsOurs {}
301pub enum ConflictsTheirs {}
302pub enum ConflictsOursMarker {}
303pub enum ConflictsTheirsMarker {}
304
305#[derive(Debug, Copy, Clone, PartialEq, Eq)]
306pub enum Navigated {
307 Yes,
308 No,
309}
310
311impl Navigated {
312 pub fn from_bool(yes: bool) -> Navigated {
313 if yes { Navigated::Yes } else { Navigated::No }
314 }
315}
316
317#[derive(Debug, Clone, PartialEq, Eq)]
318enum DisplayDiffHunk {
319 Folded {
320 display_row: DisplayRow,
321 },
322 Unfolded {
323 is_created_file: bool,
324 diff_base_byte_range: Range<usize>,
325 display_row_range: Range<DisplayRow>,
326 multi_buffer_range: Range<Anchor>,
327 status: DiffHunkStatus,
328 },
329}
330
331pub enum HideMouseCursorOrigin {
332 TypingAction,
333 MovementAction,
334}
335
336pub fn init_settings(cx: &mut App) {
337 EditorSettings::register(cx);
338}
339
340pub fn init(cx: &mut App) {
341 init_settings(cx);
342
343 cx.set_global(GlobalBlameRenderer(Arc::new(())));
344
345 workspace::register_project_item::<Editor>(cx);
346 workspace::FollowableViewRegistry::register::<Editor>(cx);
347 workspace::register_serializable_item::<Editor>(cx);
348
349 cx.observe_new(
350 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
351 workspace.register_action(Editor::new_file);
352 workspace.register_action(Editor::new_file_vertical);
353 workspace.register_action(Editor::new_file_horizontal);
354 workspace.register_action(Editor::cancel_language_server_work);
355 },
356 )
357 .detach();
358
359 cx.on_action(move |_: &workspace::NewFile, cx| {
360 let app_state = workspace::AppState::global(cx);
361 if let Some(app_state) = app_state.upgrade() {
362 workspace::open_new(
363 Default::default(),
364 app_state,
365 cx,
366 |workspace, window, cx| {
367 Editor::new_file(workspace, &Default::default(), window, cx)
368 },
369 )
370 .detach();
371 }
372 });
373 cx.on_action(move |_: &workspace::NewWindow, cx| {
374 let app_state = workspace::AppState::global(cx);
375 if let Some(app_state) = app_state.upgrade() {
376 workspace::open_new(
377 Default::default(),
378 app_state,
379 cx,
380 |workspace, window, cx| {
381 cx.activate(true);
382 Editor::new_file(workspace, &Default::default(), window, cx)
383 },
384 )
385 .detach();
386 }
387 });
388}
389
390pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
391 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
392}
393
394pub trait DiagnosticRenderer {
395 fn render_group(
396 &self,
397 diagnostic_group: Vec<DiagnosticEntry<Point>>,
398 buffer_id: BufferId,
399 snapshot: EditorSnapshot,
400 editor: WeakEntity<Editor>,
401 cx: &mut App,
402 ) -> Vec<BlockProperties<Anchor>>;
403
404 fn render_hover(
405 &self,
406 diagnostic_group: Vec<DiagnosticEntry<Point>>,
407 range: Range<Point>,
408 buffer_id: BufferId,
409 cx: &mut App,
410 ) -> Option<Entity<markdown::Markdown>>;
411
412 fn open_link(
413 &self,
414 editor: &mut Editor,
415 link: SharedString,
416 window: &mut Window,
417 cx: &mut Context<Editor>,
418 );
419}
420
421pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
422
423impl GlobalDiagnosticRenderer {
424 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
425 cx.try_global::<Self>().map(|g| g.0.clone())
426 }
427}
428
429impl gpui::Global for GlobalDiagnosticRenderer {}
430pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
431 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
432}
433
434pub struct SearchWithinRange;
435
436trait InvalidationRegion {
437 fn ranges(&self) -> &[Range<Anchor>];
438}
439
440#[derive(Clone, Debug, PartialEq)]
441pub enum SelectPhase {
442 Begin {
443 position: DisplayPoint,
444 add: bool,
445 click_count: usize,
446 },
447 BeginColumnar {
448 position: DisplayPoint,
449 reset: bool,
450 goal_column: u32,
451 },
452 Extend {
453 position: DisplayPoint,
454 click_count: usize,
455 },
456 Update {
457 position: DisplayPoint,
458 goal_column: u32,
459 scroll_delta: gpui::Point<f32>,
460 },
461 End,
462}
463
464#[derive(Clone, Debug)]
465pub enum SelectMode {
466 Character,
467 Word(Range<Anchor>),
468 Line(Range<Anchor>),
469 All,
470}
471
472#[derive(Clone, PartialEq, Eq, Debug)]
473pub enum EditorMode {
474 SingleLine {
475 auto_width: bool,
476 },
477 AutoHeight {
478 max_lines: usize,
479 },
480 Full {
481 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
482 scale_ui_elements_with_buffer_font_size: bool,
483 /// When set to `true`, the editor will render a background for the active line.
484 show_active_line_background: bool,
485 /// When set to `true`, the editor's height will be determined by its content.
486 sized_by_content: bool,
487 },
488 Minimap {
489 parent: WeakEntity<Editor>,
490 },
491}
492
493impl EditorMode {
494 pub fn full() -> Self {
495 Self::Full {
496 scale_ui_elements_with_buffer_font_size: true,
497 show_active_line_background: true,
498 sized_by_content: false,
499 }
500 }
501
502 pub fn is_full(&self) -> bool {
503 matches!(self, Self::Full { .. })
504 }
505
506 fn is_minimap(&self) -> bool {
507 matches!(self, Self::Minimap { .. })
508 }
509}
510
511#[derive(Copy, Clone, Debug)]
512pub enum SoftWrap {
513 /// Prefer not to wrap at all.
514 ///
515 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
516 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
517 GitDiff,
518 /// Prefer a single line generally, unless an overly long line is encountered.
519 None,
520 /// Soft wrap lines that exceed the editor width.
521 EditorWidth,
522 /// Soft wrap lines at the preferred line length.
523 Column(u32),
524 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
525 Bounded(u32),
526}
527
528#[derive(Clone)]
529pub struct EditorStyle {
530 pub background: Hsla,
531 pub local_player: PlayerColor,
532 pub text: TextStyle,
533 pub scrollbar_width: Pixels,
534 pub syntax: Arc<SyntaxTheme>,
535 pub status: StatusColors,
536 pub inlay_hints_style: HighlightStyle,
537 pub inline_completion_styles: InlineCompletionStyles,
538 pub unnecessary_code_fade: f32,
539 pub show_underlines: bool,
540}
541
542impl Default for EditorStyle {
543 fn default() -> Self {
544 Self {
545 background: Hsla::default(),
546 local_player: PlayerColor::default(),
547 text: TextStyle::default(),
548 scrollbar_width: Pixels::default(),
549 syntax: Default::default(),
550 // HACK: Status colors don't have a real default.
551 // We should look into removing the status colors from the editor
552 // style and retrieve them directly from the theme.
553 status: StatusColors::dark(),
554 inlay_hints_style: HighlightStyle::default(),
555 inline_completion_styles: InlineCompletionStyles {
556 insertion: HighlightStyle::default(),
557 whitespace: HighlightStyle::default(),
558 },
559 unnecessary_code_fade: Default::default(),
560 show_underlines: true,
561 }
562 }
563}
564
565pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
566 let show_background = language_settings::language_settings(None, None, cx)
567 .inlay_hints
568 .show_background;
569
570 HighlightStyle {
571 color: Some(cx.theme().status().hint),
572 background_color: show_background.then(|| cx.theme().status().hint_background),
573 ..HighlightStyle::default()
574 }
575}
576
577pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
578 InlineCompletionStyles {
579 insertion: HighlightStyle {
580 color: Some(cx.theme().status().predictive),
581 ..HighlightStyle::default()
582 },
583 whitespace: HighlightStyle {
584 background_color: Some(cx.theme().status().created_background),
585 ..HighlightStyle::default()
586 },
587 }
588}
589
590type CompletionId = usize;
591
592pub(crate) enum EditDisplayMode {
593 TabAccept,
594 DiffPopover,
595 Inline,
596}
597
598enum InlineCompletion {
599 Edit {
600 edits: Vec<(Range<Anchor>, String)>,
601 edit_preview: Option<EditPreview>,
602 display_mode: EditDisplayMode,
603 snapshot: BufferSnapshot,
604 },
605 Move {
606 target: Anchor,
607 snapshot: BufferSnapshot,
608 },
609}
610
611struct InlineCompletionState {
612 inlay_ids: Vec<InlayId>,
613 completion: InlineCompletion,
614 completion_id: Option<SharedString>,
615 invalidation_range: Range<Anchor>,
616}
617
618enum EditPredictionSettings {
619 Disabled,
620 Enabled {
621 show_in_menu: bool,
622 preview_requires_modifier: bool,
623 },
624}
625
626enum InlineCompletionHighlight {}
627
628#[derive(Debug, Clone)]
629struct InlineDiagnostic {
630 message: SharedString,
631 group_id: usize,
632 is_primary: bool,
633 start: Point,
634 severity: lsp::DiagnosticSeverity,
635}
636
637pub enum MenuInlineCompletionsPolicy {
638 Never,
639 ByProvider,
640}
641
642pub enum EditPredictionPreview {
643 /// Modifier is not pressed
644 Inactive { released_too_fast: bool },
645 /// Modifier pressed
646 Active {
647 since: Instant,
648 previous_scroll_position: Option<ScrollAnchor>,
649 },
650}
651
652impl EditPredictionPreview {
653 pub fn released_too_fast(&self) -> bool {
654 match self {
655 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
656 EditPredictionPreview::Active { .. } => false,
657 }
658 }
659
660 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
661 if let EditPredictionPreview::Active {
662 previous_scroll_position,
663 ..
664 } = self
665 {
666 *previous_scroll_position = scroll_position;
667 }
668 }
669}
670
671pub struct ContextMenuOptions {
672 pub min_entries_visible: usize,
673 pub max_entries_visible: usize,
674 pub placement: Option<ContextMenuPlacement>,
675}
676
677#[derive(Debug, Clone, PartialEq, Eq)]
678pub enum ContextMenuPlacement {
679 Above,
680 Below,
681}
682
683#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
684struct EditorActionId(usize);
685
686impl EditorActionId {
687 pub fn post_inc(&mut self) -> Self {
688 let answer = self.0;
689
690 *self = Self(answer + 1);
691
692 Self(answer)
693 }
694}
695
696// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
697// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
698
699type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
700type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
701
702#[derive(Default)]
703struct ScrollbarMarkerState {
704 scrollbar_size: Size<Pixels>,
705 dirty: bool,
706 markers: Arc<[PaintQuad]>,
707 pending_refresh: Option<Task<Result<()>>>,
708}
709
710impl ScrollbarMarkerState {
711 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
712 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
713 }
714}
715
716#[derive(Clone, Copy, PartialEq, Eq)]
717pub enum MinimapVisibility {
718 Disabled,
719 Enabled(bool),
720}
721
722impl MinimapVisibility {
723 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
724 if mode.is_full() {
725 Self::Enabled(EditorSettings::get_global(cx).minimap.minimap_enabled())
726 } else {
727 Self::Disabled
728 }
729 }
730
731 fn disabled(&self) -> bool {
732 match *self {
733 Self::Disabled => true,
734 _ => false,
735 }
736 }
737
738 fn visible(&self) -> bool {
739 match *self {
740 Self::Enabled(visible) => visible,
741 _ => false,
742 }
743 }
744
745 fn toggle_visibility(&self) -> Self {
746 match *self {
747 Self::Enabled(visible) => Self::Enabled(!visible),
748 Self::Disabled => Self::Disabled,
749 }
750 }
751}
752
753#[derive(Clone, Debug)]
754struct RunnableTasks {
755 templates: Vec<(TaskSourceKind, TaskTemplate)>,
756 offset: multi_buffer::Anchor,
757 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
758 column: u32,
759 // Values of all named captures, including those starting with '_'
760 extra_variables: HashMap<String, String>,
761 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
762 context_range: Range<BufferOffset>,
763}
764
765impl RunnableTasks {
766 fn resolve<'a>(
767 &'a self,
768 cx: &'a task::TaskContext,
769 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
770 self.templates.iter().filter_map(|(kind, template)| {
771 template
772 .resolve_task(&kind.to_id_base(), cx)
773 .map(|task| (kind.clone(), task))
774 })
775 }
776}
777
778#[derive(Clone)]
779struct ResolvedTasks {
780 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
781 position: Anchor,
782}
783
784#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
785struct BufferOffset(usize);
786
787// Addons allow storing per-editor state in other crates (e.g. Vim)
788pub trait Addon: 'static {
789 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
790
791 fn render_buffer_header_controls(
792 &self,
793 _: &ExcerptInfo,
794 _: &Window,
795 _: &App,
796 ) -> Option<AnyElement> {
797 None
798 }
799
800 fn to_any(&self) -> &dyn std::any::Any;
801
802 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
803 None
804 }
805}
806
807/// A set of caret positions, registered when the editor was edited.
808pub struct ChangeList {
809 changes: Vec<Vec<Anchor>>,
810 /// Currently "selected" change.
811 position: Option<usize>,
812}
813
814impl ChangeList {
815 pub fn new() -> Self {
816 Self {
817 changes: Vec::new(),
818 position: None,
819 }
820 }
821
822 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
823 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
824 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
825 if self.changes.is_empty() {
826 return None;
827 }
828
829 let prev = self.position.unwrap_or(self.changes.len());
830 let next = if direction == Direction::Prev {
831 prev.saturating_sub(count)
832 } else {
833 (prev + count).min(self.changes.len() - 1)
834 };
835 self.position = Some(next);
836 self.changes.get(next).map(|anchors| anchors.as_slice())
837 }
838
839 /// Adds a new change to the list, resetting the change list position.
840 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
841 self.position.take();
842 if pop_state {
843 self.changes.pop();
844 }
845 self.changes.push(new_positions.clone());
846 }
847
848 pub fn last(&self) -> Option<&[Anchor]> {
849 self.changes.last().map(|anchors| anchors.as_slice())
850 }
851}
852
853#[derive(Clone)]
854struct InlineBlamePopoverState {
855 scroll_handle: ScrollHandle,
856 commit_message: Option<ParsedCommitMessage>,
857 markdown: Entity<Markdown>,
858}
859
860struct InlineBlamePopover {
861 position: gpui::Point<Pixels>,
862 show_task: Option<Task<()>>,
863 hide_task: Option<Task<()>>,
864 popover_bounds: Option<Bounds<Pixels>>,
865 popover_state: InlineBlamePopoverState,
866}
867
868/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
869/// a breakpoint on them.
870#[derive(Clone, Copy, Debug)]
871struct PhantomBreakpointIndicator {
872 display_row: DisplayRow,
873 /// There's a small debounce between hovering over the line and showing the indicator.
874 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
875 is_active: bool,
876 collides_with_existing_breakpoint: bool,
877}
878/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
879///
880/// See the [module level documentation](self) for more information.
881pub struct Editor {
882 focus_handle: FocusHandle,
883 last_focused_descendant: Option<WeakFocusHandle>,
884 /// The text buffer being edited
885 buffer: Entity<MultiBuffer>,
886 /// Map of how text in the buffer should be displayed.
887 /// Handles soft wraps, folds, fake inlay text insertions, etc.
888 pub display_map: Entity<DisplayMap>,
889 pub selections: SelectionsCollection,
890 pub scroll_manager: ScrollManager,
891 /// When inline assist editors are linked, they all render cursors because
892 /// typing enters text into each of them, even the ones that aren't focused.
893 pub(crate) show_cursor_when_unfocused: bool,
894 columnar_selection_tail: Option<Anchor>,
895 add_selections_state: Option<AddSelectionsState>,
896 select_next_state: Option<SelectNextState>,
897 select_prev_state: Option<SelectNextState>,
898 selection_history: SelectionHistory,
899 autoclose_regions: Vec<AutocloseRegion>,
900 snippet_stack: InvalidationStack<SnippetState>,
901 select_syntax_node_history: SelectSyntaxNodeHistory,
902 ime_transaction: Option<TransactionId>,
903 pub diagnostics_max_severity: DiagnosticSeverity,
904 active_diagnostics: ActiveDiagnostic,
905 show_inline_diagnostics: bool,
906 inline_diagnostics_update: Task<()>,
907 inline_diagnostics_enabled: bool,
908 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
909 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
910 hard_wrap: Option<usize>,
911
912 // TODO: make this a access method
913 pub project: Option<Entity<Project>>,
914 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
915 completion_provider: Option<Box<dyn CompletionProvider>>,
916 collaboration_hub: Option<Box<dyn CollaborationHub>>,
917 blink_manager: Entity<BlinkManager>,
918 show_cursor_names: bool,
919 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
920 pub show_local_selections: bool,
921 mode: EditorMode,
922 show_breadcrumbs: bool,
923 show_gutter: bool,
924 show_scrollbars: bool,
925 minimap_visibility: MinimapVisibility,
926 disable_expand_excerpt_buttons: bool,
927 show_line_numbers: Option<bool>,
928 use_relative_line_numbers: Option<bool>,
929 show_git_diff_gutter: Option<bool>,
930 show_code_actions: Option<bool>,
931 show_runnables: Option<bool>,
932 show_breakpoints: Option<bool>,
933 show_wrap_guides: Option<bool>,
934 show_indent_guides: Option<bool>,
935 placeholder_text: Option<Arc<str>>,
936 highlight_order: usize,
937 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
938 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
939 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
940 scrollbar_marker_state: ScrollbarMarkerState,
941 active_indent_guides_state: ActiveIndentGuidesState,
942 nav_history: Option<ItemNavHistory>,
943 context_menu: RefCell<Option<CodeContextMenu>>,
944 context_menu_options: Option<ContextMenuOptions>,
945 mouse_context_menu: Option<MouseContextMenu>,
946 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
947 inline_blame_popover: Option<InlineBlamePopover>,
948 signature_help_state: SignatureHelpState,
949 auto_signature_help: Option<bool>,
950 find_all_references_task_sources: Vec<Anchor>,
951 next_completion_id: CompletionId,
952 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
953 code_actions_task: Option<Task<Result<()>>>,
954 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
955 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
956 document_highlights_task: Option<Task<()>>,
957 linked_editing_range_task: Option<Task<Option<()>>>,
958 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
959 pending_rename: Option<RenameState>,
960 searchable: bool,
961 cursor_shape: CursorShape,
962 current_line_highlight: Option<CurrentLineHighlight>,
963 collapse_matches: bool,
964 autoindent_mode: Option<AutoindentMode>,
965 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
966 input_enabled: bool,
967 use_modal_editing: bool,
968 read_only: bool,
969 leader_id: Option<CollaboratorId>,
970 remote_id: Option<ViewId>,
971 pub hover_state: HoverState,
972 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
973 gutter_hovered: bool,
974 hovered_link_state: Option<HoveredLinkState>,
975 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
976 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
977 active_inline_completion: Option<InlineCompletionState>,
978 /// Used to prevent flickering as the user types while the menu is open
979 stale_inline_completion_in_menu: Option<InlineCompletionState>,
980 edit_prediction_settings: EditPredictionSettings,
981 inline_completions_hidden_for_vim_mode: bool,
982 show_inline_completions_override: Option<bool>,
983 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
984 edit_prediction_preview: EditPredictionPreview,
985 edit_prediction_indent_conflict: bool,
986 edit_prediction_requires_modifier_in_indent_conflict: bool,
987 inlay_hint_cache: InlayHintCache,
988 next_inlay_id: usize,
989 _subscriptions: Vec<Subscription>,
990 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
991 gutter_dimensions: GutterDimensions,
992 style: Option<EditorStyle>,
993 text_style_refinement: Option<TextStyleRefinement>,
994 next_editor_action_id: EditorActionId,
995 editor_actions:
996 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
997 use_autoclose: bool,
998 use_auto_surround: bool,
999 auto_replace_emoji_shortcode: bool,
1000 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1001 show_git_blame_gutter: bool,
1002 show_git_blame_inline: bool,
1003 show_git_blame_inline_delay_task: Option<Task<()>>,
1004 git_blame_inline_enabled: bool,
1005 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1006 serialize_dirty_buffers: bool,
1007 show_selection_menu: Option<bool>,
1008 blame: Option<Entity<GitBlame>>,
1009 blame_subscription: Option<Subscription>,
1010 custom_context_menu: Option<
1011 Box<
1012 dyn 'static
1013 + Fn(
1014 &mut Self,
1015 DisplayPoint,
1016 &mut Window,
1017 &mut Context<Self>,
1018 ) -> Option<Entity<ui::ContextMenu>>,
1019 >,
1020 >,
1021 last_bounds: Option<Bounds<Pixels>>,
1022 last_position_map: Option<Rc<PositionMap>>,
1023 expect_bounds_change: Option<Bounds<Pixels>>,
1024 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1025 tasks_update_task: Option<Task<()>>,
1026 breakpoint_store: Option<Entity<BreakpointStore>>,
1027 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1028 in_project_search: bool,
1029 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1030 breadcrumb_header: Option<String>,
1031 focused_block: Option<FocusedBlock>,
1032 next_scroll_position: NextScrollCursorCenterTopBottom,
1033 addons: HashMap<TypeId, Box<dyn Addon>>,
1034 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1035 load_diff_task: Option<Shared<Task<()>>>,
1036 /// Whether we are temporarily displaying a diff other than git's
1037 temporary_diff_override: bool,
1038 selection_mark_mode: bool,
1039 toggle_fold_multiple_buffers: Task<()>,
1040 _scroll_cursor_center_top_bottom_task: Task<()>,
1041 serialize_selections: Task<()>,
1042 serialize_folds: Task<()>,
1043 mouse_cursor_hidden: bool,
1044 minimap: Option<Entity<Self>>,
1045 hide_mouse_mode: HideMouseMode,
1046 pub change_list: ChangeList,
1047 inline_value_cache: InlineValueCache,
1048}
1049
1050#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1051enum NextScrollCursorCenterTopBottom {
1052 #[default]
1053 Center,
1054 Top,
1055 Bottom,
1056}
1057
1058impl NextScrollCursorCenterTopBottom {
1059 fn next(&self) -> Self {
1060 match self {
1061 Self::Center => Self::Top,
1062 Self::Top => Self::Bottom,
1063 Self::Bottom => Self::Center,
1064 }
1065 }
1066}
1067
1068#[derive(Clone)]
1069pub struct EditorSnapshot {
1070 pub mode: EditorMode,
1071 show_gutter: bool,
1072 show_line_numbers: Option<bool>,
1073 show_git_diff_gutter: Option<bool>,
1074 show_runnables: Option<bool>,
1075 show_breakpoints: Option<bool>,
1076 git_blame_gutter_max_author_length: Option<usize>,
1077 pub display_snapshot: DisplaySnapshot,
1078 pub placeholder_text: Option<Arc<str>>,
1079 is_focused: bool,
1080 scroll_anchor: ScrollAnchor,
1081 ongoing_scroll: OngoingScroll,
1082 current_line_highlight: CurrentLineHighlight,
1083 gutter_hovered: bool,
1084}
1085
1086#[derive(Default, Debug, Clone, Copy)]
1087pub struct GutterDimensions {
1088 pub left_padding: Pixels,
1089 pub right_padding: Pixels,
1090 pub width: Pixels,
1091 pub margin: Pixels,
1092 pub git_blame_entries_width: Option<Pixels>,
1093}
1094
1095impl GutterDimensions {
1096 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1097 Self {
1098 margin: Self::default_gutter_margin(font_id, font_size, cx),
1099 ..Default::default()
1100 }
1101 }
1102
1103 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1104 -cx.text_system().descent(font_id, font_size)
1105 }
1106 /// The full width of the space taken up by the gutter.
1107 pub fn full_width(&self) -> Pixels {
1108 self.margin + self.width
1109 }
1110
1111 /// The width of the space reserved for the fold indicators,
1112 /// use alongside 'justify_end' and `gutter_width` to
1113 /// right align content with the line numbers
1114 pub fn fold_area_width(&self) -> Pixels {
1115 self.margin + self.right_padding
1116 }
1117}
1118
1119#[derive(Debug)]
1120pub struct RemoteSelection {
1121 pub replica_id: ReplicaId,
1122 pub selection: Selection<Anchor>,
1123 pub cursor_shape: CursorShape,
1124 pub collaborator_id: CollaboratorId,
1125 pub line_mode: bool,
1126 pub user_name: Option<SharedString>,
1127 pub color: PlayerColor,
1128}
1129
1130#[derive(Clone, Debug)]
1131struct SelectionHistoryEntry {
1132 selections: Arc<[Selection<Anchor>]>,
1133 select_next_state: Option<SelectNextState>,
1134 select_prev_state: Option<SelectNextState>,
1135 add_selections_state: Option<AddSelectionsState>,
1136}
1137
1138enum SelectionHistoryMode {
1139 Normal,
1140 Undoing,
1141 Redoing,
1142}
1143
1144#[derive(Clone, PartialEq, Eq, Hash)]
1145struct HoveredCursor {
1146 replica_id: u16,
1147 selection_id: usize,
1148}
1149
1150impl Default for SelectionHistoryMode {
1151 fn default() -> Self {
1152 Self::Normal
1153 }
1154}
1155
1156#[derive(Default)]
1157struct SelectionHistory {
1158 #[allow(clippy::type_complexity)]
1159 selections_by_transaction:
1160 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1161 mode: SelectionHistoryMode,
1162 undo_stack: VecDeque<SelectionHistoryEntry>,
1163 redo_stack: VecDeque<SelectionHistoryEntry>,
1164}
1165
1166impl SelectionHistory {
1167 fn insert_transaction(
1168 &mut self,
1169 transaction_id: TransactionId,
1170 selections: Arc<[Selection<Anchor>]>,
1171 ) {
1172 self.selections_by_transaction
1173 .insert(transaction_id, (selections, None));
1174 }
1175
1176 #[allow(clippy::type_complexity)]
1177 fn transaction(
1178 &self,
1179 transaction_id: TransactionId,
1180 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1181 self.selections_by_transaction.get(&transaction_id)
1182 }
1183
1184 #[allow(clippy::type_complexity)]
1185 fn transaction_mut(
1186 &mut self,
1187 transaction_id: TransactionId,
1188 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1189 self.selections_by_transaction.get_mut(&transaction_id)
1190 }
1191
1192 fn push(&mut self, entry: SelectionHistoryEntry) {
1193 if !entry.selections.is_empty() {
1194 match self.mode {
1195 SelectionHistoryMode::Normal => {
1196 self.push_undo(entry);
1197 self.redo_stack.clear();
1198 }
1199 SelectionHistoryMode::Undoing => self.push_redo(entry),
1200 SelectionHistoryMode::Redoing => self.push_undo(entry),
1201 }
1202 }
1203 }
1204
1205 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1206 if self
1207 .undo_stack
1208 .back()
1209 .map_or(true, |e| e.selections != entry.selections)
1210 {
1211 self.undo_stack.push_back(entry);
1212 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1213 self.undo_stack.pop_front();
1214 }
1215 }
1216 }
1217
1218 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1219 if self
1220 .redo_stack
1221 .back()
1222 .map_or(true, |e| e.selections != entry.selections)
1223 {
1224 self.redo_stack.push_back(entry);
1225 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1226 self.redo_stack.pop_front();
1227 }
1228 }
1229 }
1230}
1231
1232#[derive(Clone, Copy)]
1233pub struct RowHighlightOptions {
1234 pub autoscroll: bool,
1235 pub include_gutter: bool,
1236}
1237
1238impl Default for RowHighlightOptions {
1239 fn default() -> Self {
1240 Self {
1241 autoscroll: Default::default(),
1242 include_gutter: true,
1243 }
1244 }
1245}
1246
1247struct RowHighlight {
1248 index: usize,
1249 range: Range<Anchor>,
1250 color: Hsla,
1251 options: RowHighlightOptions,
1252 type_id: TypeId,
1253}
1254
1255#[derive(Clone, Debug)]
1256struct AddSelectionsState {
1257 above: bool,
1258 stack: Vec<usize>,
1259}
1260
1261#[derive(Clone)]
1262struct SelectNextState {
1263 query: AhoCorasick,
1264 wordwise: bool,
1265 done: bool,
1266}
1267
1268impl std::fmt::Debug for SelectNextState {
1269 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1270 f.debug_struct(std::any::type_name::<Self>())
1271 .field("wordwise", &self.wordwise)
1272 .field("done", &self.done)
1273 .finish()
1274 }
1275}
1276
1277#[derive(Debug)]
1278struct AutocloseRegion {
1279 selection_id: usize,
1280 range: Range<Anchor>,
1281 pair: BracketPair,
1282}
1283
1284#[derive(Debug)]
1285struct SnippetState {
1286 ranges: Vec<Vec<Range<Anchor>>>,
1287 active_index: usize,
1288 choices: Vec<Option<Vec<String>>>,
1289}
1290
1291#[doc(hidden)]
1292pub struct RenameState {
1293 pub range: Range<Anchor>,
1294 pub old_name: Arc<str>,
1295 pub editor: Entity<Editor>,
1296 block_id: CustomBlockId,
1297}
1298
1299struct InvalidationStack<T>(Vec<T>);
1300
1301struct RegisteredInlineCompletionProvider {
1302 provider: Arc<dyn InlineCompletionProviderHandle>,
1303 _subscription: Subscription,
1304}
1305
1306#[derive(Debug, PartialEq, Eq)]
1307pub struct ActiveDiagnosticGroup {
1308 pub active_range: Range<Anchor>,
1309 pub active_message: String,
1310 pub group_id: usize,
1311 pub blocks: HashSet<CustomBlockId>,
1312}
1313
1314#[derive(Debug, PartialEq, Eq)]
1315#[allow(clippy::large_enum_variant)]
1316pub(crate) enum ActiveDiagnostic {
1317 None,
1318 All,
1319 Group(ActiveDiagnosticGroup),
1320}
1321
1322#[derive(Serialize, Deserialize, Clone, Debug)]
1323pub struct ClipboardSelection {
1324 /// The number of bytes in this selection.
1325 pub len: usize,
1326 /// Whether this was a full-line selection.
1327 pub is_entire_line: bool,
1328 /// The indentation of the first line when this content was originally copied.
1329 pub first_line_indent: u32,
1330}
1331
1332// selections, scroll behavior, was newest selection reversed
1333type SelectSyntaxNodeHistoryState = (
1334 Box<[Selection<usize>]>,
1335 SelectSyntaxNodeScrollBehavior,
1336 bool,
1337);
1338
1339#[derive(Default)]
1340struct SelectSyntaxNodeHistory {
1341 stack: Vec<SelectSyntaxNodeHistoryState>,
1342 // disable temporarily to allow changing selections without losing the stack
1343 pub disable_clearing: bool,
1344}
1345
1346impl SelectSyntaxNodeHistory {
1347 pub fn try_clear(&mut self) {
1348 if !self.disable_clearing {
1349 self.stack.clear();
1350 }
1351 }
1352
1353 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1354 self.stack.push(selection);
1355 }
1356
1357 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1358 self.stack.pop()
1359 }
1360}
1361
1362enum SelectSyntaxNodeScrollBehavior {
1363 CursorTop,
1364 FitSelection,
1365 CursorBottom,
1366}
1367
1368#[derive(Debug)]
1369pub(crate) struct NavigationData {
1370 cursor_anchor: Anchor,
1371 cursor_position: Point,
1372 scroll_anchor: ScrollAnchor,
1373 scroll_top_row: u32,
1374}
1375
1376#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1377pub enum GotoDefinitionKind {
1378 Symbol,
1379 Declaration,
1380 Type,
1381 Implementation,
1382}
1383
1384#[derive(Debug, Clone)]
1385enum InlayHintRefreshReason {
1386 ModifiersChanged(bool),
1387 Toggle(bool),
1388 SettingsChange(InlayHintSettings),
1389 NewLinesShown,
1390 BufferEdited(HashSet<Arc<Language>>),
1391 RefreshRequested,
1392 ExcerptsRemoved(Vec<ExcerptId>),
1393}
1394
1395impl InlayHintRefreshReason {
1396 fn description(&self) -> &'static str {
1397 match self {
1398 Self::ModifiersChanged(_) => "modifiers changed",
1399 Self::Toggle(_) => "toggle",
1400 Self::SettingsChange(_) => "settings change",
1401 Self::NewLinesShown => "new lines shown",
1402 Self::BufferEdited(_) => "buffer edited",
1403 Self::RefreshRequested => "refresh requested",
1404 Self::ExcerptsRemoved(_) => "excerpts removed",
1405 }
1406 }
1407}
1408
1409pub enum FormatTarget {
1410 Buffers,
1411 Ranges(Vec<Range<MultiBufferPoint>>),
1412}
1413
1414pub(crate) struct FocusedBlock {
1415 id: BlockId,
1416 focus_handle: WeakFocusHandle,
1417}
1418
1419#[derive(Clone)]
1420enum JumpData {
1421 MultiBufferRow {
1422 row: MultiBufferRow,
1423 line_offset_from_top: u32,
1424 },
1425 MultiBufferPoint {
1426 excerpt_id: ExcerptId,
1427 position: Point,
1428 anchor: text::Anchor,
1429 line_offset_from_top: u32,
1430 },
1431}
1432
1433pub enum MultibufferSelectionMode {
1434 First,
1435 All,
1436}
1437
1438#[derive(Clone, Copy, Debug, Default)]
1439pub struct RewrapOptions {
1440 pub override_language_settings: bool,
1441 pub preserve_existing_whitespace: bool,
1442}
1443
1444impl Editor {
1445 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1446 let buffer = cx.new(|cx| Buffer::local("", cx));
1447 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1448 Self::new(
1449 EditorMode::SingleLine { auto_width: false },
1450 buffer,
1451 None,
1452 window,
1453 cx,
1454 )
1455 }
1456
1457 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1458 let buffer = cx.new(|cx| Buffer::local("", cx));
1459 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1460 Self::new(EditorMode::full(), buffer, None, window, cx)
1461 }
1462
1463 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1464 let buffer = cx.new(|cx| Buffer::local("", cx));
1465 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1466 Self::new(
1467 EditorMode::SingleLine { auto_width: true },
1468 buffer,
1469 None,
1470 window,
1471 cx,
1472 )
1473 }
1474
1475 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1476 let buffer = cx.new(|cx| Buffer::local("", cx));
1477 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1478 Self::new(
1479 EditorMode::AutoHeight { max_lines },
1480 buffer,
1481 None,
1482 window,
1483 cx,
1484 )
1485 }
1486
1487 pub fn for_buffer(
1488 buffer: Entity<Buffer>,
1489 project: Option<Entity<Project>>,
1490 window: &mut Window,
1491 cx: &mut Context<Self>,
1492 ) -> Self {
1493 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1494 Self::new(EditorMode::full(), buffer, project, window, cx)
1495 }
1496
1497 pub fn for_multibuffer(
1498 buffer: Entity<MultiBuffer>,
1499 project: Option<Entity<Project>>,
1500 window: &mut Window,
1501 cx: &mut Context<Self>,
1502 ) -> Self {
1503 Self::new(EditorMode::full(), buffer, project, window, cx)
1504 }
1505
1506 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1507 let mut clone = Self::new(
1508 self.mode.clone(),
1509 self.buffer.clone(),
1510 self.project.clone(),
1511 window,
1512 cx,
1513 );
1514 self.display_map.update(cx, |display_map, cx| {
1515 let snapshot = display_map.snapshot(cx);
1516 clone.display_map.update(cx, |display_map, cx| {
1517 display_map.set_state(&snapshot, cx);
1518 });
1519 });
1520 clone.folds_did_change(cx);
1521 clone.selections.clone_state(&self.selections);
1522 clone.scroll_manager.clone_state(&self.scroll_manager);
1523 clone.searchable = self.searchable;
1524 clone.read_only = self.read_only;
1525 clone
1526 }
1527
1528 pub fn new(
1529 mode: EditorMode,
1530 buffer: Entity<MultiBuffer>,
1531 project: Option<Entity<Project>>,
1532 window: &mut Window,
1533 cx: &mut Context<Self>,
1534 ) -> Self {
1535 Editor::new_internal(mode, buffer, project, None, window, cx)
1536 }
1537
1538 fn new_internal(
1539 mode: EditorMode,
1540 buffer: Entity<MultiBuffer>,
1541 project: Option<Entity<Project>>,
1542 display_map: Option<Entity<DisplayMap>>,
1543 window: &mut Window,
1544 cx: &mut Context<Self>,
1545 ) -> Self {
1546 debug_assert!(
1547 display_map.is_none() || mode.is_minimap(),
1548 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1549 );
1550
1551 let full_mode = mode.is_full();
1552 let diagnostics_max_severity = if full_mode {
1553 EditorSettings::get_global(cx)
1554 .diagnostics_max_severity
1555 .unwrap_or(DiagnosticSeverity::Hint)
1556 } else {
1557 DiagnosticSeverity::Off
1558 };
1559 let style = window.text_style();
1560 let font_size = style.font_size.to_pixels(window.rem_size());
1561 let editor = cx.entity().downgrade();
1562 let fold_placeholder = FoldPlaceholder {
1563 constrain_width: true,
1564 render: Arc::new(move |fold_id, fold_range, cx| {
1565 let editor = editor.clone();
1566 div()
1567 .id(fold_id)
1568 .bg(cx.theme().colors().ghost_element_background)
1569 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1570 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1571 .rounded_xs()
1572 .size_full()
1573 .cursor_pointer()
1574 .child("⋯")
1575 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1576 .on_click(move |_, _window, cx| {
1577 editor
1578 .update(cx, |editor, cx| {
1579 editor.unfold_ranges(
1580 &[fold_range.start..fold_range.end],
1581 true,
1582 false,
1583 cx,
1584 );
1585 cx.stop_propagation();
1586 })
1587 .ok();
1588 })
1589 .into_any()
1590 }),
1591 merge_adjacent: true,
1592 ..FoldPlaceholder::default()
1593 };
1594 let display_map = display_map.unwrap_or_else(|| {
1595 cx.new(|cx| {
1596 DisplayMap::new(
1597 buffer.clone(),
1598 style.font(),
1599 font_size,
1600 None,
1601 FILE_HEADER_HEIGHT,
1602 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1603 fold_placeholder,
1604 diagnostics_max_severity,
1605 cx,
1606 )
1607 })
1608 });
1609
1610 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1611
1612 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1613
1614 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1615 .then(|| language_settings::SoftWrap::None);
1616
1617 let mut project_subscriptions = Vec::new();
1618 if mode.is_full() {
1619 if let Some(project) = project.as_ref() {
1620 project_subscriptions.push(cx.subscribe_in(
1621 project,
1622 window,
1623 |editor, _, event, window, cx| match event {
1624 project::Event::RefreshCodeLens => {
1625 // we always query lens with actions, without storing them, always refreshing them
1626 }
1627 project::Event::RefreshInlayHints => {
1628 editor
1629 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1630 }
1631 project::Event::SnippetEdit(id, snippet_edits) => {
1632 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1633 let focus_handle = editor.focus_handle(cx);
1634 if focus_handle.is_focused(window) {
1635 let snapshot = buffer.read(cx).snapshot();
1636 for (range, snippet) in snippet_edits {
1637 let editor_range =
1638 language::range_from_lsp(*range).to_offset(&snapshot);
1639 editor
1640 .insert_snippet(
1641 &[editor_range],
1642 snippet.clone(),
1643 window,
1644 cx,
1645 )
1646 .ok();
1647 }
1648 }
1649 }
1650 }
1651 _ => {}
1652 },
1653 ));
1654 if let Some(task_inventory) = project
1655 .read(cx)
1656 .task_store()
1657 .read(cx)
1658 .task_inventory()
1659 .cloned()
1660 {
1661 project_subscriptions.push(cx.observe_in(
1662 &task_inventory,
1663 window,
1664 |editor, _, window, cx| {
1665 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1666 },
1667 ));
1668 };
1669
1670 project_subscriptions.push(cx.subscribe_in(
1671 &project.read(cx).breakpoint_store(),
1672 window,
1673 |editor, _, event, window, cx| match event {
1674 BreakpointStoreEvent::ClearDebugLines => {
1675 editor.clear_row_highlights::<ActiveDebugLine>();
1676 editor.refresh_inline_values(cx);
1677 }
1678 BreakpointStoreEvent::SetDebugLine => {
1679 if editor.go_to_active_debug_line(window, cx) {
1680 cx.stop_propagation();
1681 }
1682
1683 editor.refresh_inline_values(cx);
1684 }
1685 _ => {}
1686 },
1687 ));
1688 }
1689 }
1690
1691 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1692
1693 let inlay_hint_settings =
1694 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1695 let focus_handle = cx.focus_handle();
1696 cx.on_focus(&focus_handle, window, Self::handle_focus)
1697 .detach();
1698 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1699 .detach();
1700 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1701 .detach();
1702 cx.on_blur(&focus_handle, window, Self::handle_blur)
1703 .detach();
1704
1705 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1706 Some(false)
1707 } else {
1708 None
1709 };
1710
1711 let breakpoint_store = match (&mode, project.as_ref()) {
1712 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1713 _ => None,
1714 };
1715
1716 let mut code_action_providers = Vec::new();
1717 let mut load_uncommitted_diff = None;
1718 if let Some(project) = project.clone() {
1719 load_uncommitted_diff = Some(
1720 update_uncommitted_diff_for_buffer(
1721 cx.entity(),
1722 &project,
1723 buffer.read(cx).all_buffers(),
1724 buffer.clone(),
1725 cx,
1726 )
1727 .shared(),
1728 );
1729 code_action_providers.push(Rc::new(project) as Rc<_>);
1730 }
1731
1732 let mut this = Self {
1733 focus_handle,
1734 show_cursor_when_unfocused: false,
1735 last_focused_descendant: None,
1736 buffer: buffer.clone(),
1737 display_map: display_map.clone(),
1738 selections,
1739 scroll_manager: ScrollManager::new(cx),
1740 columnar_selection_tail: None,
1741 add_selections_state: None,
1742 select_next_state: None,
1743 select_prev_state: None,
1744 selection_history: SelectionHistory::default(),
1745 autoclose_regions: Vec::new(),
1746 snippet_stack: InvalidationStack::default(),
1747 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1748 ime_transaction: None,
1749 active_diagnostics: ActiveDiagnostic::None,
1750 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1751 inline_diagnostics_update: Task::ready(()),
1752 inline_diagnostics: Vec::new(),
1753 soft_wrap_mode_override,
1754 diagnostics_max_severity,
1755 hard_wrap: None,
1756 completion_provider: project.clone().map(|project| Box::new(project) as _),
1757 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1758 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1759 project,
1760 blink_manager: blink_manager.clone(),
1761 show_local_selections: true,
1762 show_scrollbars: full_mode,
1763 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1764 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1765 show_gutter: mode.is_full(),
1766 show_line_numbers: None,
1767 use_relative_line_numbers: None,
1768 disable_expand_excerpt_buttons: false,
1769 show_git_diff_gutter: None,
1770 show_code_actions: None,
1771 show_runnables: None,
1772 show_breakpoints: None,
1773 show_wrap_guides: None,
1774 show_indent_guides,
1775 placeholder_text: None,
1776 highlight_order: 0,
1777 highlighted_rows: HashMap::default(),
1778 background_highlights: TreeMap::default(),
1779 gutter_highlights: TreeMap::default(),
1780 scrollbar_marker_state: ScrollbarMarkerState::default(),
1781 active_indent_guides_state: ActiveIndentGuidesState::default(),
1782 nav_history: None,
1783 context_menu: RefCell::new(None),
1784 context_menu_options: None,
1785 mouse_context_menu: None,
1786 completion_tasks: Vec::new(),
1787 inline_blame_popover: None,
1788 signature_help_state: SignatureHelpState::default(),
1789 auto_signature_help: None,
1790 find_all_references_task_sources: Vec::new(),
1791 next_completion_id: 0,
1792 next_inlay_id: 0,
1793 code_action_providers,
1794 available_code_actions: None,
1795 code_actions_task: None,
1796 quick_selection_highlight_task: None,
1797 debounced_selection_highlight_task: None,
1798 document_highlights_task: None,
1799 linked_editing_range_task: None,
1800 pending_rename: None,
1801 searchable: true,
1802 cursor_shape: EditorSettings::get_global(cx)
1803 .cursor_shape
1804 .unwrap_or_default(),
1805 current_line_highlight: None,
1806 autoindent_mode: Some(AutoindentMode::EachLine),
1807 collapse_matches: false,
1808 workspace: None,
1809 input_enabled: true,
1810 use_modal_editing: mode.is_full(),
1811 read_only: mode.is_minimap(),
1812 use_autoclose: true,
1813 use_auto_surround: true,
1814 auto_replace_emoji_shortcode: false,
1815 jsx_tag_auto_close_enabled_in_any_buffer: false,
1816 leader_id: None,
1817 remote_id: None,
1818 hover_state: HoverState::default(),
1819 pending_mouse_down: None,
1820 hovered_link_state: None,
1821 edit_prediction_provider: None,
1822 active_inline_completion: None,
1823 stale_inline_completion_in_menu: None,
1824 edit_prediction_preview: EditPredictionPreview::Inactive {
1825 released_too_fast: false,
1826 },
1827 inline_diagnostics_enabled: mode.is_full(),
1828 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1829 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1830
1831 gutter_hovered: false,
1832 pixel_position_of_newest_cursor: None,
1833 last_bounds: None,
1834 last_position_map: None,
1835 expect_bounds_change: None,
1836 gutter_dimensions: GutterDimensions::default(),
1837 style: None,
1838 show_cursor_names: false,
1839 hovered_cursors: HashMap::default(),
1840 next_editor_action_id: EditorActionId::default(),
1841 editor_actions: Rc::default(),
1842 inline_completions_hidden_for_vim_mode: false,
1843 show_inline_completions_override: None,
1844 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1845 edit_prediction_settings: EditPredictionSettings::Disabled,
1846 edit_prediction_indent_conflict: false,
1847 edit_prediction_requires_modifier_in_indent_conflict: true,
1848 custom_context_menu: None,
1849 show_git_blame_gutter: false,
1850 show_git_blame_inline: false,
1851 show_selection_menu: None,
1852 show_git_blame_inline_delay_task: None,
1853 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1854 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1855 serialize_dirty_buffers: !mode.is_minimap()
1856 && ProjectSettings::get_global(cx)
1857 .session
1858 .restore_unsaved_buffers,
1859 blame: None,
1860 blame_subscription: None,
1861 tasks: BTreeMap::default(),
1862
1863 breakpoint_store,
1864 gutter_breakpoint_indicator: (None, None),
1865 _subscriptions: vec![
1866 cx.observe(&buffer, Self::on_buffer_changed),
1867 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1868 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1869 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1870 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1871 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1872 cx.observe_window_activation(window, |editor, window, cx| {
1873 let active = window.is_window_active();
1874 editor.blink_manager.update(cx, |blink_manager, cx| {
1875 if active {
1876 blink_manager.enable(cx);
1877 } else {
1878 blink_manager.disable(cx);
1879 }
1880 });
1881 }),
1882 ],
1883 tasks_update_task: None,
1884 linked_edit_ranges: Default::default(),
1885 in_project_search: false,
1886 previous_search_ranges: None,
1887 breadcrumb_header: None,
1888 focused_block: None,
1889 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1890 addons: HashMap::default(),
1891 registered_buffers: HashMap::default(),
1892 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1893 selection_mark_mode: false,
1894 toggle_fold_multiple_buffers: Task::ready(()),
1895 serialize_selections: Task::ready(()),
1896 serialize_folds: Task::ready(()),
1897 text_style_refinement: None,
1898 load_diff_task: load_uncommitted_diff,
1899 temporary_diff_override: false,
1900 mouse_cursor_hidden: false,
1901 minimap: None,
1902 hide_mouse_mode: EditorSettings::get_global(cx)
1903 .hide_mouse
1904 .unwrap_or_default(),
1905 change_list: ChangeList::new(),
1906 mode,
1907 };
1908 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1909 this._subscriptions
1910 .push(cx.observe(breakpoints, |_, _, cx| {
1911 cx.notify();
1912 }));
1913 }
1914 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1915 this._subscriptions.extend(project_subscriptions);
1916
1917 this._subscriptions.push(cx.subscribe_in(
1918 &cx.entity(),
1919 window,
1920 |editor, _, e: &EditorEvent, window, cx| match e {
1921 EditorEvent::ScrollPositionChanged { local, .. } => {
1922 if *local {
1923 let new_anchor = editor.scroll_manager.anchor();
1924 let snapshot = editor.snapshot(window, cx);
1925 editor.update_restoration_data(cx, move |data| {
1926 data.scroll_position = (
1927 new_anchor.top_row(&snapshot.buffer_snapshot),
1928 new_anchor.offset,
1929 );
1930 });
1931 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
1932 editor.inline_blame_popover.take();
1933 }
1934 }
1935 EditorEvent::Edited { .. } => {
1936 if !vim_enabled(cx) {
1937 let (map, selections) = editor.selections.all_adjusted_display(cx);
1938 let pop_state = editor
1939 .change_list
1940 .last()
1941 .map(|previous| {
1942 previous.len() == selections.len()
1943 && previous.iter().enumerate().all(|(ix, p)| {
1944 p.to_display_point(&map).row()
1945 == selections[ix].head().row()
1946 })
1947 })
1948 .unwrap_or(false);
1949 let new_positions = selections
1950 .into_iter()
1951 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
1952 .collect();
1953 editor
1954 .change_list
1955 .push_to_change_list(pop_state, new_positions);
1956 }
1957 }
1958 _ => (),
1959 },
1960 ));
1961
1962 if let Some(dap_store) = this
1963 .project
1964 .as_ref()
1965 .map(|project| project.read(cx).dap_store())
1966 {
1967 let weak_editor = cx.weak_entity();
1968
1969 this._subscriptions
1970 .push(
1971 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
1972 let session_entity = cx.entity();
1973 weak_editor
1974 .update(cx, |editor, cx| {
1975 editor._subscriptions.push(
1976 cx.subscribe(&session_entity, Self::on_debug_session_event),
1977 );
1978 })
1979 .ok();
1980 }),
1981 );
1982
1983 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
1984 this._subscriptions
1985 .push(cx.subscribe(&session, Self::on_debug_session_event));
1986 }
1987 }
1988
1989 this.end_selection(window, cx);
1990 this.scroll_manager.show_scrollbars(window, cx);
1991 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1992
1993 if full_mode {
1994 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1995 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1996
1997 if this.git_blame_inline_enabled {
1998 this.start_git_blame_inline(false, window, cx);
1999 }
2000
2001 this.go_to_active_debug_line(window, cx);
2002
2003 if let Some(buffer) = buffer.read(cx).as_singleton() {
2004 if let Some(project) = this.project.as_ref() {
2005 let handle = project.update(cx, |project, cx| {
2006 project.register_buffer_with_language_servers(&buffer, cx)
2007 });
2008 this.registered_buffers
2009 .insert(buffer.read(cx).remote_id(), handle);
2010 }
2011 }
2012
2013 this.minimap = this.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2014 }
2015
2016 this.report_editor_event("Editor Opened", None, cx);
2017 this
2018 }
2019
2020 pub fn deploy_mouse_context_menu(
2021 &mut self,
2022 position: gpui::Point<Pixels>,
2023 context_menu: Entity<ContextMenu>,
2024 window: &mut Window,
2025 cx: &mut Context<Self>,
2026 ) {
2027 self.mouse_context_menu = Some(MouseContextMenu::new(
2028 self,
2029 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2030 context_menu,
2031 window,
2032 cx,
2033 ));
2034 }
2035
2036 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2037 self.mouse_context_menu
2038 .as_ref()
2039 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2040 }
2041
2042 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2043 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2044 }
2045
2046 fn key_context_internal(
2047 &self,
2048 has_active_edit_prediction: bool,
2049 window: &Window,
2050 cx: &App,
2051 ) -> KeyContext {
2052 let mut key_context = KeyContext::new_with_defaults();
2053 key_context.add("Editor");
2054 let mode = match self.mode {
2055 EditorMode::SingleLine { .. } => "single_line",
2056 EditorMode::AutoHeight { .. } => "auto_height",
2057 EditorMode::Minimap { .. } => "minimap",
2058 EditorMode::Full { .. } => "full",
2059 };
2060
2061 if EditorSettings::jupyter_enabled(cx) {
2062 key_context.add("jupyter");
2063 }
2064
2065 key_context.set("mode", mode);
2066 if self.pending_rename.is_some() {
2067 key_context.add("renaming");
2068 }
2069
2070 match self.context_menu.borrow().as_ref() {
2071 Some(CodeContextMenu::Completions(_)) => {
2072 key_context.add("menu");
2073 key_context.add("showing_completions");
2074 }
2075 Some(CodeContextMenu::CodeActions(_)) => {
2076 key_context.add("menu");
2077 key_context.add("showing_code_actions")
2078 }
2079 None => {}
2080 }
2081
2082 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2083 if !self.focus_handle(cx).contains_focused(window, cx)
2084 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2085 {
2086 for addon in self.addons.values() {
2087 addon.extend_key_context(&mut key_context, cx)
2088 }
2089 }
2090
2091 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2092 if let Some(extension) = singleton_buffer
2093 .read(cx)
2094 .file()
2095 .and_then(|file| file.path().extension()?.to_str())
2096 {
2097 key_context.set("extension", extension.to_string());
2098 }
2099 } else {
2100 key_context.add("multibuffer");
2101 }
2102
2103 if has_active_edit_prediction {
2104 if self.edit_prediction_in_conflict() {
2105 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2106 } else {
2107 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2108 key_context.add("copilot_suggestion");
2109 }
2110 }
2111
2112 if self.selection_mark_mode {
2113 key_context.add("selection_mode");
2114 }
2115
2116 key_context
2117 }
2118
2119 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2120 self.mouse_cursor_hidden = match origin {
2121 HideMouseCursorOrigin::TypingAction => {
2122 matches!(
2123 self.hide_mouse_mode,
2124 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2125 )
2126 }
2127 HideMouseCursorOrigin::MovementAction => {
2128 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2129 }
2130 };
2131 }
2132
2133 pub fn edit_prediction_in_conflict(&self) -> bool {
2134 if !self.show_edit_predictions_in_menu() {
2135 return false;
2136 }
2137
2138 let showing_completions = self
2139 .context_menu
2140 .borrow()
2141 .as_ref()
2142 .map_or(false, |context| {
2143 matches!(context, CodeContextMenu::Completions(_))
2144 });
2145
2146 showing_completions
2147 || self.edit_prediction_requires_modifier()
2148 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2149 // bindings to insert tab characters.
2150 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2151 }
2152
2153 pub fn accept_edit_prediction_keybind(
2154 &self,
2155 window: &Window,
2156 cx: &App,
2157 ) -> AcceptEditPredictionBinding {
2158 let key_context = self.key_context_internal(true, window, cx);
2159 let in_conflict = self.edit_prediction_in_conflict();
2160
2161 AcceptEditPredictionBinding(
2162 window
2163 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2164 .into_iter()
2165 .filter(|binding| {
2166 !in_conflict
2167 || binding
2168 .keystrokes()
2169 .first()
2170 .map_or(false, |keystroke| keystroke.modifiers.modified())
2171 })
2172 .rev()
2173 .min_by_key(|binding| {
2174 binding
2175 .keystrokes()
2176 .first()
2177 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
2178 }),
2179 )
2180 }
2181
2182 pub fn new_file(
2183 workspace: &mut Workspace,
2184 _: &workspace::NewFile,
2185 window: &mut Window,
2186 cx: &mut Context<Workspace>,
2187 ) {
2188 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2189 "Failed to create buffer",
2190 window,
2191 cx,
2192 |e, _, _| match e.error_code() {
2193 ErrorCode::RemoteUpgradeRequired => Some(format!(
2194 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2195 e.error_tag("required").unwrap_or("the latest version")
2196 )),
2197 _ => None,
2198 },
2199 );
2200 }
2201
2202 pub fn new_in_workspace(
2203 workspace: &mut Workspace,
2204 window: &mut Window,
2205 cx: &mut Context<Workspace>,
2206 ) -> Task<Result<Entity<Editor>>> {
2207 let project = workspace.project().clone();
2208 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2209
2210 cx.spawn_in(window, async move |workspace, cx| {
2211 let buffer = create.await?;
2212 workspace.update_in(cx, |workspace, window, cx| {
2213 let editor =
2214 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2215 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2216 editor
2217 })
2218 })
2219 }
2220
2221 fn new_file_vertical(
2222 workspace: &mut Workspace,
2223 _: &workspace::NewFileSplitVertical,
2224 window: &mut Window,
2225 cx: &mut Context<Workspace>,
2226 ) {
2227 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2228 }
2229
2230 fn new_file_horizontal(
2231 workspace: &mut Workspace,
2232 _: &workspace::NewFileSplitHorizontal,
2233 window: &mut Window,
2234 cx: &mut Context<Workspace>,
2235 ) {
2236 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2237 }
2238
2239 fn new_file_in_direction(
2240 workspace: &mut Workspace,
2241 direction: SplitDirection,
2242 window: &mut Window,
2243 cx: &mut Context<Workspace>,
2244 ) {
2245 let project = workspace.project().clone();
2246 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2247
2248 cx.spawn_in(window, async move |workspace, cx| {
2249 let buffer = create.await?;
2250 workspace.update_in(cx, move |workspace, window, cx| {
2251 workspace.split_item(
2252 direction,
2253 Box::new(
2254 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2255 ),
2256 window,
2257 cx,
2258 )
2259 })?;
2260 anyhow::Ok(())
2261 })
2262 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2263 match e.error_code() {
2264 ErrorCode::RemoteUpgradeRequired => Some(format!(
2265 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2266 e.error_tag("required").unwrap_or("the latest version")
2267 )),
2268 _ => None,
2269 }
2270 });
2271 }
2272
2273 pub fn leader_id(&self) -> Option<CollaboratorId> {
2274 self.leader_id
2275 }
2276
2277 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2278 &self.buffer
2279 }
2280
2281 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2282 self.workspace.as_ref()?.0.upgrade()
2283 }
2284
2285 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2286 self.buffer().read(cx).title(cx)
2287 }
2288
2289 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2290 let git_blame_gutter_max_author_length = self
2291 .render_git_blame_gutter(cx)
2292 .then(|| {
2293 if let Some(blame) = self.blame.as_ref() {
2294 let max_author_length =
2295 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2296 Some(max_author_length)
2297 } else {
2298 None
2299 }
2300 })
2301 .flatten();
2302
2303 EditorSnapshot {
2304 mode: self.mode.clone(),
2305 show_gutter: self.show_gutter,
2306 show_line_numbers: self.show_line_numbers,
2307 show_git_diff_gutter: self.show_git_diff_gutter,
2308 show_runnables: self.show_runnables,
2309 show_breakpoints: self.show_breakpoints,
2310 git_blame_gutter_max_author_length,
2311 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2312 scroll_anchor: self.scroll_manager.anchor(),
2313 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2314 placeholder_text: self.placeholder_text.clone(),
2315 is_focused: self.focus_handle.is_focused(window),
2316 current_line_highlight: self
2317 .current_line_highlight
2318 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2319 gutter_hovered: self.gutter_hovered,
2320 }
2321 }
2322
2323 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2324 self.buffer.read(cx).language_at(point, cx)
2325 }
2326
2327 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2328 self.buffer.read(cx).read(cx).file_at(point).cloned()
2329 }
2330
2331 pub fn active_excerpt(
2332 &self,
2333 cx: &App,
2334 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2335 self.buffer
2336 .read(cx)
2337 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2338 }
2339
2340 pub fn mode(&self) -> &EditorMode {
2341 &self.mode
2342 }
2343
2344 pub fn set_mode(&mut self, mode: EditorMode) {
2345 self.mode = mode;
2346 }
2347
2348 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2349 self.collaboration_hub.as_deref()
2350 }
2351
2352 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2353 self.collaboration_hub = Some(hub);
2354 }
2355
2356 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2357 self.in_project_search = in_project_search;
2358 }
2359
2360 pub fn set_custom_context_menu(
2361 &mut self,
2362 f: impl 'static
2363 + Fn(
2364 &mut Self,
2365 DisplayPoint,
2366 &mut Window,
2367 &mut Context<Self>,
2368 ) -> Option<Entity<ui::ContextMenu>>,
2369 ) {
2370 self.custom_context_menu = Some(Box::new(f))
2371 }
2372
2373 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
2374 self.completion_provider = provider;
2375 }
2376
2377 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2378 self.semantics_provider.clone()
2379 }
2380
2381 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2382 self.semantics_provider = provider;
2383 }
2384
2385 pub fn set_edit_prediction_provider<T>(
2386 &mut self,
2387 provider: Option<Entity<T>>,
2388 window: &mut Window,
2389 cx: &mut Context<Self>,
2390 ) where
2391 T: EditPredictionProvider,
2392 {
2393 self.edit_prediction_provider =
2394 provider.map(|provider| RegisteredInlineCompletionProvider {
2395 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2396 if this.focus_handle.is_focused(window) {
2397 this.update_visible_inline_completion(window, cx);
2398 }
2399 }),
2400 provider: Arc::new(provider),
2401 });
2402 self.update_edit_prediction_settings(cx);
2403 self.refresh_inline_completion(false, false, window, cx);
2404 }
2405
2406 pub fn placeholder_text(&self) -> Option<&str> {
2407 self.placeholder_text.as_deref()
2408 }
2409
2410 pub fn set_placeholder_text(
2411 &mut self,
2412 placeholder_text: impl Into<Arc<str>>,
2413 cx: &mut Context<Self>,
2414 ) {
2415 let placeholder_text = Some(placeholder_text.into());
2416 if self.placeholder_text != placeholder_text {
2417 self.placeholder_text = placeholder_text;
2418 cx.notify();
2419 }
2420 }
2421
2422 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2423 self.cursor_shape = cursor_shape;
2424
2425 // Disrupt blink for immediate user feedback that the cursor shape has changed
2426 self.blink_manager.update(cx, BlinkManager::show_cursor);
2427
2428 cx.notify();
2429 }
2430
2431 pub fn set_current_line_highlight(
2432 &mut self,
2433 current_line_highlight: Option<CurrentLineHighlight>,
2434 ) {
2435 self.current_line_highlight = current_line_highlight;
2436 }
2437
2438 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2439 self.collapse_matches = collapse_matches;
2440 }
2441
2442 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2443 let buffers = self.buffer.read(cx).all_buffers();
2444 let Some(project) = self.project.as_ref() else {
2445 return;
2446 };
2447 project.update(cx, |project, cx| {
2448 for buffer in buffers {
2449 self.registered_buffers
2450 .entry(buffer.read(cx).remote_id())
2451 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2452 }
2453 })
2454 }
2455
2456 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2457 if self.collapse_matches {
2458 return range.start..range.start;
2459 }
2460 range.clone()
2461 }
2462
2463 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2464 if self.display_map.read(cx).clip_at_line_ends != clip {
2465 self.display_map
2466 .update(cx, |map, _| map.clip_at_line_ends = clip);
2467 }
2468 }
2469
2470 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2471 self.input_enabled = input_enabled;
2472 }
2473
2474 pub fn set_inline_completions_hidden_for_vim_mode(
2475 &mut self,
2476 hidden: bool,
2477 window: &mut Window,
2478 cx: &mut Context<Self>,
2479 ) {
2480 if hidden != self.inline_completions_hidden_for_vim_mode {
2481 self.inline_completions_hidden_for_vim_mode = hidden;
2482 if hidden {
2483 self.update_visible_inline_completion(window, cx);
2484 } else {
2485 self.refresh_inline_completion(true, false, window, cx);
2486 }
2487 }
2488 }
2489
2490 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2491 self.menu_inline_completions_policy = value;
2492 }
2493
2494 pub fn set_autoindent(&mut self, autoindent: bool) {
2495 if autoindent {
2496 self.autoindent_mode = Some(AutoindentMode::EachLine);
2497 } else {
2498 self.autoindent_mode = None;
2499 }
2500 }
2501
2502 pub fn read_only(&self, cx: &App) -> bool {
2503 self.read_only || self.buffer.read(cx).read_only()
2504 }
2505
2506 pub fn set_read_only(&mut self, read_only: bool) {
2507 self.read_only = read_only;
2508 }
2509
2510 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2511 self.use_autoclose = autoclose;
2512 }
2513
2514 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2515 self.use_auto_surround = auto_surround;
2516 }
2517
2518 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2519 self.auto_replace_emoji_shortcode = auto_replace;
2520 }
2521
2522 pub fn toggle_edit_predictions(
2523 &mut self,
2524 _: &ToggleEditPrediction,
2525 window: &mut Window,
2526 cx: &mut Context<Self>,
2527 ) {
2528 if self.show_inline_completions_override.is_some() {
2529 self.set_show_edit_predictions(None, window, cx);
2530 } else {
2531 let show_edit_predictions = !self.edit_predictions_enabled();
2532 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2533 }
2534 }
2535
2536 pub fn set_show_edit_predictions(
2537 &mut self,
2538 show_edit_predictions: Option<bool>,
2539 window: &mut Window,
2540 cx: &mut Context<Self>,
2541 ) {
2542 self.show_inline_completions_override = show_edit_predictions;
2543 self.update_edit_prediction_settings(cx);
2544
2545 if let Some(false) = show_edit_predictions {
2546 self.discard_inline_completion(false, cx);
2547 } else {
2548 self.refresh_inline_completion(false, true, window, cx);
2549 }
2550 }
2551
2552 fn inline_completions_disabled_in_scope(
2553 &self,
2554 buffer: &Entity<Buffer>,
2555 buffer_position: language::Anchor,
2556 cx: &App,
2557 ) -> bool {
2558 let snapshot = buffer.read(cx).snapshot();
2559 let settings = snapshot.settings_at(buffer_position, cx);
2560
2561 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2562 return false;
2563 };
2564
2565 scope.override_name().map_or(false, |scope_name| {
2566 settings
2567 .edit_predictions_disabled_in
2568 .iter()
2569 .any(|s| s == scope_name)
2570 })
2571 }
2572
2573 pub fn set_use_modal_editing(&mut self, to: bool) {
2574 self.use_modal_editing = to;
2575 }
2576
2577 pub fn use_modal_editing(&self) -> bool {
2578 self.use_modal_editing
2579 }
2580
2581 fn selections_did_change(
2582 &mut self,
2583 local: bool,
2584 old_cursor_position: &Anchor,
2585 show_completions: bool,
2586 window: &mut Window,
2587 cx: &mut Context<Self>,
2588 ) {
2589 window.invalidate_character_coordinates();
2590
2591 // Copy selections to primary selection buffer
2592 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2593 if local {
2594 let selections = self.selections.all::<usize>(cx);
2595 let buffer_handle = self.buffer.read(cx).read(cx);
2596
2597 let mut text = String::new();
2598 for (index, selection) in selections.iter().enumerate() {
2599 let text_for_selection = buffer_handle
2600 .text_for_range(selection.start..selection.end)
2601 .collect::<String>();
2602
2603 text.push_str(&text_for_selection);
2604 if index != selections.len() - 1 {
2605 text.push('\n');
2606 }
2607 }
2608
2609 if !text.is_empty() {
2610 cx.write_to_primary(ClipboardItem::new_string(text));
2611 }
2612 }
2613
2614 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2615 self.buffer.update(cx, |buffer, cx| {
2616 buffer.set_active_selections(
2617 &self.selections.disjoint_anchors(),
2618 self.selections.line_mode,
2619 self.cursor_shape,
2620 cx,
2621 )
2622 });
2623 }
2624 let display_map = self
2625 .display_map
2626 .update(cx, |display_map, cx| display_map.snapshot(cx));
2627 let buffer = &display_map.buffer_snapshot;
2628 self.add_selections_state = None;
2629 self.select_next_state = None;
2630 self.select_prev_state = None;
2631 self.select_syntax_node_history.try_clear();
2632 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2633 self.snippet_stack
2634 .invalidate(&self.selections.disjoint_anchors(), buffer);
2635 self.take_rename(false, window, cx);
2636
2637 let new_cursor_position = self.selections.newest_anchor().head();
2638
2639 self.push_to_nav_history(
2640 *old_cursor_position,
2641 Some(new_cursor_position.to_point(buffer)),
2642 false,
2643 cx,
2644 );
2645
2646 if local {
2647 let new_cursor_position = self.selections.newest_anchor().head();
2648 let mut context_menu = self.context_menu.borrow_mut();
2649 let completion_menu = match context_menu.as_ref() {
2650 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2651 _ => {
2652 *context_menu = None;
2653 None
2654 }
2655 };
2656 if let Some(buffer_id) = new_cursor_position.buffer_id {
2657 if !self.registered_buffers.contains_key(&buffer_id) {
2658 if let Some(project) = self.project.as_ref() {
2659 project.update(cx, |project, cx| {
2660 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2661 return;
2662 };
2663 self.registered_buffers.insert(
2664 buffer_id,
2665 project.register_buffer_with_language_servers(&buffer, cx),
2666 );
2667 })
2668 }
2669 }
2670 }
2671
2672 if let Some(completion_menu) = completion_menu {
2673 let cursor_position = new_cursor_position.to_offset(buffer);
2674 let (word_range, kind) =
2675 buffer.surrounding_word(completion_menu.initial_position, true);
2676 if kind == Some(CharKind::Word)
2677 && word_range.to_inclusive().contains(&cursor_position)
2678 {
2679 let mut completion_menu = completion_menu.clone();
2680 drop(context_menu);
2681
2682 let query = Self::completion_query(buffer, cursor_position);
2683 cx.spawn(async move |this, cx| {
2684 completion_menu
2685 .filter(query.as_deref(), cx.background_executor().clone())
2686 .await;
2687
2688 this.update(cx, |this, cx| {
2689 let mut context_menu = this.context_menu.borrow_mut();
2690 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2691 else {
2692 return;
2693 };
2694
2695 if menu.id > completion_menu.id {
2696 return;
2697 }
2698
2699 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2700 drop(context_menu);
2701 cx.notify();
2702 })
2703 })
2704 .detach();
2705
2706 if show_completions {
2707 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2708 }
2709 } else {
2710 drop(context_menu);
2711 self.hide_context_menu(window, cx);
2712 }
2713 } else {
2714 drop(context_menu);
2715 }
2716
2717 hide_hover(self, cx);
2718
2719 if old_cursor_position.to_display_point(&display_map).row()
2720 != new_cursor_position.to_display_point(&display_map).row()
2721 {
2722 self.available_code_actions.take();
2723 }
2724 self.refresh_code_actions(window, cx);
2725 self.refresh_document_highlights(cx);
2726 self.refresh_selected_text_highlights(false, window, cx);
2727 refresh_matching_bracket_highlights(self, window, cx);
2728 self.update_visible_inline_completion(window, cx);
2729 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2730 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2731 self.inline_blame_popover.take();
2732 if self.git_blame_inline_enabled {
2733 self.start_inline_blame_timer(window, cx);
2734 }
2735 }
2736
2737 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2738 cx.emit(EditorEvent::SelectionsChanged { local });
2739
2740 let selections = &self.selections.disjoint;
2741 if selections.len() == 1 {
2742 cx.emit(SearchEvent::ActiveMatchChanged)
2743 }
2744 if local {
2745 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2746 let inmemory_selections = selections
2747 .iter()
2748 .map(|s| {
2749 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2750 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2751 })
2752 .collect();
2753 self.update_restoration_data(cx, |data| {
2754 data.selections = inmemory_selections;
2755 });
2756
2757 if WorkspaceSettings::get(None, cx).restore_on_startup
2758 != RestoreOnStartupBehavior::None
2759 {
2760 if let Some(workspace_id) =
2761 self.workspace.as_ref().and_then(|workspace| workspace.1)
2762 {
2763 let snapshot = self.buffer().read(cx).snapshot(cx);
2764 let selections = selections.clone();
2765 let background_executor = cx.background_executor().clone();
2766 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2767 self.serialize_selections = cx.background_spawn(async move {
2768 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2769 let db_selections = selections
2770 .iter()
2771 .map(|selection| {
2772 (
2773 selection.start.to_offset(&snapshot),
2774 selection.end.to_offset(&snapshot),
2775 )
2776 })
2777 .collect();
2778
2779 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2780 .await
2781 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2782 .log_err();
2783 });
2784 }
2785 }
2786 }
2787 }
2788
2789 cx.notify();
2790 }
2791
2792 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2793 use text::ToOffset as _;
2794 use text::ToPoint as _;
2795
2796 if self.mode.is_minimap()
2797 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2798 {
2799 return;
2800 }
2801
2802 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2803 return;
2804 };
2805
2806 let snapshot = singleton.read(cx).snapshot();
2807 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2808 let display_snapshot = display_map.snapshot(cx);
2809
2810 display_snapshot
2811 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2812 .map(|fold| {
2813 fold.range.start.text_anchor.to_point(&snapshot)
2814 ..fold.range.end.text_anchor.to_point(&snapshot)
2815 })
2816 .collect()
2817 });
2818 self.update_restoration_data(cx, |data| {
2819 data.folds = inmemory_folds;
2820 });
2821
2822 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2823 return;
2824 };
2825 let background_executor = cx.background_executor().clone();
2826 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2827 let db_folds = self.display_map.update(cx, |display_map, cx| {
2828 display_map
2829 .snapshot(cx)
2830 .folds_in_range(0..snapshot.len())
2831 .map(|fold| {
2832 (
2833 fold.range.start.text_anchor.to_offset(&snapshot),
2834 fold.range.end.text_anchor.to_offset(&snapshot),
2835 )
2836 })
2837 .collect()
2838 });
2839 self.serialize_folds = cx.background_spawn(async move {
2840 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2841 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2842 .await
2843 .with_context(|| {
2844 format!(
2845 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2846 )
2847 })
2848 .log_err();
2849 });
2850 }
2851
2852 pub fn sync_selections(
2853 &mut self,
2854 other: Entity<Editor>,
2855 cx: &mut Context<Self>,
2856 ) -> gpui::Subscription {
2857 let other_selections = other.read(cx).selections.disjoint.to_vec();
2858 self.selections.change_with(cx, |selections| {
2859 selections.select_anchors(other_selections);
2860 });
2861
2862 let other_subscription =
2863 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2864 EditorEvent::SelectionsChanged { local: true } => {
2865 let other_selections = other.read(cx).selections.disjoint.to_vec();
2866 if other_selections.is_empty() {
2867 return;
2868 }
2869 this.selections.change_with(cx, |selections| {
2870 selections.select_anchors(other_selections);
2871 });
2872 }
2873 _ => {}
2874 });
2875
2876 let this_subscription =
2877 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2878 EditorEvent::SelectionsChanged { local: true } => {
2879 let these_selections = this.selections.disjoint.to_vec();
2880 if these_selections.is_empty() {
2881 return;
2882 }
2883 other.update(cx, |other_editor, cx| {
2884 other_editor.selections.change_with(cx, |selections| {
2885 selections.select_anchors(these_selections);
2886 })
2887 });
2888 }
2889 _ => {}
2890 });
2891
2892 Subscription::join(other_subscription, this_subscription)
2893 }
2894
2895 pub fn change_selections<R>(
2896 &mut self,
2897 autoscroll: Option<Autoscroll>,
2898 window: &mut Window,
2899 cx: &mut Context<Self>,
2900 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2901 ) -> R {
2902 self.change_selections_inner(autoscroll, true, window, cx, change)
2903 }
2904
2905 fn change_selections_inner<R>(
2906 &mut self,
2907 autoscroll: Option<Autoscroll>,
2908 request_completions: bool,
2909 window: &mut Window,
2910 cx: &mut Context<Self>,
2911 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2912 ) -> R {
2913 let old_cursor_position = self.selections.newest_anchor().head();
2914 self.push_to_selection_history();
2915
2916 let (changed, result) = self.selections.change_with(cx, change);
2917
2918 if changed {
2919 if let Some(autoscroll) = autoscroll {
2920 self.request_autoscroll(autoscroll, cx);
2921 }
2922 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2923
2924 if self.should_open_signature_help_automatically(
2925 &old_cursor_position,
2926 self.signature_help_state.backspace_pressed(),
2927 cx,
2928 ) {
2929 self.show_signature_help(&ShowSignatureHelp, window, cx);
2930 }
2931 self.signature_help_state.set_backspace_pressed(false);
2932 }
2933
2934 result
2935 }
2936
2937 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2938 where
2939 I: IntoIterator<Item = (Range<S>, T)>,
2940 S: ToOffset,
2941 T: Into<Arc<str>>,
2942 {
2943 if self.read_only(cx) {
2944 return;
2945 }
2946
2947 self.buffer
2948 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2949 }
2950
2951 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2952 where
2953 I: IntoIterator<Item = (Range<S>, T)>,
2954 S: ToOffset,
2955 T: Into<Arc<str>>,
2956 {
2957 if self.read_only(cx) {
2958 return;
2959 }
2960
2961 self.buffer.update(cx, |buffer, cx| {
2962 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2963 });
2964 }
2965
2966 pub fn edit_with_block_indent<I, S, T>(
2967 &mut self,
2968 edits: I,
2969 original_indent_columns: Vec<Option<u32>>,
2970 cx: &mut Context<Self>,
2971 ) where
2972 I: IntoIterator<Item = (Range<S>, T)>,
2973 S: ToOffset,
2974 T: Into<Arc<str>>,
2975 {
2976 if self.read_only(cx) {
2977 return;
2978 }
2979
2980 self.buffer.update(cx, |buffer, cx| {
2981 buffer.edit(
2982 edits,
2983 Some(AutoindentMode::Block {
2984 original_indent_columns,
2985 }),
2986 cx,
2987 )
2988 });
2989 }
2990
2991 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2992 self.hide_context_menu(window, cx);
2993
2994 match phase {
2995 SelectPhase::Begin {
2996 position,
2997 add,
2998 click_count,
2999 } => self.begin_selection(position, add, click_count, window, cx),
3000 SelectPhase::BeginColumnar {
3001 position,
3002 goal_column,
3003 reset,
3004 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3005 SelectPhase::Extend {
3006 position,
3007 click_count,
3008 } => self.extend_selection(position, click_count, window, cx),
3009 SelectPhase::Update {
3010 position,
3011 goal_column,
3012 scroll_delta,
3013 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3014 SelectPhase::End => self.end_selection(window, cx),
3015 }
3016 }
3017
3018 fn extend_selection(
3019 &mut self,
3020 position: DisplayPoint,
3021 click_count: usize,
3022 window: &mut Window,
3023 cx: &mut Context<Self>,
3024 ) {
3025 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3026 let tail = self.selections.newest::<usize>(cx).tail();
3027 self.begin_selection(position, false, click_count, window, cx);
3028
3029 let position = position.to_offset(&display_map, Bias::Left);
3030 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3031
3032 let mut pending_selection = self
3033 .selections
3034 .pending_anchor()
3035 .expect("extend_selection not called with pending selection");
3036 if position >= tail {
3037 pending_selection.start = tail_anchor;
3038 } else {
3039 pending_selection.end = tail_anchor;
3040 pending_selection.reversed = true;
3041 }
3042
3043 let mut pending_mode = self.selections.pending_mode().unwrap();
3044 match &mut pending_mode {
3045 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3046 _ => {}
3047 }
3048
3049 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3050
3051 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3052 s.set_pending(pending_selection, pending_mode)
3053 });
3054 }
3055
3056 fn begin_selection(
3057 &mut self,
3058 position: DisplayPoint,
3059 add: bool,
3060 click_count: usize,
3061 window: &mut Window,
3062 cx: &mut Context<Self>,
3063 ) {
3064 if !self.focus_handle.is_focused(window) {
3065 self.last_focused_descendant = None;
3066 window.focus(&self.focus_handle);
3067 }
3068
3069 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3070 let buffer = &display_map.buffer_snapshot;
3071 let position = display_map.clip_point(position, Bias::Left);
3072
3073 let start;
3074 let end;
3075 let mode;
3076 let mut auto_scroll;
3077 match click_count {
3078 1 => {
3079 start = buffer.anchor_before(position.to_point(&display_map));
3080 end = start;
3081 mode = SelectMode::Character;
3082 auto_scroll = true;
3083 }
3084 2 => {
3085 let range = movement::surrounding_word(&display_map, position);
3086 start = buffer.anchor_before(range.start.to_point(&display_map));
3087 end = buffer.anchor_before(range.end.to_point(&display_map));
3088 mode = SelectMode::Word(start..end);
3089 auto_scroll = true;
3090 }
3091 3 => {
3092 let position = display_map
3093 .clip_point(position, Bias::Left)
3094 .to_point(&display_map);
3095 let line_start = display_map.prev_line_boundary(position).0;
3096 let next_line_start = buffer.clip_point(
3097 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3098 Bias::Left,
3099 );
3100 start = buffer.anchor_before(line_start);
3101 end = buffer.anchor_before(next_line_start);
3102 mode = SelectMode::Line(start..end);
3103 auto_scroll = true;
3104 }
3105 _ => {
3106 start = buffer.anchor_before(0);
3107 end = buffer.anchor_before(buffer.len());
3108 mode = SelectMode::All;
3109 auto_scroll = false;
3110 }
3111 }
3112 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3113
3114 let point_to_delete: Option<usize> = {
3115 let selected_points: Vec<Selection<Point>> =
3116 self.selections.disjoint_in_range(start..end, cx);
3117
3118 if !add || click_count > 1 {
3119 None
3120 } else if !selected_points.is_empty() {
3121 Some(selected_points[0].id)
3122 } else {
3123 let clicked_point_already_selected =
3124 self.selections.disjoint.iter().find(|selection| {
3125 selection.start.to_point(buffer) == start.to_point(buffer)
3126 || selection.end.to_point(buffer) == end.to_point(buffer)
3127 });
3128
3129 clicked_point_already_selected.map(|selection| selection.id)
3130 }
3131 };
3132
3133 let selections_count = self.selections.count();
3134
3135 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3136 if let Some(point_to_delete) = point_to_delete {
3137 s.delete(point_to_delete);
3138
3139 if selections_count == 1 {
3140 s.set_pending_anchor_range(start..end, mode);
3141 }
3142 } else {
3143 if !add {
3144 s.clear_disjoint();
3145 }
3146
3147 s.set_pending_anchor_range(start..end, mode);
3148 }
3149 });
3150 }
3151
3152 fn begin_columnar_selection(
3153 &mut self,
3154 position: DisplayPoint,
3155 goal_column: u32,
3156 reset: bool,
3157 window: &mut Window,
3158 cx: &mut Context<Self>,
3159 ) {
3160 if !self.focus_handle.is_focused(window) {
3161 self.last_focused_descendant = None;
3162 window.focus(&self.focus_handle);
3163 }
3164
3165 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3166
3167 if reset {
3168 let pointer_position = display_map
3169 .buffer_snapshot
3170 .anchor_before(position.to_point(&display_map));
3171
3172 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3173 s.clear_disjoint();
3174 s.set_pending_anchor_range(
3175 pointer_position..pointer_position,
3176 SelectMode::Character,
3177 );
3178 });
3179 }
3180
3181 let tail = self.selections.newest::<Point>(cx).tail();
3182 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3183
3184 if !reset {
3185 self.select_columns(
3186 tail.to_display_point(&display_map),
3187 position,
3188 goal_column,
3189 &display_map,
3190 window,
3191 cx,
3192 );
3193 }
3194 }
3195
3196 fn update_selection(
3197 &mut self,
3198 position: DisplayPoint,
3199 goal_column: u32,
3200 scroll_delta: gpui::Point<f32>,
3201 window: &mut Window,
3202 cx: &mut Context<Self>,
3203 ) {
3204 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3205
3206 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3207 let tail = tail.to_display_point(&display_map);
3208 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3209 } else if let Some(mut pending) = self.selections.pending_anchor() {
3210 let buffer = self.buffer.read(cx).snapshot(cx);
3211 let head;
3212 let tail;
3213 let mode = self.selections.pending_mode().unwrap();
3214 match &mode {
3215 SelectMode::Character => {
3216 head = position.to_point(&display_map);
3217 tail = pending.tail().to_point(&buffer);
3218 }
3219 SelectMode::Word(original_range) => {
3220 let original_display_range = original_range.start.to_display_point(&display_map)
3221 ..original_range.end.to_display_point(&display_map);
3222 let original_buffer_range = original_display_range.start.to_point(&display_map)
3223 ..original_display_range.end.to_point(&display_map);
3224 if movement::is_inside_word(&display_map, position)
3225 || original_display_range.contains(&position)
3226 {
3227 let word_range = movement::surrounding_word(&display_map, position);
3228 if word_range.start < original_display_range.start {
3229 head = word_range.start.to_point(&display_map);
3230 } else {
3231 head = word_range.end.to_point(&display_map);
3232 }
3233 } else {
3234 head = position.to_point(&display_map);
3235 }
3236
3237 if head <= original_buffer_range.start {
3238 tail = original_buffer_range.end;
3239 } else {
3240 tail = original_buffer_range.start;
3241 }
3242 }
3243 SelectMode::Line(original_range) => {
3244 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3245
3246 let position = display_map
3247 .clip_point(position, Bias::Left)
3248 .to_point(&display_map);
3249 let line_start = display_map.prev_line_boundary(position).0;
3250 let next_line_start = buffer.clip_point(
3251 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3252 Bias::Left,
3253 );
3254
3255 if line_start < original_range.start {
3256 head = line_start
3257 } else {
3258 head = next_line_start
3259 }
3260
3261 if head <= original_range.start {
3262 tail = original_range.end;
3263 } else {
3264 tail = original_range.start;
3265 }
3266 }
3267 SelectMode::All => {
3268 return;
3269 }
3270 };
3271
3272 if head < tail {
3273 pending.start = buffer.anchor_before(head);
3274 pending.end = buffer.anchor_before(tail);
3275 pending.reversed = true;
3276 } else {
3277 pending.start = buffer.anchor_before(tail);
3278 pending.end = buffer.anchor_before(head);
3279 pending.reversed = false;
3280 }
3281
3282 self.change_selections(None, window, cx, |s| {
3283 s.set_pending(pending, mode);
3284 });
3285 } else {
3286 log::error!("update_selection dispatched with no pending selection");
3287 return;
3288 }
3289
3290 self.apply_scroll_delta(scroll_delta, window, cx);
3291 cx.notify();
3292 }
3293
3294 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3295 self.columnar_selection_tail.take();
3296 if self.selections.pending_anchor().is_some() {
3297 let selections = self.selections.all::<usize>(cx);
3298 self.change_selections(None, window, cx, |s| {
3299 s.select(selections);
3300 s.clear_pending();
3301 });
3302 }
3303 }
3304
3305 fn select_columns(
3306 &mut self,
3307 tail: DisplayPoint,
3308 head: DisplayPoint,
3309 goal_column: u32,
3310 display_map: &DisplaySnapshot,
3311 window: &mut Window,
3312 cx: &mut Context<Self>,
3313 ) {
3314 let start_row = cmp::min(tail.row(), head.row());
3315 let end_row = cmp::max(tail.row(), head.row());
3316 let start_column = cmp::min(tail.column(), goal_column);
3317 let end_column = cmp::max(tail.column(), goal_column);
3318 let reversed = start_column < tail.column();
3319
3320 let selection_ranges = (start_row.0..=end_row.0)
3321 .map(DisplayRow)
3322 .filter_map(|row| {
3323 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
3324 let start = display_map
3325 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3326 .to_point(display_map);
3327 let end = display_map
3328 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3329 .to_point(display_map);
3330 if reversed {
3331 Some(end..start)
3332 } else {
3333 Some(start..end)
3334 }
3335 } else {
3336 None
3337 }
3338 })
3339 .collect::<Vec<_>>();
3340
3341 self.change_selections(None, window, cx, |s| {
3342 s.select_ranges(selection_ranges);
3343 });
3344 cx.notify();
3345 }
3346
3347 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3348 self.selections
3349 .all_adjusted(cx)
3350 .iter()
3351 .any(|selection| !selection.is_empty())
3352 }
3353
3354 pub fn has_pending_nonempty_selection(&self) -> bool {
3355 let pending_nonempty_selection = match self.selections.pending_anchor() {
3356 Some(Selection { start, end, .. }) => start != end,
3357 None => false,
3358 };
3359
3360 pending_nonempty_selection
3361 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3362 }
3363
3364 pub fn has_pending_selection(&self) -> bool {
3365 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3366 }
3367
3368 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3369 self.selection_mark_mode = false;
3370
3371 if self.clear_expanded_diff_hunks(cx) {
3372 cx.notify();
3373 return;
3374 }
3375 if self.dismiss_menus_and_popups(true, window, cx) {
3376 return;
3377 }
3378
3379 if self.mode.is_full()
3380 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3381 {
3382 return;
3383 }
3384
3385 cx.propagate();
3386 }
3387
3388 pub fn dismiss_menus_and_popups(
3389 &mut self,
3390 is_user_requested: bool,
3391 window: &mut Window,
3392 cx: &mut Context<Self>,
3393 ) -> bool {
3394 if self.take_rename(false, window, cx).is_some() {
3395 return true;
3396 }
3397
3398 if hide_hover(self, cx) {
3399 return true;
3400 }
3401
3402 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3403 return true;
3404 }
3405
3406 if self.hide_context_menu(window, cx).is_some() {
3407 return true;
3408 }
3409
3410 if self.mouse_context_menu.take().is_some() {
3411 return true;
3412 }
3413
3414 if is_user_requested && self.discard_inline_completion(true, cx) {
3415 return true;
3416 }
3417
3418 if self.snippet_stack.pop().is_some() {
3419 return true;
3420 }
3421
3422 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3423 self.dismiss_diagnostics(cx);
3424 return true;
3425 }
3426
3427 false
3428 }
3429
3430 fn linked_editing_ranges_for(
3431 &self,
3432 selection: Range<text::Anchor>,
3433 cx: &App,
3434 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3435 if self.linked_edit_ranges.is_empty() {
3436 return None;
3437 }
3438 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3439 selection.end.buffer_id.and_then(|end_buffer_id| {
3440 if selection.start.buffer_id != Some(end_buffer_id) {
3441 return None;
3442 }
3443 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3444 let snapshot = buffer.read(cx).snapshot();
3445 self.linked_edit_ranges
3446 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3447 .map(|ranges| (ranges, snapshot, buffer))
3448 })?;
3449 use text::ToOffset as TO;
3450 // find offset from the start of current range to current cursor position
3451 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3452
3453 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3454 let start_difference = start_offset - start_byte_offset;
3455 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3456 let end_difference = end_offset - start_byte_offset;
3457 // Current range has associated linked ranges.
3458 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3459 for range in linked_ranges.iter() {
3460 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3461 let end_offset = start_offset + end_difference;
3462 let start_offset = start_offset + start_difference;
3463 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3464 continue;
3465 }
3466 if self.selections.disjoint_anchor_ranges().any(|s| {
3467 if s.start.buffer_id != selection.start.buffer_id
3468 || s.end.buffer_id != selection.end.buffer_id
3469 {
3470 return false;
3471 }
3472 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3473 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3474 }) {
3475 continue;
3476 }
3477 let start = buffer_snapshot.anchor_after(start_offset);
3478 let end = buffer_snapshot.anchor_after(end_offset);
3479 linked_edits
3480 .entry(buffer.clone())
3481 .or_default()
3482 .push(start..end);
3483 }
3484 Some(linked_edits)
3485 }
3486
3487 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3488 let text: Arc<str> = text.into();
3489
3490 if self.read_only(cx) {
3491 return;
3492 }
3493
3494 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3495
3496 let selections = self.selections.all_adjusted(cx);
3497 let mut bracket_inserted = false;
3498 let mut edits = Vec::new();
3499 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3500 let mut new_selections = Vec::with_capacity(selections.len());
3501 let mut new_autoclose_regions = Vec::new();
3502 let snapshot = self.buffer.read(cx).read(cx);
3503 let mut clear_linked_edit_ranges = false;
3504
3505 for (selection, autoclose_region) in
3506 self.selections_with_autoclose_regions(selections, &snapshot)
3507 {
3508 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3509 // Determine if the inserted text matches the opening or closing
3510 // bracket of any of this language's bracket pairs.
3511 let mut bracket_pair = None;
3512 let mut is_bracket_pair_start = false;
3513 let mut is_bracket_pair_end = false;
3514 if !text.is_empty() {
3515 let mut bracket_pair_matching_end = None;
3516 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3517 // and they are removing the character that triggered IME popup.
3518 for (pair, enabled) in scope.brackets() {
3519 if !pair.close && !pair.surround {
3520 continue;
3521 }
3522
3523 if enabled && pair.start.ends_with(text.as_ref()) {
3524 let prefix_len = pair.start.len() - text.len();
3525 let preceding_text_matches_prefix = prefix_len == 0
3526 || (selection.start.column >= (prefix_len as u32)
3527 && snapshot.contains_str_at(
3528 Point::new(
3529 selection.start.row,
3530 selection.start.column - (prefix_len as u32),
3531 ),
3532 &pair.start[..prefix_len],
3533 ));
3534 if preceding_text_matches_prefix {
3535 bracket_pair = Some(pair.clone());
3536 is_bracket_pair_start = true;
3537 break;
3538 }
3539 }
3540 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3541 {
3542 // take first bracket pair matching end, but don't break in case a later bracket
3543 // pair matches start
3544 bracket_pair_matching_end = Some(pair.clone());
3545 }
3546 }
3547 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3548 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3549 is_bracket_pair_end = true;
3550 }
3551 }
3552
3553 if let Some(bracket_pair) = bracket_pair {
3554 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3555 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3556 let auto_surround =
3557 self.use_auto_surround && snapshot_settings.use_auto_surround;
3558 if selection.is_empty() {
3559 if is_bracket_pair_start {
3560 // If the inserted text is a suffix of an opening bracket and the
3561 // selection is preceded by the rest of the opening bracket, then
3562 // insert the closing bracket.
3563 let following_text_allows_autoclose = snapshot
3564 .chars_at(selection.start)
3565 .next()
3566 .map_or(true, |c| scope.should_autoclose_before(c));
3567
3568 let preceding_text_allows_autoclose = selection.start.column == 0
3569 || snapshot.reversed_chars_at(selection.start).next().map_or(
3570 true,
3571 |c| {
3572 bracket_pair.start != bracket_pair.end
3573 || !snapshot
3574 .char_classifier_at(selection.start)
3575 .is_word(c)
3576 },
3577 );
3578
3579 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3580 && bracket_pair.start.len() == 1
3581 {
3582 let target = bracket_pair.start.chars().next().unwrap();
3583 let current_line_count = snapshot
3584 .reversed_chars_at(selection.start)
3585 .take_while(|&c| c != '\n')
3586 .filter(|&c| c == target)
3587 .count();
3588 current_line_count % 2 == 1
3589 } else {
3590 false
3591 };
3592
3593 if autoclose
3594 && bracket_pair.close
3595 && following_text_allows_autoclose
3596 && preceding_text_allows_autoclose
3597 && !is_closing_quote
3598 {
3599 let anchor = snapshot.anchor_before(selection.end);
3600 new_selections.push((selection.map(|_| anchor), text.len()));
3601 new_autoclose_regions.push((
3602 anchor,
3603 text.len(),
3604 selection.id,
3605 bracket_pair.clone(),
3606 ));
3607 edits.push((
3608 selection.range(),
3609 format!("{}{}", text, bracket_pair.end).into(),
3610 ));
3611 bracket_inserted = true;
3612 continue;
3613 }
3614 }
3615
3616 if let Some(region) = autoclose_region {
3617 // If the selection is followed by an auto-inserted closing bracket,
3618 // then don't insert that closing bracket again; just move the selection
3619 // past the closing bracket.
3620 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3621 && text.as_ref() == region.pair.end.as_str();
3622 if should_skip {
3623 let anchor = snapshot.anchor_after(selection.end);
3624 new_selections
3625 .push((selection.map(|_| anchor), region.pair.end.len()));
3626 continue;
3627 }
3628 }
3629
3630 let always_treat_brackets_as_autoclosed = snapshot
3631 .language_settings_at(selection.start, cx)
3632 .always_treat_brackets_as_autoclosed;
3633 if always_treat_brackets_as_autoclosed
3634 && is_bracket_pair_end
3635 && snapshot.contains_str_at(selection.end, text.as_ref())
3636 {
3637 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3638 // and the inserted text is a closing bracket and the selection is followed
3639 // by the closing bracket then move the selection past the closing bracket.
3640 let anchor = snapshot.anchor_after(selection.end);
3641 new_selections.push((selection.map(|_| anchor), text.len()));
3642 continue;
3643 }
3644 }
3645 // If an opening bracket is 1 character long and is typed while
3646 // text is selected, then surround that text with the bracket pair.
3647 else if auto_surround
3648 && bracket_pair.surround
3649 && is_bracket_pair_start
3650 && bracket_pair.start.chars().count() == 1
3651 {
3652 edits.push((selection.start..selection.start, text.clone()));
3653 edits.push((
3654 selection.end..selection.end,
3655 bracket_pair.end.as_str().into(),
3656 ));
3657 bracket_inserted = true;
3658 new_selections.push((
3659 Selection {
3660 id: selection.id,
3661 start: snapshot.anchor_after(selection.start),
3662 end: snapshot.anchor_before(selection.end),
3663 reversed: selection.reversed,
3664 goal: selection.goal,
3665 },
3666 0,
3667 ));
3668 continue;
3669 }
3670 }
3671 }
3672
3673 if self.auto_replace_emoji_shortcode
3674 && selection.is_empty()
3675 && text.as_ref().ends_with(':')
3676 {
3677 if let Some(possible_emoji_short_code) =
3678 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3679 {
3680 if !possible_emoji_short_code.is_empty() {
3681 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3682 let emoji_shortcode_start = Point::new(
3683 selection.start.row,
3684 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3685 );
3686
3687 // Remove shortcode from buffer
3688 edits.push((
3689 emoji_shortcode_start..selection.start,
3690 "".to_string().into(),
3691 ));
3692 new_selections.push((
3693 Selection {
3694 id: selection.id,
3695 start: snapshot.anchor_after(emoji_shortcode_start),
3696 end: snapshot.anchor_before(selection.start),
3697 reversed: selection.reversed,
3698 goal: selection.goal,
3699 },
3700 0,
3701 ));
3702
3703 // Insert emoji
3704 let selection_start_anchor = snapshot.anchor_after(selection.start);
3705 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3706 edits.push((selection.start..selection.end, emoji.to_string().into()));
3707
3708 continue;
3709 }
3710 }
3711 }
3712 }
3713
3714 // If not handling any auto-close operation, then just replace the selected
3715 // text with the given input and move the selection to the end of the
3716 // newly inserted text.
3717 let anchor = snapshot.anchor_after(selection.end);
3718 if !self.linked_edit_ranges.is_empty() {
3719 let start_anchor = snapshot.anchor_before(selection.start);
3720
3721 let is_word_char = text.chars().next().map_or(true, |char| {
3722 let classifier = snapshot
3723 .char_classifier_at(start_anchor.to_offset(&snapshot))
3724 .ignore_punctuation(true);
3725 classifier.is_word(char)
3726 });
3727
3728 if is_word_char {
3729 if let Some(ranges) = self
3730 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3731 {
3732 for (buffer, edits) in ranges {
3733 linked_edits
3734 .entry(buffer.clone())
3735 .or_default()
3736 .extend(edits.into_iter().map(|range| (range, text.clone())));
3737 }
3738 }
3739 } else {
3740 clear_linked_edit_ranges = true;
3741 }
3742 }
3743
3744 new_selections.push((selection.map(|_| anchor), 0));
3745 edits.push((selection.start..selection.end, text.clone()));
3746 }
3747
3748 drop(snapshot);
3749
3750 self.transact(window, cx, |this, window, cx| {
3751 if clear_linked_edit_ranges {
3752 this.linked_edit_ranges.clear();
3753 }
3754 let initial_buffer_versions =
3755 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3756
3757 this.buffer.update(cx, |buffer, cx| {
3758 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3759 });
3760 for (buffer, edits) in linked_edits {
3761 buffer.update(cx, |buffer, cx| {
3762 let snapshot = buffer.snapshot();
3763 let edits = edits
3764 .into_iter()
3765 .map(|(range, text)| {
3766 use text::ToPoint as TP;
3767 let end_point = TP::to_point(&range.end, &snapshot);
3768 let start_point = TP::to_point(&range.start, &snapshot);
3769 (start_point..end_point, text)
3770 })
3771 .sorted_by_key(|(range, _)| range.start);
3772 buffer.edit(edits, None, cx);
3773 })
3774 }
3775 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3776 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3777 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3778 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3779 .zip(new_selection_deltas)
3780 .map(|(selection, delta)| Selection {
3781 id: selection.id,
3782 start: selection.start + delta,
3783 end: selection.end + delta,
3784 reversed: selection.reversed,
3785 goal: SelectionGoal::None,
3786 })
3787 .collect::<Vec<_>>();
3788
3789 let mut i = 0;
3790 for (position, delta, selection_id, pair) in new_autoclose_regions {
3791 let position = position.to_offset(&map.buffer_snapshot) + delta;
3792 let start = map.buffer_snapshot.anchor_before(position);
3793 let end = map.buffer_snapshot.anchor_after(position);
3794 while let Some(existing_state) = this.autoclose_regions.get(i) {
3795 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3796 Ordering::Less => i += 1,
3797 Ordering::Greater => break,
3798 Ordering::Equal => {
3799 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3800 Ordering::Less => i += 1,
3801 Ordering::Equal => break,
3802 Ordering::Greater => break,
3803 }
3804 }
3805 }
3806 }
3807 this.autoclose_regions.insert(
3808 i,
3809 AutocloseRegion {
3810 selection_id,
3811 range: start..end,
3812 pair,
3813 },
3814 );
3815 }
3816
3817 let had_active_inline_completion = this.has_active_inline_completion();
3818 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3819 s.select(new_selections)
3820 });
3821
3822 if !bracket_inserted {
3823 if let Some(on_type_format_task) =
3824 this.trigger_on_type_formatting(text.to_string(), window, cx)
3825 {
3826 on_type_format_task.detach_and_log_err(cx);
3827 }
3828 }
3829
3830 let editor_settings = EditorSettings::get_global(cx);
3831 if bracket_inserted
3832 && (editor_settings.auto_signature_help
3833 || editor_settings.show_signature_help_after_edits)
3834 {
3835 this.show_signature_help(&ShowSignatureHelp, window, cx);
3836 }
3837
3838 let trigger_in_words =
3839 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3840 if this.hard_wrap.is_some() {
3841 let latest: Range<Point> = this.selections.newest(cx).range();
3842 if latest.is_empty()
3843 && this
3844 .buffer()
3845 .read(cx)
3846 .snapshot(cx)
3847 .line_len(MultiBufferRow(latest.start.row))
3848 == latest.start.column
3849 {
3850 this.rewrap_impl(
3851 RewrapOptions {
3852 override_language_settings: true,
3853 preserve_existing_whitespace: true,
3854 },
3855 cx,
3856 )
3857 }
3858 }
3859 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3860 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3861 this.refresh_inline_completion(true, false, window, cx);
3862 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3863 });
3864 }
3865
3866 fn find_possible_emoji_shortcode_at_position(
3867 snapshot: &MultiBufferSnapshot,
3868 position: Point,
3869 ) -> Option<String> {
3870 let mut chars = Vec::new();
3871 let mut found_colon = false;
3872 for char in snapshot.reversed_chars_at(position).take(100) {
3873 // Found a possible emoji shortcode in the middle of the buffer
3874 if found_colon {
3875 if char.is_whitespace() {
3876 chars.reverse();
3877 return Some(chars.iter().collect());
3878 }
3879 // If the previous character is not a whitespace, we are in the middle of a word
3880 // and we only want to complete the shortcode if the word is made up of other emojis
3881 let mut containing_word = String::new();
3882 for ch in snapshot
3883 .reversed_chars_at(position)
3884 .skip(chars.len() + 1)
3885 .take(100)
3886 {
3887 if ch.is_whitespace() {
3888 break;
3889 }
3890 containing_word.push(ch);
3891 }
3892 let containing_word = containing_word.chars().rev().collect::<String>();
3893 if util::word_consists_of_emojis(containing_word.as_str()) {
3894 chars.reverse();
3895 return Some(chars.iter().collect());
3896 }
3897 }
3898
3899 if char.is_whitespace() || !char.is_ascii() {
3900 return None;
3901 }
3902 if char == ':' {
3903 found_colon = true;
3904 } else {
3905 chars.push(char);
3906 }
3907 }
3908 // Found a possible emoji shortcode at the beginning of the buffer
3909 chars.reverse();
3910 Some(chars.iter().collect())
3911 }
3912
3913 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3914 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3915 self.transact(window, cx, |this, window, cx| {
3916 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
3917 let selections = this.selections.all::<usize>(cx);
3918 let multi_buffer = this.buffer.read(cx);
3919 let buffer = multi_buffer.snapshot(cx);
3920 selections
3921 .iter()
3922 .map(|selection| {
3923 let start_point = selection.start.to_point(&buffer);
3924 let mut indent =
3925 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3926 indent.len = cmp::min(indent.len, start_point.column);
3927 let start = selection.start;
3928 let end = selection.end;
3929 let selection_is_empty = start == end;
3930 let language_scope = buffer.language_scope_at(start);
3931 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
3932 &language_scope
3933 {
3934 let insert_extra_newline =
3935 insert_extra_newline_brackets(&buffer, start..end, language)
3936 || insert_extra_newline_tree_sitter(&buffer, start..end);
3937
3938 // Comment extension on newline is allowed only for cursor selections
3939 let comment_delimiter = maybe!({
3940 if !selection_is_empty {
3941 return None;
3942 }
3943
3944 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3945 return None;
3946 }
3947
3948 let delimiters = language.line_comment_prefixes();
3949 let max_len_of_delimiter =
3950 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3951 let (snapshot, range) =
3952 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3953
3954 let mut index_of_first_non_whitespace = 0;
3955 let comment_candidate = snapshot
3956 .chars_for_range(range)
3957 .skip_while(|c| {
3958 let should_skip = c.is_whitespace();
3959 if should_skip {
3960 index_of_first_non_whitespace += 1;
3961 }
3962 should_skip
3963 })
3964 .take(max_len_of_delimiter)
3965 .collect::<String>();
3966 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3967 comment_candidate.starts_with(comment_prefix.as_ref())
3968 })?;
3969 let cursor_is_placed_after_comment_marker =
3970 index_of_first_non_whitespace + comment_prefix.len()
3971 <= start_point.column as usize;
3972 if cursor_is_placed_after_comment_marker {
3973 Some(comment_prefix.clone())
3974 } else {
3975 None
3976 }
3977 });
3978 (comment_delimiter, insert_extra_newline)
3979 } else {
3980 (None, false)
3981 };
3982
3983 let capacity_for_delimiter = comment_delimiter
3984 .as_deref()
3985 .map(str::len)
3986 .unwrap_or_default();
3987 let mut new_text =
3988 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3989 new_text.push('\n');
3990 new_text.extend(indent.chars());
3991 if let Some(delimiter) = &comment_delimiter {
3992 new_text.push_str(delimiter);
3993 }
3994 if insert_extra_newline {
3995 new_text = new_text.repeat(2);
3996 }
3997
3998 let anchor = buffer.anchor_after(end);
3999 let new_selection = selection.map(|_| anchor);
4000 (
4001 (start..end, new_text),
4002 (insert_extra_newline, new_selection),
4003 )
4004 })
4005 .unzip()
4006 };
4007
4008 this.edit_with_autoindent(edits, cx);
4009 let buffer = this.buffer.read(cx).snapshot(cx);
4010 let new_selections = selection_fixup_info
4011 .into_iter()
4012 .map(|(extra_newline_inserted, new_selection)| {
4013 let mut cursor = new_selection.end.to_point(&buffer);
4014 if extra_newline_inserted {
4015 cursor.row -= 1;
4016 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4017 }
4018 new_selection.map(|_| cursor)
4019 })
4020 .collect();
4021
4022 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4023 s.select(new_selections)
4024 });
4025 this.refresh_inline_completion(true, false, window, cx);
4026 });
4027 }
4028
4029 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4030 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4031
4032 let buffer = self.buffer.read(cx);
4033 let snapshot = buffer.snapshot(cx);
4034
4035 let mut edits = Vec::new();
4036 let mut rows = Vec::new();
4037
4038 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4039 let cursor = selection.head();
4040 let row = cursor.row;
4041
4042 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4043
4044 let newline = "\n".to_string();
4045 edits.push((start_of_line..start_of_line, newline));
4046
4047 rows.push(row + rows_inserted as u32);
4048 }
4049
4050 self.transact(window, cx, |editor, window, cx| {
4051 editor.edit(edits, cx);
4052
4053 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4054 let mut index = 0;
4055 s.move_cursors_with(|map, _, _| {
4056 let row = rows[index];
4057 index += 1;
4058
4059 let point = Point::new(row, 0);
4060 let boundary = map.next_line_boundary(point).1;
4061 let clipped = map.clip_point(boundary, Bias::Left);
4062
4063 (clipped, SelectionGoal::None)
4064 });
4065 });
4066
4067 let mut indent_edits = Vec::new();
4068 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4069 for row in rows {
4070 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4071 for (row, indent) in indents {
4072 if indent.len == 0 {
4073 continue;
4074 }
4075
4076 let text = match indent.kind {
4077 IndentKind::Space => " ".repeat(indent.len as usize),
4078 IndentKind::Tab => "\t".repeat(indent.len as usize),
4079 };
4080 let point = Point::new(row.0, 0);
4081 indent_edits.push((point..point, text));
4082 }
4083 }
4084 editor.edit(indent_edits, cx);
4085 });
4086 }
4087
4088 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4089 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4090
4091 let buffer = self.buffer.read(cx);
4092 let snapshot = buffer.snapshot(cx);
4093
4094 let mut edits = Vec::new();
4095 let mut rows = Vec::new();
4096 let mut rows_inserted = 0;
4097
4098 for selection in self.selections.all_adjusted(cx) {
4099 let cursor = selection.head();
4100 let row = cursor.row;
4101
4102 let point = Point::new(row + 1, 0);
4103 let start_of_line = snapshot.clip_point(point, Bias::Left);
4104
4105 let newline = "\n".to_string();
4106 edits.push((start_of_line..start_of_line, newline));
4107
4108 rows_inserted += 1;
4109 rows.push(row + rows_inserted);
4110 }
4111
4112 self.transact(window, cx, |editor, window, cx| {
4113 editor.edit(edits, cx);
4114
4115 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4116 let mut index = 0;
4117 s.move_cursors_with(|map, _, _| {
4118 let row = rows[index];
4119 index += 1;
4120
4121 let point = Point::new(row, 0);
4122 let boundary = map.next_line_boundary(point).1;
4123 let clipped = map.clip_point(boundary, Bias::Left);
4124
4125 (clipped, SelectionGoal::None)
4126 });
4127 });
4128
4129 let mut indent_edits = Vec::new();
4130 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4131 for row in rows {
4132 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4133 for (row, indent) in indents {
4134 if indent.len == 0 {
4135 continue;
4136 }
4137
4138 let text = match indent.kind {
4139 IndentKind::Space => " ".repeat(indent.len as usize),
4140 IndentKind::Tab => "\t".repeat(indent.len as usize),
4141 };
4142 let point = Point::new(row.0, 0);
4143 indent_edits.push((point..point, text));
4144 }
4145 }
4146 editor.edit(indent_edits, cx);
4147 });
4148 }
4149
4150 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4151 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4152 original_indent_columns: Vec::new(),
4153 });
4154 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4155 }
4156
4157 fn insert_with_autoindent_mode(
4158 &mut self,
4159 text: &str,
4160 autoindent_mode: Option<AutoindentMode>,
4161 window: &mut Window,
4162 cx: &mut Context<Self>,
4163 ) {
4164 if self.read_only(cx) {
4165 return;
4166 }
4167
4168 let text: Arc<str> = text.into();
4169 self.transact(window, cx, |this, window, cx| {
4170 let old_selections = this.selections.all_adjusted(cx);
4171 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4172 let anchors = {
4173 let snapshot = buffer.read(cx);
4174 old_selections
4175 .iter()
4176 .map(|s| {
4177 let anchor = snapshot.anchor_after(s.head());
4178 s.map(|_| anchor)
4179 })
4180 .collect::<Vec<_>>()
4181 };
4182 buffer.edit(
4183 old_selections
4184 .iter()
4185 .map(|s| (s.start..s.end, text.clone())),
4186 autoindent_mode,
4187 cx,
4188 );
4189 anchors
4190 });
4191
4192 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4193 s.select_anchors(selection_anchors);
4194 });
4195
4196 cx.notify();
4197 });
4198 }
4199
4200 fn trigger_completion_on_input(
4201 &mut self,
4202 text: &str,
4203 trigger_in_words: bool,
4204 window: &mut Window,
4205 cx: &mut Context<Self>,
4206 ) {
4207 let ignore_completion_provider = self
4208 .context_menu
4209 .borrow()
4210 .as_ref()
4211 .map(|menu| match menu {
4212 CodeContextMenu::Completions(completions_menu) => {
4213 completions_menu.ignore_completion_provider
4214 }
4215 CodeContextMenu::CodeActions(_) => false,
4216 })
4217 .unwrap_or(false);
4218
4219 if ignore_completion_provider {
4220 self.show_word_completions(&ShowWordCompletions, window, cx);
4221 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
4222 self.show_completions(
4223 &ShowCompletions {
4224 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4225 },
4226 window,
4227 cx,
4228 );
4229 } else {
4230 self.hide_context_menu(window, cx);
4231 }
4232 }
4233
4234 fn is_completion_trigger(
4235 &self,
4236 text: &str,
4237 trigger_in_words: bool,
4238 cx: &mut Context<Self>,
4239 ) -> bool {
4240 let position = self.selections.newest_anchor().head();
4241 let multibuffer = self.buffer.read(cx);
4242 let Some(buffer) = position
4243 .buffer_id
4244 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4245 else {
4246 return false;
4247 };
4248
4249 if let Some(completion_provider) = &self.completion_provider {
4250 completion_provider.is_completion_trigger(
4251 &buffer,
4252 position.text_anchor,
4253 text,
4254 trigger_in_words,
4255 cx,
4256 )
4257 } else {
4258 false
4259 }
4260 }
4261
4262 /// If any empty selections is touching the start of its innermost containing autoclose
4263 /// region, expand it to select the brackets.
4264 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4265 let selections = self.selections.all::<usize>(cx);
4266 let buffer = self.buffer.read(cx).read(cx);
4267 let new_selections = self
4268 .selections_with_autoclose_regions(selections, &buffer)
4269 .map(|(mut selection, region)| {
4270 if !selection.is_empty() {
4271 return selection;
4272 }
4273
4274 if let Some(region) = region {
4275 let mut range = region.range.to_offset(&buffer);
4276 if selection.start == range.start && range.start >= region.pair.start.len() {
4277 range.start -= region.pair.start.len();
4278 if buffer.contains_str_at(range.start, ®ion.pair.start)
4279 && buffer.contains_str_at(range.end, ®ion.pair.end)
4280 {
4281 range.end += region.pair.end.len();
4282 selection.start = range.start;
4283 selection.end = range.end;
4284
4285 return selection;
4286 }
4287 }
4288 }
4289
4290 let always_treat_brackets_as_autoclosed = buffer
4291 .language_settings_at(selection.start, cx)
4292 .always_treat_brackets_as_autoclosed;
4293
4294 if !always_treat_brackets_as_autoclosed {
4295 return selection;
4296 }
4297
4298 if let Some(scope) = buffer.language_scope_at(selection.start) {
4299 for (pair, enabled) in scope.brackets() {
4300 if !enabled || !pair.close {
4301 continue;
4302 }
4303
4304 if buffer.contains_str_at(selection.start, &pair.end) {
4305 let pair_start_len = pair.start.len();
4306 if buffer.contains_str_at(
4307 selection.start.saturating_sub(pair_start_len),
4308 &pair.start,
4309 ) {
4310 selection.start -= pair_start_len;
4311 selection.end += pair.end.len();
4312
4313 return selection;
4314 }
4315 }
4316 }
4317 }
4318
4319 selection
4320 })
4321 .collect();
4322
4323 drop(buffer);
4324 self.change_selections(None, window, cx, |selections| {
4325 selections.select(new_selections)
4326 });
4327 }
4328
4329 /// Iterate the given selections, and for each one, find the smallest surrounding
4330 /// autoclose region. This uses the ordering of the selections and the autoclose
4331 /// regions to avoid repeated comparisons.
4332 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4333 &'a self,
4334 selections: impl IntoIterator<Item = Selection<D>>,
4335 buffer: &'a MultiBufferSnapshot,
4336 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4337 let mut i = 0;
4338 let mut regions = self.autoclose_regions.as_slice();
4339 selections.into_iter().map(move |selection| {
4340 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4341
4342 let mut enclosing = None;
4343 while let Some(pair_state) = regions.get(i) {
4344 if pair_state.range.end.to_offset(buffer) < range.start {
4345 regions = ®ions[i + 1..];
4346 i = 0;
4347 } else if pair_state.range.start.to_offset(buffer) > range.end {
4348 break;
4349 } else {
4350 if pair_state.selection_id == selection.id {
4351 enclosing = Some(pair_state);
4352 }
4353 i += 1;
4354 }
4355 }
4356
4357 (selection, enclosing)
4358 })
4359 }
4360
4361 /// Remove any autoclose regions that no longer contain their selection.
4362 fn invalidate_autoclose_regions(
4363 &mut self,
4364 mut selections: &[Selection<Anchor>],
4365 buffer: &MultiBufferSnapshot,
4366 ) {
4367 self.autoclose_regions.retain(|state| {
4368 let mut i = 0;
4369 while let Some(selection) = selections.get(i) {
4370 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4371 selections = &selections[1..];
4372 continue;
4373 }
4374 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4375 break;
4376 }
4377 if selection.id == state.selection_id {
4378 return true;
4379 } else {
4380 i += 1;
4381 }
4382 }
4383 false
4384 });
4385 }
4386
4387 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4388 let offset = position.to_offset(buffer);
4389 let (word_range, kind) = buffer.surrounding_word(offset, true);
4390 if offset > word_range.start && kind == Some(CharKind::Word) {
4391 Some(
4392 buffer
4393 .text_for_range(word_range.start..offset)
4394 .collect::<String>(),
4395 )
4396 } else {
4397 None
4398 }
4399 }
4400
4401 pub fn toggle_inline_values(
4402 &mut self,
4403 _: &ToggleInlineValues,
4404 _: &mut Window,
4405 cx: &mut Context<Self>,
4406 ) {
4407 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4408
4409 self.refresh_inline_values(cx);
4410 }
4411
4412 pub fn toggle_inlay_hints(
4413 &mut self,
4414 _: &ToggleInlayHints,
4415 _: &mut Window,
4416 cx: &mut Context<Self>,
4417 ) {
4418 self.refresh_inlay_hints(
4419 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4420 cx,
4421 );
4422 }
4423
4424 pub fn inlay_hints_enabled(&self) -> bool {
4425 self.inlay_hint_cache.enabled
4426 }
4427
4428 pub fn inline_values_enabled(&self) -> bool {
4429 self.inline_value_cache.enabled
4430 }
4431
4432 #[cfg(any(test, feature = "test-support"))]
4433 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4434 self.display_map
4435 .read(cx)
4436 .current_inlays()
4437 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4438 .cloned()
4439 .collect()
4440 }
4441
4442 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4443 if self.semantics_provider.is_none() || !self.mode.is_full() {
4444 return;
4445 }
4446
4447 let reason_description = reason.description();
4448 let ignore_debounce = matches!(
4449 reason,
4450 InlayHintRefreshReason::SettingsChange(_)
4451 | InlayHintRefreshReason::Toggle(_)
4452 | InlayHintRefreshReason::ExcerptsRemoved(_)
4453 | InlayHintRefreshReason::ModifiersChanged(_)
4454 );
4455 let (invalidate_cache, required_languages) = match reason {
4456 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4457 match self.inlay_hint_cache.modifiers_override(enabled) {
4458 Some(enabled) => {
4459 if enabled {
4460 (InvalidationStrategy::RefreshRequested, None)
4461 } else {
4462 self.splice_inlays(
4463 &self
4464 .visible_inlay_hints(cx)
4465 .iter()
4466 .map(|inlay| inlay.id)
4467 .collect::<Vec<InlayId>>(),
4468 Vec::new(),
4469 cx,
4470 );
4471 return;
4472 }
4473 }
4474 None => return,
4475 }
4476 }
4477 InlayHintRefreshReason::Toggle(enabled) => {
4478 if self.inlay_hint_cache.toggle(enabled) {
4479 if enabled {
4480 (InvalidationStrategy::RefreshRequested, None)
4481 } else {
4482 self.splice_inlays(
4483 &self
4484 .visible_inlay_hints(cx)
4485 .iter()
4486 .map(|inlay| inlay.id)
4487 .collect::<Vec<InlayId>>(),
4488 Vec::new(),
4489 cx,
4490 );
4491 return;
4492 }
4493 } else {
4494 return;
4495 }
4496 }
4497 InlayHintRefreshReason::SettingsChange(new_settings) => {
4498 match self.inlay_hint_cache.update_settings(
4499 &self.buffer,
4500 new_settings,
4501 self.visible_inlay_hints(cx),
4502 cx,
4503 ) {
4504 ControlFlow::Break(Some(InlaySplice {
4505 to_remove,
4506 to_insert,
4507 })) => {
4508 self.splice_inlays(&to_remove, to_insert, cx);
4509 return;
4510 }
4511 ControlFlow::Break(None) => return,
4512 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4513 }
4514 }
4515 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4516 if let Some(InlaySplice {
4517 to_remove,
4518 to_insert,
4519 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4520 {
4521 self.splice_inlays(&to_remove, to_insert, cx);
4522 }
4523 self.display_map.update(cx, |display_map, _| {
4524 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4525 });
4526 return;
4527 }
4528 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4529 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4530 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4531 }
4532 InlayHintRefreshReason::RefreshRequested => {
4533 (InvalidationStrategy::RefreshRequested, None)
4534 }
4535 };
4536
4537 if let Some(InlaySplice {
4538 to_remove,
4539 to_insert,
4540 }) = self.inlay_hint_cache.spawn_hint_refresh(
4541 reason_description,
4542 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4543 invalidate_cache,
4544 ignore_debounce,
4545 cx,
4546 ) {
4547 self.splice_inlays(&to_remove, to_insert, cx);
4548 }
4549 }
4550
4551 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4552 self.display_map
4553 .read(cx)
4554 .current_inlays()
4555 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4556 .cloned()
4557 .collect()
4558 }
4559
4560 pub fn excerpts_for_inlay_hints_query(
4561 &self,
4562 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4563 cx: &mut Context<Editor>,
4564 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4565 let Some(project) = self.project.as_ref() else {
4566 return HashMap::default();
4567 };
4568 let project = project.read(cx);
4569 let multi_buffer = self.buffer().read(cx);
4570 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4571 let multi_buffer_visible_start = self
4572 .scroll_manager
4573 .anchor()
4574 .anchor
4575 .to_point(&multi_buffer_snapshot);
4576 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4577 multi_buffer_visible_start
4578 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4579 Bias::Left,
4580 );
4581 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4582 multi_buffer_snapshot
4583 .range_to_buffer_ranges(multi_buffer_visible_range)
4584 .into_iter()
4585 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4586 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4587 let buffer_file = project::File::from_dyn(buffer.file())?;
4588 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4589 let worktree_entry = buffer_worktree
4590 .read(cx)
4591 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4592 if worktree_entry.is_ignored {
4593 return None;
4594 }
4595
4596 let language = buffer.language()?;
4597 if let Some(restrict_to_languages) = restrict_to_languages {
4598 if !restrict_to_languages.contains(language) {
4599 return None;
4600 }
4601 }
4602 Some((
4603 excerpt_id,
4604 (
4605 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4606 buffer.version().clone(),
4607 excerpt_visible_range,
4608 ),
4609 ))
4610 })
4611 .collect()
4612 }
4613
4614 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4615 TextLayoutDetails {
4616 text_system: window.text_system().clone(),
4617 editor_style: self.style.clone().unwrap(),
4618 rem_size: window.rem_size(),
4619 scroll_anchor: self.scroll_manager.anchor(),
4620 visible_rows: self.visible_line_count(),
4621 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4622 }
4623 }
4624
4625 pub fn splice_inlays(
4626 &self,
4627 to_remove: &[InlayId],
4628 to_insert: Vec<Inlay>,
4629 cx: &mut Context<Self>,
4630 ) {
4631 self.display_map.update(cx, |display_map, cx| {
4632 display_map.splice_inlays(to_remove, to_insert, cx)
4633 });
4634 cx.notify();
4635 }
4636
4637 fn trigger_on_type_formatting(
4638 &self,
4639 input: String,
4640 window: &mut Window,
4641 cx: &mut Context<Self>,
4642 ) -> Option<Task<Result<()>>> {
4643 if input.len() != 1 {
4644 return None;
4645 }
4646
4647 let project = self.project.as_ref()?;
4648 let position = self.selections.newest_anchor().head();
4649 let (buffer, buffer_position) = self
4650 .buffer
4651 .read(cx)
4652 .text_anchor_for_position(position, cx)?;
4653
4654 let settings = language_settings::language_settings(
4655 buffer
4656 .read(cx)
4657 .language_at(buffer_position)
4658 .map(|l| l.name()),
4659 buffer.read(cx).file(),
4660 cx,
4661 );
4662 if !settings.use_on_type_format {
4663 return None;
4664 }
4665
4666 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4667 // hence we do LSP request & edit on host side only — add formats to host's history.
4668 let push_to_lsp_host_history = true;
4669 // If this is not the host, append its history with new edits.
4670 let push_to_client_history = project.read(cx).is_via_collab();
4671
4672 let on_type_formatting = project.update(cx, |project, cx| {
4673 project.on_type_format(
4674 buffer.clone(),
4675 buffer_position,
4676 input,
4677 push_to_lsp_host_history,
4678 cx,
4679 )
4680 });
4681 Some(cx.spawn_in(window, async move |editor, cx| {
4682 if let Some(transaction) = on_type_formatting.await? {
4683 if push_to_client_history {
4684 buffer
4685 .update(cx, |buffer, _| {
4686 buffer.push_transaction(transaction, Instant::now());
4687 buffer.finalize_last_transaction();
4688 })
4689 .ok();
4690 }
4691 editor.update(cx, |editor, cx| {
4692 editor.refresh_document_highlights(cx);
4693 })?;
4694 }
4695 Ok(())
4696 }))
4697 }
4698
4699 pub fn show_word_completions(
4700 &mut self,
4701 _: &ShowWordCompletions,
4702 window: &mut Window,
4703 cx: &mut Context<Self>,
4704 ) {
4705 self.open_completions_menu(true, None, window, cx);
4706 }
4707
4708 pub fn show_completions(
4709 &mut self,
4710 options: &ShowCompletions,
4711 window: &mut Window,
4712 cx: &mut Context<Self>,
4713 ) {
4714 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4715 }
4716
4717 fn open_completions_menu(
4718 &mut self,
4719 ignore_completion_provider: bool,
4720 trigger: Option<&str>,
4721 window: &mut Window,
4722 cx: &mut Context<Self>,
4723 ) {
4724 if self.pending_rename.is_some() {
4725 return;
4726 }
4727 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4728 return;
4729 }
4730
4731 let position = self.selections.newest_anchor().head();
4732 if position.diff_base_anchor.is_some() {
4733 return;
4734 }
4735 let (buffer, buffer_position) =
4736 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4737 output
4738 } else {
4739 return;
4740 };
4741 let buffer_snapshot = buffer.read(cx).snapshot();
4742 let show_completion_documentation = buffer_snapshot
4743 .settings_at(buffer_position, cx)
4744 .show_completion_documentation;
4745
4746 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4747
4748 let trigger_kind = match trigger {
4749 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4750 CompletionTriggerKind::TRIGGER_CHARACTER
4751 }
4752 _ => CompletionTriggerKind::INVOKED,
4753 };
4754 let completion_context = CompletionContext {
4755 trigger_character: trigger.and_then(|trigger| {
4756 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4757 Some(String::from(trigger))
4758 } else {
4759 None
4760 }
4761 }),
4762 trigger_kind,
4763 };
4764
4765 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4766 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4767 let word_to_exclude = buffer_snapshot
4768 .text_for_range(old_range.clone())
4769 .collect::<String>();
4770 (
4771 buffer_snapshot.anchor_before(old_range.start)
4772 ..buffer_snapshot.anchor_after(old_range.end),
4773 Some(word_to_exclude),
4774 )
4775 } else {
4776 (buffer_position..buffer_position, None)
4777 };
4778
4779 let completion_settings = language_settings(
4780 buffer_snapshot
4781 .language_at(buffer_position)
4782 .map(|language| language.name()),
4783 buffer_snapshot.file(),
4784 cx,
4785 )
4786 .completions;
4787
4788 // The document can be large, so stay in reasonable bounds when searching for words,
4789 // otherwise completion pop-up might be slow to appear.
4790 const WORD_LOOKUP_ROWS: u32 = 5_000;
4791 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4792 let min_word_search = buffer_snapshot.clip_point(
4793 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4794 Bias::Left,
4795 );
4796 let max_word_search = buffer_snapshot.clip_point(
4797 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4798 Bias::Right,
4799 );
4800 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4801 ..buffer_snapshot.point_to_offset(max_word_search);
4802
4803 let provider = self
4804 .completion_provider
4805 .as_ref()
4806 .filter(|_| !ignore_completion_provider);
4807 let skip_digits = query
4808 .as_ref()
4809 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4810
4811 let (mut words, provided_completions) = match provider {
4812 Some(provider) => {
4813 let completions = provider.completions(
4814 position.excerpt_id,
4815 &buffer,
4816 buffer_position,
4817 completion_context,
4818 window,
4819 cx,
4820 );
4821
4822 let words = match completion_settings.words {
4823 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4824 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4825 .background_spawn(async move {
4826 buffer_snapshot.words_in_range(WordsQuery {
4827 fuzzy_contents: None,
4828 range: word_search_range,
4829 skip_digits,
4830 })
4831 }),
4832 };
4833
4834 (words, completions)
4835 }
4836 None => (
4837 cx.background_spawn(async move {
4838 buffer_snapshot.words_in_range(WordsQuery {
4839 fuzzy_contents: None,
4840 range: word_search_range,
4841 skip_digits,
4842 })
4843 }),
4844 Task::ready(Ok(None)),
4845 ),
4846 };
4847
4848 let sort_completions = provider
4849 .as_ref()
4850 .map_or(false, |provider| provider.sort_completions());
4851
4852 let filter_completions = provider
4853 .as_ref()
4854 .map_or(true, |provider| provider.filter_completions());
4855
4856 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
4857
4858 let id = post_inc(&mut self.next_completion_id);
4859 let task = cx.spawn_in(window, async move |editor, cx| {
4860 async move {
4861 editor.update(cx, |this, _| {
4862 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4863 })?;
4864
4865 let mut completions = Vec::new();
4866 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
4867 completions.extend(provided_completions);
4868 if completion_settings.words == WordsCompletionMode::Fallback {
4869 words = Task::ready(BTreeMap::default());
4870 }
4871 }
4872
4873 let mut words = words.await;
4874 if let Some(word_to_exclude) = &word_to_exclude {
4875 words.remove(word_to_exclude);
4876 }
4877 for lsp_completion in &completions {
4878 words.remove(&lsp_completion.new_text);
4879 }
4880 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
4881 replace_range: old_range.clone(),
4882 new_text: word.clone(),
4883 label: CodeLabel::plain(word, None),
4884 icon_path: None,
4885 documentation: None,
4886 source: CompletionSource::BufferWord {
4887 word_range,
4888 resolved: false,
4889 },
4890 insert_text_mode: Some(InsertTextMode::AS_IS),
4891 confirm: None,
4892 }));
4893
4894 let menu = if completions.is_empty() {
4895 None
4896 } else {
4897 let mut menu = CompletionsMenu::new(
4898 id,
4899 sort_completions,
4900 show_completion_documentation,
4901 ignore_completion_provider,
4902 position,
4903 buffer.clone(),
4904 completions.into(),
4905 snippet_sort_order,
4906 );
4907
4908 menu.filter(
4909 if filter_completions {
4910 query.as_deref()
4911 } else {
4912 None
4913 },
4914 cx.background_executor().clone(),
4915 )
4916 .await;
4917
4918 menu.visible().then_some(menu)
4919 };
4920
4921 editor.update_in(cx, |editor, window, cx| {
4922 match editor.context_menu.borrow().as_ref() {
4923 None => {}
4924 Some(CodeContextMenu::Completions(prev_menu)) => {
4925 if prev_menu.id > id {
4926 return;
4927 }
4928 }
4929 _ => return,
4930 }
4931
4932 if editor.focus_handle.is_focused(window) && menu.is_some() {
4933 let mut menu = menu.unwrap();
4934 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
4935
4936 *editor.context_menu.borrow_mut() =
4937 Some(CodeContextMenu::Completions(menu));
4938
4939 if editor.show_edit_predictions_in_menu() {
4940 editor.update_visible_inline_completion(window, cx);
4941 } else {
4942 editor.discard_inline_completion(false, cx);
4943 }
4944
4945 cx.notify();
4946 } else if editor.completion_tasks.len() <= 1 {
4947 // If there are no more completion tasks and the last menu was
4948 // empty, we should hide it.
4949 let was_hidden = editor.hide_context_menu(window, cx).is_none();
4950 // If it was already hidden and we don't show inline
4951 // completions in the menu, we should also show the
4952 // inline-completion when available.
4953 if was_hidden && editor.show_edit_predictions_in_menu() {
4954 editor.update_visible_inline_completion(window, cx);
4955 }
4956 }
4957 })?;
4958
4959 anyhow::Ok(())
4960 }
4961 .log_err()
4962 .await
4963 });
4964
4965 self.completion_tasks.push((id, task));
4966 }
4967
4968 #[cfg(feature = "test-support")]
4969 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
4970 let menu = self.context_menu.borrow();
4971 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
4972 let completions = menu.completions.borrow();
4973 Some(completions.to_vec())
4974 } else {
4975 None
4976 }
4977 }
4978
4979 pub fn confirm_completion(
4980 &mut self,
4981 action: &ConfirmCompletion,
4982 window: &mut Window,
4983 cx: &mut Context<Self>,
4984 ) -> Option<Task<Result<()>>> {
4985 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4986 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
4987 }
4988
4989 pub fn confirm_completion_insert(
4990 &mut self,
4991 _: &ConfirmCompletionInsert,
4992 window: &mut Window,
4993 cx: &mut Context<Self>,
4994 ) -> Option<Task<Result<()>>> {
4995 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4996 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
4997 }
4998
4999 pub fn confirm_completion_replace(
5000 &mut self,
5001 _: &ConfirmCompletionReplace,
5002 window: &mut Window,
5003 cx: &mut Context<Self>,
5004 ) -> Option<Task<Result<()>>> {
5005 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5006 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5007 }
5008
5009 pub fn compose_completion(
5010 &mut self,
5011 action: &ComposeCompletion,
5012 window: &mut Window,
5013 cx: &mut Context<Self>,
5014 ) -> Option<Task<Result<()>>> {
5015 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5016 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5017 }
5018
5019 fn do_completion(
5020 &mut self,
5021 item_ix: Option<usize>,
5022 intent: CompletionIntent,
5023 window: &mut Window,
5024 cx: &mut Context<Editor>,
5025 ) -> Option<Task<Result<()>>> {
5026 use language::ToOffset as _;
5027
5028 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5029 else {
5030 return None;
5031 };
5032
5033 let candidate_id = {
5034 let entries = completions_menu.entries.borrow();
5035 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5036 if self.show_edit_predictions_in_menu() {
5037 self.discard_inline_completion(true, cx);
5038 }
5039 mat.candidate_id
5040 };
5041
5042 let buffer_handle = completions_menu.buffer;
5043 let completion = completions_menu
5044 .completions
5045 .borrow()
5046 .get(candidate_id)?
5047 .clone();
5048 cx.stop_propagation();
5049
5050 let snapshot = self.buffer.read(cx).snapshot(cx);
5051 let newest_anchor = self.selections.newest_anchor();
5052
5053 let snippet;
5054 let new_text;
5055 if completion.is_snippet() {
5056 let mut snippet_source = completion.new_text.clone();
5057 if let Some(scope) = snapshot.language_scope_at(newest_anchor.head()) {
5058 if scope.prefers_label_for_snippet_in_completion() {
5059 if let Some(label) = completion.label() {
5060 if matches!(
5061 completion.kind(),
5062 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
5063 ) {
5064 snippet_source = label;
5065 }
5066 }
5067 }
5068 }
5069 snippet = Some(Snippet::parse(&snippet_source).log_err()?);
5070 new_text = snippet.as_ref().unwrap().text.clone();
5071 } else {
5072 snippet = None;
5073 new_text = completion.new_text.clone();
5074 };
5075
5076 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
5077 let buffer = buffer_handle.read(cx);
5078 let replace_range_multibuffer = {
5079 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5080 let multibuffer_anchor = snapshot
5081 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5082 .unwrap()
5083 ..snapshot
5084 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5085 .unwrap();
5086 multibuffer_anchor.start.to_offset(&snapshot)
5087 ..multibuffer_anchor.end.to_offset(&snapshot)
5088 };
5089 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5090 return None;
5091 }
5092
5093 let old_text = buffer
5094 .text_for_range(replace_range.clone())
5095 .collect::<String>();
5096 let lookbehind = newest_anchor
5097 .start
5098 .text_anchor
5099 .to_offset(buffer)
5100 .saturating_sub(replace_range.start);
5101 let lookahead = replace_range
5102 .end
5103 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5104 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5105 let suffix = &old_text[lookbehind.min(old_text.len())..];
5106
5107 let selections = self.selections.all::<usize>(cx);
5108 let mut ranges = Vec::new();
5109 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5110
5111 for selection in &selections {
5112 let range = if selection.id == newest_anchor.id {
5113 replace_range_multibuffer.clone()
5114 } else {
5115 let mut range = selection.range();
5116
5117 // if prefix is present, don't duplicate it
5118 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5119 range.start = range.start.saturating_sub(lookbehind);
5120
5121 // if suffix is also present, mimic the newest cursor and replace it
5122 if selection.id != newest_anchor.id
5123 && snapshot.contains_str_at(range.end, suffix)
5124 {
5125 range.end += lookahead;
5126 }
5127 }
5128 range
5129 };
5130
5131 ranges.push(range.clone());
5132
5133 if !self.linked_edit_ranges.is_empty() {
5134 let start_anchor = snapshot.anchor_before(range.start);
5135 let end_anchor = snapshot.anchor_after(range.end);
5136 if let Some(ranges) = self
5137 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5138 {
5139 for (buffer, edits) in ranges {
5140 linked_edits
5141 .entry(buffer.clone())
5142 .or_default()
5143 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5144 }
5145 }
5146 }
5147 }
5148
5149 cx.emit(EditorEvent::InputHandled {
5150 utf16_range_to_replace: None,
5151 text: new_text.clone().into(),
5152 });
5153
5154 self.transact(window, cx, |this, window, cx| {
5155 if let Some(mut snippet) = snippet {
5156 snippet.text = new_text.to_string();
5157 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5158 } else {
5159 this.buffer.update(cx, |buffer, cx| {
5160 let auto_indent = match completion.insert_text_mode {
5161 Some(InsertTextMode::AS_IS) => None,
5162 _ => this.autoindent_mode.clone(),
5163 };
5164 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5165 buffer.edit(edits, auto_indent, cx);
5166 });
5167 }
5168 for (buffer, edits) in linked_edits {
5169 buffer.update(cx, |buffer, cx| {
5170 let snapshot = buffer.snapshot();
5171 let edits = edits
5172 .into_iter()
5173 .map(|(range, text)| {
5174 use text::ToPoint as TP;
5175 let end_point = TP::to_point(&range.end, &snapshot);
5176 let start_point = TP::to_point(&range.start, &snapshot);
5177 (start_point..end_point, text)
5178 })
5179 .sorted_by_key(|(range, _)| range.start);
5180 buffer.edit(edits, None, cx);
5181 })
5182 }
5183
5184 this.refresh_inline_completion(true, false, window, cx);
5185 });
5186
5187 let show_new_completions_on_confirm = completion
5188 .confirm
5189 .as_ref()
5190 .map_or(false, |confirm| confirm(intent, window, cx));
5191 if show_new_completions_on_confirm {
5192 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5193 }
5194
5195 let provider = self.completion_provider.as_ref()?;
5196 drop(completion);
5197 let apply_edits = provider.apply_additional_edits_for_completion(
5198 buffer_handle,
5199 completions_menu.completions.clone(),
5200 candidate_id,
5201 true,
5202 cx,
5203 );
5204
5205 let editor_settings = EditorSettings::get_global(cx);
5206 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5207 // After the code completion is finished, users often want to know what signatures are needed.
5208 // so we should automatically call signature_help
5209 self.show_signature_help(&ShowSignatureHelp, window, cx);
5210 }
5211
5212 Some(cx.foreground_executor().spawn(async move {
5213 apply_edits.await?;
5214 Ok(())
5215 }))
5216 }
5217
5218 pub fn toggle_code_actions(
5219 &mut self,
5220 action: &ToggleCodeActions,
5221 window: &mut Window,
5222 cx: &mut Context<Self>,
5223 ) {
5224 let quick_launch = action.quick_launch;
5225 let mut context_menu = self.context_menu.borrow_mut();
5226 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5227 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
5228 // Toggle if we're selecting the same one
5229 *context_menu = None;
5230 cx.notify();
5231 return;
5232 } else {
5233 // Otherwise, clear it and start a new one
5234 *context_menu = None;
5235 cx.notify();
5236 }
5237 }
5238 drop(context_menu);
5239 let snapshot = self.snapshot(window, cx);
5240 let deployed_from_indicator = action.deployed_from_indicator;
5241 let mut task = self.code_actions_task.take();
5242 let action = action.clone();
5243 cx.spawn_in(window, async move |editor, cx| {
5244 while let Some(prev_task) = task {
5245 prev_task.await.log_err();
5246 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5247 }
5248
5249 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5250 if editor.focus_handle.is_focused(window) {
5251 let multibuffer_point = action
5252 .deployed_from_indicator
5253 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
5254 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
5255 let (buffer, buffer_row) = snapshot
5256 .buffer_snapshot
5257 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5258 .and_then(|(buffer_snapshot, range)| {
5259 editor
5260 .buffer
5261 .read(cx)
5262 .buffer(buffer_snapshot.remote_id())
5263 .map(|buffer| (buffer, range.start.row))
5264 })?;
5265 let (_, code_actions) = editor
5266 .available_code_actions
5267 .clone()
5268 .and_then(|(location, code_actions)| {
5269 let snapshot = location.buffer.read(cx).snapshot();
5270 let point_range = location.range.to_point(&snapshot);
5271 let point_range = point_range.start.row..=point_range.end.row;
5272 if point_range.contains(&buffer_row) {
5273 Some((location, code_actions))
5274 } else {
5275 None
5276 }
5277 })
5278 .unzip();
5279 let buffer_id = buffer.read(cx).remote_id();
5280 let tasks = editor
5281 .tasks
5282 .get(&(buffer_id, buffer_row))
5283 .map(|t| Arc::new(t.to_owned()));
5284 if tasks.is_none() && code_actions.is_none() {
5285 return None;
5286 }
5287
5288 editor.completion_tasks.clear();
5289 editor.discard_inline_completion(false, cx);
5290 let task_context =
5291 tasks
5292 .as_ref()
5293 .zip(editor.project.clone())
5294 .map(|(tasks, project)| {
5295 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5296 });
5297
5298 Some(cx.spawn_in(window, async move |editor, cx| {
5299 let task_context = match task_context {
5300 Some(task_context) => task_context.await,
5301 None => None,
5302 };
5303 let resolved_tasks =
5304 tasks
5305 .zip(task_context.clone())
5306 .map(|(tasks, task_context)| ResolvedTasks {
5307 templates: tasks.resolve(&task_context).collect(),
5308 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5309 multibuffer_point.row,
5310 tasks.column,
5311 )),
5312 });
5313 let debug_scenarios = editor.update(cx, |editor, cx| {
5314 if cx.has_flag::<DebuggerFeatureFlag>() {
5315 maybe!({
5316 let project = editor.project.as_ref()?;
5317 let dap_store = project.read(cx).dap_store();
5318 let mut scenarios = vec![];
5319 let resolved_tasks = resolved_tasks.as_ref()?;
5320 let buffer = buffer.read(cx);
5321 let language = buffer.language()?;
5322 let file = buffer.file();
5323 let debug_adapter =
5324 language_settings(language.name().into(), file, cx)
5325 .debuggers
5326 .first()
5327 .map(SharedString::from)
5328 .or_else(|| {
5329 language
5330 .config()
5331 .debuggers
5332 .first()
5333 .map(SharedString::from)
5334 })?;
5335
5336 dap_store.update(cx, |dap_store, cx| {
5337 for (_, task) in &resolved_tasks.templates {
5338 if let Some(scenario) = dap_store
5339 .debug_scenario_for_build_task(
5340 task.original_task().clone(),
5341 debug_adapter.clone().into(),
5342 task.display_label().to_owned().into(),
5343 cx,
5344 )
5345 {
5346 scenarios.push(scenario);
5347 }
5348 }
5349 });
5350 Some(scenarios)
5351 })
5352 .unwrap_or_default()
5353 } else {
5354 vec![]
5355 }
5356 })?;
5357 let spawn_straight_away = quick_launch
5358 && resolved_tasks
5359 .as_ref()
5360 .map_or(false, |tasks| tasks.templates.len() == 1)
5361 && code_actions
5362 .as_ref()
5363 .map_or(true, |actions| actions.is_empty())
5364 && debug_scenarios.is_empty();
5365 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5366 *editor.context_menu.borrow_mut() =
5367 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5368 buffer,
5369 actions: CodeActionContents::new(
5370 resolved_tasks,
5371 code_actions,
5372 debug_scenarios,
5373 task_context.unwrap_or_default(),
5374 ),
5375 selected_item: Default::default(),
5376 scroll_handle: UniformListScrollHandle::default(),
5377 deployed_from_indicator,
5378 }));
5379 if spawn_straight_away {
5380 if let Some(task) = editor.confirm_code_action(
5381 &ConfirmCodeAction { item_ix: Some(0) },
5382 window,
5383 cx,
5384 ) {
5385 cx.notify();
5386 return task;
5387 }
5388 }
5389 cx.notify();
5390 Task::ready(Ok(()))
5391 }) {
5392 task.await
5393 } else {
5394 Ok(())
5395 }
5396 }))
5397 } else {
5398 Some(Task::ready(Ok(())))
5399 }
5400 })?;
5401 if let Some(task) = spawned_test_task {
5402 task.await?;
5403 }
5404
5405 Ok::<_, anyhow::Error>(())
5406 })
5407 .detach_and_log_err(cx);
5408 }
5409
5410 pub fn confirm_code_action(
5411 &mut self,
5412 action: &ConfirmCodeAction,
5413 window: &mut Window,
5414 cx: &mut Context<Self>,
5415 ) -> Option<Task<Result<()>>> {
5416 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5417
5418 let actions_menu =
5419 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5420 menu
5421 } else {
5422 return None;
5423 };
5424
5425 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5426 let action = actions_menu.actions.get(action_ix)?;
5427 let title = action.label();
5428 let buffer = actions_menu.buffer;
5429 let workspace = self.workspace()?;
5430
5431 match action {
5432 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5433 workspace.update(cx, |workspace, cx| {
5434 workspace.schedule_resolved_task(
5435 task_source_kind,
5436 resolved_task,
5437 false,
5438 window,
5439 cx,
5440 );
5441
5442 Some(Task::ready(Ok(())))
5443 })
5444 }
5445 CodeActionsItem::CodeAction {
5446 excerpt_id,
5447 action,
5448 provider,
5449 } => {
5450 let apply_code_action =
5451 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5452 let workspace = workspace.downgrade();
5453 Some(cx.spawn_in(window, async move |editor, cx| {
5454 let project_transaction = apply_code_action.await?;
5455 Self::open_project_transaction(
5456 &editor,
5457 workspace,
5458 project_transaction,
5459 title,
5460 cx,
5461 )
5462 .await
5463 }))
5464 }
5465 CodeActionsItem::DebugScenario(scenario) => {
5466 let context = actions_menu.actions.context.clone();
5467
5468 workspace.update(cx, |workspace, cx| {
5469 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5470 });
5471 Some(Task::ready(Ok(())))
5472 }
5473 }
5474 }
5475
5476 pub async fn open_project_transaction(
5477 this: &WeakEntity<Editor>,
5478 workspace: WeakEntity<Workspace>,
5479 transaction: ProjectTransaction,
5480 title: String,
5481 cx: &mut AsyncWindowContext,
5482 ) -> Result<()> {
5483 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5484 cx.update(|_, cx| {
5485 entries.sort_unstable_by_key(|(buffer, _)| {
5486 buffer.read(cx).file().map(|f| f.path().clone())
5487 });
5488 })?;
5489
5490 // If the project transaction's edits are all contained within this editor, then
5491 // avoid opening a new editor to display them.
5492
5493 if let Some((buffer, transaction)) = entries.first() {
5494 if entries.len() == 1 {
5495 let excerpt = this.update(cx, |editor, cx| {
5496 editor
5497 .buffer()
5498 .read(cx)
5499 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5500 })?;
5501 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5502 if excerpted_buffer == *buffer {
5503 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5504 let excerpt_range = excerpt_range.to_offset(buffer);
5505 buffer
5506 .edited_ranges_for_transaction::<usize>(transaction)
5507 .all(|range| {
5508 excerpt_range.start <= range.start
5509 && excerpt_range.end >= range.end
5510 })
5511 })?;
5512
5513 if all_edits_within_excerpt {
5514 return Ok(());
5515 }
5516 }
5517 }
5518 }
5519 } else {
5520 return Ok(());
5521 }
5522
5523 let mut ranges_to_highlight = Vec::new();
5524 let excerpt_buffer = cx.new(|cx| {
5525 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5526 for (buffer_handle, transaction) in &entries {
5527 let edited_ranges = buffer_handle
5528 .read(cx)
5529 .edited_ranges_for_transaction::<Point>(transaction)
5530 .collect::<Vec<_>>();
5531 let (ranges, _) = multibuffer.set_excerpts_for_path(
5532 PathKey::for_buffer(buffer_handle, cx),
5533 buffer_handle.clone(),
5534 edited_ranges,
5535 DEFAULT_MULTIBUFFER_CONTEXT,
5536 cx,
5537 );
5538
5539 ranges_to_highlight.extend(ranges);
5540 }
5541 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5542 multibuffer
5543 })?;
5544
5545 workspace.update_in(cx, |workspace, window, cx| {
5546 let project = workspace.project().clone();
5547 let editor =
5548 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5549 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5550 editor.update(cx, |editor, cx| {
5551 editor.highlight_background::<Self>(
5552 &ranges_to_highlight,
5553 |theme| theme.editor_highlighted_line_background,
5554 cx,
5555 );
5556 });
5557 })?;
5558
5559 Ok(())
5560 }
5561
5562 pub fn clear_code_action_providers(&mut self) {
5563 self.code_action_providers.clear();
5564 self.available_code_actions.take();
5565 }
5566
5567 pub fn add_code_action_provider(
5568 &mut self,
5569 provider: Rc<dyn CodeActionProvider>,
5570 window: &mut Window,
5571 cx: &mut Context<Self>,
5572 ) {
5573 if self
5574 .code_action_providers
5575 .iter()
5576 .any(|existing_provider| existing_provider.id() == provider.id())
5577 {
5578 return;
5579 }
5580
5581 self.code_action_providers.push(provider);
5582 self.refresh_code_actions(window, cx);
5583 }
5584
5585 pub fn remove_code_action_provider(
5586 &mut self,
5587 id: Arc<str>,
5588 window: &mut Window,
5589 cx: &mut Context<Self>,
5590 ) {
5591 self.code_action_providers
5592 .retain(|provider| provider.id() != id);
5593 self.refresh_code_actions(window, cx);
5594 }
5595
5596 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5597 let newest_selection = self.selections.newest_anchor().clone();
5598 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5599 let buffer = self.buffer.read(cx);
5600 if newest_selection.head().diff_base_anchor.is_some() {
5601 return None;
5602 }
5603 let (start_buffer, start) =
5604 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5605 let (end_buffer, end) =
5606 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5607 if start_buffer != end_buffer {
5608 return None;
5609 }
5610
5611 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5612 cx.background_executor()
5613 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5614 .await;
5615
5616 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5617 let providers = this.code_action_providers.clone();
5618 let tasks = this
5619 .code_action_providers
5620 .iter()
5621 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5622 .collect::<Vec<_>>();
5623 (providers, tasks)
5624 })?;
5625
5626 let mut actions = Vec::new();
5627 for (provider, provider_actions) in
5628 providers.into_iter().zip(future::join_all(tasks).await)
5629 {
5630 if let Some(provider_actions) = provider_actions.log_err() {
5631 actions.extend(provider_actions.into_iter().map(|action| {
5632 AvailableCodeAction {
5633 excerpt_id: newest_selection.start.excerpt_id,
5634 action,
5635 provider: provider.clone(),
5636 }
5637 }));
5638 }
5639 }
5640
5641 this.update(cx, |this, cx| {
5642 this.available_code_actions = if actions.is_empty() {
5643 None
5644 } else {
5645 Some((
5646 Location {
5647 buffer: start_buffer,
5648 range: start..end,
5649 },
5650 actions.into(),
5651 ))
5652 };
5653 cx.notify();
5654 })
5655 }));
5656 None
5657 }
5658
5659 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5660 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5661 self.show_git_blame_inline = false;
5662
5663 self.show_git_blame_inline_delay_task =
5664 Some(cx.spawn_in(window, async move |this, cx| {
5665 cx.background_executor().timer(delay).await;
5666
5667 this.update(cx, |this, cx| {
5668 this.show_git_blame_inline = true;
5669 cx.notify();
5670 })
5671 .log_err();
5672 }));
5673 }
5674 }
5675
5676 fn show_blame_popover(
5677 &mut self,
5678 blame_entry: &BlameEntry,
5679 position: gpui::Point<Pixels>,
5680 cx: &mut Context<Self>,
5681 ) {
5682 if let Some(state) = &mut self.inline_blame_popover {
5683 state.hide_task.take();
5684 cx.notify();
5685 } else {
5686 let delay = EditorSettings::get_global(cx).hover_popover_delay;
5687 let show_task = cx.spawn(async move |editor, cx| {
5688 cx.background_executor()
5689 .timer(std::time::Duration::from_millis(delay))
5690 .await;
5691 editor
5692 .update(cx, |editor, cx| {
5693 if let Some(state) = &mut editor.inline_blame_popover {
5694 state.show_task = None;
5695 cx.notify();
5696 }
5697 })
5698 .ok();
5699 });
5700 let Some(blame) = self.blame.as_ref() else {
5701 return;
5702 };
5703 let blame = blame.read(cx);
5704 let details = blame.details_for_entry(&blame_entry);
5705 let markdown = cx.new(|cx| {
5706 Markdown::new(
5707 details
5708 .as_ref()
5709 .map(|message| message.message.clone())
5710 .unwrap_or_default(),
5711 None,
5712 None,
5713 cx,
5714 )
5715 });
5716 self.inline_blame_popover = Some(InlineBlamePopover {
5717 position,
5718 show_task: Some(show_task),
5719 hide_task: None,
5720 popover_bounds: None,
5721 popover_state: InlineBlamePopoverState {
5722 scroll_handle: ScrollHandle::new(),
5723 commit_message: details,
5724 markdown,
5725 },
5726 });
5727 }
5728 }
5729
5730 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
5731 if let Some(state) = &mut self.inline_blame_popover {
5732 if state.show_task.is_some() {
5733 self.inline_blame_popover.take();
5734 cx.notify();
5735 } else {
5736 let hide_task = cx.spawn(async move |editor, cx| {
5737 cx.background_executor()
5738 .timer(std::time::Duration::from_millis(100))
5739 .await;
5740 editor
5741 .update(cx, |editor, cx| {
5742 editor.inline_blame_popover.take();
5743 cx.notify();
5744 })
5745 .ok();
5746 });
5747 state.hide_task = Some(hide_task);
5748 }
5749 }
5750 }
5751
5752 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5753 if self.pending_rename.is_some() {
5754 return None;
5755 }
5756
5757 let provider = self.semantics_provider.clone()?;
5758 let buffer = self.buffer.read(cx);
5759 let newest_selection = self.selections.newest_anchor().clone();
5760 let cursor_position = newest_selection.head();
5761 let (cursor_buffer, cursor_buffer_position) =
5762 buffer.text_anchor_for_position(cursor_position, cx)?;
5763 let (tail_buffer, tail_buffer_position) =
5764 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5765 if cursor_buffer != tail_buffer {
5766 return None;
5767 }
5768
5769 let snapshot = cursor_buffer.read(cx).snapshot();
5770 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
5771 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
5772 if start_word_range != end_word_range {
5773 self.document_highlights_task.take();
5774 self.clear_background_highlights::<DocumentHighlightRead>(cx);
5775 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
5776 return None;
5777 }
5778
5779 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5780 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5781 cx.background_executor()
5782 .timer(Duration::from_millis(debounce))
5783 .await;
5784
5785 let highlights = if let Some(highlights) = cx
5786 .update(|cx| {
5787 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5788 })
5789 .ok()
5790 .flatten()
5791 {
5792 highlights.await.log_err()
5793 } else {
5794 None
5795 };
5796
5797 if let Some(highlights) = highlights {
5798 this.update(cx, |this, cx| {
5799 if this.pending_rename.is_some() {
5800 return;
5801 }
5802
5803 let buffer_id = cursor_position.buffer_id;
5804 let buffer = this.buffer.read(cx);
5805 if !buffer
5806 .text_anchor_for_position(cursor_position, cx)
5807 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5808 {
5809 return;
5810 }
5811
5812 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5813 let mut write_ranges = Vec::new();
5814 let mut read_ranges = Vec::new();
5815 for highlight in highlights {
5816 for (excerpt_id, excerpt_range) in
5817 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5818 {
5819 let start = highlight
5820 .range
5821 .start
5822 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5823 let end = highlight
5824 .range
5825 .end
5826 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5827 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5828 continue;
5829 }
5830
5831 let range = Anchor {
5832 buffer_id,
5833 excerpt_id,
5834 text_anchor: start,
5835 diff_base_anchor: None,
5836 }..Anchor {
5837 buffer_id,
5838 excerpt_id,
5839 text_anchor: end,
5840 diff_base_anchor: None,
5841 };
5842 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5843 write_ranges.push(range);
5844 } else {
5845 read_ranges.push(range);
5846 }
5847 }
5848 }
5849
5850 this.highlight_background::<DocumentHighlightRead>(
5851 &read_ranges,
5852 |theme| theme.editor_document_highlight_read_background,
5853 cx,
5854 );
5855 this.highlight_background::<DocumentHighlightWrite>(
5856 &write_ranges,
5857 |theme| theme.editor_document_highlight_write_background,
5858 cx,
5859 );
5860 cx.notify();
5861 })
5862 .log_err();
5863 }
5864 }));
5865 None
5866 }
5867
5868 fn prepare_highlight_query_from_selection(
5869 &mut self,
5870 cx: &mut Context<Editor>,
5871 ) -> Option<(String, Range<Anchor>)> {
5872 if matches!(self.mode, EditorMode::SingleLine { .. }) {
5873 return None;
5874 }
5875 if !EditorSettings::get_global(cx).selection_highlight {
5876 return None;
5877 }
5878 if self.selections.count() != 1 || self.selections.line_mode {
5879 return None;
5880 }
5881 let selection = self.selections.newest::<Point>(cx);
5882 if selection.is_empty() || selection.start.row != selection.end.row {
5883 return None;
5884 }
5885 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5886 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
5887 let query = multi_buffer_snapshot
5888 .text_for_range(selection_anchor_range.clone())
5889 .collect::<String>();
5890 if query.trim().is_empty() {
5891 return None;
5892 }
5893 Some((query, selection_anchor_range))
5894 }
5895
5896 fn update_selection_occurrence_highlights(
5897 &mut self,
5898 query_text: String,
5899 query_range: Range<Anchor>,
5900 multi_buffer_range_to_query: Range<Point>,
5901 use_debounce: bool,
5902 window: &mut Window,
5903 cx: &mut Context<Editor>,
5904 ) -> Task<()> {
5905 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5906 cx.spawn_in(window, async move |editor, cx| {
5907 if use_debounce {
5908 cx.background_executor()
5909 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
5910 .await;
5911 }
5912 let match_task = cx.background_spawn(async move {
5913 let buffer_ranges = multi_buffer_snapshot
5914 .range_to_buffer_ranges(multi_buffer_range_to_query)
5915 .into_iter()
5916 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
5917 let mut match_ranges = Vec::new();
5918 let Ok(regex) = project::search::SearchQuery::text(
5919 query_text.clone(),
5920 false,
5921 false,
5922 false,
5923 Default::default(),
5924 Default::default(),
5925 false,
5926 None,
5927 ) else {
5928 return Vec::default();
5929 };
5930 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
5931 match_ranges.extend(
5932 regex
5933 .search(&buffer_snapshot, Some(search_range.clone()))
5934 .await
5935 .into_iter()
5936 .filter_map(|match_range| {
5937 let match_start = buffer_snapshot
5938 .anchor_after(search_range.start + match_range.start);
5939 let match_end = buffer_snapshot
5940 .anchor_before(search_range.start + match_range.end);
5941 let match_anchor_range = Anchor::range_in_buffer(
5942 excerpt_id,
5943 buffer_snapshot.remote_id(),
5944 match_start..match_end,
5945 );
5946 (match_anchor_range != query_range).then_some(match_anchor_range)
5947 }),
5948 );
5949 }
5950 match_ranges
5951 });
5952 let match_ranges = match_task.await;
5953 editor
5954 .update_in(cx, |editor, _, cx| {
5955 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5956 if !match_ranges.is_empty() {
5957 editor.highlight_background::<SelectedTextHighlight>(
5958 &match_ranges,
5959 |theme| theme.editor_document_highlight_bracket_background,
5960 cx,
5961 )
5962 }
5963 })
5964 .log_err();
5965 })
5966 }
5967
5968 fn refresh_selected_text_highlights(
5969 &mut self,
5970 on_buffer_edit: bool,
5971 window: &mut Window,
5972 cx: &mut Context<Editor>,
5973 ) {
5974 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
5975 else {
5976 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5977 self.quick_selection_highlight_task.take();
5978 self.debounced_selection_highlight_task.take();
5979 return;
5980 };
5981 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5982 if on_buffer_edit
5983 || self
5984 .quick_selection_highlight_task
5985 .as_ref()
5986 .map_or(true, |(prev_anchor_range, _)| {
5987 prev_anchor_range != &query_range
5988 })
5989 {
5990 let multi_buffer_visible_start = self
5991 .scroll_manager
5992 .anchor()
5993 .anchor
5994 .to_point(&multi_buffer_snapshot);
5995 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5996 multi_buffer_visible_start
5997 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5998 Bias::Left,
5999 );
6000 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6001 self.quick_selection_highlight_task = Some((
6002 query_range.clone(),
6003 self.update_selection_occurrence_highlights(
6004 query_text.clone(),
6005 query_range.clone(),
6006 multi_buffer_visible_range,
6007 false,
6008 window,
6009 cx,
6010 ),
6011 ));
6012 }
6013 if on_buffer_edit
6014 || self
6015 .debounced_selection_highlight_task
6016 .as_ref()
6017 .map_or(true, |(prev_anchor_range, _)| {
6018 prev_anchor_range != &query_range
6019 })
6020 {
6021 let multi_buffer_start = multi_buffer_snapshot
6022 .anchor_before(0)
6023 .to_point(&multi_buffer_snapshot);
6024 let multi_buffer_end = multi_buffer_snapshot
6025 .anchor_after(multi_buffer_snapshot.len())
6026 .to_point(&multi_buffer_snapshot);
6027 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6028 self.debounced_selection_highlight_task = Some((
6029 query_range.clone(),
6030 self.update_selection_occurrence_highlights(
6031 query_text,
6032 query_range,
6033 multi_buffer_full_range,
6034 true,
6035 window,
6036 cx,
6037 ),
6038 ));
6039 }
6040 }
6041
6042 pub fn refresh_inline_completion(
6043 &mut self,
6044 debounce: bool,
6045 user_requested: bool,
6046 window: &mut Window,
6047 cx: &mut Context<Self>,
6048 ) -> Option<()> {
6049 let provider = self.edit_prediction_provider()?;
6050 let cursor = self.selections.newest_anchor().head();
6051 let (buffer, cursor_buffer_position) =
6052 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6053
6054 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6055 self.discard_inline_completion(false, cx);
6056 return None;
6057 }
6058
6059 if !user_requested
6060 && (!self.should_show_edit_predictions()
6061 || !self.is_focused(window)
6062 || buffer.read(cx).is_empty())
6063 {
6064 self.discard_inline_completion(false, cx);
6065 return None;
6066 }
6067
6068 self.update_visible_inline_completion(window, cx);
6069 provider.refresh(
6070 self.project.clone(),
6071 buffer,
6072 cursor_buffer_position,
6073 debounce,
6074 cx,
6075 );
6076 Some(())
6077 }
6078
6079 fn show_edit_predictions_in_menu(&self) -> bool {
6080 match self.edit_prediction_settings {
6081 EditPredictionSettings::Disabled => false,
6082 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6083 }
6084 }
6085
6086 pub fn edit_predictions_enabled(&self) -> bool {
6087 match self.edit_prediction_settings {
6088 EditPredictionSettings::Disabled => false,
6089 EditPredictionSettings::Enabled { .. } => true,
6090 }
6091 }
6092
6093 fn edit_prediction_requires_modifier(&self) -> bool {
6094 match self.edit_prediction_settings {
6095 EditPredictionSettings::Disabled => false,
6096 EditPredictionSettings::Enabled {
6097 preview_requires_modifier,
6098 ..
6099 } => preview_requires_modifier,
6100 }
6101 }
6102
6103 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6104 if self.edit_prediction_provider.is_none() {
6105 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6106 } else {
6107 let selection = self.selections.newest_anchor();
6108 let cursor = selection.head();
6109
6110 if let Some((buffer, cursor_buffer_position)) =
6111 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6112 {
6113 self.edit_prediction_settings =
6114 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6115 }
6116 }
6117 }
6118
6119 fn edit_prediction_settings_at_position(
6120 &self,
6121 buffer: &Entity<Buffer>,
6122 buffer_position: language::Anchor,
6123 cx: &App,
6124 ) -> EditPredictionSettings {
6125 if !self.mode.is_full()
6126 || !self.show_inline_completions_override.unwrap_or(true)
6127 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6128 {
6129 return EditPredictionSettings::Disabled;
6130 }
6131
6132 let buffer = buffer.read(cx);
6133
6134 let file = buffer.file();
6135
6136 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6137 return EditPredictionSettings::Disabled;
6138 };
6139
6140 let by_provider = matches!(
6141 self.menu_inline_completions_policy,
6142 MenuInlineCompletionsPolicy::ByProvider
6143 );
6144
6145 let show_in_menu = by_provider
6146 && self
6147 .edit_prediction_provider
6148 .as_ref()
6149 .map_or(false, |provider| {
6150 provider.provider.show_completions_in_menu()
6151 });
6152
6153 let preview_requires_modifier =
6154 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6155
6156 EditPredictionSettings::Enabled {
6157 show_in_menu,
6158 preview_requires_modifier,
6159 }
6160 }
6161
6162 fn should_show_edit_predictions(&self) -> bool {
6163 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6164 }
6165
6166 pub fn edit_prediction_preview_is_active(&self) -> bool {
6167 matches!(
6168 self.edit_prediction_preview,
6169 EditPredictionPreview::Active { .. }
6170 )
6171 }
6172
6173 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6174 let cursor = self.selections.newest_anchor().head();
6175 if let Some((buffer, cursor_position)) =
6176 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6177 {
6178 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6179 } else {
6180 false
6181 }
6182 }
6183
6184 pub fn supports_minimap(&self, cx: &App) -> bool {
6185 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6186 }
6187
6188 fn edit_predictions_enabled_in_buffer(
6189 &self,
6190 buffer: &Entity<Buffer>,
6191 buffer_position: language::Anchor,
6192 cx: &App,
6193 ) -> bool {
6194 maybe!({
6195 if self.read_only(cx) {
6196 return Some(false);
6197 }
6198 let provider = self.edit_prediction_provider()?;
6199 if !provider.is_enabled(&buffer, buffer_position, cx) {
6200 return Some(false);
6201 }
6202 let buffer = buffer.read(cx);
6203 let Some(file) = buffer.file() else {
6204 return Some(true);
6205 };
6206 let settings = all_language_settings(Some(file), cx);
6207 Some(settings.edit_predictions_enabled_for_file(file, cx))
6208 })
6209 .unwrap_or(false)
6210 }
6211
6212 fn cycle_inline_completion(
6213 &mut self,
6214 direction: Direction,
6215 window: &mut Window,
6216 cx: &mut Context<Self>,
6217 ) -> Option<()> {
6218 let provider = self.edit_prediction_provider()?;
6219 let cursor = self.selections.newest_anchor().head();
6220 let (buffer, cursor_buffer_position) =
6221 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6222 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6223 return None;
6224 }
6225
6226 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6227 self.update_visible_inline_completion(window, cx);
6228
6229 Some(())
6230 }
6231
6232 pub fn show_inline_completion(
6233 &mut self,
6234 _: &ShowEditPrediction,
6235 window: &mut Window,
6236 cx: &mut Context<Self>,
6237 ) {
6238 if !self.has_active_inline_completion() {
6239 self.refresh_inline_completion(false, true, window, cx);
6240 return;
6241 }
6242
6243 self.update_visible_inline_completion(window, cx);
6244 }
6245
6246 pub fn display_cursor_names(
6247 &mut self,
6248 _: &DisplayCursorNames,
6249 window: &mut Window,
6250 cx: &mut Context<Self>,
6251 ) {
6252 self.show_cursor_names(window, cx);
6253 }
6254
6255 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6256 self.show_cursor_names = true;
6257 cx.notify();
6258 cx.spawn_in(window, async move |this, cx| {
6259 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6260 this.update(cx, |this, cx| {
6261 this.show_cursor_names = false;
6262 cx.notify()
6263 })
6264 .ok()
6265 })
6266 .detach();
6267 }
6268
6269 pub fn next_edit_prediction(
6270 &mut self,
6271 _: &NextEditPrediction,
6272 window: &mut Window,
6273 cx: &mut Context<Self>,
6274 ) {
6275 if self.has_active_inline_completion() {
6276 self.cycle_inline_completion(Direction::Next, window, cx);
6277 } else {
6278 let is_copilot_disabled = self
6279 .refresh_inline_completion(false, true, window, cx)
6280 .is_none();
6281 if is_copilot_disabled {
6282 cx.propagate();
6283 }
6284 }
6285 }
6286
6287 pub fn previous_edit_prediction(
6288 &mut self,
6289 _: &PreviousEditPrediction,
6290 window: &mut Window,
6291 cx: &mut Context<Self>,
6292 ) {
6293 if self.has_active_inline_completion() {
6294 self.cycle_inline_completion(Direction::Prev, window, cx);
6295 } else {
6296 let is_copilot_disabled = self
6297 .refresh_inline_completion(false, true, window, cx)
6298 .is_none();
6299 if is_copilot_disabled {
6300 cx.propagate();
6301 }
6302 }
6303 }
6304
6305 pub fn accept_edit_prediction(
6306 &mut self,
6307 _: &AcceptEditPrediction,
6308 window: &mut Window,
6309 cx: &mut Context<Self>,
6310 ) {
6311 if self.show_edit_predictions_in_menu() {
6312 self.hide_context_menu(window, cx);
6313 }
6314
6315 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6316 return;
6317 };
6318
6319 self.report_inline_completion_event(
6320 active_inline_completion.completion_id.clone(),
6321 true,
6322 cx,
6323 );
6324
6325 match &active_inline_completion.completion {
6326 InlineCompletion::Move { target, .. } => {
6327 let target = *target;
6328
6329 if let Some(position_map) = &self.last_position_map {
6330 if position_map
6331 .visible_row_range
6332 .contains(&target.to_display_point(&position_map.snapshot).row())
6333 || !self.edit_prediction_requires_modifier()
6334 {
6335 self.unfold_ranges(&[target..target], true, false, cx);
6336 // Note that this is also done in vim's handler of the Tab action.
6337 self.change_selections(
6338 Some(Autoscroll::newest()),
6339 window,
6340 cx,
6341 |selections| {
6342 selections.select_anchor_ranges([target..target]);
6343 },
6344 );
6345 self.clear_row_highlights::<EditPredictionPreview>();
6346
6347 self.edit_prediction_preview
6348 .set_previous_scroll_position(None);
6349 } else {
6350 self.edit_prediction_preview
6351 .set_previous_scroll_position(Some(
6352 position_map.snapshot.scroll_anchor,
6353 ));
6354
6355 self.highlight_rows::<EditPredictionPreview>(
6356 target..target,
6357 cx.theme().colors().editor_highlighted_line_background,
6358 RowHighlightOptions {
6359 autoscroll: true,
6360 ..Default::default()
6361 },
6362 cx,
6363 );
6364 self.request_autoscroll(Autoscroll::fit(), cx);
6365 }
6366 }
6367 }
6368 InlineCompletion::Edit { edits, .. } => {
6369 if let Some(provider) = self.edit_prediction_provider() {
6370 provider.accept(cx);
6371 }
6372
6373 let snapshot = self.buffer.read(cx).snapshot(cx);
6374 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6375
6376 self.buffer.update(cx, |buffer, cx| {
6377 buffer.edit(edits.iter().cloned(), None, cx)
6378 });
6379
6380 self.change_selections(None, window, cx, |s| {
6381 s.select_anchor_ranges([last_edit_end..last_edit_end])
6382 });
6383
6384 self.update_visible_inline_completion(window, cx);
6385 if self.active_inline_completion.is_none() {
6386 self.refresh_inline_completion(true, true, window, cx);
6387 }
6388
6389 cx.notify();
6390 }
6391 }
6392
6393 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6394 }
6395
6396 pub fn accept_partial_inline_completion(
6397 &mut self,
6398 _: &AcceptPartialEditPrediction,
6399 window: &mut Window,
6400 cx: &mut Context<Self>,
6401 ) {
6402 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6403 return;
6404 };
6405 if self.selections.count() != 1 {
6406 return;
6407 }
6408
6409 self.report_inline_completion_event(
6410 active_inline_completion.completion_id.clone(),
6411 true,
6412 cx,
6413 );
6414
6415 match &active_inline_completion.completion {
6416 InlineCompletion::Move { target, .. } => {
6417 let target = *target;
6418 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6419 selections.select_anchor_ranges([target..target]);
6420 });
6421 }
6422 InlineCompletion::Edit { edits, .. } => {
6423 // Find an insertion that starts at the cursor position.
6424 let snapshot = self.buffer.read(cx).snapshot(cx);
6425 let cursor_offset = self.selections.newest::<usize>(cx).head();
6426 let insertion = edits.iter().find_map(|(range, text)| {
6427 let range = range.to_offset(&snapshot);
6428 if range.is_empty() && range.start == cursor_offset {
6429 Some(text)
6430 } else {
6431 None
6432 }
6433 });
6434
6435 if let Some(text) = insertion {
6436 let mut partial_completion = text
6437 .chars()
6438 .by_ref()
6439 .take_while(|c| c.is_alphabetic())
6440 .collect::<String>();
6441 if partial_completion.is_empty() {
6442 partial_completion = text
6443 .chars()
6444 .by_ref()
6445 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6446 .collect::<String>();
6447 }
6448
6449 cx.emit(EditorEvent::InputHandled {
6450 utf16_range_to_replace: None,
6451 text: partial_completion.clone().into(),
6452 });
6453
6454 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6455
6456 self.refresh_inline_completion(true, true, window, cx);
6457 cx.notify();
6458 } else {
6459 self.accept_edit_prediction(&Default::default(), window, cx);
6460 }
6461 }
6462 }
6463 }
6464
6465 fn discard_inline_completion(
6466 &mut self,
6467 should_report_inline_completion_event: bool,
6468 cx: &mut Context<Self>,
6469 ) -> bool {
6470 if should_report_inline_completion_event {
6471 let completion_id = self
6472 .active_inline_completion
6473 .as_ref()
6474 .and_then(|active_completion| active_completion.completion_id.clone());
6475
6476 self.report_inline_completion_event(completion_id, false, cx);
6477 }
6478
6479 if let Some(provider) = self.edit_prediction_provider() {
6480 provider.discard(cx);
6481 }
6482
6483 self.take_active_inline_completion(cx)
6484 }
6485
6486 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6487 let Some(provider) = self.edit_prediction_provider() else {
6488 return;
6489 };
6490
6491 let Some((_, buffer, _)) = self
6492 .buffer
6493 .read(cx)
6494 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6495 else {
6496 return;
6497 };
6498
6499 let extension = buffer
6500 .read(cx)
6501 .file()
6502 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6503
6504 let event_type = match accepted {
6505 true => "Edit Prediction Accepted",
6506 false => "Edit Prediction Discarded",
6507 };
6508 telemetry::event!(
6509 event_type,
6510 provider = provider.name(),
6511 prediction_id = id,
6512 suggestion_accepted = accepted,
6513 file_extension = extension,
6514 );
6515 }
6516
6517 pub fn has_active_inline_completion(&self) -> bool {
6518 self.active_inline_completion.is_some()
6519 }
6520
6521 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6522 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6523 return false;
6524 };
6525
6526 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6527 self.clear_highlights::<InlineCompletionHighlight>(cx);
6528 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6529 true
6530 }
6531
6532 /// Returns true when we're displaying the edit prediction popover below the cursor
6533 /// like we are not previewing and the LSP autocomplete menu is visible
6534 /// or we are in `when_holding_modifier` mode.
6535 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6536 if self.edit_prediction_preview_is_active()
6537 || !self.show_edit_predictions_in_menu()
6538 || !self.edit_predictions_enabled()
6539 {
6540 return false;
6541 }
6542
6543 if self.has_visible_completions_menu() {
6544 return true;
6545 }
6546
6547 has_completion && self.edit_prediction_requires_modifier()
6548 }
6549
6550 fn handle_modifiers_changed(
6551 &mut self,
6552 modifiers: Modifiers,
6553 position_map: &PositionMap,
6554 window: &mut Window,
6555 cx: &mut Context<Self>,
6556 ) {
6557 if self.show_edit_predictions_in_menu() {
6558 self.update_edit_prediction_preview(&modifiers, window, cx);
6559 }
6560
6561 self.update_selection_mode(&modifiers, position_map, window, cx);
6562
6563 let mouse_position = window.mouse_position();
6564 if !position_map.text_hitbox.is_hovered(window) {
6565 return;
6566 }
6567
6568 self.update_hovered_link(
6569 position_map.point_for_position(mouse_position),
6570 &position_map.snapshot,
6571 modifiers,
6572 window,
6573 cx,
6574 )
6575 }
6576
6577 fn update_selection_mode(
6578 &mut self,
6579 modifiers: &Modifiers,
6580 position_map: &PositionMap,
6581 window: &mut Window,
6582 cx: &mut Context<Self>,
6583 ) {
6584 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6585 return;
6586 }
6587
6588 let mouse_position = window.mouse_position();
6589 let point_for_position = position_map.point_for_position(mouse_position);
6590 let position = point_for_position.previous_valid;
6591
6592 self.select(
6593 SelectPhase::BeginColumnar {
6594 position,
6595 reset: false,
6596 goal_column: point_for_position.exact_unclipped.column(),
6597 },
6598 window,
6599 cx,
6600 );
6601 }
6602
6603 fn update_edit_prediction_preview(
6604 &mut self,
6605 modifiers: &Modifiers,
6606 window: &mut Window,
6607 cx: &mut Context<Self>,
6608 ) {
6609 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6610 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6611 return;
6612 };
6613
6614 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6615 if matches!(
6616 self.edit_prediction_preview,
6617 EditPredictionPreview::Inactive { .. }
6618 ) {
6619 self.edit_prediction_preview = EditPredictionPreview::Active {
6620 previous_scroll_position: None,
6621 since: Instant::now(),
6622 };
6623
6624 self.update_visible_inline_completion(window, cx);
6625 cx.notify();
6626 }
6627 } else if let EditPredictionPreview::Active {
6628 previous_scroll_position,
6629 since,
6630 } = self.edit_prediction_preview
6631 {
6632 if let (Some(previous_scroll_position), Some(position_map)) =
6633 (previous_scroll_position, self.last_position_map.as_ref())
6634 {
6635 self.set_scroll_position(
6636 previous_scroll_position
6637 .scroll_position(&position_map.snapshot.display_snapshot),
6638 window,
6639 cx,
6640 );
6641 }
6642
6643 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6644 released_too_fast: since.elapsed() < Duration::from_millis(200),
6645 };
6646 self.clear_row_highlights::<EditPredictionPreview>();
6647 self.update_visible_inline_completion(window, cx);
6648 cx.notify();
6649 }
6650 }
6651
6652 fn update_visible_inline_completion(
6653 &mut self,
6654 _window: &mut Window,
6655 cx: &mut Context<Self>,
6656 ) -> Option<()> {
6657 let selection = self.selections.newest_anchor();
6658 let cursor = selection.head();
6659 let multibuffer = self.buffer.read(cx).snapshot(cx);
6660 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6661 let excerpt_id = cursor.excerpt_id;
6662
6663 let show_in_menu = self.show_edit_predictions_in_menu();
6664 let completions_menu_has_precedence = !show_in_menu
6665 && (self.context_menu.borrow().is_some()
6666 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6667
6668 if completions_menu_has_precedence
6669 || !offset_selection.is_empty()
6670 || self
6671 .active_inline_completion
6672 .as_ref()
6673 .map_or(false, |completion| {
6674 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6675 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6676 !invalidation_range.contains(&offset_selection.head())
6677 })
6678 {
6679 self.discard_inline_completion(false, cx);
6680 return None;
6681 }
6682
6683 self.take_active_inline_completion(cx);
6684 let Some(provider) = self.edit_prediction_provider() else {
6685 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6686 return None;
6687 };
6688
6689 let (buffer, cursor_buffer_position) =
6690 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6691
6692 self.edit_prediction_settings =
6693 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6694
6695 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6696
6697 if self.edit_prediction_indent_conflict {
6698 let cursor_point = cursor.to_point(&multibuffer);
6699
6700 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6701
6702 if let Some((_, indent)) = indents.iter().next() {
6703 if indent.len == cursor_point.column {
6704 self.edit_prediction_indent_conflict = false;
6705 }
6706 }
6707 }
6708
6709 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6710 let edits = inline_completion
6711 .edits
6712 .into_iter()
6713 .flat_map(|(range, new_text)| {
6714 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6715 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6716 Some((start..end, new_text))
6717 })
6718 .collect::<Vec<_>>();
6719 if edits.is_empty() {
6720 return None;
6721 }
6722
6723 let first_edit_start = edits.first().unwrap().0.start;
6724 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6725 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6726
6727 let last_edit_end = edits.last().unwrap().0.end;
6728 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6729 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6730
6731 let cursor_row = cursor.to_point(&multibuffer).row;
6732
6733 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6734
6735 let mut inlay_ids = Vec::new();
6736 let invalidation_row_range;
6737 let move_invalidation_row_range = if cursor_row < edit_start_row {
6738 Some(cursor_row..edit_end_row)
6739 } else if cursor_row > edit_end_row {
6740 Some(edit_start_row..cursor_row)
6741 } else {
6742 None
6743 };
6744 let is_move =
6745 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6746 let completion = if is_move {
6747 invalidation_row_range =
6748 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6749 let target = first_edit_start;
6750 InlineCompletion::Move { target, snapshot }
6751 } else {
6752 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6753 && !self.inline_completions_hidden_for_vim_mode;
6754
6755 if show_completions_in_buffer {
6756 if edits
6757 .iter()
6758 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6759 {
6760 let mut inlays = Vec::new();
6761 for (range, new_text) in &edits {
6762 let inlay = Inlay::inline_completion(
6763 post_inc(&mut self.next_inlay_id),
6764 range.start,
6765 new_text.as_str(),
6766 );
6767 inlay_ids.push(inlay.id);
6768 inlays.push(inlay);
6769 }
6770
6771 self.splice_inlays(&[], inlays, cx);
6772 } else {
6773 let background_color = cx.theme().status().deleted_background;
6774 self.highlight_text::<InlineCompletionHighlight>(
6775 edits.iter().map(|(range, _)| range.clone()).collect(),
6776 HighlightStyle {
6777 background_color: Some(background_color),
6778 ..Default::default()
6779 },
6780 cx,
6781 );
6782 }
6783 }
6784
6785 invalidation_row_range = edit_start_row..edit_end_row;
6786
6787 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6788 if provider.show_tab_accept_marker() {
6789 EditDisplayMode::TabAccept
6790 } else {
6791 EditDisplayMode::Inline
6792 }
6793 } else {
6794 EditDisplayMode::DiffPopover
6795 };
6796
6797 InlineCompletion::Edit {
6798 edits,
6799 edit_preview: inline_completion.edit_preview,
6800 display_mode,
6801 snapshot,
6802 }
6803 };
6804
6805 let invalidation_range = multibuffer
6806 .anchor_before(Point::new(invalidation_row_range.start, 0))
6807 ..multibuffer.anchor_after(Point::new(
6808 invalidation_row_range.end,
6809 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6810 ));
6811
6812 self.stale_inline_completion_in_menu = None;
6813 self.active_inline_completion = Some(InlineCompletionState {
6814 inlay_ids,
6815 completion,
6816 completion_id: inline_completion.id,
6817 invalidation_range,
6818 });
6819
6820 cx.notify();
6821
6822 Some(())
6823 }
6824
6825 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6826 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6827 }
6828
6829 fn clear_tasks(&mut self) {
6830 self.tasks.clear()
6831 }
6832
6833 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6834 if self.tasks.insert(key, value).is_some() {
6835 // This case should hopefully be rare, but just in case...
6836 log::error!(
6837 "multiple different run targets found on a single line, only the last target will be rendered"
6838 )
6839 }
6840 }
6841
6842 /// Get all display points of breakpoints that will be rendered within editor
6843 ///
6844 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6845 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6846 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6847 fn active_breakpoints(
6848 &self,
6849 range: Range<DisplayRow>,
6850 window: &mut Window,
6851 cx: &mut Context<Self>,
6852 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
6853 let mut breakpoint_display_points = HashMap::default();
6854
6855 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
6856 return breakpoint_display_points;
6857 };
6858
6859 let snapshot = self.snapshot(window, cx);
6860
6861 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
6862 let Some(project) = self.project.as_ref() else {
6863 return breakpoint_display_points;
6864 };
6865
6866 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
6867 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
6868
6869 for (buffer_snapshot, range, excerpt_id) in
6870 multi_buffer_snapshot.range_to_buffer_ranges(range)
6871 {
6872 let Some(buffer) = project.read_with(cx, |this, cx| {
6873 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
6874 }) else {
6875 continue;
6876 };
6877 let breakpoints = breakpoint_store.read(cx).breakpoints(
6878 &buffer,
6879 Some(
6880 buffer_snapshot.anchor_before(range.start)
6881 ..buffer_snapshot.anchor_after(range.end),
6882 ),
6883 buffer_snapshot,
6884 cx,
6885 );
6886 for (breakpoint, state) in breakpoints {
6887 let multi_buffer_anchor =
6888 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
6889 let position = multi_buffer_anchor
6890 .to_point(&multi_buffer_snapshot)
6891 .to_display_point(&snapshot);
6892
6893 breakpoint_display_points.insert(
6894 position.row(),
6895 (multi_buffer_anchor, breakpoint.bp.clone(), state),
6896 );
6897 }
6898 }
6899
6900 breakpoint_display_points
6901 }
6902
6903 fn breakpoint_context_menu(
6904 &self,
6905 anchor: Anchor,
6906 window: &mut Window,
6907 cx: &mut Context<Self>,
6908 ) -> Entity<ui::ContextMenu> {
6909 let weak_editor = cx.weak_entity();
6910 let focus_handle = self.focus_handle(cx);
6911
6912 let row = self
6913 .buffer
6914 .read(cx)
6915 .snapshot(cx)
6916 .summary_for_anchor::<Point>(&anchor)
6917 .row;
6918
6919 let breakpoint = self
6920 .breakpoint_at_row(row, window, cx)
6921 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
6922
6923 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
6924 "Edit Log Breakpoint"
6925 } else {
6926 "Set Log Breakpoint"
6927 };
6928
6929 let condition_breakpoint_msg = if breakpoint
6930 .as_ref()
6931 .is_some_and(|bp| bp.1.condition.is_some())
6932 {
6933 "Edit Condition Breakpoint"
6934 } else {
6935 "Set Condition Breakpoint"
6936 };
6937
6938 let hit_condition_breakpoint_msg = if breakpoint
6939 .as_ref()
6940 .is_some_and(|bp| bp.1.hit_condition.is_some())
6941 {
6942 "Edit Hit Condition Breakpoint"
6943 } else {
6944 "Set Hit Condition Breakpoint"
6945 };
6946
6947 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
6948 "Unset Breakpoint"
6949 } else {
6950 "Set Breakpoint"
6951 };
6952
6953 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
6954 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
6955
6956 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
6957 BreakpointState::Enabled => Some("Disable"),
6958 BreakpointState::Disabled => Some("Enable"),
6959 });
6960
6961 let (anchor, breakpoint) =
6962 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
6963
6964 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6965 menu.on_blur_subscription(Subscription::new(|| {}))
6966 .context(focus_handle)
6967 .when(run_to_cursor, |this| {
6968 let weak_editor = weak_editor.clone();
6969 this.entry("Run to cursor", None, move |window, cx| {
6970 weak_editor
6971 .update(cx, |editor, cx| {
6972 editor.change_selections(None, window, cx, |s| {
6973 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
6974 });
6975 })
6976 .ok();
6977
6978 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
6979 })
6980 .separator()
6981 })
6982 .when_some(toggle_state_msg, |this, msg| {
6983 this.entry(msg, None, {
6984 let weak_editor = weak_editor.clone();
6985 let breakpoint = breakpoint.clone();
6986 move |_window, cx| {
6987 weak_editor
6988 .update(cx, |this, cx| {
6989 this.edit_breakpoint_at_anchor(
6990 anchor,
6991 breakpoint.as_ref().clone(),
6992 BreakpointEditAction::InvertState,
6993 cx,
6994 );
6995 })
6996 .log_err();
6997 }
6998 })
6999 })
7000 .entry(set_breakpoint_msg, None, {
7001 let weak_editor = weak_editor.clone();
7002 let breakpoint = breakpoint.clone();
7003 move |_window, cx| {
7004 weak_editor
7005 .update(cx, |this, cx| {
7006 this.edit_breakpoint_at_anchor(
7007 anchor,
7008 breakpoint.as_ref().clone(),
7009 BreakpointEditAction::Toggle,
7010 cx,
7011 );
7012 })
7013 .log_err();
7014 }
7015 })
7016 .entry(log_breakpoint_msg, None, {
7017 let breakpoint = breakpoint.clone();
7018 let weak_editor = weak_editor.clone();
7019 move |window, cx| {
7020 weak_editor
7021 .update(cx, |this, cx| {
7022 this.add_edit_breakpoint_block(
7023 anchor,
7024 breakpoint.as_ref(),
7025 BreakpointPromptEditAction::Log,
7026 window,
7027 cx,
7028 );
7029 })
7030 .log_err();
7031 }
7032 })
7033 .entry(condition_breakpoint_msg, None, {
7034 let breakpoint = breakpoint.clone();
7035 let weak_editor = weak_editor.clone();
7036 move |window, cx| {
7037 weak_editor
7038 .update(cx, |this, cx| {
7039 this.add_edit_breakpoint_block(
7040 anchor,
7041 breakpoint.as_ref(),
7042 BreakpointPromptEditAction::Condition,
7043 window,
7044 cx,
7045 );
7046 })
7047 .log_err();
7048 }
7049 })
7050 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7051 weak_editor
7052 .update(cx, |this, cx| {
7053 this.add_edit_breakpoint_block(
7054 anchor,
7055 breakpoint.as_ref(),
7056 BreakpointPromptEditAction::HitCondition,
7057 window,
7058 cx,
7059 );
7060 })
7061 .log_err();
7062 })
7063 })
7064 }
7065
7066 fn render_breakpoint(
7067 &self,
7068 position: Anchor,
7069 row: DisplayRow,
7070 breakpoint: &Breakpoint,
7071 state: Option<BreakpointSessionState>,
7072 cx: &mut Context<Self>,
7073 ) -> IconButton {
7074 let is_rejected = state.is_some_and(|s| !s.verified);
7075 // Is it a breakpoint that shows up when hovering over gutter?
7076 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7077 (false, false),
7078 |PhantomBreakpointIndicator {
7079 is_active,
7080 display_row,
7081 collides_with_existing_breakpoint,
7082 }| {
7083 (
7084 is_active && display_row == row,
7085 collides_with_existing_breakpoint,
7086 )
7087 },
7088 );
7089
7090 let (color, icon) = {
7091 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7092 (false, false) => ui::IconName::DebugBreakpoint,
7093 (true, false) => ui::IconName::DebugLogBreakpoint,
7094 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7095 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7096 };
7097
7098 let color = if is_phantom {
7099 Color::Hint
7100 } else if is_rejected {
7101 Color::Disabled
7102 } else {
7103 Color::Debugger
7104 };
7105
7106 (color, icon)
7107 };
7108
7109 let breakpoint = Arc::from(breakpoint.clone());
7110
7111 let alt_as_text = gpui::Keystroke {
7112 modifiers: Modifiers::secondary_key(),
7113 ..Default::default()
7114 };
7115 let primary_action_text = if breakpoint.is_disabled() {
7116 "enable"
7117 } else if is_phantom && !collides_with_existing {
7118 "set"
7119 } else {
7120 "unset"
7121 };
7122 let mut primary_text = format!("Click to {primary_action_text}");
7123 if collides_with_existing && !breakpoint.is_disabled() {
7124 use std::fmt::Write;
7125 write!(primary_text, ", {alt_as_text}-click to disable").ok();
7126 }
7127 let primary_text = SharedString::from(primary_text);
7128 let focus_handle = self.focus_handle.clone();
7129
7130 let meta = if is_rejected {
7131 "No executable code is associated with this line."
7132 } else {
7133 "Right-click for more options."
7134 };
7135 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7136 .icon_size(IconSize::XSmall)
7137 .size(ui::ButtonSize::None)
7138 .when(is_rejected, |this| {
7139 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7140 })
7141 .icon_color(color)
7142 .style(ButtonStyle::Transparent)
7143 .on_click(cx.listener({
7144 let breakpoint = breakpoint.clone();
7145
7146 move |editor, event: &ClickEvent, window, cx| {
7147 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7148 BreakpointEditAction::InvertState
7149 } else {
7150 BreakpointEditAction::Toggle
7151 };
7152
7153 window.focus(&editor.focus_handle(cx));
7154 editor.edit_breakpoint_at_anchor(
7155 position,
7156 breakpoint.as_ref().clone(),
7157 edit_action,
7158 cx,
7159 );
7160 }
7161 }))
7162 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7163 editor.set_breakpoint_context_menu(
7164 row,
7165 Some(position),
7166 event.down.position,
7167 window,
7168 cx,
7169 );
7170 }))
7171 .tooltip(move |window, cx| {
7172 Tooltip::with_meta_in(primary_text.clone(), None, meta, &focus_handle, window, cx)
7173 })
7174 }
7175
7176 fn build_tasks_context(
7177 project: &Entity<Project>,
7178 buffer: &Entity<Buffer>,
7179 buffer_row: u32,
7180 tasks: &Arc<RunnableTasks>,
7181 cx: &mut Context<Self>,
7182 ) -> Task<Option<task::TaskContext>> {
7183 let position = Point::new(buffer_row, tasks.column);
7184 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7185 let location = Location {
7186 buffer: buffer.clone(),
7187 range: range_start..range_start,
7188 };
7189 // Fill in the environmental variables from the tree-sitter captures
7190 let mut captured_task_variables = TaskVariables::default();
7191 for (capture_name, value) in tasks.extra_variables.clone() {
7192 captured_task_variables.insert(
7193 task::VariableName::Custom(capture_name.into()),
7194 value.clone(),
7195 );
7196 }
7197 project.update(cx, |project, cx| {
7198 project.task_store().update(cx, |task_store, cx| {
7199 task_store.task_context_for_location(captured_task_variables, location, cx)
7200 })
7201 })
7202 }
7203
7204 pub fn spawn_nearest_task(
7205 &mut self,
7206 action: &SpawnNearestTask,
7207 window: &mut Window,
7208 cx: &mut Context<Self>,
7209 ) {
7210 let Some((workspace, _)) = self.workspace.clone() else {
7211 return;
7212 };
7213 let Some(project) = self.project.clone() else {
7214 return;
7215 };
7216
7217 // Try to find a closest, enclosing node using tree-sitter that has a
7218 // task
7219 let Some((buffer, buffer_row, tasks)) = self
7220 .find_enclosing_node_task(cx)
7221 // Or find the task that's closest in row-distance.
7222 .or_else(|| self.find_closest_task(cx))
7223 else {
7224 return;
7225 };
7226
7227 let reveal_strategy = action.reveal;
7228 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7229 cx.spawn_in(window, async move |_, cx| {
7230 let context = task_context.await?;
7231 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7232
7233 let resolved = &mut resolved_task.resolved;
7234 resolved.reveal = reveal_strategy;
7235
7236 workspace
7237 .update_in(cx, |workspace, window, cx| {
7238 workspace.schedule_resolved_task(
7239 task_source_kind,
7240 resolved_task,
7241 false,
7242 window,
7243 cx,
7244 );
7245 })
7246 .ok()
7247 })
7248 .detach();
7249 }
7250
7251 fn find_closest_task(
7252 &mut self,
7253 cx: &mut Context<Self>,
7254 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7255 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7256
7257 let ((buffer_id, row), tasks) = self
7258 .tasks
7259 .iter()
7260 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7261
7262 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7263 let tasks = Arc::new(tasks.to_owned());
7264 Some((buffer, *row, tasks))
7265 }
7266
7267 fn find_enclosing_node_task(
7268 &mut self,
7269 cx: &mut Context<Self>,
7270 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7271 let snapshot = self.buffer.read(cx).snapshot(cx);
7272 let offset = self.selections.newest::<usize>(cx).head();
7273 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7274 let buffer_id = excerpt.buffer().remote_id();
7275
7276 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7277 let mut cursor = layer.node().walk();
7278
7279 while cursor.goto_first_child_for_byte(offset).is_some() {
7280 if cursor.node().end_byte() == offset {
7281 cursor.goto_next_sibling();
7282 }
7283 }
7284
7285 // Ascend to the smallest ancestor that contains the range and has a task.
7286 loop {
7287 let node = cursor.node();
7288 let node_range = node.byte_range();
7289 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7290
7291 // Check if this node contains our offset
7292 if node_range.start <= offset && node_range.end >= offset {
7293 // If it contains offset, check for task
7294 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7295 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7296 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7297 }
7298 }
7299
7300 if !cursor.goto_parent() {
7301 break;
7302 }
7303 }
7304 None
7305 }
7306
7307 fn render_run_indicator(
7308 &self,
7309 _style: &EditorStyle,
7310 is_active: bool,
7311 row: DisplayRow,
7312 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7313 cx: &mut Context<Self>,
7314 ) -> IconButton {
7315 let color = Color::Muted;
7316 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7317
7318 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7319 .shape(ui::IconButtonShape::Square)
7320 .icon_size(IconSize::XSmall)
7321 .icon_color(color)
7322 .toggle_state(is_active)
7323 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7324 let quick_launch = e.down.button == MouseButton::Left;
7325 window.focus(&editor.focus_handle(cx));
7326 editor.toggle_code_actions(
7327 &ToggleCodeActions {
7328 deployed_from_indicator: Some(row),
7329 quick_launch,
7330 },
7331 window,
7332 cx,
7333 );
7334 }))
7335 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7336 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7337 }))
7338 }
7339
7340 pub fn context_menu_visible(&self) -> bool {
7341 !self.edit_prediction_preview_is_active()
7342 && self
7343 .context_menu
7344 .borrow()
7345 .as_ref()
7346 .map_or(false, |menu| menu.visible())
7347 }
7348
7349 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7350 self.context_menu
7351 .borrow()
7352 .as_ref()
7353 .map(|menu| menu.origin())
7354 }
7355
7356 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7357 self.context_menu_options = Some(options);
7358 }
7359
7360 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7361 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7362
7363 fn render_edit_prediction_popover(
7364 &mut self,
7365 text_bounds: &Bounds<Pixels>,
7366 content_origin: gpui::Point<Pixels>,
7367 right_margin: Pixels,
7368 editor_snapshot: &EditorSnapshot,
7369 visible_row_range: Range<DisplayRow>,
7370 scroll_top: f32,
7371 scroll_bottom: f32,
7372 line_layouts: &[LineWithInvisibles],
7373 line_height: Pixels,
7374 scroll_pixel_position: gpui::Point<Pixels>,
7375 newest_selection_head: Option<DisplayPoint>,
7376 editor_width: Pixels,
7377 style: &EditorStyle,
7378 window: &mut Window,
7379 cx: &mut App,
7380 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7381 if self.mode().is_minimap() {
7382 return None;
7383 }
7384 let active_inline_completion = self.active_inline_completion.as_ref()?;
7385
7386 if self.edit_prediction_visible_in_cursor_popover(true) {
7387 return None;
7388 }
7389
7390 match &active_inline_completion.completion {
7391 InlineCompletion::Move { target, .. } => {
7392 let target_display_point = target.to_display_point(editor_snapshot);
7393
7394 if self.edit_prediction_requires_modifier() {
7395 if !self.edit_prediction_preview_is_active() {
7396 return None;
7397 }
7398
7399 self.render_edit_prediction_modifier_jump_popover(
7400 text_bounds,
7401 content_origin,
7402 visible_row_range,
7403 line_layouts,
7404 line_height,
7405 scroll_pixel_position,
7406 newest_selection_head,
7407 target_display_point,
7408 window,
7409 cx,
7410 )
7411 } else {
7412 self.render_edit_prediction_eager_jump_popover(
7413 text_bounds,
7414 content_origin,
7415 editor_snapshot,
7416 visible_row_range,
7417 scroll_top,
7418 scroll_bottom,
7419 line_height,
7420 scroll_pixel_position,
7421 target_display_point,
7422 editor_width,
7423 window,
7424 cx,
7425 )
7426 }
7427 }
7428 InlineCompletion::Edit {
7429 display_mode: EditDisplayMode::Inline,
7430 ..
7431 } => None,
7432 InlineCompletion::Edit {
7433 display_mode: EditDisplayMode::TabAccept,
7434 edits,
7435 ..
7436 } => {
7437 let range = &edits.first()?.0;
7438 let target_display_point = range.end.to_display_point(editor_snapshot);
7439
7440 self.render_edit_prediction_end_of_line_popover(
7441 "Accept",
7442 editor_snapshot,
7443 visible_row_range,
7444 target_display_point,
7445 line_height,
7446 scroll_pixel_position,
7447 content_origin,
7448 editor_width,
7449 window,
7450 cx,
7451 )
7452 }
7453 InlineCompletion::Edit {
7454 edits,
7455 edit_preview,
7456 display_mode: EditDisplayMode::DiffPopover,
7457 snapshot,
7458 } => self.render_edit_prediction_diff_popover(
7459 text_bounds,
7460 content_origin,
7461 right_margin,
7462 editor_snapshot,
7463 visible_row_range,
7464 line_layouts,
7465 line_height,
7466 scroll_pixel_position,
7467 newest_selection_head,
7468 editor_width,
7469 style,
7470 edits,
7471 edit_preview,
7472 snapshot,
7473 window,
7474 cx,
7475 ),
7476 }
7477 }
7478
7479 fn render_edit_prediction_modifier_jump_popover(
7480 &mut self,
7481 text_bounds: &Bounds<Pixels>,
7482 content_origin: gpui::Point<Pixels>,
7483 visible_row_range: Range<DisplayRow>,
7484 line_layouts: &[LineWithInvisibles],
7485 line_height: Pixels,
7486 scroll_pixel_position: gpui::Point<Pixels>,
7487 newest_selection_head: Option<DisplayPoint>,
7488 target_display_point: DisplayPoint,
7489 window: &mut Window,
7490 cx: &mut App,
7491 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7492 let scrolled_content_origin =
7493 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7494
7495 const SCROLL_PADDING_Y: Pixels = px(12.);
7496
7497 if target_display_point.row() < visible_row_range.start {
7498 return self.render_edit_prediction_scroll_popover(
7499 |_| SCROLL_PADDING_Y,
7500 IconName::ArrowUp,
7501 visible_row_range,
7502 line_layouts,
7503 newest_selection_head,
7504 scrolled_content_origin,
7505 window,
7506 cx,
7507 );
7508 } else if target_display_point.row() >= visible_row_range.end {
7509 return self.render_edit_prediction_scroll_popover(
7510 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7511 IconName::ArrowDown,
7512 visible_row_range,
7513 line_layouts,
7514 newest_selection_head,
7515 scrolled_content_origin,
7516 window,
7517 cx,
7518 );
7519 }
7520
7521 const POLE_WIDTH: Pixels = px(2.);
7522
7523 let line_layout =
7524 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7525 let target_column = target_display_point.column() as usize;
7526
7527 let target_x = line_layout.x_for_index(target_column);
7528 let target_y =
7529 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7530
7531 let flag_on_right = target_x < text_bounds.size.width / 2.;
7532
7533 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7534 border_color.l += 0.001;
7535
7536 let mut element = v_flex()
7537 .items_end()
7538 .when(flag_on_right, |el| el.items_start())
7539 .child(if flag_on_right {
7540 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7541 .rounded_bl(px(0.))
7542 .rounded_tl(px(0.))
7543 .border_l_2()
7544 .border_color(border_color)
7545 } else {
7546 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7547 .rounded_br(px(0.))
7548 .rounded_tr(px(0.))
7549 .border_r_2()
7550 .border_color(border_color)
7551 })
7552 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7553 .into_any();
7554
7555 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7556
7557 let mut origin = scrolled_content_origin + point(target_x, target_y)
7558 - point(
7559 if flag_on_right {
7560 POLE_WIDTH
7561 } else {
7562 size.width - POLE_WIDTH
7563 },
7564 size.height - line_height,
7565 );
7566
7567 origin.x = origin.x.max(content_origin.x);
7568
7569 element.prepaint_at(origin, window, cx);
7570
7571 Some((element, origin))
7572 }
7573
7574 fn render_edit_prediction_scroll_popover(
7575 &mut self,
7576 to_y: impl Fn(Size<Pixels>) -> Pixels,
7577 scroll_icon: IconName,
7578 visible_row_range: Range<DisplayRow>,
7579 line_layouts: &[LineWithInvisibles],
7580 newest_selection_head: Option<DisplayPoint>,
7581 scrolled_content_origin: gpui::Point<Pixels>,
7582 window: &mut Window,
7583 cx: &mut App,
7584 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7585 let mut element = self
7586 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7587 .into_any();
7588
7589 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7590
7591 let cursor = newest_selection_head?;
7592 let cursor_row_layout =
7593 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7594 let cursor_column = cursor.column() as usize;
7595
7596 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7597
7598 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7599
7600 element.prepaint_at(origin, window, cx);
7601 Some((element, origin))
7602 }
7603
7604 fn render_edit_prediction_eager_jump_popover(
7605 &mut self,
7606 text_bounds: &Bounds<Pixels>,
7607 content_origin: gpui::Point<Pixels>,
7608 editor_snapshot: &EditorSnapshot,
7609 visible_row_range: Range<DisplayRow>,
7610 scroll_top: f32,
7611 scroll_bottom: f32,
7612 line_height: Pixels,
7613 scroll_pixel_position: gpui::Point<Pixels>,
7614 target_display_point: DisplayPoint,
7615 editor_width: Pixels,
7616 window: &mut Window,
7617 cx: &mut App,
7618 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7619 if target_display_point.row().as_f32() < scroll_top {
7620 let mut element = self
7621 .render_edit_prediction_line_popover(
7622 "Jump to Edit",
7623 Some(IconName::ArrowUp),
7624 window,
7625 cx,
7626 )?
7627 .into_any();
7628
7629 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7630 let offset = point(
7631 (text_bounds.size.width - size.width) / 2.,
7632 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7633 );
7634
7635 let origin = text_bounds.origin + offset;
7636 element.prepaint_at(origin, window, cx);
7637 Some((element, origin))
7638 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7639 let mut element = self
7640 .render_edit_prediction_line_popover(
7641 "Jump to Edit",
7642 Some(IconName::ArrowDown),
7643 window,
7644 cx,
7645 )?
7646 .into_any();
7647
7648 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7649 let offset = point(
7650 (text_bounds.size.width - size.width) / 2.,
7651 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7652 );
7653
7654 let origin = text_bounds.origin + offset;
7655 element.prepaint_at(origin, window, cx);
7656 Some((element, origin))
7657 } else {
7658 self.render_edit_prediction_end_of_line_popover(
7659 "Jump to Edit",
7660 editor_snapshot,
7661 visible_row_range,
7662 target_display_point,
7663 line_height,
7664 scroll_pixel_position,
7665 content_origin,
7666 editor_width,
7667 window,
7668 cx,
7669 )
7670 }
7671 }
7672
7673 fn render_edit_prediction_end_of_line_popover(
7674 self: &mut Editor,
7675 label: &'static str,
7676 editor_snapshot: &EditorSnapshot,
7677 visible_row_range: Range<DisplayRow>,
7678 target_display_point: DisplayPoint,
7679 line_height: Pixels,
7680 scroll_pixel_position: gpui::Point<Pixels>,
7681 content_origin: gpui::Point<Pixels>,
7682 editor_width: Pixels,
7683 window: &mut Window,
7684 cx: &mut App,
7685 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7686 let target_line_end = DisplayPoint::new(
7687 target_display_point.row(),
7688 editor_snapshot.line_len(target_display_point.row()),
7689 );
7690
7691 let mut element = self
7692 .render_edit_prediction_line_popover(label, None, window, cx)?
7693 .into_any();
7694
7695 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7696
7697 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7698
7699 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7700 let mut origin = start_point
7701 + line_origin
7702 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7703 origin.x = origin.x.max(content_origin.x);
7704
7705 let max_x = content_origin.x + editor_width - size.width;
7706
7707 if origin.x > max_x {
7708 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7709
7710 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7711 origin.y += offset;
7712 IconName::ArrowUp
7713 } else {
7714 origin.y -= offset;
7715 IconName::ArrowDown
7716 };
7717
7718 element = self
7719 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7720 .into_any();
7721
7722 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7723
7724 origin.x = content_origin.x + editor_width - size.width - px(2.);
7725 }
7726
7727 element.prepaint_at(origin, window, cx);
7728 Some((element, origin))
7729 }
7730
7731 fn render_edit_prediction_diff_popover(
7732 self: &Editor,
7733 text_bounds: &Bounds<Pixels>,
7734 content_origin: gpui::Point<Pixels>,
7735 right_margin: Pixels,
7736 editor_snapshot: &EditorSnapshot,
7737 visible_row_range: Range<DisplayRow>,
7738 line_layouts: &[LineWithInvisibles],
7739 line_height: Pixels,
7740 scroll_pixel_position: gpui::Point<Pixels>,
7741 newest_selection_head: Option<DisplayPoint>,
7742 editor_width: Pixels,
7743 style: &EditorStyle,
7744 edits: &Vec<(Range<Anchor>, String)>,
7745 edit_preview: &Option<language::EditPreview>,
7746 snapshot: &language::BufferSnapshot,
7747 window: &mut Window,
7748 cx: &mut App,
7749 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7750 let edit_start = edits
7751 .first()
7752 .unwrap()
7753 .0
7754 .start
7755 .to_display_point(editor_snapshot);
7756 let edit_end = edits
7757 .last()
7758 .unwrap()
7759 .0
7760 .end
7761 .to_display_point(editor_snapshot);
7762
7763 let is_visible = visible_row_range.contains(&edit_start.row())
7764 || visible_row_range.contains(&edit_end.row());
7765 if !is_visible {
7766 return None;
7767 }
7768
7769 let highlighted_edits =
7770 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7771
7772 let styled_text = highlighted_edits.to_styled_text(&style.text);
7773 let line_count = highlighted_edits.text.lines().count();
7774
7775 const BORDER_WIDTH: Pixels = px(1.);
7776
7777 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7778 let has_keybind = keybind.is_some();
7779
7780 let mut element = h_flex()
7781 .items_start()
7782 .child(
7783 h_flex()
7784 .bg(cx.theme().colors().editor_background)
7785 .border(BORDER_WIDTH)
7786 .shadow_sm()
7787 .border_color(cx.theme().colors().border)
7788 .rounded_l_lg()
7789 .when(line_count > 1, |el| el.rounded_br_lg())
7790 .pr_1()
7791 .child(styled_text),
7792 )
7793 .child(
7794 h_flex()
7795 .h(line_height + BORDER_WIDTH * 2.)
7796 .px_1p5()
7797 .gap_1()
7798 // Workaround: For some reason, there's a gap if we don't do this
7799 .ml(-BORDER_WIDTH)
7800 .shadow(smallvec![gpui::BoxShadow {
7801 color: gpui::black().opacity(0.05),
7802 offset: point(px(1.), px(1.)),
7803 blur_radius: px(2.),
7804 spread_radius: px(0.),
7805 }])
7806 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7807 .border(BORDER_WIDTH)
7808 .border_color(cx.theme().colors().border)
7809 .rounded_r_lg()
7810 .id("edit_prediction_diff_popover_keybind")
7811 .when(!has_keybind, |el| {
7812 let status_colors = cx.theme().status();
7813
7814 el.bg(status_colors.error_background)
7815 .border_color(status_colors.error.opacity(0.6))
7816 .child(Icon::new(IconName::Info).color(Color::Error))
7817 .cursor_default()
7818 .hoverable_tooltip(move |_window, cx| {
7819 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7820 })
7821 })
7822 .children(keybind),
7823 )
7824 .into_any();
7825
7826 let longest_row =
7827 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7828 let longest_line_width = if visible_row_range.contains(&longest_row) {
7829 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7830 } else {
7831 layout_line(
7832 longest_row,
7833 editor_snapshot,
7834 style,
7835 editor_width,
7836 |_| false,
7837 window,
7838 cx,
7839 )
7840 .width
7841 };
7842
7843 let viewport_bounds =
7844 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
7845 right: -right_margin,
7846 ..Default::default()
7847 });
7848
7849 let x_after_longest =
7850 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
7851 - scroll_pixel_position.x;
7852
7853 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7854
7855 // Fully visible if it can be displayed within the window (allow overlapping other
7856 // panes). However, this is only allowed if the popover starts within text_bounds.
7857 let can_position_to_the_right = x_after_longest < text_bounds.right()
7858 && x_after_longest + element_bounds.width < viewport_bounds.right();
7859
7860 let mut origin = if can_position_to_the_right {
7861 point(
7862 x_after_longest,
7863 text_bounds.origin.y + edit_start.row().as_f32() * line_height
7864 - scroll_pixel_position.y,
7865 )
7866 } else {
7867 let cursor_row = newest_selection_head.map(|head| head.row());
7868 let above_edit = edit_start
7869 .row()
7870 .0
7871 .checked_sub(line_count as u32)
7872 .map(DisplayRow);
7873 let below_edit = Some(edit_end.row() + 1);
7874 let above_cursor =
7875 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
7876 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
7877
7878 // Place the edit popover adjacent to the edit if there is a location
7879 // available that is onscreen and does not obscure the cursor. Otherwise,
7880 // place it adjacent to the cursor.
7881 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
7882 .into_iter()
7883 .flatten()
7884 .find(|&start_row| {
7885 let end_row = start_row + line_count as u32;
7886 visible_row_range.contains(&start_row)
7887 && visible_row_range.contains(&end_row)
7888 && cursor_row.map_or(true, |cursor_row| {
7889 !((start_row..end_row).contains(&cursor_row))
7890 })
7891 })?;
7892
7893 content_origin
7894 + point(
7895 -scroll_pixel_position.x,
7896 row_target.as_f32() * line_height - scroll_pixel_position.y,
7897 )
7898 };
7899
7900 origin.x -= BORDER_WIDTH;
7901
7902 window.defer_draw(element, origin, 1);
7903
7904 // Do not return an element, since it will already be drawn due to defer_draw.
7905 None
7906 }
7907
7908 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
7909 px(30.)
7910 }
7911
7912 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
7913 if self.read_only(cx) {
7914 cx.theme().players().read_only()
7915 } else {
7916 self.style.as_ref().unwrap().local_player
7917 }
7918 }
7919
7920 fn render_edit_prediction_accept_keybind(
7921 &self,
7922 window: &mut Window,
7923 cx: &App,
7924 ) -> Option<AnyElement> {
7925 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
7926 let accept_keystroke = accept_binding.keystroke()?;
7927
7928 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7929
7930 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
7931 Color::Accent
7932 } else {
7933 Color::Muted
7934 };
7935
7936 h_flex()
7937 .px_0p5()
7938 .when(is_platform_style_mac, |parent| parent.gap_0p5())
7939 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7940 .text_size(TextSize::XSmall.rems(cx))
7941 .child(h_flex().children(ui::render_modifiers(
7942 &accept_keystroke.modifiers,
7943 PlatformStyle::platform(),
7944 Some(modifiers_color),
7945 Some(IconSize::XSmall.rems().into()),
7946 true,
7947 )))
7948 .when(is_platform_style_mac, |parent| {
7949 parent.child(accept_keystroke.key.clone())
7950 })
7951 .when(!is_platform_style_mac, |parent| {
7952 parent.child(
7953 Key::new(
7954 util::capitalize(&accept_keystroke.key),
7955 Some(Color::Default),
7956 )
7957 .size(Some(IconSize::XSmall.rems().into())),
7958 )
7959 })
7960 .into_any()
7961 .into()
7962 }
7963
7964 fn render_edit_prediction_line_popover(
7965 &self,
7966 label: impl Into<SharedString>,
7967 icon: Option<IconName>,
7968 window: &mut Window,
7969 cx: &App,
7970 ) -> Option<Stateful<Div>> {
7971 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
7972
7973 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7974 let has_keybind = keybind.is_some();
7975
7976 let result = h_flex()
7977 .id("ep-line-popover")
7978 .py_0p5()
7979 .pl_1()
7980 .pr(padding_right)
7981 .gap_1()
7982 .rounded_md()
7983 .border_1()
7984 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7985 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7986 .shadow_sm()
7987 .when(!has_keybind, |el| {
7988 let status_colors = cx.theme().status();
7989
7990 el.bg(status_colors.error_background)
7991 .border_color(status_colors.error.opacity(0.6))
7992 .pl_2()
7993 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7994 .cursor_default()
7995 .hoverable_tooltip(move |_window, cx| {
7996 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7997 })
7998 })
7999 .children(keybind)
8000 .child(
8001 Label::new(label)
8002 .size(LabelSize::Small)
8003 .when(!has_keybind, |el| {
8004 el.color(cx.theme().status().error.into()).strikethrough()
8005 }),
8006 )
8007 .when(!has_keybind, |el| {
8008 el.child(
8009 h_flex().ml_1().child(
8010 Icon::new(IconName::Info)
8011 .size(IconSize::Small)
8012 .color(cx.theme().status().error.into()),
8013 ),
8014 )
8015 })
8016 .when_some(icon, |element, icon| {
8017 element.child(
8018 div()
8019 .mt(px(1.5))
8020 .child(Icon::new(icon).size(IconSize::Small)),
8021 )
8022 });
8023
8024 Some(result)
8025 }
8026
8027 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8028 let accent_color = cx.theme().colors().text_accent;
8029 let editor_bg_color = cx.theme().colors().editor_background;
8030 editor_bg_color.blend(accent_color.opacity(0.1))
8031 }
8032
8033 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8034 let accent_color = cx.theme().colors().text_accent;
8035 let editor_bg_color = cx.theme().colors().editor_background;
8036 editor_bg_color.blend(accent_color.opacity(0.6))
8037 }
8038
8039 fn render_edit_prediction_cursor_popover(
8040 &self,
8041 min_width: Pixels,
8042 max_width: Pixels,
8043 cursor_point: Point,
8044 style: &EditorStyle,
8045 accept_keystroke: Option<&gpui::Keystroke>,
8046 _window: &Window,
8047 cx: &mut Context<Editor>,
8048 ) -> Option<AnyElement> {
8049 let provider = self.edit_prediction_provider.as_ref()?;
8050
8051 if provider.provider.needs_terms_acceptance(cx) {
8052 return Some(
8053 h_flex()
8054 .min_w(min_width)
8055 .flex_1()
8056 .px_2()
8057 .py_1()
8058 .gap_3()
8059 .elevation_2(cx)
8060 .hover(|style| style.bg(cx.theme().colors().element_hover))
8061 .id("accept-terms")
8062 .cursor_pointer()
8063 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8064 .on_click(cx.listener(|this, _event, window, cx| {
8065 cx.stop_propagation();
8066 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8067 window.dispatch_action(
8068 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8069 cx,
8070 );
8071 }))
8072 .child(
8073 h_flex()
8074 .flex_1()
8075 .gap_2()
8076 .child(Icon::new(IconName::ZedPredict))
8077 .child(Label::new("Accept Terms of Service"))
8078 .child(div().w_full())
8079 .child(
8080 Icon::new(IconName::ArrowUpRight)
8081 .color(Color::Muted)
8082 .size(IconSize::Small),
8083 )
8084 .into_any_element(),
8085 )
8086 .into_any(),
8087 );
8088 }
8089
8090 let is_refreshing = provider.provider.is_refreshing(cx);
8091
8092 fn pending_completion_container() -> Div {
8093 h_flex()
8094 .h_full()
8095 .flex_1()
8096 .gap_2()
8097 .child(Icon::new(IconName::ZedPredict))
8098 }
8099
8100 let completion = match &self.active_inline_completion {
8101 Some(prediction) => {
8102 if !self.has_visible_completions_menu() {
8103 const RADIUS: Pixels = px(6.);
8104 const BORDER_WIDTH: Pixels = px(1.);
8105
8106 return Some(
8107 h_flex()
8108 .elevation_2(cx)
8109 .border(BORDER_WIDTH)
8110 .border_color(cx.theme().colors().border)
8111 .when(accept_keystroke.is_none(), |el| {
8112 el.border_color(cx.theme().status().error)
8113 })
8114 .rounded(RADIUS)
8115 .rounded_tl(px(0.))
8116 .overflow_hidden()
8117 .child(div().px_1p5().child(match &prediction.completion {
8118 InlineCompletion::Move { target, snapshot } => {
8119 use text::ToPoint as _;
8120 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8121 {
8122 Icon::new(IconName::ZedPredictDown)
8123 } else {
8124 Icon::new(IconName::ZedPredictUp)
8125 }
8126 }
8127 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8128 }))
8129 .child(
8130 h_flex()
8131 .gap_1()
8132 .py_1()
8133 .px_2()
8134 .rounded_r(RADIUS - BORDER_WIDTH)
8135 .border_l_1()
8136 .border_color(cx.theme().colors().border)
8137 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8138 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8139 el.child(
8140 Label::new("Hold")
8141 .size(LabelSize::Small)
8142 .when(accept_keystroke.is_none(), |el| {
8143 el.strikethrough()
8144 })
8145 .line_height_style(LineHeightStyle::UiLabel),
8146 )
8147 })
8148 .id("edit_prediction_cursor_popover_keybind")
8149 .when(accept_keystroke.is_none(), |el| {
8150 let status_colors = cx.theme().status();
8151
8152 el.bg(status_colors.error_background)
8153 .border_color(status_colors.error.opacity(0.6))
8154 .child(Icon::new(IconName::Info).color(Color::Error))
8155 .cursor_default()
8156 .hoverable_tooltip(move |_window, cx| {
8157 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8158 .into()
8159 })
8160 })
8161 .when_some(
8162 accept_keystroke.as_ref(),
8163 |el, accept_keystroke| {
8164 el.child(h_flex().children(ui::render_modifiers(
8165 &accept_keystroke.modifiers,
8166 PlatformStyle::platform(),
8167 Some(Color::Default),
8168 Some(IconSize::XSmall.rems().into()),
8169 false,
8170 )))
8171 },
8172 ),
8173 )
8174 .into_any(),
8175 );
8176 }
8177
8178 self.render_edit_prediction_cursor_popover_preview(
8179 prediction,
8180 cursor_point,
8181 style,
8182 cx,
8183 )?
8184 }
8185
8186 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8187 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8188 stale_completion,
8189 cursor_point,
8190 style,
8191 cx,
8192 )?,
8193
8194 None => {
8195 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8196 }
8197 },
8198
8199 None => pending_completion_container().child(Label::new("No Prediction")),
8200 };
8201
8202 let completion = if is_refreshing {
8203 completion
8204 .with_animation(
8205 "loading-completion",
8206 Animation::new(Duration::from_secs(2))
8207 .repeat()
8208 .with_easing(pulsating_between(0.4, 0.8)),
8209 |label, delta| label.opacity(delta),
8210 )
8211 .into_any_element()
8212 } else {
8213 completion.into_any_element()
8214 };
8215
8216 let has_completion = self.active_inline_completion.is_some();
8217
8218 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8219 Some(
8220 h_flex()
8221 .min_w(min_width)
8222 .max_w(max_width)
8223 .flex_1()
8224 .elevation_2(cx)
8225 .border_color(cx.theme().colors().border)
8226 .child(
8227 div()
8228 .flex_1()
8229 .py_1()
8230 .px_2()
8231 .overflow_hidden()
8232 .child(completion),
8233 )
8234 .when_some(accept_keystroke, |el, accept_keystroke| {
8235 if !accept_keystroke.modifiers.modified() {
8236 return el;
8237 }
8238
8239 el.child(
8240 h_flex()
8241 .h_full()
8242 .border_l_1()
8243 .rounded_r_lg()
8244 .border_color(cx.theme().colors().border)
8245 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8246 .gap_1()
8247 .py_1()
8248 .px_2()
8249 .child(
8250 h_flex()
8251 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8252 .when(is_platform_style_mac, |parent| parent.gap_1())
8253 .child(h_flex().children(ui::render_modifiers(
8254 &accept_keystroke.modifiers,
8255 PlatformStyle::platform(),
8256 Some(if !has_completion {
8257 Color::Muted
8258 } else {
8259 Color::Default
8260 }),
8261 None,
8262 false,
8263 ))),
8264 )
8265 .child(Label::new("Preview").into_any_element())
8266 .opacity(if has_completion { 1.0 } else { 0.4 }),
8267 )
8268 })
8269 .into_any(),
8270 )
8271 }
8272
8273 fn render_edit_prediction_cursor_popover_preview(
8274 &self,
8275 completion: &InlineCompletionState,
8276 cursor_point: Point,
8277 style: &EditorStyle,
8278 cx: &mut Context<Editor>,
8279 ) -> Option<Div> {
8280 use text::ToPoint as _;
8281
8282 fn render_relative_row_jump(
8283 prefix: impl Into<String>,
8284 current_row: u32,
8285 target_row: u32,
8286 ) -> Div {
8287 let (row_diff, arrow) = if target_row < current_row {
8288 (current_row - target_row, IconName::ArrowUp)
8289 } else {
8290 (target_row - current_row, IconName::ArrowDown)
8291 };
8292
8293 h_flex()
8294 .child(
8295 Label::new(format!("{}{}", prefix.into(), row_diff))
8296 .color(Color::Muted)
8297 .size(LabelSize::Small),
8298 )
8299 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8300 }
8301
8302 match &completion.completion {
8303 InlineCompletion::Move {
8304 target, snapshot, ..
8305 } => Some(
8306 h_flex()
8307 .px_2()
8308 .gap_2()
8309 .flex_1()
8310 .child(
8311 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8312 Icon::new(IconName::ZedPredictDown)
8313 } else {
8314 Icon::new(IconName::ZedPredictUp)
8315 },
8316 )
8317 .child(Label::new("Jump to Edit")),
8318 ),
8319
8320 InlineCompletion::Edit {
8321 edits,
8322 edit_preview,
8323 snapshot,
8324 display_mode: _,
8325 } => {
8326 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8327
8328 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8329 &snapshot,
8330 &edits,
8331 edit_preview.as_ref()?,
8332 true,
8333 cx,
8334 )
8335 .first_line_preview();
8336
8337 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8338 .with_default_highlights(&style.text, highlighted_edits.highlights);
8339
8340 let preview = h_flex()
8341 .gap_1()
8342 .min_w_16()
8343 .child(styled_text)
8344 .when(has_more_lines, |parent| parent.child("…"));
8345
8346 let left = if first_edit_row != cursor_point.row {
8347 render_relative_row_jump("", cursor_point.row, first_edit_row)
8348 .into_any_element()
8349 } else {
8350 Icon::new(IconName::ZedPredict).into_any_element()
8351 };
8352
8353 Some(
8354 h_flex()
8355 .h_full()
8356 .flex_1()
8357 .gap_2()
8358 .pr_1()
8359 .overflow_x_hidden()
8360 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8361 .child(left)
8362 .child(preview),
8363 )
8364 }
8365 }
8366 }
8367
8368 fn render_context_menu(
8369 &self,
8370 style: &EditorStyle,
8371 max_height_in_lines: u32,
8372 window: &mut Window,
8373 cx: &mut Context<Editor>,
8374 ) -> Option<AnyElement> {
8375 let menu = self.context_menu.borrow();
8376 let menu = menu.as_ref()?;
8377 if !menu.visible() {
8378 return None;
8379 };
8380 Some(menu.render(style, max_height_in_lines, window, cx))
8381 }
8382
8383 fn render_context_menu_aside(
8384 &mut self,
8385 max_size: Size<Pixels>,
8386 window: &mut Window,
8387 cx: &mut Context<Editor>,
8388 ) -> Option<AnyElement> {
8389 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8390 if menu.visible() {
8391 menu.render_aside(self, max_size, window, cx)
8392 } else {
8393 None
8394 }
8395 })
8396 }
8397
8398 fn hide_context_menu(
8399 &mut self,
8400 window: &mut Window,
8401 cx: &mut Context<Self>,
8402 ) -> Option<CodeContextMenu> {
8403 cx.notify();
8404 self.completion_tasks.clear();
8405 let context_menu = self.context_menu.borrow_mut().take();
8406 self.stale_inline_completion_in_menu.take();
8407 self.update_visible_inline_completion(window, cx);
8408 context_menu
8409 }
8410
8411 fn show_snippet_choices(
8412 &mut self,
8413 choices: &Vec<String>,
8414 selection: Range<Anchor>,
8415 cx: &mut Context<Self>,
8416 ) {
8417 if selection.start.buffer_id.is_none() {
8418 return;
8419 }
8420 let buffer_id = selection.start.buffer_id.unwrap();
8421 let buffer = self.buffer().read(cx).buffer(buffer_id);
8422 let id = post_inc(&mut self.next_completion_id);
8423 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8424
8425 if let Some(buffer) = buffer {
8426 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8427 CompletionsMenu::new_snippet_choices(
8428 id,
8429 true,
8430 choices,
8431 selection,
8432 buffer,
8433 snippet_sort_order,
8434 ),
8435 ));
8436 }
8437 }
8438
8439 pub fn insert_snippet(
8440 &mut self,
8441 insertion_ranges: &[Range<usize>],
8442 snippet: Snippet,
8443 window: &mut Window,
8444 cx: &mut Context<Self>,
8445 ) -> Result<()> {
8446 struct Tabstop<T> {
8447 is_end_tabstop: bool,
8448 ranges: Vec<Range<T>>,
8449 choices: Option<Vec<String>>,
8450 }
8451
8452 let tabstops = self.buffer.update(cx, |buffer, cx| {
8453 let snippet_text: Arc<str> = snippet.text.clone().into();
8454 let edits = insertion_ranges
8455 .iter()
8456 .cloned()
8457 .map(|range| (range, snippet_text.clone()));
8458 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8459
8460 let snapshot = &*buffer.read(cx);
8461 let snippet = &snippet;
8462 snippet
8463 .tabstops
8464 .iter()
8465 .map(|tabstop| {
8466 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8467 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8468 });
8469 let mut tabstop_ranges = tabstop
8470 .ranges
8471 .iter()
8472 .flat_map(|tabstop_range| {
8473 let mut delta = 0_isize;
8474 insertion_ranges.iter().map(move |insertion_range| {
8475 let insertion_start = insertion_range.start as isize + delta;
8476 delta +=
8477 snippet.text.len() as isize - insertion_range.len() as isize;
8478
8479 let start = ((insertion_start + tabstop_range.start) as usize)
8480 .min(snapshot.len());
8481 let end = ((insertion_start + tabstop_range.end) as usize)
8482 .min(snapshot.len());
8483 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8484 })
8485 })
8486 .collect::<Vec<_>>();
8487 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8488
8489 Tabstop {
8490 is_end_tabstop,
8491 ranges: tabstop_ranges,
8492 choices: tabstop.choices.clone(),
8493 }
8494 })
8495 .collect::<Vec<_>>()
8496 });
8497 if let Some(tabstop) = tabstops.first() {
8498 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8499 s.select_ranges(tabstop.ranges.iter().cloned());
8500 });
8501
8502 if let Some(choices) = &tabstop.choices {
8503 if let Some(selection) = tabstop.ranges.first() {
8504 self.show_snippet_choices(choices, selection.clone(), cx)
8505 }
8506 }
8507
8508 // If we're already at the last tabstop and it's at the end of the snippet,
8509 // we're done, we don't need to keep the state around.
8510 if !tabstop.is_end_tabstop {
8511 let choices = tabstops
8512 .iter()
8513 .map(|tabstop| tabstop.choices.clone())
8514 .collect();
8515
8516 let ranges = tabstops
8517 .into_iter()
8518 .map(|tabstop| tabstop.ranges)
8519 .collect::<Vec<_>>();
8520
8521 self.snippet_stack.push(SnippetState {
8522 active_index: 0,
8523 ranges,
8524 choices,
8525 });
8526 }
8527
8528 // Check whether the just-entered snippet ends with an auto-closable bracket.
8529 if self.autoclose_regions.is_empty() {
8530 let snapshot = self.buffer.read(cx).snapshot(cx);
8531 for selection in &mut self.selections.all::<Point>(cx) {
8532 let selection_head = selection.head();
8533 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8534 continue;
8535 };
8536
8537 let mut bracket_pair = None;
8538 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8539 let prev_chars = snapshot
8540 .reversed_chars_at(selection_head)
8541 .collect::<String>();
8542 for (pair, enabled) in scope.brackets() {
8543 if enabled
8544 && pair.close
8545 && prev_chars.starts_with(pair.start.as_str())
8546 && next_chars.starts_with(pair.end.as_str())
8547 {
8548 bracket_pair = Some(pair.clone());
8549 break;
8550 }
8551 }
8552 if let Some(pair) = bracket_pair {
8553 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8554 let autoclose_enabled =
8555 self.use_autoclose && snapshot_settings.use_autoclose;
8556 if autoclose_enabled {
8557 let start = snapshot.anchor_after(selection_head);
8558 let end = snapshot.anchor_after(selection_head);
8559 self.autoclose_regions.push(AutocloseRegion {
8560 selection_id: selection.id,
8561 range: start..end,
8562 pair,
8563 });
8564 }
8565 }
8566 }
8567 }
8568 }
8569 Ok(())
8570 }
8571
8572 pub fn move_to_next_snippet_tabstop(
8573 &mut self,
8574 window: &mut Window,
8575 cx: &mut Context<Self>,
8576 ) -> bool {
8577 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8578 }
8579
8580 pub fn move_to_prev_snippet_tabstop(
8581 &mut self,
8582 window: &mut Window,
8583 cx: &mut Context<Self>,
8584 ) -> bool {
8585 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8586 }
8587
8588 pub fn move_to_snippet_tabstop(
8589 &mut self,
8590 bias: Bias,
8591 window: &mut Window,
8592 cx: &mut Context<Self>,
8593 ) -> bool {
8594 if let Some(mut snippet) = self.snippet_stack.pop() {
8595 match bias {
8596 Bias::Left => {
8597 if snippet.active_index > 0 {
8598 snippet.active_index -= 1;
8599 } else {
8600 self.snippet_stack.push(snippet);
8601 return false;
8602 }
8603 }
8604 Bias::Right => {
8605 if snippet.active_index + 1 < snippet.ranges.len() {
8606 snippet.active_index += 1;
8607 } else {
8608 self.snippet_stack.push(snippet);
8609 return false;
8610 }
8611 }
8612 }
8613 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8614 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8615 s.select_anchor_ranges(current_ranges.iter().cloned())
8616 });
8617
8618 if let Some(choices) = &snippet.choices[snippet.active_index] {
8619 if let Some(selection) = current_ranges.first() {
8620 self.show_snippet_choices(&choices, selection.clone(), cx);
8621 }
8622 }
8623
8624 // If snippet state is not at the last tabstop, push it back on the stack
8625 if snippet.active_index + 1 < snippet.ranges.len() {
8626 self.snippet_stack.push(snippet);
8627 }
8628 return true;
8629 }
8630 }
8631
8632 false
8633 }
8634
8635 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8636 self.transact(window, cx, |this, window, cx| {
8637 this.select_all(&SelectAll, window, cx);
8638 this.insert("", window, cx);
8639 });
8640 }
8641
8642 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8643 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8644 self.transact(window, cx, |this, window, cx| {
8645 this.select_autoclose_pair(window, cx);
8646 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8647 if !this.linked_edit_ranges.is_empty() {
8648 let selections = this.selections.all::<MultiBufferPoint>(cx);
8649 let snapshot = this.buffer.read(cx).snapshot(cx);
8650
8651 for selection in selections.iter() {
8652 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8653 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8654 if selection_start.buffer_id != selection_end.buffer_id {
8655 continue;
8656 }
8657 if let Some(ranges) =
8658 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8659 {
8660 for (buffer, entries) in ranges {
8661 linked_ranges.entry(buffer).or_default().extend(entries);
8662 }
8663 }
8664 }
8665 }
8666
8667 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8668 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8669 for selection in &mut selections {
8670 if selection.is_empty() {
8671 let old_head = selection.head();
8672 let mut new_head =
8673 movement::left(&display_map, old_head.to_display_point(&display_map))
8674 .to_point(&display_map);
8675 if let Some((buffer, line_buffer_range)) = display_map
8676 .buffer_snapshot
8677 .buffer_line_for_row(MultiBufferRow(old_head.row))
8678 {
8679 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8680 let indent_len = match indent_size.kind {
8681 IndentKind::Space => {
8682 buffer.settings_at(line_buffer_range.start, cx).tab_size
8683 }
8684 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8685 };
8686 if old_head.column <= indent_size.len && old_head.column > 0 {
8687 let indent_len = indent_len.get();
8688 new_head = cmp::min(
8689 new_head,
8690 MultiBufferPoint::new(
8691 old_head.row,
8692 ((old_head.column - 1) / indent_len) * indent_len,
8693 ),
8694 );
8695 }
8696 }
8697
8698 selection.set_head(new_head, SelectionGoal::None);
8699 }
8700 }
8701
8702 this.signature_help_state.set_backspace_pressed(true);
8703 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8704 s.select(selections)
8705 });
8706 this.insert("", window, cx);
8707 let empty_str: Arc<str> = Arc::from("");
8708 for (buffer, edits) in linked_ranges {
8709 let snapshot = buffer.read(cx).snapshot();
8710 use text::ToPoint as TP;
8711
8712 let edits = edits
8713 .into_iter()
8714 .map(|range| {
8715 let end_point = TP::to_point(&range.end, &snapshot);
8716 let mut start_point = TP::to_point(&range.start, &snapshot);
8717
8718 if end_point == start_point {
8719 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8720 .saturating_sub(1);
8721 start_point =
8722 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8723 };
8724
8725 (start_point..end_point, empty_str.clone())
8726 })
8727 .sorted_by_key(|(range, _)| range.start)
8728 .collect::<Vec<_>>();
8729 buffer.update(cx, |this, cx| {
8730 this.edit(edits, None, cx);
8731 })
8732 }
8733 this.refresh_inline_completion(true, false, window, cx);
8734 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8735 });
8736 }
8737
8738 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8739 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8740 self.transact(window, cx, |this, window, cx| {
8741 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8742 s.move_with(|map, selection| {
8743 if selection.is_empty() {
8744 let cursor = movement::right(map, selection.head());
8745 selection.end = cursor;
8746 selection.reversed = true;
8747 selection.goal = SelectionGoal::None;
8748 }
8749 })
8750 });
8751 this.insert("", window, cx);
8752 this.refresh_inline_completion(true, false, window, cx);
8753 });
8754 }
8755
8756 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8757 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8758 if self.move_to_prev_snippet_tabstop(window, cx) {
8759 return;
8760 }
8761 self.outdent(&Outdent, window, cx);
8762 }
8763
8764 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8765 if self.move_to_next_snippet_tabstop(window, cx) {
8766 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8767 return;
8768 }
8769 if self.read_only(cx) {
8770 return;
8771 }
8772 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8773 let mut selections = self.selections.all_adjusted(cx);
8774 let buffer = self.buffer.read(cx);
8775 let snapshot = buffer.snapshot(cx);
8776 let rows_iter = selections.iter().map(|s| s.head().row);
8777 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8778
8779 let has_some_cursor_in_whitespace = selections
8780 .iter()
8781 .filter(|selection| selection.is_empty())
8782 .any(|selection| {
8783 let cursor = selection.head();
8784 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8785 cursor.column < current_indent.len
8786 });
8787
8788 let mut edits = Vec::new();
8789 let mut prev_edited_row = 0;
8790 let mut row_delta = 0;
8791 for selection in &mut selections {
8792 if selection.start.row != prev_edited_row {
8793 row_delta = 0;
8794 }
8795 prev_edited_row = selection.end.row;
8796
8797 // If the selection is non-empty, then increase the indentation of the selected lines.
8798 if !selection.is_empty() {
8799 row_delta =
8800 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8801 continue;
8802 }
8803
8804 let cursor = selection.head();
8805 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8806 if let Some(suggested_indent) =
8807 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8808 {
8809 // Don't do anything if already at suggested indent
8810 // and there is any other cursor which is not
8811 if has_some_cursor_in_whitespace
8812 && cursor.column == current_indent.len
8813 && current_indent.len == suggested_indent.len
8814 {
8815 continue;
8816 }
8817
8818 // Adjust line and move cursor to suggested indent
8819 // if cursor is not at suggested indent
8820 if cursor.column < suggested_indent.len
8821 && cursor.column <= current_indent.len
8822 && current_indent.len <= suggested_indent.len
8823 {
8824 selection.start = Point::new(cursor.row, suggested_indent.len);
8825 selection.end = selection.start;
8826 if row_delta == 0 {
8827 edits.extend(Buffer::edit_for_indent_size_adjustment(
8828 cursor.row,
8829 current_indent,
8830 suggested_indent,
8831 ));
8832 row_delta = suggested_indent.len - current_indent.len;
8833 }
8834 continue;
8835 }
8836
8837 // If current indent is more than suggested indent
8838 // only move cursor to current indent and skip indent
8839 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
8840 selection.start = Point::new(cursor.row, current_indent.len);
8841 selection.end = selection.start;
8842 continue;
8843 }
8844 }
8845
8846 // Otherwise, insert a hard or soft tab.
8847 let settings = buffer.language_settings_at(cursor, cx);
8848 let tab_size = if settings.hard_tabs {
8849 IndentSize::tab()
8850 } else {
8851 let tab_size = settings.tab_size.get();
8852 let indent_remainder = snapshot
8853 .text_for_range(Point::new(cursor.row, 0)..cursor)
8854 .flat_map(str::chars)
8855 .fold(row_delta % tab_size, |counter: u32, c| {
8856 if c == '\t' {
8857 0
8858 } else {
8859 (counter + 1) % tab_size
8860 }
8861 });
8862
8863 let chars_to_next_tab_stop = tab_size - indent_remainder;
8864 IndentSize::spaces(chars_to_next_tab_stop)
8865 };
8866 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
8867 selection.end = selection.start;
8868 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
8869 row_delta += tab_size.len;
8870 }
8871
8872 self.transact(window, cx, |this, window, cx| {
8873 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8874 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8875 s.select(selections)
8876 });
8877 this.refresh_inline_completion(true, false, window, cx);
8878 });
8879 }
8880
8881 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
8882 if self.read_only(cx) {
8883 return;
8884 }
8885 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8886 let mut selections = self.selections.all::<Point>(cx);
8887 let mut prev_edited_row = 0;
8888 let mut row_delta = 0;
8889 let mut edits = Vec::new();
8890 let buffer = self.buffer.read(cx);
8891 let snapshot = buffer.snapshot(cx);
8892 for selection in &mut selections {
8893 if selection.start.row != prev_edited_row {
8894 row_delta = 0;
8895 }
8896 prev_edited_row = selection.end.row;
8897
8898 row_delta =
8899 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8900 }
8901
8902 self.transact(window, cx, |this, window, cx| {
8903 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8904 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8905 s.select(selections)
8906 });
8907 });
8908 }
8909
8910 fn indent_selection(
8911 buffer: &MultiBuffer,
8912 snapshot: &MultiBufferSnapshot,
8913 selection: &mut Selection<Point>,
8914 edits: &mut Vec<(Range<Point>, String)>,
8915 delta_for_start_row: u32,
8916 cx: &App,
8917 ) -> u32 {
8918 let settings = buffer.language_settings_at(selection.start, cx);
8919 let tab_size = settings.tab_size.get();
8920 let indent_kind = if settings.hard_tabs {
8921 IndentKind::Tab
8922 } else {
8923 IndentKind::Space
8924 };
8925 let mut start_row = selection.start.row;
8926 let mut end_row = selection.end.row + 1;
8927
8928 // If a selection ends at the beginning of a line, don't indent
8929 // that last line.
8930 if selection.end.column == 0 && selection.end.row > selection.start.row {
8931 end_row -= 1;
8932 }
8933
8934 // Avoid re-indenting a row that has already been indented by a
8935 // previous selection, but still update this selection's column
8936 // to reflect that indentation.
8937 if delta_for_start_row > 0 {
8938 start_row += 1;
8939 selection.start.column += delta_for_start_row;
8940 if selection.end.row == selection.start.row {
8941 selection.end.column += delta_for_start_row;
8942 }
8943 }
8944
8945 let mut delta_for_end_row = 0;
8946 let has_multiple_rows = start_row + 1 != end_row;
8947 for row in start_row..end_row {
8948 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
8949 let indent_delta = match (current_indent.kind, indent_kind) {
8950 (IndentKind::Space, IndentKind::Space) => {
8951 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
8952 IndentSize::spaces(columns_to_next_tab_stop)
8953 }
8954 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
8955 (_, IndentKind::Tab) => IndentSize::tab(),
8956 };
8957
8958 let start = if has_multiple_rows || current_indent.len < selection.start.column {
8959 0
8960 } else {
8961 selection.start.column
8962 };
8963 let row_start = Point::new(row, start);
8964 edits.push((
8965 row_start..row_start,
8966 indent_delta.chars().collect::<String>(),
8967 ));
8968
8969 // Update this selection's endpoints to reflect the indentation.
8970 if row == selection.start.row {
8971 selection.start.column += indent_delta.len;
8972 }
8973 if row == selection.end.row {
8974 selection.end.column += indent_delta.len;
8975 delta_for_end_row = indent_delta.len;
8976 }
8977 }
8978
8979 if selection.start.row == selection.end.row {
8980 delta_for_start_row + delta_for_end_row
8981 } else {
8982 delta_for_end_row
8983 }
8984 }
8985
8986 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
8987 if self.read_only(cx) {
8988 return;
8989 }
8990 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8991 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8992 let selections = self.selections.all::<Point>(cx);
8993 let mut deletion_ranges = Vec::new();
8994 let mut last_outdent = None;
8995 {
8996 let buffer = self.buffer.read(cx);
8997 let snapshot = buffer.snapshot(cx);
8998 for selection in &selections {
8999 let settings = buffer.language_settings_at(selection.start, cx);
9000 let tab_size = settings.tab_size.get();
9001 let mut rows = selection.spanned_rows(false, &display_map);
9002
9003 // Avoid re-outdenting a row that has already been outdented by a
9004 // previous selection.
9005 if let Some(last_row) = last_outdent {
9006 if last_row == rows.start {
9007 rows.start = rows.start.next_row();
9008 }
9009 }
9010 let has_multiple_rows = rows.len() > 1;
9011 for row in rows.iter_rows() {
9012 let indent_size = snapshot.indent_size_for_line(row);
9013 if indent_size.len > 0 {
9014 let deletion_len = match indent_size.kind {
9015 IndentKind::Space => {
9016 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9017 if columns_to_prev_tab_stop == 0 {
9018 tab_size
9019 } else {
9020 columns_to_prev_tab_stop
9021 }
9022 }
9023 IndentKind::Tab => 1,
9024 };
9025 let start = if has_multiple_rows
9026 || deletion_len > selection.start.column
9027 || indent_size.len < selection.start.column
9028 {
9029 0
9030 } else {
9031 selection.start.column - deletion_len
9032 };
9033 deletion_ranges.push(
9034 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9035 );
9036 last_outdent = Some(row);
9037 }
9038 }
9039 }
9040 }
9041
9042 self.transact(window, cx, |this, window, cx| {
9043 this.buffer.update(cx, |buffer, cx| {
9044 let empty_str: Arc<str> = Arc::default();
9045 buffer.edit(
9046 deletion_ranges
9047 .into_iter()
9048 .map(|range| (range, empty_str.clone())),
9049 None,
9050 cx,
9051 );
9052 });
9053 let selections = this.selections.all::<usize>(cx);
9054 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9055 s.select(selections)
9056 });
9057 });
9058 }
9059
9060 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9061 if self.read_only(cx) {
9062 return;
9063 }
9064 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9065 let selections = self
9066 .selections
9067 .all::<usize>(cx)
9068 .into_iter()
9069 .map(|s| s.range());
9070
9071 self.transact(window, cx, |this, window, cx| {
9072 this.buffer.update(cx, |buffer, cx| {
9073 buffer.autoindent_ranges(selections, cx);
9074 });
9075 let selections = this.selections.all::<usize>(cx);
9076 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9077 s.select(selections)
9078 });
9079 });
9080 }
9081
9082 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9083 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9084 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9085 let selections = self.selections.all::<Point>(cx);
9086
9087 let mut new_cursors = Vec::new();
9088 let mut edit_ranges = Vec::new();
9089 let mut selections = selections.iter().peekable();
9090 while let Some(selection) = selections.next() {
9091 let mut rows = selection.spanned_rows(false, &display_map);
9092 let goal_display_column = selection.head().to_display_point(&display_map).column();
9093
9094 // Accumulate contiguous regions of rows that we want to delete.
9095 while let Some(next_selection) = selections.peek() {
9096 let next_rows = next_selection.spanned_rows(false, &display_map);
9097 if next_rows.start <= rows.end {
9098 rows.end = next_rows.end;
9099 selections.next().unwrap();
9100 } else {
9101 break;
9102 }
9103 }
9104
9105 let buffer = &display_map.buffer_snapshot;
9106 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9107 let edit_end;
9108 let cursor_buffer_row;
9109 if buffer.max_point().row >= rows.end.0 {
9110 // If there's a line after the range, delete the \n from the end of the row range
9111 // and position the cursor on the next line.
9112 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9113 cursor_buffer_row = rows.end;
9114 } else {
9115 // If there isn't a line after the range, delete the \n from the line before the
9116 // start of the row range and position the cursor there.
9117 edit_start = edit_start.saturating_sub(1);
9118 edit_end = buffer.len();
9119 cursor_buffer_row = rows.start.previous_row();
9120 }
9121
9122 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9123 *cursor.column_mut() =
9124 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9125
9126 new_cursors.push((
9127 selection.id,
9128 buffer.anchor_after(cursor.to_point(&display_map)),
9129 ));
9130 edit_ranges.push(edit_start..edit_end);
9131 }
9132
9133 self.transact(window, cx, |this, window, cx| {
9134 let buffer = this.buffer.update(cx, |buffer, cx| {
9135 let empty_str: Arc<str> = Arc::default();
9136 buffer.edit(
9137 edit_ranges
9138 .into_iter()
9139 .map(|range| (range, empty_str.clone())),
9140 None,
9141 cx,
9142 );
9143 buffer.snapshot(cx)
9144 });
9145 let new_selections = new_cursors
9146 .into_iter()
9147 .map(|(id, cursor)| {
9148 let cursor = cursor.to_point(&buffer);
9149 Selection {
9150 id,
9151 start: cursor,
9152 end: cursor,
9153 reversed: false,
9154 goal: SelectionGoal::None,
9155 }
9156 })
9157 .collect();
9158
9159 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9160 s.select(new_selections);
9161 });
9162 });
9163 }
9164
9165 pub fn join_lines_impl(
9166 &mut self,
9167 insert_whitespace: bool,
9168 window: &mut Window,
9169 cx: &mut Context<Self>,
9170 ) {
9171 if self.read_only(cx) {
9172 return;
9173 }
9174 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9175 for selection in self.selections.all::<Point>(cx) {
9176 let start = MultiBufferRow(selection.start.row);
9177 // Treat single line selections as if they include the next line. Otherwise this action
9178 // would do nothing for single line selections individual cursors.
9179 let end = if selection.start.row == selection.end.row {
9180 MultiBufferRow(selection.start.row + 1)
9181 } else {
9182 MultiBufferRow(selection.end.row)
9183 };
9184
9185 if let Some(last_row_range) = row_ranges.last_mut() {
9186 if start <= last_row_range.end {
9187 last_row_range.end = end;
9188 continue;
9189 }
9190 }
9191 row_ranges.push(start..end);
9192 }
9193
9194 let snapshot = self.buffer.read(cx).snapshot(cx);
9195 let mut cursor_positions = Vec::new();
9196 for row_range in &row_ranges {
9197 let anchor = snapshot.anchor_before(Point::new(
9198 row_range.end.previous_row().0,
9199 snapshot.line_len(row_range.end.previous_row()),
9200 ));
9201 cursor_positions.push(anchor..anchor);
9202 }
9203
9204 self.transact(window, cx, |this, window, cx| {
9205 for row_range in row_ranges.into_iter().rev() {
9206 for row in row_range.iter_rows().rev() {
9207 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9208 let next_line_row = row.next_row();
9209 let indent = snapshot.indent_size_for_line(next_line_row);
9210 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9211
9212 let replace =
9213 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9214 " "
9215 } else {
9216 ""
9217 };
9218
9219 this.buffer.update(cx, |buffer, cx| {
9220 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9221 });
9222 }
9223 }
9224
9225 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9226 s.select_anchor_ranges(cursor_positions)
9227 });
9228 });
9229 }
9230
9231 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9232 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9233 self.join_lines_impl(true, window, cx);
9234 }
9235
9236 pub fn sort_lines_case_sensitive(
9237 &mut self,
9238 _: &SortLinesCaseSensitive,
9239 window: &mut Window,
9240 cx: &mut Context<Self>,
9241 ) {
9242 self.manipulate_lines(window, cx, |lines| lines.sort())
9243 }
9244
9245 pub fn sort_lines_case_insensitive(
9246 &mut self,
9247 _: &SortLinesCaseInsensitive,
9248 window: &mut Window,
9249 cx: &mut Context<Self>,
9250 ) {
9251 self.manipulate_lines(window, cx, |lines| {
9252 lines.sort_by_key(|line| line.to_lowercase())
9253 })
9254 }
9255
9256 pub fn unique_lines_case_insensitive(
9257 &mut self,
9258 _: &UniqueLinesCaseInsensitive,
9259 window: &mut Window,
9260 cx: &mut Context<Self>,
9261 ) {
9262 self.manipulate_lines(window, cx, |lines| {
9263 let mut seen = HashSet::default();
9264 lines.retain(|line| seen.insert(line.to_lowercase()));
9265 })
9266 }
9267
9268 pub fn unique_lines_case_sensitive(
9269 &mut self,
9270 _: &UniqueLinesCaseSensitive,
9271 window: &mut Window,
9272 cx: &mut Context<Self>,
9273 ) {
9274 self.manipulate_lines(window, cx, |lines| {
9275 let mut seen = HashSet::default();
9276 lines.retain(|line| seen.insert(*line));
9277 })
9278 }
9279
9280 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9281 let Some(project) = self.project.clone() else {
9282 return;
9283 };
9284 self.reload(project, window, cx)
9285 .detach_and_notify_err(window, cx);
9286 }
9287
9288 pub fn restore_file(
9289 &mut self,
9290 _: &::git::RestoreFile,
9291 window: &mut Window,
9292 cx: &mut Context<Self>,
9293 ) {
9294 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9295 let mut buffer_ids = HashSet::default();
9296 let snapshot = self.buffer().read(cx).snapshot(cx);
9297 for selection in self.selections.all::<usize>(cx) {
9298 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9299 }
9300
9301 let buffer = self.buffer().read(cx);
9302 let ranges = buffer_ids
9303 .into_iter()
9304 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9305 .collect::<Vec<_>>();
9306
9307 self.restore_hunks_in_ranges(ranges, window, cx);
9308 }
9309
9310 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9311 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9312 let selections = self
9313 .selections
9314 .all(cx)
9315 .into_iter()
9316 .map(|s| s.range())
9317 .collect();
9318 self.restore_hunks_in_ranges(selections, window, cx);
9319 }
9320
9321 pub fn restore_hunks_in_ranges(
9322 &mut self,
9323 ranges: Vec<Range<Point>>,
9324 window: &mut Window,
9325 cx: &mut Context<Editor>,
9326 ) {
9327 let mut revert_changes = HashMap::default();
9328 let chunk_by = self
9329 .snapshot(window, cx)
9330 .hunks_for_ranges(ranges)
9331 .into_iter()
9332 .chunk_by(|hunk| hunk.buffer_id);
9333 for (buffer_id, hunks) in &chunk_by {
9334 let hunks = hunks.collect::<Vec<_>>();
9335 for hunk in &hunks {
9336 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9337 }
9338 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9339 }
9340 drop(chunk_by);
9341 if !revert_changes.is_empty() {
9342 self.transact(window, cx, |editor, window, cx| {
9343 editor.restore(revert_changes, window, cx);
9344 });
9345 }
9346 }
9347
9348 pub fn open_active_item_in_terminal(
9349 &mut self,
9350 _: &OpenInTerminal,
9351 window: &mut Window,
9352 cx: &mut Context<Self>,
9353 ) {
9354 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9355 let project_path = buffer.read(cx).project_path(cx)?;
9356 let project = self.project.as_ref()?.read(cx);
9357 let entry = project.entry_for_path(&project_path, cx)?;
9358 let parent = match &entry.canonical_path {
9359 Some(canonical_path) => canonical_path.to_path_buf(),
9360 None => project.absolute_path(&project_path, cx)?,
9361 }
9362 .parent()?
9363 .to_path_buf();
9364 Some(parent)
9365 }) {
9366 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9367 }
9368 }
9369
9370 fn set_breakpoint_context_menu(
9371 &mut self,
9372 display_row: DisplayRow,
9373 position: Option<Anchor>,
9374 clicked_point: gpui::Point<Pixels>,
9375 window: &mut Window,
9376 cx: &mut Context<Self>,
9377 ) {
9378 if !cx.has_flag::<DebuggerFeatureFlag>() {
9379 return;
9380 }
9381 let source = self
9382 .buffer
9383 .read(cx)
9384 .snapshot(cx)
9385 .anchor_before(Point::new(display_row.0, 0u32));
9386
9387 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9388
9389 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9390 self,
9391 source,
9392 clicked_point,
9393 context_menu,
9394 window,
9395 cx,
9396 );
9397 }
9398
9399 fn add_edit_breakpoint_block(
9400 &mut self,
9401 anchor: Anchor,
9402 breakpoint: &Breakpoint,
9403 edit_action: BreakpointPromptEditAction,
9404 window: &mut Window,
9405 cx: &mut Context<Self>,
9406 ) {
9407 let weak_editor = cx.weak_entity();
9408 let bp_prompt = cx.new(|cx| {
9409 BreakpointPromptEditor::new(
9410 weak_editor,
9411 anchor,
9412 breakpoint.clone(),
9413 edit_action,
9414 window,
9415 cx,
9416 )
9417 });
9418
9419 let height = bp_prompt.update(cx, |this, cx| {
9420 this.prompt
9421 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9422 });
9423 let cloned_prompt = bp_prompt.clone();
9424 let blocks = vec![BlockProperties {
9425 style: BlockStyle::Sticky,
9426 placement: BlockPlacement::Above(anchor),
9427 height: Some(height),
9428 render: Arc::new(move |cx| {
9429 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9430 cloned_prompt.clone().into_any_element()
9431 }),
9432 priority: 0,
9433 render_in_minimap: true,
9434 }];
9435
9436 let focus_handle = bp_prompt.focus_handle(cx);
9437 window.focus(&focus_handle);
9438
9439 let block_ids = self.insert_blocks(blocks, None, cx);
9440 bp_prompt.update(cx, |prompt, _| {
9441 prompt.add_block_ids(block_ids);
9442 });
9443 }
9444
9445 pub(crate) fn breakpoint_at_row(
9446 &self,
9447 row: u32,
9448 window: &mut Window,
9449 cx: &mut Context<Self>,
9450 ) -> Option<(Anchor, Breakpoint)> {
9451 let snapshot = self.snapshot(window, cx);
9452 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9453
9454 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9455 }
9456
9457 pub(crate) fn breakpoint_at_anchor(
9458 &self,
9459 breakpoint_position: Anchor,
9460 snapshot: &EditorSnapshot,
9461 cx: &mut Context<Self>,
9462 ) -> Option<(Anchor, Breakpoint)> {
9463 let project = self.project.clone()?;
9464
9465 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9466 snapshot
9467 .buffer_snapshot
9468 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9469 })?;
9470
9471 let enclosing_excerpt = breakpoint_position.excerpt_id;
9472 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
9473 let buffer_snapshot = buffer.read(cx).snapshot();
9474
9475 let row = buffer_snapshot
9476 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9477 .row;
9478
9479 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9480 let anchor_end = snapshot
9481 .buffer_snapshot
9482 .anchor_after(Point::new(row, line_len));
9483
9484 let bp = self
9485 .breakpoint_store
9486 .as_ref()?
9487 .read_with(cx, |breakpoint_store, cx| {
9488 breakpoint_store
9489 .breakpoints(
9490 &buffer,
9491 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9492 &buffer_snapshot,
9493 cx,
9494 )
9495 .next()
9496 .and_then(|(bp, _)| {
9497 let breakpoint_row = buffer_snapshot
9498 .summary_for_anchor::<text::PointUtf16>(&bp.position)
9499 .row;
9500
9501 if breakpoint_row == row {
9502 snapshot
9503 .buffer_snapshot
9504 .anchor_in_excerpt(enclosing_excerpt, bp.position)
9505 .map(|position| (position, bp.bp.clone()))
9506 } else {
9507 None
9508 }
9509 })
9510 });
9511 bp
9512 }
9513
9514 pub fn edit_log_breakpoint(
9515 &mut self,
9516 _: &EditLogBreakpoint,
9517 window: &mut Window,
9518 cx: &mut Context<Self>,
9519 ) {
9520 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9521 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9522 message: None,
9523 state: BreakpointState::Enabled,
9524 condition: None,
9525 hit_condition: None,
9526 });
9527
9528 self.add_edit_breakpoint_block(
9529 anchor,
9530 &breakpoint,
9531 BreakpointPromptEditAction::Log,
9532 window,
9533 cx,
9534 );
9535 }
9536 }
9537
9538 fn breakpoints_at_cursors(
9539 &self,
9540 window: &mut Window,
9541 cx: &mut Context<Self>,
9542 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9543 let snapshot = self.snapshot(window, cx);
9544 let cursors = self
9545 .selections
9546 .disjoint_anchors()
9547 .into_iter()
9548 .map(|selection| {
9549 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9550
9551 let breakpoint_position = self
9552 .breakpoint_at_row(cursor_position.row, window, cx)
9553 .map(|bp| bp.0)
9554 .unwrap_or_else(|| {
9555 snapshot
9556 .display_snapshot
9557 .buffer_snapshot
9558 .anchor_after(Point::new(cursor_position.row, 0))
9559 });
9560
9561 let breakpoint = self
9562 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9563 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9564
9565 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9566 })
9567 // 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.
9568 .collect::<HashMap<Anchor, _>>();
9569
9570 cursors.into_iter().collect()
9571 }
9572
9573 pub fn enable_breakpoint(
9574 &mut self,
9575 _: &crate::actions::EnableBreakpoint,
9576 window: &mut Window,
9577 cx: &mut Context<Self>,
9578 ) {
9579 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9580 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9581 continue;
9582 };
9583 self.edit_breakpoint_at_anchor(
9584 anchor,
9585 breakpoint,
9586 BreakpointEditAction::InvertState,
9587 cx,
9588 );
9589 }
9590 }
9591
9592 pub fn disable_breakpoint(
9593 &mut self,
9594 _: &crate::actions::DisableBreakpoint,
9595 window: &mut Window,
9596 cx: &mut Context<Self>,
9597 ) {
9598 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9599 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9600 continue;
9601 };
9602 self.edit_breakpoint_at_anchor(
9603 anchor,
9604 breakpoint,
9605 BreakpointEditAction::InvertState,
9606 cx,
9607 );
9608 }
9609 }
9610
9611 pub fn toggle_breakpoint(
9612 &mut self,
9613 _: &crate::actions::ToggleBreakpoint,
9614 window: &mut Window,
9615 cx: &mut Context<Self>,
9616 ) {
9617 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9618 if let Some(breakpoint) = breakpoint {
9619 self.edit_breakpoint_at_anchor(
9620 anchor,
9621 breakpoint,
9622 BreakpointEditAction::Toggle,
9623 cx,
9624 );
9625 } else {
9626 self.edit_breakpoint_at_anchor(
9627 anchor,
9628 Breakpoint::new_standard(),
9629 BreakpointEditAction::Toggle,
9630 cx,
9631 );
9632 }
9633 }
9634 }
9635
9636 pub fn edit_breakpoint_at_anchor(
9637 &mut self,
9638 breakpoint_position: Anchor,
9639 breakpoint: Breakpoint,
9640 edit_action: BreakpointEditAction,
9641 cx: &mut Context<Self>,
9642 ) {
9643 let Some(breakpoint_store) = &self.breakpoint_store else {
9644 return;
9645 };
9646
9647 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9648 if breakpoint_position == Anchor::min() {
9649 self.buffer()
9650 .read(cx)
9651 .excerpt_buffer_ids()
9652 .into_iter()
9653 .next()
9654 } else {
9655 None
9656 }
9657 }) else {
9658 return;
9659 };
9660
9661 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9662 return;
9663 };
9664
9665 breakpoint_store.update(cx, |breakpoint_store, cx| {
9666 breakpoint_store.toggle_breakpoint(
9667 buffer,
9668 BreakpointWithPosition {
9669 position: breakpoint_position.text_anchor,
9670 bp: breakpoint,
9671 },
9672 edit_action,
9673 cx,
9674 );
9675 });
9676
9677 cx.notify();
9678 }
9679
9680 #[cfg(any(test, feature = "test-support"))]
9681 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9682 self.breakpoint_store.clone()
9683 }
9684
9685 pub fn prepare_restore_change(
9686 &self,
9687 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9688 hunk: &MultiBufferDiffHunk,
9689 cx: &mut App,
9690 ) -> Option<()> {
9691 if hunk.is_created_file() {
9692 return None;
9693 }
9694 let buffer = self.buffer.read(cx);
9695 let diff = buffer.diff_for(hunk.buffer_id)?;
9696 let buffer = buffer.buffer(hunk.buffer_id)?;
9697 let buffer = buffer.read(cx);
9698 let original_text = diff
9699 .read(cx)
9700 .base_text()
9701 .as_rope()
9702 .slice(hunk.diff_base_byte_range.clone());
9703 let buffer_snapshot = buffer.snapshot();
9704 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9705 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9706 probe
9707 .0
9708 .start
9709 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9710 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9711 }) {
9712 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9713 Some(())
9714 } else {
9715 None
9716 }
9717 }
9718
9719 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9720 self.manipulate_lines(window, cx, |lines| lines.reverse())
9721 }
9722
9723 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9724 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9725 }
9726
9727 fn manipulate_lines<Fn>(
9728 &mut self,
9729 window: &mut Window,
9730 cx: &mut Context<Self>,
9731 mut callback: Fn,
9732 ) where
9733 Fn: FnMut(&mut Vec<&str>),
9734 {
9735 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9736
9737 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9738 let buffer = self.buffer.read(cx).snapshot(cx);
9739
9740 let mut edits = Vec::new();
9741
9742 let selections = self.selections.all::<Point>(cx);
9743 let mut selections = selections.iter().peekable();
9744 let mut contiguous_row_selections = Vec::new();
9745 let mut new_selections = Vec::new();
9746 let mut added_lines = 0;
9747 let mut removed_lines = 0;
9748
9749 while let Some(selection) = selections.next() {
9750 let (start_row, end_row) = consume_contiguous_rows(
9751 &mut contiguous_row_selections,
9752 selection,
9753 &display_map,
9754 &mut selections,
9755 );
9756
9757 let start_point = Point::new(start_row.0, 0);
9758 let end_point = Point::new(
9759 end_row.previous_row().0,
9760 buffer.line_len(end_row.previous_row()),
9761 );
9762 let text = buffer
9763 .text_for_range(start_point..end_point)
9764 .collect::<String>();
9765
9766 let mut lines = text.split('\n').collect_vec();
9767
9768 let lines_before = lines.len();
9769 callback(&mut lines);
9770 let lines_after = lines.len();
9771
9772 edits.push((start_point..end_point, lines.join("\n")));
9773
9774 // Selections must change based on added and removed line count
9775 let start_row =
9776 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9777 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9778 new_selections.push(Selection {
9779 id: selection.id,
9780 start: start_row,
9781 end: end_row,
9782 goal: SelectionGoal::None,
9783 reversed: selection.reversed,
9784 });
9785
9786 if lines_after > lines_before {
9787 added_lines += lines_after - lines_before;
9788 } else if lines_before > lines_after {
9789 removed_lines += lines_before - lines_after;
9790 }
9791 }
9792
9793 self.transact(window, cx, |this, window, cx| {
9794 let buffer = this.buffer.update(cx, |buffer, cx| {
9795 buffer.edit(edits, None, cx);
9796 buffer.snapshot(cx)
9797 });
9798
9799 // Recalculate offsets on newly edited buffer
9800 let new_selections = new_selections
9801 .iter()
9802 .map(|s| {
9803 let start_point = Point::new(s.start.0, 0);
9804 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9805 Selection {
9806 id: s.id,
9807 start: buffer.point_to_offset(start_point),
9808 end: buffer.point_to_offset(end_point),
9809 goal: s.goal,
9810 reversed: s.reversed,
9811 }
9812 })
9813 .collect();
9814
9815 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9816 s.select(new_selections);
9817 });
9818
9819 this.request_autoscroll(Autoscroll::fit(), cx);
9820 });
9821 }
9822
9823 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
9824 self.manipulate_text(window, cx, |text| {
9825 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
9826 if has_upper_case_characters {
9827 text.to_lowercase()
9828 } else {
9829 text.to_uppercase()
9830 }
9831 })
9832 }
9833
9834 pub fn convert_to_upper_case(
9835 &mut self,
9836 _: &ConvertToUpperCase,
9837 window: &mut Window,
9838 cx: &mut Context<Self>,
9839 ) {
9840 self.manipulate_text(window, cx, |text| text.to_uppercase())
9841 }
9842
9843 pub fn convert_to_lower_case(
9844 &mut self,
9845 _: &ConvertToLowerCase,
9846 window: &mut Window,
9847 cx: &mut Context<Self>,
9848 ) {
9849 self.manipulate_text(window, cx, |text| text.to_lowercase())
9850 }
9851
9852 pub fn convert_to_title_case(
9853 &mut self,
9854 _: &ConvertToTitleCase,
9855 window: &mut Window,
9856 cx: &mut Context<Self>,
9857 ) {
9858 self.manipulate_text(window, cx, |text| {
9859 text.split('\n')
9860 .map(|line| line.to_case(Case::Title))
9861 .join("\n")
9862 })
9863 }
9864
9865 pub fn convert_to_snake_case(
9866 &mut self,
9867 _: &ConvertToSnakeCase,
9868 window: &mut Window,
9869 cx: &mut Context<Self>,
9870 ) {
9871 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
9872 }
9873
9874 pub fn convert_to_kebab_case(
9875 &mut self,
9876 _: &ConvertToKebabCase,
9877 window: &mut Window,
9878 cx: &mut Context<Self>,
9879 ) {
9880 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
9881 }
9882
9883 pub fn convert_to_upper_camel_case(
9884 &mut self,
9885 _: &ConvertToUpperCamelCase,
9886 window: &mut Window,
9887 cx: &mut Context<Self>,
9888 ) {
9889 self.manipulate_text(window, cx, |text| {
9890 text.split('\n')
9891 .map(|line| line.to_case(Case::UpperCamel))
9892 .join("\n")
9893 })
9894 }
9895
9896 pub fn convert_to_lower_camel_case(
9897 &mut self,
9898 _: &ConvertToLowerCamelCase,
9899 window: &mut Window,
9900 cx: &mut Context<Self>,
9901 ) {
9902 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
9903 }
9904
9905 pub fn convert_to_opposite_case(
9906 &mut self,
9907 _: &ConvertToOppositeCase,
9908 window: &mut Window,
9909 cx: &mut Context<Self>,
9910 ) {
9911 self.manipulate_text(window, cx, |text| {
9912 text.chars()
9913 .fold(String::with_capacity(text.len()), |mut t, c| {
9914 if c.is_uppercase() {
9915 t.extend(c.to_lowercase());
9916 } else {
9917 t.extend(c.to_uppercase());
9918 }
9919 t
9920 })
9921 })
9922 }
9923
9924 pub fn convert_to_rot13(
9925 &mut self,
9926 _: &ConvertToRot13,
9927 window: &mut Window,
9928 cx: &mut Context<Self>,
9929 ) {
9930 self.manipulate_text(window, cx, |text| {
9931 text.chars()
9932 .map(|c| match c {
9933 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
9934 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
9935 _ => c,
9936 })
9937 .collect()
9938 })
9939 }
9940
9941 pub fn convert_to_rot47(
9942 &mut self,
9943 _: &ConvertToRot47,
9944 window: &mut Window,
9945 cx: &mut Context<Self>,
9946 ) {
9947 self.manipulate_text(window, cx, |text| {
9948 text.chars()
9949 .map(|c| {
9950 let code_point = c as u32;
9951 if code_point >= 33 && code_point <= 126 {
9952 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
9953 }
9954 c
9955 })
9956 .collect()
9957 })
9958 }
9959
9960 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
9961 where
9962 Fn: FnMut(&str) -> String,
9963 {
9964 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9965 let buffer = self.buffer.read(cx).snapshot(cx);
9966
9967 let mut new_selections = Vec::new();
9968 let mut edits = Vec::new();
9969 let mut selection_adjustment = 0i32;
9970
9971 for selection in self.selections.all::<usize>(cx) {
9972 let selection_is_empty = selection.is_empty();
9973
9974 let (start, end) = if selection_is_empty {
9975 let word_range = movement::surrounding_word(
9976 &display_map,
9977 selection.start.to_display_point(&display_map),
9978 );
9979 let start = word_range.start.to_offset(&display_map, Bias::Left);
9980 let end = word_range.end.to_offset(&display_map, Bias::Left);
9981 (start, end)
9982 } else {
9983 (selection.start, selection.end)
9984 };
9985
9986 let text = buffer.text_for_range(start..end).collect::<String>();
9987 let old_length = text.len() as i32;
9988 let text = callback(&text);
9989
9990 new_selections.push(Selection {
9991 start: (start as i32 - selection_adjustment) as usize,
9992 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
9993 goal: SelectionGoal::None,
9994 ..selection
9995 });
9996
9997 selection_adjustment += old_length - text.len() as i32;
9998
9999 edits.push((start..end, text));
10000 }
10001
10002 self.transact(window, cx, |this, window, cx| {
10003 this.buffer.update(cx, |buffer, cx| {
10004 buffer.edit(edits, None, cx);
10005 });
10006
10007 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10008 s.select(new_selections);
10009 });
10010
10011 this.request_autoscroll(Autoscroll::fit(), cx);
10012 });
10013 }
10014
10015 pub fn duplicate(
10016 &mut self,
10017 upwards: bool,
10018 whole_lines: bool,
10019 window: &mut Window,
10020 cx: &mut Context<Self>,
10021 ) {
10022 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10023
10024 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10025 let buffer = &display_map.buffer_snapshot;
10026 let selections = self.selections.all::<Point>(cx);
10027
10028 let mut edits = Vec::new();
10029 let mut selections_iter = selections.iter().peekable();
10030 while let Some(selection) = selections_iter.next() {
10031 let mut rows = selection.spanned_rows(false, &display_map);
10032 // duplicate line-wise
10033 if whole_lines || selection.start == selection.end {
10034 // Avoid duplicating the same lines twice.
10035 while let Some(next_selection) = selections_iter.peek() {
10036 let next_rows = next_selection.spanned_rows(false, &display_map);
10037 if next_rows.start < rows.end {
10038 rows.end = next_rows.end;
10039 selections_iter.next().unwrap();
10040 } else {
10041 break;
10042 }
10043 }
10044
10045 // Copy the text from the selected row region and splice it either at the start
10046 // or end of the region.
10047 let start = Point::new(rows.start.0, 0);
10048 let end = Point::new(
10049 rows.end.previous_row().0,
10050 buffer.line_len(rows.end.previous_row()),
10051 );
10052 let text = buffer
10053 .text_for_range(start..end)
10054 .chain(Some("\n"))
10055 .collect::<String>();
10056 let insert_location = if upwards {
10057 Point::new(rows.end.0, 0)
10058 } else {
10059 start
10060 };
10061 edits.push((insert_location..insert_location, text));
10062 } else {
10063 // duplicate character-wise
10064 let start = selection.start;
10065 let end = selection.end;
10066 let text = buffer.text_for_range(start..end).collect::<String>();
10067 edits.push((selection.end..selection.end, text));
10068 }
10069 }
10070
10071 self.transact(window, cx, |this, _, cx| {
10072 this.buffer.update(cx, |buffer, cx| {
10073 buffer.edit(edits, None, cx);
10074 });
10075
10076 this.request_autoscroll(Autoscroll::fit(), cx);
10077 });
10078 }
10079
10080 pub fn duplicate_line_up(
10081 &mut self,
10082 _: &DuplicateLineUp,
10083 window: &mut Window,
10084 cx: &mut Context<Self>,
10085 ) {
10086 self.duplicate(true, true, window, cx);
10087 }
10088
10089 pub fn duplicate_line_down(
10090 &mut self,
10091 _: &DuplicateLineDown,
10092 window: &mut Window,
10093 cx: &mut Context<Self>,
10094 ) {
10095 self.duplicate(false, true, window, cx);
10096 }
10097
10098 pub fn duplicate_selection(
10099 &mut self,
10100 _: &DuplicateSelection,
10101 window: &mut Window,
10102 cx: &mut Context<Self>,
10103 ) {
10104 self.duplicate(false, false, window, cx);
10105 }
10106
10107 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10108 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10109
10110 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10111 let buffer = self.buffer.read(cx).snapshot(cx);
10112
10113 let mut edits = Vec::new();
10114 let mut unfold_ranges = Vec::new();
10115 let mut refold_creases = Vec::new();
10116
10117 let selections = self.selections.all::<Point>(cx);
10118 let mut selections = selections.iter().peekable();
10119 let mut contiguous_row_selections = Vec::new();
10120 let mut new_selections = Vec::new();
10121
10122 while let Some(selection) = selections.next() {
10123 // Find all the selections that span a contiguous row range
10124 let (start_row, end_row) = consume_contiguous_rows(
10125 &mut contiguous_row_selections,
10126 selection,
10127 &display_map,
10128 &mut selections,
10129 );
10130
10131 // Move the text spanned by the row range to be before the line preceding the row range
10132 if start_row.0 > 0 {
10133 let range_to_move = Point::new(
10134 start_row.previous_row().0,
10135 buffer.line_len(start_row.previous_row()),
10136 )
10137 ..Point::new(
10138 end_row.previous_row().0,
10139 buffer.line_len(end_row.previous_row()),
10140 );
10141 let insertion_point = display_map
10142 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10143 .0;
10144
10145 // Don't move lines across excerpts
10146 if buffer
10147 .excerpt_containing(insertion_point..range_to_move.end)
10148 .is_some()
10149 {
10150 let text = buffer
10151 .text_for_range(range_to_move.clone())
10152 .flat_map(|s| s.chars())
10153 .skip(1)
10154 .chain(['\n'])
10155 .collect::<String>();
10156
10157 edits.push((
10158 buffer.anchor_after(range_to_move.start)
10159 ..buffer.anchor_before(range_to_move.end),
10160 String::new(),
10161 ));
10162 let insertion_anchor = buffer.anchor_after(insertion_point);
10163 edits.push((insertion_anchor..insertion_anchor, text));
10164
10165 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10166
10167 // Move selections up
10168 new_selections.extend(contiguous_row_selections.drain(..).map(
10169 |mut selection| {
10170 selection.start.row -= row_delta;
10171 selection.end.row -= row_delta;
10172 selection
10173 },
10174 ));
10175
10176 // Move folds up
10177 unfold_ranges.push(range_to_move.clone());
10178 for fold in display_map.folds_in_range(
10179 buffer.anchor_before(range_to_move.start)
10180 ..buffer.anchor_after(range_to_move.end),
10181 ) {
10182 let mut start = fold.range.start.to_point(&buffer);
10183 let mut end = fold.range.end.to_point(&buffer);
10184 start.row -= row_delta;
10185 end.row -= row_delta;
10186 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10187 }
10188 }
10189 }
10190
10191 // If we didn't move line(s), preserve the existing selections
10192 new_selections.append(&mut contiguous_row_selections);
10193 }
10194
10195 self.transact(window, cx, |this, window, cx| {
10196 this.unfold_ranges(&unfold_ranges, true, true, cx);
10197 this.buffer.update(cx, |buffer, cx| {
10198 for (range, text) in edits {
10199 buffer.edit([(range, text)], None, cx);
10200 }
10201 });
10202 this.fold_creases(refold_creases, true, window, cx);
10203 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10204 s.select(new_selections);
10205 })
10206 });
10207 }
10208
10209 pub fn move_line_down(
10210 &mut self,
10211 _: &MoveLineDown,
10212 window: &mut Window,
10213 cx: &mut Context<Self>,
10214 ) {
10215 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10216
10217 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10218 let buffer = self.buffer.read(cx).snapshot(cx);
10219
10220 let mut edits = Vec::new();
10221 let mut unfold_ranges = Vec::new();
10222 let mut refold_creases = Vec::new();
10223
10224 let selections = self.selections.all::<Point>(cx);
10225 let mut selections = selections.iter().peekable();
10226 let mut contiguous_row_selections = Vec::new();
10227 let mut new_selections = Vec::new();
10228
10229 while let Some(selection) = selections.next() {
10230 // Find all the selections that span a contiguous row range
10231 let (start_row, end_row) = consume_contiguous_rows(
10232 &mut contiguous_row_selections,
10233 selection,
10234 &display_map,
10235 &mut selections,
10236 );
10237
10238 // Move the text spanned by the row range to be after the last line of the row range
10239 if end_row.0 <= buffer.max_point().row {
10240 let range_to_move =
10241 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10242 let insertion_point = display_map
10243 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10244 .0;
10245
10246 // Don't move lines across excerpt boundaries
10247 if buffer
10248 .excerpt_containing(range_to_move.start..insertion_point)
10249 .is_some()
10250 {
10251 let mut text = String::from("\n");
10252 text.extend(buffer.text_for_range(range_to_move.clone()));
10253 text.pop(); // Drop trailing newline
10254 edits.push((
10255 buffer.anchor_after(range_to_move.start)
10256 ..buffer.anchor_before(range_to_move.end),
10257 String::new(),
10258 ));
10259 let insertion_anchor = buffer.anchor_after(insertion_point);
10260 edits.push((insertion_anchor..insertion_anchor, text));
10261
10262 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10263
10264 // Move selections down
10265 new_selections.extend(contiguous_row_selections.drain(..).map(
10266 |mut selection| {
10267 selection.start.row += row_delta;
10268 selection.end.row += row_delta;
10269 selection
10270 },
10271 ));
10272
10273 // Move folds down
10274 unfold_ranges.push(range_to_move.clone());
10275 for fold in display_map.folds_in_range(
10276 buffer.anchor_before(range_to_move.start)
10277 ..buffer.anchor_after(range_to_move.end),
10278 ) {
10279 let mut start = fold.range.start.to_point(&buffer);
10280 let mut end = fold.range.end.to_point(&buffer);
10281 start.row += row_delta;
10282 end.row += row_delta;
10283 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10284 }
10285 }
10286 }
10287
10288 // If we didn't move line(s), preserve the existing selections
10289 new_selections.append(&mut contiguous_row_selections);
10290 }
10291
10292 self.transact(window, cx, |this, window, cx| {
10293 this.unfold_ranges(&unfold_ranges, true, true, cx);
10294 this.buffer.update(cx, |buffer, cx| {
10295 for (range, text) in edits {
10296 buffer.edit([(range, text)], None, cx);
10297 }
10298 });
10299 this.fold_creases(refold_creases, true, window, cx);
10300 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10301 s.select(new_selections)
10302 });
10303 });
10304 }
10305
10306 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10307 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10308 let text_layout_details = &self.text_layout_details(window);
10309 self.transact(window, cx, |this, window, cx| {
10310 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10311 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10312 s.move_with(|display_map, selection| {
10313 if !selection.is_empty() {
10314 return;
10315 }
10316
10317 let mut head = selection.head();
10318 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10319 if head.column() == display_map.line_len(head.row()) {
10320 transpose_offset = display_map
10321 .buffer_snapshot
10322 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10323 }
10324
10325 if transpose_offset == 0 {
10326 return;
10327 }
10328
10329 *head.column_mut() += 1;
10330 head = display_map.clip_point(head, Bias::Right);
10331 let goal = SelectionGoal::HorizontalPosition(
10332 display_map
10333 .x_for_display_point(head, text_layout_details)
10334 .into(),
10335 );
10336 selection.collapse_to(head, goal);
10337
10338 let transpose_start = display_map
10339 .buffer_snapshot
10340 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10341 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10342 let transpose_end = display_map
10343 .buffer_snapshot
10344 .clip_offset(transpose_offset + 1, Bias::Right);
10345 if let Some(ch) =
10346 display_map.buffer_snapshot.chars_at(transpose_start).next()
10347 {
10348 edits.push((transpose_start..transpose_offset, String::new()));
10349 edits.push((transpose_end..transpose_end, ch.to_string()));
10350 }
10351 }
10352 });
10353 edits
10354 });
10355 this.buffer
10356 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10357 let selections = this.selections.all::<usize>(cx);
10358 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10359 s.select(selections);
10360 });
10361 });
10362 }
10363
10364 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10365 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10366 self.rewrap_impl(RewrapOptions::default(), cx)
10367 }
10368
10369 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10370 let buffer = self.buffer.read(cx).snapshot(cx);
10371 let selections = self.selections.all::<Point>(cx);
10372 let mut selections = selections.iter().peekable();
10373
10374 let mut edits = Vec::new();
10375 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10376
10377 while let Some(selection) = selections.next() {
10378 let mut start_row = selection.start.row;
10379 let mut end_row = selection.end.row;
10380
10381 // Skip selections that overlap with a range that has already been rewrapped.
10382 let selection_range = start_row..end_row;
10383 if rewrapped_row_ranges
10384 .iter()
10385 .any(|range| range.overlaps(&selection_range))
10386 {
10387 continue;
10388 }
10389
10390 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10391
10392 // Since not all lines in the selection may be at the same indent
10393 // level, choose the indent size that is the most common between all
10394 // of the lines.
10395 //
10396 // If there is a tie, we use the deepest indent.
10397 let (indent_size, indent_end) = {
10398 let mut indent_size_occurrences = HashMap::default();
10399 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10400
10401 for row in start_row..=end_row {
10402 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10403 rows_by_indent_size.entry(indent).or_default().push(row);
10404 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10405 }
10406
10407 let indent_size = indent_size_occurrences
10408 .into_iter()
10409 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10410 .map(|(indent, _)| indent)
10411 .unwrap_or_default();
10412 let row = rows_by_indent_size[&indent_size][0];
10413 let indent_end = Point::new(row, indent_size.len);
10414
10415 (indent_size, indent_end)
10416 };
10417
10418 let mut line_prefix = indent_size.chars().collect::<String>();
10419
10420 let mut inside_comment = false;
10421 if let Some(comment_prefix) =
10422 buffer
10423 .language_scope_at(selection.head())
10424 .and_then(|language| {
10425 language
10426 .line_comment_prefixes()
10427 .iter()
10428 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10429 .cloned()
10430 })
10431 {
10432 line_prefix.push_str(&comment_prefix);
10433 inside_comment = true;
10434 }
10435
10436 let language_settings = buffer.language_settings_at(selection.head(), cx);
10437 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10438 RewrapBehavior::InComments => inside_comment,
10439 RewrapBehavior::InSelections => !selection.is_empty(),
10440 RewrapBehavior::Anywhere => true,
10441 };
10442
10443 let should_rewrap = options.override_language_settings
10444 || allow_rewrap_based_on_language
10445 || self.hard_wrap.is_some();
10446 if !should_rewrap {
10447 continue;
10448 }
10449
10450 if selection.is_empty() {
10451 'expand_upwards: while start_row > 0 {
10452 let prev_row = start_row - 1;
10453 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10454 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10455 {
10456 start_row = prev_row;
10457 } else {
10458 break 'expand_upwards;
10459 }
10460 }
10461
10462 'expand_downwards: while end_row < buffer.max_point().row {
10463 let next_row = end_row + 1;
10464 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10465 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10466 {
10467 end_row = next_row;
10468 } else {
10469 break 'expand_downwards;
10470 }
10471 }
10472 }
10473
10474 let start = Point::new(start_row, 0);
10475 let start_offset = start.to_offset(&buffer);
10476 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10477 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10478 let Some(lines_without_prefixes) = selection_text
10479 .lines()
10480 .map(|line| {
10481 line.strip_prefix(&line_prefix)
10482 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10483 .ok_or_else(|| {
10484 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
10485 })
10486 })
10487 .collect::<Result<Vec<_>, _>>()
10488 .log_err()
10489 else {
10490 continue;
10491 };
10492
10493 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10494 buffer
10495 .language_settings_at(Point::new(start_row, 0), cx)
10496 .preferred_line_length as usize
10497 });
10498 let wrapped_text = wrap_with_prefix(
10499 line_prefix,
10500 lines_without_prefixes.join("\n"),
10501 wrap_column,
10502 tab_size,
10503 options.preserve_existing_whitespace,
10504 );
10505
10506 // TODO: should always use char-based diff while still supporting cursor behavior that
10507 // matches vim.
10508 let mut diff_options = DiffOptions::default();
10509 if options.override_language_settings {
10510 diff_options.max_word_diff_len = 0;
10511 diff_options.max_word_diff_line_count = 0;
10512 } else {
10513 diff_options.max_word_diff_len = usize::MAX;
10514 diff_options.max_word_diff_line_count = usize::MAX;
10515 }
10516
10517 for (old_range, new_text) in
10518 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10519 {
10520 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10521 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10522 edits.push((edit_start..edit_end, new_text));
10523 }
10524
10525 rewrapped_row_ranges.push(start_row..=end_row);
10526 }
10527
10528 self.buffer
10529 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10530 }
10531
10532 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10533 let mut text = String::new();
10534 let buffer = self.buffer.read(cx).snapshot(cx);
10535 let mut selections = self.selections.all::<Point>(cx);
10536 let mut clipboard_selections = Vec::with_capacity(selections.len());
10537 {
10538 let max_point = buffer.max_point();
10539 let mut is_first = true;
10540 for selection in &mut selections {
10541 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10542 if is_entire_line {
10543 selection.start = Point::new(selection.start.row, 0);
10544 if !selection.is_empty() && selection.end.column == 0 {
10545 selection.end = cmp::min(max_point, selection.end);
10546 } else {
10547 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10548 }
10549 selection.goal = SelectionGoal::None;
10550 }
10551 if is_first {
10552 is_first = false;
10553 } else {
10554 text += "\n";
10555 }
10556 let mut len = 0;
10557 for chunk in buffer.text_for_range(selection.start..selection.end) {
10558 text.push_str(chunk);
10559 len += chunk.len();
10560 }
10561 clipboard_selections.push(ClipboardSelection {
10562 len,
10563 is_entire_line,
10564 first_line_indent: buffer
10565 .indent_size_for_line(MultiBufferRow(selection.start.row))
10566 .len,
10567 });
10568 }
10569 }
10570
10571 self.transact(window, cx, |this, window, cx| {
10572 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10573 s.select(selections);
10574 });
10575 this.insert("", window, cx);
10576 });
10577 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10578 }
10579
10580 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10581 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10582 let item = self.cut_common(window, cx);
10583 cx.write_to_clipboard(item);
10584 }
10585
10586 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10587 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10588 self.change_selections(None, window, cx, |s| {
10589 s.move_with(|snapshot, sel| {
10590 if sel.is_empty() {
10591 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10592 }
10593 });
10594 });
10595 let item = self.cut_common(window, cx);
10596 cx.set_global(KillRing(item))
10597 }
10598
10599 pub fn kill_ring_yank(
10600 &mut self,
10601 _: &KillRingYank,
10602 window: &mut Window,
10603 cx: &mut Context<Self>,
10604 ) {
10605 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10606 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10607 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10608 (kill_ring.text().to_string(), kill_ring.metadata_json())
10609 } else {
10610 return;
10611 }
10612 } else {
10613 return;
10614 };
10615 self.do_paste(&text, metadata, false, window, cx);
10616 }
10617
10618 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10619 self.do_copy(true, cx);
10620 }
10621
10622 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10623 self.do_copy(false, cx);
10624 }
10625
10626 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10627 let selections = self.selections.all::<Point>(cx);
10628 let buffer = self.buffer.read(cx).read(cx);
10629 let mut text = String::new();
10630
10631 let mut clipboard_selections = Vec::with_capacity(selections.len());
10632 {
10633 let max_point = buffer.max_point();
10634 let mut is_first = true;
10635 for selection in &selections {
10636 let mut start = selection.start;
10637 let mut end = selection.end;
10638 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10639 if is_entire_line {
10640 start = Point::new(start.row, 0);
10641 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10642 }
10643
10644 let mut trimmed_selections = Vec::new();
10645 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10646 let row = MultiBufferRow(start.row);
10647 let first_indent = buffer.indent_size_for_line(row);
10648 if first_indent.len == 0 || start.column > first_indent.len {
10649 trimmed_selections.push(start..end);
10650 } else {
10651 trimmed_selections.push(
10652 Point::new(row.0, first_indent.len)
10653 ..Point::new(row.0, buffer.line_len(row)),
10654 );
10655 for row in start.row + 1..=end.row {
10656 let mut line_len = buffer.line_len(MultiBufferRow(row));
10657 if row == end.row {
10658 line_len = end.column;
10659 }
10660 if line_len == 0 {
10661 trimmed_selections
10662 .push(Point::new(row, 0)..Point::new(row, line_len));
10663 continue;
10664 }
10665 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10666 if row_indent_size.len >= first_indent.len {
10667 trimmed_selections.push(
10668 Point::new(row, first_indent.len)..Point::new(row, line_len),
10669 );
10670 } else {
10671 trimmed_selections.clear();
10672 trimmed_selections.push(start..end);
10673 break;
10674 }
10675 }
10676 }
10677 } else {
10678 trimmed_selections.push(start..end);
10679 }
10680
10681 for trimmed_range in trimmed_selections {
10682 if is_first {
10683 is_first = false;
10684 } else {
10685 text += "\n";
10686 }
10687 let mut len = 0;
10688 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10689 text.push_str(chunk);
10690 len += chunk.len();
10691 }
10692 clipboard_selections.push(ClipboardSelection {
10693 len,
10694 is_entire_line,
10695 first_line_indent: buffer
10696 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10697 .len,
10698 });
10699 }
10700 }
10701 }
10702
10703 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10704 text,
10705 clipboard_selections,
10706 ));
10707 }
10708
10709 pub fn do_paste(
10710 &mut self,
10711 text: &String,
10712 clipboard_selections: Option<Vec<ClipboardSelection>>,
10713 handle_entire_lines: bool,
10714 window: &mut Window,
10715 cx: &mut Context<Self>,
10716 ) {
10717 if self.read_only(cx) {
10718 return;
10719 }
10720
10721 let clipboard_text = Cow::Borrowed(text);
10722
10723 self.transact(window, cx, |this, window, cx| {
10724 if let Some(mut clipboard_selections) = clipboard_selections {
10725 let old_selections = this.selections.all::<usize>(cx);
10726 let all_selections_were_entire_line =
10727 clipboard_selections.iter().all(|s| s.is_entire_line);
10728 let first_selection_indent_column =
10729 clipboard_selections.first().map(|s| s.first_line_indent);
10730 if clipboard_selections.len() != old_selections.len() {
10731 clipboard_selections.drain(..);
10732 }
10733 let cursor_offset = this.selections.last::<usize>(cx).head();
10734 let mut auto_indent_on_paste = true;
10735
10736 this.buffer.update(cx, |buffer, cx| {
10737 let snapshot = buffer.read(cx);
10738 auto_indent_on_paste = snapshot
10739 .language_settings_at(cursor_offset, cx)
10740 .auto_indent_on_paste;
10741
10742 let mut start_offset = 0;
10743 let mut edits = Vec::new();
10744 let mut original_indent_columns = Vec::new();
10745 for (ix, selection) in old_selections.iter().enumerate() {
10746 let to_insert;
10747 let entire_line;
10748 let original_indent_column;
10749 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10750 let end_offset = start_offset + clipboard_selection.len;
10751 to_insert = &clipboard_text[start_offset..end_offset];
10752 entire_line = clipboard_selection.is_entire_line;
10753 start_offset = end_offset + 1;
10754 original_indent_column = Some(clipboard_selection.first_line_indent);
10755 } else {
10756 to_insert = clipboard_text.as_str();
10757 entire_line = all_selections_were_entire_line;
10758 original_indent_column = first_selection_indent_column
10759 }
10760
10761 // If the corresponding selection was empty when this slice of the
10762 // clipboard text was written, then the entire line containing the
10763 // selection was copied. If this selection is also currently empty,
10764 // then paste the line before the current line of the buffer.
10765 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10766 let column = selection.start.to_point(&snapshot).column as usize;
10767 let line_start = selection.start - column;
10768 line_start..line_start
10769 } else {
10770 selection.range()
10771 };
10772
10773 edits.push((range, to_insert));
10774 original_indent_columns.push(original_indent_column);
10775 }
10776 drop(snapshot);
10777
10778 buffer.edit(
10779 edits,
10780 if auto_indent_on_paste {
10781 Some(AutoindentMode::Block {
10782 original_indent_columns,
10783 })
10784 } else {
10785 None
10786 },
10787 cx,
10788 );
10789 });
10790
10791 let selections = this.selections.all::<usize>(cx);
10792 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10793 s.select(selections)
10794 });
10795 } else {
10796 this.insert(&clipboard_text, window, cx);
10797 }
10798 });
10799 }
10800
10801 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10802 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10803 if let Some(item) = cx.read_from_clipboard() {
10804 let entries = item.entries();
10805
10806 match entries.first() {
10807 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10808 // of all the pasted entries.
10809 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10810 .do_paste(
10811 clipboard_string.text(),
10812 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10813 true,
10814 window,
10815 cx,
10816 ),
10817 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10818 }
10819 }
10820 }
10821
10822 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10823 if self.read_only(cx) {
10824 return;
10825 }
10826
10827 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10828
10829 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10830 if let Some((selections, _)) =
10831 self.selection_history.transaction(transaction_id).cloned()
10832 {
10833 self.change_selections(None, window, cx, |s| {
10834 s.select_anchors(selections.to_vec());
10835 });
10836 } else {
10837 log::error!(
10838 "No entry in selection_history found for undo. \
10839 This may correspond to a bug where undo does not update the selection. \
10840 If this is occurring, please add details to \
10841 https://github.com/zed-industries/zed/issues/22692"
10842 );
10843 }
10844 self.request_autoscroll(Autoscroll::fit(), cx);
10845 self.unmark_text(window, cx);
10846 self.refresh_inline_completion(true, false, window, cx);
10847 cx.emit(EditorEvent::Edited { transaction_id });
10848 cx.emit(EditorEvent::TransactionUndone { transaction_id });
10849 }
10850 }
10851
10852 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
10853 if self.read_only(cx) {
10854 return;
10855 }
10856
10857 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10858
10859 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
10860 if let Some((_, Some(selections))) =
10861 self.selection_history.transaction(transaction_id).cloned()
10862 {
10863 self.change_selections(None, window, cx, |s| {
10864 s.select_anchors(selections.to_vec());
10865 });
10866 } else {
10867 log::error!(
10868 "No entry in selection_history found for redo. \
10869 This may correspond to a bug where undo does not update the selection. \
10870 If this is occurring, please add details to \
10871 https://github.com/zed-industries/zed/issues/22692"
10872 );
10873 }
10874 self.request_autoscroll(Autoscroll::fit(), cx);
10875 self.unmark_text(window, cx);
10876 self.refresh_inline_completion(true, false, window, cx);
10877 cx.emit(EditorEvent::Edited { transaction_id });
10878 }
10879 }
10880
10881 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
10882 self.buffer
10883 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
10884 }
10885
10886 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
10887 self.buffer
10888 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
10889 }
10890
10891 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
10892 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10893 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10894 s.move_with(|map, selection| {
10895 let cursor = if selection.is_empty() {
10896 movement::left(map, selection.start)
10897 } else {
10898 selection.start
10899 };
10900 selection.collapse_to(cursor, SelectionGoal::None);
10901 });
10902 })
10903 }
10904
10905 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
10906 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10907 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10908 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
10909 })
10910 }
10911
10912 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
10913 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10914 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10915 s.move_with(|map, selection| {
10916 let cursor = if selection.is_empty() {
10917 movement::right(map, selection.end)
10918 } else {
10919 selection.end
10920 };
10921 selection.collapse_to(cursor, SelectionGoal::None)
10922 });
10923 })
10924 }
10925
10926 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
10927 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10928 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10929 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
10930 })
10931 }
10932
10933 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
10934 if self.take_rename(true, window, cx).is_some() {
10935 return;
10936 }
10937
10938 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10939 cx.propagate();
10940 return;
10941 }
10942
10943 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10944
10945 let text_layout_details = &self.text_layout_details(window);
10946 let selection_count = self.selections.count();
10947 let first_selection = self.selections.first_anchor();
10948
10949 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10950 s.move_with(|map, selection| {
10951 if !selection.is_empty() {
10952 selection.goal = SelectionGoal::None;
10953 }
10954 let (cursor, goal) = movement::up(
10955 map,
10956 selection.start,
10957 selection.goal,
10958 false,
10959 text_layout_details,
10960 );
10961 selection.collapse_to(cursor, goal);
10962 });
10963 });
10964
10965 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10966 {
10967 cx.propagate();
10968 }
10969 }
10970
10971 pub fn move_up_by_lines(
10972 &mut self,
10973 action: &MoveUpByLines,
10974 window: &mut Window,
10975 cx: &mut Context<Self>,
10976 ) {
10977 if self.take_rename(true, window, cx).is_some() {
10978 return;
10979 }
10980
10981 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10982 cx.propagate();
10983 return;
10984 }
10985
10986 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10987
10988 let text_layout_details = &self.text_layout_details(window);
10989
10990 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10991 s.move_with(|map, selection| {
10992 if !selection.is_empty() {
10993 selection.goal = SelectionGoal::None;
10994 }
10995 let (cursor, goal) = movement::up_by_rows(
10996 map,
10997 selection.start,
10998 action.lines,
10999 selection.goal,
11000 false,
11001 text_layout_details,
11002 );
11003 selection.collapse_to(cursor, goal);
11004 });
11005 })
11006 }
11007
11008 pub fn move_down_by_lines(
11009 &mut self,
11010 action: &MoveDownByLines,
11011 window: &mut Window,
11012 cx: &mut Context<Self>,
11013 ) {
11014 if self.take_rename(true, window, cx).is_some() {
11015 return;
11016 }
11017
11018 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11019 cx.propagate();
11020 return;
11021 }
11022
11023 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11024
11025 let text_layout_details = &self.text_layout_details(window);
11026
11027 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11028 s.move_with(|map, selection| {
11029 if !selection.is_empty() {
11030 selection.goal = SelectionGoal::None;
11031 }
11032 let (cursor, goal) = movement::down_by_rows(
11033 map,
11034 selection.start,
11035 action.lines,
11036 selection.goal,
11037 false,
11038 text_layout_details,
11039 );
11040 selection.collapse_to(cursor, goal);
11041 });
11042 })
11043 }
11044
11045 pub fn select_down_by_lines(
11046 &mut self,
11047 action: &SelectDownByLines,
11048 window: &mut Window,
11049 cx: &mut Context<Self>,
11050 ) {
11051 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11052 let text_layout_details = &self.text_layout_details(window);
11053 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11054 s.move_heads_with(|map, head, goal| {
11055 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11056 })
11057 })
11058 }
11059
11060 pub fn select_up_by_lines(
11061 &mut self,
11062 action: &SelectUpByLines,
11063 window: &mut Window,
11064 cx: &mut Context<Self>,
11065 ) {
11066 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11067 let text_layout_details = &self.text_layout_details(window);
11068 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11069 s.move_heads_with(|map, head, goal| {
11070 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11071 })
11072 })
11073 }
11074
11075 pub fn select_page_up(
11076 &mut self,
11077 _: &SelectPageUp,
11078 window: &mut Window,
11079 cx: &mut Context<Self>,
11080 ) {
11081 let Some(row_count) = self.visible_row_count() else {
11082 return;
11083 };
11084
11085 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11086
11087 let text_layout_details = &self.text_layout_details(window);
11088
11089 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11090 s.move_heads_with(|map, head, goal| {
11091 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11092 })
11093 })
11094 }
11095
11096 pub fn move_page_up(
11097 &mut self,
11098 action: &MovePageUp,
11099 window: &mut Window,
11100 cx: &mut Context<Self>,
11101 ) {
11102 if self.take_rename(true, window, cx).is_some() {
11103 return;
11104 }
11105
11106 if self
11107 .context_menu
11108 .borrow_mut()
11109 .as_mut()
11110 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
11111 .unwrap_or(false)
11112 {
11113 return;
11114 }
11115
11116 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11117 cx.propagate();
11118 return;
11119 }
11120
11121 let Some(row_count) = self.visible_row_count() else {
11122 return;
11123 };
11124
11125 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11126
11127 let autoscroll = if action.center_cursor {
11128 Autoscroll::center()
11129 } else {
11130 Autoscroll::fit()
11131 };
11132
11133 let text_layout_details = &self.text_layout_details(window);
11134
11135 self.change_selections(Some(autoscroll), window, cx, |s| {
11136 s.move_with(|map, selection| {
11137 if !selection.is_empty() {
11138 selection.goal = SelectionGoal::None;
11139 }
11140 let (cursor, goal) = movement::up_by_rows(
11141 map,
11142 selection.end,
11143 row_count,
11144 selection.goal,
11145 false,
11146 text_layout_details,
11147 );
11148 selection.collapse_to(cursor, goal);
11149 });
11150 });
11151 }
11152
11153 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11154 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11155 let text_layout_details = &self.text_layout_details(window);
11156 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11157 s.move_heads_with(|map, head, goal| {
11158 movement::up(map, head, goal, false, text_layout_details)
11159 })
11160 })
11161 }
11162
11163 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11164 self.take_rename(true, window, cx);
11165
11166 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11167 cx.propagate();
11168 return;
11169 }
11170
11171 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11172
11173 let text_layout_details = &self.text_layout_details(window);
11174 let selection_count = self.selections.count();
11175 let first_selection = self.selections.first_anchor();
11176
11177 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11178 s.move_with(|map, selection| {
11179 if !selection.is_empty() {
11180 selection.goal = SelectionGoal::None;
11181 }
11182 let (cursor, goal) = movement::down(
11183 map,
11184 selection.end,
11185 selection.goal,
11186 false,
11187 text_layout_details,
11188 );
11189 selection.collapse_to(cursor, goal);
11190 });
11191 });
11192
11193 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11194 {
11195 cx.propagate();
11196 }
11197 }
11198
11199 pub fn select_page_down(
11200 &mut self,
11201 _: &SelectPageDown,
11202 window: &mut Window,
11203 cx: &mut Context<Self>,
11204 ) {
11205 let Some(row_count) = self.visible_row_count() else {
11206 return;
11207 };
11208
11209 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11210
11211 let text_layout_details = &self.text_layout_details(window);
11212
11213 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11214 s.move_heads_with(|map, head, goal| {
11215 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11216 })
11217 })
11218 }
11219
11220 pub fn move_page_down(
11221 &mut self,
11222 action: &MovePageDown,
11223 window: &mut Window,
11224 cx: &mut Context<Self>,
11225 ) {
11226 if self.take_rename(true, window, cx).is_some() {
11227 return;
11228 }
11229
11230 if self
11231 .context_menu
11232 .borrow_mut()
11233 .as_mut()
11234 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
11235 .unwrap_or(false)
11236 {
11237 return;
11238 }
11239
11240 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11241 cx.propagate();
11242 return;
11243 }
11244
11245 let Some(row_count) = self.visible_row_count() else {
11246 return;
11247 };
11248
11249 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11250
11251 let autoscroll = if action.center_cursor {
11252 Autoscroll::center()
11253 } else {
11254 Autoscroll::fit()
11255 };
11256
11257 let text_layout_details = &self.text_layout_details(window);
11258 self.change_selections(Some(autoscroll), window, cx, |s| {
11259 s.move_with(|map, selection| {
11260 if !selection.is_empty() {
11261 selection.goal = SelectionGoal::None;
11262 }
11263 let (cursor, goal) = movement::down_by_rows(
11264 map,
11265 selection.end,
11266 row_count,
11267 selection.goal,
11268 false,
11269 text_layout_details,
11270 );
11271 selection.collapse_to(cursor, goal);
11272 });
11273 });
11274 }
11275
11276 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11277 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11278 let text_layout_details = &self.text_layout_details(window);
11279 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11280 s.move_heads_with(|map, head, goal| {
11281 movement::down(map, head, goal, false, text_layout_details)
11282 })
11283 });
11284 }
11285
11286 pub fn context_menu_first(
11287 &mut self,
11288 _: &ContextMenuFirst,
11289 _window: &mut Window,
11290 cx: &mut Context<Self>,
11291 ) {
11292 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11293 context_menu.select_first(self.completion_provider.as_deref(), cx);
11294 }
11295 }
11296
11297 pub fn context_menu_prev(
11298 &mut self,
11299 _: &ContextMenuPrevious,
11300 _window: &mut Window,
11301 cx: &mut Context<Self>,
11302 ) {
11303 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11304 context_menu.select_prev(self.completion_provider.as_deref(), cx);
11305 }
11306 }
11307
11308 pub fn context_menu_next(
11309 &mut self,
11310 _: &ContextMenuNext,
11311 _window: &mut Window,
11312 cx: &mut Context<Self>,
11313 ) {
11314 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11315 context_menu.select_next(self.completion_provider.as_deref(), cx);
11316 }
11317 }
11318
11319 pub fn context_menu_last(
11320 &mut self,
11321 _: &ContextMenuLast,
11322 _window: &mut Window,
11323 cx: &mut Context<Self>,
11324 ) {
11325 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11326 context_menu.select_last(self.completion_provider.as_deref(), cx);
11327 }
11328 }
11329
11330 pub fn move_to_previous_word_start(
11331 &mut self,
11332 _: &MoveToPreviousWordStart,
11333 window: &mut Window,
11334 cx: &mut Context<Self>,
11335 ) {
11336 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11337 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11338 s.move_cursors_with(|map, head, _| {
11339 (
11340 movement::previous_word_start(map, head),
11341 SelectionGoal::None,
11342 )
11343 });
11344 })
11345 }
11346
11347 pub fn move_to_previous_subword_start(
11348 &mut self,
11349 _: &MoveToPreviousSubwordStart,
11350 window: &mut Window,
11351 cx: &mut Context<Self>,
11352 ) {
11353 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11354 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11355 s.move_cursors_with(|map, head, _| {
11356 (
11357 movement::previous_subword_start(map, head),
11358 SelectionGoal::None,
11359 )
11360 });
11361 })
11362 }
11363
11364 pub fn select_to_previous_word_start(
11365 &mut self,
11366 _: &SelectToPreviousWordStart,
11367 window: &mut Window,
11368 cx: &mut Context<Self>,
11369 ) {
11370 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11371 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11372 s.move_heads_with(|map, head, _| {
11373 (
11374 movement::previous_word_start(map, head),
11375 SelectionGoal::None,
11376 )
11377 });
11378 })
11379 }
11380
11381 pub fn select_to_previous_subword_start(
11382 &mut self,
11383 _: &SelectToPreviousSubwordStart,
11384 window: &mut Window,
11385 cx: &mut Context<Self>,
11386 ) {
11387 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11388 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11389 s.move_heads_with(|map, head, _| {
11390 (
11391 movement::previous_subword_start(map, head),
11392 SelectionGoal::None,
11393 )
11394 });
11395 })
11396 }
11397
11398 pub fn delete_to_previous_word_start(
11399 &mut self,
11400 action: &DeleteToPreviousWordStart,
11401 window: &mut Window,
11402 cx: &mut Context<Self>,
11403 ) {
11404 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11405 self.transact(window, cx, |this, window, cx| {
11406 this.select_autoclose_pair(window, cx);
11407 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11408 s.move_with(|map, selection| {
11409 if selection.is_empty() {
11410 let cursor = if action.ignore_newlines {
11411 movement::previous_word_start(map, selection.head())
11412 } else {
11413 movement::previous_word_start_or_newline(map, selection.head())
11414 };
11415 selection.set_head(cursor, SelectionGoal::None);
11416 }
11417 });
11418 });
11419 this.insert("", window, cx);
11420 });
11421 }
11422
11423 pub fn delete_to_previous_subword_start(
11424 &mut self,
11425 _: &DeleteToPreviousSubwordStart,
11426 window: &mut Window,
11427 cx: &mut Context<Self>,
11428 ) {
11429 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11430 self.transact(window, cx, |this, window, cx| {
11431 this.select_autoclose_pair(window, cx);
11432 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11433 s.move_with(|map, selection| {
11434 if selection.is_empty() {
11435 let cursor = movement::previous_subword_start(map, selection.head());
11436 selection.set_head(cursor, SelectionGoal::None);
11437 }
11438 });
11439 });
11440 this.insert("", window, cx);
11441 });
11442 }
11443
11444 pub fn move_to_next_word_end(
11445 &mut self,
11446 _: &MoveToNextWordEnd,
11447 window: &mut Window,
11448 cx: &mut Context<Self>,
11449 ) {
11450 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11451 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11452 s.move_cursors_with(|map, head, _| {
11453 (movement::next_word_end(map, head), SelectionGoal::None)
11454 });
11455 })
11456 }
11457
11458 pub fn move_to_next_subword_end(
11459 &mut self,
11460 _: &MoveToNextSubwordEnd,
11461 window: &mut Window,
11462 cx: &mut Context<Self>,
11463 ) {
11464 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11465 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11466 s.move_cursors_with(|map, head, _| {
11467 (movement::next_subword_end(map, head), SelectionGoal::None)
11468 });
11469 })
11470 }
11471
11472 pub fn select_to_next_word_end(
11473 &mut self,
11474 _: &SelectToNextWordEnd,
11475 window: &mut Window,
11476 cx: &mut Context<Self>,
11477 ) {
11478 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11479 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11480 s.move_heads_with(|map, head, _| {
11481 (movement::next_word_end(map, head), SelectionGoal::None)
11482 });
11483 })
11484 }
11485
11486 pub fn select_to_next_subword_end(
11487 &mut self,
11488 _: &SelectToNextSubwordEnd,
11489 window: &mut Window,
11490 cx: &mut Context<Self>,
11491 ) {
11492 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11493 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11494 s.move_heads_with(|map, head, _| {
11495 (movement::next_subword_end(map, head), SelectionGoal::None)
11496 });
11497 })
11498 }
11499
11500 pub fn delete_to_next_word_end(
11501 &mut self,
11502 action: &DeleteToNextWordEnd,
11503 window: &mut Window,
11504 cx: &mut Context<Self>,
11505 ) {
11506 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11507 self.transact(window, cx, |this, window, cx| {
11508 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11509 s.move_with(|map, selection| {
11510 if selection.is_empty() {
11511 let cursor = if action.ignore_newlines {
11512 movement::next_word_end(map, selection.head())
11513 } else {
11514 movement::next_word_end_or_newline(map, selection.head())
11515 };
11516 selection.set_head(cursor, SelectionGoal::None);
11517 }
11518 });
11519 });
11520 this.insert("", window, cx);
11521 });
11522 }
11523
11524 pub fn delete_to_next_subword_end(
11525 &mut self,
11526 _: &DeleteToNextSubwordEnd,
11527 window: &mut Window,
11528 cx: &mut Context<Self>,
11529 ) {
11530 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11531 self.transact(window, cx, |this, window, cx| {
11532 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11533 s.move_with(|map, selection| {
11534 if selection.is_empty() {
11535 let cursor = movement::next_subword_end(map, selection.head());
11536 selection.set_head(cursor, SelectionGoal::None);
11537 }
11538 });
11539 });
11540 this.insert("", window, cx);
11541 });
11542 }
11543
11544 pub fn move_to_beginning_of_line(
11545 &mut self,
11546 action: &MoveToBeginningOfLine,
11547 window: &mut Window,
11548 cx: &mut Context<Self>,
11549 ) {
11550 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11551 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11552 s.move_cursors_with(|map, head, _| {
11553 (
11554 movement::indented_line_beginning(
11555 map,
11556 head,
11557 action.stop_at_soft_wraps,
11558 action.stop_at_indent,
11559 ),
11560 SelectionGoal::None,
11561 )
11562 });
11563 })
11564 }
11565
11566 pub fn select_to_beginning_of_line(
11567 &mut self,
11568 action: &SelectToBeginningOfLine,
11569 window: &mut Window,
11570 cx: &mut Context<Self>,
11571 ) {
11572 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11573 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11574 s.move_heads_with(|map, head, _| {
11575 (
11576 movement::indented_line_beginning(
11577 map,
11578 head,
11579 action.stop_at_soft_wraps,
11580 action.stop_at_indent,
11581 ),
11582 SelectionGoal::None,
11583 )
11584 });
11585 });
11586 }
11587
11588 pub fn delete_to_beginning_of_line(
11589 &mut self,
11590 action: &DeleteToBeginningOfLine,
11591 window: &mut Window,
11592 cx: &mut Context<Self>,
11593 ) {
11594 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11595 self.transact(window, cx, |this, window, cx| {
11596 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11597 s.move_with(|_, selection| {
11598 selection.reversed = true;
11599 });
11600 });
11601
11602 this.select_to_beginning_of_line(
11603 &SelectToBeginningOfLine {
11604 stop_at_soft_wraps: false,
11605 stop_at_indent: action.stop_at_indent,
11606 },
11607 window,
11608 cx,
11609 );
11610 this.backspace(&Backspace, window, cx);
11611 });
11612 }
11613
11614 pub fn move_to_end_of_line(
11615 &mut self,
11616 action: &MoveToEndOfLine,
11617 window: &mut Window,
11618 cx: &mut Context<Self>,
11619 ) {
11620 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11621 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11622 s.move_cursors_with(|map, head, _| {
11623 (
11624 movement::line_end(map, head, action.stop_at_soft_wraps),
11625 SelectionGoal::None,
11626 )
11627 });
11628 })
11629 }
11630
11631 pub fn select_to_end_of_line(
11632 &mut self,
11633 action: &SelectToEndOfLine,
11634 window: &mut Window,
11635 cx: &mut Context<Self>,
11636 ) {
11637 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11638 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11639 s.move_heads_with(|map, head, _| {
11640 (
11641 movement::line_end(map, head, action.stop_at_soft_wraps),
11642 SelectionGoal::None,
11643 )
11644 });
11645 })
11646 }
11647
11648 pub fn delete_to_end_of_line(
11649 &mut self,
11650 _: &DeleteToEndOfLine,
11651 window: &mut Window,
11652 cx: &mut Context<Self>,
11653 ) {
11654 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11655 self.transact(window, cx, |this, window, cx| {
11656 this.select_to_end_of_line(
11657 &SelectToEndOfLine {
11658 stop_at_soft_wraps: false,
11659 },
11660 window,
11661 cx,
11662 );
11663 this.delete(&Delete, window, cx);
11664 });
11665 }
11666
11667 pub fn cut_to_end_of_line(
11668 &mut self,
11669 _: &CutToEndOfLine,
11670 window: &mut Window,
11671 cx: &mut Context<Self>,
11672 ) {
11673 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11674 self.transact(window, cx, |this, window, cx| {
11675 this.select_to_end_of_line(
11676 &SelectToEndOfLine {
11677 stop_at_soft_wraps: false,
11678 },
11679 window,
11680 cx,
11681 );
11682 this.cut(&Cut, window, cx);
11683 });
11684 }
11685
11686 pub fn move_to_start_of_paragraph(
11687 &mut self,
11688 _: &MoveToStartOfParagraph,
11689 window: &mut Window,
11690 cx: &mut Context<Self>,
11691 ) {
11692 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11693 cx.propagate();
11694 return;
11695 }
11696 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11697 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11698 s.move_with(|map, selection| {
11699 selection.collapse_to(
11700 movement::start_of_paragraph(map, selection.head(), 1),
11701 SelectionGoal::None,
11702 )
11703 });
11704 })
11705 }
11706
11707 pub fn move_to_end_of_paragraph(
11708 &mut self,
11709 _: &MoveToEndOfParagraph,
11710 window: &mut Window,
11711 cx: &mut Context<Self>,
11712 ) {
11713 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11714 cx.propagate();
11715 return;
11716 }
11717 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11718 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11719 s.move_with(|map, selection| {
11720 selection.collapse_to(
11721 movement::end_of_paragraph(map, selection.head(), 1),
11722 SelectionGoal::None,
11723 )
11724 });
11725 })
11726 }
11727
11728 pub fn select_to_start_of_paragraph(
11729 &mut self,
11730 _: &SelectToStartOfParagraph,
11731 window: &mut Window,
11732 cx: &mut Context<Self>,
11733 ) {
11734 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11735 cx.propagate();
11736 return;
11737 }
11738 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11739 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11740 s.move_heads_with(|map, head, _| {
11741 (
11742 movement::start_of_paragraph(map, head, 1),
11743 SelectionGoal::None,
11744 )
11745 });
11746 })
11747 }
11748
11749 pub fn select_to_end_of_paragraph(
11750 &mut self,
11751 _: &SelectToEndOfParagraph,
11752 window: &mut Window,
11753 cx: &mut Context<Self>,
11754 ) {
11755 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11756 cx.propagate();
11757 return;
11758 }
11759 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11760 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11761 s.move_heads_with(|map, head, _| {
11762 (
11763 movement::end_of_paragraph(map, head, 1),
11764 SelectionGoal::None,
11765 )
11766 });
11767 })
11768 }
11769
11770 pub fn move_to_start_of_excerpt(
11771 &mut self,
11772 _: &MoveToStartOfExcerpt,
11773 window: &mut Window,
11774 cx: &mut Context<Self>,
11775 ) {
11776 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11777 cx.propagate();
11778 return;
11779 }
11780 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11781 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11782 s.move_with(|map, selection| {
11783 selection.collapse_to(
11784 movement::start_of_excerpt(
11785 map,
11786 selection.head(),
11787 workspace::searchable::Direction::Prev,
11788 ),
11789 SelectionGoal::None,
11790 )
11791 });
11792 })
11793 }
11794
11795 pub fn move_to_start_of_next_excerpt(
11796 &mut self,
11797 _: &MoveToStartOfNextExcerpt,
11798 window: &mut Window,
11799 cx: &mut Context<Self>,
11800 ) {
11801 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11802 cx.propagate();
11803 return;
11804 }
11805
11806 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11807 s.move_with(|map, selection| {
11808 selection.collapse_to(
11809 movement::start_of_excerpt(
11810 map,
11811 selection.head(),
11812 workspace::searchable::Direction::Next,
11813 ),
11814 SelectionGoal::None,
11815 )
11816 });
11817 })
11818 }
11819
11820 pub fn move_to_end_of_excerpt(
11821 &mut self,
11822 _: &MoveToEndOfExcerpt,
11823 window: &mut Window,
11824 cx: &mut Context<Self>,
11825 ) {
11826 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11827 cx.propagate();
11828 return;
11829 }
11830 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11831 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11832 s.move_with(|map, selection| {
11833 selection.collapse_to(
11834 movement::end_of_excerpt(
11835 map,
11836 selection.head(),
11837 workspace::searchable::Direction::Next,
11838 ),
11839 SelectionGoal::None,
11840 )
11841 });
11842 })
11843 }
11844
11845 pub fn move_to_end_of_previous_excerpt(
11846 &mut self,
11847 _: &MoveToEndOfPreviousExcerpt,
11848 window: &mut Window,
11849 cx: &mut Context<Self>,
11850 ) {
11851 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11852 cx.propagate();
11853 return;
11854 }
11855 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11856 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11857 s.move_with(|map, selection| {
11858 selection.collapse_to(
11859 movement::end_of_excerpt(
11860 map,
11861 selection.head(),
11862 workspace::searchable::Direction::Prev,
11863 ),
11864 SelectionGoal::None,
11865 )
11866 });
11867 })
11868 }
11869
11870 pub fn select_to_start_of_excerpt(
11871 &mut self,
11872 _: &SelectToStartOfExcerpt,
11873 window: &mut Window,
11874 cx: &mut Context<Self>,
11875 ) {
11876 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11877 cx.propagate();
11878 return;
11879 }
11880 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11881 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11882 s.move_heads_with(|map, head, _| {
11883 (
11884 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11885 SelectionGoal::None,
11886 )
11887 });
11888 })
11889 }
11890
11891 pub fn select_to_start_of_next_excerpt(
11892 &mut self,
11893 _: &SelectToStartOfNextExcerpt,
11894 window: &mut Window,
11895 cx: &mut Context<Self>,
11896 ) {
11897 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11898 cx.propagate();
11899 return;
11900 }
11901 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11902 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11903 s.move_heads_with(|map, head, _| {
11904 (
11905 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
11906 SelectionGoal::None,
11907 )
11908 });
11909 })
11910 }
11911
11912 pub fn select_to_end_of_excerpt(
11913 &mut self,
11914 _: &SelectToEndOfExcerpt,
11915 window: &mut Window,
11916 cx: &mut Context<Self>,
11917 ) {
11918 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11919 cx.propagate();
11920 return;
11921 }
11922 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11923 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11924 s.move_heads_with(|map, head, _| {
11925 (
11926 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
11927 SelectionGoal::None,
11928 )
11929 });
11930 })
11931 }
11932
11933 pub fn select_to_end_of_previous_excerpt(
11934 &mut self,
11935 _: &SelectToEndOfPreviousExcerpt,
11936 window: &mut Window,
11937 cx: &mut Context<Self>,
11938 ) {
11939 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11940 cx.propagate();
11941 return;
11942 }
11943 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11944 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11945 s.move_heads_with(|map, head, _| {
11946 (
11947 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11948 SelectionGoal::None,
11949 )
11950 });
11951 })
11952 }
11953
11954 pub fn move_to_beginning(
11955 &mut self,
11956 _: &MoveToBeginning,
11957 window: &mut Window,
11958 cx: &mut Context<Self>,
11959 ) {
11960 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11961 cx.propagate();
11962 return;
11963 }
11964 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11965 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11966 s.select_ranges(vec![0..0]);
11967 });
11968 }
11969
11970 pub fn select_to_beginning(
11971 &mut self,
11972 _: &SelectToBeginning,
11973 window: &mut Window,
11974 cx: &mut Context<Self>,
11975 ) {
11976 let mut selection = self.selections.last::<Point>(cx);
11977 selection.set_head(Point::zero(), SelectionGoal::None);
11978 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11979 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11980 s.select(vec![selection]);
11981 });
11982 }
11983
11984 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
11985 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11986 cx.propagate();
11987 return;
11988 }
11989 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11990 let cursor = self.buffer.read(cx).read(cx).len();
11991 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11992 s.select_ranges(vec![cursor..cursor])
11993 });
11994 }
11995
11996 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
11997 self.nav_history = nav_history;
11998 }
11999
12000 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12001 self.nav_history.as_ref()
12002 }
12003
12004 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12005 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12006 }
12007
12008 fn push_to_nav_history(
12009 &mut self,
12010 cursor_anchor: Anchor,
12011 new_position: Option<Point>,
12012 is_deactivate: bool,
12013 cx: &mut Context<Self>,
12014 ) {
12015 if let Some(nav_history) = self.nav_history.as_mut() {
12016 let buffer = self.buffer.read(cx).read(cx);
12017 let cursor_position = cursor_anchor.to_point(&buffer);
12018 let scroll_state = self.scroll_manager.anchor();
12019 let scroll_top_row = scroll_state.top_row(&buffer);
12020 drop(buffer);
12021
12022 if let Some(new_position) = new_position {
12023 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12024 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12025 return;
12026 }
12027 }
12028
12029 nav_history.push(
12030 Some(NavigationData {
12031 cursor_anchor,
12032 cursor_position,
12033 scroll_anchor: scroll_state,
12034 scroll_top_row,
12035 }),
12036 cx,
12037 );
12038 cx.emit(EditorEvent::PushedToNavHistory {
12039 anchor: cursor_anchor,
12040 is_deactivate,
12041 })
12042 }
12043 }
12044
12045 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12046 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12047 let buffer = self.buffer.read(cx).snapshot(cx);
12048 let mut selection = self.selections.first::<usize>(cx);
12049 selection.set_head(buffer.len(), SelectionGoal::None);
12050 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12051 s.select(vec![selection]);
12052 });
12053 }
12054
12055 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12056 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12057 let end = self.buffer.read(cx).read(cx).len();
12058 self.change_selections(None, window, cx, |s| {
12059 s.select_ranges(vec![0..end]);
12060 });
12061 }
12062
12063 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12064 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12065 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12066 let mut selections = self.selections.all::<Point>(cx);
12067 let max_point = display_map.buffer_snapshot.max_point();
12068 for selection in &mut selections {
12069 let rows = selection.spanned_rows(true, &display_map);
12070 selection.start = Point::new(rows.start.0, 0);
12071 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12072 selection.reversed = false;
12073 }
12074 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12075 s.select(selections);
12076 });
12077 }
12078
12079 pub fn split_selection_into_lines(
12080 &mut self,
12081 _: &SplitSelectionIntoLines,
12082 window: &mut Window,
12083 cx: &mut Context<Self>,
12084 ) {
12085 let selections = self
12086 .selections
12087 .all::<Point>(cx)
12088 .into_iter()
12089 .map(|selection| selection.start..selection.end)
12090 .collect::<Vec<_>>();
12091 self.unfold_ranges(&selections, true, true, cx);
12092
12093 let mut new_selection_ranges = Vec::new();
12094 {
12095 let buffer = self.buffer.read(cx).read(cx);
12096 for selection in selections {
12097 for row in selection.start.row..selection.end.row {
12098 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12099 new_selection_ranges.push(cursor..cursor);
12100 }
12101
12102 let is_multiline_selection = selection.start.row != selection.end.row;
12103 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12104 // so this action feels more ergonomic when paired with other selection operations
12105 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12106 if !should_skip_last {
12107 new_selection_ranges.push(selection.end..selection.end);
12108 }
12109 }
12110 }
12111 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12112 s.select_ranges(new_selection_ranges);
12113 });
12114 }
12115
12116 pub fn add_selection_above(
12117 &mut self,
12118 _: &AddSelectionAbove,
12119 window: &mut Window,
12120 cx: &mut Context<Self>,
12121 ) {
12122 self.add_selection(true, window, cx);
12123 }
12124
12125 pub fn add_selection_below(
12126 &mut self,
12127 _: &AddSelectionBelow,
12128 window: &mut Window,
12129 cx: &mut Context<Self>,
12130 ) {
12131 self.add_selection(false, window, cx);
12132 }
12133
12134 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12135 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12136
12137 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12138 let mut selections = self.selections.all::<Point>(cx);
12139 let text_layout_details = self.text_layout_details(window);
12140 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12141 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12142 let range = oldest_selection.display_range(&display_map).sorted();
12143
12144 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12145 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12146 let positions = start_x.min(end_x)..start_x.max(end_x);
12147
12148 selections.clear();
12149 let mut stack = Vec::new();
12150 for row in range.start.row().0..=range.end.row().0 {
12151 if let Some(selection) = self.selections.build_columnar_selection(
12152 &display_map,
12153 DisplayRow(row),
12154 &positions,
12155 oldest_selection.reversed,
12156 &text_layout_details,
12157 ) {
12158 stack.push(selection.id);
12159 selections.push(selection);
12160 }
12161 }
12162
12163 if above {
12164 stack.reverse();
12165 }
12166
12167 AddSelectionsState { above, stack }
12168 });
12169
12170 let last_added_selection = *state.stack.last().unwrap();
12171 let mut new_selections = Vec::new();
12172 if above == state.above {
12173 let end_row = if above {
12174 DisplayRow(0)
12175 } else {
12176 display_map.max_point().row()
12177 };
12178
12179 'outer: for selection in selections {
12180 if selection.id == last_added_selection {
12181 let range = selection.display_range(&display_map).sorted();
12182 debug_assert_eq!(range.start.row(), range.end.row());
12183 let mut row = range.start.row();
12184 let positions =
12185 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12186 px(start)..px(end)
12187 } else {
12188 let start_x =
12189 display_map.x_for_display_point(range.start, &text_layout_details);
12190 let end_x =
12191 display_map.x_for_display_point(range.end, &text_layout_details);
12192 start_x.min(end_x)..start_x.max(end_x)
12193 };
12194
12195 while row != end_row {
12196 if above {
12197 row.0 -= 1;
12198 } else {
12199 row.0 += 1;
12200 }
12201
12202 if let Some(new_selection) = self.selections.build_columnar_selection(
12203 &display_map,
12204 row,
12205 &positions,
12206 selection.reversed,
12207 &text_layout_details,
12208 ) {
12209 state.stack.push(new_selection.id);
12210 if above {
12211 new_selections.push(new_selection);
12212 new_selections.push(selection);
12213 } else {
12214 new_selections.push(selection);
12215 new_selections.push(new_selection);
12216 }
12217
12218 continue 'outer;
12219 }
12220 }
12221 }
12222
12223 new_selections.push(selection);
12224 }
12225 } else {
12226 new_selections = selections;
12227 new_selections.retain(|s| s.id != last_added_selection);
12228 state.stack.pop();
12229 }
12230
12231 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12232 s.select(new_selections);
12233 });
12234 if state.stack.len() > 1 {
12235 self.add_selections_state = Some(state);
12236 }
12237 }
12238
12239 fn select_match_ranges(
12240 &mut self,
12241 range: Range<usize>,
12242 reversed: bool,
12243 replace_newest: bool,
12244 auto_scroll: Option<Autoscroll>,
12245 window: &mut Window,
12246 cx: &mut Context<Editor>,
12247 ) {
12248 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12249 self.change_selections(auto_scroll, window, cx, |s| {
12250 if replace_newest {
12251 s.delete(s.newest_anchor().id);
12252 }
12253 if reversed {
12254 s.insert_range(range.end..range.start);
12255 } else {
12256 s.insert_range(range);
12257 }
12258 });
12259 }
12260
12261 pub fn select_next_match_internal(
12262 &mut self,
12263 display_map: &DisplaySnapshot,
12264 replace_newest: bool,
12265 autoscroll: Option<Autoscroll>,
12266 window: &mut Window,
12267 cx: &mut Context<Self>,
12268 ) -> Result<()> {
12269 let buffer = &display_map.buffer_snapshot;
12270 let mut selections = self.selections.all::<usize>(cx);
12271 if let Some(mut select_next_state) = self.select_next_state.take() {
12272 let query = &select_next_state.query;
12273 if !select_next_state.done {
12274 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12275 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12276 let mut next_selected_range = None;
12277
12278 let bytes_after_last_selection =
12279 buffer.bytes_in_range(last_selection.end..buffer.len());
12280 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12281 let query_matches = query
12282 .stream_find_iter(bytes_after_last_selection)
12283 .map(|result| (last_selection.end, result))
12284 .chain(
12285 query
12286 .stream_find_iter(bytes_before_first_selection)
12287 .map(|result| (0, result)),
12288 );
12289
12290 for (start_offset, query_match) in query_matches {
12291 let query_match = query_match.unwrap(); // can only fail due to I/O
12292 let offset_range =
12293 start_offset + query_match.start()..start_offset + query_match.end();
12294 let display_range = offset_range.start.to_display_point(display_map)
12295 ..offset_range.end.to_display_point(display_map);
12296
12297 if !select_next_state.wordwise
12298 || (!movement::is_inside_word(display_map, display_range.start)
12299 && !movement::is_inside_word(display_map, display_range.end))
12300 {
12301 // TODO: This is n^2, because we might check all the selections
12302 if !selections
12303 .iter()
12304 .any(|selection| selection.range().overlaps(&offset_range))
12305 {
12306 next_selected_range = Some(offset_range);
12307 break;
12308 }
12309 }
12310 }
12311
12312 if let Some(next_selected_range) = next_selected_range {
12313 self.select_match_ranges(
12314 next_selected_range,
12315 last_selection.reversed,
12316 replace_newest,
12317 autoscroll,
12318 window,
12319 cx,
12320 );
12321 } else {
12322 select_next_state.done = true;
12323 }
12324 }
12325
12326 self.select_next_state = Some(select_next_state);
12327 } else {
12328 let mut only_carets = true;
12329 let mut same_text_selected = true;
12330 let mut selected_text = None;
12331
12332 let mut selections_iter = selections.iter().peekable();
12333 while let Some(selection) = selections_iter.next() {
12334 if selection.start != selection.end {
12335 only_carets = false;
12336 }
12337
12338 if same_text_selected {
12339 if selected_text.is_none() {
12340 selected_text =
12341 Some(buffer.text_for_range(selection.range()).collect::<String>());
12342 }
12343
12344 if let Some(next_selection) = selections_iter.peek() {
12345 if next_selection.range().len() == selection.range().len() {
12346 let next_selected_text = buffer
12347 .text_for_range(next_selection.range())
12348 .collect::<String>();
12349 if Some(next_selected_text) != selected_text {
12350 same_text_selected = false;
12351 selected_text = None;
12352 }
12353 } else {
12354 same_text_selected = false;
12355 selected_text = None;
12356 }
12357 }
12358 }
12359 }
12360
12361 if only_carets {
12362 for selection in &mut selections {
12363 let word_range = movement::surrounding_word(
12364 display_map,
12365 selection.start.to_display_point(display_map),
12366 );
12367 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12368 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12369 selection.goal = SelectionGoal::None;
12370 selection.reversed = false;
12371 self.select_match_ranges(
12372 selection.start..selection.end,
12373 selection.reversed,
12374 replace_newest,
12375 autoscroll,
12376 window,
12377 cx,
12378 );
12379 }
12380
12381 if selections.len() == 1 {
12382 let selection = selections
12383 .last()
12384 .expect("ensured that there's only one selection");
12385 let query = buffer
12386 .text_for_range(selection.start..selection.end)
12387 .collect::<String>();
12388 let is_empty = query.is_empty();
12389 let select_state = SelectNextState {
12390 query: AhoCorasick::new(&[query])?,
12391 wordwise: true,
12392 done: is_empty,
12393 };
12394 self.select_next_state = Some(select_state);
12395 } else {
12396 self.select_next_state = None;
12397 }
12398 } else if let Some(selected_text) = selected_text {
12399 self.select_next_state = Some(SelectNextState {
12400 query: AhoCorasick::new(&[selected_text])?,
12401 wordwise: false,
12402 done: false,
12403 });
12404 self.select_next_match_internal(
12405 display_map,
12406 replace_newest,
12407 autoscroll,
12408 window,
12409 cx,
12410 )?;
12411 }
12412 }
12413 Ok(())
12414 }
12415
12416 pub fn select_all_matches(
12417 &mut self,
12418 _action: &SelectAllMatches,
12419 window: &mut Window,
12420 cx: &mut Context<Self>,
12421 ) -> Result<()> {
12422 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12423
12424 self.push_to_selection_history();
12425 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12426
12427 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12428 let Some(select_next_state) = self.select_next_state.as_mut() else {
12429 return Ok(());
12430 };
12431 if select_next_state.done {
12432 return Ok(());
12433 }
12434
12435 let mut new_selections = Vec::new();
12436
12437 let reversed = self.selections.oldest::<usize>(cx).reversed;
12438 let buffer = &display_map.buffer_snapshot;
12439 let query_matches = select_next_state
12440 .query
12441 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12442
12443 for query_match in query_matches.into_iter() {
12444 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12445 let offset_range = if reversed {
12446 query_match.end()..query_match.start()
12447 } else {
12448 query_match.start()..query_match.end()
12449 };
12450 let display_range = offset_range.start.to_display_point(&display_map)
12451 ..offset_range.end.to_display_point(&display_map);
12452
12453 if !select_next_state.wordwise
12454 || (!movement::is_inside_word(&display_map, display_range.start)
12455 && !movement::is_inside_word(&display_map, display_range.end))
12456 {
12457 new_selections.push(offset_range.start..offset_range.end);
12458 }
12459 }
12460
12461 select_next_state.done = true;
12462 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12463 self.change_selections(None, window, cx, |selections| {
12464 selections.select_ranges(new_selections)
12465 });
12466
12467 Ok(())
12468 }
12469
12470 pub fn select_next(
12471 &mut self,
12472 action: &SelectNext,
12473 window: &mut Window,
12474 cx: &mut Context<Self>,
12475 ) -> Result<()> {
12476 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12477 self.push_to_selection_history();
12478 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12479 self.select_next_match_internal(
12480 &display_map,
12481 action.replace_newest,
12482 Some(Autoscroll::newest()),
12483 window,
12484 cx,
12485 )?;
12486 Ok(())
12487 }
12488
12489 pub fn select_previous(
12490 &mut self,
12491 action: &SelectPrevious,
12492 window: &mut Window,
12493 cx: &mut Context<Self>,
12494 ) -> Result<()> {
12495 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12496 self.push_to_selection_history();
12497 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12498 let buffer = &display_map.buffer_snapshot;
12499 let mut selections = self.selections.all::<usize>(cx);
12500 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12501 let query = &select_prev_state.query;
12502 if !select_prev_state.done {
12503 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12504 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12505 let mut next_selected_range = None;
12506 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12507 let bytes_before_last_selection =
12508 buffer.reversed_bytes_in_range(0..last_selection.start);
12509 let bytes_after_first_selection =
12510 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12511 let query_matches = query
12512 .stream_find_iter(bytes_before_last_selection)
12513 .map(|result| (last_selection.start, result))
12514 .chain(
12515 query
12516 .stream_find_iter(bytes_after_first_selection)
12517 .map(|result| (buffer.len(), result)),
12518 );
12519 for (end_offset, query_match) in query_matches {
12520 let query_match = query_match.unwrap(); // can only fail due to I/O
12521 let offset_range =
12522 end_offset - query_match.end()..end_offset - query_match.start();
12523 let display_range = offset_range.start.to_display_point(&display_map)
12524 ..offset_range.end.to_display_point(&display_map);
12525
12526 if !select_prev_state.wordwise
12527 || (!movement::is_inside_word(&display_map, display_range.start)
12528 && !movement::is_inside_word(&display_map, display_range.end))
12529 {
12530 next_selected_range = Some(offset_range);
12531 break;
12532 }
12533 }
12534
12535 if let Some(next_selected_range) = next_selected_range {
12536 self.select_match_ranges(
12537 next_selected_range,
12538 last_selection.reversed,
12539 action.replace_newest,
12540 Some(Autoscroll::newest()),
12541 window,
12542 cx,
12543 );
12544 } else {
12545 select_prev_state.done = true;
12546 }
12547 }
12548
12549 self.select_prev_state = Some(select_prev_state);
12550 } else {
12551 let mut only_carets = true;
12552 let mut same_text_selected = true;
12553 let mut selected_text = None;
12554
12555 let mut selections_iter = selections.iter().peekable();
12556 while let Some(selection) = selections_iter.next() {
12557 if selection.start != selection.end {
12558 only_carets = false;
12559 }
12560
12561 if same_text_selected {
12562 if selected_text.is_none() {
12563 selected_text =
12564 Some(buffer.text_for_range(selection.range()).collect::<String>());
12565 }
12566
12567 if let Some(next_selection) = selections_iter.peek() {
12568 if next_selection.range().len() == selection.range().len() {
12569 let next_selected_text = buffer
12570 .text_for_range(next_selection.range())
12571 .collect::<String>();
12572 if Some(next_selected_text) != selected_text {
12573 same_text_selected = false;
12574 selected_text = None;
12575 }
12576 } else {
12577 same_text_selected = false;
12578 selected_text = None;
12579 }
12580 }
12581 }
12582 }
12583
12584 if only_carets {
12585 for selection in &mut selections {
12586 let word_range = movement::surrounding_word(
12587 &display_map,
12588 selection.start.to_display_point(&display_map),
12589 );
12590 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12591 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12592 selection.goal = SelectionGoal::None;
12593 selection.reversed = false;
12594 self.select_match_ranges(
12595 selection.start..selection.end,
12596 selection.reversed,
12597 action.replace_newest,
12598 Some(Autoscroll::newest()),
12599 window,
12600 cx,
12601 );
12602 }
12603 if selections.len() == 1 {
12604 let selection = selections
12605 .last()
12606 .expect("ensured that there's only one selection");
12607 let query = buffer
12608 .text_for_range(selection.start..selection.end)
12609 .collect::<String>();
12610 let is_empty = query.is_empty();
12611 let select_state = SelectNextState {
12612 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12613 wordwise: true,
12614 done: is_empty,
12615 };
12616 self.select_prev_state = Some(select_state);
12617 } else {
12618 self.select_prev_state = None;
12619 }
12620 } else if let Some(selected_text) = selected_text {
12621 self.select_prev_state = Some(SelectNextState {
12622 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12623 wordwise: false,
12624 done: false,
12625 });
12626 self.select_previous(action, window, cx)?;
12627 }
12628 }
12629 Ok(())
12630 }
12631
12632 pub fn find_next_match(
12633 &mut self,
12634 _: &FindNextMatch,
12635 window: &mut Window,
12636 cx: &mut Context<Self>,
12637 ) -> Result<()> {
12638 let selections = self.selections.disjoint_anchors();
12639 match selections.first() {
12640 Some(first) if selections.len() >= 2 => {
12641 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12642 s.select_ranges([first.range()]);
12643 });
12644 }
12645 _ => self.select_next(
12646 &SelectNext {
12647 replace_newest: true,
12648 },
12649 window,
12650 cx,
12651 )?,
12652 }
12653 Ok(())
12654 }
12655
12656 pub fn find_previous_match(
12657 &mut self,
12658 _: &FindPreviousMatch,
12659 window: &mut Window,
12660 cx: &mut Context<Self>,
12661 ) -> Result<()> {
12662 let selections = self.selections.disjoint_anchors();
12663 match selections.last() {
12664 Some(last) if selections.len() >= 2 => {
12665 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12666 s.select_ranges([last.range()]);
12667 });
12668 }
12669 _ => self.select_previous(
12670 &SelectPrevious {
12671 replace_newest: true,
12672 },
12673 window,
12674 cx,
12675 )?,
12676 }
12677 Ok(())
12678 }
12679
12680 pub fn toggle_comments(
12681 &mut self,
12682 action: &ToggleComments,
12683 window: &mut Window,
12684 cx: &mut Context<Self>,
12685 ) {
12686 if self.read_only(cx) {
12687 return;
12688 }
12689 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12690 let text_layout_details = &self.text_layout_details(window);
12691 self.transact(window, cx, |this, window, cx| {
12692 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12693 let mut edits = Vec::new();
12694 let mut selection_edit_ranges = Vec::new();
12695 let mut last_toggled_row = None;
12696 let snapshot = this.buffer.read(cx).read(cx);
12697 let empty_str: Arc<str> = Arc::default();
12698 let mut suffixes_inserted = Vec::new();
12699 let ignore_indent = action.ignore_indent;
12700
12701 fn comment_prefix_range(
12702 snapshot: &MultiBufferSnapshot,
12703 row: MultiBufferRow,
12704 comment_prefix: &str,
12705 comment_prefix_whitespace: &str,
12706 ignore_indent: bool,
12707 ) -> Range<Point> {
12708 let indent_size = if ignore_indent {
12709 0
12710 } else {
12711 snapshot.indent_size_for_line(row).len
12712 };
12713
12714 let start = Point::new(row.0, indent_size);
12715
12716 let mut line_bytes = snapshot
12717 .bytes_in_range(start..snapshot.max_point())
12718 .flatten()
12719 .copied();
12720
12721 // If this line currently begins with the line comment prefix, then record
12722 // the range containing the prefix.
12723 if line_bytes
12724 .by_ref()
12725 .take(comment_prefix.len())
12726 .eq(comment_prefix.bytes())
12727 {
12728 // Include any whitespace that matches the comment prefix.
12729 let matching_whitespace_len = line_bytes
12730 .zip(comment_prefix_whitespace.bytes())
12731 .take_while(|(a, b)| a == b)
12732 .count() as u32;
12733 let end = Point::new(
12734 start.row,
12735 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12736 );
12737 start..end
12738 } else {
12739 start..start
12740 }
12741 }
12742
12743 fn comment_suffix_range(
12744 snapshot: &MultiBufferSnapshot,
12745 row: MultiBufferRow,
12746 comment_suffix: &str,
12747 comment_suffix_has_leading_space: bool,
12748 ) -> Range<Point> {
12749 let end = Point::new(row.0, snapshot.line_len(row));
12750 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12751
12752 let mut line_end_bytes = snapshot
12753 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12754 .flatten()
12755 .copied();
12756
12757 let leading_space_len = if suffix_start_column > 0
12758 && line_end_bytes.next() == Some(b' ')
12759 && comment_suffix_has_leading_space
12760 {
12761 1
12762 } else {
12763 0
12764 };
12765
12766 // If this line currently begins with the line comment prefix, then record
12767 // the range containing the prefix.
12768 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12769 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12770 start..end
12771 } else {
12772 end..end
12773 }
12774 }
12775
12776 // TODO: Handle selections that cross excerpts
12777 for selection in &mut selections {
12778 let start_column = snapshot
12779 .indent_size_for_line(MultiBufferRow(selection.start.row))
12780 .len;
12781 let language = if let Some(language) =
12782 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12783 {
12784 language
12785 } else {
12786 continue;
12787 };
12788
12789 selection_edit_ranges.clear();
12790
12791 // If multiple selections contain a given row, avoid processing that
12792 // row more than once.
12793 let mut start_row = MultiBufferRow(selection.start.row);
12794 if last_toggled_row == Some(start_row) {
12795 start_row = start_row.next_row();
12796 }
12797 let end_row =
12798 if selection.end.row > selection.start.row && selection.end.column == 0 {
12799 MultiBufferRow(selection.end.row - 1)
12800 } else {
12801 MultiBufferRow(selection.end.row)
12802 };
12803 last_toggled_row = Some(end_row);
12804
12805 if start_row > end_row {
12806 continue;
12807 }
12808
12809 // If the language has line comments, toggle those.
12810 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12811
12812 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12813 if ignore_indent {
12814 full_comment_prefixes = full_comment_prefixes
12815 .into_iter()
12816 .map(|s| Arc::from(s.trim_end()))
12817 .collect();
12818 }
12819
12820 if !full_comment_prefixes.is_empty() {
12821 let first_prefix = full_comment_prefixes
12822 .first()
12823 .expect("prefixes is non-empty");
12824 let prefix_trimmed_lengths = full_comment_prefixes
12825 .iter()
12826 .map(|p| p.trim_end_matches(' ').len())
12827 .collect::<SmallVec<[usize; 4]>>();
12828
12829 let mut all_selection_lines_are_comments = true;
12830
12831 for row in start_row.0..=end_row.0 {
12832 let row = MultiBufferRow(row);
12833 if start_row < end_row && snapshot.is_line_blank(row) {
12834 continue;
12835 }
12836
12837 let prefix_range = full_comment_prefixes
12838 .iter()
12839 .zip(prefix_trimmed_lengths.iter().copied())
12840 .map(|(prefix, trimmed_prefix_len)| {
12841 comment_prefix_range(
12842 snapshot.deref(),
12843 row,
12844 &prefix[..trimmed_prefix_len],
12845 &prefix[trimmed_prefix_len..],
12846 ignore_indent,
12847 )
12848 })
12849 .max_by_key(|range| range.end.column - range.start.column)
12850 .expect("prefixes is non-empty");
12851
12852 if prefix_range.is_empty() {
12853 all_selection_lines_are_comments = false;
12854 }
12855
12856 selection_edit_ranges.push(prefix_range);
12857 }
12858
12859 if all_selection_lines_are_comments {
12860 edits.extend(
12861 selection_edit_ranges
12862 .iter()
12863 .cloned()
12864 .map(|range| (range, empty_str.clone())),
12865 );
12866 } else {
12867 let min_column = selection_edit_ranges
12868 .iter()
12869 .map(|range| range.start.column)
12870 .min()
12871 .unwrap_or(0);
12872 edits.extend(selection_edit_ranges.iter().map(|range| {
12873 let position = Point::new(range.start.row, min_column);
12874 (position..position, first_prefix.clone())
12875 }));
12876 }
12877 } else if let Some((full_comment_prefix, comment_suffix)) =
12878 language.block_comment_delimiters()
12879 {
12880 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
12881 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
12882 let prefix_range = comment_prefix_range(
12883 snapshot.deref(),
12884 start_row,
12885 comment_prefix,
12886 comment_prefix_whitespace,
12887 ignore_indent,
12888 );
12889 let suffix_range = comment_suffix_range(
12890 snapshot.deref(),
12891 end_row,
12892 comment_suffix.trim_start_matches(' '),
12893 comment_suffix.starts_with(' '),
12894 );
12895
12896 if prefix_range.is_empty() || suffix_range.is_empty() {
12897 edits.push((
12898 prefix_range.start..prefix_range.start,
12899 full_comment_prefix.clone(),
12900 ));
12901 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
12902 suffixes_inserted.push((end_row, comment_suffix.len()));
12903 } else {
12904 edits.push((prefix_range, empty_str.clone()));
12905 edits.push((suffix_range, empty_str.clone()));
12906 }
12907 } else {
12908 continue;
12909 }
12910 }
12911
12912 drop(snapshot);
12913 this.buffer.update(cx, |buffer, cx| {
12914 buffer.edit(edits, None, cx);
12915 });
12916
12917 // Adjust selections so that they end before any comment suffixes that
12918 // were inserted.
12919 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
12920 let mut selections = this.selections.all::<Point>(cx);
12921 let snapshot = this.buffer.read(cx).read(cx);
12922 for selection in &mut selections {
12923 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
12924 match row.cmp(&MultiBufferRow(selection.end.row)) {
12925 Ordering::Less => {
12926 suffixes_inserted.next();
12927 continue;
12928 }
12929 Ordering::Greater => break,
12930 Ordering::Equal => {
12931 if selection.end.column == snapshot.line_len(row) {
12932 if selection.is_empty() {
12933 selection.start.column -= suffix_len as u32;
12934 }
12935 selection.end.column -= suffix_len as u32;
12936 }
12937 break;
12938 }
12939 }
12940 }
12941 }
12942
12943 drop(snapshot);
12944 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12945 s.select(selections)
12946 });
12947
12948 let selections = this.selections.all::<Point>(cx);
12949 let selections_on_single_row = selections.windows(2).all(|selections| {
12950 selections[0].start.row == selections[1].start.row
12951 && selections[0].end.row == selections[1].end.row
12952 && selections[0].start.row == selections[0].end.row
12953 });
12954 let selections_selecting = selections
12955 .iter()
12956 .any(|selection| selection.start != selection.end);
12957 let advance_downwards = action.advance_downwards
12958 && selections_on_single_row
12959 && !selections_selecting
12960 && !matches!(this.mode, EditorMode::SingleLine { .. });
12961
12962 if advance_downwards {
12963 let snapshot = this.buffer.read(cx).snapshot(cx);
12964
12965 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12966 s.move_cursors_with(|display_snapshot, display_point, _| {
12967 let mut point = display_point.to_point(display_snapshot);
12968 point.row += 1;
12969 point = snapshot.clip_point(point, Bias::Left);
12970 let display_point = point.to_display_point(display_snapshot);
12971 let goal = SelectionGoal::HorizontalPosition(
12972 display_snapshot
12973 .x_for_display_point(display_point, text_layout_details)
12974 .into(),
12975 );
12976 (display_point, goal)
12977 })
12978 });
12979 }
12980 });
12981 }
12982
12983 pub fn select_enclosing_symbol(
12984 &mut self,
12985 _: &SelectEnclosingSymbol,
12986 window: &mut Window,
12987 cx: &mut Context<Self>,
12988 ) {
12989 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12990
12991 let buffer = self.buffer.read(cx).snapshot(cx);
12992 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
12993
12994 fn update_selection(
12995 selection: &Selection<usize>,
12996 buffer_snap: &MultiBufferSnapshot,
12997 ) -> Option<Selection<usize>> {
12998 let cursor = selection.head();
12999 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13000 for symbol in symbols.iter().rev() {
13001 let start = symbol.range.start.to_offset(buffer_snap);
13002 let end = symbol.range.end.to_offset(buffer_snap);
13003 let new_range = start..end;
13004 if start < selection.start || end > selection.end {
13005 return Some(Selection {
13006 id: selection.id,
13007 start: new_range.start,
13008 end: new_range.end,
13009 goal: SelectionGoal::None,
13010 reversed: selection.reversed,
13011 });
13012 }
13013 }
13014 None
13015 }
13016
13017 let mut selected_larger_symbol = false;
13018 let new_selections = old_selections
13019 .iter()
13020 .map(|selection| match update_selection(selection, &buffer) {
13021 Some(new_selection) => {
13022 if new_selection.range() != selection.range() {
13023 selected_larger_symbol = true;
13024 }
13025 new_selection
13026 }
13027 None => selection.clone(),
13028 })
13029 .collect::<Vec<_>>();
13030
13031 if selected_larger_symbol {
13032 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13033 s.select(new_selections);
13034 });
13035 }
13036 }
13037
13038 pub fn select_larger_syntax_node(
13039 &mut self,
13040 _: &SelectLargerSyntaxNode,
13041 window: &mut Window,
13042 cx: &mut Context<Self>,
13043 ) {
13044 let Some(visible_row_count) = self.visible_row_count() else {
13045 return;
13046 };
13047 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13048 if old_selections.is_empty() {
13049 return;
13050 }
13051
13052 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13053
13054 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13055 let buffer = self.buffer.read(cx).snapshot(cx);
13056
13057 let mut selected_larger_node = false;
13058 let mut new_selections = old_selections
13059 .iter()
13060 .map(|selection| {
13061 let old_range = selection.start..selection.end;
13062
13063 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13064 // manually select word at selection
13065 if ["string_content", "inline"].contains(&node.kind()) {
13066 let word_range = {
13067 let display_point = buffer
13068 .offset_to_point(old_range.start)
13069 .to_display_point(&display_map);
13070 let Range { start, end } =
13071 movement::surrounding_word(&display_map, display_point);
13072 start.to_point(&display_map).to_offset(&buffer)
13073 ..end.to_point(&display_map).to_offset(&buffer)
13074 };
13075 // ignore if word is already selected
13076 if !word_range.is_empty() && old_range != word_range {
13077 let last_word_range = {
13078 let display_point = buffer
13079 .offset_to_point(old_range.end)
13080 .to_display_point(&display_map);
13081 let Range { start, end } =
13082 movement::surrounding_word(&display_map, display_point);
13083 start.to_point(&display_map).to_offset(&buffer)
13084 ..end.to_point(&display_map).to_offset(&buffer)
13085 };
13086 // only select word if start and end point belongs to same word
13087 if word_range == last_word_range {
13088 selected_larger_node = true;
13089 return Selection {
13090 id: selection.id,
13091 start: word_range.start,
13092 end: word_range.end,
13093 goal: SelectionGoal::None,
13094 reversed: selection.reversed,
13095 };
13096 }
13097 }
13098 }
13099 }
13100
13101 let mut new_range = old_range.clone();
13102 while let Some((_node, containing_range)) =
13103 buffer.syntax_ancestor(new_range.clone())
13104 {
13105 new_range = match containing_range {
13106 MultiOrSingleBufferOffsetRange::Single(_) => break,
13107 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13108 };
13109 if !display_map.intersects_fold(new_range.start)
13110 && !display_map.intersects_fold(new_range.end)
13111 {
13112 break;
13113 }
13114 }
13115
13116 selected_larger_node |= new_range != old_range;
13117 Selection {
13118 id: selection.id,
13119 start: new_range.start,
13120 end: new_range.end,
13121 goal: SelectionGoal::None,
13122 reversed: selection.reversed,
13123 }
13124 })
13125 .collect::<Vec<_>>();
13126
13127 if !selected_larger_node {
13128 return; // don't put this call in the history
13129 }
13130
13131 // scroll based on transformation done to the last selection created by the user
13132 let (last_old, last_new) = old_selections
13133 .last()
13134 .zip(new_selections.last().cloned())
13135 .expect("old_selections isn't empty");
13136
13137 // revert selection
13138 let is_selection_reversed = {
13139 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13140 new_selections.last_mut().expect("checked above").reversed =
13141 should_newest_selection_be_reversed;
13142 should_newest_selection_be_reversed
13143 };
13144
13145 if selected_larger_node {
13146 self.select_syntax_node_history.disable_clearing = true;
13147 self.change_selections(None, window, cx, |s| {
13148 s.select(new_selections.clone());
13149 });
13150 self.select_syntax_node_history.disable_clearing = false;
13151 }
13152
13153 let start_row = last_new.start.to_display_point(&display_map).row().0;
13154 let end_row = last_new.end.to_display_point(&display_map).row().0;
13155 let selection_height = end_row - start_row + 1;
13156 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13157
13158 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13159 let scroll_behavior = if fits_on_the_screen {
13160 self.request_autoscroll(Autoscroll::fit(), cx);
13161 SelectSyntaxNodeScrollBehavior::FitSelection
13162 } else if is_selection_reversed {
13163 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13164 SelectSyntaxNodeScrollBehavior::CursorTop
13165 } else {
13166 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13167 SelectSyntaxNodeScrollBehavior::CursorBottom
13168 };
13169
13170 self.select_syntax_node_history.push((
13171 old_selections,
13172 scroll_behavior,
13173 is_selection_reversed,
13174 ));
13175 }
13176
13177 pub fn select_smaller_syntax_node(
13178 &mut self,
13179 _: &SelectSmallerSyntaxNode,
13180 window: &mut Window,
13181 cx: &mut Context<Self>,
13182 ) {
13183 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13184
13185 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13186 self.select_syntax_node_history.pop()
13187 {
13188 if let Some(selection) = selections.last_mut() {
13189 selection.reversed = is_selection_reversed;
13190 }
13191
13192 self.select_syntax_node_history.disable_clearing = true;
13193 self.change_selections(None, window, cx, |s| {
13194 s.select(selections.to_vec());
13195 });
13196 self.select_syntax_node_history.disable_clearing = false;
13197
13198 match scroll_behavior {
13199 SelectSyntaxNodeScrollBehavior::CursorTop => {
13200 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13201 }
13202 SelectSyntaxNodeScrollBehavior::FitSelection => {
13203 self.request_autoscroll(Autoscroll::fit(), cx);
13204 }
13205 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13206 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13207 }
13208 }
13209 }
13210 }
13211
13212 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13213 if !EditorSettings::get_global(cx).gutter.runnables {
13214 self.clear_tasks();
13215 return Task::ready(());
13216 }
13217 let project = self.project.as_ref().map(Entity::downgrade);
13218 let task_sources = self.lsp_task_sources(cx);
13219 cx.spawn_in(window, async move |editor, cx| {
13220 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13221 let Some(project) = project.and_then(|p| p.upgrade()) else {
13222 return;
13223 };
13224 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13225 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13226 }) else {
13227 return;
13228 };
13229
13230 let hide_runnables = project
13231 .update(cx, |project, cx| {
13232 // Do not display any test indicators in non-dev server remote projects.
13233 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13234 })
13235 .unwrap_or(true);
13236 if hide_runnables {
13237 return;
13238 }
13239 let new_rows =
13240 cx.background_spawn({
13241 let snapshot = display_snapshot.clone();
13242 async move {
13243 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13244 }
13245 })
13246 .await;
13247 let Ok(lsp_tasks) =
13248 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13249 else {
13250 return;
13251 };
13252 let lsp_tasks = lsp_tasks.await;
13253
13254 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13255 lsp_tasks
13256 .into_iter()
13257 .flat_map(|(kind, tasks)| {
13258 tasks.into_iter().filter_map(move |(location, task)| {
13259 Some((kind.clone(), location?, task))
13260 })
13261 })
13262 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13263 let buffer = location.target.buffer;
13264 let buffer_snapshot = buffer.read(cx).snapshot();
13265 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13266 |(excerpt_id, snapshot, _)| {
13267 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13268 display_snapshot
13269 .buffer_snapshot
13270 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13271 } else {
13272 None
13273 }
13274 },
13275 );
13276 if let Some(offset) = offset {
13277 let task_buffer_range =
13278 location.target.range.to_point(&buffer_snapshot);
13279 let context_buffer_range =
13280 task_buffer_range.to_offset(&buffer_snapshot);
13281 let context_range = BufferOffset(context_buffer_range.start)
13282 ..BufferOffset(context_buffer_range.end);
13283
13284 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13285 .or_insert_with(|| RunnableTasks {
13286 templates: Vec::new(),
13287 offset,
13288 column: task_buffer_range.start.column,
13289 extra_variables: HashMap::default(),
13290 context_range,
13291 })
13292 .templates
13293 .push((kind, task.original_task().clone()));
13294 }
13295
13296 acc
13297 })
13298 }) else {
13299 return;
13300 };
13301
13302 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
13303 editor
13304 .update(cx, |editor, _| {
13305 editor.clear_tasks();
13306 for (key, mut value) in rows {
13307 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13308 value.templates.extend(lsp_tasks.templates);
13309 }
13310
13311 editor.insert_tasks(key, value);
13312 }
13313 for (key, value) in lsp_tasks_by_rows {
13314 editor.insert_tasks(key, value);
13315 }
13316 })
13317 .ok();
13318 })
13319 }
13320 fn fetch_runnable_ranges(
13321 snapshot: &DisplaySnapshot,
13322 range: Range<Anchor>,
13323 ) -> Vec<language::RunnableRange> {
13324 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13325 }
13326
13327 fn runnable_rows(
13328 project: Entity<Project>,
13329 snapshot: DisplaySnapshot,
13330 runnable_ranges: Vec<RunnableRange>,
13331 mut cx: AsyncWindowContext,
13332 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13333 runnable_ranges
13334 .into_iter()
13335 .filter_map(|mut runnable| {
13336 let tasks = cx
13337 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13338 .ok()?;
13339 if tasks.is_empty() {
13340 return None;
13341 }
13342
13343 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13344
13345 let row = snapshot
13346 .buffer_snapshot
13347 .buffer_line_for_row(MultiBufferRow(point.row))?
13348 .1
13349 .start
13350 .row;
13351
13352 let context_range =
13353 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13354 Some((
13355 (runnable.buffer_id, row),
13356 RunnableTasks {
13357 templates: tasks,
13358 offset: snapshot
13359 .buffer_snapshot
13360 .anchor_before(runnable.run_range.start),
13361 context_range,
13362 column: point.column,
13363 extra_variables: runnable.extra_captures,
13364 },
13365 ))
13366 })
13367 .collect()
13368 }
13369
13370 fn templates_with_tags(
13371 project: &Entity<Project>,
13372 runnable: &mut Runnable,
13373 cx: &mut App,
13374 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13375 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13376 let (worktree_id, file) = project
13377 .buffer_for_id(runnable.buffer, cx)
13378 .and_then(|buffer| buffer.read(cx).file())
13379 .map(|file| (file.worktree_id(cx), file.clone()))
13380 .unzip();
13381
13382 (
13383 project.task_store().read(cx).task_inventory().cloned(),
13384 worktree_id,
13385 file,
13386 )
13387 });
13388
13389 let mut templates_with_tags = mem::take(&mut runnable.tags)
13390 .into_iter()
13391 .flat_map(|RunnableTag(tag)| {
13392 inventory
13393 .as_ref()
13394 .into_iter()
13395 .flat_map(|inventory| {
13396 inventory.read(cx).list_tasks(
13397 file.clone(),
13398 Some(runnable.language.clone()),
13399 worktree_id,
13400 cx,
13401 )
13402 })
13403 .filter(move |(_, template)| {
13404 template.tags.iter().any(|source_tag| source_tag == &tag)
13405 })
13406 })
13407 .sorted_by_key(|(kind, _)| kind.to_owned())
13408 .collect::<Vec<_>>();
13409 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13410 // Strongest source wins; if we have worktree tag binding, prefer that to
13411 // global and language bindings;
13412 // if we have a global binding, prefer that to language binding.
13413 let first_mismatch = templates_with_tags
13414 .iter()
13415 .position(|(tag_source, _)| tag_source != leading_tag_source);
13416 if let Some(index) = first_mismatch {
13417 templates_with_tags.truncate(index);
13418 }
13419 }
13420
13421 templates_with_tags
13422 }
13423
13424 pub fn move_to_enclosing_bracket(
13425 &mut self,
13426 _: &MoveToEnclosingBracket,
13427 window: &mut Window,
13428 cx: &mut Context<Self>,
13429 ) {
13430 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13431 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13432 s.move_offsets_with(|snapshot, selection| {
13433 let Some(enclosing_bracket_ranges) =
13434 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13435 else {
13436 return;
13437 };
13438
13439 let mut best_length = usize::MAX;
13440 let mut best_inside = false;
13441 let mut best_in_bracket_range = false;
13442 let mut best_destination = None;
13443 for (open, close) in enclosing_bracket_ranges {
13444 let close = close.to_inclusive();
13445 let length = close.end() - open.start;
13446 let inside = selection.start >= open.end && selection.end <= *close.start();
13447 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13448 || close.contains(&selection.head());
13449
13450 // If best is next to a bracket and current isn't, skip
13451 if !in_bracket_range && best_in_bracket_range {
13452 continue;
13453 }
13454
13455 // Prefer smaller lengths unless best is inside and current isn't
13456 if length > best_length && (best_inside || !inside) {
13457 continue;
13458 }
13459
13460 best_length = length;
13461 best_inside = inside;
13462 best_in_bracket_range = in_bracket_range;
13463 best_destination = Some(
13464 if close.contains(&selection.start) && close.contains(&selection.end) {
13465 if inside { open.end } else { open.start }
13466 } else if inside {
13467 *close.start()
13468 } else {
13469 *close.end()
13470 },
13471 );
13472 }
13473
13474 if let Some(destination) = best_destination {
13475 selection.collapse_to(destination, SelectionGoal::None);
13476 }
13477 })
13478 });
13479 }
13480
13481 pub fn undo_selection(
13482 &mut self,
13483 _: &UndoSelection,
13484 window: &mut Window,
13485 cx: &mut Context<Self>,
13486 ) {
13487 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13488 self.end_selection(window, cx);
13489 self.selection_history.mode = SelectionHistoryMode::Undoing;
13490 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13491 self.change_selections(None, window, cx, |s| {
13492 s.select_anchors(entry.selections.to_vec())
13493 });
13494 self.select_next_state = entry.select_next_state;
13495 self.select_prev_state = entry.select_prev_state;
13496 self.add_selections_state = entry.add_selections_state;
13497 self.request_autoscroll(Autoscroll::newest(), cx);
13498 }
13499 self.selection_history.mode = SelectionHistoryMode::Normal;
13500 }
13501
13502 pub fn redo_selection(
13503 &mut self,
13504 _: &RedoSelection,
13505 window: &mut Window,
13506 cx: &mut Context<Self>,
13507 ) {
13508 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13509 self.end_selection(window, cx);
13510 self.selection_history.mode = SelectionHistoryMode::Redoing;
13511 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13512 self.change_selections(None, window, cx, |s| {
13513 s.select_anchors(entry.selections.to_vec())
13514 });
13515 self.select_next_state = entry.select_next_state;
13516 self.select_prev_state = entry.select_prev_state;
13517 self.add_selections_state = entry.add_selections_state;
13518 self.request_autoscroll(Autoscroll::newest(), cx);
13519 }
13520 self.selection_history.mode = SelectionHistoryMode::Normal;
13521 }
13522
13523 pub fn expand_excerpts(
13524 &mut self,
13525 action: &ExpandExcerpts,
13526 _: &mut Window,
13527 cx: &mut Context<Self>,
13528 ) {
13529 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13530 }
13531
13532 pub fn expand_excerpts_down(
13533 &mut self,
13534 action: &ExpandExcerptsDown,
13535 _: &mut Window,
13536 cx: &mut Context<Self>,
13537 ) {
13538 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13539 }
13540
13541 pub fn expand_excerpts_up(
13542 &mut self,
13543 action: &ExpandExcerptsUp,
13544 _: &mut Window,
13545 cx: &mut Context<Self>,
13546 ) {
13547 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13548 }
13549
13550 pub fn expand_excerpts_for_direction(
13551 &mut self,
13552 lines: u32,
13553 direction: ExpandExcerptDirection,
13554
13555 cx: &mut Context<Self>,
13556 ) {
13557 let selections = self.selections.disjoint_anchors();
13558
13559 let lines = if lines == 0 {
13560 EditorSettings::get_global(cx).expand_excerpt_lines
13561 } else {
13562 lines
13563 };
13564
13565 self.buffer.update(cx, |buffer, cx| {
13566 let snapshot = buffer.snapshot(cx);
13567 let mut excerpt_ids = selections
13568 .iter()
13569 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13570 .collect::<Vec<_>>();
13571 excerpt_ids.sort();
13572 excerpt_ids.dedup();
13573 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13574 })
13575 }
13576
13577 pub fn expand_excerpt(
13578 &mut self,
13579 excerpt: ExcerptId,
13580 direction: ExpandExcerptDirection,
13581 window: &mut Window,
13582 cx: &mut Context<Self>,
13583 ) {
13584 let current_scroll_position = self.scroll_position(cx);
13585 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13586 let mut should_scroll_up = false;
13587
13588 if direction == ExpandExcerptDirection::Down {
13589 let multi_buffer = self.buffer.read(cx);
13590 let snapshot = multi_buffer.snapshot(cx);
13591 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13592 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13593 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13594 let buffer_snapshot = buffer.read(cx).snapshot();
13595 let excerpt_end_row =
13596 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13597 let last_row = buffer_snapshot.max_point().row;
13598 let lines_below = last_row.saturating_sub(excerpt_end_row);
13599 should_scroll_up = lines_below >= lines_to_expand;
13600 }
13601 }
13602 }
13603 }
13604
13605 self.buffer.update(cx, |buffer, cx| {
13606 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13607 });
13608
13609 if should_scroll_up {
13610 let new_scroll_position =
13611 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13612 self.set_scroll_position(new_scroll_position, window, cx);
13613 }
13614 }
13615
13616 pub fn go_to_singleton_buffer_point(
13617 &mut self,
13618 point: Point,
13619 window: &mut Window,
13620 cx: &mut Context<Self>,
13621 ) {
13622 self.go_to_singleton_buffer_range(point..point, window, cx);
13623 }
13624
13625 pub fn go_to_singleton_buffer_range(
13626 &mut self,
13627 range: Range<Point>,
13628 window: &mut Window,
13629 cx: &mut Context<Self>,
13630 ) {
13631 let multibuffer = self.buffer().read(cx);
13632 let Some(buffer) = multibuffer.as_singleton() else {
13633 return;
13634 };
13635 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13636 return;
13637 };
13638 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13639 return;
13640 };
13641 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13642 s.select_anchor_ranges([start..end])
13643 });
13644 }
13645
13646 pub fn go_to_diagnostic(
13647 &mut self,
13648 _: &GoToDiagnostic,
13649 window: &mut Window,
13650 cx: &mut Context<Self>,
13651 ) {
13652 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13653 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13654 }
13655
13656 pub fn go_to_prev_diagnostic(
13657 &mut self,
13658 _: &GoToPreviousDiagnostic,
13659 window: &mut Window,
13660 cx: &mut Context<Self>,
13661 ) {
13662 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13663 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13664 }
13665
13666 pub fn go_to_diagnostic_impl(
13667 &mut self,
13668 direction: Direction,
13669 window: &mut Window,
13670 cx: &mut Context<Self>,
13671 ) {
13672 let buffer = self.buffer.read(cx).snapshot(cx);
13673 let selection = self.selections.newest::<usize>(cx);
13674
13675 let mut active_group_id = None;
13676 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13677 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13678 active_group_id = Some(active_group.group_id);
13679 }
13680 }
13681
13682 fn filtered(
13683 snapshot: EditorSnapshot,
13684 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13685 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13686 diagnostics
13687 .filter(|entry| entry.range.start != entry.range.end)
13688 .filter(|entry| !entry.diagnostic.is_unnecessary)
13689 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13690 }
13691
13692 let snapshot = self.snapshot(window, cx);
13693 let before = filtered(
13694 snapshot.clone(),
13695 buffer
13696 .diagnostics_in_range(0..selection.start)
13697 .filter(|entry| entry.range.start <= selection.start),
13698 );
13699 let after = filtered(
13700 snapshot,
13701 buffer
13702 .diagnostics_in_range(selection.start..buffer.len())
13703 .filter(|entry| entry.range.start >= selection.start),
13704 );
13705
13706 let mut found: Option<DiagnosticEntry<usize>> = None;
13707 if direction == Direction::Prev {
13708 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13709 {
13710 for diagnostic in prev_diagnostics.into_iter().rev() {
13711 if diagnostic.range.start != selection.start
13712 || active_group_id
13713 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13714 {
13715 found = Some(diagnostic);
13716 break 'outer;
13717 }
13718 }
13719 }
13720 } else {
13721 for diagnostic in after.chain(before) {
13722 if diagnostic.range.start != selection.start
13723 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13724 {
13725 found = Some(diagnostic);
13726 break;
13727 }
13728 }
13729 }
13730 let Some(next_diagnostic) = found else {
13731 return;
13732 };
13733
13734 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13735 return;
13736 };
13737 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13738 s.select_ranges(vec![
13739 next_diagnostic.range.start..next_diagnostic.range.start,
13740 ])
13741 });
13742 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13743 self.refresh_inline_completion(false, true, window, cx);
13744 }
13745
13746 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13747 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13748 let snapshot = self.snapshot(window, cx);
13749 let selection = self.selections.newest::<Point>(cx);
13750 self.go_to_hunk_before_or_after_position(
13751 &snapshot,
13752 selection.head(),
13753 Direction::Next,
13754 window,
13755 cx,
13756 );
13757 }
13758
13759 pub fn go_to_hunk_before_or_after_position(
13760 &mut self,
13761 snapshot: &EditorSnapshot,
13762 position: Point,
13763 direction: Direction,
13764 window: &mut Window,
13765 cx: &mut Context<Editor>,
13766 ) {
13767 let row = if direction == Direction::Next {
13768 self.hunk_after_position(snapshot, position)
13769 .map(|hunk| hunk.row_range.start)
13770 } else {
13771 self.hunk_before_position(snapshot, position)
13772 };
13773
13774 if let Some(row) = row {
13775 let destination = Point::new(row.0, 0);
13776 let autoscroll = Autoscroll::center();
13777
13778 self.unfold_ranges(&[destination..destination], false, false, cx);
13779 self.change_selections(Some(autoscroll), window, cx, |s| {
13780 s.select_ranges([destination..destination]);
13781 });
13782 }
13783 }
13784
13785 fn hunk_after_position(
13786 &mut self,
13787 snapshot: &EditorSnapshot,
13788 position: Point,
13789 ) -> Option<MultiBufferDiffHunk> {
13790 snapshot
13791 .buffer_snapshot
13792 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13793 .find(|hunk| hunk.row_range.start.0 > position.row)
13794 .or_else(|| {
13795 snapshot
13796 .buffer_snapshot
13797 .diff_hunks_in_range(Point::zero()..position)
13798 .find(|hunk| hunk.row_range.end.0 < position.row)
13799 })
13800 }
13801
13802 fn go_to_prev_hunk(
13803 &mut self,
13804 _: &GoToPreviousHunk,
13805 window: &mut Window,
13806 cx: &mut Context<Self>,
13807 ) {
13808 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13809 let snapshot = self.snapshot(window, cx);
13810 let selection = self.selections.newest::<Point>(cx);
13811 self.go_to_hunk_before_or_after_position(
13812 &snapshot,
13813 selection.head(),
13814 Direction::Prev,
13815 window,
13816 cx,
13817 );
13818 }
13819
13820 fn hunk_before_position(
13821 &mut self,
13822 snapshot: &EditorSnapshot,
13823 position: Point,
13824 ) -> Option<MultiBufferRow> {
13825 snapshot
13826 .buffer_snapshot
13827 .diff_hunk_before(position)
13828 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
13829 }
13830
13831 fn go_to_next_change(
13832 &mut self,
13833 _: &GoToNextChange,
13834 window: &mut Window,
13835 cx: &mut Context<Self>,
13836 ) {
13837 if let Some(selections) = self
13838 .change_list
13839 .next_change(1, Direction::Next)
13840 .map(|s| s.to_vec())
13841 {
13842 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13843 let map = s.display_map();
13844 s.select_display_ranges(selections.iter().map(|a| {
13845 let point = a.to_display_point(&map);
13846 point..point
13847 }))
13848 })
13849 }
13850 }
13851
13852 fn go_to_previous_change(
13853 &mut self,
13854 _: &GoToPreviousChange,
13855 window: &mut Window,
13856 cx: &mut Context<Self>,
13857 ) {
13858 if let Some(selections) = self
13859 .change_list
13860 .next_change(1, Direction::Prev)
13861 .map(|s| s.to_vec())
13862 {
13863 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13864 let map = s.display_map();
13865 s.select_display_ranges(selections.iter().map(|a| {
13866 let point = a.to_display_point(&map);
13867 point..point
13868 }))
13869 })
13870 }
13871 }
13872
13873 fn go_to_line<T: 'static>(
13874 &mut self,
13875 position: Anchor,
13876 highlight_color: Option<Hsla>,
13877 window: &mut Window,
13878 cx: &mut Context<Self>,
13879 ) {
13880 let snapshot = self.snapshot(window, cx).display_snapshot;
13881 let position = position.to_point(&snapshot.buffer_snapshot);
13882 let start = snapshot
13883 .buffer_snapshot
13884 .clip_point(Point::new(position.row, 0), Bias::Left);
13885 let end = start + Point::new(1, 0);
13886 let start = snapshot.buffer_snapshot.anchor_before(start);
13887 let end = snapshot.buffer_snapshot.anchor_before(end);
13888
13889 self.highlight_rows::<T>(
13890 start..end,
13891 highlight_color
13892 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
13893 Default::default(),
13894 cx,
13895 );
13896
13897 if self.buffer.read(cx).is_singleton() {
13898 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
13899 }
13900 }
13901
13902 pub fn go_to_definition(
13903 &mut self,
13904 _: &GoToDefinition,
13905 window: &mut Window,
13906 cx: &mut Context<Self>,
13907 ) -> Task<Result<Navigated>> {
13908 let definition =
13909 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
13910 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
13911 cx.spawn_in(window, async move |editor, cx| {
13912 if definition.await? == Navigated::Yes {
13913 return Ok(Navigated::Yes);
13914 }
13915 match fallback_strategy {
13916 GoToDefinitionFallback::None => Ok(Navigated::No),
13917 GoToDefinitionFallback::FindAllReferences => {
13918 match editor.update_in(cx, |editor, window, cx| {
13919 editor.find_all_references(&FindAllReferences, window, cx)
13920 })? {
13921 Some(references) => references.await,
13922 None => Ok(Navigated::No),
13923 }
13924 }
13925 }
13926 })
13927 }
13928
13929 pub fn go_to_declaration(
13930 &mut self,
13931 _: &GoToDeclaration,
13932 window: &mut Window,
13933 cx: &mut Context<Self>,
13934 ) -> Task<Result<Navigated>> {
13935 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
13936 }
13937
13938 pub fn go_to_declaration_split(
13939 &mut self,
13940 _: &GoToDeclaration,
13941 window: &mut Window,
13942 cx: &mut Context<Self>,
13943 ) -> Task<Result<Navigated>> {
13944 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
13945 }
13946
13947 pub fn go_to_implementation(
13948 &mut self,
13949 _: &GoToImplementation,
13950 window: &mut Window,
13951 cx: &mut Context<Self>,
13952 ) -> Task<Result<Navigated>> {
13953 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
13954 }
13955
13956 pub fn go_to_implementation_split(
13957 &mut self,
13958 _: &GoToImplementationSplit,
13959 window: &mut Window,
13960 cx: &mut Context<Self>,
13961 ) -> Task<Result<Navigated>> {
13962 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
13963 }
13964
13965 pub fn go_to_type_definition(
13966 &mut self,
13967 _: &GoToTypeDefinition,
13968 window: &mut Window,
13969 cx: &mut Context<Self>,
13970 ) -> Task<Result<Navigated>> {
13971 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
13972 }
13973
13974 pub fn go_to_definition_split(
13975 &mut self,
13976 _: &GoToDefinitionSplit,
13977 window: &mut Window,
13978 cx: &mut Context<Self>,
13979 ) -> Task<Result<Navigated>> {
13980 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
13981 }
13982
13983 pub fn go_to_type_definition_split(
13984 &mut self,
13985 _: &GoToTypeDefinitionSplit,
13986 window: &mut Window,
13987 cx: &mut Context<Self>,
13988 ) -> Task<Result<Navigated>> {
13989 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
13990 }
13991
13992 fn go_to_definition_of_kind(
13993 &mut self,
13994 kind: GotoDefinitionKind,
13995 split: bool,
13996 window: &mut Window,
13997 cx: &mut Context<Self>,
13998 ) -> Task<Result<Navigated>> {
13999 let Some(provider) = self.semantics_provider.clone() else {
14000 return Task::ready(Ok(Navigated::No));
14001 };
14002 let head = self.selections.newest::<usize>(cx).head();
14003 let buffer = self.buffer.read(cx);
14004 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14005 text_anchor
14006 } else {
14007 return Task::ready(Ok(Navigated::No));
14008 };
14009
14010 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14011 return Task::ready(Ok(Navigated::No));
14012 };
14013
14014 cx.spawn_in(window, async move |editor, cx| {
14015 let definitions = definitions.await?;
14016 let navigated = editor
14017 .update_in(cx, |editor, window, cx| {
14018 editor.navigate_to_hover_links(
14019 Some(kind),
14020 definitions
14021 .into_iter()
14022 .filter(|location| {
14023 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14024 })
14025 .map(HoverLink::Text)
14026 .collect::<Vec<_>>(),
14027 split,
14028 window,
14029 cx,
14030 )
14031 })?
14032 .await?;
14033 anyhow::Ok(navigated)
14034 })
14035 }
14036
14037 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14038 let selection = self.selections.newest_anchor();
14039 let head = selection.head();
14040 let tail = selection.tail();
14041
14042 let Some((buffer, start_position)) =
14043 self.buffer.read(cx).text_anchor_for_position(head, cx)
14044 else {
14045 return;
14046 };
14047
14048 let end_position = if head != tail {
14049 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14050 return;
14051 };
14052 Some(pos)
14053 } else {
14054 None
14055 };
14056
14057 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14058 let url = if let Some(end_pos) = end_position {
14059 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14060 } else {
14061 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14062 };
14063
14064 if let Some(url) = url {
14065 editor.update(cx, |_, cx| {
14066 cx.open_url(&url);
14067 })
14068 } else {
14069 Ok(())
14070 }
14071 });
14072
14073 url_finder.detach();
14074 }
14075
14076 pub fn open_selected_filename(
14077 &mut self,
14078 _: &OpenSelectedFilename,
14079 window: &mut Window,
14080 cx: &mut Context<Self>,
14081 ) {
14082 let Some(workspace) = self.workspace() else {
14083 return;
14084 };
14085
14086 let position = self.selections.newest_anchor().head();
14087
14088 let Some((buffer, buffer_position)) =
14089 self.buffer.read(cx).text_anchor_for_position(position, cx)
14090 else {
14091 return;
14092 };
14093
14094 let project = self.project.clone();
14095
14096 cx.spawn_in(window, async move |_, cx| {
14097 let result = find_file(&buffer, project, buffer_position, cx).await;
14098
14099 if let Some((_, path)) = result {
14100 workspace
14101 .update_in(cx, |workspace, window, cx| {
14102 workspace.open_resolved_path(path, window, cx)
14103 })?
14104 .await?;
14105 }
14106 anyhow::Ok(())
14107 })
14108 .detach();
14109 }
14110
14111 pub(crate) fn navigate_to_hover_links(
14112 &mut self,
14113 kind: Option<GotoDefinitionKind>,
14114 mut definitions: Vec<HoverLink>,
14115 split: bool,
14116 window: &mut Window,
14117 cx: &mut Context<Editor>,
14118 ) -> Task<Result<Navigated>> {
14119 // If there is one definition, just open it directly
14120 if definitions.len() == 1 {
14121 let definition = definitions.pop().unwrap();
14122
14123 enum TargetTaskResult {
14124 Location(Option<Location>),
14125 AlreadyNavigated,
14126 }
14127
14128 let target_task = match definition {
14129 HoverLink::Text(link) => {
14130 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14131 }
14132 HoverLink::InlayHint(lsp_location, server_id) => {
14133 let computation =
14134 self.compute_target_location(lsp_location, server_id, window, cx);
14135 cx.background_spawn(async move {
14136 let location = computation.await?;
14137 Ok(TargetTaskResult::Location(location))
14138 })
14139 }
14140 HoverLink::Url(url) => {
14141 cx.open_url(&url);
14142 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14143 }
14144 HoverLink::File(path) => {
14145 if let Some(workspace) = self.workspace() {
14146 cx.spawn_in(window, async move |_, cx| {
14147 workspace
14148 .update_in(cx, |workspace, window, cx| {
14149 workspace.open_resolved_path(path, window, cx)
14150 })?
14151 .await
14152 .map(|_| TargetTaskResult::AlreadyNavigated)
14153 })
14154 } else {
14155 Task::ready(Ok(TargetTaskResult::Location(None)))
14156 }
14157 }
14158 };
14159 cx.spawn_in(window, async move |editor, cx| {
14160 let target = match target_task.await.context("target resolution task")? {
14161 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14162 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14163 TargetTaskResult::Location(Some(target)) => target,
14164 };
14165
14166 editor.update_in(cx, |editor, window, cx| {
14167 let Some(workspace) = editor.workspace() else {
14168 return Navigated::No;
14169 };
14170 let pane = workspace.read(cx).active_pane().clone();
14171
14172 let range = target.range.to_point(target.buffer.read(cx));
14173 let range = editor.range_for_match(&range);
14174 let range = collapse_multiline_range(range);
14175
14176 if !split
14177 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14178 {
14179 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14180 } else {
14181 window.defer(cx, move |window, cx| {
14182 let target_editor: Entity<Self> =
14183 workspace.update(cx, |workspace, cx| {
14184 let pane = if split {
14185 workspace.adjacent_pane(window, cx)
14186 } else {
14187 workspace.active_pane().clone()
14188 };
14189
14190 workspace.open_project_item(
14191 pane,
14192 target.buffer.clone(),
14193 true,
14194 true,
14195 window,
14196 cx,
14197 )
14198 });
14199 target_editor.update(cx, |target_editor, cx| {
14200 // When selecting a definition in a different buffer, disable the nav history
14201 // to avoid creating a history entry at the previous cursor location.
14202 pane.update(cx, |pane, _| pane.disable_history());
14203 target_editor.go_to_singleton_buffer_range(range, window, cx);
14204 pane.update(cx, |pane, _| pane.enable_history());
14205 });
14206 });
14207 }
14208 Navigated::Yes
14209 })
14210 })
14211 } else if !definitions.is_empty() {
14212 cx.spawn_in(window, async move |editor, cx| {
14213 let (title, location_tasks, workspace) = editor
14214 .update_in(cx, |editor, window, cx| {
14215 let tab_kind = match kind {
14216 Some(GotoDefinitionKind::Implementation) => "Implementations",
14217 _ => "Definitions",
14218 };
14219 let title = definitions
14220 .iter()
14221 .find_map(|definition| match definition {
14222 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14223 let buffer = origin.buffer.read(cx);
14224 format!(
14225 "{} for {}",
14226 tab_kind,
14227 buffer
14228 .text_for_range(origin.range.clone())
14229 .collect::<String>()
14230 )
14231 }),
14232 HoverLink::InlayHint(_, _) => None,
14233 HoverLink::Url(_) => None,
14234 HoverLink::File(_) => None,
14235 })
14236 .unwrap_or(tab_kind.to_string());
14237 let location_tasks = definitions
14238 .into_iter()
14239 .map(|definition| match definition {
14240 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14241 HoverLink::InlayHint(lsp_location, server_id) => editor
14242 .compute_target_location(lsp_location, server_id, window, cx),
14243 HoverLink::Url(_) => Task::ready(Ok(None)),
14244 HoverLink::File(_) => Task::ready(Ok(None)),
14245 })
14246 .collect::<Vec<_>>();
14247 (title, location_tasks, editor.workspace().clone())
14248 })
14249 .context("location tasks preparation")?;
14250
14251 let locations = future::join_all(location_tasks)
14252 .await
14253 .into_iter()
14254 .filter_map(|location| location.transpose())
14255 .collect::<Result<_>>()
14256 .context("location tasks")?;
14257
14258 let Some(workspace) = workspace else {
14259 return Ok(Navigated::No);
14260 };
14261 let opened = workspace
14262 .update_in(cx, |workspace, window, cx| {
14263 Self::open_locations_in_multibuffer(
14264 workspace,
14265 locations,
14266 title,
14267 split,
14268 MultibufferSelectionMode::First,
14269 window,
14270 cx,
14271 )
14272 })
14273 .ok();
14274
14275 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14276 })
14277 } else {
14278 Task::ready(Ok(Navigated::No))
14279 }
14280 }
14281
14282 fn compute_target_location(
14283 &self,
14284 lsp_location: lsp::Location,
14285 server_id: LanguageServerId,
14286 window: &mut Window,
14287 cx: &mut Context<Self>,
14288 ) -> Task<anyhow::Result<Option<Location>>> {
14289 let Some(project) = self.project.clone() else {
14290 return Task::ready(Ok(None));
14291 };
14292
14293 cx.spawn_in(window, async move |editor, cx| {
14294 let location_task = editor.update(cx, |_, cx| {
14295 project.update(cx, |project, cx| {
14296 let language_server_name = project
14297 .language_server_statuses(cx)
14298 .find(|(id, _)| server_id == *id)
14299 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14300 language_server_name.map(|language_server_name| {
14301 project.open_local_buffer_via_lsp(
14302 lsp_location.uri.clone(),
14303 server_id,
14304 language_server_name,
14305 cx,
14306 )
14307 })
14308 })
14309 })?;
14310 let location = match location_task {
14311 Some(task) => Some({
14312 let target_buffer_handle = task.await.context("open local buffer")?;
14313 let range = target_buffer_handle.update(cx, |target_buffer, _| {
14314 let target_start = target_buffer
14315 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14316 let target_end = target_buffer
14317 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14318 target_buffer.anchor_after(target_start)
14319 ..target_buffer.anchor_before(target_end)
14320 })?;
14321 Location {
14322 buffer: target_buffer_handle,
14323 range,
14324 }
14325 }),
14326 None => None,
14327 };
14328 Ok(location)
14329 })
14330 }
14331
14332 pub fn find_all_references(
14333 &mut self,
14334 _: &FindAllReferences,
14335 window: &mut Window,
14336 cx: &mut Context<Self>,
14337 ) -> Option<Task<Result<Navigated>>> {
14338 let selection = self.selections.newest::<usize>(cx);
14339 let multi_buffer = self.buffer.read(cx);
14340 let head = selection.head();
14341
14342 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14343 let head_anchor = multi_buffer_snapshot.anchor_at(
14344 head,
14345 if head < selection.tail() {
14346 Bias::Right
14347 } else {
14348 Bias::Left
14349 },
14350 );
14351
14352 match self
14353 .find_all_references_task_sources
14354 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14355 {
14356 Ok(_) => {
14357 log::info!(
14358 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14359 );
14360 return None;
14361 }
14362 Err(i) => {
14363 self.find_all_references_task_sources.insert(i, head_anchor);
14364 }
14365 }
14366
14367 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14368 let workspace = self.workspace()?;
14369 let project = workspace.read(cx).project().clone();
14370 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14371 Some(cx.spawn_in(window, async move |editor, cx| {
14372 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14373 if let Ok(i) = editor
14374 .find_all_references_task_sources
14375 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14376 {
14377 editor.find_all_references_task_sources.remove(i);
14378 }
14379 });
14380
14381 let locations = references.await?;
14382 if locations.is_empty() {
14383 return anyhow::Ok(Navigated::No);
14384 }
14385
14386 workspace.update_in(cx, |workspace, window, cx| {
14387 let title = locations
14388 .first()
14389 .as_ref()
14390 .map(|location| {
14391 let buffer = location.buffer.read(cx);
14392 format!(
14393 "References to `{}`",
14394 buffer
14395 .text_for_range(location.range.clone())
14396 .collect::<String>()
14397 )
14398 })
14399 .unwrap();
14400 Self::open_locations_in_multibuffer(
14401 workspace,
14402 locations,
14403 title,
14404 false,
14405 MultibufferSelectionMode::First,
14406 window,
14407 cx,
14408 );
14409 Navigated::Yes
14410 })
14411 }))
14412 }
14413
14414 /// Opens a multibuffer with the given project locations in it
14415 pub fn open_locations_in_multibuffer(
14416 workspace: &mut Workspace,
14417 mut locations: Vec<Location>,
14418 title: String,
14419 split: bool,
14420 multibuffer_selection_mode: MultibufferSelectionMode,
14421 window: &mut Window,
14422 cx: &mut Context<Workspace>,
14423 ) {
14424 // If there are multiple definitions, open them in a multibuffer
14425 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14426 let mut locations = locations.into_iter().peekable();
14427 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14428 let capability = workspace.project().read(cx).capability();
14429
14430 let excerpt_buffer = cx.new(|cx| {
14431 let mut multibuffer = MultiBuffer::new(capability);
14432 while let Some(location) = locations.next() {
14433 let buffer = location.buffer.read(cx);
14434 let mut ranges_for_buffer = Vec::new();
14435 let range = location.range.to_point(buffer);
14436 ranges_for_buffer.push(range.clone());
14437
14438 while let Some(next_location) = locations.peek() {
14439 if next_location.buffer == location.buffer {
14440 ranges_for_buffer.push(next_location.range.to_point(buffer));
14441 locations.next();
14442 } else {
14443 break;
14444 }
14445 }
14446
14447 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14448 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14449 PathKey::for_buffer(&location.buffer, cx),
14450 location.buffer.clone(),
14451 ranges_for_buffer,
14452 DEFAULT_MULTIBUFFER_CONTEXT,
14453 cx,
14454 );
14455 ranges.extend(new_ranges)
14456 }
14457
14458 multibuffer.with_title(title)
14459 });
14460
14461 let editor = cx.new(|cx| {
14462 Editor::for_multibuffer(
14463 excerpt_buffer,
14464 Some(workspace.project().clone()),
14465 window,
14466 cx,
14467 )
14468 });
14469 editor.update(cx, |editor, cx| {
14470 match multibuffer_selection_mode {
14471 MultibufferSelectionMode::First => {
14472 if let Some(first_range) = ranges.first() {
14473 editor.change_selections(None, window, cx, |selections| {
14474 selections.clear_disjoint();
14475 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14476 });
14477 }
14478 editor.highlight_background::<Self>(
14479 &ranges,
14480 |theme| theme.editor_highlighted_line_background,
14481 cx,
14482 );
14483 }
14484 MultibufferSelectionMode::All => {
14485 editor.change_selections(None, window, cx, |selections| {
14486 selections.clear_disjoint();
14487 selections.select_anchor_ranges(ranges);
14488 });
14489 }
14490 }
14491 editor.register_buffers_with_language_servers(cx);
14492 });
14493
14494 let item = Box::new(editor);
14495 let item_id = item.item_id();
14496
14497 if split {
14498 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14499 } else {
14500 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14501 let (preview_item_id, preview_item_idx) =
14502 workspace.active_pane().update(cx, |pane, _| {
14503 (pane.preview_item_id(), pane.preview_item_idx())
14504 });
14505
14506 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14507
14508 if let Some(preview_item_id) = preview_item_id {
14509 workspace.active_pane().update(cx, |pane, cx| {
14510 pane.remove_item(preview_item_id, false, false, window, cx);
14511 });
14512 }
14513 } else {
14514 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14515 }
14516 }
14517 workspace.active_pane().update(cx, |pane, cx| {
14518 pane.set_preview_item_id(Some(item_id), cx);
14519 });
14520 }
14521
14522 pub fn rename(
14523 &mut self,
14524 _: &Rename,
14525 window: &mut Window,
14526 cx: &mut Context<Self>,
14527 ) -> Option<Task<Result<()>>> {
14528 use language::ToOffset as _;
14529
14530 let provider = self.semantics_provider.clone()?;
14531 let selection = self.selections.newest_anchor().clone();
14532 let (cursor_buffer, cursor_buffer_position) = self
14533 .buffer
14534 .read(cx)
14535 .text_anchor_for_position(selection.head(), cx)?;
14536 let (tail_buffer, cursor_buffer_position_end) = self
14537 .buffer
14538 .read(cx)
14539 .text_anchor_for_position(selection.tail(), cx)?;
14540 if tail_buffer != cursor_buffer {
14541 return None;
14542 }
14543
14544 let snapshot = cursor_buffer.read(cx).snapshot();
14545 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14546 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14547 let prepare_rename = provider
14548 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14549 .unwrap_or_else(|| Task::ready(Ok(None)));
14550 drop(snapshot);
14551
14552 Some(cx.spawn_in(window, async move |this, cx| {
14553 let rename_range = if let Some(range) = prepare_rename.await? {
14554 Some(range)
14555 } else {
14556 this.update(cx, |this, cx| {
14557 let buffer = this.buffer.read(cx).snapshot(cx);
14558 let mut buffer_highlights = this
14559 .document_highlights_for_position(selection.head(), &buffer)
14560 .filter(|highlight| {
14561 highlight.start.excerpt_id == selection.head().excerpt_id
14562 && highlight.end.excerpt_id == selection.head().excerpt_id
14563 });
14564 buffer_highlights
14565 .next()
14566 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14567 })?
14568 };
14569 if let Some(rename_range) = rename_range {
14570 this.update_in(cx, |this, window, cx| {
14571 let snapshot = cursor_buffer.read(cx).snapshot();
14572 let rename_buffer_range = rename_range.to_offset(&snapshot);
14573 let cursor_offset_in_rename_range =
14574 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14575 let cursor_offset_in_rename_range_end =
14576 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14577
14578 this.take_rename(false, window, cx);
14579 let buffer = this.buffer.read(cx).read(cx);
14580 let cursor_offset = selection.head().to_offset(&buffer);
14581 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14582 let rename_end = rename_start + rename_buffer_range.len();
14583 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14584 let mut old_highlight_id = None;
14585 let old_name: Arc<str> = buffer
14586 .chunks(rename_start..rename_end, true)
14587 .map(|chunk| {
14588 if old_highlight_id.is_none() {
14589 old_highlight_id = chunk.syntax_highlight_id;
14590 }
14591 chunk.text
14592 })
14593 .collect::<String>()
14594 .into();
14595
14596 drop(buffer);
14597
14598 // Position the selection in the rename editor so that it matches the current selection.
14599 this.show_local_selections = false;
14600 let rename_editor = cx.new(|cx| {
14601 let mut editor = Editor::single_line(window, cx);
14602 editor.buffer.update(cx, |buffer, cx| {
14603 buffer.edit([(0..0, old_name.clone())], None, cx)
14604 });
14605 let rename_selection_range = match cursor_offset_in_rename_range
14606 .cmp(&cursor_offset_in_rename_range_end)
14607 {
14608 Ordering::Equal => {
14609 editor.select_all(&SelectAll, window, cx);
14610 return editor;
14611 }
14612 Ordering::Less => {
14613 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14614 }
14615 Ordering::Greater => {
14616 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14617 }
14618 };
14619 if rename_selection_range.end > old_name.len() {
14620 editor.select_all(&SelectAll, window, cx);
14621 } else {
14622 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14623 s.select_ranges([rename_selection_range]);
14624 });
14625 }
14626 editor
14627 });
14628 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14629 if e == &EditorEvent::Focused {
14630 cx.emit(EditorEvent::FocusedIn)
14631 }
14632 })
14633 .detach();
14634
14635 let write_highlights =
14636 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14637 let read_highlights =
14638 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14639 let ranges = write_highlights
14640 .iter()
14641 .flat_map(|(_, ranges)| ranges.iter())
14642 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14643 .cloned()
14644 .collect();
14645
14646 this.highlight_text::<Rename>(
14647 ranges,
14648 HighlightStyle {
14649 fade_out: Some(0.6),
14650 ..Default::default()
14651 },
14652 cx,
14653 );
14654 let rename_focus_handle = rename_editor.focus_handle(cx);
14655 window.focus(&rename_focus_handle);
14656 let block_id = this.insert_blocks(
14657 [BlockProperties {
14658 style: BlockStyle::Flex,
14659 placement: BlockPlacement::Below(range.start),
14660 height: Some(1),
14661 render: Arc::new({
14662 let rename_editor = rename_editor.clone();
14663 move |cx: &mut BlockContext| {
14664 let mut text_style = cx.editor_style.text.clone();
14665 if let Some(highlight_style) = old_highlight_id
14666 .and_then(|h| h.style(&cx.editor_style.syntax))
14667 {
14668 text_style = text_style.highlight(highlight_style);
14669 }
14670 div()
14671 .block_mouse_down()
14672 .pl(cx.anchor_x)
14673 .child(EditorElement::new(
14674 &rename_editor,
14675 EditorStyle {
14676 background: cx.theme().system().transparent,
14677 local_player: cx.editor_style.local_player,
14678 text: text_style,
14679 scrollbar_width: cx.editor_style.scrollbar_width,
14680 syntax: cx.editor_style.syntax.clone(),
14681 status: cx.editor_style.status.clone(),
14682 inlay_hints_style: HighlightStyle {
14683 font_weight: Some(FontWeight::BOLD),
14684 ..make_inlay_hints_style(cx.app)
14685 },
14686 inline_completion_styles: make_suggestion_styles(
14687 cx.app,
14688 ),
14689 ..EditorStyle::default()
14690 },
14691 ))
14692 .into_any_element()
14693 }
14694 }),
14695 priority: 0,
14696 render_in_minimap: true,
14697 }],
14698 Some(Autoscroll::fit()),
14699 cx,
14700 )[0];
14701 this.pending_rename = Some(RenameState {
14702 range,
14703 old_name,
14704 editor: rename_editor,
14705 block_id,
14706 });
14707 })?;
14708 }
14709
14710 Ok(())
14711 }))
14712 }
14713
14714 pub fn confirm_rename(
14715 &mut self,
14716 _: &ConfirmRename,
14717 window: &mut Window,
14718 cx: &mut Context<Self>,
14719 ) -> Option<Task<Result<()>>> {
14720 let rename = self.take_rename(false, window, cx)?;
14721 let workspace = self.workspace()?.downgrade();
14722 let (buffer, start) = self
14723 .buffer
14724 .read(cx)
14725 .text_anchor_for_position(rename.range.start, cx)?;
14726 let (end_buffer, _) = self
14727 .buffer
14728 .read(cx)
14729 .text_anchor_for_position(rename.range.end, cx)?;
14730 if buffer != end_buffer {
14731 return None;
14732 }
14733
14734 let old_name = rename.old_name;
14735 let new_name = rename.editor.read(cx).text(cx);
14736
14737 let rename = self.semantics_provider.as_ref()?.perform_rename(
14738 &buffer,
14739 start,
14740 new_name.clone(),
14741 cx,
14742 )?;
14743
14744 Some(cx.spawn_in(window, async move |editor, cx| {
14745 let project_transaction = rename.await?;
14746 Self::open_project_transaction(
14747 &editor,
14748 workspace,
14749 project_transaction,
14750 format!("Rename: {} → {}", old_name, new_name),
14751 cx,
14752 )
14753 .await?;
14754
14755 editor.update(cx, |editor, cx| {
14756 editor.refresh_document_highlights(cx);
14757 })?;
14758 Ok(())
14759 }))
14760 }
14761
14762 fn take_rename(
14763 &mut self,
14764 moving_cursor: bool,
14765 window: &mut Window,
14766 cx: &mut Context<Self>,
14767 ) -> Option<RenameState> {
14768 let rename = self.pending_rename.take()?;
14769 if rename.editor.focus_handle(cx).is_focused(window) {
14770 window.focus(&self.focus_handle);
14771 }
14772
14773 self.remove_blocks(
14774 [rename.block_id].into_iter().collect(),
14775 Some(Autoscroll::fit()),
14776 cx,
14777 );
14778 self.clear_highlights::<Rename>(cx);
14779 self.show_local_selections = true;
14780
14781 if moving_cursor {
14782 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14783 editor.selections.newest::<usize>(cx).head()
14784 });
14785
14786 // Update the selection to match the position of the selection inside
14787 // the rename editor.
14788 let snapshot = self.buffer.read(cx).read(cx);
14789 let rename_range = rename.range.to_offset(&snapshot);
14790 let cursor_in_editor = snapshot
14791 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14792 .min(rename_range.end);
14793 drop(snapshot);
14794
14795 self.change_selections(None, window, cx, |s| {
14796 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14797 });
14798 } else {
14799 self.refresh_document_highlights(cx);
14800 }
14801
14802 Some(rename)
14803 }
14804
14805 pub fn pending_rename(&self) -> Option<&RenameState> {
14806 self.pending_rename.as_ref()
14807 }
14808
14809 fn format(
14810 &mut self,
14811 _: &Format,
14812 window: &mut Window,
14813 cx: &mut Context<Self>,
14814 ) -> Option<Task<Result<()>>> {
14815 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14816
14817 let project = match &self.project {
14818 Some(project) => project.clone(),
14819 None => return None,
14820 };
14821
14822 Some(self.perform_format(
14823 project,
14824 FormatTrigger::Manual,
14825 FormatTarget::Buffers,
14826 window,
14827 cx,
14828 ))
14829 }
14830
14831 fn format_selections(
14832 &mut self,
14833 _: &FormatSelections,
14834 window: &mut Window,
14835 cx: &mut Context<Self>,
14836 ) -> Option<Task<Result<()>>> {
14837 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14838
14839 let project = match &self.project {
14840 Some(project) => project.clone(),
14841 None => return None,
14842 };
14843
14844 let ranges = self
14845 .selections
14846 .all_adjusted(cx)
14847 .into_iter()
14848 .map(|selection| selection.range())
14849 .collect_vec();
14850
14851 Some(self.perform_format(
14852 project,
14853 FormatTrigger::Manual,
14854 FormatTarget::Ranges(ranges),
14855 window,
14856 cx,
14857 ))
14858 }
14859
14860 fn perform_format(
14861 &mut self,
14862 project: Entity<Project>,
14863 trigger: FormatTrigger,
14864 target: FormatTarget,
14865 window: &mut Window,
14866 cx: &mut Context<Self>,
14867 ) -> Task<Result<()>> {
14868 let buffer = self.buffer.clone();
14869 let (buffers, target) = match target {
14870 FormatTarget::Buffers => {
14871 let mut buffers = buffer.read(cx).all_buffers();
14872 if trigger == FormatTrigger::Save {
14873 buffers.retain(|buffer| buffer.read(cx).is_dirty());
14874 }
14875 (buffers, LspFormatTarget::Buffers)
14876 }
14877 FormatTarget::Ranges(selection_ranges) => {
14878 let multi_buffer = buffer.read(cx);
14879 let snapshot = multi_buffer.read(cx);
14880 let mut buffers = HashSet::default();
14881 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
14882 BTreeMap::new();
14883 for selection_range in selection_ranges {
14884 for (buffer, buffer_range, _) in
14885 snapshot.range_to_buffer_ranges(selection_range)
14886 {
14887 let buffer_id = buffer.remote_id();
14888 let start = buffer.anchor_before(buffer_range.start);
14889 let end = buffer.anchor_after(buffer_range.end);
14890 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
14891 buffer_id_to_ranges
14892 .entry(buffer_id)
14893 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
14894 .or_insert_with(|| vec![start..end]);
14895 }
14896 }
14897 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
14898 }
14899 };
14900
14901 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
14902 let selections_prev = transaction_id_prev
14903 .and_then(|transaction_id_prev| {
14904 // default to selections as they were after the last edit, if we have them,
14905 // instead of how they are now.
14906 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
14907 // will take you back to where you made the last edit, instead of staying where you scrolled
14908 self.selection_history
14909 .transaction(transaction_id_prev)
14910 .map(|t| t.0.clone())
14911 })
14912 .unwrap_or_else(|| {
14913 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
14914 self.selections.disjoint_anchors()
14915 });
14916
14917 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
14918 let format = project.update(cx, |project, cx| {
14919 project.format(buffers, target, true, trigger, cx)
14920 });
14921
14922 cx.spawn_in(window, async move |editor, cx| {
14923 let transaction = futures::select_biased! {
14924 transaction = format.log_err().fuse() => transaction,
14925 () = timeout => {
14926 log::warn!("timed out waiting for formatting");
14927 None
14928 }
14929 };
14930
14931 buffer
14932 .update(cx, |buffer, cx| {
14933 if let Some(transaction) = transaction {
14934 if !buffer.is_singleton() {
14935 buffer.push_transaction(&transaction.0, cx);
14936 }
14937 }
14938 cx.notify();
14939 })
14940 .ok();
14941
14942 if let Some(transaction_id_now) =
14943 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
14944 {
14945 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
14946 if has_new_transaction {
14947 _ = editor.update(cx, |editor, _| {
14948 editor
14949 .selection_history
14950 .insert_transaction(transaction_id_now, selections_prev);
14951 });
14952 }
14953 }
14954
14955 Ok(())
14956 })
14957 }
14958
14959 fn organize_imports(
14960 &mut self,
14961 _: &OrganizeImports,
14962 window: &mut Window,
14963 cx: &mut Context<Self>,
14964 ) -> Option<Task<Result<()>>> {
14965 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14966 let project = match &self.project {
14967 Some(project) => project.clone(),
14968 None => return None,
14969 };
14970 Some(self.perform_code_action_kind(
14971 project,
14972 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
14973 window,
14974 cx,
14975 ))
14976 }
14977
14978 fn perform_code_action_kind(
14979 &mut self,
14980 project: Entity<Project>,
14981 kind: CodeActionKind,
14982 window: &mut Window,
14983 cx: &mut Context<Self>,
14984 ) -> Task<Result<()>> {
14985 let buffer = self.buffer.clone();
14986 let buffers = buffer.read(cx).all_buffers();
14987 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
14988 let apply_action = project.update(cx, |project, cx| {
14989 project.apply_code_action_kind(buffers, kind, true, cx)
14990 });
14991 cx.spawn_in(window, async move |_, cx| {
14992 let transaction = futures::select_biased! {
14993 () = timeout => {
14994 log::warn!("timed out waiting for executing code action");
14995 None
14996 }
14997 transaction = apply_action.log_err().fuse() => transaction,
14998 };
14999 buffer
15000 .update(cx, |buffer, cx| {
15001 // check if we need this
15002 if let Some(transaction) = transaction {
15003 if !buffer.is_singleton() {
15004 buffer.push_transaction(&transaction.0, cx);
15005 }
15006 }
15007 cx.notify();
15008 })
15009 .ok();
15010 Ok(())
15011 })
15012 }
15013
15014 fn restart_language_server(
15015 &mut self,
15016 _: &RestartLanguageServer,
15017 _: &mut Window,
15018 cx: &mut Context<Self>,
15019 ) {
15020 if let Some(project) = self.project.clone() {
15021 self.buffer.update(cx, |multi_buffer, cx| {
15022 project.update(cx, |project, cx| {
15023 project.restart_language_servers_for_buffers(
15024 multi_buffer.all_buffers().into_iter().collect(),
15025 cx,
15026 );
15027 });
15028 })
15029 }
15030 }
15031
15032 fn stop_language_server(
15033 &mut self,
15034 _: &StopLanguageServer,
15035 _: &mut Window,
15036 cx: &mut Context<Self>,
15037 ) {
15038 if let Some(project) = self.project.clone() {
15039 self.buffer.update(cx, |multi_buffer, cx| {
15040 project.update(cx, |project, cx| {
15041 project.stop_language_servers_for_buffers(
15042 multi_buffer.all_buffers().into_iter().collect(),
15043 cx,
15044 );
15045 cx.emit(project::Event::RefreshInlayHints);
15046 });
15047 });
15048 }
15049 }
15050
15051 fn cancel_language_server_work(
15052 workspace: &mut Workspace,
15053 _: &actions::CancelLanguageServerWork,
15054 _: &mut Window,
15055 cx: &mut Context<Workspace>,
15056 ) {
15057 let project = workspace.project();
15058 let buffers = workspace
15059 .active_item(cx)
15060 .and_then(|item| item.act_as::<Editor>(cx))
15061 .map_or(HashSet::default(), |editor| {
15062 editor.read(cx).buffer.read(cx).all_buffers()
15063 });
15064 project.update(cx, |project, cx| {
15065 project.cancel_language_server_work_for_buffers(buffers, cx);
15066 });
15067 }
15068
15069 fn show_character_palette(
15070 &mut self,
15071 _: &ShowCharacterPalette,
15072 window: &mut Window,
15073 _: &mut Context<Self>,
15074 ) {
15075 window.show_character_palette();
15076 }
15077
15078 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15079 if self.mode.is_minimap() {
15080 return;
15081 }
15082
15083 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15084 let buffer = self.buffer.read(cx).snapshot(cx);
15085 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15086 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15087 let is_valid = buffer
15088 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15089 .any(|entry| {
15090 entry.diagnostic.is_primary
15091 && !entry.range.is_empty()
15092 && entry.range.start == primary_range_start
15093 && entry.diagnostic.message == active_diagnostics.active_message
15094 });
15095
15096 if !is_valid {
15097 self.dismiss_diagnostics(cx);
15098 }
15099 }
15100 }
15101
15102 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15103 match &self.active_diagnostics {
15104 ActiveDiagnostic::Group(group) => Some(group),
15105 _ => None,
15106 }
15107 }
15108
15109 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15110 self.dismiss_diagnostics(cx);
15111 self.active_diagnostics = ActiveDiagnostic::All;
15112 }
15113
15114 fn activate_diagnostics(
15115 &mut self,
15116 buffer_id: BufferId,
15117 diagnostic: DiagnosticEntry<usize>,
15118 window: &mut Window,
15119 cx: &mut Context<Self>,
15120 ) {
15121 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15122 return;
15123 }
15124 self.dismiss_diagnostics(cx);
15125 let snapshot = self.snapshot(window, cx);
15126 let buffer = self.buffer.read(cx).snapshot(cx);
15127 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15128 return;
15129 };
15130
15131 let diagnostic_group = buffer
15132 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15133 .collect::<Vec<_>>();
15134
15135 let blocks =
15136 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15137
15138 let blocks = self.display_map.update(cx, |display_map, cx| {
15139 display_map.insert_blocks(blocks, cx).into_iter().collect()
15140 });
15141 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15142 active_range: buffer.anchor_before(diagnostic.range.start)
15143 ..buffer.anchor_after(diagnostic.range.end),
15144 active_message: diagnostic.diagnostic.message.clone(),
15145 group_id: diagnostic.diagnostic.group_id,
15146 blocks,
15147 });
15148 cx.notify();
15149 }
15150
15151 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15152 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15153 return;
15154 };
15155
15156 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15157 if let ActiveDiagnostic::Group(group) = prev {
15158 self.display_map.update(cx, |display_map, cx| {
15159 display_map.remove_blocks(group.blocks, cx);
15160 });
15161 cx.notify();
15162 }
15163 }
15164
15165 /// Disable inline diagnostics rendering for this editor.
15166 pub fn disable_inline_diagnostics(&mut self) {
15167 self.inline_diagnostics_enabled = false;
15168 self.inline_diagnostics_update = Task::ready(());
15169 self.inline_diagnostics.clear();
15170 }
15171
15172 pub fn diagnostics_enabled(&self) -> bool {
15173 self.mode.is_full()
15174 }
15175
15176 pub fn inline_diagnostics_enabled(&self) -> bool {
15177 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15178 }
15179
15180 pub fn show_inline_diagnostics(&self) -> bool {
15181 self.show_inline_diagnostics
15182 }
15183
15184 pub fn toggle_inline_diagnostics(
15185 &mut self,
15186 _: &ToggleInlineDiagnostics,
15187 window: &mut Window,
15188 cx: &mut Context<Editor>,
15189 ) {
15190 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15191 self.refresh_inline_diagnostics(false, window, cx);
15192 }
15193
15194 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15195 self.diagnostics_max_severity = severity;
15196 self.display_map.update(cx, |display_map, _| {
15197 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15198 });
15199 }
15200
15201 pub fn toggle_diagnostics(
15202 &mut self,
15203 _: &ToggleDiagnostics,
15204 window: &mut Window,
15205 cx: &mut Context<Editor>,
15206 ) {
15207 if !self.diagnostics_enabled() {
15208 return;
15209 }
15210
15211 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15212 EditorSettings::get_global(cx)
15213 .diagnostics_max_severity
15214 .filter(|severity| severity != &DiagnosticSeverity::Off)
15215 .unwrap_or(DiagnosticSeverity::Hint)
15216 } else {
15217 DiagnosticSeverity::Off
15218 };
15219 self.set_max_diagnostics_severity(new_severity, cx);
15220 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15221 self.active_diagnostics = ActiveDiagnostic::None;
15222 self.inline_diagnostics_update = Task::ready(());
15223 self.inline_diagnostics.clear();
15224 } else {
15225 self.refresh_inline_diagnostics(false, window, cx);
15226 }
15227
15228 cx.notify();
15229 }
15230
15231 pub fn toggle_minimap(
15232 &mut self,
15233 _: &ToggleMinimap,
15234 window: &mut Window,
15235 cx: &mut Context<Editor>,
15236 ) {
15237 if self.supports_minimap(cx) {
15238 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15239 }
15240 }
15241
15242 fn refresh_inline_diagnostics(
15243 &mut self,
15244 debounce: bool,
15245 window: &mut Window,
15246 cx: &mut Context<Self>,
15247 ) {
15248 let max_severity = ProjectSettings::get_global(cx)
15249 .diagnostics
15250 .inline
15251 .max_severity
15252 .unwrap_or(self.diagnostics_max_severity);
15253
15254 if self.mode.is_minimap()
15255 || !self.inline_diagnostics_enabled()
15256 || !self.show_inline_diagnostics
15257 || max_severity == DiagnosticSeverity::Off
15258 {
15259 self.inline_diagnostics_update = Task::ready(());
15260 self.inline_diagnostics.clear();
15261 return;
15262 }
15263
15264 let debounce_ms = ProjectSettings::get_global(cx)
15265 .diagnostics
15266 .inline
15267 .update_debounce_ms;
15268 let debounce = if debounce && debounce_ms > 0 {
15269 Some(Duration::from_millis(debounce_ms))
15270 } else {
15271 None
15272 };
15273 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15274 let editor = editor.upgrade().unwrap();
15275
15276 if let Some(debounce) = debounce {
15277 cx.background_executor().timer(debounce).await;
15278 }
15279 let Some(snapshot) = editor
15280 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15281 .ok()
15282 else {
15283 return;
15284 };
15285
15286 let new_inline_diagnostics = cx
15287 .background_spawn(async move {
15288 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15289 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15290 let message = diagnostic_entry
15291 .diagnostic
15292 .message
15293 .split_once('\n')
15294 .map(|(line, _)| line)
15295 .map(SharedString::new)
15296 .unwrap_or_else(|| {
15297 SharedString::from(diagnostic_entry.diagnostic.message)
15298 });
15299 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15300 let (Ok(i) | Err(i)) = inline_diagnostics
15301 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15302 inline_diagnostics.insert(
15303 i,
15304 (
15305 start_anchor,
15306 InlineDiagnostic {
15307 message,
15308 group_id: diagnostic_entry.diagnostic.group_id,
15309 start: diagnostic_entry.range.start.to_point(&snapshot),
15310 is_primary: diagnostic_entry.diagnostic.is_primary,
15311 severity: diagnostic_entry.diagnostic.severity,
15312 },
15313 ),
15314 );
15315 }
15316 inline_diagnostics
15317 })
15318 .await;
15319
15320 editor
15321 .update(cx, |editor, cx| {
15322 editor.inline_diagnostics = new_inline_diagnostics;
15323 cx.notify();
15324 })
15325 .ok();
15326 });
15327 }
15328
15329 pub fn set_selections_from_remote(
15330 &mut self,
15331 selections: Vec<Selection<Anchor>>,
15332 pending_selection: Option<Selection<Anchor>>,
15333 window: &mut Window,
15334 cx: &mut Context<Self>,
15335 ) {
15336 let old_cursor_position = self.selections.newest_anchor().head();
15337 self.selections.change_with(cx, |s| {
15338 s.select_anchors(selections);
15339 if let Some(pending_selection) = pending_selection {
15340 s.set_pending(pending_selection, SelectMode::Character);
15341 } else {
15342 s.clear_pending();
15343 }
15344 });
15345 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15346 }
15347
15348 fn push_to_selection_history(&mut self) {
15349 self.selection_history.push(SelectionHistoryEntry {
15350 selections: self.selections.disjoint_anchors(),
15351 select_next_state: self.select_next_state.clone(),
15352 select_prev_state: self.select_prev_state.clone(),
15353 add_selections_state: self.add_selections_state.clone(),
15354 });
15355 }
15356
15357 pub fn transact(
15358 &mut self,
15359 window: &mut Window,
15360 cx: &mut Context<Self>,
15361 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15362 ) -> Option<TransactionId> {
15363 self.start_transaction_at(Instant::now(), window, cx);
15364 update(self, window, cx);
15365 self.end_transaction_at(Instant::now(), cx)
15366 }
15367
15368 pub fn start_transaction_at(
15369 &mut self,
15370 now: Instant,
15371 window: &mut Window,
15372 cx: &mut Context<Self>,
15373 ) {
15374 self.end_selection(window, cx);
15375 if let Some(tx_id) = self
15376 .buffer
15377 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15378 {
15379 self.selection_history
15380 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15381 cx.emit(EditorEvent::TransactionBegun {
15382 transaction_id: tx_id,
15383 })
15384 }
15385 }
15386
15387 pub fn end_transaction_at(
15388 &mut self,
15389 now: Instant,
15390 cx: &mut Context<Self>,
15391 ) -> Option<TransactionId> {
15392 if let Some(transaction_id) = self
15393 .buffer
15394 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15395 {
15396 if let Some((_, end_selections)) =
15397 self.selection_history.transaction_mut(transaction_id)
15398 {
15399 *end_selections = Some(self.selections.disjoint_anchors());
15400 } else {
15401 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15402 }
15403
15404 cx.emit(EditorEvent::Edited { transaction_id });
15405 Some(transaction_id)
15406 } else {
15407 None
15408 }
15409 }
15410
15411 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15412 if self.selection_mark_mode {
15413 self.change_selections(None, window, cx, |s| {
15414 s.move_with(|_, sel| {
15415 sel.collapse_to(sel.head(), SelectionGoal::None);
15416 });
15417 })
15418 }
15419 self.selection_mark_mode = true;
15420 cx.notify();
15421 }
15422
15423 pub fn swap_selection_ends(
15424 &mut self,
15425 _: &actions::SwapSelectionEnds,
15426 window: &mut Window,
15427 cx: &mut Context<Self>,
15428 ) {
15429 self.change_selections(None, window, cx, |s| {
15430 s.move_with(|_, sel| {
15431 if sel.start != sel.end {
15432 sel.reversed = !sel.reversed
15433 }
15434 });
15435 });
15436 self.request_autoscroll(Autoscroll::newest(), cx);
15437 cx.notify();
15438 }
15439
15440 pub fn toggle_fold(
15441 &mut self,
15442 _: &actions::ToggleFold,
15443 window: &mut Window,
15444 cx: &mut Context<Self>,
15445 ) {
15446 if self.is_singleton(cx) {
15447 let selection = self.selections.newest::<Point>(cx);
15448
15449 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15450 let range = if selection.is_empty() {
15451 let point = selection.head().to_display_point(&display_map);
15452 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15453 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15454 .to_point(&display_map);
15455 start..end
15456 } else {
15457 selection.range()
15458 };
15459 if display_map.folds_in_range(range).next().is_some() {
15460 self.unfold_lines(&Default::default(), window, cx)
15461 } else {
15462 self.fold(&Default::default(), window, cx)
15463 }
15464 } else {
15465 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15466 let buffer_ids: HashSet<_> = self
15467 .selections
15468 .disjoint_anchor_ranges()
15469 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15470 .collect();
15471
15472 let should_unfold = buffer_ids
15473 .iter()
15474 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15475
15476 for buffer_id in buffer_ids {
15477 if should_unfold {
15478 self.unfold_buffer(buffer_id, cx);
15479 } else {
15480 self.fold_buffer(buffer_id, cx);
15481 }
15482 }
15483 }
15484 }
15485
15486 pub fn toggle_fold_recursive(
15487 &mut self,
15488 _: &actions::ToggleFoldRecursive,
15489 window: &mut Window,
15490 cx: &mut Context<Self>,
15491 ) {
15492 let selection = self.selections.newest::<Point>(cx);
15493
15494 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15495 let range = if selection.is_empty() {
15496 let point = selection.head().to_display_point(&display_map);
15497 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15498 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15499 .to_point(&display_map);
15500 start..end
15501 } else {
15502 selection.range()
15503 };
15504 if display_map.folds_in_range(range).next().is_some() {
15505 self.unfold_recursive(&Default::default(), window, cx)
15506 } else {
15507 self.fold_recursive(&Default::default(), window, cx)
15508 }
15509 }
15510
15511 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15512 if self.is_singleton(cx) {
15513 let mut to_fold = Vec::new();
15514 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15515 let selections = self.selections.all_adjusted(cx);
15516
15517 for selection in selections {
15518 let range = selection.range().sorted();
15519 let buffer_start_row = range.start.row;
15520
15521 if range.start.row != range.end.row {
15522 let mut found = false;
15523 let mut row = range.start.row;
15524 while row <= range.end.row {
15525 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15526 {
15527 found = true;
15528 row = crease.range().end.row + 1;
15529 to_fold.push(crease);
15530 } else {
15531 row += 1
15532 }
15533 }
15534 if found {
15535 continue;
15536 }
15537 }
15538
15539 for row in (0..=range.start.row).rev() {
15540 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15541 if crease.range().end.row >= buffer_start_row {
15542 to_fold.push(crease);
15543 if row <= range.start.row {
15544 break;
15545 }
15546 }
15547 }
15548 }
15549 }
15550
15551 self.fold_creases(to_fold, true, window, cx);
15552 } else {
15553 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15554 let buffer_ids = self
15555 .selections
15556 .disjoint_anchor_ranges()
15557 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15558 .collect::<HashSet<_>>();
15559 for buffer_id in buffer_ids {
15560 self.fold_buffer(buffer_id, cx);
15561 }
15562 }
15563 }
15564
15565 fn fold_at_level(
15566 &mut self,
15567 fold_at: &FoldAtLevel,
15568 window: &mut Window,
15569 cx: &mut Context<Self>,
15570 ) {
15571 if !self.buffer.read(cx).is_singleton() {
15572 return;
15573 }
15574
15575 let fold_at_level = fold_at.0;
15576 let snapshot = self.buffer.read(cx).snapshot(cx);
15577 let mut to_fold = Vec::new();
15578 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15579
15580 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15581 while start_row < end_row {
15582 match self
15583 .snapshot(window, cx)
15584 .crease_for_buffer_row(MultiBufferRow(start_row))
15585 {
15586 Some(crease) => {
15587 let nested_start_row = crease.range().start.row + 1;
15588 let nested_end_row = crease.range().end.row;
15589
15590 if current_level < fold_at_level {
15591 stack.push((nested_start_row, nested_end_row, current_level + 1));
15592 } else if current_level == fold_at_level {
15593 to_fold.push(crease);
15594 }
15595
15596 start_row = nested_end_row + 1;
15597 }
15598 None => start_row += 1,
15599 }
15600 }
15601 }
15602
15603 self.fold_creases(to_fold, true, window, cx);
15604 }
15605
15606 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15607 if self.buffer.read(cx).is_singleton() {
15608 let mut fold_ranges = Vec::new();
15609 let snapshot = self.buffer.read(cx).snapshot(cx);
15610
15611 for row in 0..snapshot.max_row().0 {
15612 if let Some(foldable_range) = self
15613 .snapshot(window, cx)
15614 .crease_for_buffer_row(MultiBufferRow(row))
15615 {
15616 fold_ranges.push(foldable_range);
15617 }
15618 }
15619
15620 self.fold_creases(fold_ranges, true, window, cx);
15621 } else {
15622 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15623 editor
15624 .update_in(cx, |editor, _, cx| {
15625 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15626 editor.fold_buffer(buffer_id, cx);
15627 }
15628 })
15629 .ok();
15630 });
15631 }
15632 }
15633
15634 pub fn fold_function_bodies(
15635 &mut self,
15636 _: &actions::FoldFunctionBodies,
15637 window: &mut Window,
15638 cx: &mut Context<Self>,
15639 ) {
15640 let snapshot = self.buffer.read(cx).snapshot(cx);
15641
15642 let ranges = snapshot
15643 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15644 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15645 .collect::<Vec<_>>();
15646
15647 let creases = ranges
15648 .into_iter()
15649 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15650 .collect();
15651
15652 self.fold_creases(creases, true, window, cx);
15653 }
15654
15655 pub fn fold_recursive(
15656 &mut self,
15657 _: &actions::FoldRecursive,
15658 window: &mut Window,
15659 cx: &mut Context<Self>,
15660 ) {
15661 let mut to_fold = Vec::new();
15662 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15663 let selections = self.selections.all_adjusted(cx);
15664
15665 for selection in selections {
15666 let range = selection.range().sorted();
15667 let buffer_start_row = range.start.row;
15668
15669 if range.start.row != range.end.row {
15670 let mut found = false;
15671 for row in range.start.row..=range.end.row {
15672 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15673 found = true;
15674 to_fold.push(crease);
15675 }
15676 }
15677 if found {
15678 continue;
15679 }
15680 }
15681
15682 for row in (0..=range.start.row).rev() {
15683 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15684 if crease.range().end.row >= buffer_start_row {
15685 to_fold.push(crease);
15686 } else {
15687 break;
15688 }
15689 }
15690 }
15691 }
15692
15693 self.fold_creases(to_fold, true, window, cx);
15694 }
15695
15696 pub fn fold_at(
15697 &mut self,
15698 buffer_row: MultiBufferRow,
15699 window: &mut Window,
15700 cx: &mut Context<Self>,
15701 ) {
15702 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15703
15704 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15705 let autoscroll = self
15706 .selections
15707 .all::<Point>(cx)
15708 .iter()
15709 .any(|selection| crease.range().overlaps(&selection.range()));
15710
15711 self.fold_creases(vec![crease], autoscroll, window, cx);
15712 }
15713 }
15714
15715 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15716 if self.is_singleton(cx) {
15717 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15718 let buffer = &display_map.buffer_snapshot;
15719 let selections = self.selections.all::<Point>(cx);
15720 let ranges = selections
15721 .iter()
15722 .map(|s| {
15723 let range = s.display_range(&display_map).sorted();
15724 let mut start = range.start.to_point(&display_map);
15725 let mut end = range.end.to_point(&display_map);
15726 start.column = 0;
15727 end.column = buffer.line_len(MultiBufferRow(end.row));
15728 start..end
15729 })
15730 .collect::<Vec<_>>();
15731
15732 self.unfold_ranges(&ranges, true, true, cx);
15733 } else {
15734 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15735 let buffer_ids = self
15736 .selections
15737 .disjoint_anchor_ranges()
15738 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15739 .collect::<HashSet<_>>();
15740 for buffer_id in buffer_ids {
15741 self.unfold_buffer(buffer_id, cx);
15742 }
15743 }
15744 }
15745
15746 pub fn unfold_recursive(
15747 &mut self,
15748 _: &UnfoldRecursive,
15749 _window: &mut Window,
15750 cx: &mut Context<Self>,
15751 ) {
15752 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15753 let selections = self.selections.all::<Point>(cx);
15754 let ranges = selections
15755 .iter()
15756 .map(|s| {
15757 let mut range = s.display_range(&display_map).sorted();
15758 *range.start.column_mut() = 0;
15759 *range.end.column_mut() = display_map.line_len(range.end.row());
15760 let start = range.start.to_point(&display_map);
15761 let end = range.end.to_point(&display_map);
15762 start..end
15763 })
15764 .collect::<Vec<_>>();
15765
15766 self.unfold_ranges(&ranges, true, true, cx);
15767 }
15768
15769 pub fn unfold_at(
15770 &mut self,
15771 buffer_row: MultiBufferRow,
15772 _window: &mut Window,
15773 cx: &mut Context<Self>,
15774 ) {
15775 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15776
15777 let intersection_range = Point::new(buffer_row.0, 0)
15778 ..Point::new(
15779 buffer_row.0,
15780 display_map.buffer_snapshot.line_len(buffer_row),
15781 );
15782
15783 let autoscroll = self
15784 .selections
15785 .all::<Point>(cx)
15786 .iter()
15787 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15788
15789 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15790 }
15791
15792 pub fn unfold_all(
15793 &mut self,
15794 _: &actions::UnfoldAll,
15795 _window: &mut Window,
15796 cx: &mut Context<Self>,
15797 ) {
15798 if self.buffer.read(cx).is_singleton() {
15799 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15800 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15801 } else {
15802 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15803 editor
15804 .update(cx, |editor, cx| {
15805 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15806 editor.unfold_buffer(buffer_id, cx);
15807 }
15808 })
15809 .ok();
15810 });
15811 }
15812 }
15813
15814 pub fn fold_selected_ranges(
15815 &mut self,
15816 _: &FoldSelectedRanges,
15817 window: &mut Window,
15818 cx: &mut Context<Self>,
15819 ) {
15820 let selections = self.selections.all_adjusted(cx);
15821 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15822 let ranges = selections
15823 .into_iter()
15824 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
15825 .collect::<Vec<_>>();
15826 self.fold_creases(ranges, true, window, cx);
15827 }
15828
15829 pub fn fold_ranges<T: ToOffset + Clone>(
15830 &mut self,
15831 ranges: Vec<Range<T>>,
15832 auto_scroll: bool,
15833 window: &mut Window,
15834 cx: &mut Context<Self>,
15835 ) {
15836 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15837 let ranges = ranges
15838 .into_iter()
15839 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
15840 .collect::<Vec<_>>();
15841 self.fold_creases(ranges, auto_scroll, window, cx);
15842 }
15843
15844 pub fn fold_creases<T: ToOffset + Clone>(
15845 &mut self,
15846 creases: Vec<Crease<T>>,
15847 auto_scroll: bool,
15848 _window: &mut Window,
15849 cx: &mut Context<Self>,
15850 ) {
15851 if creases.is_empty() {
15852 return;
15853 }
15854
15855 let mut buffers_affected = HashSet::default();
15856 let multi_buffer = self.buffer().read(cx);
15857 for crease in &creases {
15858 if let Some((_, buffer, _)) =
15859 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
15860 {
15861 buffers_affected.insert(buffer.read(cx).remote_id());
15862 };
15863 }
15864
15865 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
15866
15867 if auto_scroll {
15868 self.request_autoscroll(Autoscroll::fit(), cx);
15869 }
15870
15871 cx.notify();
15872
15873 self.scrollbar_marker_state.dirty = true;
15874 self.folds_did_change(cx);
15875 }
15876
15877 /// Removes any folds whose ranges intersect any of the given ranges.
15878 pub fn unfold_ranges<T: ToOffset + Clone>(
15879 &mut self,
15880 ranges: &[Range<T>],
15881 inclusive: bool,
15882 auto_scroll: bool,
15883 cx: &mut Context<Self>,
15884 ) {
15885 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15886 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
15887 });
15888 self.folds_did_change(cx);
15889 }
15890
15891 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15892 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
15893 return;
15894 }
15895 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15896 self.display_map.update(cx, |display_map, cx| {
15897 display_map.fold_buffers([buffer_id], cx)
15898 });
15899 cx.emit(EditorEvent::BufferFoldToggled {
15900 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
15901 folded: true,
15902 });
15903 cx.notify();
15904 }
15905
15906 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15907 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
15908 return;
15909 }
15910 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15911 self.display_map.update(cx, |display_map, cx| {
15912 display_map.unfold_buffers([buffer_id], cx);
15913 });
15914 cx.emit(EditorEvent::BufferFoldToggled {
15915 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
15916 folded: false,
15917 });
15918 cx.notify();
15919 }
15920
15921 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
15922 self.display_map.read(cx).is_buffer_folded(buffer)
15923 }
15924
15925 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
15926 self.display_map.read(cx).folded_buffers()
15927 }
15928
15929 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15930 self.display_map.update(cx, |display_map, cx| {
15931 display_map.disable_header_for_buffer(buffer_id, cx);
15932 });
15933 cx.notify();
15934 }
15935
15936 /// Removes any folds with the given ranges.
15937 pub fn remove_folds_with_type<T: ToOffset + Clone>(
15938 &mut self,
15939 ranges: &[Range<T>],
15940 type_id: TypeId,
15941 auto_scroll: bool,
15942 cx: &mut Context<Self>,
15943 ) {
15944 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15945 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
15946 });
15947 self.folds_did_change(cx);
15948 }
15949
15950 fn remove_folds_with<T: ToOffset + Clone>(
15951 &mut self,
15952 ranges: &[Range<T>],
15953 auto_scroll: bool,
15954 cx: &mut Context<Self>,
15955 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
15956 ) {
15957 if ranges.is_empty() {
15958 return;
15959 }
15960
15961 let mut buffers_affected = HashSet::default();
15962 let multi_buffer = self.buffer().read(cx);
15963 for range in ranges {
15964 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
15965 buffers_affected.insert(buffer.read(cx).remote_id());
15966 };
15967 }
15968
15969 self.display_map.update(cx, update);
15970
15971 if auto_scroll {
15972 self.request_autoscroll(Autoscroll::fit(), cx);
15973 }
15974
15975 cx.notify();
15976 self.scrollbar_marker_state.dirty = true;
15977 self.active_indent_guides_state.dirty = true;
15978 }
15979
15980 pub fn update_fold_widths(
15981 &mut self,
15982 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
15983 cx: &mut Context<Self>,
15984 ) -> bool {
15985 self.display_map
15986 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
15987 }
15988
15989 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
15990 self.display_map.read(cx).fold_placeholder.clone()
15991 }
15992
15993 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
15994 self.buffer.update(cx, |buffer, cx| {
15995 buffer.set_all_diff_hunks_expanded(cx);
15996 });
15997 }
15998
15999 pub fn expand_all_diff_hunks(
16000 &mut self,
16001 _: &ExpandAllDiffHunks,
16002 _window: &mut Window,
16003 cx: &mut Context<Self>,
16004 ) {
16005 self.buffer.update(cx, |buffer, cx| {
16006 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16007 });
16008 }
16009
16010 pub fn toggle_selected_diff_hunks(
16011 &mut self,
16012 _: &ToggleSelectedDiffHunks,
16013 _window: &mut Window,
16014 cx: &mut Context<Self>,
16015 ) {
16016 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16017 self.toggle_diff_hunks_in_ranges(ranges, cx);
16018 }
16019
16020 pub fn diff_hunks_in_ranges<'a>(
16021 &'a self,
16022 ranges: &'a [Range<Anchor>],
16023 buffer: &'a MultiBufferSnapshot,
16024 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16025 ranges.iter().flat_map(move |range| {
16026 let end_excerpt_id = range.end.excerpt_id;
16027 let range = range.to_point(buffer);
16028 let mut peek_end = range.end;
16029 if range.end.row < buffer.max_row().0 {
16030 peek_end = Point::new(range.end.row + 1, 0);
16031 }
16032 buffer
16033 .diff_hunks_in_range(range.start..peek_end)
16034 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16035 })
16036 }
16037
16038 pub fn has_stageable_diff_hunks_in_ranges(
16039 &self,
16040 ranges: &[Range<Anchor>],
16041 snapshot: &MultiBufferSnapshot,
16042 ) -> bool {
16043 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16044 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16045 }
16046
16047 pub fn toggle_staged_selected_diff_hunks(
16048 &mut self,
16049 _: &::git::ToggleStaged,
16050 _: &mut Window,
16051 cx: &mut Context<Self>,
16052 ) {
16053 let snapshot = self.buffer.read(cx).snapshot(cx);
16054 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16055 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16056 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16057 }
16058
16059 pub fn set_render_diff_hunk_controls(
16060 &mut self,
16061 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16062 cx: &mut Context<Self>,
16063 ) {
16064 self.render_diff_hunk_controls = render_diff_hunk_controls;
16065 cx.notify();
16066 }
16067
16068 pub fn stage_and_next(
16069 &mut self,
16070 _: &::git::StageAndNext,
16071 window: &mut Window,
16072 cx: &mut Context<Self>,
16073 ) {
16074 self.do_stage_or_unstage_and_next(true, window, cx);
16075 }
16076
16077 pub fn unstage_and_next(
16078 &mut self,
16079 _: &::git::UnstageAndNext,
16080 window: &mut Window,
16081 cx: &mut Context<Self>,
16082 ) {
16083 self.do_stage_or_unstage_and_next(false, window, cx);
16084 }
16085
16086 pub fn stage_or_unstage_diff_hunks(
16087 &mut self,
16088 stage: bool,
16089 ranges: Vec<Range<Anchor>>,
16090 cx: &mut Context<Self>,
16091 ) {
16092 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16093 cx.spawn(async move |this, cx| {
16094 task.await?;
16095 this.update(cx, |this, cx| {
16096 let snapshot = this.buffer.read(cx).snapshot(cx);
16097 let chunk_by = this
16098 .diff_hunks_in_ranges(&ranges, &snapshot)
16099 .chunk_by(|hunk| hunk.buffer_id);
16100 for (buffer_id, hunks) in &chunk_by {
16101 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16102 }
16103 })
16104 })
16105 .detach_and_log_err(cx);
16106 }
16107
16108 fn save_buffers_for_ranges_if_needed(
16109 &mut self,
16110 ranges: &[Range<Anchor>],
16111 cx: &mut Context<Editor>,
16112 ) -> Task<Result<()>> {
16113 let multibuffer = self.buffer.read(cx);
16114 let snapshot = multibuffer.read(cx);
16115 let buffer_ids: HashSet<_> = ranges
16116 .iter()
16117 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16118 .collect();
16119 drop(snapshot);
16120
16121 let mut buffers = HashSet::default();
16122 for buffer_id in buffer_ids {
16123 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16124 let buffer = buffer_entity.read(cx);
16125 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16126 {
16127 buffers.insert(buffer_entity);
16128 }
16129 }
16130 }
16131
16132 if let Some(project) = &self.project {
16133 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16134 } else {
16135 Task::ready(Ok(()))
16136 }
16137 }
16138
16139 fn do_stage_or_unstage_and_next(
16140 &mut self,
16141 stage: bool,
16142 window: &mut Window,
16143 cx: &mut Context<Self>,
16144 ) {
16145 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16146
16147 if ranges.iter().any(|range| range.start != range.end) {
16148 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16149 return;
16150 }
16151
16152 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16153 let snapshot = self.snapshot(window, cx);
16154 let position = self.selections.newest::<Point>(cx).head();
16155 let mut row = snapshot
16156 .buffer_snapshot
16157 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16158 .find(|hunk| hunk.row_range.start.0 > position.row)
16159 .map(|hunk| hunk.row_range.start);
16160
16161 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16162 // Outside of the project diff editor, wrap around to the beginning.
16163 if !all_diff_hunks_expanded {
16164 row = row.or_else(|| {
16165 snapshot
16166 .buffer_snapshot
16167 .diff_hunks_in_range(Point::zero()..position)
16168 .find(|hunk| hunk.row_range.end.0 < position.row)
16169 .map(|hunk| hunk.row_range.start)
16170 });
16171 }
16172
16173 if let Some(row) = row {
16174 let destination = Point::new(row.0, 0);
16175 let autoscroll = Autoscroll::center();
16176
16177 self.unfold_ranges(&[destination..destination], false, false, cx);
16178 self.change_selections(Some(autoscroll), window, cx, |s| {
16179 s.select_ranges([destination..destination]);
16180 });
16181 }
16182 }
16183
16184 fn do_stage_or_unstage(
16185 &self,
16186 stage: bool,
16187 buffer_id: BufferId,
16188 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16189 cx: &mut App,
16190 ) -> Option<()> {
16191 let project = self.project.as_ref()?;
16192 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16193 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16194 let buffer_snapshot = buffer.read(cx).snapshot();
16195 let file_exists = buffer_snapshot
16196 .file()
16197 .is_some_and(|file| file.disk_state().exists());
16198 diff.update(cx, |diff, cx| {
16199 diff.stage_or_unstage_hunks(
16200 stage,
16201 &hunks
16202 .map(|hunk| buffer_diff::DiffHunk {
16203 buffer_range: hunk.buffer_range,
16204 diff_base_byte_range: hunk.diff_base_byte_range,
16205 secondary_status: hunk.secondary_status,
16206 range: Point::zero()..Point::zero(), // unused
16207 })
16208 .collect::<Vec<_>>(),
16209 &buffer_snapshot,
16210 file_exists,
16211 cx,
16212 )
16213 });
16214 None
16215 }
16216
16217 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16218 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16219 self.buffer
16220 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16221 }
16222
16223 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16224 self.buffer.update(cx, |buffer, cx| {
16225 let ranges = vec![Anchor::min()..Anchor::max()];
16226 if !buffer.all_diff_hunks_expanded()
16227 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16228 {
16229 buffer.collapse_diff_hunks(ranges, cx);
16230 true
16231 } else {
16232 false
16233 }
16234 })
16235 }
16236
16237 fn toggle_diff_hunks_in_ranges(
16238 &mut self,
16239 ranges: Vec<Range<Anchor>>,
16240 cx: &mut Context<Editor>,
16241 ) {
16242 self.buffer.update(cx, |buffer, cx| {
16243 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16244 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16245 })
16246 }
16247
16248 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16249 self.buffer.update(cx, |buffer, cx| {
16250 let snapshot = buffer.snapshot(cx);
16251 let excerpt_id = range.end.excerpt_id;
16252 let point_range = range.to_point(&snapshot);
16253 let expand = !buffer.single_hunk_is_expanded(range, cx);
16254 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16255 })
16256 }
16257
16258 pub(crate) fn apply_all_diff_hunks(
16259 &mut self,
16260 _: &ApplyAllDiffHunks,
16261 window: &mut Window,
16262 cx: &mut Context<Self>,
16263 ) {
16264 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16265
16266 let buffers = self.buffer.read(cx).all_buffers();
16267 for branch_buffer in buffers {
16268 branch_buffer.update(cx, |branch_buffer, cx| {
16269 branch_buffer.merge_into_base(Vec::new(), cx);
16270 });
16271 }
16272
16273 if let Some(project) = self.project.clone() {
16274 self.save(true, project, window, cx).detach_and_log_err(cx);
16275 }
16276 }
16277
16278 pub(crate) fn apply_selected_diff_hunks(
16279 &mut self,
16280 _: &ApplyDiffHunk,
16281 window: &mut Window,
16282 cx: &mut Context<Self>,
16283 ) {
16284 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16285 let snapshot = self.snapshot(window, cx);
16286 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16287 let mut ranges_by_buffer = HashMap::default();
16288 self.transact(window, cx, |editor, _window, cx| {
16289 for hunk in hunks {
16290 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16291 ranges_by_buffer
16292 .entry(buffer.clone())
16293 .or_insert_with(Vec::new)
16294 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16295 }
16296 }
16297
16298 for (buffer, ranges) in ranges_by_buffer {
16299 buffer.update(cx, |buffer, cx| {
16300 buffer.merge_into_base(ranges, cx);
16301 });
16302 }
16303 });
16304
16305 if let Some(project) = self.project.clone() {
16306 self.save(true, project, window, cx).detach_and_log_err(cx);
16307 }
16308 }
16309
16310 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16311 if hovered != self.gutter_hovered {
16312 self.gutter_hovered = hovered;
16313 cx.notify();
16314 }
16315 }
16316
16317 pub fn insert_blocks(
16318 &mut self,
16319 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16320 autoscroll: Option<Autoscroll>,
16321 cx: &mut Context<Self>,
16322 ) -> Vec<CustomBlockId> {
16323 let blocks = self
16324 .display_map
16325 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16326 if let Some(autoscroll) = autoscroll {
16327 self.request_autoscroll(autoscroll, cx);
16328 }
16329 cx.notify();
16330 blocks
16331 }
16332
16333 pub fn resize_blocks(
16334 &mut self,
16335 heights: HashMap<CustomBlockId, u32>,
16336 autoscroll: Option<Autoscroll>,
16337 cx: &mut Context<Self>,
16338 ) {
16339 self.display_map
16340 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16341 if let Some(autoscroll) = autoscroll {
16342 self.request_autoscroll(autoscroll, cx);
16343 }
16344 cx.notify();
16345 }
16346
16347 pub fn replace_blocks(
16348 &mut self,
16349 renderers: HashMap<CustomBlockId, RenderBlock>,
16350 autoscroll: Option<Autoscroll>,
16351 cx: &mut Context<Self>,
16352 ) {
16353 self.display_map
16354 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16355 if let Some(autoscroll) = autoscroll {
16356 self.request_autoscroll(autoscroll, cx);
16357 }
16358 cx.notify();
16359 }
16360
16361 pub fn remove_blocks(
16362 &mut self,
16363 block_ids: HashSet<CustomBlockId>,
16364 autoscroll: Option<Autoscroll>,
16365 cx: &mut Context<Self>,
16366 ) {
16367 self.display_map.update(cx, |display_map, cx| {
16368 display_map.remove_blocks(block_ids, cx)
16369 });
16370 if let Some(autoscroll) = autoscroll {
16371 self.request_autoscroll(autoscroll, cx);
16372 }
16373 cx.notify();
16374 }
16375
16376 pub fn row_for_block(
16377 &self,
16378 block_id: CustomBlockId,
16379 cx: &mut Context<Self>,
16380 ) -> Option<DisplayRow> {
16381 self.display_map
16382 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16383 }
16384
16385 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16386 self.focused_block = Some(focused_block);
16387 }
16388
16389 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16390 self.focused_block.take()
16391 }
16392
16393 pub fn insert_creases(
16394 &mut self,
16395 creases: impl IntoIterator<Item = Crease<Anchor>>,
16396 cx: &mut Context<Self>,
16397 ) -> Vec<CreaseId> {
16398 self.display_map
16399 .update(cx, |map, cx| map.insert_creases(creases, cx))
16400 }
16401
16402 pub fn remove_creases(
16403 &mut self,
16404 ids: impl IntoIterator<Item = CreaseId>,
16405 cx: &mut Context<Self>,
16406 ) -> Vec<(CreaseId, Range<Anchor>)> {
16407 self.display_map
16408 .update(cx, |map, cx| map.remove_creases(ids, cx))
16409 }
16410
16411 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16412 self.display_map
16413 .update(cx, |map, cx| map.snapshot(cx))
16414 .longest_row()
16415 }
16416
16417 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16418 self.display_map
16419 .update(cx, |map, cx| map.snapshot(cx))
16420 .max_point()
16421 }
16422
16423 pub fn text(&self, cx: &App) -> String {
16424 self.buffer.read(cx).read(cx).text()
16425 }
16426
16427 pub fn is_empty(&self, cx: &App) -> bool {
16428 self.buffer.read(cx).read(cx).is_empty()
16429 }
16430
16431 pub fn text_option(&self, cx: &App) -> Option<String> {
16432 let text = self.text(cx);
16433 let text = text.trim();
16434
16435 if text.is_empty() {
16436 return None;
16437 }
16438
16439 Some(text.to_string())
16440 }
16441
16442 pub fn set_text(
16443 &mut self,
16444 text: impl Into<Arc<str>>,
16445 window: &mut Window,
16446 cx: &mut Context<Self>,
16447 ) {
16448 self.transact(window, cx, |this, _, cx| {
16449 this.buffer
16450 .read(cx)
16451 .as_singleton()
16452 .expect("you can only call set_text on editors for singleton buffers")
16453 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16454 });
16455 }
16456
16457 pub fn display_text(&self, cx: &mut App) -> String {
16458 self.display_map
16459 .update(cx, |map, cx| map.snapshot(cx))
16460 .text()
16461 }
16462
16463 fn create_minimap(
16464 &self,
16465 minimap_settings: MinimapSettings,
16466 window: &mut Window,
16467 cx: &mut Context<Self>,
16468 ) -> Option<Entity<Self>> {
16469 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
16470 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
16471 }
16472
16473 fn initialize_new_minimap(
16474 &self,
16475 minimap_settings: MinimapSettings,
16476 window: &mut Window,
16477 cx: &mut Context<Self>,
16478 ) -> Entity<Self> {
16479 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
16480
16481 let mut minimap = Editor::new_internal(
16482 EditorMode::Minimap {
16483 parent: cx.weak_entity(),
16484 },
16485 self.buffer.clone(),
16486 self.project.clone(),
16487 Some(self.display_map.clone()),
16488 window,
16489 cx,
16490 );
16491 minimap.scroll_manager.clone_state(&self.scroll_manager);
16492 minimap.set_text_style_refinement(TextStyleRefinement {
16493 font_size: Some(MINIMAP_FONT_SIZE),
16494 font_weight: Some(MINIMAP_FONT_WEIGHT),
16495 ..Default::default()
16496 });
16497 minimap.update_minimap_configuration(minimap_settings, cx);
16498 cx.new(|_| minimap)
16499 }
16500
16501 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
16502 let current_line_highlight = minimap_settings
16503 .current_line_highlight
16504 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
16505 self.set_current_line_highlight(Some(current_line_highlight));
16506 }
16507
16508 pub fn minimap(&self) -> Option<&Entity<Self>> {
16509 self.minimap
16510 .as_ref()
16511 .filter(|_| self.minimap_visibility.visible())
16512 }
16513
16514 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16515 let mut wrap_guides = smallvec::smallvec![];
16516
16517 if self.show_wrap_guides == Some(false) {
16518 return wrap_guides;
16519 }
16520
16521 let settings = self.buffer.read(cx).language_settings(cx);
16522 if settings.show_wrap_guides {
16523 match self.soft_wrap_mode(cx) {
16524 SoftWrap::Column(soft_wrap) => {
16525 wrap_guides.push((soft_wrap as usize, true));
16526 }
16527 SoftWrap::Bounded(soft_wrap) => {
16528 wrap_guides.push((soft_wrap as usize, true));
16529 }
16530 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16531 }
16532 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16533 }
16534
16535 wrap_guides
16536 }
16537
16538 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16539 let settings = self.buffer.read(cx).language_settings(cx);
16540 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16541 match mode {
16542 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16543 SoftWrap::None
16544 }
16545 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16546 language_settings::SoftWrap::PreferredLineLength => {
16547 SoftWrap::Column(settings.preferred_line_length)
16548 }
16549 language_settings::SoftWrap::Bounded => {
16550 SoftWrap::Bounded(settings.preferred_line_length)
16551 }
16552 }
16553 }
16554
16555 pub fn set_soft_wrap_mode(
16556 &mut self,
16557 mode: language_settings::SoftWrap,
16558
16559 cx: &mut Context<Self>,
16560 ) {
16561 self.soft_wrap_mode_override = Some(mode);
16562 cx.notify();
16563 }
16564
16565 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16566 self.hard_wrap = hard_wrap;
16567 cx.notify();
16568 }
16569
16570 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16571 self.text_style_refinement = Some(style);
16572 }
16573
16574 /// called by the Element so we know what style we were most recently rendered with.
16575 pub(crate) fn set_style(
16576 &mut self,
16577 style: EditorStyle,
16578 window: &mut Window,
16579 cx: &mut Context<Self>,
16580 ) {
16581 // We intentionally do not inform the display map about the minimap style
16582 // so that wrapping is not recalculated and stays consistent for the editor
16583 // and its linked minimap.
16584 if !self.mode.is_minimap() {
16585 let rem_size = window.rem_size();
16586 self.display_map.update(cx, |map, cx| {
16587 map.set_font(
16588 style.text.font(),
16589 style.text.font_size.to_pixels(rem_size),
16590 cx,
16591 )
16592 });
16593 }
16594 self.style = Some(style);
16595 }
16596
16597 pub fn style(&self) -> Option<&EditorStyle> {
16598 self.style.as_ref()
16599 }
16600
16601 // Called by the element. This method is not designed to be called outside of the editor
16602 // element's layout code because it does not notify when rewrapping is computed synchronously.
16603 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16604 self.display_map
16605 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16606 }
16607
16608 pub fn set_soft_wrap(&mut self) {
16609 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16610 }
16611
16612 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16613 if self.soft_wrap_mode_override.is_some() {
16614 self.soft_wrap_mode_override.take();
16615 } else {
16616 let soft_wrap = match self.soft_wrap_mode(cx) {
16617 SoftWrap::GitDiff => return,
16618 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16619 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16620 language_settings::SoftWrap::None
16621 }
16622 };
16623 self.soft_wrap_mode_override = Some(soft_wrap);
16624 }
16625 cx.notify();
16626 }
16627
16628 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16629 let Some(workspace) = self.workspace() else {
16630 return;
16631 };
16632 let fs = workspace.read(cx).app_state().fs.clone();
16633 let current_show = TabBarSettings::get_global(cx).show;
16634 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16635 setting.show = Some(!current_show);
16636 });
16637 }
16638
16639 pub fn toggle_indent_guides(
16640 &mut self,
16641 _: &ToggleIndentGuides,
16642 _: &mut Window,
16643 cx: &mut Context<Self>,
16644 ) {
16645 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16646 self.buffer
16647 .read(cx)
16648 .language_settings(cx)
16649 .indent_guides
16650 .enabled
16651 });
16652 self.show_indent_guides = Some(!currently_enabled);
16653 cx.notify();
16654 }
16655
16656 fn should_show_indent_guides(&self) -> Option<bool> {
16657 self.show_indent_guides
16658 }
16659
16660 pub fn toggle_line_numbers(
16661 &mut self,
16662 _: &ToggleLineNumbers,
16663 _: &mut Window,
16664 cx: &mut Context<Self>,
16665 ) {
16666 let mut editor_settings = EditorSettings::get_global(cx).clone();
16667 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16668 EditorSettings::override_global(editor_settings, cx);
16669 }
16670
16671 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16672 if let Some(show_line_numbers) = self.show_line_numbers {
16673 return show_line_numbers;
16674 }
16675 EditorSettings::get_global(cx).gutter.line_numbers
16676 }
16677
16678 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16679 self.use_relative_line_numbers
16680 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16681 }
16682
16683 pub fn toggle_relative_line_numbers(
16684 &mut self,
16685 _: &ToggleRelativeLineNumbers,
16686 _: &mut Window,
16687 cx: &mut Context<Self>,
16688 ) {
16689 let is_relative = self.should_use_relative_line_numbers(cx);
16690 self.set_relative_line_number(Some(!is_relative), cx)
16691 }
16692
16693 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16694 self.use_relative_line_numbers = is_relative;
16695 cx.notify();
16696 }
16697
16698 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16699 self.show_gutter = show_gutter;
16700 cx.notify();
16701 }
16702
16703 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
16704 self.show_scrollbars = show_scrollbars;
16705 cx.notify();
16706 }
16707
16708 pub fn set_minimap_visibility(
16709 &mut self,
16710 minimap_visibility: MinimapVisibility,
16711 window: &mut Window,
16712 cx: &mut Context<Self>,
16713 ) {
16714 if self.minimap_visibility != minimap_visibility {
16715 if minimap_visibility.visible() && self.minimap.is_none() {
16716 let minimap_settings = EditorSettings::get_global(cx).minimap;
16717 self.minimap =
16718 self.create_minimap(minimap_settings.with_show_override(), window, cx);
16719 }
16720 self.minimap_visibility = minimap_visibility;
16721 cx.notify();
16722 }
16723 }
16724
16725 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16726 self.set_show_scrollbars(false, cx);
16727 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
16728 }
16729
16730 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
16731 self.show_line_numbers = Some(show_line_numbers);
16732 cx.notify();
16733 }
16734
16735 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
16736 self.disable_expand_excerpt_buttons = true;
16737 cx.notify();
16738 }
16739
16740 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
16741 self.show_git_diff_gutter = Some(show_git_diff_gutter);
16742 cx.notify();
16743 }
16744
16745 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
16746 self.show_code_actions = Some(show_code_actions);
16747 cx.notify();
16748 }
16749
16750 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
16751 self.show_runnables = Some(show_runnables);
16752 cx.notify();
16753 }
16754
16755 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
16756 self.show_breakpoints = Some(show_breakpoints);
16757 cx.notify();
16758 }
16759
16760 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
16761 if self.display_map.read(cx).masked != masked {
16762 self.display_map.update(cx, |map, _| map.masked = masked);
16763 }
16764 cx.notify()
16765 }
16766
16767 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
16768 self.show_wrap_guides = Some(show_wrap_guides);
16769 cx.notify();
16770 }
16771
16772 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
16773 self.show_indent_guides = Some(show_indent_guides);
16774 cx.notify();
16775 }
16776
16777 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
16778 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
16779 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
16780 if let Some(dir) = file.abs_path(cx).parent() {
16781 return Some(dir.to_owned());
16782 }
16783 }
16784
16785 if let Some(project_path) = buffer.read(cx).project_path(cx) {
16786 return Some(project_path.path.to_path_buf());
16787 }
16788 }
16789
16790 None
16791 }
16792
16793 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
16794 self.active_excerpt(cx)?
16795 .1
16796 .read(cx)
16797 .file()
16798 .and_then(|f| f.as_local())
16799 }
16800
16801 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16802 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16803 let buffer = buffer.read(cx);
16804 if let Some(project_path) = buffer.project_path(cx) {
16805 let project = self.project.as_ref()?.read(cx);
16806 project.absolute_path(&project_path, cx)
16807 } else {
16808 buffer
16809 .file()
16810 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
16811 }
16812 })
16813 }
16814
16815 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16816 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16817 let project_path = buffer.read(cx).project_path(cx)?;
16818 let project = self.project.as_ref()?.read(cx);
16819 let entry = project.entry_for_path(&project_path, cx)?;
16820 let path = entry.path.to_path_buf();
16821 Some(path)
16822 })
16823 }
16824
16825 pub fn reveal_in_finder(
16826 &mut self,
16827 _: &RevealInFileManager,
16828 _window: &mut Window,
16829 cx: &mut Context<Self>,
16830 ) {
16831 if let Some(target) = self.target_file(cx) {
16832 cx.reveal_path(&target.abs_path(cx));
16833 }
16834 }
16835
16836 pub fn copy_path(
16837 &mut self,
16838 _: &zed_actions::workspace::CopyPath,
16839 _window: &mut Window,
16840 cx: &mut Context<Self>,
16841 ) {
16842 if let Some(path) = self.target_file_abs_path(cx) {
16843 if let Some(path) = path.to_str() {
16844 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16845 }
16846 }
16847 }
16848
16849 pub fn copy_relative_path(
16850 &mut self,
16851 _: &zed_actions::workspace::CopyRelativePath,
16852 _window: &mut Window,
16853 cx: &mut Context<Self>,
16854 ) {
16855 if let Some(path) = self.target_file_path(cx) {
16856 if let Some(path) = path.to_str() {
16857 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16858 }
16859 }
16860 }
16861
16862 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
16863 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
16864 buffer.read(cx).project_path(cx)
16865 } else {
16866 None
16867 }
16868 }
16869
16870 // Returns true if the editor handled a go-to-line request
16871 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
16872 maybe!({
16873 let breakpoint_store = self.breakpoint_store.as_ref()?;
16874
16875 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
16876 else {
16877 self.clear_row_highlights::<ActiveDebugLine>();
16878 return None;
16879 };
16880
16881 let position = active_stack_frame.position;
16882 let buffer_id = position.buffer_id?;
16883 let snapshot = self
16884 .project
16885 .as_ref()?
16886 .read(cx)
16887 .buffer_for_id(buffer_id, cx)?
16888 .read(cx)
16889 .snapshot();
16890
16891 let mut handled = false;
16892 for (id, ExcerptRange { context, .. }) in
16893 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
16894 {
16895 if context.start.cmp(&position, &snapshot).is_ge()
16896 || context.end.cmp(&position, &snapshot).is_lt()
16897 {
16898 continue;
16899 }
16900 let snapshot = self.buffer.read(cx).snapshot(cx);
16901 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
16902
16903 handled = true;
16904 self.clear_row_highlights::<ActiveDebugLine>();
16905
16906 self.go_to_line::<ActiveDebugLine>(
16907 multibuffer_anchor,
16908 Some(cx.theme().colors().editor_debugger_active_line_background),
16909 window,
16910 cx,
16911 );
16912
16913 cx.notify();
16914 }
16915
16916 handled.then_some(())
16917 })
16918 .is_some()
16919 }
16920
16921 pub fn copy_file_name_without_extension(
16922 &mut self,
16923 _: &CopyFileNameWithoutExtension,
16924 _: &mut Window,
16925 cx: &mut Context<Self>,
16926 ) {
16927 if let Some(file) = self.target_file(cx) {
16928 if let Some(file_stem) = file.path().file_stem() {
16929 if let Some(name) = file_stem.to_str() {
16930 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16931 }
16932 }
16933 }
16934 }
16935
16936 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
16937 if let Some(file) = self.target_file(cx) {
16938 if let Some(file_name) = file.path().file_name() {
16939 if let Some(name) = file_name.to_str() {
16940 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16941 }
16942 }
16943 }
16944 }
16945
16946 pub fn toggle_git_blame(
16947 &mut self,
16948 _: &::git::Blame,
16949 window: &mut Window,
16950 cx: &mut Context<Self>,
16951 ) {
16952 self.show_git_blame_gutter = !self.show_git_blame_gutter;
16953
16954 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
16955 self.start_git_blame(true, window, cx);
16956 }
16957
16958 cx.notify();
16959 }
16960
16961 pub fn toggle_git_blame_inline(
16962 &mut self,
16963 _: &ToggleGitBlameInline,
16964 window: &mut Window,
16965 cx: &mut Context<Self>,
16966 ) {
16967 self.toggle_git_blame_inline_internal(true, window, cx);
16968 cx.notify();
16969 }
16970
16971 pub fn open_git_blame_commit(
16972 &mut self,
16973 _: &OpenGitBlameCommit,
16974 window: &mut Window,
16975 cx: &mut Context<Self>,
16976 ) {
16977 self.open_git_blame_commit_internal(window, cx);
16978 }
16979
16980 fn open_git_blame_commit_internal(
16981 &mut self,
16982 window: &mut Window,
16983 cx: &mut Context<Self>,
16984 ) -> Option<()> {
16985 let blame = self.blame.as_ref()?;
16986 let snapshot = self.snapshot(window, cx);
16987 let cursor = self.selections.newest::<Point>(cx).head();
16988 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
16989 let blame_entry = blame
16990 .update(cx, |blame, cx| {
16991 blame
16992 .blame_for_rows(
16993 &[RowInfo {
16994 buffer_id: Some(buffer.remote_id()),
16995 buffer_row: Some(point.row),
16996 ..Default::default()
16997 }],
16998 cx,
16999 )
17000 .next()
17001 })
17002 .flatten()?;
17003 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17004 let repo = blame.read(cx).repository(cx)?;
17005 let workspace = self.workspace()?.downgrade();
17006 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17007 None
17008 }
17009
17010 pub fn git_blame_inline_enabled(&self) -> bool {
17011 self.git_blame_inline_enabled
17012 }
17013
17014 pub fn toggle_selection_menu(
17015 &mut self,
17016 _: &ToggleSelectionMenu,
17017 _: &mut Window,
17018 cx: &mut Context<Self>,
17019 ) {
17020 self.show_selection_menu = self
17021 .show_selection_menu
17022 .map(|show_selections_menu| !show_selections_menu)
17023 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17024
17025 cx.notify();
17026 }
17027
17028 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17029 self.show_selection_menu
17030 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17031 }
17032
17033 fn start_git_blame(
17034 &mut self,
17035 user_triggered: bool,
17036 window: &mut Window,
17037 cx: &mut Context<Self>,
17038 ) {
17039 if let Some(project) = self.project.as_ref() {
17040 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17041 return;
17042 };
17043
17044 if buffer.read(cx).file().is_none() {
17045 return;
17046 }
17047
17048 let focused = self.focus_handle(cx).contains_focused(window, cx);
17049
17050 let project = project.clone();
17051 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17052 self.blame_subscription =
17053 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17054 self.blame = Some(blame);
17055 }
17056 }
17057
17058 fn toggle_git_blame_inline_internal(
17059 &mut self,
17060 user_triggered: bool,
17061 window: &mut Window,
17062 cx: &mut Context<Self>,
17063 ) {
17064 if self.git_blame_inline_enabled {
17065 self.git_blame_inline_enabled = false;
17066 self.show_git_blame_inline = false;
17067 self.show_git_blame_inline_delay_task.take();
17068 } else {
17069 self.git_blame_inline_enabled = true;
17070 self.start_git_blame_inline(user_triggered, window, cx);
17071 }
17072
17073 cx.notify();
17074 }
17075
17076 fn start_git_blame_inline(
17077 &mut self,
17078 user_triggered: bool,
17079 window: &mut Window,
17080 cx: &mut Context<Self>,
17081 ) {
17082 self.start_git_blame(user_triggered, window, cx);
17083
17084 if ProjectSettings::get_global(cx)
17085 .git
17086 .inline_blame_delay()
17087 .is_some()
17088 {
17089 self.start_inline_blame_timer(window, cx);
17090 } else {
17091 self.show_git_blame_inline = true
17092 }
17093 }
17094
17095 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17096 self.blame.as_ref()
17097 }
17098
17099 pub fn show_git_blame_gutter(&self) -> bool {
17100 self.show_git_blame_gutter
17101 }
17102
17103 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17104 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17105 }
17106
17107 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17108 self.show_git_blame_inline
17109 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17110 && !self.newest_selection_head_on_empty_line(cx)
17111 && self.has_blame_entries(cx)
17112 }
17113
17114 fn has_blame_entries(&self, cx: &App) -> bool {
17115 self.blame()
17116 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17117 }
17118
17119 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17120 let cursor_anchor = self.selections.newest_anchor().head();
17121
17122 let snapshot = self.buffer.read(cx).snapshot(cx);
17123 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17124
17125 snapshot.line_len(buffer_row) == 0
17126 }
17127
17128 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17129 let buffer_and_selection = maybe!({
17130 let selection = self.selections.newest::<Point>(cx);
17131 let selection_range = selection.range();
17132
17133 let multi_buffer = self.buffer().read(cx);
17134 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17135 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17136
17137 let (buffer, range, _) = if selection.reversed {
17138 buffer_ranges.first()
17139 } else {
17140 buffer_ranges.last()
17141 }?;
17142
17143 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17144 ..text::ToPoint::to_point(&range.end, &buffer).row;
17145 Some((
17146 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17147 selection,
17148 ))
17149 });
17150
17151 let Some((buffer, selection)) = buffer_and_selection else {
17152 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17153 };
17154
17155 let Some(project) = self.project.as_ref() else {
17156 return Task::ready(Err(anyhow!("editor does not have project")));
17157 };
17158
17159 project.update(cx, |project, cx| {
17160 project.get_permalink_to_line(&buffer, selection, cx)
17161 })
17162 }
17163
17164 pub fn copy_permalink_to_line(
17165 &mut self,
17166 _: &CopyPermalinkToLine,
17167 window: &mut Window,
17168 cx: &mut Context<Self>,
17169 ) {
17170 let permalink_task = self.get_permalink_to_line(cx);
17171 let workspace = self.workspace();
17172
17173 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17174 Ok(permalink) => {
17175 cx.update(|_, cx| {
17176 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17177 })
17178 .ok();
17179 }
17180 Err(err) => {
17181 let message = format!("Failed to copy permalink: {err}");
17182
17183 Err::<(), anyhow::Error>(err).log_err();
17184
17185 if let Some(workspace) = workspace {
17186 workspace
17187 .update_in(cx, |workspace, _, cx| {
17188 struct CopyPermalinkToLine;
17189
17190 workspace.show_toast(
17191 Toast::new(
17192 NotificationId::unique::<CopyPermalinkToLine>(),
17193 message,
17194 ),
17195 cx,
17196 )
17197 })
17198 .ok();
17199 }
17200 }
17201 })
17202 .detach();
17203 }
17204
17205 pub fn copy_file_location(
17206 &mut self,
17207 _: &CopyFileLocation,
17208 _: &mut Window,
17209 cx: &mut Context<Self>,
17210 ) {
17211 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17212 if let Some(file) = self.target_file(cx) {
17213 if let Some(path) = file.path().to_str() {
17214 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17215 }
17216 }
17217 }
17218
17219 pub fn open_permalink_to_line(
17220 &mut self,
17221 _: &OpenPermalinkToLine,
17222 window: &mut Window,
17223 cx: &mut Context<Self>,
17224 ) {
17225 let permalink_task = self.get_permalink_to_line(cx);
17226 let workspace = self.workspace();
17227
17228 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17229 Ok(permalink) => {
17230 cx.update(|_, cx| {
17231 cx.open_url(permalink.as_ref());
17232 })
17233 .ok();
17234 }
17235 Err(err) => {
17236 let message = format!("Failed to open permalink: {err}");
17237
17238 Err::<(), anyhow::Error>(err).log_err();
17239
17240 if let Some(workspace) = workspace {
17241 workspace
17242 .update(cx, |workspace, cx| {
17243 struct OpenPermalinkToLine;
17244
17245 workspace.show_toast(
17246 Toast::new(
17247 NotificationId::unique::<OpenPermalinkToLine>(),
17248 message,
17249 ),
17250 cx,
17251 )
17252 })
17253 .ok();
17254 }
17255 }
17256 })
17257 .detach();
17258 }
17259
17260 pub fn insert_uuid_v4(
17261 &mut self,
17262 _: &InsertUuidV4,
17263 window: &mut Window,
17264 cx: &mut Context<Self>,
17265 ) {
17266 self.insert_uuid(UuidVersion::V4, window, cx);
17267 }
17268
17269 pub fn insert_uuid_v7(
17270 &mut self,
17271 _: &InsertUuidV7,
17272 window: &mut Window,
17273 cx: &mut Context<Self>,
17274 ) {
17275 self.insert_uuid(UuidVersion::V7, window, cx);
17276 }
17277
17278 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17279 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17280 self.transact(window, cx, |this, window, cx| {
17281 let edits = this
17282 .selections
17283 .all::<Point>(cx)
17284 .into_iter()
17285 .map(|selection| {
17286 let uuid = match version {
17287 UuidVersion::V4 => uuid::Uuid::new_v4(),
17288 UuidVersion::V7 => uuid::Uuid::now_v7(),
17289 };
17290
17291 (selection.range(), uuid.to_string())
17292 });
17293 this.edit(edits, cx);
17294 this.refresh_inline_completion(true, false, window, cx);
17295 });
17296 }
17297
17298 pub fn open_selections_in_multibuffer(
17299 &mut self,
17300 _: &OpenSelectionsInMultibuffer,
17301 window: &mut Window,
17302 cx: &mut Context<Self>,
17303 ) {
17304 let multibuffer = self.buffer.read(cx);
17305
17306 let Some(buffer) = multibuffer.as_singleton() else {
17307 return;
17308 };
17309
17310 let Some(workspace) = self.workspace() else {
17311 return;
17312 };
17313
17314 let locations = self
17315 .selections
17316 .disjoint_anchors()
17317 .iter()
17318 .map(|range| Location {
17319 buffer: buffer.clone(),
17320 range: range.start.text_anchor..range.end.text_anchor,
17321 })
17322 .collect::<Vec<_>>();
17323
17324 let title = multibuffer.title(cx).to_string();
17325
17326 cx.spawn_in(window, async move |_, cx| {
17327 workspace.update_in(cx, |workspace, window, cx| {
17328 Self::open_locations_in_multibuffer(
17329 workspace,
17330 locations,
17331 format!("Selections for '{title}'"),
17332 false,
17333 MultibufferSelectionMode::All,
17334 window,
17335 cx,
17336 );
17337 })
17338 })
17339 .detach();
17340 }
17341
17342 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17343 /// last highlight added will be used.
17344 ///
17345 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17346 pub fn highlight_rows<T: 'static>(
17347 &mut self,
17348 range: Range<Anchor>,
17349 color: Hsla,
17350 options: RowHighlightOptions,
17351 cx: &mut Context<Self>,
17352 ) {
17353 let snapshot = self.buffer().read(cx).snapshot(cx);
17354 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17355 let ix = row_highlights.binary_search_by(|highlight| {
17356 Ordering::Equal
17357 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17358 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17359 });
17360
17361 if let Err(mut ix) = ix {
17362 let index = post_inc(&mut self.highlight_order);
17363
17364 // If this range intersects with the preceding highlight, then merge it with
17365 // the preceding highlight. Otherwise insert a new highlight.
17366 let mut merged = false;
17367 if ix > 0 {
17368 let prev_highlight = &mut row_highlights[ix - 1];
17369 if prev_highlight
17370 .range
17371 .end
17372 .cmp(&range.start, &snapshot)
17373 .is_ge()
17374 {
17375 ix -= 1;
17376 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17377 prev_highlight.range.end = range.end;
17378 }
17379 merged = true;
17380 prev_highlight.index = index;
17381 prev_highlight.color = color;
17382 prev_highlight.options = options;
17383 }
17384 }
17385
17386 if !merged {
17387 row_highlights.insert(
17388 ix,
17389 RowHighlight {
17390 range: range.clone(),
17391 index,
17392 color,
17393 options,
17394 type_id: TypeId::of::<T>(),
17395 },
17396 );
17397 }
17398
17399 // If any of the following highlights intersect with this one, merge them.
17400 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17401 let highlight = &row_highlights[ix];
17402 if next_highlight
17403 .range
17404 .start
17405 .cmp(&highlight.range.end, &snapshot)
17406 .is_le()
17407 {
17408 if next_highlight
17409 .range
17410 .end
17411 .cmp(&highlight.range.end, &snapshot)
17412 .is_gt()
17413 {
17414 row_highlights[ix].range.end = next_highlight.range.end;
17415 }
17416 row_highlights.remove(ix + 1);
17417 } else {
17418 break;
17419 }
17420 }
17421 }
17422 }
17423
17424 /// Remove any highlighted row ranges of the given type that intersect the
17425 /// given ranges.
17426 pub fn remove_highlighted_rows<T: 'static>(
17427 &mut self,
17428 ranges_to_remove: Vec<Range<Anchor>>,
17429 cx: &mut Context<Self>,
17430 ) {
17431 let snapshot = self.buffer().read(cx).snapshot(cx);
17432 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17433 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17434 row_highlights.retain(|highlight| {
17435 while let Some(range_to_remove) = ranges_to_remove.peek() {
17436 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17437 Ordering::Less | Ordering::Equal => {
17438 ranges_to_remove.next();
17439 }
17440 Ordering::Greater => {
17441 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17442 Ordering::Less | Ordering::Equal => {
17443 return false;
17444 }
17445 Ordering::Greater => break,
17446 }
17447 }
17448 }
17449 }
17450
17451 true
17452 })
17453 }
17454
17455 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17456 pub fn clear_row_highlights<T: 'static>(&mut self) {
17457 self.highlighted_rows.remove(&TypeId::of::<T>());
17458 }
17459
17460 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17461 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17462 self.highlighted_rows
17463 .get(&TypeId::of::<T>())
17464 .map_or(&[] as &[_], |vec| vec.as_slice())
17465 .iter()
17466 .map(|highlight| (highlight.range.clone(), highlight.color))
17467 }
17468
17469 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17470 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17471 /// Allows to ignore certain kinds of highlights.
17472 pub fn highlighted_display_rows(
17473 &self,
17474 window: &mut Window,
17475 cx: &mut App,
17476 ) -> BTreeMap<DisplayRow, LineHighlight> {
17477 let snapshot = self.snapshot(window, cx);
17478 let mut used_highlight_orders = HashMap::default();
17479 self.highlighted_rows
17480 .iter()
17481 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17482 .fold(
17483 BTreeMap::<DisplayRow, LineHighlight>::new(),
17484 |mut unique_rows, highlight| {
17485 let start = highlight.range.start.to_display_point(&snapshot);
17486 let end = highlight.range.end.to_display_point(&snapshot);
17487 let start_row = start.row().0;
17488 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17489 && end.column() == 0
17490 {
17491 end.row().0.saturating_sub(1)
17492 } else {
17493 end.row().0
17494 };
17495 for row in start_row..=end_row {
17496 let used_index =
17497 used_highlight_orders.entry(row).or_insert(highlight.index);
17498 if highlight.index >= *used_index {
17499 *used_index = highlight.index;
17500 unique_rows.insert(
17501 DisplayRow(row),
17502 LineHighlight {
17503 include_gutter: highlight.options.include_gutter,
17504 border: None,
17505 background: highlight.color.into(),
17506 type_id: Some(highlight.type_id),
17507 },
17508 );
17509 }
17510 }
17511 unique_rows
17512 },
17513 )
17514 }
17515
17516 pub fn highlighted_display_row_for_autoscroll(
17517 &self,
17518 snapshot: &DisplaySnapshot,
17519 ) -> Option<DisplayRow> {
17520 self.highlighted_rows
17521 .values()
17522 .flat_map(|highlighted_rows| highlighted_rows.iter())
17523 .filter_map(|highlight| {
17524 if highlight.options.autoscroll {
17525 Some(highlight.range.start.to_display_point(snapshot).row())
17526 } else {
17527 None
17528 }
17529 })
17530 .min()
17531 }
17532
17533 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17534 self.highlight_background::<SearchWithinRange>(
17535 ranges,
17536 |colors| colors.editor_document_highlight_read_background,
17537 cx,
17538 )
17539 }
17540
17541 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17542 self.breadcrumb_header = Some(new_header);
17543 }
17544
17545 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17546 self.clear_background_highlights::<SearchWithinRange>(cx);
17547 }
17548
17549 pub fn highlight_background<T: 'static>(
17550 &mut self,
17551 ranges: &[Range<Anchor>],
17552 color_fetcher: fn(&ThemeColors) -> Hsla,
17553 cx: &mut Context<Self>,
17554 ) {
17555 self.background_highlights
17556 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17557 self.scrollbar_marker_state.dirty = true;
17558 cx.notify();
17559 }
17560
17561 pub fn clear_background_highlights<T: 'static>(
17562 &mut self,
17563 cx: &mut Context<Self>,
17564 ) -> Option<BackgroundHighlight> {
17565 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17566 if !text_highlights.1.is_empty() {
17567 self.scrollbar_marker_state.dirty = true;
17568 cx.notify();
17569 }
17570 Some(text_highlights)
17571 }
17572
17573 pub fn highlight_gutter<T: 'static>(
17574 &mut self,
17575 ranges: &[Range<Anchor>],
17576 color_fetcher: fn(&App) -> Hsla,
17577 cx: &mut Context<Self>,
17578 ) {
17579 self.gutter_highlights
17580 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17581 cx.notify();
17582 }
17583
17584 pub fn clear_gutter_highlights<T: 'static>(
17585 &mut self,
17586 cx: &mut Context<Self>,
17587 ) -> Option<GutterHighlight> {
17588 cx.notify();
17589 self.gutter_highlights.remove(&TypeId::of::<T>())
17590 }
17591
17592 #[cfg(feature = "test-support")]
17593 pub fn all_text_background_highlights(
17594 &self,
17595 window: &mut Window,
17596 cx: &mut Context<Self>,
17597 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17598 let snapshot = self.snapshot(window, cx);
17599 let buffer = &snapshot.buffer_snapshot;
17600 let start = buffer.anchor_before(0);
17601 let end = buffer.anchor_after(buffer.len());
17602 let theme = cx.theme().colors();
17603 self.background_highlights_in_range(start..end, &snapshot, theme)
17604 }
17605
17606 #[cfg(feature = "test-support")]
17607 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17608 let snapshot = self.buffer().read(cx).snapshot(cx);
17609
17610 let highlights = self
17611 .background_highlights
17612 .get(&TypeId::of::<items::BufferSearchHighlights>());
17613
17614 if let Some((_color, ranges)) = highlights {
17615 ranges
17616 .iter()
17617 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17618 .collect_vec()
17619 } else {
17620 vec![]
17621 }
17622 }
17623
17624 fn document_highlights_for_position<'a>(
17625 &'a self,
17626 position: Anchor,
17627 buffer: &'a MultiBufferSnapshot,
17628 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17629 let read_highlights = self
17630 .background_highlights
17631 .get(&TypeId::of::<DocumentHighlightRead>())
17632 .map(|h| &h.1);
17633 let write_highlights = self
17634 .background_highlights
17635 .get(&TypeId::of::<DocumentHighlightWrite>())
17636 .map(|h| &h.1);
17637 let left_position = position.bias_left(buffer);
17638 let right_position = position.bias_right(buffer);
17639 read_highlights
17640 .into_iter()
17641 .chain(write_highlights)
17642 .flat_map(move |ranges| {
17643 let start_ix = match ranges.binary_search_by(|probe| {
17644 let cmp = probe.end.cmp(&left_position, buffer);
17645 if cmp.is_ge() {
17646 Ordering::Greater
17647 } else {
17648 Ordering::Less
17649 }
17650 }) {
17651 Ok(i) | Err(i) => i,
17652 };
17653
17654 ranges[start_ix..]
17655 .iter()
17656 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17657 })
17658 }
17659
17660 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17661 self.background_highlights
17662 .get(&TypeId::of::<T>())
17663 .map_or(false, |(_, highlights)| !highlights.is_empty())
17664 }
17665
17666 pub fn background_highlights_in_range(
17667 &self,
17668 search_range: Range<Anchor>,
17669 display_snapshot: &DisplaySnapshot,
17670 theme: &ThemeColors,
17671 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17672 let mut results = Vec::new();
17673 for (color_fetcher, ranges) in self.background_highlights.values() {
17674 let color = color_fetcher(theme);
17675 let start_ix = match ranges.binary_search_by(|probe| {
17676 let cmp = probe
17677 .end
17678 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17679 if cmp.is_gt() {
17680 Ordering::Greater
17681 } else {
17682 Ordering::Less
17683 }
17684 }) {
17685 Ok(i) | Err(i) => i,
17686 };
17687 for range in &ranges[start_ix..] {
17688 if range
17689 .start
17690 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17691 .is_ge()
17692 {
17693 break;
17694 }
17695
17696 let start = range.start.to_display_point(display_snapshot);
17697 let end = range.end.to_display_point(display_snapshot);
17698 results.push((start..end, color))
17699 }
17700 }
17701 results
17702 }
17703
17704 pub fn background_highlight_row_ranges<T: 'static>(
17705 &self,
17706 search_range: Range<Anchor>,
17707 display_snapshot: &DisplaySnapshot,
17708 count: usize,
17709 ) -> Vec<RangeInclusive<DisplayPoint>> {
17710 let mut results = Vec::new();
17711 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
17712 return vec![];
17713 };
17714
17715 let start_ix = match ranges.binary_search_by(|probe| {
17716 let cmp = probe
17717 .end
17718 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17719 if cmp.is_gt() {
17720 Ordering::Greater
17721 } else {
17722 Ordering::Less
17723 }
17724 }) {
17725 Ok(i) | Err(i) => i,
17726 };
17727 let mut push_region = |start: Option<Point>, end: Option<Point>| {
17728 if let (Some(start_display), Some(end_display)) = (start, end) {
17729 results.push(
17730 start_display.to_display_point(display_snapshot)
17731 ..=end_display.to_display_point(display_snapshot),
17732 );
17733 }
17734 };
17735 let mut start_row: Option<Point> = None;
17736 let mut end_row: Option<Point> = None;
17737 if ranges.len() > count {
17738 return Vec::new();
17739 }
17740 for range in &ranges[start_ix..] {
17741 if range
17742 .start
17743 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17744 .is_ge()
17745 {
17746 break;
17747 }
17748 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
17749 if let Some(current_row) = &end_row {
17750 if end.row == current_row.row {
17751 continue;
17752 }
17753 }
17754 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
17755 if start_row.is_none() {
17756 assert_eq!(end_row, None);
17757 start_row = Some(start);
17758 end_row = Some(end);
17759 continue;
17760 }
17761 if let Some(current_end) = end_row.as_mut() {
17762 if start.row > current_end.row + 1 {
17763 push_region(start_row, end_row);
17764 start_row = Some(start);
17765 end_row = Some(end);
17766 } else {
17767 // Merge two hunks.
17768 *current_end = end;
17769 }
17770 } else {
17771 unreachable!();
17772 }
17773 }
17774 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
17775 push_region(start_row, end_row);
17776 results
17777 }
17778
17779 pub fn gutter_highlights_in_range(
17780 &self,
17781 search_range: Range<Anchor>,
17782 display_snapshot: &DisplaySnapshot,
17783 cx: &App,
17784 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17785 let mut results = Vec::new();
17786 for (color_fetcher, ranges) in self.gutter_highlights.values() {
17787 let color = color_fetcher(cx);
17788 let start_ix = match ranges.binary_search_by(|probe| {
17789 let cmp = probe
17790 .end
17791 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17792 if cmp.is_gt() {
17793 Ordering::Greater
17794 } else {
17795 Ordering::Less
17796 }
17797 }) {
17798 Ok(i) | Err(i) => i,
17799 };
17800 for range in &ranges[start_ix..] {
17801 if range
17802 .start
17803 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17804 .is_ge()
17805 {
17806 break;
17807 }
17808
17809 let start = range.start.to_display_point(display_snapshot);
17810 let end = range.end.to_display_point(display_snapshot);
17811 results.push((start..end, color))
17812 }
17813 }
17814 results
17815 }
17816
17817 /// Get the text ranges corresponding to the redaction query
17818 pub fn redacted_ranges(
17819 &self,
17820 search_range: Range<Anchor>,
17821 display_snapshot: &DisplaySnapshot,
17822 cx: &App,
17823 ) -> Vec<Range<DisplayPoint>> {
17824 display_snapshot
17825 .buffer_snapshot
17826 .redacted_ranges(search_range, |file| {
17827 if let Some(file) = file {
17828 file.is_private()
17829 && EditorSettings::get(
17830 Some(SettingsLocation {
17831 worktree_id: file.worktree_id(cx),
17832 path: file.path().as_ref(),
17833 }),
17834 cx,
17835 )
17836 .redact_private_values
17837 } else {
17838 false
17839 }
17840 })
17841 .map(|range| {
17842 range.start.to_display_point(display_snapshot)
17843 ..range.end.to_display_point(display_snapshot)
17844 })
17845 .collect()
17846 }
17847
17848 pub fn highlight_text<T: 'static>(
17849 &mut self,
17850 ranges: Vec<Range<Anchor>>,
17851 style: HighlightStyle,
17852 cx: &mut Context<Self>,
17853 ) {
17854 self.display_map.update(cx, |map, _| {
17855 map.highlight_text(TypeId::of::<T>(), ranges, style)
17856 });
17857 cx.notify();
17858 }
17859
17860 pub(crate) fn highlight_inlays<T: 'static>(
17861 &mut self,
17862 highlights: Vec<InlayHighlight>,
17863 style: HighlightStyle,
17864 cx: &mut Context<Self>,
17865 ) {
17866 self.display_map.update(cx, |map, _| {
17867 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
17868 });
17869 cx.notify();
17870 }
17871
17872 pub fn text_highlights<'a, T: 'static>(
17873 &'a self,
17874 cx: &'a App,
17875 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
17876 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
17877 }
17878
17879 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
17880 let cleared = self
17881 .display_map
17882 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
17883 if cleared {
17884 cx.notify();
17885 }
17886 }
17887
17888 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
17889 (self.read_only(cx) || self.blink_manager.read(cx).visible())
17890 && self.focus_handle.is_focused(window)
17891 }
17892
17893 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
17894 self.show_cursor_when_unfocused = is_enabled;
17895 cx.notify();
17896 }
17897
17898 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
17899 cx.notify();
17900 }
17901
17902 fn on_debug_session_event(
17903 &mut self,
17904 _session: Entity<Session>,
17905 event: &SessionEvent,
17906 cx: &mut Context<Self>,
17907 ) {
17908 match event {
17909 SessionEvent::InvalidateInlineValue => {
17910 self.refresh_inline_values(cx);
17911 }
17912 _ => {}
17913 }
17914 }
17915
17916 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
17917 let Some(project) = self.project.clone() else {
17918 return;
17919 };
17920
17921 if !self.inline_value_cache.enabled {
17922 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
17923 self.splice_inlays(&inlays, Vec::new(), cx);
17924 return;
17925 }
17926
17927 let current_execution_position = self
17928 .highlighted_rows
17929 .get(&TypeId::of::<ActiveDebugLine>())
17930 .and_then(|lines| lines.last().map(|line| line.range.start));
17931
17932 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
17933 let inline_values = editor
17934 .update(cx, |editor, cx| {
17935 let Some(current_execution_position) = current_execution_position else {
17936 return Some(Task::ready(Ok(Vec::new())));
17937 };
17938
17939 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
17940 let snapshot = buffer.snapshot(cx);
17941
17942 let excerpt = snapshot.excerpt_containing(
17943 current_execution_position..current_execution_position,
17944 )?;
17945
17946 editor.buffer.read(cx).buffer(excerpt.buffer_id())
17947 })?;
17948
17949 let range =
17950 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
17951
17952 project.inline_values(buffer, range, cx)
17953 })
17954 .ok()
17955 .flatten()?
17956 .await
17957 .context("refreshing debugger inlays")
17958 .log_err()?;
17959
17960 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
17961
17962 for (buffer_id, inline_value) in inline_values
17963 .into_iter()
17964 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
17965 {
17966 buffer_inline_values
17967 .entry(buffer_id)
17968 .or_default()
17969 .push(inline_value);
17970 }
17971
17972 editor
17973 .update(cx, |editor, cx| {
17974 let snapshot = editor.buffer.read(cx).snapshot(cx);
17975 let mut new_inlays = Vec::default();
17976
17977 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
17978 let buffer_id = buffer_snapshot.remote_id();
17979 buffer_inline_values
17980 .get(&buffer_id)
17981 .into_iter()
17982 .flatten()
17983 .for_each(|hint| {
17984 let inlay = Inlay::debugger_hint(
17985 post_inc(&mut editor.next_inlay_id),
17986 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
17987 hint.text(),
17988 );
17989
17990 new_inlays.push(inlay);
17991 });
17992 }
17993
17994 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
17995 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
17996
17997 editor.splice_inlays(&inlay_ids, new_inlays, cx);
17998 })
17999 .ok()?;
18000 Some(())
18001 });
18002 }
18003
18004 fn on_buffer_event(
18005 &mut self,
18006 multibuffer: &Entity<MultiBuffer>,
18007 event: &multi_buffer::Event,
18008 window: &mut Window,
18009 cx: &mut Context<Self>,
18010 ) {
18011 match event {
18012 multi_buffer::Event::Edited {
18013 singleton_buffer_edited,
18014 edited_buffer: buffer_edited,
18015 } => {
18016 self.scrollbar_marker_state.dirty = true;
18017 self.active_indent_guides_state.dirty = true;
18018 self.refresh_active_diagnostics(cx);
18019 self.refresh_code_actions(window, cx);
18020 self.refresh_selected_text_highlights(true, window, cx);
18021 refresh_matching_bracket_highlights(self, window, cx);
18022 if self.has_active_inline_completion() {
18023 self.update_visible_inline_completion(window, cx);
18024 }
18025 if let Some(buffer) = buffer_edited {
18026 let buffer_id = buffer.read(cx).remote_id();
18027 if !self.registered_buffers.contains_key(&buffer_id) {
18028 if let Some(project) = self.project.as_ref() {
18029 project.update(cx, |project, cx| {
18030 self.registered_buffers.insert(
18031 buffer_id,
18032 project.register_buffer_with_language_servers(&buffer, cx),
18033 );
18034 })
18035 }
18036 }
18037 }
18038 cx.emit(EditorEvent::BufferEdited);
18039 cx.emit(SearchEvent::MatchesInvalidated);
18040 if *singleton_buffer_edited {
18041 if let Some(project) = &self.project {
18042 #[allow(clippy::mutable_key_type)]
18043 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18044 multibuffer
18045 .all_buffers()
18046 .into_iter()
18047 .filter_map(|buffer| {
18048 buffer.update(cx, |buffer, cx| {
18049 let language = buffer.language()?;
18050 let should_discard = project.update(cx, |project, cx| {
18051 project.is_local()
18052 && !project.has_language_servers_for(buffer, cx)
18053 });
18054 should_discard.not().then_some(language.clone())
18055 })
18056 })
18057 .collect::<HashSet<_>>()
18058 });
18059 if !languages_affected.is_empty() {
18060 self.refresh_inlay_hints(
18061 InlayHintRefreshReason::BufferEdited(languages_affected),
18062 cx,
18063 );
18064 }
18065 }
18066 }
18067
18068 let Some(project) = &self.project else { return };
18069 let (telemetry, is_via_ssh) = {
18070 let project = project.read(cx);
18071 let telemetry = project.client().telemetry().clone();
18072 let is_via_ssh = project.is_via_ssh();
18073 (telemetry, is_via_ssh)
18074 };
18075 refresh_linked_ranges(self, window, cx);
18076 telemetry.log_edit_event("editor", is_via_ssh);
18077 }
18078 multi_buffer::Event::ExcerptsAdded {
18079 buffer,
18080 predecessor,
18081 excerpts,
18082 } => {
18083 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18084 let buffer_id = buffer.read(cx).remote_id();
18085 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18086 if let Some(project) = &self.project {
18087 update_uncommitted_diff_for_buffer(
18088 cx.entity(),
18089 project,
18090 [buffer.clone()],
18091 self.buffer.clone(),
18092 cx,
18093 )
18094 .detach();
18095 }
18096 }
18097 cx.emit(EditorEvent::ExcerptsAdded {
18098 buffer: buffer.clone(),
18099 predecessor: *predecessor,
18100 excerpts: excerpts.clone(),
18101 });
18102 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18103 }
18104 multi_buffer::Event::ExcerptsRemoved {
18105 ids,
18106 removed_buffer_ids,
18107 } => {
18108 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18109 let buffer = self.buffer.read(cx);
18110 self.registered_buffers
18111 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18112 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18113 cx.emit(EditorEvent::ExcerptsRemoved {
18114 ids: ids.clone(),
18115 removed_buffer_ids: removed_buffer_ids.clone(),
18116 })
18117 }
18118 multi_buffer::Event::ExcerptsEdited {
18119 excerpt_ids,
18120 buffer_ids,
18121 } => {
18122 self.display_map.update(cx, |map, cx| {
18123 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18124 });
18125 cx.emit(EditorEvent::ExcerptsEdited {
18126 ids: excerpt_ids.clone(),
18127 })
18128 }
18129 multi_buffer::Event::ExcerptsExpanded { ids } => {
18130 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18131 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18132 }
18133 multi_buffer::Event::Reparsed(buffer_id) => {
18134 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18135 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18136
18137 cx.emit(EditorEvent::Reparsed(*buffer_id));
18138 }
18139 multi_buffer::Event::DiffHunksToggled => {
18140 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18141 }
18142 multi_buffer::Event::LanguageChanged(buffer_id) => {
18143 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18144 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18145 cx.emit(EditorEvent::Reparsed(*buffer_id));
18146 cx.notify();
18147 }
18148 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18149 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18150 multi_buffer::Event::FileHandleChanged
18151 | multi_buffer::Event::Reloaded
18152 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18153 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18154 multi_buffer::Event::DiagnosticsUpdated => {
18155 self.refresh_active_diagnostics(cx);
18156 self.refresh_inline_diagnostics(true, window, cx);
18157 self.scrollbar_marker_state.dirty = true;
18158 cx.notify();
18159 }
18160 _ => {}
18161 };
18162 }
18163
18164 pub fn start_temporary_diff_override(&mut self) {
18165 self.load_diff_task.take();
18166 self.temporary_diff_override = true;
18167 }
18168
18169 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18170 self.temporary_diff_override = false;
18171 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18172 self.buffer.update(cx, |buffer, cx| {
18173 buffer.set_all_diff_hunks_collapsed(cx);
18174 });
18175
18176 if let Some(project) = self.project.clone() {
18177 self.load_diff_task = Some(
18178 update_uncommitted_diff_for_buffer(
18179 cx.entity(),
18180 &project,
18181 self.buffer.read(cx).all_buffers(),
18182 self.buffer.clone(),
18183 cx,
18184 )
18185 .shared(),
18186 );
18187 }
18188 }
18189
18190 fn on_display_map_changed(
18191 &mut self,
18192 _: Entity<DisplayMap>,
18193 _: &mut Window,
18194 cx: &mut Context<Self>,
18195 ) {
18196 cx.notify();
18197 }
18198
18199 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18200 let new_severity = if self.diagnostics_enabled() {
18201 EditorSettings::get_global(cx)
18202 .diagnostics_max_severity
18203 .unwrap_or(DiagnosticSeverity::Hint)
18204 } else {
18205 DiagnosticSeverity::Off
18206 };
18207 self.set_max_diagnostics_severity(new_severity, cx);
18208 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18209 self.update_edit_prediction_settings(cx);
18210 self.refresh_inline_completion(true, false, window, cx);
18211 self.refresh_inlay_hints(
18212 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18213 self.selections.newest_anchor().head(),
18214 &self.buffer.read(cx).snapshot(cx),
18215 cx,
18216 )),
18217 cx,
18218 );
18219
18220 let old_cursor_shape = self.cursor_shape;
18221
18222 {
18223 let editor_settings = EditorSettings::get_global(cx);
18224 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18225 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18226 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18227 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18228 }
18229
18230 if old_cursor_shape != self.cursor_shape {
18231 cx.emit(EditorEvent::CursorShapeChanged);
18232 }
18233
18234 let project_settings = ProjectSettings::get_global(cx);
18235 self.serialize_dirty_buffers =
18236 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18237
18238 if self.mode.is_full() {
18239 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18240 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18241 if self.show_inline_diagnostics != show_inline_diagnostics {
18242 self.show_inline_diagnostics = show_inline_diagnostics;
18243 self.refresh_inline_diagnostics(false, window, cx);
18244 }
18245
18246 if self.git_blame_inline_enabled != inline_blame_enabled {
18247 self.toggle_git_blame_inline_internal(false, window, cx);
18248 }
18249
18250 let minimap_settings = EditorSettings::get_global(cx).minimap;
18251 if self.minimap_visibility.visible() != minimap_settings.minimap_enabled() {
18252 self.set_minimap_visibility(
18253 self.minimap_visibility.toggle_visibility(),
18254 window,
18255 cx,
18256 );
18257 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18258 minimap_entity.update(cx, |minimap_editor, cx| {
18259 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18260 })
18261 }
18262 }
18263
18264 cx.notify();
18265 }
18266
18267 pub fn set_searchable(&mut self, searchable: bool) {
18268 self.searchable = searchable;
18269 }
18270
18271 pub fn searchable(&self) -> bool {
18272 self.searchable
18273 }
18274
18275 fn open_proposed_changes_editor(
18276 &mut self,
18277 _: &OpenProposedChangesEditor,
18278 window: &mut Window,
18279 cx: &mut Context<Self>,
18280 ) {
18281 let Some(workspace) = self.workspace() else {
18282 cx.propagate();
18283 return;
18284 };
18285
18286 let selections = self.selections.all::<usize>(cx);
18287 let multi_buffer = self.buffer.read(cx);
18288 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18289 let mut new_selections_by_buffer = HashMap::default();
18290 for selection in selections {
18291 for (buffer, range, _) in
18292 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18293 {
18294 let mut range = range.to_point(buffer);
18295 range.start.column = 0;
18296 range.end.column = buffer.line_len(range.end.row);
18297 new_selections_by_buffer
18298 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18299 .or_insert(Vec::new())
18300 .push(range)
18301 }
18302 }
18303
18304 let proposed_changes_buffers = new_selections_by_buffer
18305 .into_iter()
18306 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18307 .collect::<Vec<_>>();
18308 let proposed_changes_editor = cx.new(|cx| {
18309 ProposedChangesEditor::new(
18310 "Proposed changes",
18311 proposed_changes_buffers,
18312 self.project.clone(),
18313 window,
18314 cx,
18315 )
18316 });
18317
18318 window.defer(cx, move |window, cx| {
18319 workspace.update(cx, |workspace, cx| {
18320 workspace.active_pane().update(cx, |pane, cx| {
18321 pane.add_item(
18322 Box::new(proposed_changes_editor),
18323 true,
18324 true,
18325 None,
18326 window,
18327 cx,
18328 );
18329 });
18330 });
18331 });
18332 }
18333
18334 pub fn open_excerpts_in_split(
18335 &mut self,
18336 _: &OpenExcerptsSplit,
18337 window: &mut Window,
18338 cx: &mut Context<Self>,
18339 ) {
18340 self.open_excerpts_common(None, true, window, cx)
18341 }
18342
18343 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18344 self.open_excerpts_common(None, false, window, cx)
18345 }
18346
18347 fn open_excerpts_common(
18348 &mut self,
18349 jump_data: Option<JumpData>,
18350 split: bool,
18351 window: &mut Window,
18352 cx: &mut Context<Self>,
18353 ) {
18354 let Some(workspace) = self.workspace() else {
18355 cx.propagate();
18356 return;
18357 };
18358
18359 if self.buffer.read(cx).is_singleton() {
18360 cx.propagate();
18361 return;
18362 }
18363
18364 let mut new_selections_by_buffer = HashMap::default();
18365 match &jump_data {
18366 Some(JumpData::MultiBufferPoint {
18367 excerpt_id,
18368 position,
18369 anchor,
18370 line_offset_from_top,
18371 }) => {
18372 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18373 if let Some(buffer) = multi_buffer_snapshot
18374 .buffer_id_for_excerpt(*excerpt_id)
18375 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18376 {
18377 let buffer_snapshot = buffer.read(cx).snapshot();
18378 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18379 language::ToPoint::to_point(anchor, &buffer_snapshot)
18380 } else {
18381 buffer_snapshot.clip_point(*position, Bias::Left)
18382 };
18383 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18384 new_selections_by_buffer.insert(
18385 buffer,
18386 (
18387 vec![jump_to_offset..jump_to_offset],
18388 Some(*line_offset_from_top),
18389 ),
18390 );
18391 }
18392 }
18393 Some(JumpData::MultiBufferRow {
18394 row,
18395 line_offset_from_top,
18396 }) => {
18397 let point = MultiBufferPoint::new(row.0, 0);
18398 if let Some((buffer, buffer_point, _)) =
18399 self.buffer.read(cx).point_to_buffer_point(point, cx)
18400 {
18401 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18402 new_selections_by_buffer
18403 .entry(buffer)
18404 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18405 .0
18406 .push(buffer_offset..buffer_offset)
18407 }
18408 }
18409 None => {
18410 let selections = self.selections.all::<usize>(cx);
18411 let multi_buffer = self.buffer.read(cx);
18412 for selection in selections {
18413 for (snapshot, range, _, anchor) in multi_buffer
18414 .snapshot(cx)
18415 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18416 {
18417 if let Some(anchor) = anchor {
18418 // selection is in a deleted hunk
18419 let Some(buffer_id) = anchor.buffer_id else {
18420 continue;
18421 };
18422 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18423 continue;
18424 };
18425 let offset = text::ToOffset::to_offset(
18426 &anchor.text_anchor,
18427 &buffer_handle.read(cx).snapshot(),
18428 );
18429 let range = offset..offset;
18430 new_selections_by_buffer
18431 .entry(buffer_handle)
18432 .or_insert((Vec::new(), None))
18433 .0
18434 .push(range)
18435 } else {
18436 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18437 else {
18438 continue;
18439 };
18440 new_selections_by_buffer
18441 .entry(buffer_handle)
18442 .or_insert((Vec::new(), None))
18443 .0
18444 .push(range)
18445 }
18446 }
18447 }
18448 }
18449 }
18450
18451 new_selections_by_buffer
18452 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18453
18454 if new_selections_by_buffer.is_empty() {
18455 return;
18456 }
18457
18458 // We defer the pane interaction because we ourselves are a workspace item
18459 // and activating a new item causes the pane to call a method on us reentrantly,
18460 // which panics if we're on the stack.
18461 window.defer(cx, move |window, cx| {
18462 workspace.update(cx, |workspace, cx| {
18463 let pane = if split {
18464 workspace.adjacent_pane(window, cx)
18465 } else {
18466 workspace.active_pane().clone()
18467 };
18468
18469 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18470 let editor = buffer
18471 .read(cx)
18472 .file()
18473 .is_none()
18474 .then(|| {
18475 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18476 // so `workspace.open_project_item` will never find them, always opening a new editor.
18477 // Instead, we try to activate the existing editor in the pane first.
18478 let (editor, pane_item_index) =
18479 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18480 let editor = item.downcast::<Editor>()?;
18481 let singleton_buffer =
18482 editor.read(cx).buffer().read(cx).as_singleton()?;
18483 if singleton_buffer == buffer {
18484 Some((editor, i))
18485 } else {
18486 None
18487 }
18488 })?;
18489 pane.update(cx, |pane, cx| {
18490 pane.activate_item(pane_item_index, true, true, window, cx)
18491 });
18492 Some(editor)
18493 })
18494 .flatten()
18495 .unwrap_or_else(|| {
18496 workspace.open_project_item::<Self>(
18497 pane.clone(),
18498 buffer,
18499 true,
18500 true,
18501 window,
18502 cx,
18503 )
18504 });
18505
18506 editor.update(cx, |editor, cx| {
18507 let autoscroll = match scroll_offset {
18508 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18509 None => Autoscroll::newest(),
18510 };
18511 let nav_history = editor.nav_history.take();
18512 editor.change_selections(Some(autoscroll), window, cx, |s| {
18513 s.select_ranges(ranges);
18514 });
18515 editor.nav_history = nav_history;
18516 });
18517 }
18518 })
18519 });
18520 }
18521
18522 // For now, don't allow opening excerpts in buffers that aren't backed by
18523 // regular project files.
18524 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18525 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18526 }
18527
18528 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18529 let snapshot = self.buffer.read(cx).read(cx);
18530 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18531 Some(
18532 ranges
18533 .iter()
18534 .map(move |range| {
18535 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18536 })
18537 .collect(),
18538 )
18539 }
18540
18541 fn selection_replacement_ranges(
18542 &self,
18543 range: Range<OffsetUtf16>,
18544 cx: &mut App,
18545 ) -> Vec<Range<OffsetUtf16>> {
18546 let selections = self.selections.all::<OffsetUtf16>(cx);
18547 let newest_selection = selections
18548 .iter()
18549 .max_by_key(|selection| selection.id)
18550 .unwrap();
18551 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
18552 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
18553 let snapshot = self.buffer.read(cx).read(cx);
18554 selections
18555 .into_iter()
18556 .map(|mut selection| {
18557 selection.start.0 =
18558 (selection.start.0 as isize).saturating_add(start_delta) as usize;
18559 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
18560 snapshot.clip_offset_utf16(selection.start, Bias::Left)
18561 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
18562 })
18563 .collect()
18564 }
18565
18566 fn report_editor_event(
18567 &self,
18568 event_type: &'static str,
18569 file_extension: Option<String>,
18570 cx: &App,
18571 ) {
18572 if cfg!(any(test, feature = "test-support")) {
18573 return;
18574 }
18575
18576 let Some(project) = &self.project else { return };
18577
18578 // If None, we are in a file without an extension
18579 let file = self
18580 .buffer
18581 .read(cx)
18582 .as_singleton()
18583 .and_then(|b| b.read(cx).file());
18584 let file_extension = file_extension.or(file
18585 .as_ref()
18586 .and_then(|file| Path::new(file.file_name(cx)).extension())
18587 .and_then(|e| e.to_str())
18588 .map(|a| a.to_string()));
18589
18590 let vim_mode = vim_enabled(cx);
18591
18592 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
18593 let copilot_enabled = edit_predictions_provider
18594 == language::language_settings::EditPredictionProvider::Copilot;
18595 let copilot_enabled_for_language = self
18596 .buffer
18597 .read(cx)
18598 .language_settings(cx)
18599 .show_edit_predictions;
18600
18601 let project = project.read(cx);
18602 telemetry::event!(
18603 event_type,
18604 file_extension,
18605 vim_mode,
18606 copilot_enabled,
18607 copilot_enabled_for_language,
18608 edit_predictions_provider,
18609 is_via_ssh = project.is_via_ssh(),
18610 );
18611 }
18612
18613 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
18614 /// with each line being an array of {text, highlight} objects.
18615 fn copy_highlight_json(
18616 &mut self,
18617 _: &CopyHighlightJson,
18618 window: &mut Window,
18619 cx: &mut Context<Self>,
18620 ) {
18621 #[derive(Serialize)]
18622 struct Chunk<'a> {
18623 text: String,
18624 highlight: Option<&'a str>,
18625 }
18626
18627 let snapshot = self.buffer.read(cx).snapshot(cx);
18628 let range = self
18629 .selected_text_range(false, window, cx)
18630 .and_then(|selection| {
18631 if selection.range.is_empty() {
18632 None
18633 } else {
18634 Some(selection.range)
18635 }
18636 })
18637 .unwrap_or_else(|| 0..snapshot.len());
18638
18639 let chunks = snapshot.chunks(range, true);
18640 let mut lines = Vec::new();
18641 let mut line: VecDeque<Chunk> = VecDeque::new();
18642
18643 let Some(style) = self.style.as_ref() else {
18644 return;
18645 };
18646
18647 for chunk in chunks {
18648 let highlight = chunk
18649 .syntax_highlight_id
18650 .and_then(|id| id.name(&style.syntax));
18651 let mut chunk_lines = chunk.text.split('\n').peekable();
18652 while let Some(text) = chunk_lines.next() {
18653 let mut merged_with_last_token = false;
18654 if let Some(last_token) = line.back_mut() {
18655 if last_token.highlight == highlight {
18656 last_token.text.push_str(text);
18657 merged_with_last_token = true;
18658 }
18659 }
18660
18661 if !merged_with_last_token {
18662 line.push_back(Chunk {
18663 text: text.into(),
18664 highlight,
18665 });
18666 }
18667
18668 if chunk_lines.peek().is_some() {
18669 if line.len() > 1 && line.front().unwrap().text.is_empty() {
18670 line.pop_front();
18671 }
18672 if line.len() > 1 && line.back().unwrap().text.is_empty() {
18673 line.pop_back();
18674 }
18675
18676 lines.push(mem::take(&mut line));
18677 }
18678 }
18679 }
18680
18681 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
18682 return;
18683 };
18684 cx.write_to_clipboard(ClipboardItem::new_string(lines));
18685 }
18686
18687 pub fn open_context_menu(
18688 &mut self,
18689 _: &OpenContextMenu,
18690 window: &mut Window,
18691 cx: &mut Context<Self>,
18692 ) {
18693 self.request_autoscroll(Autoscroll::newest(), cx);
18694 let position = self.selections.newest_display(cx).start;
18695 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
18696 }
18697
18698 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
18699 &self.inlay_hint_cache
18700 }
18701
18702 pub fn replay_insert_event(
18703 &mut self,
18704 text: &str,
18705 relative_utf16_range: Option<Range<isize>>,
18706 window: &mut Window,
18707 cx: &mut Context<Self>,
18708 ) {
18709 if !self.input_enabled {
18710 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18711 return;
18712 }
18713 if let Some(relative_utf16_range) = relative_utf16_range {
18714 let selections = self.selections.all::<OffsetUtf16>(cx);
18715 self.change_selections(None, window, cx, |s| {
18716 let new_ranges = selections.into_iter().map(|range| {
18717 let start = OffsetUtf16(
18718 range
18719 .head()
18720 .0
18721 .saturating_add_signed(relative_utf16_range.start),
18722 );
18723 let end = OffsetUtf16(
18724 range
18725 .head()
18726 .0
18727 .saturating_add_signed(relative_utf16_range.end),
18728 );
18729 start..end
18730 });
18731 s.select_ranges(new_ranges);
18732 });
18733 }
18734
18735 self.handle_input(text, window, cx);
18736 }
18737
18738 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
18739 let Some(provider) = self.semantics_provider.as_ref() else {
18740 return false;
18741 };
18742
18743 let mut supports = false;
18744 self.buffer().update(cx, |this, cx| {
18745 this.for_each_buffer(|buffer| {
18746 supports |= provider.supports_inlay_hints(buffer, cx);
18747 });
18748 });
18749
18750 supports
18751 }
18752
18753 pub fn is_focused(&self, window: &Window) -> bool {
18754 self.focus_handle.is_focused(window)
18755 }
18756
18757 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18758 cx.emit(EditorEvent::Focused);
18759
18760 if let Some(descendant) = self
18761 .last_focused_descendant
18762 .take()
18763 .and_then(|descendant| descendant.upgrade())
18764 {
18765 window.focus(&descendant);
18766 } else {
18767 if let Some(blame) = self.blame.as_ref() {
18768 blame.update(cx, GitBlame::focus)
18769 }
18770
18771 self.blink_manager.update(cx, BlinkManager::enable);
18772 self.show_cursor_names(window, cx);
18773 self.buffer.update(cx, |buffer, cx| {
18774 buffer.finalize_last_transaction(cx);
18775 if self.leader_id.is_none() {
18776 buffer.set_active_selections(
18777 &self.selections.disjoint_anchors(),
18778 self.selections.line_mode,
18779 self.cursor_shape,
18780 cx,
18781 );
18782 }
18783 });
18784 }
18785 }
18786
18787 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18788 cx.emit(EditorEvent::FocusedIn)
18789 }
18790
18791 fn handle_focus_out(
18792 &mut self,
18793 event: FocusOutEvent,
18794 _window: &mut Window,
18795 cx: &mut Context<Self>,
18796 ) {
18797 if event.blurred != self.focus_handle {
18798 self.last_focused_descendant = Some(event.blurred);
18799 }
18800 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
18801 }
18802
18803 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18804 self.blink_manager.update(cx, BlinkManager::disable);
18805 self.buffer
18806 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
18807
18808 if let Some(blame) = self.blame.as_ref() {
18809 blame.update(cx, GitBlame::blur)
18810 }
18811 if !self.hover_state.focused(window, cx) {
18812 hide_hover(self, cx);
18813 }
18814 if !self
18815 .context_menu
18816 .borrow()
18817 .as_ref()
18818 .is_some_and(|context_menu| context_menu.focused(window, cx))
18819 {
18820 self.hide_context_menu(window, cx);
18821 }
18822 self.discard_inline_completion(false, cx);
18823 cx.emit(EditorEvent::Blurred);
18824 cx.notify();
18825 }
18826
18827 pub fn register_action<A: Action>(
18828 &mut self,
18829 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
18830 ) -> Subscription {
18831 let id = self.next_editor_action_id.post_inc();
18832 let listener = Arc::new(listener);
18833 self.editor_actions.borrow_mut().insert(
18834 id,
18835 Box::new(move |window, _| {
18836 let listener = listener.clone();
18837 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
18838 let action = action.downcast_ref().unwrap();
18839 if phase == DispatchPhase::Bubble {
18840 listener(action, window, cx)
18841 }
18842 })
18843 }),
18844 );
18845
18846 let editor_actions = self.editor_actions.clone();
18847 Subscription::new(move || {
18848 editor_actions.borrow_mut().remove(&id);
18849 })
18850 }
18851
18852 pub fn file_header_size(&self) -> u32 {
18853 FILE_HEADER_HEIGHT
18854 }
18855
18856 pub fn restore(
18857 &mut self,
18858 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
18859 window: &mut Window,
18860 cx: &mut Context<Self>,
18861 ) {
18862 let workspace = self.workspace();
18863 let project = self.project.as_ref();
18864 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
18865 let mut tasks = Vec::new();
18866 for (buffer_id, changes) in revert_changes {
18867 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
18868 buffer.update(cx, |buffer, cx| {
18869 buffer.edit(
18870 changes
18871 .into_iter()
18872 .map(|(range, text)| (range, text.to_string())),
18873 None,
18874 cx,
18875 );
18876 });
18877
18878 if let Some(project) =
18879 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
18880 {
18881 project.update(cx, |project, cx| {
18882 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
18883 })
18884 }
18885 }
18886 }
18887 tasks
18888 });
18889 cx.spawn_in(window, async move |_, cx| {
18890 for (buffer, task) in save_tasks {
18891 let result = task.await;
18892 if result.is_err() {
18893 let Some(path) = buffer
18894 .read_with(cx, |buffer, cx| buffer.project_path(cx))
18895 .ok()
18896 else {
18897 continue;
18898 };
18899 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
18900 let Some(task) = cx
18901 .update_window_entity(&workspace, |workspace, window, cx| {
18902 workspace
18903 .open_path_preview(path, None, false, false, false, window, cx)
18904 })
18905 .ok()
18906 else {
18907 continue;
18908 };
18909 task.await.log_err();
18910 }
18911 }
18912 }
18913 })
18914 .detach();
18915 self.change_selections(None, window, cx, |selections| selections.refresh());
18916 }
18917
18918 pub fn to_pixel_point(
18919 &self,
18920 source: multi_buffer::Anchor,
18921 editor_snapshot: &EditorSnapshot,
18922 window: &mut Window,
18923 ) -> Option<gpui::Point<Pixels>> {
18924 let source_point = source.to_display_point(editor_snapshot);
18925 self.display_to_pixel_point(source_point, editor_snapshot, window)
18926 }
18927
18928 pub fn display_to_pixel_point(
18929 &self,
18930 source: DisplayPoint,
18931 editor_snapshot: &EditorSnapshot,
18932 window: &mut Window,
18933 ) -> Option<gpui::Point<Pixels>> {
18934 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
18935 let text_layout_details = self.text_layout_details(window);
18936 let scroll_top = text_layout_details
18937 .scroll_anchor
18938 .scroll_position(editor_snapshot)
18939 .y;
18940
18941 if source.row().as_f32() < scroll_top.floor() {
18942 return None;
18943 }
18944 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
18945 let source_y = line_height * (source.row().as_f32() - scroll_top);
18946 Some(gpui::Point::new(source_x, source_y))
18947 }
18948
18949 pub fn has_visible_completions_menu(&self) -> bool {
18950 !self.edit_prediction_preview_is_active()
18951 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
18952 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
18953 })
18954 }
18955
18956 pub fn register_addon<T: Addon>(&mut self, instance: T) {
18957 if self.mode.is_minimap() {
18958 return;
18959 }
18960 self.addons
18961 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
18962 }
18963
18964 pub fn unregister_addon<T: Addon>(&mut self) {
18965 self.addons.remove(&std::any::TypeId::of::<T>());
18966 }
18967
18968 pub fn addon<T: Addon>(&self) -> Option<&T> {
18969 let type_id = std::any::TypeId::of::<T>();
18970 self.addons
18971 .get(&type_id)
18972 .and_then(|item| item.to_any().downcast_ref::<T>())
18973 }
18974
18975 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
18976 let type_id = std::any::TypeId::of::<T>();
18977 self.addons
18978 .get_mut(&type_id)
18979 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
18980 }
18981
18982 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
18983 let text_layout_details = self.text_layout_details(window);
18984 let style = &text_layout_details.editor_style;
18985 let font_id = window.text_system().resolve_font(&style.text.font());
18986 let font_size = style.text.font_size.to_pixels(window.rem_size());
18987 let line_height = style.text.line_height_in_pixels(window.rem_size());
18988 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
18989
18990 gpui::Size::new(em_width, line_height)
18991 }
18992
18993 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
18994 self.load_diff_task.clone()
18995 }
18996
18997 fn read_metadata_from_db(
18998 &mut self,
18999 item_id: u64,
19000 workspace_id: WorkspaceId,
19001 window: &mut Window,
19002 cx: &mut Context<Editor>,
19003 ) {
19004 if self.is_singleton(cx)
19005 && !self.mode.is_minimap()
19006 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
19007 {
19008 let buffer_snapshot = OnceCell::new();
19009
19010 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
19011 if !folds.is_empty() {
19012 let snapshot =
19013 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19014 self.fold_ranges(
19015 folds
19016 .into_iter()
19017 .map(|(start, end)| {
19018 snapshot.clip_offset(start, Bias::Left)
19019 ..snapshot.clip_offset(end, Bias::Right)
19020 })
19021 .collect(),
19022 false,
19023 window,
19024 cx,
19025 );
19026 }
19027 }
19028
19029 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
19030 if !selections.is_empty() {
19031 let snapshot =
19032 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19033 self.change_selections(None, window, cx, |s| {
19034 s.select_ranges(selections.into_iter().map(|(start, end)| {
19035 snapshot.clip_offset(start, Bias::Left)
19036 ..snapshot.clip_offset(end, Bias::Right)
19037 }));
19038 });
19039 }
19040 };
19041 }
19042
19043 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
19044 }
19045}
19046
19047fn vim_enabled(cx: &App) -> bool {
19048 cx.global::<SettingsStore>()
19049 .raw_user_settings()
19050 .get("vim_mode")
19051 == Some(&serde_json::Value::Bool(true))
19052}
19053
19054// Consider user intent and default settings
19055fn choose_completion_range(
19056 completion: &Completion,
19057 intent: CompletionIntent,
19058 buffer: &Entity<Buffer>,
19059 cx: &mut Context<Editor>,
19060) -> Range<usize> {
19061 fn should_replace(
19062 completion: &Completion,
19063 insert_range: &Range<text::Anchor>,
19064 intent: CompletionIntent,
19065 completion_mode_setting: LspInsertMode,
19066 buffer: &Buffer,
19067 ) -> bool {
19068 // specific actions take precedence over settings
19069 match intent {
19070 CompletionIntent::CompleteWithInsert => return false,
19071 CompletionIntent::CompleteWithReplace => return true,
19072 CompletionIntent::Complete | CompletionIntent::Compose => {}
19073 }
19074
19075 match completion_mode_setting {
19076 LspInsertMode::Insert => false,
19077 LspInsertMode::Replace => true,
19078 LspInsertMode::ReplaceSubsequence => {
19079 let mut text_to_replace = buffer.chars_for_range(
19080 buffer.anchor_before(completion.replace_range.start)
19081 ..buffer.anchor_after(completion.replace_range.end),
19082 );
19083 let mut completion_text = completion.new_text.chars();
19084
19085 // is `text_to_replace` a subsequence of `completion_text`
19086 text_to_replace
19087 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
19088 }
19089 LspInsertMode::ReplaceSuffix => {
19090 let range_after_cursor = insert_range.end..completion.replace_range.end;
19091
19092 let text_after_cursor = buffer
19093 .text_for_range(
19094 buffer.anchor_before(range_after_cursor.start)
19095 ..buffer.anchor_after(range_after_cursor.end),
19096 )
19097 .collect::<String>();
19098 completion.new_text.ends_with(&text_after_cursor)
19099 }
19100 }
19101 }
19102
19103 let buffer = buffer.read(cx);
19104
19105 if let CompletionSource::Lsp {
19106 insert_range: Some(insert_range),
19107 ..
19108 } = &completion.source
19109 {
19110 let completion_mode_setting =
19111 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
19112 .completions
19113 .lsp_insert_mode;
19114
19115 if !should_replace(
19116 completion,
19117 &insert_range,
19118 intent,
19119 completion_mode_setting,
19120 buffer,
19121 ) {
19122 return insert_range.to_offset(buffer);
19123 }
19124 }
19125
19126 completion.replace_range.to_offset(buffer)
19127}
19128
19129fn insert_extra_newline_brackets(
19130 buffer: &MultiBufferSnapshot,
19131 range: Range<usize>,
19132 language: &language::LanguageScope,
19133) -> bool {
19134 let leading_whitespace_len = buffer
19135 .reversed_chars_at(range.start)
19136 .take_while(|c| c.is_whitespace() && *c != '\n')
19137 .map(|c| c.len_utf8())
19138 .sum::<usize>();
19139 let trailing_whitespace_len = buffer
19140 .chars_at(range.end)
19141 .take_while(|c| c.is_whitespace() && *c != '\n')
19142 .map(|c| c.len_utf8())
19143 .sum::<usize>();
19144 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
19145
19146 language.brackets().any(|(pair, enabled)| {
19147 let pair_start = pair.start.trim_end();
19148 let pair_end = pair.end.trim_start();
19149
19150 enabled
19151 && pair.newline
19152 && buffer.contains_str_at(range.end, pair_end)
19153 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
19154 })
19155}
19156
19157fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
19158 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
19159 [(buffer, range, _)] => (*buffer, range.clone()),
19160 _ => return false,
19161 };
19162 let pair = {
19163 let mut result: Option<BracketMatch> = None;
19164
19165 for pair in buffer
19166 .all_bracket_ranges(range.clone())
19167 .filter(move |pair| {
19168 pair.open_range.start <= range.start && pair.close_range.end >= range.end
19169 })
19170 {
19171 let len = pair.close_range.end - pair.open_range.start;
19172
19173 if let Some(existing) = &result {
19174 let existing_len = existing.close_range.end - existing.open_range.start;
19175 if len > existing_len {
19176 continue;
19177 }
19178 }
19179
19180 result = Some(pair);
19181 }
19182
19183 result
19184 };
19185 let Some(pair) = pair else {
19186 return false;
19187 };
19188 pair.newline_only
19189 && buffer
19190 .chars_for_range(pair.open_range.end..range.start)
19191 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
19192 .all(|c| c.is_whitespace() && c != '\n')
19193}
19194
19195fn update_uncommitted_diff_for_buffer(
19196 editor: Entity<Editor>,
19197 project: &Entity<Project>,
19198 buffers: impl IntoIterator<Item = Entity<Buffer>>,
19199 buffer: Entity<MultiBuffer>,
19200 cx: &mut App,
19201) -> Task<()> {
19202 let mut tasks = Vec::new();
19203 project.update(cx, |project, cx| {
19204 for buffer in buffers {
19205 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
19206 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
19207 }
19208 }
19209 });
19210 cx.spawn(async move |cx| {
19211 let diffs = future::join_all(tasks).await;
19212 if editor
19213 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19214 .unwrap_or(false)
19215 {
19216 return;
19217 }
19218
19219 buffer
19220 .update(cx, |buffer, cx| {
19221 for diff in diffs.into_iter().flatten() {
19222 buffer.add_diff(diff, cx);
19223 }
19224 })
19225 .ok();
19226 })
19227}
19228
19229fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
19230 let tab_size = tab_size.get() as usize;
19231 let mut width = offset;
19232
19233 for ch in text.chars() {
19234 width += if ch == '\t' {
19235 tab_size - (width % tab_size)
19236 } else {
19237 1
19238 };
19239 }
19240
19241 width - offset
19242}
19243
19244#[cfg(test)]
19245mod tests {
19246 use super::*;
19247
19248 #[test]
19249 fn test_string_size_with_expanded_tabs() {
19250 let nz = |val| NonZeroU32::new(val).unwrap();
19251 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
19252 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
19253 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
19254 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
19255 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
19256 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
19257 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
19258 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
19259 }
19260}
19261
19262/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
19263struct WordBreakingTokenizer<'a> {
19264 input: &'a str,
19265}
19266
19267impl<'a> WordBreakingTokenizer<'a> {
19268 fn new(input: &'a str) -> Self {
19269 Self { input }
19270 }
19271}
19272
19273fn is_char_ideographic(ch: char) -> bool {
19274 use unicode_script::Script::*;
19275 use unicode_script::UnicodeScript;
19276 matches!(ch.script(), Han | Tangut | Yi)
19277}
19278
19279fn is_grapheme_ideographic(text: &str) -> bool {
19280 text.chars().any(is_char_ideographic)
19281}
19282
19283fn is_grapheme_whitespace(text: &str) -> bool {
19284 text.chars().any(|x| x.is_whitespace())
19285}
19286
19287fn should_stay_with_preceding_ideograph(text: &str) -> bool {
19288 text.chars().next().map_or(false, |ch| {
19289 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
19290 })
19291}
19292
19293#[derive(PartialEq, Eq, Debug, Clone, Copy)]
19294enum WordBreakToken<'a> {
19295 Word { token: &'a str, grapheme_len: usize },
19296 InlineWhitespace { token: &'a str, grapheme_len: usize },
19297 Newline,
19298}
19299
19300impl<'a> Iterator for WordBreakingTokenizer<'a> {
19301 /// Yields a span, the count of graphemes in the token, and whether it was
19302 /// whitespace. Note that it also breaks at word boundaries.
19303 type Item = WordBreakToken<'a>;
19304
19305 fn next(&mut self) -> Option<Self::Item> {
19306 use unicode_segmentation::UnicodeSegmentation;
19307 if self.input.is_empty() {
19308 return None;
19309 }
19310
19311 let mut iter = self.input.graphemes(true).peekable();
19312 let mut offset = 0;
19313 let mut grapheme_len = 0;
19314 if let Some(first_grapheme) = iter.next() {
19315 let is_newline = first_grapheme == "\n";
19316 let is_whitespace = is_grapheme_whitespace(first_grapheme);
19317 offset += first_grapheme.len();
19318 grapheme_len += 1;
19319 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
19320 if let Some(grapheme) = iter.peek().copied() {
19321 if should_stay_with_preceding_ideograph(grapheme) {
19322 offset += grapheme.len();
19323 grapheme_len += 1;
19324 }
19325 }
19326 } else {
19327 let mut words = self.input[offset..].split_word_bound_indices().peekable();
19328 let mut next_word_bound = words.peek().copied();
19329 if next_word_bound.map_or(false, |(i, _)| i == 0) {
19330 next_word_bound = words.next();
19331 }
19332 while let Some(grapheme) = iter.peek().copied() {
19333 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19334 break;
19335 };
19336 if is_grapheme_whitespace(grapheme) != is_whitespace
19337 || (grapheme == "\n") != is_newline
19338 {
19339 break;
19340 };
19341 offset += grapheme.len();
19342 grapheme_len += 1;
19343 iter.next();
19344 }
19345 }
19346 let token = &self.input[..offset];
19347 self.input = &self.input[offset..];
19348 if token == "\n" {
19349 Some(WordBreakToken::Newline)
19350 } else if is_whitespace {
19351 Some(WordBreakToken::InlineWhitespace {
19352 token,
19353 grapheme_len,
19354 })
19355 } else {
19356 Some(WordBreakToken::Word {
19357 token,
19358 grapheme_len,
19359 })
19360 }
19361 } else {
19362 None
19363 }
19364 }
19365}
19366
19367#[test]
19368fn test_word_breaking_tokenizer() {
19369 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
19370 ("", &[]),
19371 (" ", &[whitespace(" ", 2)]),
19372 ("Ʒ", &[word("Ʒ", 1)]),
19373 ("Ǽ", &[word("Ǽ", 1)]),
19374 ("⋑", &[word("⋑", 1)]),
19375 ("⋑⋑", &[word("⋑⋑", 2)]),
19376 (
19377 "原理,进而",
19378 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
19379 ),
19380 (
19381 "hello world",
19382 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
19383 ),
19384 (
19385 "hello, world",
19386 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
19387 ),
19388 (
19389 " hello world",
19390 &[
19391 whitespace(" ", 2),
19392 word("hello", 5),
19393 whitespace(" ", 1),
19394 word("world", 5),
19395 ],
19396 ),
19397 (
19398 "这是什么 \n 钢笔",
19399 &[
19400 word("这", 1),
19401 word("是", 1),
19402 word("什", 1),
19403 word("么", 1),
19404 whitespace(" ", 1),
19405 newline(),
19406 whitespace(" ", 1),
19407 word("钢", 1),
19408 word("笔", 1),
19409 ],
19410 ),
19411 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
19412 ];
19413
19414 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19415 WordBreakToken::Word {
19416 token,
19417 grapheme_len,
19418 }
19419 }
19420
19421 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19422 WordBreakToken::InlineWhitespace {
19423 token,
19424 grapheme_len,
19425 }
19426 }
19427
19428 fn newline() -> WordBreakToken<'static> {
19429 WordBreakToken::Newline
19430 }
19431
19432 for (input, result) in tests {
19433 assert_eq!(
19434 WordBreakingTokenizer::new(input)
19435 .collect::<Vec<_>>()
19436 .as_slice(),
19437 *result,
19438 );
19439 }
19440}
19441
19442fn wrap_with_prefix(
19443 line_prefix: String,
19444 unwrapped_text: String,
19445 wrap_column: usize,
19446 tab_size: NonZeroU32,
19447 preserve_existing_whitespace: bool,
19448) -> String {
19449 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
19450 let mut wrapped_text = String::new();
19451 let mut current_line = line_prefix.clone();
19452
19453 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
19454 let mut current_line_len = line_prefix_len;
19455 let mut in_whitespace = false;
19456 for token in tokenizer {
19457 let have_preceding_whitespace = in_whitespace;
19458 match token {
19459 WordBreakToken::Word {
19460 token,
19461 grapheme_len,
19462 } => {
19463 in_whitespace = false;
19464 if current_line_len + grapheme_len > wrap_column
19465 && current_line_len != line_prefix_len
19466 {
19467 wrapped_text.push_str(current_line.trim_end());
19468 wrapped_text.push('\n');
19469 current_line.truncate(line_prefix.len());
19470 current_line_len = line_prefix_len;
19471 }
19472 current_line.push_str(token);
19473 current_line_len += grapheme_len;
19474 }
19475 WordBreakToken::InlineWhitespace {
19476 mut token,
19477 mut grapheme_len,
19478 } => {
19479 in_whitespace = true;
19480 if have_preceding_whitespace && !preserve_existing_whitespace {
19481 continue;
19482 }
19483 if !preserve_existing_whitespace {
19484 token = " ";
19485 grapheme_len = 1;
19486 }
19487 if current_line_len + grapheme_len > wrap_column {
19488 wrapped_text.push_str(current_line.trim_end());
19489 wrapped_text.push('\n');
19490 current_line.truncate(line_prefix.len());
19491 current_line_len = line_prefix_len;
19492 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
19493 current_line.push_str(token);
19494 current_line_len += grapheme_len;
19495 }
19496 }
19497 WordBreakToken::Newline => {
19498 in_whitespace = true;
19499 if preserve_existing_whitespace {
19500 wrapped_text.push_str(current_line.trim_end());
19501 wrapped_text.push('\n');
19502 current_line.truncate(line_prefix.len());
19503 current_line_len = line_prefix_len;
19504 } else if have_preceding_whitespace {
19505 continue;
19506 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
19507 {
19508 wrapped_text.push_str(current_line.trim_end());
19509 wrapped_text.push('\n');
19510 current_line.truncate(line_prefix.len());
19511 current_line_len = line_prefix_len;
19512 } else if current_line_len != line_prefix_len {
19513 current_line.push(' ');
19514 current_line_len += 1;
19515 }
19516 }
19517 }
19518 }
19519
19520 if !current_line.is_empty() {
19521 wrapped_text.push_str(¤t_line);
19522 }
19523 wrapped_text
19524}
19525
19526#[test]
19527fn test_wrap_with_prefix() {
19528 assert_eq!(
19529 wrap_with_prefix(
19530 "# ".to_string(),
19531 "abcdefg".to_string(),
19532 4,
19533 NonZeroU32::new(4).unwrap(),
19534 false,
19535 ),
19536 "# abcdefg"
19537 );
19538 assert_eq!(
19539 wrap_with_prefix(
19540 "".to_string(),
19541 "\thello world".to_string(),
19542 8,
19543 NonZeroU32::new(4).unwrap(),
19544 false,
19545 ),
19546 "hello\nworld"
19547 );
19548 assert_eq!(
19549 wrap_with_prefix(
19550 "// ".to_string(),
19551 "xx \nyy zz aa bb cc".to_string(),
19552 12,
19553 NonZeroU32::new(4).unwrap(),
19554 false,
19555 ),
19556 "// xx yy zz\n// aa bb cc"
19557 );
19558 assert_eq!(
19559 wrap_with_prefix(
19560 String::new(),
19561 "这是什么 \n 钢笔".to_string(),
19562 3,
19563 NonZeroU32::new(4).unwrap(),
19564 false,
19565 ),
19566 "这是什\n么 钢\n笔"
19567 );
19568}
19569
19570pub trait CollaborationHub {
19571 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
19572 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
19573 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
19574}
19575
19576impl CollaborationHub for Entity<Project> {
19577 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
19578 self.read(cx).collaborators()
19579 }
19580
19581 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
19582 self.read(cx).user_store().read(cx).participant_indices()
19583 }
19584
19585 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
19586 let this = self.read(cx);
19587 let user_ids = this.collaborators().values().map(|c| c.user_id);
19588 this.user_store().read_with(cx, |user_store, cx| {
19589 user_store.participant_names(user_ids, cx)
19590 })
19591 }
19592}
19593
19594pub trait SemanticsProvider {
19595 fn hover(
19596 &self,
19597 buffer: &Entity<Buffer>,
19598 position: text::Anchor,
19599 cx: &mut App,
19600 ) -> Option<Task<Vec<project::Hover>>>;
19601
19602 fn inline_values(
19603 &self,
19604 buffer_handle: Entity<Buffer>,
19605 range: Range<text::Anchor>,
19606 cx: &mut App,
19607 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19608
19609 fn inlay_hints(
19610 &self,
19611 buffer_handle: Entity<Buffer>,
19612 range: Range<text::Anchor>,
19613 cx: &mut App,
19614 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19615
19616 fn resolve_inlay_hint(
19617 &self,
19618 hint: InlayHint,
19619 buffer_handle: Entity<Buffer>,
19620 server_id: LanguageServerId,
19621 cx: &mut App,
19622 ) -> Option<Task<anyhow::Result<InlayHint>>>;
19623
19624 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
19625
19626 fn document_highlights(
19627 &self,
19628 buffer: &Entity<Buffer>,
19629 position: text::Anchor,
19630 cx: &mut App,
19631 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
19632
19633 fn definitions(
19634 &self,
19635 buffer: &Entity<Buffer>,
19636 position: text::Anchor,
19637 kind: GotoDefinitionKind,
19638 cx: &mut App,
19639 ) -> Option<Task<Result<Vec<LocationLink>>>>;
19640
19641 fn range_for_rename(
19642 &self,
19643 buffer: &Entity<Buffer>,
19644 position: text::Anchor,
19645 cx: &mut App,
19646 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
19647
19648 fn perform_rename(
19649 &self,
19650 buffer: &Entity<Buffer>,
19651 position: text::Anchor,
19652 new_name: String,
19653 cx: &mut App,
19654 ) -> Option<Task<Result<ProjectTransaction>>>;
19655}
19656
19657pub trait CompletionProvider {
19658 fn completions(
19659 &self,
19660 excerpt_id: ExcerptId,
19661 buffer: &Entity<Buffer>,
19662 buffer_position: text::Anchor,
19663 trigger: CompletionContext,
19664 window: &mut Window,
19665 cx: &mut Context<Editor>,
19666 ) -> Task<Result<Option<Vec<Completion>>>>;
19667
19668 fn resolve_completions(
19669 &self,
19670 buffer: Entity<Buffer>,
19671 completion_indices: Vec<usize>,
19672 completions: Rc<RefCell<Box<[Completion]>>>,
19673 cx: &mut Context<Editor>,
19674 ) -> Task<Result<bool>>;
19675
19676 fn apply_additional_edits_for_completion(
19677 &self,
19678 _buffer: Entity<Buffer>,
19679 _completions: Rc<RefCell<Box<[Completion]>>>,
19680 _completion_index: usize,
19681 _push_to_history: bool,
19682 _cx: &mut Context<Editor>,
19683 ) -> Task<Result<Option<language::Transaction>>> {
19684 Task::ready(Ok(None))
19685 }
19686
19687 fn is_completion_trigger(
19688 &self,
19689 buffer: &Entity<Buffer>,
19690 position: language::Anchor,
19691 text: &str,
19692 trigger_in_words: bool,
19693 cx: &mut Context<Editor>,
19694 ) -> bool;
19695
19696 fn sort_completions(&self) -> bool {
19697 true
19698 }
19699
19700 fn filter_completions(&self) -> bool {
19701 true
19702 }
19703}
19704
19705pub trait CodeActionProvider {
19706 fn id(&self) -> Arc<str>;
19707
19708 fn code_actions(
19709 &self,
19710 buffer: &Entity<Buffer>,
19711 range: Range<text::Anchor>,
19712 window: &mut Window,
19713 cx: &mut App,
19714 ) -> Task<Result<Vec<CodeAction>>>;
19715
19716 fn apply_code_action(
19717 &self,
19718 buffer_handle: Entity<Buffer>,
19719 action: CodeAction,
19720 excerpt_id: ExcerptId,
19721 push_to_history: bool,
19722 window: &mut Window,
19723 cx: &mut App,
19724 ) -> Task<Result<ProjectTransaction>>;
19725}
19726
19727impl CodeActionProvider for Entity<Project> {
19728 fn id(&self) -> Arc<str> {
19729 "project".into()
19730 }
19731
19732 fn code_actions(
19733 &self,
19734 buffer: &Entity<Buffer>,
19735 range: Range<text::Anchor>,
19736 _window: &mut Window,
19737 cx: &mut App,
19738 ) -> Task<Result<Vec<CodeAction>>> {
19739 self.update(cx, |project, cx| {
19740 let code_lens = project.code_lens(buffer, range.clone(), cx);
19741 let code_actions = project.code_actions(buffer, range, None, cx);
19742 cx.background_spawn(async move {
19743 let (code_lens, code_actions) = join(code_lens, code_actions).await;
19744 Ok(code_lens
19745 .context("code lens fetch")?
19746 .into_iter()
19747 .chain(code_actions.context("code action fetch")?)
19748 .collect())
19749 })
19750 })
19751 }
19752
19753 fn apply_code_action(
19754 &self,
19755 buffer_handle: Entity<Buffer>,
19756 action: CodeAction,
19757 _excerpt_id: ExcerptId,
19758 push_to_history: bool,
19759 _window: &mut Window,
19760 cx: &mut App,
19761 ) -> Task<Result<ProjectTransaction>> {
19762 self.update(cx, |project, cx| {
19763 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19764 })
19765 }
19766}
19767
19768fn snippet_completions(
19769 project: &Project,
19770 buffer: &Entity<Buffer>,
19771 buffer_position: text::Anchor,
19772 cx: &mut App,
19773) -> Task<Result<Vec<Completion>>> {
19774 let languages = buffer.read(cx).languages_at(buffer_position);
19775 let snippet_store = project.snippets().read(cx);
19776
19777 let scopes: Vec<_> = languages
19778 .iter()
19779 .filter_map(|language| {
19780 let language_name = language.lsp_id();
19781 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19782
19783 if snippets.is_empty() {
19784 None
19785 } else {
19786 Some((language.default_scope(), snippets))
19787 }
19788 })
19789 .collect();
19790
19791 if scopes.is_empty() {
19792 return Task::ready(Ok(vec![]));
19793 }
19794
19795 let snapshot = buffer.read(cx).text_snapshot();
19796 let chars: String = snapshot
19797 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19798 .collect();
19799 let executor = cx.background_executor().clone();
19800
19801 cx.background_spawn(async move {
19802 let mut all_results: Vec<Completion> = Vec::new();
19803 for (scope, snippets) in scopes.into_iter() {
19804 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19805 let mut last_word = chars
19806 .chars()
19807 .take_while(|c| classifier.is_word(*c))
19808 .collect::<String>();
19809 last_word = last_word.chars().rev().collect();
19810
19811 if last_word.is_empty() {
19812 return Ok(vec![]);
19813 }
19814
19815 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
19816 let to_lsp = |point: &text::Anchor| {
19817 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
19818 point_to_lsp(end)
19819 };
19820 let lsp_end = to_lsp(&buffer_position);
19821
19822 let candidates = snippets
19823 .iter()
19824 .enumerate()
19825 .flat_map(|(ix, snippet)| {
19826 snippet
19827 .prefix
19828 .iter()
19829 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
19830 })
19831 .collect::<Vec<StringMatchCandidate>>();
19832
19833 let mut matches = fuzzy::match_strings(
19834 &candidates,
19835 &last_word,
19836 last_word.chars().any(|c| c.is_uppercase()),
19837 100,
19838 &Default::default(),
19839 executor.clone(),
19840 )
19841 .await;
19842
19843 // Remove all candidates where the query's start does not match the start of any word in the candidate
19844 if let Some(query_start) = last_word.chars().next() {
19845 matches.retain(|string_match| {
19846 split_words(&string_match.string).any(|word| {
19847 // Check that the first codepoint of the word as lowercase matches the first
19848 // codepoint of the query as lowercase
19849 word.chars()
19850 .flat_map(|codepoint| codepoint.to_lowercase())
19851 .zip(query_start.to_lowercase())
19852 .all(|(word_cp, query_cp)| word_cp == query_cp)
19853 })
19854 });
19855 }
19856
19857 let matched_strings = matches
19858 .into_iter()
19859 .map(|m| m.string)
19860 .collect::<HashSet<_>>();
19861
19862 let mut result: Vec<Completion> = snippets
19863 .iter()
19864 .filter_map(|snippet| {
19865 let matching_prefix = snippet
19866 .prefix
19867 .iter()
19868 .find(|prefix| matched_strings.contains(*prefix))?;
19869 let start = as_offset - last_word.len();
19870 let start = snapshot.anchor_before(start);
19871 let range = start..buffer_position;
19872 let lsp_start = to_lsp(&start);
19873 let lsp_range = lsp::Range {
19874 start: lsp_start,
19875 end: lsp_end,
19876 };
19877 Some(Completion {
19878 replace_range: range,
19879 new_text: snippet.body.clone(),
19880 source: CompletionSource::Lsp {
19881 insert_range: None,
19882 server_id: LanguageServerId(usize::MAX),
19883 resolved: true,
19884 lsp_completion: Box::new(lsp::CompletionItem {
19885 label: snippet.prefix.first().unwrap().clone(),
19886 kind: Some(CompletionItemKind::SNIPPET),
19887 label_details: snippet.description.as_ref().map(|description| {
19888 lsp::CompletionItemLabelDetails {
19889 detail: Some(description.clone()),
19890 description: None,
19891 }
19892 }),
19893 insert_text_format: Some(InsertTextFormat::SNIPPET),
19894 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19895 lsp::InsertReplaceEdit {
19896 new_text: snippet.body.clone(),
19897 insert: lsp_range,
19898 replace: lsp_range,
19899 },
19900 )),
19901 filter_text: Some(snippet.body.clone()),
19902 sort_text: Some(char::MAX.to_string()),
19903 ..lsp::CompletionItem::default()
19904 }),
19905 lsp_defaults: None,
19906 },
19907 label: CodeLabel {
19908 text: matching_prefix.clone(),
19909 runs: Vec::new(),
19910 filter_range: 0..matching_prefix.len(),
19911 },
19912 icon_path: None,
19913 documentation: Some(
19914 CompletionDocumentation::SingleLineAndMultiLinePlainText {
19915 single_line: snippet.name.clone().into(),
19916 plain_text: snippet
19917 .description
19918 .clone()
19919 .map(|description| description.into()),
19920 },
19921 ),
19922 insert_text_mode: None,
19923 confirm: None,
19924 })
19925 })
19926 .collect();
19927
19928 all_results.append(&mut result);
19929 }
19930
19931 Ok(all_results)
19932 })
19933}
19934
19935impl CompletionProvider for Entity<Project> {
19936 fn completions(
19937 &self,
19938 _excerpt_id: ExcerptId,
19939 buffer: &Entity<Buffer>,
19940 buffer_position: text::Anchor,
19941 options: CompletionContext,
19942 _window: &mut Window,
19943 cx: &mut Context<Editor>,
19944 ) -> Task<Result<Option<Vec<Completion>>>> {
19945 self.update(cx, |project, cx| {
19946 let snippets = snippet_completions(project, buffer, buffer_position, cx);
19947 let project_completions = project.completions(buffer, buffer_position, options, cx);
19948 cx.background_spawn(async move {
19949 let snippets_completions = snippets.await?;
19950 match project_completions.await? {
19951 Some(mut completions) => {
19952 completions.extend(snippets_completions);
19953 Ok(Some(completions))
19954 }
19955 None => {
19956 if snippets_completions.is_empty() {
19957 Ok(None)
19958 } else {
19959 Ok(Some(snippets_completions))
19960 }
19961 }
19962 }
19963 })
19964 })
19965 }
19966
19967 fn resolve_completions(
19968 &self,
19969 buffer: Entity<Buffer>,
19970 completion_indices: Vec<usize>,
19971 completions: Rc<RefCell<Box<[Completion]>>>,
19972 cx: &mut Context<Editor>,
19973 ) -> Task<Result<bool>> {
19974 self.update(cx, |project, cx| {
19975 project.lsp_store().update(cx, |lsp_store, cx| {
19976 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
19977 })
19978 })
19979 }
19980
19981 fn apply_additional_edits_for_completion(
19982 &self,
19983 buffer: Entity<Buffer>,
19984 completions: Rc<RefCell<Box<[Completion]>>>,
19985 completion_index: usize,
19986 push_to_history: bool,
19987 cx: &mut Context<Editor>,
19988 ) -> Task<Result<Option<language::Transaction>>> {
19989 self.update(cx, |project, cx| {
19990 project.lsp_store().update(cx, |lsp_store, cx| {
19991 lsp_store.apply_additional_edits_for_completion(
19992 buffer,
19993 completions,
19994 completion_index,
19995 push_to_history,
19996 cx,
19997 )
19998 })
19999 })
20000 }
20001
20002 fn is_completion_trigger(
20003 &self,
20004 buffer: &Entity<Buffer>,
20005 position: language::Anchor,
20006 text: &str,
20007 trigger_in_words: bool,
20008 cx: &mut Context<Editor>,
20009 ) -> bool {
20010 let mut chars = text.chars();
20011 let char = if let Some(char) = chars.next() {
20012 char
20013 } else {
20014 return false;
20015 };
20016 if chars.next().is_some() {
20017 return false;
20018 }
20019
20020 let buffer = buffer.read(cx);
20021 let snapshot = buffer.snapshot();
20022 if !snapshot.settings_at(position, cx).show_completions_on_input {
20023 return false;
20024 }
20025 let classifier = snapshot.char_classifier_at(position).for_completion(true);
20026 if trigger_in_words && classifier.is_word(char) {
20027 return true;
20028 }
20029
20030 buffer.completion_triggers().contains(text)
20031 }
20032}
20033
20034impl SemanticsProvider for Entity<Project> {
20035 fn hover(
20036 &self,
20037 buffer: &Entity<Buffer>,
20038 position: text::Anchor,
20039 cx: &mut App,
20040 ) -> Option<Task<Vec<project::Hover>>> {
20041 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
20042 }
20043
20044 fn document_highlights(
20045 &self,
20046 buffer: &Entity<Buffer>,
20047 position: text::Anchor,
20048 cx: &mut App,
20049 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
20050 Some(self.update(cx, |project, cx| {
20051 project.document_highlights(buffer, position, cx)
20052 }))
20053 }
20054
20055 fn definitions(
20056 &self,
20057 buffer: &Entity<Buffer>,
20058 position: text::Anchor,
20059 kind: GotoDefinitionKind,
20060 cx: &mut App,
20061 ) -> Option<Task<Result<Vec<LocationLink>>>> {
20062 Some(self.update(cx, |project, cx| match kind {
20063 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
20064 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
20065 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
20066 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
20067 }))
20068 }
20069
20070 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
20071 // TODO: make this work for remote projects
20072 self.update(cx, |project, cx| {
20073 if project
20074 .active_debug_session(cx)
20075 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
20076 {
20077 return true;
20078 }
20079
20080 buffer.update(cx, |buffer, cx| {
20081 project.any_language_server_supports_inlay_hints(buffer, cx)
20082 })
20083 })
20084 }
20085
20086 fn inline_values(
20087 &self,
20088 buffer_handle: Entity<Buffer>,
20089 range: Range<text::Anchor>,
20090 cx: &mut App,
20091 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20092 self.update(cx, |project, cx| {
20093 let (session, active_stack_frame) = project.active_debug_session(cx)?;
20094
20095 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
20096 })
20097 }
20098
20099 fn inlay_hints(
20100 &self,
20101 buffer_handle: Entity<Buffer>,
20102 range: Range<text::Anchor>,
20103 cx: &mut App,
20104 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20105 Some(self.update(cx, |project, cx| {
20106 project.inlay_hints(buffer_handle, range, cx)
20107 }))
20108 }
20109
20110 fn resolve_inlay_hint(
20111 &self,
20112 hint: InlayHint,
20113 buffer_handle: Entity<Buffer>,
20114 server_id: LanguageServerId,
20115 cx: &mut App,
20116 ) -> Option<Task<anyhow::Result<InlayHint>>> {
20117 Some(self.update(cx, |project, cx| {
20118 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
20119 }))
20120 }
20121
20122 fn range_for_rename(
20123 &self,
20124 buffer: &Entity<Buffer>,
20125 position: text::Anchor,
20126 cx: &mut App,
20127 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
20128 Some(self.update(cx, |project, cx| {
20129 let buffer = buffer.clone();
20130 let task = project.prepare_rename(buffer.clone(), position, cx);
20131 cx.spawn(async move |_, cx| {
20132 Ok(match task.await? {
20133 PrepareRenameResponse::Success(range) => Some(range),
20134 PrepareRenameResponse::InvalidPosition => None,
20135 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
20136 // Fallback on using TreeSitter info to determine identifier range
20137 buffer.update(cx, |buffer, _| {
20138 let snapshot = buffer.snapshot();
20139 let (range, kind) = snapshot.surrounding_word(position);
20140 if kind != Some(CharKind::Word) {
20141 return None;
20142 }
20143 Some(
20144 snapshot.anchor_before(range.start)
20145 ..snapshot.anchor_after(range.end),
20146 )
20147 })?
20148 }
20149 })
20150 })
20151 }))
20152 }
20153
20154 fn perform_rename(
20155 &self,
20156 buffer: &Entity<Buffer>,
20157 position: text::Anchor,
20158 new_name: String,
20159 cx: &mut App,
20160 ) -> Option<Task<Result<ProjectTransaction>>> {
20161 Some(self.update(cx, |project, cx| {
20162 project.perform_rename(buffer.clone(), position, new_name, cx)
20163 }))
20164 }
20165}
20166
20167fn inlay_hint_settings(
20168 location: Anchor,
20169 snapshot: &MultiBufferSnapshot,
20170 cx: &mut Context<Editor>,
20171) -> InlayHintSettings {
20172 let file = snapshot.file_at(location);
20173 let language = snapshot.language_at(location).map(|l| l.name());
20174 language_settings(language, file, cx).inlay_hints
20175}
20176
20177fn consume_contiguous_rows(
20178 contiguous_row_selections: &mut Vec<Selection<Point>>,
20179 selection: &Selection<Point>,
20180 display_map: &DisplaySnapshot,
20181 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
20182) -> (MultiBufferRow, MultiBufferRow) {
20183 contiguous_row_selections.push(selection.clone());
20184 let start_row = MultiBufferRow(selection.start.row);
20185 let mut end_row = ending_row(selection, display_map);
20186
20187 while let Some(next_selection) = selections.peek() {
20188 if next_selection.start.row <= end_row.0 {
20189 end_row = ending_row(next_selection, display_map);
20190 contiguous_row_selections.push(selections.next().unwrap().clone());
20191 } else {
20192 break;
20193 }
20194 }
20195 (start_row, end_row)
20196}
20197
20198fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
20199 if next_selection.end.column > 0 || next_selection.is_empty() {
20200 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
20201 } else {
20202 MultiBufferRow(next_selection.end.row)
20203 }
20204}
20205
20206impl EditorSnapshot {
20207 pub fn remote_selections_in_range<'a>(
20208 &'a self,
20209 range: &'a Range<Anchor>,
20210 collaboration_hub: &dyn CollaborationHub,
20211 cx: &'a App,
20212 ) -> impl 'a + Iterator<Item = RemoteSelection> {
20213 let participant_names = collaboration_hub.user_names(cx);
20214 let participant_indices = collaboration_hub.user_participant_indices(cx);
20215 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20216 let collaborators_by_replica_id = collaborators_by_peer_id
20217 .iter()
20218 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
20219 .collect::<HashMap<_, _>>();
20220 self.buffer_snapshot
20221 .selections_in_range(range, false)
20222 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20223 if replica_id == AGENT_REPLICA_ID {
20224 Some(RemoteSelection {
20225 replica_id,
20226 selection,
20227 cursor_shape,
20228 line_mode,
20229 collaborator_id: CollaboratorId::Agent,
20230 user_name: Some("Agent".into()),
20231 color: cx.theme().players().agent(),
20232 })
20233 } else {
20234 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20235 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20236 let user_name = participant_names.get(&collaborator.user_id).cloned();
20237 Some(RemoteSelection {
20238 replica_id,
20239 selection,
20240 cursor_shape,
20241 line_mode,
20242 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20243 user_name,
20244 color: if let Some(index) = participant_index {
20245 cx.theme().players().color_for_participant(index.0)
20246 } else {
20247 cx.theme().players().absent()
20248 },
20249 })
20250 }
20251 })
20252 }
20253
20254 pub fn hunks_for_ranges(
20255 &self,
20256 ranges: impl IntoIterator<Item = Range<Point>>,
20257 ) -> Vec<MultiBufferDiffHunk> {
20258 let mut hunks = Vec::new();
20259 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20260 HashMap::default();
20261 for query_range in ranges {
20262 let query_rows =
20263 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20264 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20265 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20266 ) {
20267 // Include deleted hunks that are adjacent to the query range, because
20268 // otherwise they would be missed.
20269 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20270 if hunk.status().is_deleted() {
20271 intersects_range |= hunk.row_range.start == query_rows.end;
20272 intersects_range |= hunk.row_range.end == query_rows.start;
20273 }
20274 if intersects_range {
20275 if !processed_buffer_rows
20276 .entry(hunk.buffer_id)
20277 .or_default()
20278 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20279 {
20280 continue;
20281 }
20282 hunks.push(hunk);
20283 }
20284 }
20285 }
20286
20287 hunks
20288 }
20289
20290 fn display_diff_hunks_for_rows<'a>(
20291 &'a self,
20292 display_rows: Range<DisplayRow>,
20293 folded_buffers: &'a HashSet<BufferId>,
20294 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20295 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20296 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20297
20298 self.buffer_snapshot
20299 .diff_hunks_in_range(buffer_start..buffer_end)
20300 .filter_map(|hunk| {
20301 if folded_buffers.contains(&hunk.buffer_id) {
20302 return None;
20303 }
20304
20305 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20306 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20307
20308 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20309 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20310
20311 let display_hunk = if hunk_display_start.column() != 0 {
20312 DisplayDiffHunk::Folded {
20313 display_row: hunk_display_start.row(),
20314 }
20315 } else {
20316 let mut end_row = hunk_display_end.row();
20317 if hunk_display_end.column() > 0 {
20318 end_row.0 += 1;
20319 }
20320 let is_created_file = hunk.is_created_file();
20321 DisplayDiffHunk::Unfolded {
20322 status: hunk.status(),
20323 diff_base_byte_range: hunk.diff_base_byte_range,
20324 display_row_range: hunk_display_start.row()..end_row,
20325 multi_buffer_range: Anchor::range_in_buffer(
20326 hunk.excerpt_id,
20327 hunk.buffer_id,
20328 hunk.buffer_range,
20329 ),
20330 is_created_file,
20331 }
20332 };
20333
20334 Some(display_hunk)
20335 })
20336 }
20337
20338 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20339 self.display_snapshot.buffer_snapshot.language_at(position)
20340 }
20341
20342 pub fn is_focused(&self) -> bool {
20343 self.is_focused
20344 }
20345
20346 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20347 self.placeholder_text.as_ref()
20348 }
20349
20350 pub fn scroll_position(&self) -> gpui::Point<f32> {
20351 self.scroll_anchor.scroll_position(&self.display_snapshot)
20352 }
20353
20354 fn gutter_dimensions(
20355 &self,
20356 font_id: FontId,
20357 font_size: Pixels,
20358 max_line_number_width: Pixels,
20359 cx: &App,
20360 ) -> Option<GutterDimensions> {
20361 if !self.show_gutter {
20362 return None;
20363 }
20364
20365 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20366 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20367
20368 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20369 matches!(
20370 ProjectSettings::get_global(cx).git.git_gutter,
20371 Some(GitGutterSetting::TrackedFiles)
20372 )
20373 });
20374 let gutter_settings = EditorSettings::get_global(cx).gutter;
20375 let show_line_numbers = self
20376 .show_line_numbers
20377 .unwrap_or(gutter_settings.line_numbers);
20378 let line_gutter_width = if show_line_numbers {
20379 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20380 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20381 max_line_number_width.max(min_width_for_number_on_gutter)
20382 } else {
20383 0.0.into()
20384 };
20385
20386 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20387 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20388
20389 let git_blame_entries_width =
20390 self.git_blame_gutter_max_author_length
20391 .map(|max_author_length| {
20392 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20393 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20394
20395 /// The number of characters to dedicate to gaps and margins.
20396 const SPACING_WIDTH: usize = 4;
20397
20398 let max_char_count = max_author_length.min(renderer.max_author_length())
20399 + ::git::SHORT_SHA_LENGTH
20400 + MAX_RELATIVE_TIMESTAMP.len()
20401 + SPACING_WIDTH;
20402
20403 em_advance * max_char_count
20404 });
20405
20406 let is_singleton = self.buffer_snapshot.is_singleton();
20407
20408 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
20409 left_padding += if !is_singleton {
20410 em_width * 4.0
20411 } else if show_runnables || show_breakpoints {
20412 em_width * 3.0
20413 } else if show_git_gutter && show_line_numbers {
20414 em_width * 2.0
20415 } else if show_git_gutter || show_line_numbers {
20416 em_width
20417 } else {
20418 px(0.)
20419 };
20420
20421 let shows_folds = is_singleton && gutter_settings.folds;
20422
20423 let right_padding = if shows_folds && show_line_numbers {
20424 em_width * 4.0
20425 } else if shows_folds || (!is_singleton && show_line_numbers) {
20426 em_width * 3.0
20427 } else if show_line_numbers {
20428 em_width
20429 } else {
20430 px(0.)
20431 };
20432
20433 Some(GutterDimensions {
20434 left_padding,
20435 right_padding,
20436 width: line_gutter_width + left_padding + right_padding,
20437 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
20438 git_blame_entries_width,
20439 })
20440 }
20441
20442 pub fn render_crease_toggle(
20443 &self,
20444 buffer_row: MultiBufferRow,
20445 row_contains_cursor: bool,
20446 editor: Entity<Editor>,
20447 window: &mut Window,
20448 cx: &mut App,
20449 ) -> Option<AnyElement> {
20450 let folded = self.is_line_folded(buffer_row);
20451 let mut is_foldable = false;
20452
20453 if let Some(crease) = self
20454 .crease_snapshot
20455 .query_row(buffer_row, &self.buffer_snapshot)
20456 {
20457 is_foldable = true;
20458 match crease {
20459 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20460 if let Some(render_toggle) = render_toggle {
20461 let toggle_callback =
20462 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20463 if folded {
20464 editor.update(cx, |editor, cx| {
20465 editor.fold_at(buffer_row, window, cx)
20466 });
20467 } else {
20468 editor.update(cx, |editor, cx| {
20469 editor.unfold_at(buffer_row, window, cx)
20470 });
20471 }
20472 });
20473 return Some((render_toggle)(
20474 buffer_row,
20475 folded,
20476 toggle_callback,
20477 window,
20478 cx,
20479 ));
20480 }
20481 }
20482 }
20483 }
20484
20485 is_foldable |= self.starts_indent(buffer_row);
20486
20487 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20488 Some(
20489 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20490 .toggle_state(folded)
20491 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20492 if folded {
20493 this.unfold_at(buffer_row, window, cx);
20494 } else {
20495 this.fold_at(buffer_row, window, cx);
20496 }
20497 }))
20498 .into_any_element(),
20499 )
20500 } else {
20501 None
20502 }
20503 }
20504
20505 pub fn render_crease_trailer(
20506 &self,
20507 buffer_row: MultiBufferRow,
20508 window: &mut Window,
20509 cx: &mut App,
20510 ) -> Option<AnyElement> {
20511 let folded = self.is_line_folded(buffer_row);
20512 if let Crease::Inline { render_trailer, .. } = self
20513 .crease_snapshot
20514 .query_row(buffer_row, &self.buffer_snapshot)?
20515 {
20516 let render_trailer = render_trailer.as_ref()?;
20517 Some(render_trailer(buffer_row, folded, window, cx))
20518 } else {
20519 None
20520 }
20521 }
20522}
20523
20524impl Deref for EditorSnapshot {
20525 type Target = DisplaySnapshot;
20526
20527 fn deref(&self) -> &Self::Target {
20528 &self.display_snapshot
20529 }
20530}
20531
20532#[derive(Clone, Debug, PartialEq, Eq)]
20533pub enum EditorEvent {
20534 InputIgnored {
20535 text: Arc<str>,
20536 },
20537 InputHandled {
20538 utf16_range_to_replace: Option<Range<isize>>,
20539 text: Arc<str>,
20540 },
20541 ExcerptsAdded {
20542 buffer: Entity<Buffer>,
20543 predecessor: ExcerptId,
20544 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
20545 },
20546 ExcerptsRemoved {
20547 ids: Vec<ExcerptId>,
20548 removed_buffer_ids: Vec<BufferId>,
20549 },
20550 BufferFoldToggled {
20551 ids: Vec<ExcerptId>,
20552 folded: bool,
20553 },
20554 ExcerptsEdited {
20555 ids: Vec<ExcerptId>,
20556 },
20557 ExcerptsExpanded {
20558 ids: Vec<ExcerptId>,
20559 },
20560 BufferEdited,
20561 Edited {
20562 transaction_id: clock::Lamport,
20563 },
20564 Reparsed(BufferId),
20565 Focused,
20566 FocusedIn,
20567 Blurred,
20568 DirtyChanged,
20569 Saved,
20570 TitleChanged,
20571 DiffBaseChanged,
20572 SelectionsChanged {
20573 local: bool,
20574 },
20575 ScrollPositionChanged {
20576 local: bool,
20577 autoscroll: bool,
20578 },
20579 Closed,
20580 TransactionUndone {
20581 transaction_id: clock::Lamport,
20582 },
20583 TransactionBegun {
20584 transaction_id: clock::Lamport,
20585 },
20586 Reloaded,
20587 CursorShapeChanged,
20588 PushedToNavHistory {
20589 anchor: Anchor,
20590 is_deactivate: bool,
20591 },
20592}
20593
20594impl EventEmitter<EditorEvent> for Editor {}
20595
20596impl Focusable for Editor {
20597 fn focus_handle(&self, _cx: &App) -> FocusHandle {
20598 self.focus_handle.clone()
20599 }
20600}
20601
20602impl Render for Editor {
20603 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20604 let settings = ThemeSettings::get_global(cx);
20605
20606 let mut text_style = match self.mode {
20607 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
20608 color: cx.theme().colors().editor_foreground,
20609 font_family: settings.ui_font.family.clone(),
20610 font_features: settings.ui_font.features.clone(),
20611 font_fallbacks: settings.ui_font.fallbacks.clone(),
20612 font_size: rems(0.875).into(),
20613 font_weight: settings.ui_font.weight,
20614 line_height: relative(settings.buffer_line_height.value()),
20615 ..Default::default()
20616 },
20617 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
20618 color: cx.theme().colors().editor_foreground,
20619 font_family: settings.buffer_font.family.clone(),
20620 font_features: settings.buffer_font.features.clone(),
20621 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20622 font_size: settings.buffer_font_size(cx).into(),
20623 font_weight: settings.buffer_font.weight,
20624 line_height: relative(settings.buffer_line_height.value()),
20625 ..Default::default()
20626 },
20627 };
20628 if let Some(text_style_refinement) = &self.text_style_refinement {
20629 text_style.refine(text_style_refinement)
20630 }
20631
20632 let background = match self.mode {
20633 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
20634 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
20635 EditorMode::Full { .. } => cx.theme().colors().editor_background,
20636 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
20637 };
20638
20639 EditorElement::new(
20640 &cx.entity(),
20641 EditorStyle {
20642 background,
20643 local_player: cx.theme().players().local(),
20644 text: text_style,
20645 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
20646 syntax: cx.theme().syntax().clone(),
20647 status: cx.theme().status().clone(),
20648 inlay_hints_style: make_inlay_hints_style(cx),
20649 inline_completion_styles: make_suggestion_styles(cx),
20650 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
20651 show_underlines: !self.mode.is_minimap(),
20652 },
20653 )
20654 }
20655}
20656
20657impl EntityInputHandler for Editor {
20658 fn text_for_range(
20659 &mut self,
20660 range_utf16: Range<usize>,
20661 adjusted_range: &mut Option<Range<usize>>,
20662 _: &mut Window,
20663 cx: &mut Context<Self>,
20664 ) -> Option<String> {
20665 let snapshot = self.buffer.read(cx).read(cx);
20666 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
20667 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
20668 if (start.0..end.0) != range_utf16 {
20669 adjusted_range.replace(start.0..end.0);
20670 }
20671 Some(snapshot.text_for_range(start..end).collect())
20672 }
20673
20674 fn selected_text_range(
20675 &mut self,
20676 ignore_disabled_input: bool,
20677 _: &mut Window,
20678 cx: &mut Context<Self>,
20679 ) -> Option<UTF16Selection> {
20680 // Prevent the IME menu from appearing when holding down an alphabetic key
20681 // while input is disabled.
20682 if !ignore_disabled_input && !self.input_enabled {
20683 return None;
20684 }
20685
20686 let selection = self.selections.newest::<OffsetUtf16>(cx);
20687 let range = selection.range();
20688
20689 Some(UTF16Selection {
20690 range: range.start.0..range.end.0,
20691 reversed: selection.reversed,
20692 })
20693 }
20694
20695 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
20696 let snapshot = self.buffer.read(cx).read(cx);
20697 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
20698 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
20699 }
20700
20701 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20702 self.clear_highlights::<InputComposition>(cx);
20703 self.ime_transaction.take();
20704 }
20705
20706 fn replace_text_in_range(
20707 &mut self,
20708 range_utf16: Option<Range<usize>>,
20709 text: &str,
20710 window: &mut Window,
20711 cx: &mut Context<Self>,
20712 ) {
20713 if !self.input_enabled {
20714 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20715 return;
20716 }
20717
20718 self.transact(window, cx, |this, window, cx| {
20719 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
20720 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20721 Some(this.selection_replacement_ranges(range_utf16, cx))
20722 } else {
20723 this.marked_text_ranges(cx)
20724 };
20725
20726 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
20727 let newest_selection_id = this.selections.newest_anchor().id;
20728 this.selections
20729 .all::<OffsetUtf16>(cx)
20730 .iter()
20731 .zip(ranges_to_replace.iter())
20732 .find_map(|(selection, range)| {
20733 if selection.id == newest_selection_id {
20734 Some(
20735 (range.start.0 as isize - selection.head().0 as isize)
20736 ..(range.end.0 as isize - selection.head().0 as isize),
20737 )
20738 } else {
20739 None
20740 }
20741 })
20742 });
20743
20744 cx.emit(EditorEvent::InputHandled {
20745 utf16_range_to_replace: range_to_replace,
20746 text: text.into(),
20747 });
20748
20749 if let Some(new_selected_ranges) = new_selected_ranges {
20750 this.change_selections(None, window, cx, |selections| {
20751 selections.select_ranges(new_selected_ranges)
20752 });
20753 this.backspace(&Default::default(), window, cx);
20754 }
20755
20756 this.handle_input(text, window, cx);
20757 });
20758
20759 if let Some(transaction) = self.ime_transaction {
20760 self.buffer.update(cx, |buffer, cx| {
20761 buffer.group_until_transaction(transaction, cx);
20762 });
20763 }
20764
20765 self.unmark_text(window, cx);
20766 }
20767
20768 fn replace_and_mark_text_in_range(
20769 &mut self,
20770 range_utf16: Option<Range<usize>>,
20771 text: &str,
20772 new_selected_range_utf16: Option<Range<usize>>,
20773 window: &mut Window,
20774 cx: &mut Context<Self>,
20775 ) {
20776 if !self.input_enabled {
20777 return;
20778 }
20779
20780 let transaction = self.transact(window, cx, |this, window, cx| {
20781 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
20782 let snapshot = this.buffer.read(cx).read(cx);
20783 if let Some(relative_range_utf16) = range_utf16.as_ref() {
20784 for marked_range in &mut marked_ranges {
20785 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
20786 marked_range.start.0 += relative_range_utf16.start;
20787 marked_range.start =
20788 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
20789 marked_range.end =
20790 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
20791 }
20792 }
20793 Some(marked_ranges)
20794 } else if let Some(range_utf16) = range_utf16 {
20795 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20796 Some(this.selection_replacement_ranges(range_utf16, cx))
20797 } else {
20798 None
20799 };
20800
20801 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
20802 let newest_selection_id = this.selections.newest_anchor().id;
20803 this.selections
20804 .all::<OffsetUtf16>(cx)
20805 .iter()
20806 .zip(ranges_to_replace.iter())
20807 .find_map(|(selection, range)| {
20808 if selection.id == newest_selection_id {
20809 Some(
20810 (range.start.0 as isize - selection.head().0 as isize)
20811 ..(range.end.0 as isize - selection.head().0 as isize),
20812 )
20813 } else {
20814 None
20815 }
20816 })
20817 });
20818
20819 cx.emit(EditorEvent::InputHandled {
20820 utf16_range_to_replace: range_to_replace,
20821 text: text.into(),
20822 });
20823
20824 if let Some(ranges) = ranges_to_replace {
20825 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
20826 }
20827
20828 let marked_ranges = {
20829 let snapshot = this.buffer.read(cx).read(cx);
20830 this.selections
20831 .disjoint_anchors()
20832 .iter()
20833 .map(|selection| {
20834 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
20835 })
20836 .collect::<Vec<_>>()
20837 };
20838
20839 if text.is_empty() {
20840 this.unmark_text(window, cx);
20841 } else {
20842 this.highlight_text::<InputComposition>(
20843 marked_ranges.clone(),
20844 HighlightStyle {
20845 underline: Some(UnderlineStyle {
20846 thickness: px(1.),
20847 color: None,
20848 wavy: false,
20849 }),
20850 ..Default::default()
20851 },
20852 cx,
20853 );
20854 }
20855
20856 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
20857 let use_autoclose = this.use_autoclose;
20858 let use_auto_surround = this.use_auto_surround;
20859 this.set_use_autoclose(false);
20860 this.set_use_auto_surround(false);
20861 this.handle_input(text, window, cx);
20862 this.set_use_autoclose(use_autoclose);
20863 this.set_use_auto_surround(use_auto_surround);
20864
20865 if let Some(new_selected_range) = new_selected_range_utf16 {
20866 let snapshot = this.buffer.read(cx).read(cx);
20867 let new_selected_ranges = marked_ranges
20868 .into_iter()
20869 .map(|marked_range| {
20870 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
20871 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
20872 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
20873 snapshot.clip_offset_utf16(new_start, Bias::Left)
20874 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
20875 })
20876 .collect::<Vec<_>>();
20877
20878 drop(snapshot);
20879 this.change_selections(None, window, cx, |selections| {
20880 selections.select_ranges(new_selected_ranges)
20881 });
20882 }
20883 });
20884
20885 self.ime_transaction = self.ime_transaction.or(transaction);
20886 if let Some(transaction) = self.ime_transaction {
20887 self.buffer.update(cx, |buffer, cx| {
20888 buffer.group_until_transaction(transaction, cx);
20889 });
20890 }
20891
20892 if self.text_highlights::<InputComposition>(cx).is_none() {
20893 self.ime_transaction.take();
20894 }
20895 }
20896
20897 fn bounds_for_range(
20898 &mut self,
20899 range_utf16: Range<usize>,
20900 element_bounds: gpui::Bounds<Pixels>,
20901 window: &mut Window,
20902 cx: &mut Context<Self>,
20903 ) -> Option<gpui::Bounds<Pixels>> {
20904 let text_layout_details = self.text_layout_details(window);
20905 let gpui::Size {
20906 width: em_width,
20907 height: line_height,
20908 } = self.character_size(window);
20909
20910 let snapshot = self.snapshot(window, cx);
20911 let scroll_position = snapshot.scroll_position();
20912 let scroll_left = scroll_position.x * em_width;
20913
20914 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
20915 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
20916 + self.gutter_dimensions.width
20917 + self.gutter_dimensions.margin;
20918 let y = line_height * (start.row().as_f32() - scroll_position.y);
20919
20920 Some(Bounds {
20921 origin: element_bounds.origin + point(x, y),
20922 size: size(em_width, line_height),
20923 })
20924 }
20925
20926 fn character_index_for_point(
20927 &mut self,
20928 point: gpui::Point<Pixels>,
20929 _window: &mut Window,
20930 _cx: &mut Context<Self>,
20931 ) -> Option<usize> {
20932 let position_map = self.last_position_map.as_ref()?;
20933 if !position_map.text_hitbox.contains(&point) {
20934 return None;
20935 }
20936 let display_point = position_map.point_for_position(point).previous_valid;
20937 let anchor = position_map
20938 .snapshot
20939 .display_point_to_anchor(display_point, Bias::Left);
20940 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
20941 Some(utf16_offset.0)
20942 }
20943}
20944
20945trait SelectionExt {
20946 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
20947 fn spanned_rows(
20948 &self,
20949 include_end_if_at_line_start: bool,
20950 map: &DisplaySnapshot,
20951 ) -> Range<MultiBufferRow>;
20952}
20953
20954impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
20955 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
20956 let start = self
20957 .start
20958 .to_point(&map.buffer_snapshot)
20959 .to_display_point(map);
20960 let end = self
20961 .end
20962 .to_point(&map.buffer_snapshot)
20963 .to_display_point(map);
20964 if self.reversed {
20965 end..start
20966 } else {
20967 start..end
20968 }
20969 }
20970
20971 fn spanned_rows(
20972 &self,
20973 include_end_if_at_line_start: bool,
20974 map: &DisplaySnapshot,
20975 ) -> Range<MultiBufferRow> {
20976 let start = self.start.to_point(&map.buffer_snapshot);
20977 let mut end = self.end.to_point(&map.buffer_snapshot);
20978 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
20979 end.row -= 1;
20980 }
20981
20982 let buffer_start = map.prev_line_boundary(start).0;
20983 let buffer_end = map.next_line_boundary(end).0;
20984 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
20985 }
20986}
20987
20988impl<T: InvalidationRegion> InvalidationStack<T> {
20989 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
20990 where
20991 S: Clone + ToOffset,
20992 {
20993 while let Some(region) = self.last() {
20994 let all_selections_inside_invalidation_ranges =
20995 if selections.len() == region.ranges().len() {
20996 selections
20997 .iter()
20998 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
20999 .all(|(selection, invalidation_range)| {
21000 let head = selection.head().to_offset(buffer);
21001 invalidation_range.start <= head && invalidation_range.end >= head
21002 })
21003 } else {
21004 false
21005 };
21006
21007 if all_selections_inside_invalidation_ranges {
21008 break;
21009 } else {
21010 self.pop();
21011 }
21012 }
21013 }
21014}
21015
21016impl<T> Default for InvalidationStack<T> {
21017 fn default() -> Self {
21018 Self(Default::default())
21019 }
21020}
21021
21022impl<T> Deref for InvalidationStack<T> {
21023 type Target = Vec<T>;
21024
21025 fn deref(&self) -> &Self::Target {
21026 &self.0
21027 }
21028}
21029
21030impl<T> DerefMut for InvalidationStack<T> {
21031 fn deref_mut(&mut self) -> &mut Self::Target {
21032 &mut self.0
21033 }
21034}
21035
21036impl InvalidationRegion for SnippetState {
21037 fn ranges(&self) -> &[Range<Anchor>] {
21038 &self.ranges[self.active_index]
21039 }
21040}
21041
21042fn inline_completion_edit_text(
21043 current_snapshot: &BufferSnapshot,
21044 edits: &[(Range<Anchor>, String)],
21045 edit_preview: &EditPreview,
21046 include_deletions: bool,
21047 cx: &App,
21048) -> HighlightedText {
21049 let edits = edits
21050 .iter()
21051 .map(|(anchor, text)| {
21052 (
21053 anchor.start.text_anchor..anchor.end.text_anchor,
21054 text.clone(),
21055 )
21056 })
21057 .collect::<Vec<_>>();
21058
21059 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
21060}
21061
21062pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
21063 match severity {
21064 lsp::DiagnosticSeverity::ERROR => colors.error,
21065 lsp::DiagnosticSeverity::WARNING => colors.warning,
21066 lsp::DiagnosticSeverity::INFORMATION => colors.info,
21067 lsp::DiagnosticSeverity::HINT => colors.info,
21068 _ => colors.ignored,
21069 }
21070}
21071
21072pub fn styled_runs_for_code_label<'a>(
21073 label: &'a CodeLabel,
21074 syntax_theme: &'a theme::SyntaxTheme,
21075) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
21076 let fade_out = HighlightStyle {
21077 fade_out: Some(0.35),
21078 ..Default::default()
21079 };
21080
21081 let mut prev_end = label.filter_range.end;
21082 label
21083 .runs
21084 .iter()
21085 .enumerate()
21086 .flat_map(move |(ix, (range, highlight_id))| {
21087 let style = if let Some(style) = highlight_id.style(syntax_theme) {
21088 style
21089 } else {
21090 return Default::default();
21091 };
21092 let mut muted_style = style;
21093 muted_style.highlight(fade_out);
21094
21095 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
21096 if range.start >= label.filter_range.end {
21097 if range.start > prev_end {
21098 runs.push((prev_end..range.start, fade_out));
21099 }
21100 runs.push((range.clone(), muted_style));
21101 } else if range.end <= label.filter_range.end {
21102 runs.push((range.clone(), style));
21103 } else {
21104 runs.push((range.start..label.filter_range.end, style));
21105 runs.push((label.filter_range.end..range.end, muted_style));
21106 }
21107 prev_end = cmp::max(prev_end, range.end);
21108
21109 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
21110 runs.push((prev_end..label.text.len(), fade_out));
21111 }
21112
21113 runs
21114 })
21115}
21116
21117pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
21118 let mut prev_index = 0;
21119 let mut prev_codepoint: Option<char> = None;
21120 text.char_indices()
21121 .chain([(text.len(), '\0')])
21122 .filter_map(move |(index, codepoint)| {
21123 let prev_codepoint = prev_codepoint.replace(codepoint)?;
21124 let is_boundary = index == text.len()
21125 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
21126 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
21127 if is_boundary {
21128 let chunk = &text[prev_index..index];
21129 prev_index = index;
21130 Some(chunk)
21131 } else {
21132 None
21133 }
21134 })
21135}
21136
21137pub trait RangeToAnchorExt: Sized {
21138 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
21139
21140 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
21141 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
21142 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
21143 }
21144}
21145
21146impl<T: ToOffset> RangeToAnchorExt for Range<T> {
21147 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
21148 let start_offset = self.start.to_offset(snapshot);
21149 let end_offset = self.end.to_offset(snapshot);
21150 if start_offset == end_offset {
21151 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
21152 } else {
21153 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
21154 }
21155 }
21156}
21157
21158pub trait RowExt {
21159 fn as_f32(&self) -> f32;
21160
21161 fn next_row(&self) -> Self;
21162
21163 fn previous_row(&self) -> Self;
21164
21165 fn minus(&self, other: Self) -> u32;
21166}
21167
21168impl RowExt for DisplayRow {
21169 fn as_f32(&self) -> f32 {
21170 self.0 as f32
21171 }
21172
21173 fn next_row(&self) -> Self {
21174 Self(self.0 + 1)
21175 }
21176
21177 fn previous_row(&self) -> Self {
21178 Self(self.0.saturating_sub(1))
21179 }
21180
21181 fn minus(&self, other: Self) -> u32 {
21182 self.0 - other.0
21183 }
21184}
21185
21186impl RowExt for MultiBufferRow {
21187 fn as_f32(&self) -> f32 {
21188 self.0 as f32
21189 }
21190
21191 fn next_row(&self) -> Self {
21192 Self(self.0 + 1)
21193 }
21194
21195 fn previous_row(&self) -> Self {
21196 Self(self.0.saturating_sub(1))
21197 }
21198
21199 fn minus(&self, other: Self) -> u32 {
21200 self.0 - other.0
21201 }
21202}
21203
21204trait RowRangeExt {
21205 type Row;
21206
21207 fn len(&self) -> usize;
21208
21209 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
21210}
21211
21212impl RowRangeExt for Range<MultiBufferRow> {
21213 type Row = MultiBufferRow;
21214
21215 fn len(&self) -> usize {
21216 (self.end.0 - self.start.0) as usize
21217 }
21218
21219 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21220 (self.start.0..self.end.0).map(MultiBufferRow)
21221 }
21222}
21223
21224impl RowRangeExt for Range<DisplayRow> {
21225 type Row = DisplayRow;
21226
21227 fn len(&self) -> usize {
21228 (self.end.0 - self.start.0) as usize
21229 }
21230
21231 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21232 (self.start.0..self.end.0).map(DisplayRow)
21233 }
21234}
21235
21236/// If select range has more than one line, we
21237/// just point the cursor to range.start.
21238fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21239 if range.start.row == range.end.row {
21240 range
21241 } else {
21242 range.start..range.start
21243 }
21244}
21245pub struct KillRing(ClipboardItem);
21246impl Global for KillRing {}
21247
21248const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21249
21250enum BreakpointPromptEditAction {
21251 Log,
21252 Condition,
21253 HitCondition,
21254}
21255
21256struct BreakpointPromptEditor {
21257 pub(crate) prompt: Entity<Editor>,
21258 editor: WeakEntity<Editor>,
21259 breakpoint_anchor: Anchor,
21260 breakpoint: Breakpoint,
21261 edit_action: BreakpointPromptEditAction,
21262 block_ids: HashSet<CustomBlockId>,
21263 editor_margins: Arc<Mutex<EditorMargins>>,
21264 _subscriptions: Vec<Subscription>,
21265}
21266
21267impl BreakpointPromptEditor {
21268 const MAX_LINES: u8 = 4;
21269
21270 fn new(
21271 editor: WeakEntity<Editor>,
21272 breakpoint_anchor: Anchor,
21273 breakpoint: Breakpoint,
21274 edit_action: BreakpointPromptEditAction,
21275 window: &mut Window,
21276 cx: &mut Context<Self>,
21277 ) -> Self {
21278 let base_text = match edit_action {
21279 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21280 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21281 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21282 }
21283 .map(|msg| msg.to_string())
21284 .unwrap_or_default();
21285
21286 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21287 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21288
21289 let prompt = cx.new(|cx| {
21290 let mut prompt = Editor::new(
21291 EditorMode::AutoHeight {
21292 max_lines: Self::MAX_LINES as usize,
21293 },
21294 buffer,
21295 None,
21296 window,
21297 cx,
21298 );
21299 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21300 prompt.set_show_cursor_when_unfocused(false, cx);
21301 prompt.set_placeholder_text(
21302 match edit_action {
21303 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21304 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21305 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21306 },
21307 cx,
21308 );
21309
21310 prompt
21311 });
21312
21313 Self {
21314 prompt,
21315 editor,
21316 breakpoint_anchor,
21317 breakpoint,
21318 edit_action,
21319 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21320 block_ids: Default::default(),
21321 _subscriptions: vec![],
21322 }
21323 }
21324
21325 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21326 self.block_ids.extend(block_ids)
21327 }
21328
21329 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21330 if let Some(editor) = self.editor.upgrade() {
21331 let message = self
21332 .prompt
21333 .read(cx)
21334 .buffer
21335 .read(cx)
21336 .as_singleton()
21337 .expect("A multi buffer in breakpoint prompt isn't possible")
21338 .read(cx)
21339 .as_rope()
21340 .to_string();
21341
21342 editor.update(cx, |editor, cx| {
21343 editor.edit_breakpoint_at_anchor(
21344 self.breakpoint_anchor,
21345 self.breakpoint.clone(),
21346 match self.edit_action {
21347 BreakpointPromptEditAction::Log => {
21348 BreakpointEditAction::EditLogMessage(message.into())
21349 }
21350 BreakpointPromptEditAction::Condition => {
21351 BreakpointEditAction::EditCondition(message.into())
21352 }
21353 BreakpointPromptEditAction::HitCondition => {
21354 BreakpointEditAction::EditHitCondition(message.into())
21355 }
21356 },
21357 cx,
21358 );
21359
21360 editor.remove_blocks(self.block_ids.clone(), None, cx);
21361 cx.focus_self(window);
21362 });
21363 }
21364 }
21365
21366 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21367 self.editor
21368 .update(cx, |editor, cx| {
21369 editor.remove_blocks(self.block_ids.clone(), None, cx);
21370 window.focus(&editor.focus_handle);
21371 })
21372 .log_err();
21373 }
21374
21375 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21376 let settings = ThemeSettings::get_global(cx);
21377 let text_style = TextStyle {
21378 color: if self.prompt.read(cx).read_only(cx) {
21379 cx.theme().colors().text_disabled
21380 } else {
21381 cx.theme().colors().text
21382 },
21383 font_family: settings.buffer_font.family.clone(),
21384 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21385 font_size: settings.buffer_font_size(cx).into(),
21386 font_weight: settings.buffer_font.weight,
21387 line_height: relative(settings.buffer_line_height.value()),
21388 ..Default::default()
21389 };
21390 EditorElement::new(
21391 &self.prompt,
21392 EditorStyle {
21393 background: cx.theme().colors().editor_background,
21394 local_player: cx.theme().players().local(),
21395 text: text_style,
21396 ..Default::default()
21397 },
21398 )
21399 }
21400}
21401
21402impl Render for BreakpointPromptEditor {
21403 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21404 let editor_margins = *self.editor_margins.lock();
21405 let gutter_dimensions = editor_margins.gutter;
21406 h_flex()
21407 .key_context("Editor")
21408 .bg(cx.theme().colors().editor_background)
21409 .border_y_1()
21410 .border_color(cx.theme().status().info_border)
21411 .size_full()
21412 .py(window.line_height() / 2.5)
21413 .on_action(cx.listener(Self::confirm))
21414 .on_action(cx.listener(Self::cancel))
21415 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21416 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21417 }
21418}
21419
21420impl Focusable for BreakpointPromptEditor {
21421 fn focus_handle(&self, cx: &App) -> FocusHandle {
21422 self.prompt.focus_handle(cx)
21423 }
21424}
21425
21426fn all_edits_insertions_or_deletions(
21427 edits: &Vec<(Range<Anchor>, String)>,
21428 snapshot: &MultiBufferSnapshot,
21429) -> bool {
21430 let mut all_insertions = true;
21431 let mut all_deletions = true;
21432
21433 for (range, new_text) in edits.iter() {
21434 let range_is_empty = range.to_offset(&snapshot).is_empty();
21435 let text_is_empty = new_text.is_empty();
21436
21437 if range_is_empty != text_is_empty {
21438 if range_is_empty {
21439 all_deletions = false;
21440 } else {
21441 all_insertions = false;
21442 }
21443 } else {
21444 return false;
21445 }
21446
21447 if !all_insertions && !all_deletions {
21448 return false;
21449 }
21450 }
21451 all_insertions || all_deletions
21452}
21453
21454struct MissingEditPredictionKeybindingTooltip;
21455
21456impl Render for MissingEditPredictionKeybindingTooltip {
21457 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21458 ui::tooltip_container(window, cx, |container, _, cx| {
21459 container
21460 .flex_shrink_0()
21461 .max_w_80()
21462 .min_h(rems_from_px(124.))
21463 .justify_between()
21464 .child(
21465 v_flex()
21466 .flex_1()
21467 .text_ui_sm(cx)
21468 .child(Label::new("Conflict with Accept Keybinding"))
21469 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21470 )
21471 .child(
21472 h_flex()
21473 .pb_1()
21474 .gap_1()
21475 .items_end()
21476 .w_full()
21477 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21478 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21479 }))
21480 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21481 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21482 })),
21483 )
21484 })
21485 }
21486}
21487
21488#[derive(Debug, Clone, Copy, PartialEq)]
21489pub struct LineHighlight {
21490 pub background: Background,
21491 pub border: Option<gpui::Hsla>,
21492 pub include_gutter: bool,
21493 pub type_id: Option<TypeId>,
21494}
21495
21496fn render_diff_hunk_controls(
21497 row: u32,
21498 status: &DiffHunkStatus,
21499 hunk_range: Range<Anchor>,
21500 is_created_file: bool,
21501 line_height: Pixels,
21502 editor: &Entity<Editor>,
21503 _window: &mut Window,
21504 cx: &mut App,
21505) -> AnyElement {
21506 h_flex()
21507 .h(line_height)
21508 .mr_1()
21509 .gap_1()
21510 .px_0p5()
21511 .pb_1()
21512 .border_x_1()
21513 .border_b_1()
21514 .border_color(cx.theme().colors().border_variant)
21515 .rounded_b_lg()
21516 .bg(cx.theme().colors().editor_background)
21517 .gap_1()
21518 .occlude()
21519 .shadow_md()
21520 .child(if status.has_secondary_hunk() {
21521 Button::new(("stage", row as u64), "Stage")
21522 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21523 .tooltip({
21524 let focus_handle = editor.focus_handle(cx);
21525 move |window, cx| {
21526 Tooltip::for_action_in(
21527 "Stage Hunk",
21528 &::git::ToggleStaged,
21529 &focus_handle,
21530 window,
21531 cx,
21532 )
21533 }
21534 })
21535 .on_click({
21536 let editor = editor.clone();
21537 move |_event, _window, cx| {
21538 editor.update(cx, |editor, cx| {
21539 editor.stage_or_unstage_diff_hunks(
21540 true,
21541 vec![hunk_range.start..hunk_range.start],
21542 cx,
21543 );
21544 });
21545 }
21546 })
21547 } else {
21548 Button::new(("unstage", row as u64), "Unstage")
21549 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21550 .tooltip({
21551 let focus_handle = editor.focus_handle(cx);
21552 move |window, cx| {
21553 Tooltip::for_action_in(
21554 "Unstage Hunk",
21555 &::git::ToggleStaged,
21556 &focus_handle,
21557 window,
21558 cx,
21559 )
21560 }
21561 })
21562 .on_click({
21563 let editor = editor.clone();
21564 move |_event, _window, cx| {
21565 editor.update(cx, |editor, cx| {
21566 editor.stage_or_unstage_diff_hunks(
21567 false,
21568 vec![hunk_range.start..hunk_range.start],
21569 cx,
21570 );
21571 });
21572 }
21573 })
21574 })
21575 .child(
21576 Button::new(("restore", row as u64), "Restore")
21577 .tooltip({
21578 let focus_handle = editor.focus_handle(cx);
21579 move |window, cx| {
21580 Tooltip::for_action_in(
21581 "Restore Hunk",
21582 &::git::Restore,
21583 &focus_handle,
21584 window,
21585 cx,
21586 )
21587 }
21588 })
21589 .on_click({
21590 let editor = editor.clone();
21591 move |_event, window, cx| {
21592 editor.update(cx, |editor, cx| {
21593 let snapshot = editor.snapshot(window, cx);
21594 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
21595 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
21596 });
21597 }
21598 })
21599 .disabled(is_created_file),
21600 )
21601 .when(
21602 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
21603 |el| {
21604 el.child(
21605 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
21606 .shape(IconButtonShape::Square)
21607 .icon_size(IconSize::Small)
21608 // .disabled(!has_multiple_hunks)
21609 .tooltip({
21610 let focus_handle = editor.focus_handle(cx);
21611 move |window, cx| {
21612 Tooltip::for_action_in(
21613 "Next Hunk",
21614 &GoToHunk,
21615 &focus_handle,
21616 window,
21617 cx,
21618 )
21619 }
21620 })
21621 .on_click({
21622 let editor = editor.clone();
21623 move |_event, window, cx| {
21624 editor.update(cx, |editor, cx| {
21625 let snapshot = editor.snapshot(window, cx);
21626 let position =
21627 hunk_range.end.to_point(&snapshot.buffer_snapshot);
21628 editor.go_to_hunk_before_or_after_position(
21629 &snapshot,
21630 position,
21631 Direction::Next,
21632 window,
21633 cx,
21634 );
21635 editor.expand_selected_diff_hunks(cx);
21636 });
21637 }
21638 }),
21639 )
21640 .child(
21641 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
21642 .shape(IconButtonShape::Square)
21643 .icon_size(IconSize::Small)
21644 // .disabled(!has_multiple_hunks)
21645 .tooltip({
21646 let focus_handle = editor.focus_handle(cx);
21647 move |window, cx| {
21648 Tooltip::for_action_in(
21649 "Previous Hunk",
21650 &GoToPreviousHunk,
21651 &focus_handle,
21652 window,
21653 cx,
21654 )
21655 }
21656 })
21657 .on_click({
21658 let editor = editor.clone();
21659 move |_event, window, cx| {
21660 editor.update(cx, |editor, cx| {
21661 let snapshot = editor.snapshot(window, cx);
21662 let point =
21663 hunk_range.start.to_point(&snapshot.buffer_snapshot);
21664 editor.go_to_hunk_before_or_after_position(
21665 &snapshot,
21666 point,
21667 Direction::Prev,
21668 window,
21669 cx,
21670 );
21671 editor.expand_selected_diff_hunks(cx);
21672 });
21673 }
21674 }),
21675 )
21676 },
21677 )
21678 .into_any_element()
21679}