1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_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 dap::TelemetrySpawnLocation;
63use display_map::*;
64pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
65pub use editor_settings::{
66 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes,
67 SearchSettings, ShowScrollbar,
68};
69use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
70pub use editor_settings_controls::*;
71use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
72pub use element::{
73 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
74};
75use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
76use futures::{
77 FutureExt,
78 future::{self, Shared, join},
79};
80use fuzzy::{StringMatch, StringMatchCandidate};
81
82use ::git::blame::BlameEntry;
83use ::git::{Restore, blame::ParsedCommitMessage};
84use code_context_menus::{
85 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
86 CompletionsMenu, ContextMenuOrigin,
87};
88use git::blame::{GitBlame, GlobalBlameRenderer};
89use gpui::{
90 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
91 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
92 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
93 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
94 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
95 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
96 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
97 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
98};
99use highlight_matching_bracket::refresh_matching_bracket_highlights;
100use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
101pub use hover_popover::hover_markdown_style;
102use hover_popover::{HoverState, hide_hover};
103use indent_guides::ActiveIndentGuidesState;
104use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
105pub use inline_completion::Direction;
106use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
107pub use items::MAX_TAB_TITLE_LEN;
108use itertools::Itertools;
109use language::{
110 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
111 CursorShape, DiagnosticEntry, DiffOptions, DocumentationConfig, EditPredictionsMode,
112 EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point,
113 Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
114 language_settings::{
115 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
116 all_language_settings, language_settings,
117 },
118 point_from_lsp, text_diff_with_options,
119};
120use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
121use linked_editing_ranges::refresh_linked_ranges;
122use markdown::Markdown;
123use mouse_context_menu::MouseContextMenu;
124use persistence::DB;
125use project::{
126 BreakpointWithPosition, ProjectPath,
127 debugger::{
128 breakpoint_store::{
129 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
130 BreakpointStoreEvent,
131 },
132 session::{Session, SessionEvent},
133 },
134 project_settings::DiagnosticSeverity,
135};
136
137pub use git::blame::BlameRenderer;
138pub use proposed_changes_editor::{
139 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
140};
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, 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 {
720 /// The configuration currently present in the users settings.
721 setting_configuration: bool,
722 /// Whether to override the currently set visibility from the users setting.
723 toggle_override: bool,
724 },
725}
726
727impl MinimapVisibility {
728 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
729 if mode.is_full() {
730 Self::Enabled {
731 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
732 toggle_override: false,
733 }
734 } else {
735 Self::Disabled
736 }
737 }
738
739 fn hidden(&self) -> Self {
740 match *self {
741 Self::Enabled {
742 setting_configuration,
743 ..
744 } => Self::Enabled {
745 setting_configuration,
746 toggle_override: setting_configuration,
747 },
748 Self::Disabled => Self::Disabled,
749 }
750 }
751
752 fn disabled(&self) -> bool {
753 match *self {
754 Self::Disabled => true,
755 _ => false,
756 }
757 }
758
759 fn settings_visibility(&self) -> bool {
760 match *self {
761 Self::Enabled {
762 setting_configuration,
763 ..
764 } => setting_configuration,
765 _ => false,
766 }
767 }
768
769 fn visible(&self) -> bool {
770 match *self {
771 Self::Enabled {
772 setting_configuration,
773 toggle_override,
774 } => setting_configuration ^ toggle_override,
775 _ => false,
776 }
777 }
778
779 fn toggle_visibility(&self) -> Self {
780 match *self {
781 Self::Enabled {
782 toggle_override,
783 setting_configuration,
784 } => Self::Enabled {
785 setting_configuration,
786 toggle_override: !toggle_override,
787 },
788 Self::Disabled => Self::Disabled,
789 }
790 }
791}
792
793#[derive(Clone, Debug)]
794struct RunnableTasks {
795 templates: Vec<(TaskSourceKind, TaskTemplate)>,
796 offset: multi_buffer::Anchor,
797 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
798 column: u32,
799 // Values of all named captures, including those starting with '_'
800 extra_variables: HashMap<String, String>,
801 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
802 context_range: Range<BufferOffset>,
803}
804
805impl RunnableTasks {
806 fn resolve<'a>(
807 &'a self,
808 cx: &'a task::TaskContext,
809 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
810 self.templates.iter().filter_map(|(kind, template)| {
811 template
812 .resolve_task(&kind.to_id_base(), cx)
813 .map(|task| (kind.clone(), task))
814 })
815 }
816}
817
818#[derive(Clone)]
819pub struct ResolvedTasks {
820 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
821 position: Anchor,
822}
823
824#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
825struct BufferOffset(usize);
826
827// Addons allow storing per-editor state in other crates (e.g. Vim)
828pub trait Addon: 'static {
829 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
830
831 fn render_buffer_header_controls(
832 &self,
833 _: &ExcerptInfo,
834 _: &Window,
835 _: &App,
836 ) -> Option<AnyElement> {
837 None
838 }
839
840 fn to_any(&self) -> &dyn std::any::Any;
841
842 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
843 None
844 }
845}
846
847/// A set of caret positions, registered when the editor was edited.
848pub struct ChangeList {
849 changes: Vec<Vec<Anchor>>,
850 /// Currently "selected" change.
851 position: Option<usize>,
852}
853
854impl ChangeList {
855 pub fn new() -> Self {
856 Self {
857 changes: Vec::new(),
858 position: None,
859 }
860 }
861
862 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
863 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
864 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
865 if self.changes.is_empty() {
866 return None;
867 }
868
869 let prev = self.position.unwrap_or(self.changes.len());
870 let next = if direction == Direction::Prev {
871 prev.saturating_sub(count)
872 } else {
873 (prev + count).min(self.changes.len() - 1)
874 };
875 self.position = Some(next);
876 self.changes.get(next).map(|anchors| anchors.as_slice())
877 }
878
879 /// Adds a new change to the list, resetting the change list position.
880 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
881 self.position.take();
882 if pop_state {
883 self.changes.pop();
884 }
885 self.changes.push(new_positions.clone());
886 }
887
888 pub fn last(&self) -> Option<&[Anchor]> {
889 self.changes.last().map(|anchors| anchors.as_slice())
890 }
891}
892
893#[derive(Clone)]
894struct InlineBlamePopoverState {
895 scroll_handle: ScrollHandle,
896 commit_message: Option<ParsedCommitMessage>,
897 markdown: Entity<Markdown>,
898}
899
900struct InlineBlamePopover {
901 position: gpui::Point<Pixels>,
902 show_task: Option<Task<()>>,
903 hide_task: Option<Task<()>>,
904 popover_bounds: Option<Bounds<Pixels>>,
905 popover_state: InlineBlamePopoverState,
906}
907
908/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
909/// a breakpoint on them.
910#[derive(Clone, Copy, Debug)]
911struct PhantomBreakpointIndicator {
912 display_row: DisplayRow,
913 /// There's a small debounce between hovering over the line and showing the indicator.
914 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
915 is_active: bool,
916 collides_with_existing_breakpoint: bool,
917}
918/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
919///
920/// See the [module level documentation](self) for more information.
921pub struct Editor {
922 focus_handle: FocusHandle,
923 last_focused_descendant: Option<WeakFocusHandle>,
924 /// The text buffer being edited
925 buffer: Entity<MultiBuffer>,
926 /// Map of how text in the buffer should be displayed.
927 /// Handles soft wraps, folds, fake inlay text insertions, etc.
928 pub display_map: Entity<DisplayMap>,
929 pub selections: SelectionsCollection,
930 pub scroll_manager: ScrollManager,
931 /// When inline assist editors are linked, they all render cursors because
932 /// typing enters text into each of them, even the ones that aren't focused.
933 pub(crate) show_cursor_when_unfocused: bool,
934 columnar_selection_tail: Option<Anchor>,
935 add_selections_state: Option<AddSelectionsState>,
936 select_next_state: Option<SelectNextState>,
937 select_prev_state: Option<SelectNextState>,
938 selection_history: SelectionHistory,
939 defer_selection_effects: bool,
940 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
941 autoclose_regions: Vec<AutocloseRegion>,
942 snippet_stack: InvalidationStack<SnippetState>,
943 select_syntax_node_history: SelectSyntaxNodeHistory,
944 ime_transaction: Option<TransactionId>,
945 pub diagnostics_max_severity: DiagnosticSeverity,
946 active_diagnostics: ActiveDiagnostic,
947 show_inline_diagnostics: bool,
948 inline_diagnostics_update: Task<()>,
949 inline_diagnostics_enabled: bool,
950 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
951 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
952 hard_wrap: Option<usize>,
953
954 // TODO: make this a access method
955 pub project: Option<Entity<Project>>,
956 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
957 completion_provider: Option<Rc<dyn CompletionProvider>>,
958 collaboration_hub: Option<Box<dyn CollaborationHub>>,
959 blink_manager: Entity<BlinkManager>,
960 show_cursor_names: bool,
961 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
962 pub show_local_selections: bool,
963 mode: EditorMode,
964 show_breadcrumbs: bool,
965 show_gutter: bool,
966 show_scrollbars: ScrollbarAxes,
967 minimap_visibility: MinimapVisibility,
968 offset_content: bool,
969 disable_expand_excerpt_buttons: bool,
970 show_line_numbers: Option<bool>,
971 use_relative_line_numbers: Option<bool>,
972 show_git_diff_gutter: Option<bool>,
973 show_code_actions: Option<bool>,
974 show_runnables: Option<bool>,
975 show_breakpoints: Option<bool>,
976 show_wrap_guides: Option<bool>,
977 show_indent_guides: Option<bool>,
978 placeholder_text: Option<Arc<str>>,
979 highlight_order: usize,
980 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
981 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
982 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
983 scrollbar_marker_state: ScrollbarMarkerState,
984 active_indent_guides_state: ActiveIndentGuidesState,
985 nav_history: Option<ItemNavHistory>,
986 context_menu: RefCell<Option<CodeContextMenu>>,
987 context_menu_options: Option<ContextMenuOptions>,
988 mouse_context_menu: Option<MouseContextMenu>,
989 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
990 inline_blame_popover: Option<InlineBlamePopover>,
991 signature_help_state: SignatureHelpState,
992 auto_signature_help: Option<bool>,
993 find_all_references_task_sources: Vec<Anchor>,
994 next_completion_id: CompletionId,
995 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
996 code_actions_task: Option<Task<Result<()>>>,
997 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
998 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
999 document_highlights_task: Option<Task<()>>,
1000 linked_editing_range_task: Option<Task<Option<()>>>,
1001 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1002 pending_rename: Option<RenameState>,
1003 searchable: bool,
1004 cursor_shape: CursorShape,
1005 current_line_highlight: Option<CurrentLineHighlight>,
1006 collapse_matches: bool,
1007 autoindent_mode: Option<AutoindentMode>,
1008 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1009 input_enabled: bool,
1010 use_modal_editing: bool,
1011 read_only: bool,
1012 leader_id: Option<CollaboratorId>,
1013 remote_id: Option<ViewId>,
1014 pub hover_state: HoverState,
1015 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1016 gutter_hovered: bool,
1017 hovered_link_state: Option<HoveredLinkState>,
1018 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1019 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1020 active_inline_completion: Option<InlineCompletionState>,
1021 /// Used to prevent flickering as the user types while the menu is open
1022 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1023 edit_prediction_settings: EditPredictionSettings,
1024 inline_completions_hidden_for_vim_mode: bool,
1025 show_inline_completions_override: Option<bool>,
1026 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1027 edit_prediction_preview: EditPredictionPreview,
1028 edit_prediction_indent_conflict: bool,
1029 edit_prediction_requires_modifier_in_indent_conflict: bool,
1030 inlay_hint_cache: InlayHintCache,
1031 next_inlay_id: usize,
1032 _subscriptions: Vec<Subscription>,
1033 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1034 gutter_dimensions: GutterDimensions,
1035 style: Option<EditorStyle>,
1036 text_style_refinement: Option<TextStyleRefinement>,
1037 next_editor_action_id: EditorActionId,
1038 editor_actions:
1039 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
1040 use_autoclose: bool,
1041 use_auto_surround: bool,
1042 auto_replace_emoji_shortcode: bool,
1043 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1044 show_git_blame_gutter: bool,
1045 show_git_blame_inline: bool,
1046 show_git_blame_inline_delay_task: Option<Task<()>>,
1047 git_blame_inline_enabled: bool,
1048 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1049 serialize_dirty_buffers: bool,
1050 show_selection_menu: Option<bool>,
1051 blame: Option<Entity<GitBlame>>,
1052 blame_subscription: Option<Subscription>,
1053 custom_context_menu: Option<
1054 Box<
1055 dyn 'static
1056 + Fn(
1057 &mut Self,
1058 DisplayPoint,
1059 &mut Window,
1060 &mut Context<Self>,
1061 ) -> Option<Entity<ui::ContextMenu>>,
1062 >,
1063 >,
1064 last_bounds: Option<Bounds<Pixels>>,
1065 last_position_map: Option<Rc<PositionMap>>,
1066 expect_bounds_change: Option<Bounds<Pixels>>,
1067 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1068 tasks_update_task: Option<Task<()>>,
1069 breakpoint_store: Option<Entity<BreakpointStore>>,
1070 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1071 in_project_search: bool,
1072 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1073 breadcrumb_header: Option<String>,
1074 focused_block: Option<FocusedBlock>,
1075 next_scroll_position: NextScrollCursorCenterTopBottom,
1076 addons: HashMap<TypeId, Box<dyn Addon>>,
1077 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1078 load_diff_task: Option<Shared<Task<()>>>,
1079 /// Whether we are temporarily displaying a diff other than git's
1080 temporary_diff_override: bool,
1081 selection_mark_mode: bool,
1082 toggle_fold_multiple_buffers: Task<()>,
1083 _scroll_cursor_center_top_bottom_task: Task<()>,
1084 serialize_selections: Task<()>,
1085 serialize_folds: Task<()>,
1086 mouse_cursor_hidden: bool,
1087 minimap: Option<Entity<Self>>,
1088 hide_mouse_mode: HideMouseMode,
1089 pub change_list: ChangeList,
1090 inline_value_cache: InlineValueCache,
1091}
1092
1093#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1094enum NextScrollCursorCenterTopBottom {
1095 #[default]
1096 Center,
1097 Top,
1098 Bottom,
1099}
1100
1101impl NextScrollCursorCenterTopBottom {
1102 fn next(&self) -> Self {
1103 match self {
1104 Self::Center => Self::Top,
1105 Self::Top => Self::Bottom,
1106 Self::Bottom => Self::Center,
1107 }
1108 }
1109}
1110
1111#[derive(Clone)]
1112pub struct EditorSnapshot {
1113 pub mode: EditorMode,
1114 show_gutter: bool,
1115 show_line_numbers: Option<bool>,
1116 show_git_diff_gutter: Option<bool>,
1117 show_code_actions: Option<bool>,
1118 show_runnables: Option<bool>,
1119 show_breakpoints: Option<bool>,
1120 git_blame_gutter_max_author_length: Option<usize>,
1121 pub display_snapshot: DisplaySnapshot,
1122 pub placeholder_text: Option<Arc<str>>,
1123 is_focused: bool,
1124 scroll_anchor: ScrollAnchor,
1125 ongoing_scroll: OngoingScroll,
1126 current_line_highlight: CurrentLineHighlight,
1127 gutter_hovered: bool,
1128}
1129
1130#[derive(Default, Debug, Clone, Copy)]
1131pub struct GutterDimensions {
1132 pub left_padding: Pixels,
1133 pub right_padding: Pixels,
1134 pub width: Pixels,
1135 pub margin: Pixels,
1136 pub git_blame_entries_width: Option<Pixels>,
1137}
1138
1139impl GutterDimensions {
1140 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1141 Self {
1142 margin: Self::default_gutter_margin(font_id, font_size, cx),
1143 ..Default::default()
1144 }
1145 }
1146
1147 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1148 -cx.text_system().descent(font_id, font_size)
1149 }
1150 /// The full width of the space taken up by the gutter.
1151 pub fn full_width(&self) -> Pixels {
1152 self.margin + self.width
1153 }
1154
1155 /// The width of the space reserved for the fold indicators,
1156 /// use alongside 'justify_end' and `gutter_width` to
1157 /// right align content with the line numbers
1158 pub fn fold_area_width(&self) -> Pixels {
1159 self.margin + self.right_padding
1160 }
1161}
1162
1163#[derive(Debug)]
1164pub struct RemoteSelection {
1165 pub replica_id: ReplicaId,
1166 pub selection: Selection<Anchor>,
1167 pub cursor_shape: CursorShape,
1168 pub collaborator_id: CollaboratorId,
1169 pub line_mode: bool,
1170 pub user_name: Option<SharedString>,
1171 pub color: PlayerColor,
1172}
1173
1174#[derive(Clone, Debug)]
1175struct SelectionHistoryEntry {
1176 selections: Arc<[Selection<Anchor>]>,
1177 select_next_state: Option<SelectNextState>,
1178 select_prev_state: Option<SelectNextState>,
1179 add_selections_state: Option<AddSelectionsState>,
1180}
1181
1182enum SelectionHistoryMode {
1183 Normal,
1184 Undoing,
1185 Redoing,
1186}
1187
1188#[derive(Clone, PartialEq, Eq, Hash)]
1189struct HoveredCursor {
1190 replica_id: u16,
1191 selection_id: usize,
1192}
1193
1194impl Default for SelectionHistoryMode {
1195 fn default() -> Self {
1196 Self::Normal
1197 }
1198}
1199
1200struct DeferredSelectionEffectsState {
1201 changed: bool,
1202 show_completions: bool,
1203 autoscroll: Option<Autoscroll>,
1204 old_cursor_position: Anchor,
1205 history_entry: SelectionHistoryEntry,
1206}
1207
1208#[derive(Default)]
1209struct SelectionHistory {
1210 #[allow(clippy::type_complexity)]
1211 selections_by_transaction:
1212 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1213 mode: SelectionHistoryMode,
1214 undo_stack: VecDeque<SelectionHistoryEntry>,
1215 redo_stack: VecDeque<SelectionHistoryEntry>,
1216}
1217
1218impl SelectionHistory {
1219 fn insert_transaction(
1220 &mut self,
1221 transaction_id: TransactionId,
1222 selections: Arc<[Selection<Anchor>]>,
1223 ) {
1224 self.selections_by_transaction
1225 .insert(transaction_id, (selections, None));
1226 }
1227
1228 #[allow(clippy::type_complexity)]
1229 fn transaction(
1230 &self,
1231 transaction_id: TransactionId,
1232 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1233 self.selections_by_transaction.get(&transaction_id)
1234 }
1235
1236 #[allow(clippy::type_complexity)]
1237 fn transaction_mut(
1238 &mut self,
1239 transaction_id: TransactionId,
1240 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1241 self.selections_by_transaction.get_mut(&transaction_id)
1242 }
1243
1244 fn push(&mut self, entry: SelectionHistoryEntry) {
1245 if !entry.selections.is_empty() {
1246 match self.mode {
1247 SelectionHistoryMode::Normal => {
1248 self.push_undo(entry);
1249 self.redo_stack.clear();
1250 }
1251 SelectionHistoryMode::Undoing => self.push_redo(entry),
1252 SelectionHistoryMode::Redoing => self.push_undo(entry),
1253 }
1254 }
1255 }
1256
1257 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1258 if self
1259 .undo_stack
1260 .back()
1261 .map_or(true, |e| e.selections != entry.selections)
1262 {
1263 self.undo_stack.push_back(entry);
1264 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1265 self.undo_stack.pop_front();
1266 }
1267 }
1268 }
1269
1270 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1271 if self
1272 .redo_stack
1273 .back()
1274 .map_or(true, |e| e.selections != entry.selections)
1275 {
1276 self.redo_stack.push_back(entry);
1277 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1278 self.redo_stack.pop_front();
1279 }
1280 }
1281 }
1282}
1283
1284#[derive(Clone, Copy)]
1285pub struct RowHighlightOptions {
1286 pub autoscroll: bool,
1287 pub include_gutter: bool,
1288}
1289
1290impl Default for RowHighlightOptions {
1291 fn default() -> Self {
1292 Self {
1293 autoscroll: Default::default(),
1294 include_gutter: true,
1295 }
1296 }
1297}
1298
1299struct RowHighlight {
1300 index: usize,
1301 range: Range<Anchor>,
1302 color: Hsla,
1303 options: RowHighlightOptions,
1304 type_id: TypeId,
1305}
1306
1307#[derive(Clone, Debug)]
1308struct AddSelectionsState {
1309 above: bool,
1310 stack: Vec<usize>,
1311}
1312
1313#[derive(Clone)]
1314struct SelectNextState {
1315 query: AhoCorasick,
1316 wordwise: bool,
1317 done: bool,
1318}
1319
1320impl std::fmt::Debug for SelectNextState {
1321 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1322 f.debug_struct(std::any::type_name::<Self>())
1323 .field("wordwise", &self.wordwise)
1324 .field("done", &self.done)
1325 .finish()
1326 }
1327}
1328
1329#[derive(Debug)]
1330struct AutocloseRegion {
1331 selection_id: usize,
1332 range: Range<Anchor>,
1333 pair: BracketPair,
1334}
1335
1336#[derive(Debug)]
1337struct SnippetState {
1338 ranges: Vec<Vec<Range<Anchor>>>,
1339 active_index: usize,
1340 choices: Vec<Option<Vec<String>>>,
1341}
1342
1343#[doc(hidden)]
1344pub struct RenameState {
1345 pub range: Range<Anchor>,
1346 pub old_name: Arc<str>,
1347 pub editor: Entity<Editor>,
1348 block_id: CustomBlockId,
1349}
1350
1351struct InvalidationStack<T>(Vec<T>);
1352
1353struct RegisteredInlineCompletionProvider {
1354 provider: Arc<dyn InlineCompletionProviderHandle>,
1355 _subscription: Subscription,
1356}
1357
1358#[derive(Debug, PartialEq, Eq)]
1359pub struct ActiveDiagnosticGroup {
1360 pub active_range: Range<Anchor>,
1361 pub active_message: String,
1362 pub group_id: usize,
1363 pub blocks: HashSet<CustomBlockId>,
1364}
1365
1366#[derive(Debug, PartialEq, Eq)]
1367
1368pub(crate) enum ActiveDiagnostic {
1369 None,
1370 All,
1371 Group(ActiveDiagnosticGroup),
1372}
1373
1374#[derive(Serialize, Deserialize, Clone, Debug)]
1375pub struct ClipboardSelection {
1376 /// The number of bytes in this selection.
1377 pub len: usize,
1378 /// Whether this was a full-line selection.
1379 pub is_entire_line: bool,
1380 /// The indentation of the first line when this content was originally copied.
1381 pub first_line_indent: u32,
1382}
1383
1384// selections, scroll behavior, was newest selection reversed
1385type SelectSyntaxNodeHistoryState = (
1386 Box<[Selection<usize>]>,
1387 SelectSyntaxNodeScrollBehavior,
1388 bool,
1389);
1390
1391#[derive(Default)]
1392struct SelectSyntaxNodeHistory {
1393 stack: Vec<SelectSyntaxNodeHistoryState>,
1394 // disable temporarily to allow changing selections without losing the stack
1395 pub disable_clearing: bool,
1396}
1397
1398impl SelectSyntaxNodeHistory {
1399 pub fn try_clear(&mut self) {
1400 if !self.disable_clearing {
1401 self.stack.clear();
1402 }
1403 }
1404
1405 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1406 self.stack.push(selection);
1407 }
1408
1409 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1410 self.stack.pop()
1411 }
1412}
1413
1414enum SelectSyntaxNodeScrollBehavior {
1415 CursorTop,
1416 FitSelection,
1417 CursorBottom,
1418}
1419
1420#[derive(Debug)]
1421pub(crate) struct NavigationData {
1422 cursor_anchor: Anchor,
1423 cursor_position: Point,
1424 scroll_anchor: ScrollAnchor,
1425 scroll_top_row: u32,
1426}
1427
1428#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1429pub enum GotoDefinitionKind {
1430 Symbol,
1431 Declaration,
1432 Type,
1433 Implementation,
1434}
1435
1436#[derive(Debug, Clone)]
1437enum InlayHintRefreshReason {
1438 ModifiersChanged(bool),
1439 Toggle(bool),
1440 SettingsChange(InlayHintSettings),
1441 NewLinesShown,
1442 BufferEdited(HashSet<Arc<Language>>),
1443 RefreshRequested,
1444 ExcerptsRemoved(Vec<ExcerptId>),
1445}
1446
1447impl InlayHintRefreshReason {
1448 fn description(&self) -> &'static str {
1449 match self {
1450 Self::ModifiersChanged(_) => "modifiers changed",
1451 Self::Toggle(_) => "toggle",
1452 Self::SettingsChange(_) => "settings change",
1453 Self::NewLinesShown => "new lines shown",
1454 Self::BufferEdited(_) => "buffer edited",
1455 Self::RefreshRequested => "refresh requested",
1456 Self::ExcerptsRemoved(_) => "excerpts removed",
1457 }
1458 }
1459}
1460
1461pub enum FormatTarget {
1462 Buffers,
1463 Ranges(Vec<Range<MultiBufferPoint>>),
1464}
1465
1466pub(crate) struct FocusedBlock {
1467 id: BlockId,
1468 focus_handle: WeakFocusHandle,
1469}
1470
1471#[derive(Clone)]
1472enum JumpData {
1473 MultiBufferRow {
1474 row: MultiBufferRow,
1475 line_offset_from_top: u32,
1476 },
1477 MultiBufferPoint {
1478 excerpt_id: ExcerptId,
1479 position: Point,
1480 anchor: text::Anchor,
1481 line_offset_from_top: u32,
1482 },
1483}
1484
1485pub enum MultibufferSelectionMode {
1486 First,
1487 All,
1488}
1489
1490#[derive(Clone, Copy, Debug, Default)]
1491pub struct RewrapOptions {
1492 pub override_language_settings: bool,
1493 pub preserve_existing_whitespace: bool,
1494}
1495
1496impl Editor {
1497 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1498 let buffer = cx.new(|cx| Buffer::local("", cx));
1499 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1500 Self::new(
1501 EditorMode::SingleLine { auto_width: false },
1502 buffer,
1503 None,
1504 window,
1505 cx,
1506 )
1507 }
1508
1509 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1510 let buffer = cx.new(|cx| Buffer::local("", cx));
1511 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1512 Self::new(EditorMode::full(), buffer, None, window, cx)
1513 }
1514
1515 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1516 let buffer = cx.new(|cx| Buffer::local("", cx));
1517 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1518 Self::new(
1519 EditorMode::SingleLine { auto_width: true },
1520 buffer,
1521 None,
1522 window,
1523 cx,
1524 )
1525 }
1526
1527 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1528 let buffer = cx.new(|cx| Buffer::local("", cx));
1529 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1530 Self::new(
1531 EditorMode::AutoHeight { max_lines },
1532 buffer,
1533 None,
1534 window,
1535 cx,
1536 )
1537 }
1538
1539 pub fn for_buffer(
1540 buffer: Entity<Buffer>,
1541 project: Option<Entity<Project>>,
1542 window: &mut Window,
1543 cx: &mut Context<Self>,
1544 ) -> Self {
1545 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1546 Self::new(EditorMode::full(), buffer, project, window, cx)
1547 }
1548
1549 pub fn for_multibuffer(
1550 buffer: Entity<MultiBuffer>,
1551 project: Option<Entity<Project>>,
1552 window: &mut Window,
1553 cx: &mut Context<Self>,
1554 ) -> Self {
1555 Self::new(EditorMode::full(), buffer, project, window, cx)
1556 }
1557
1558 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1559 let mut clone = Self::new(
1560 self.mode.clone(),
1561 self.buffer.clone(),
1562 self.project.clone(),
1563 window,
1564 cx,
1565 );
1566 self.display_map.update(cx, |display_map, cx| {
1567 let snapshot = display_map.snapshot(cx);
1568 clone.display_map.update(cx, |display_map, cx| {
1569 display_map.set_state(&snapshot, cx);
1570 });
1571 });
1572 clone.folds_did_change(cx);
1573 clone.selections.clone_state(&self.selections);
1574 clone.scroll_manager.clone_state(&self.scroll_manager);
1575 clone.searchable = self.searchable;
1576 clone.read_only = self.read_only;
1577 clone
1578 }
1579
1580 pub fn new(
1581 mode: EditorMode,
1582 buffer: Entity<MultiBuffer>,
1583 project: Option<Entity<Project>>,
1584 window: &mut Window,
1585 cx: &mut Context<Self>,
1586 ) -> Self {
1587 Editor::new_internal(mode, buffer, project, None, window, cx)
1588 }
1589
1590 fn new_internal(
1591 mode: EditorMode,
1592 buffer: Entity<MultiBuffer>,
1593 project: Option<Entity<Project>>,
1594 display_map: Option<Entity<DisplayMap>>,
1595 window: &mut Window,
1596 cx: &mut Context<Self>,
1597 ) -> Self {
1598 debug_assert!(
1599 display_map.is_none() || mode.is_minimap(),
1600 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1601 );
1602
1603 let full_mode = mode.is_full();
1604 let diagnostics_max_severity = if full_mode {
1605 EditorSettings::get_global(cx)
1606 .diagnostics_max_severity
1607 .unwrap_or(DiagnosticSeverity::Hint)
1608 } else {
1609 DiagnosticSeverity::Off
1610 };
1611 let style = window.text_style();
1612 let font_size = style.font_size.to_pixels(window.rem_size());
1613 let editor = cx.entity().downgrade();
1614 let fold_placeholder = FoldPlaceholder {
1615 constrain_width: true,
1616 render: Arc::new(move |fold_id, fold_range, cx| {
1617 let editor = editor.clone();
1618 div()
1619 .id(fold_id)
1620 .bg(cx.theme().colors().ghost_element_background)
1621 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1622 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1623 .rounded_xs()
1624 .size_full()
1625 .cursor_pointer()
1626 .child("⋯")
1627 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1628 .on_click(move |_, _window, cx| {
1629 editor
1630 .update(cx, |editor, cx| {
1631 editor.unfold_ranges(
1632 &[fold_range.start..fold_range.end],
1633 true,
1634 false,
1635 cx,
1636 );
1637 cx.stop_propagation();
1638 })
1639 .ok();
1640 })
1641 .into_any()
1642 }),
1643 merge_adjacent: true,
1644 ..FoldPlaceholder::default()
1645 };
1646 let display_map = display_map.unwrap_or_else(|| {
1647 cx.new(|cx| {
1648 DisplayMap::new(
1649 buffer.clone(),
1650 style.font(),
1651 font_size,
1652 None,
1653 FILE_HEADER_HEIGHT,
1654 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1655 fold_placeholder,
1656 diagnostics_max_severity,
1657 cx,
1658 )
1659 })
1660 });
1661
1662 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1663
1664 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1665
1666 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1667 .then(|| language_settings::SoftWrap::None);
1668
1669 let mut project_subscriptions = Vec::new();
1670 if mode.is_full() {
1671 if let Some(project) = project.as_ref() {
1672 project_subscriptions.push(cx.subscribe_in(
1673 project,
1674 window,
1675 |editor, _, event, window, cx| match event {
1676 project::Event::RefreshCodeLens => {
1677 // we always query lens with actions, without storing them, always refreshing them
1678 }
1679 project::Event::RefreshInlayHints => {
1680 editor
1681 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1682 }
1683 project::Event::LanguageServerAdded(..)
1684 | project::Event::LanguageServerRemoved(..) => {
1685 if editor.tasks_update_task.is_none() {
1686 editor.tasks_update_task =
1687 Some(editor.refresh_runnables(window, cx));
1688 }
1689 }
1690 project::Event::SnippetEdit(id, snippet_edits) => {
1691 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1692 let focus_handle = editor.focus_handle(cx);
1693 if focus_handle.is_focused(window) {
1694 let snapshot = buffer.read(cx).snapshot();
1695 for (range, snippet) in snippet_edits {
1696 let editor_range =
1697 language::range_from_lsp(*range).to_offset(&snapshot);
1698 editor
1699 .insert_snippet(
1700 &[editor_range],
1701 snippet.clone(),
1702 window,
1703 cx,
1704 )
1705 .ok();
1706 }
1707 }
1708 }
1709 }
1710 _ => {}
1711 },
1712 ));
1713 if let Some(task_inventory) = project
1714 .read(cx)
1715 .task_store()
1716 .read(cx)
1717 .task_inventory()
1718 .cloned()
1719 {
1720 project_subscriptions.push(cx.observe_in(
1721 &task_inventory,
1722 window,
1723 |editor, _, window, cx| {
1724 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1725 },
1726 ));
1727 };
1728
1729 project_subscriptions.push(cx.subscribe_in(
1730 &project.read(cx).breakpoint_store(),
1731 window,
1732 |editor, _, event, window, cx| match event {
1733 BreakpointStoreEvent::ClearDebugLines => {
1734 editor.clear_row_highlights::<ActiveDebugLine>();
1735 editor.refresh_inline_values(cx);
1736 }
1737 BreakpointStoreEvent::SetDebugLine => {
1738 if editor.go_to_active_debug_line(window, cx) {
1739 cx.stop_propagation();
1740 }
1741
1742 editor.refresh_inline_values(cx);
1743 }
1744 _ => {}
1745 },
1746 ));
1747 }
1748 }
1749
1750 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1751
1752 let inlay_hint_settings =
1753 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1754 let focus_handle = cx.focus_handle();
1755 cx.on_focus(&focus_handle, window, Self::handle_focus)
1756 .detach();
1757 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1758 .detach();
1759 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1760 .detach();
1761 cx.on_blur(&focus_handle, window, Self::handle_blur)
1762 .detach();
1763
1764 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1765 Some(false)
1766 } else {
1767 None
1768 };
1769
1770 let breakpoint_store = match (&mode, project.as_ref()) {
1771 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1772 _ => None,
1773 };
1774
1775 let mut code_action_providers = Vec::new();
1776 let mut load_uncommitted_diff = None;
1777 if let Some(project) = project.clone() {
1778 load_uncommitted_diff = Some(
1779 update_uncommitted_diff_for_buffer(
1780 cx.entity(),
1781 &project,
1782 buffer.read(cx).all_buffers(),
1783 buffer.clone(),
1784 cx,
1785 )
1786 .shared(),
1787 );
1788 code_action_providers.push(Rc::new(project) as Rc<_>);
1789 }
1790
1791 let mut this = Self {
1792 focus_handle,
1793 show_cursor_when_unfocused: false,
1794 last_focused_descendant: None,
1795 buffer: buffer.clone(),
1796 display_map: display_map.clone(),
1797 selections,
1798 scroll_manager: ScrollManager::new(cx),
1799 columnar_selection_tail: None,
1800 add_selections_state: None,
1801 select_next_state: None,
1802 select_prev_state: None,
1803 selection_history: SelectionHistory::default(),
1804 defer_selection_effects: false,
1805 deferred_selection_effects_state: None,
1806 autoclose_regions: Vec::new(),
1807 snippet_stack: InvalidationStack::default(),
1808 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1809 ime_transaction: None,
1810 active_diagnostics: ActiveDiagnostic::None,
1811 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1812 inline_diagnostics_update: Task::ready(()),
1813 inline_diagnostics: Vec::new(),
1814 soft_wrap_mode_override,
1815 diagnostics_max_severity,
1816 hard_wrap: None,
1817 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1818 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1819 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1820 project,
1821 blink_manager: blink_manager.clone(),
1822 show_local_selections: true,
1823 show_scrollbars: ScrollbarAxes {
1824 horizontal: full_mode,
1825 vertical: full_mode,
1826 },
1827 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1828 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1829 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1830 show_gutter: mode.is_full(),
1831 show_line_numbers: None,
1832 use_relative_line_numbers: None,
1833 disable_expand_excerpt_buttons: false,
1834 show_git_diff_gutter: None,
1835 show_code_actions: None,
1836 show_runnables: None,
1837 show_breakpoints: None,
1838 show_wrap_guides: None,
1839 show_indent_guides,
1840 placeholder_text: None,
1841 highlight_order: 0,
1842 highlighted_rows: HashMap::default(),
1843 background_highlights: TreeMap::default(),
1844 gutter_highlights: TreeMap::default(),
1845 scrollbar_marker_state: ScrollbarMarkerState::default(),
1846 active_indent_guides_state: ActiveIndentGuidesState::default(),
1847 nav_history: None,
1848 context_menu: RefCell::new(None),
1849 context_menu_options: None,
1850 mouse_context_menu: None,
1851 completion_tasks: Vec::new(),
1852 inline_blame_popover: None,
1853 signature_help_state: SignatureHelpState::default(),
1854 auto_signature_help: None,
1855 find_all_references_task_sources: Vec::new(),
1856 next_completion_id: 0,
1857 next_inlay_id: 0,
1858 code_action_providers,
1859 available_code_actions: None,
1860 code_actions_task: None,
1861 quick_selection_highlight_task: None,
1862 debounced_selection_highlight_task: None,
1863 document_highlights_task: None,
1864 linked_editing_range_task: None,
1865 pending_rename: None,
1866 searchable: true,
1867 cursor_shape: EditorSettings::get_global(cx)
1868 .cursor_shape
1869 .unwrap_or_default(),
1870 current_line_highlight: None,
1871 autoindent_mode: Some(AutoindentMode::EachLine),
1872 collapse_matches: false,
1873 workspace: None,
1874 input_enabled: true,
1875 use_modal_editing: mode.is_full(),
1876 read_only: mode.is_minimap(),
1877 use_autoclose: true,
1878 use_auto_surround: true,
1879 auto_replace_emoji_shortcode: false,
1880 jsx_tag_auto_close_enabled_in_any_buffer: false,
1881 leader_id: None,
1882 remote_id: None,
1883 hover_state: HoverState::default(),
1884 pending_mouse_down: None,
1885 hovered_link_state: None,
1886 edit_prediction_provider: None,
1887 active_inline_completion: None,
1888 stale_inline_completion_in_menu: None,
1889 edit_prediction_preview: EditPredictionPreview::Inactive {
1890 released_too_fast: false,
1891 },
1892 inline_diagnostics_enabled: mode.is_full(),
1893 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1894 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1895
1896 gutter_hovered: false,
1897 pixel_position_of_newest_cursor: None,
1898 last_bounds: None,
1899 last_position_map: None,
1900 expect_bounds_change: None,
1901 gutter_dimensions: GutterDimensions::default(),
1902 style: None,
1903 show_cursor_names: false,
1904 hovered_cursors: HashMap::default(),
1905 next_editor_action_id: EditorActionId::default(),
1906 editor_actions: Rc::default(),
1907 inline_completions_hidden_for_vim_mode: false,
1908 show_inline_completions_override: None,
1909 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1910 edit_prediction_settings: EditPredictionSettings::Disabled,
1911 edit_prediction_indent_conflict: false,
1912 edit_prediction_requires_modifier_in_indent_conflict: true,
1913 custom_context_menu: None,
1914 show_git_blame_gutter: false,
1915 show_git_blame_inline: false,
1916 show_selection_menu: None,
1917 show_git_blame_inline_delay_task: None,
1918 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1919 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1920 serialize_dirty_buffers: !mode.is_minimap()
1921 && ProjectSettings::get_global(cx)
1922 .session
1923 .restore_unsaved_buffers,
1924 blame: None,
1925 blame_subscription: None,
1926 tasks: BTreeMap::default(),
1927
1928 breakpoint_store,
1929 gutter_breakpoint_indicator: (None, None),
1930 _subscriptions: vec![
1931 cx.observe(&buffer, Self::on_buffer_changed),
1932 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1933 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1934 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1935 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1936 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1937 cx.observe_window_activation(window, |editor, window, cx| {
1938 let active = window.is_window_active();
1939 editor.blink_manager.update(cx, |blink_manager, cx| {
1940 if active {
1941 blink_manager.enable(cx);
1942 } else {
1943 blink_manager.disable(cx);
1944 }
1945 });
1946 if active {
1947 editor.show_mouse_cursor();
1948 }
1949 }),
1950 ],
1951 tasks_update_task: None,
1952 linked_edit_ranges: Default::default(),
1953 in_project_search: false,
1954 previous_search_ranges: None,
1955 breadcrumb_header: None,
1956 focused_block: None,
1957 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1958 addons: HashMap::default(),
1959 registered_buffers: HashMap::default(),
1960 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1961 selection_mark_mode: false,
1962 toggle_fold_multiple_buffers: Task::ready(()),
1963 serialize_selections: Task::ready(()),
1964 serialize_folds: Task::ready(()),
1965 text_style_refinement: None,
1966 load_diff_task: load_uncommitted_diff,
1967 temporary_diff_override: false,
1968 mouse_cursor_hidden: false,
1969 minimap: None,
1970 hide_mouse_mode: EditorSettings::get_global(cx)
1971 .hide_mouse
1972 .unwrap_or_default(),
1973 change_list: ChangeList::new(),
1974 mode,
1975 };
1976 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1977 this._subscriptions
1978 .push(cx.observe(breakpoints, |_, _, cx| {
1979 cx.notify();
1980 }));
1981 }
1982 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1983 this._subscriptions.extend(project_subscriptions);
1984
1985 this._subscriptions.push(cx.subscribe_in(
1986 &cx.entity(),
1987 window,
1988 |editor, _, e: &EditorEvent, window, cx| match e {
1989 EditorEvent::ScrollPositionChanged { local, .. } => {
1990 if *local {
1991 let new_anchor = editor.scroll_manager.anchor();
1992 let snapshot = editor.snapshot(window, cx);
1993 editor.update_restoration_data(cx, move |data| {
1994 data.scroll_position = (
1995 new_anchor.top_row(&snapshot.buffer_snapshot),
1996 new_anchor.offset,
1997 );
1998 });
1999 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2000 editor.inline_blame_popover.take();
2001 }
2002 }
2003 EditorEvent::Edited { .. } => {
2004 if !vim_enabled(cx) {
2005 let (map, selections) = editor.selections.all_adjusted_display(cx);
2006 let pop_state = editor
2007 .change_list
2008 .last()
2009 .map(|previous| {
2010 previous.len() == selections.len()
2011 && previous.iter().enumerate().all(|(ix, p)| {
2012 p.to_display_point(&map).row()
2013 == selections[ix].head().row()
2014 })
2015 })
2016 .unwrap_or(false);
2017 let new_positions = selections
2018 .into_iter()
2019 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2020 .collect();
2021 editor
2022 .change_list
2023 .push_to_change_list(pop_state, new_positions);
2024 }
2025 }
2026 _ => (),
2027 },
2028 ));
2029
2030 if let Some(dap_store) = this
2031 .project
2032 .as_ref()
2033 .map(|project| project.read(cx).dap_store())
2034 {
2035 let weak_editor = cx.weak_entity();
2036
2037 this._subscriptions
2038 .push(
2039 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2040 let session_entity = cx.entity();
2041 weak_editor
2042 .update(cx, |editor, cx| {
2043 editor._subscriptions.push(
2044 cx.subscribe(&session_entity, Self::on_debug_session_event),
2045 );
2046 })
2047 .ok();
2048 }),
2049 );
2050
2051 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2052 this._subscriptions
2053 .push(cx.subscribe(&session, Self::on_debug_session_event));
2054 }
2055 }
2056
2057 this.end_selection(window, cx);
2058 this.scroll_manager.show_scrollbars(window, cx);
2059 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
2060
2061 if full_mode {
2062 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2063 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2064
2065 if this.git_blame_inline_enabled {
2066 this.start_git_blame_inline(false, window, cx);
2067 }
2068
2069 this.go_to_active_debug_line(window, cx);
2070
2071 if let Some(buffer) = buffer.read(cx).as_singleton() {
2072 if let Some(project) = this.project.as_ref() {
2073 let handle = project.update(cx, |project, cx| {
2074 project.register_buffer_with_language_servers(&buffer, cx)
2075 });
2076 this.registered_buffers
2077 .insert(buffer.read(cx).remote_id(), handle);
2078 }
2079 }
2080
2081 this.minimap = this.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2082 }
2083
2084 this.report_editor_event("Editor Opened", None, cx);
2085 this
2086 }
2087
2088 pub fn deploy_mouse_context_menu(
2089 &mut self,
2090 position: gpui::Point<Pixels>,
2091 context_menu: Entity<ContextMenu>,
2092 window: &mut Window,
2093 cx: &mut Context<Self>,
2094 ) {
2095 self.mouse_context_menu = Some(MouseContextMenu::new(
2096 self,
2097 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2098 context_menu,
2099 window,
2100 cx,
2101 ));
2102 }
2103
2104 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2105 self.mouse_context_menu
2106 .as_ref()
2107 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2108 }
2109
2110 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2111 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2112 }
2113
2114 fn key_context_internal(
2115 &self,
2116 has_active_edit_prediction: bool,
2117 window: &Window,
2118 cx: &App,
2119 ) -> KeyContext {
2120 let mut key_context = KeyContext::new_with_defaults();
2121 key_context.add("Editor");
2122 let mode = match self.mode {
2123 EditorMode::SingleLine { .. } => "single_line",
2124 EditorMode::AutoHeight { .. } => "auto_height",
2125 EditorMode::Minimap { .. } => "minimap",
2126 EditorMode::Full { .. } => "full",
2127 };
2128
2129 if EditorSettings::jupyter_enabled(cx) {
2130 key_context.add("jupyter");
2131 }
2132
2133 key_context.set("mode", mode);
2134 if self.pending_rename.is_some() {
2135 key_context.add("renaming");
2136 }
2137
2138 match self.context_menu.borrow().as_ref() {
2139 Some(CodeContextMenu::Completions(_)) => {
2140 key_context.add("menu");
2141 key_context.add("showing_completions");
2142 }
2143 Some(CodeContextMenu::CodeActions(_)) => {
2144 key_context.add("menu");
2145 key_context.add("showing_code_actions")
2146 }
2147 None => {}
2148 }
2149
2150 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2151 if !self.focus_handle(cx).contains_focused(window, cx)
2152 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2153 {
2154 for addon in self.addons.values() {
2155 addon.extend_key_context(&mut key_context, cx)
2156 }
2157 }
2158
2159 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2160 if let Some(extension) = singleton_buffer
2161 .read(cx)
2162 .file()
2163 .and_then(|file| file.path().extension()?.to_str())
2164 {
2165 key_context.set("extension", extension.to_string());
2166 }
2167 } else {
2168 key_context.add("multibuffer");
2169 }
2170
2171 if has_active_edit_prediction {
2172 if self.edit_prediction_in_conflict() {
2173 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2174 } else {
2175 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2176 key_context.add("copilot_suggestion");
2177 }
2178 }
2179
2180 if self.selection_mark_mode {
2181 key_context.add("selection_mode");
2182 }
2183
2184 key_context
2185 }
2186
2187 fn show_mouse_cursor(&mut self) {
2188 self.mouse_cursor_hidden = false;
2189 }
2190
2191 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2192 self.mouse_cursor_hidden = match origin {
2193 HideMouseCursorOrigin::TypingAction => {
2194 matches!(
2195 self.hide_mouse_mode,
2196 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2197 )
2198 }
2199 HideMouseCursorOrigin::MovementAction => {
2200 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2201 }
2202 };
2203 }
2204
2205 pub fn edit_prediction_in_conflict(&self) -> bool {
2206 if !self.show_edit_predictions_in_menu() {
2207 return false;
2208 }
2209
2210 let showing_completions = self
2211 .context_menu
2212 .borrow()
2213 .as_ref()
2214 .map_or(false, |context| {
2215 matches!(context, CodeContextMenu::Completions(_))
2216 });
2217
2218 showing_completions
2219 || self.edit_prediction_requires_modifier()
2220 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2221 // bindings to insert tab characters.
2222 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2223 }
2224
2225 pub fn accept_edit_prediction_keybind(
2226 &self,
2227 window: &Window,
2228 cx: &App,
2229 ) -> AcceptEditPredictionBinding {
2230 let key_context = self.key_context_internal(true, window, cx);
2231 let in_conflict = self.edit_prediction_in_conflict();
2232
2233 AcceptEditPredictionBinding(
2234 window
2235 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2236 .into_iter()
2237 .filter(|binding| {
2238 !in_conflict
2239 || binding
2240 .keystrokes()
2241 .first()
2242 .map_or(false, |keystroke| keystroke.modifiers.modified())
2243 })
2244 .rev()
2245 .min_by_key(|binding| {
2246 binding
2247 .keystrokes()
2248 .first()
2249 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
2250 }),
2251 )
2252 }
2253
2254 pub fn new_file(
2255 workspace: &mut Workspace,
2256 _: &workspace::NewFile,
2257 window: &mut Window,
2258 cx: &mut Context<Workspace>,
2259 ) {
2260 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2261 "Failed to create buffer",
2262 window,
2263 cx,
2264 |e, _, _| match e.error_code() {
2265 ErrorCode::RemoteUpgradeRequired => Some(format!(
2266 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2267 e.error_tag("required").unwrap_or("the latest version")
2268 )),
2269 _ => None,
2270 },
2271 );
2272 }
2273
2274 pub fn new_in_workspace(
2275 workspace: &mut Workspace,
2276 window: &mut Window,
2277 cx: &mut Context<Workspace>,
2278 ) -> Task<Result<Entity<Editor>>> {
2279 let project = workspace.project().clone();
2280 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2281
2282 cx.spawn_in(window, async move |workspace, cx| {
2283 let buffer = create.await?;
2284 workspace.update_in(cx, |workspace, window, cx| {
2285 let editor =
2286 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2287 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2288 editor
2289 })
2290 })
2291 }
2292
2293 fn new_file_vertical(
2294 workspace: &mut Workspace,
2295 _: &workspace::NewFileSplitVertical,
2296 window: &mut Window,
2297 cx: &mut Context<Workspace>,
2298 ) {
2299 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2300 }
2301
2302 fn new_file_horizontal(
2303 workspace: &mut Workspace,
2304 _: &workspace::NewFileSplitHorizontal,
2305 window: &mut Window,
2306 cx: &mut Context<Workspace>,
2307 ) {
2308 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2309 }
2310
2311 fn new_file_in_direction(
2312 workspace: &mut Workspace,
2313 direction: SplitDirection,
2314 window: &mut Window,
2315 cx: &mut Context<Workspace>,
2316 ) {
2317 let project = workspace.project().clone();
2318 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2319
2320 cx.spawn_in(window, async move |workspace, cx| {
2321 let buffer = create.await?;
2322 workspace.update_in(cx, move |workspace, window, cx| {
2323 workspace.split_item(
2324 direction,
2325 Box::new(
2326 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2327 ),
2328 window,
2329 cx,
2330 )
2331 })?;
2332 anyhow::Ok(())
2333 })
2334 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2335 match e.error_code() {
2336 ErrorCode::RemoteUpgradeRequired => Some(format!(
2337 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2338 e.error_tag("required").unwrap_or("the latest version")
2339 )),
2340 _ => None,
2341 }
2342 });
2343 }
2344
2345 pub fn leader_id(&self) -> Option<CollaboratorId> {
2346 self.leader_id
2347 }
2348
2349 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2350 &self.buffer
2351 }
2352
2353 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2354 self.workspace.as_ref()?.0.upgrade()
2355 }
2356
2357 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2358 self.buffer().read(cx).title(cx)
2359 }
2360
2361 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2362 let git_blame_gutter_max_author_length = self
2363 .render_git_blame_gutter(cx)
2364 .then(|| {
2365 if let Some(blame) = self.blame.as_ref() {
2366 let max_author_length =
2367 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2368 Some(max_author_length)
2369 } else {
2370 None
2371 }
2372 })
2373 .flatten();
2374
2375 EditorSnapshot {
2376 mode: self.mode.clone(),
2377 show_gutter: self.show_gutter,
2378 show_line_numbers: self.show_line_numbers,
2379 show_git_diff_gutter: self.show_git_diff_gutter,
2380 show_code_actions: self.show_code_actions,
2381 show_runnables: self.show_runnables,
2382 show_breakpoints: self.show_breakpoints,
2383 git_blame_gutter_max_author_length,
2384 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2385 scroll_anchor: self.scroll_manager.anchor(),
2386 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2387 placeholder_text: self.placeholder_text.clone(),
2388 is_focused: self.focus_handle.is_focused(window),
2389 current_line_highlight: self
2390 .current_line_highlight
2391 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2392 gutter_hovered: self.gutter_hovered,
2393 }
2394 }
2395
2396 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2397 self.buffer.read(cx).language_at(point, cx)
2398 }
2399
2400 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2401 self.buffer.read(cx).read(cx).file_at(point).cloned()
2402 }
2403
2404 pub fn active_excerpt(
2405 &self,
2406 cx: &App,
2407 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2408 self.buffer
2409 .read(cx)
2410 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2411 }
2412
2413 pub fn mode(&self) -> &EditorMode {
2414 &self.mode
2415 }
2416
2417 pub fn set_mode(&mut self, mode: EditorMode) {
2418 self.mode = mode;
2419 }
2420
2421 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2422 self.collaboration_hub.as_deref()
2423 }
2424
2425 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2426 self.collaboration_hub = Some(hub);
2427 }
2428
2429 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2430 self.in_project_search = in_project_search;
2431 }
2432
2433 pub fn set_custom_context_menu(
2434 &mut self,
2435 f: impl 'static
2436 + Fn(
2437 &mut Self,
2438 DisplayPoint,
2439 &mut Window,
2440 &mut Context<Self>,
2441 ) -> Option<Entity<ui::ContextMenu>>,
2442 ) {
2443 self.custom_context_menu = Some(Box::new(f))
2444 }
2445
2446 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2447 self.completion_provider = provider;
2448 }
2449
2450 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2451 self.semantics_provider.clone()
2452 }
2453
2454 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2455 self.semantics_provider = provider;
2456 }
2457
2458 pub fn set_edit_prediction_provider<T>(
2459 &mut self,
2460 provider: Option<Entity<T>>,
2461 window: &mut Window,
2462 cx: &mut Context<Self>,
2463 ) where
2464 T: EditPredictionProvider,
2465 {
2466 self.edit_prediction_provider =
2467 provider.map(|provider| RegisteredInlineCompletionProvider {
2468 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2469 if this.focus_handle.is_focused(window) {
2470 this.update_visible_inline_completion(window, cx);
2471 }
2472 }),
2473 provider: Arc::new(provider),
2474 });
2475 self.update_edit_prediction_settings(cx);
2476 self.refresh_inline_completion(false, false, window, cx);
2477 }
2478
2479 pub fn placeholder_text(&self) -> Option<&str> {
2480 self.placeholder_text.as_deref()
2481 }
2482
2483 pub fn set_placeholder_text(
2484 &mut self,
2485 placeholder_text: impl Into<Arc<str>>,
2486 cx: &mut Context<Self>,
2487 ) {
2488 let placeholder_text = Some(placeholder_text.into());
2489 if self.placeholder_text != placeholder_text {
2490 self.placeholder_text = placeholder_text;
2491 cx.notify();
2492 }
2493 }
2494
2495 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2496 self.cursor_shape = cursor_shape;
2497
2498 // Disrupt blink for immediate user feedback that the cursor shape has changed
2499 self.blink_manager.update(cx, BlinkManager::show_cursor);
2500
2501 cx.notify();
2502 }
2503
2504 pub fn set_current_line_highlight(
2505 &mut self,
2506 current_line_highlight: Option<CurrentLineHighlight>,
2507 ) {
2508 self.current_line_highlight = current_line_highlight;
2509 }
2510
2511 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2512 self.collapse_matches = collapse_matches;
2513 }
2514
2515 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2516 let buffers = self.buffer.read(cx).all_buffers();
2517 let Some(project) = self.project.as_ref() else {
2518 return;
2519 };
2520 project.update(cx, |project, cx| {
2521 for buffer in buffers {
2522 self.registered_buffers
2523 .entry(buffer.read(cx).remote_id())
2524 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2525 }
2526 })
2527 }
2528
2529 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2530 if self.collapse_matches {
2531 return range.start..range.start;
2532 }
2533 range.clone()
2534 }
2535
2536 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2537 if self.display_map.read(cx).clip_at_line_ends != clip {
2538 self.display_map
2539 .update(cx, |map, _| map.clip_at_line_ends = clip);
2540 }
2541 }
2542
2543 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2544 self.input_enabled = input_enabled;
2545 }
2546
2547 pub fn set_inline_completions_hidden_for_vim_mode(
2548 &mut self,
2549 hidden: bool,
2550 window: &mut Window,
2551 cx: &mut Context<Self>,
2552 ) {
2553 if hidden != self.inline_completions_hidden_for_vim_mode {
2554 self.inline_completions_hidden_for_vim_mode = hidden;
2555 if hidden {
2556 self.update_visible_inline_completion(window, cx);
2557 } else {
2558 self.refresh_inline_completion(true, false, window, cx);
2559 }
2560 }
2561 }
2562
2563 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2564 self.menu_inline_completions_policy = value;
2565 }
2566
2567 pub fn set_autoindent(&mut self, autoindent: bool) {
2568 if autoindent {
2569 self.autoindent_mode = Some(AutoindentMode::EachLine);
2570 } else {
2571 self.autoindent_mode = None;
2572 }
2573 }
2574
2575 pub fn read_only(&self, cx: &App) -> bool {
2576 self.read_only || self.buffer.read(cx).read_only()
2577 }
2578
2579 pub fn set_read_only(&mut self, read_only: bool) {
2580 self.read_only = read_only;
2581 }
2582
2583 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2584 self.use_autoclose = autoclose;
2585 }
2586
2587 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2588 self.use_auto_surround = auto_surround;
2589 }
2590
2591 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2592 self.auto_replace_emoji_shortcode = auto_replace;
2593 }
2594
2595 pub fn toggle_edit_predictions(
2596 &mut self,
2597 _: &ToggleEditPrediction,
2598 window: &mut Window,
2599 cx: &mut Context<Self>,
2600 ) {
2601 if self.show_inline_completions_override.is_some() {
2602 self.set_show_edit_predictions(None, window, cx);
2603 } else {
2604 let show_edit_predictions = !self.edit_predictions_enabled();
2605 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2606 }
2607 }
2608
2609 pub fn set_show_edit_predictions(
2610 &mut self,
2611 show_edit_predictions: Option<bool>,
2612 window: &mut Window,
2613 cx: &mut Context<Self>,
2614 ) {
2615 self.show_inline_completions_override = show_edit_predictions;
2616 self.update_edit_prediction_settings(cx);
2617
2618 if let Some(false) = show_edit_predictions {
2619 self.discard_inline_completion(false, cx);
2620 } else {
2621 self.refresh_inline_completion(false, true, window, cx);
2622 }
2623 }
2624
2625 fn inline_completions_disabled_in_scope(
2626 &self,
2627 buffer: &Entity<Buffer>,
2628 buffer_position: language::Anchor,
2629 cx: &App,
2630 ) -> bool {
2631 let snapshot = buffer.read(cx).snapshot();
2632 let settings = snapshot.settings_at(buffer_position, cx);
2633
2634 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2635 return false;
2636 };
2637
2638 scope.override_name().map_or(false, |scope_name| {
2639 settings
2640 .edit_predictions_disabled_in
2641 .iter()
2642 .any(|s| s == scope_name)
2643 })
2644 }
2645
2646 pub fn set_use_modal_editing(&mut self, to: bool) {
2647 self.use_modal_editing = to;
2648 }
2649
2650 pub fn use_modal_editing(&self) -> bool {
2651 self.use_modal_editing
2652 }
2653
2654 fn selections_did_change(
2655 &mut self,
2656 local: bool,
2657 old_cursor_position: &Anchor,
2658 show_completions: bool,
2659 window: &mut Window,
2660 cx: &mut Context<Self>,
2661 ) {
2662 window.invalidate_character_coordinates();
2663
2664 // Copy selections to primary selection buffer
2665 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2666 if local {
2667 let selections = self.selections.all::<usize>(cx);
2668 let buffer_handle = self.buffer.read(cx).read(cx);
2669
2670 let mut text = String::new();
2671 for (index, selection) in selections.iter().enumerate() {
2672 let text_for_selection = buffer_handle
2673 .text_for_range(selection.start..selection.end)
2674 .collect::<String>();
2675
2676 text.push_str(&text_for_selection);
2677 if index != selections.len() - 1 {
2678 text.push('\n');
2679 }
2680 }
2681
2682 if !text.is_empty() {
2683 cx.write_to_primary(ClipboardItem::new_string(text));
2684 }
2685 }
2686
2687 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2688 self.buffer.update(cx, |buffer, cx| {
2689 buffer.set_active_selections(
2690 &self.selections.disjoint_anchors(),
2691 self.selections.line_mode,
2692 self.cursor_shape,
2693 cx,
2694 )
2695 });
2696 }
2697 let display_map = self
2698 .display_map
2699 .update(cx, |display_map, cx| display_map.snapshot(cx));
2700 let buffer = &display_map.buffer_snapshot;
2701 self.add_selections_state = None;
2702 self.select_next_state = None;
2703 self.select_prev_state = None;
2704 self.select_syntax_node_history.try_clear();
2705 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2706 self.snippet_stack
2707 .invalidate(&self.selections.disjoint_anchors(), buffer);
2708 self.take_rename(false, window, cx);
2709
2710 let new_cursor_position = self.selections.newest_anchor().head();
2711
2712 self.push_to_nav_history(
2713 *old_cursor_position,
2714 Some(new_cursor_position.to_point(buffer)),
2715 false,
2716 cx,
2717 );
2718
2719 if local {
2720 let new_cursor_position = self.selections.newest_anchor().head();
2721 let mut context_menu = self.context_menu.borrow_mut();
2722 let completion_menu = match context_menu.as_ref() {
2723 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2724 _ => {
2725 *context_menu = None;
2726 None
2727 }
2728 };
2729 if let Some(buffer_id) = new_cursor_position.buffer_id {
2730 if !self.registered_buffers.contains_key(&buffer_id) {
2731 if let Some(project) = self.project.as_ref() {
2732 project.update(cx, |project, cx| {
2733 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2734 return;
2735 };
2736 self.registered_buffers.insert(
2737 buffer_id,
2738 project.register_buffer_with_language_servers(&buffer, cx),
2739 );
2740 })
2741 }
2742 }
2743 }
2744
2745 if let Some(completion_menu) = completion_menu {
2746 let cursor_position = new_cursor_position.to_offset(buffer);
2747 let (word_range, kind) =
2748 buffer.surrounding_word(completion_menu.initial_position, true);
2749 if kind == Some(CharKind::Word)
2750 && word_range.to_inclusive().contains(&cursor_position)
2751 {
2752 let mut completion_menu = completion_menu.clone();
2753 drop(context_menu);
2754
2755 let query = Self::completion_query(buffer, cursor_position);
2756 let completion_provider = self.completion_provider.clone();
2757 cx.spawn_in(window, async move |this, cx| {
2758 completion_menu
2759 .filter(query.as_deref(), completion_provider, this.clone(), cx)
2760 .await;
2761
2762 this.update(cx, |this, cx| {
2763 let mut context_menu = this.context_menu.borrow_mut();
2764 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2765 else {
2766 return;
2767 };
2768
2769 if menu.id > completion_menu.id {
2770 return;
2771 }
2772
2773 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2774 drop(context_menu);
2775 cx.notify();
2776 })
2777 })
2778 .detach();
2779
2780 if show_completions {
2781 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2782 }
2783 } else {
2784 drop(context_menu);
2785 self.hide_context_menu(window, cx);
2786 }
2787 } else {
2788 drop(context_menu);
2789 }
2790
2791 hide_hover(self, cx);
2792
2793 if old_cursor_position.to_display_point(&display_map).row()
2794 != new_cursor_position.to_display_point(&display_map).row()
2795 {
2796 self.available_code_actions.take();
2797 }
2798 self.refresh_code_actions(window, cx);
2799 self.refresh_document_highlights(cx);
2800 self.refresh_selected_text_highlights(false, window, cx);
2801 refresh_matching_bracket_highlights(self, window, cx);
2802 self.update_visible_inline_completion(window, cx);
2803 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2804 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2805 self.inline_blame_popover.take();
2806 if self.git_blame_inline_enabled {
2807 self.start_inline_blame_timer(window, cx);
2808 }
2809 }
2810
2811 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2812 cx.emit(EditorEvent::SelectionsChanged { local });
2813
2814 let selections = &self.selections.disjoint;
2815 if selections.len() == 1 {
2816 cx.emit(SearchEvent::ActiveMatchChanged)
2817 }
2818 if local {
2819 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2820 let inmemory_selections = selections
2821 .iter()
2822 .map(|s| {
2823 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2824 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2825 })
2826 .collect();
2827 self.update_restoration_data(cx, |data| {
2828 data.selections = inmemory_selections;
2829 });
2830
2831 if WorkspaceSettings::get(None, cx).restore_on_startup
2832 != RestoreOnStartupBehavior::None
2833 {
2834 if let Some(workspace_id) =
2835 self.workspace.as_ref().and_then(|workspace| workspace.1)
2836 {
2837 let snapshot = self.buffer().read(cx).snapshot(cx);
2838 let selections = selections.clone();
2839 let background_executor = cx.background_executor().clone();
2840 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2841 self.serialize_selections = cx.background_spawn(async move {
2842 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2843 let db_selections = selections
2844 .iter()
2845 .map(|selection| {
2846 (
2847 selection.start.to_offset(&snapshot),
2848 selection.end.to_offset(&snapshot),
2849 )
2850 })
2851 .collect();
2852
2853 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2854 .await
2855 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2856 .log_err();
2857 });
2858 }
2859 }
2860 }
2861 }
2862
2863 cx.notify();
2864 }
2865
2866 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2867 use text::ToOffset as _;
2868 use text::ToPoint as _;
2869
2870 if self.mode.is_minimap()
2871 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2872 {
2873 return;
2874 }
2875
2876 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2877 return;
2878 };
2879
2880 let snapshot = singleton.read(cx).snapshot();
2881 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2882 let display_snapshot = display_map.snapshot(cx);
2883
2884 display_snapshot
2885 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2886 .map(|fold| {
2887 fold.range.start.text_anchor.to_point(&snapshot)
2888 ..fold.range.end.text_anchor.to_point(&snapshot)
2889 })
2890 .collect()
2891 });
2892 self.update_restoration_data(cx, |data| {
2893 data.folds = inmemory_folds;
2894 });
2895
2896 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2897 return;
2898 };
2899 let background_executor = cx.background_executor().clone();
2900 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2901 let db_folds = self.display_map.update(cx, |display_map, cx| {
2902 display_map
2903 .snapshot(cx)
2904 .folds_in_range(0..snapshot.len())
2905 .map(|fold| {
2906 (
2907 fold.range.start.text_anchor.to_offset(&snapshot),
2908 fold.range.end.text_anchor.to_offset(&snapshot),
2909 )
2910 })
2911 .collect()
2912 });
2913 self.serialize_folds = cx.background_spawn(async move {
2914 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2915 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2916 .await
2917 .with_context(|| {
2918 format!(
2919 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2920 )
2921 })
2922 .log_err();
2923 });
2924 }
2925
2926 pub fn sync_selections(
2927 &mut self,
2928 other: Entity<Editor>,
2929 cx: &mut Context<Self>,
2930 ) -> gpui::Subscription {
2931 let other_selections = other.read(cx).selections.disjoint.to_vec();
2932 self.selections.change_with(cx, |selections| {
2933 selections.select_anchors(other_selections);
2934 });
2935
2936 let other_subscription =
2937 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2938 EditorEvent::SelectionsChanged { local: true } => {
2939 let other_selections = other.read(cx).selections.disjoint.to_vec();
2940 if other_selections.is_empty() {
2941 return;
2942 }
2943 this.selections.change_with(cx, |selections| {
2944 selections.select_anchors(other_selections);
2945 });
2946 }
2947 _ => {}
2948 });
2949
2950 let this_subscription =
2951 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2952 EditorEvent::SelectionsChanged { local: true } => {
2953 let these_selections = this.selections.disjoint.to_vec();
2954 if these_selections.is_empty() {
2955 return;
2956 }
2957 other.update(cx, |other_editor, cx| {
2958 other_editor.selections.change_with(cx, |selections| {
2959 selections.select_anchors(these_selections);
2960 })
2961 });
2962 }
2963 _ => {}
2964 });
2965
2966 Subscription::join(other_subscription, this_subscription)
2967 }
2968
2969 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
2970 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
2971 /// effects of selection change occur at the end of the transaction.
2972 pub fn change_selections<R>(
2973 &mut self,
2974 autoscroll: Option<Autoscroll>,
2975 window: &mut Window,
2976 cx: &mut Context<Self>,
2977 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2978 ) -> R {
2979 self.change_selections_inner(true, autoscroll, window, cx, change)
2980 }
2981
2982 pub(crate) fn change_selections_without_showing_completions<R>(
2983 &mut self,
2984 autoscroll: Option<Autoscroll>,
2985 window: &mut Window,
2986 cx: &mut Context<Self>,
2987 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2988 ) -> R {
2989 self.change_selections_inner(false, autoscroll, window, cx, change)
2990 }
2991
2992 fn change_selections_inner<R>(
2993 &mut self,
2994 show_completions: bool,
2995 autoscroll: Option<Autoscroll>,
2996 window: &mut Window,
2997 cx: &mut Context<Self>,
2998 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2999 ) -> R {
3000 if let Some(state) = &mut self.deferred_selection_effects_state {
3001 state.autoscroll = autoscroll.or(state.autoscroll);
3002 state.show_completions = show_completions;
3003 let (changed, result) = self.selections.change_with(cx, change);
3004 state.changed |= changed;
3005 return result;
3006 }
3007 let mut state = DeferredSelectionEffectsState {
3008 changed: false,
3009 show_completions,
3010 autoscroll,
3011 old_cursor_position: self.selections.newest_anchor().head(),
3012 history_entry: SelectionHistoryEntry {
3013 selections: self.selections.disjoint_anchors(),
3014 select_next_state: self.select_next_state.clone(),
3015 select_prev_state: self.select_prev_state.clone(),
3016 add_selections_state: self.add_selections_state.clone(),
3017 },
3018 };
3019 let (changed, result) = self.selections.change_with(cx, change);
3020 state.changed = state.changed || changed;
3021 if self.defer_selection_effects {
3022 self.deferred_selection_effects_state = Some(state);
3023 } else {
3024 self.apply_selection_effects(state, window, cx);
3025 }
3026 result
3027 }
3028
3029 /// Defers the effects of selection change, so that the effects of multiple calls to
3030 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3031 /// to selection history and the state of popovers based on selection position aren't
3032 /// erroneously updated.
3033 pub fn with_selection_effects_deferred<R>(
3034 &mut self,
3035 window: &mut Window,
3036 cx: &mut Context<Self>,
3037 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3038 ) -> R {
3039 let already_deferred = self.defer_selection_effects;
3040 self.defer_selection_effects = true;
3041 let result = update(self, window, cx);
3042 if !already_deferred {
3043 self.defer_selection_effects = false;
3044 if let Some(state) = self.deferred_selection_effects_state.take() {
3045 self.apply_selection_effects(state, window, cx);
3046 }
3047 }
3048 result
3049 }
3050
3051 fn apply_selection_effects(
3052 &mut self,
3053 state: DeferredSelectionEffectsState,
3054 window: &mut Window,
3055 cx: &mut Context<Self>,
3056 ) {
3057 if state.changed {
3058 self.selection_history.push(state.history_entry);
3059
3060 if let Some(autoscroll) = state.autoscroll {
3061 self.request_autoscroll(autoscroll, cx);
3062 }
3063
3064 let old_cursor_position = &state.old_cursor_position;
3065
3066 self.selections_did_change(
3067 true,
3068 &old_cursor_position,
3069 state.show_completions,
3070 window,
3071 cx,
3072 );
3073
3074 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3075 self.show_signature_help(&ShowSignatureHelp, window, cx);
3076 }
3077 }
3078 }
3079
3080 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3081 where
3082 I: IntoIterator<Item = (Range<S>, T)>,
3083 S: ToOffset,
3084 T: Into<Arc<str>>,
3085 {
3086 if self.read_only(cx) {
3087 return;
3088 }
3089
3090 self.buffer
3091 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3092 }
3093
3094 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3095 where
3096 I: IntoIterator<Item = (Range<S>, T)>,
3097 S: ToOffset,
3098 T: Into<Arc<str>>,
3099 {
3100 if self.read_only(cx) {
3101 return;
3102 }
3103
3104 self.buffer.update(cx, |buffer, cx| {
3105 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3106 });
3107 }
3108
3109 pub fn edit_with_block_indent<I, S, T>(
3110 &mut self,
3111 edits: I,
3112 original_indent_columns: Vec<Option<u32>>,
3113 cx: &mut Context<Self>,
3114 ) where
3115 I: IntoIterator<Item = (Range<S>, T)>,
3116 S: ToOffset,
3117 T: Into<Arc<str>>,
3118 {
3119 if self.read_only(cx) {
3120 return;
3121 }
3122
3123 self.buffer.update(cx, |buffer, cx| {
3124 buffer.edit(
3125 edits,
3126 Some(AutoindentMode::Block {
3127 original_indent_columns,
3128 }),
3129 cx,
3130 )
3131 });
3132 }
3133
3134 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3135 self.hide_context_menu(window, cx);
3136
3137 match phase {
3138 SelectPhase::Begin {
3139 position,
3140 add,
3141 click_count,
3142 } => self.begin_selection(position, add, click_count, window, cx),
3143 SelectPhase::BeginColumnar {
3144 position,
3145 goal_column,
3146 reset,
3147 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3148 SelectPhase::Extend {
3149 position,
3150 click_count,
3151 } => self.extend_selection(position, click_count, window, cx),
3152 SelectPhase::Update {
3153 position,
3154 goal_column,
3155 scroll_delta,
3156 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3157 SelectPhase::End => self.end_selection(window, cx),
3158 }
3159 }
3160
3161 fn extend_selection(
3162 &mut self,
3163 position: DisplayPoint,
3164 click_count: usize,
3165 window: &mut Window,
3166 cx: &mut Context<Self>,
3167 ) {
3168 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3169 let tail = self.selections.newest::<usize>(cx).tail();
3170 self.begin_selection(position, false, click_count, window, cx);
3171
3172 let position = position.to_offset(&display_map, Bias::Left);
3173 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3174
3175 let mut pending_selection = self
3176 .selections
3177 .pending_anchor()
3178 .expect("extend_selection not called with pending selection");
3179 if position >= tail {
3180 pending_selection.start = tail_anchor;
3181 } else {
3182 pending_selection.end = tail_anchor;
3183 pending_selection.reversed = true;
3184 }
3185
3186 let mut pending_mode = self.selections.pending_mode().unwrap();
3187 match &mut pending_mode {
3188 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3189 _ => {}
3190 }
3191
3192 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3193
3194 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3195 s.set_pending(pending_selection, pending_mode)
3196 });
3197 }
3198
3199 fn begin_selection(
3200 &mut self,
3201 position: DisplayPoint,
3202 add: bool,
3203 click_count: usize,
3204 window: &mut Window,
3205 cx: &mut Context<Self>,
3206 ) {
3207 if !self.focus_handle.is_focused(window) {
3208 self.last_focused_descendant = None;
3209 window.focus(&self.focus_handle);
3210 }
3211
3212 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3213 let buffer = &display_map.buffer_snapshot;
3214 let position = display_map.clip_point(position, Bias::Left);
3215
3216 let start;
3217 let end;
3218 let mode;
3219 let mut auto_scroll;
3220 match click_count {
3221 1 => {
3222 start = buffer.anchor_before(position.to_point(&display_map));
3223 end = start;
3224 mode = SelectMode::Character;
3225 auto_scroll = true;
3226 }
3227 2 => {
3228 let range = movement::surrounding_word(&display_map, position);
3229 start = buffer.anchor_before(range.start.to_point(&display_map));
3230 end = buffer.anchor_before(range.end.to_point(&display_map));
3231 mode = SelectMode::Word(start..end);
3232 auto_scroll = true;
3233 }
3234 3 => {
3235 let position = display_map
3236 .clip_point(position, Bias::Left)
3237 .to_point(&display_map);
3238 let line_start = display_map.prev_line_boundary(position).0;
3239 let next_line_start = buffer.clip_point(
3240 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3241 Bias::Left,
3242 );
3243 start = buffer.anchor_before(line_start);
3244 end = buffer.anchor_before(next_line_start);
3245 mode = SelectMode::Line(start..end);
3246 auto_scroll = true;
3247 }
3248 _ => {
3249 start = buffer.anchor_before(0);
3250 end = buffer.anchor_before(buffer.len());
3251 mode = SelectMode::All;
3252 auto_scroll = false;
3253 }
3254 }
3255 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3256
3257 let point_to_delete: Option<usize> = {
3258 let selected_points: Vec<Selection<Point>> =
3259 self.selections.disjoint_in_range(start..end, cx);
3260
3261 if !add || click_count > 1 {
3262 None
3263 } else if !selected_points.is_empty() {
3264 Some(selected_points[0].id)
3265 } else {
3266 let clicked_point_already_selected =
3267 self.selections.disjoint.iter().find(|selection| {
3268 selection.start.to_point(buffer) == start.to_point(buffer)
3269 || selection.end.to_point(buffer) == end.to_point(buffer)
3270 });
3271
3272 clicked_point_already_selected.map(|selection| selection.id)
3273 }
3274 };
3275
3276 let selections_count = self.selections.count();
3277
3278 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3279 if let Some(point_to_delete) = point_to_delete {
3280 s.delete(point_to_delete);
3281
3282 if selections_count == 1 {
3283 s.set_pending_anchor_range(start..end, mode);
3284 }
3285 } else {
3286 if !add {
3287 s.clear_disjoint();
3288 }
3289
3290 s.set_pending_anchor_range(start..end, mode);
3291 }
3292 });
3293 }
3294
3295 fn begin_columnar_selection(
3296 &mut self,
3297 position: DisplayPoint,
3298 goal_column: u32,
3299 reset: bool,
3300 window: &mut Window,
3301 cx: &mut Context<Self>,
3302 ) {
3303 if !self.focus_handle.is_focused(window) {
3304 self.last_focused_descendant = None;
3305 window.focus(&self.focus_handle);
3306 }
3307
3308 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3309
3310 if reset {
3311 let pointer_position = display_map
3312 .buffer_snapshot
3313 .anchor_before(position.to_point(&display_map));
3314
3315 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3316 s.clear_disjoint();
3317 s.set_pending_anchor_range(
3318 pointer_position..pointer_position,
3319 SelectMode::Character,
3320 );
3321 });
3322 }
3323
3324 let tail = self.selections.newest::<Point>(cx).tail();
3325 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3326
3327 if !reset {
3328 self.select_columns(
3329 tail.to_display_point(&display_map),
3330 position,
3331 goal_column,
3332 &display_map,
3333 window,
3334 cx,
3335 );
3336 }
3337 }
3338
3339 fn update_selection(
3340 &mut self,
3341 position: DisplayPoint,
3342 goal_column: u32,
3343 scroll_delta: gpui::Point<f32>,
3344 window: &mut Window,
3345 cx: &mut Context<Self>,
3346 ) {
3347 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3348
3349 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3350 let tail = tail.to_display_point(&display_map);
3351 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3352 } else if let Some(mut pending) = self.selections.pending_anchor() {
3353 let buffer = self.buffer.read(cx).snapshot(cx);
3354 let head;
3355 let tail;
3356 let mode = self.selections.pending_mode().unwrap();
3357 match &mode {
3358 SelectMode::Character => {
3359 head = position.to_point(&display_map);
3360 tail = pending.tail().to_point(&buffer);
3361 }
3362 SelectMode::Word(original_range) => {
3363 let original_display_range = original_range.start.to_display_point(&display_map)
3364 ..original_range.end.to_display_point(&display_map);
3365 let original_buffer_range = original_display_range.start.to_point(&display_map)
3366 ..original_display_range.end.to_point(&display_map);
3367 if movement::is_inside_word(&display_map, position)
3368 || original_display_range.contains(&position)
3369 {
3370 let word_range = movement::surrounding_word(&display_map, position);
3371 if word_range.start < original_display_range.start {
3372 head = word_range.start.to_point(&display_map);
3373 } else {
3374 head = word_range.end.to_point(&display_map);
3375 }
3376 } else {
3377 head = position.to_point(&display_map);
3378 }
3379
3380 if head <= original_buffer_range.start {
3381 tail = original_buffer_range.end;
3382 } else {
3383 tail = original_buffer_range.start;
3384 }
3385 }
3386 SelectMode::Line(original_range) => {
3387 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3388
3389 let position = display_map
3390 .clip_point(position, Bias::Left)
3391 .to_point(&display_map);
3392 let line_start = display_map.prev_line_boundary(position).0;
3393 let next_line_start = buffer.clip_point(
3394 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3395 Bias::Left,
3396 );
3397
3398 if line_start < original_range.start {
3399 head = line_start
3400 } else {
3401 head = next_line_start
3402 }
3403
3404 if head <= original_range.start {
3405 tail = original_range.end;
3406 } else {
3407 tail = original_range.start;
3408 }
3409 }
3410 SelectMode::All => {
3411 return;
3412 }
3413 };
3414
3415 if head < tail {
3416 pending.start = buffer.anchor_before(head);
3417 pending.end = buffer.anchor_before(tail);
3418 pending.reversed = true;
3419 } else {
3420 pending.start = buffer.anchor_before(tail);
3421 pending.end = buffer.anchor_before(head);
3422 pending.reversed = false;
3423 }
3424
3425 self.change_selections(None, window, cx, |s| {
3426 s.set_pending(pending, mode);
3427 });
3428 } else {
3429 log::error!("update_selection dispatched with no pending selection");
3430 return;
3431 }
3432
3433 self.apply_scroll_delta(scroll_delta, window, cx);
3434 cx.notify();
3435 }
3436
3437 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3438 self.columnar_selection_tail.take();
3439 if self.selections.pending_anchor().is_some() {
3440 let selections = self.selections.all::<usize>(cx);
3441 self.change_selections(None, window, cx, |s| {
3442 s.select(selections);
3443 s.clear_pending();
3444 });
3445 }
3446 }
3447
3448 fn select_columns(
3449 &mut self,
3450 tail: DisplayPoint,
3451 head: DisplayPoint,
3452 goal_column: u32,
3453 display_map: &DisplaySnapshot,
3454 window: &mut Window,
3455 cx: &mut Context<Self>,
3456 ) {
3457 let start_row = cmp::min(tail.row(), head.row());
3458 let end_row = cmp::max(tail.row(), head.row());
3459 let start_column = cmp::min(tail.column(), goal_column);
3460 let end_column = cmp::max(tail.column(), goal_column);
3461 let reversed = start_column < tail.column();
3462
3463 let selection_ranges = (start_row.0..=end_row.0)
3464 .map(DisplayRow)
3465 .filter_map(|row| {
3466 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
3467 let start = display_map
3468 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3469 .to_point(display_map);
3470 let end = display_map
3471 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3472 .to_point(display_map);
3473 if reversed {
3474 Some(end..start)
3475 } else {
3476 Some(start..end)
3477 }
3478 } else {
3479 None
3480 }
3481 })
3482 .collect::<Vec<_>>();
3483
3484 self.change_selections(None, window, cx, |s| {
3485 s.select_ranges(selection_ranges);
3486 });
3487 cx.notify();
3488 }
3489
3490 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3491 self.selections
3492 .all_adjusted(cx)
3493 .iter()
3494 .any(|selection| !selection.is_empty())
3495 }
3496
3497 pub fn has_pending_nonempty_selection(&self) -> bool {
3498 let pending_nonempty_selection = match self.selections.pending_anchor() {
3499 Some(Selection { start, end, .. }) => start != end,
3500 None => false,
3501 };
3502
3503 pending_nonempty_selection
3504 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3505 }
3506
3507 pub fn has_pending_selection(&self) -> bool {
3508 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3509 }
3510
3511 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3512 self.selection_mark_mode = false;
3513
3514 if self.clear_expanded_diff_hunks(cx) {
3515 cx.notify();
3516 return;
3517 }
3518 if self.dismiss_menus_and_popups(true, window, cx) {
3519 return;
3520 }
3521
3522 if self.mode.is_full()
3523 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3524 {
3525 return;
3526 }
3527
3528 cx.propagate();
3529 }
3530
3531 pub fn dismiss_menus_and_popups(
3532 &mut self,
3533 is_user_requested: bool,
3534 window: &mut Window,
3535 cx: &mut Context<Self>,
3536 ) -> bool {
3537 if self.take_rename(false, window, cx).is_some() {
3538 return true;
3539 }
3540
3541 if hide_hover(self, cx) {
3542 return true;
3543 }
3544
3545 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3546 return true;
3547 }
3548
3549 if self.hide_context_menu(window, cx).is_some() {
3550 return true;
3551 }
3552
3553 if self.mouse_context_menu.take().is_some() {
3554 return true;
3555 }
3556
3557 if is_user_requested && self.discard_inline_completion(true, cx) {
3558 return true;
3559 }
3560
3561 if self.snippet_stack.pop().is_some() {
3562 return true;
3563 }
3564
3565 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3566 self.dismiss_diagnostics(cx);
3567 return true;
3568 }
3569
3570 false
3571 }
3572
3573 fn linked_editing_ranges_for(
3574 &self,
3575 selection: Range<text::Anchor>,
3576 cx: &App,
3577 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3578 if self.linked_edit_ranges.is_empty() {
3579 return None;
3580 }
3581 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3582 selection.end.buffer_id.and_then(|end_buffer_id| {
3583 if selection.start.buffer_id != Some(end_buffer_id) {
3584 return None;
3585 }
3586 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3587 let snapshot = buffer.read(cx).snapshot();
3588 self.linked_edit_ranges
3589 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3590 .map(|ranges| (ranges, snapshot, buffer))
3591 })?;
3592 use text::ToOffset as TO;
3593 // find offset from the start of current range to current cursor position
3594 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3595
3596 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3597 let start_difference = start_offset - start_byte_offset;
3598 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3599 let end_difference = end_offset - start_byte_offset;
3600 // Current range has associated linked ranges.
3601 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3602 for range in linked_ranges.iter() {
3603 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3604 let end_offset = start_offset + end_difference;
3605 let start_offset = start_offset + start_difference;
3606 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3607 continue;
3608 }
3609 if self.selections.disjoint_anchor_ranges().any(|s| {
3610 if s.start.buffer_id != selection.start.buffer_id
3611 || s.end.buffer_id != selection.end.buffer_id
3612 {
3613 return false;
3614 }
3615 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3616 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3617 }) {
3618 continue;
3619 }
3620 let start = buffer_snapshot.anchor_after(start_offset);
3621 let end = buffer_snapshot.anchor_after(end_offset);
3622 linked_edits
3623 .entry(buffer.clone())
3624 .or_default()
3625 .push(start..end);
3626 }
3627 Some(linked_edits)
3628 }
3629
3630 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3631 let text: Arc<str> = text.into();
3632
3633 if self.read_only(cx) {
3634 return;
3635 }
3636
3637 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3638
3639 let selections = self.selections.all_adjusted(cx);
3640 let mut bracket_inserted = false;
3641 let mut edits = Vec::new();
3642 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3643 let mut new_selections = Vec::with_capacity(selections.len());
3644 let mut new_autoclose_regions = Vec::new();
3645 let snapshot = self.buffer.read(cx).read(cx);
3646 let mut clear_linked_edit_ranges = false;
3647
3648 for (selection, autoclose_region) in
3649 self.selections_with_autoclose_regions(selections, &snapshot)
3650 {
3651 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3652 // Determine if the inserted text matches the opening or closing
3653 // bracket of any of this language's bracket pairs.
3654 let mut bracket_pair = None;
3655 let mut is_bracket_pair_start = false;
3656 let mut is_bracket_pair_end = false;
3657 if !text.is_empty() {
3658 let mut bracket_pair_matching_end = None;
3659 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3660 // and they are removing the character that triggered IME popup.
3661 for (pair, enabled) in scope.brackets() {
3662 if !pair.close && !pair.surround {
3663 continue;
3664 }
3665
3666 if enabled && pair.start.ends_with(text.as_ref()) {
3667 let prefix_len = pair.start.len() - text.len();
3668 let preceding_text_matches_prefix = prefix_len == 0
3669 || (selection.start.column >= (prefix_len as u32)
3670 && snapshot.contains_str_at(
3671 Point::new(
3672 selection.start.row,
3673 selection.start.column - (prefix_len as u32),
3674 ),
3675 &pair.start[..prefix_len],
3676 ));
3677 if preceding_text_matches_prefix {
3678 bracket_pair = Some(pair.clone());
3679 is_bracket_pair_start = true;
3680 break;
3681 }
3682 }
3683 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3684 {
3685 // take first bracket pair matching end, but don't break in case a later bracket
3686 // pair matches start
3687 bracket_pair_matching_end = Some(pair.clone());
3688 }
3689 }
3690 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3691 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3692 is_bracket_pair_end = true;
3693 }
3694 }
3695
3696 if let Some(bracket_pair) = bracket_pair {
3697 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3698 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3699 let auto_surround =
3700 self.use_auto_surround && snapshot_settings.use_auto_surround;
3701 if selection.is_empty() {
3702 if is_bracket_pair_start {
3703 // If the inserted text is a suffix of an opening bracket and the
3704 // selection is preceded by the rest of the opening bracket, then
3705 // insert the closing bracket.
3706 let following_text_allows_autoclose = snapshot
3707 .chars_at(selection.start)
3708 .next()
3709 .map_or(true, |c| scope.should_autoclose_before(c));
3710
3711 let preceding_text_allows_autoclose = selection.start.column == 0
3712 || snapshot.reversed_chars_at(selection.start).next().map_or(
3713 true,
3714 |c| {
3715 bracket_pair.start != bracket_pair.end
3716 || !snapshot
3717 .char_classifier_at(selection.start)
3718 .is_word(c)
3719 },
3720 );
3721
3722 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3723 && bracket_pair.start.len() == 1
3724 {
3725 let target = bracket_pair.start.chars().next().unwrap();
3726 let current_line_count = snapshot
3727 .reversed_chars_at(selection.start)
3728 .take_while(|&c| c != '\n')
3729 .filter(|&c| c == target)
3730 .count();
3731 current_line_count % 2 == 1
3732 } else {
3733 false
3734 };
3735
3736 if autoclose
3737 && bracket_pair.close
3738 && following_text_allows_autoclose
3739 && preceding_text_allows_autoclose
3740 && !is_closing_quote
3741 {
3742 let anchor = snapshot.anchor_before(selection.end);
3743 new_selections.push((selection.map(|_| anchor), text.len()));
3744 new_autoclose_regions.push((
3745 anchor,
3746 text.len(),
3747 selection.id,
3748 bracket_pair.clone(),
3749 ));
3750 edits.push((
3751 selection.range(),
3752 format!("{}{}", text, bracket_pair.end).into(),
3753 ));
3754 bracket_inserted = true;
3755 continue;
3756 }
3757 }
3758
3759 if let Some(region) = autoclose_region {
3760 // If the selection is followed by an auto-inserted closing bracket,
3761 // then don't insert that closing bracket again; just move the selection
3762 // past the closing bracket.
3763 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3764 && text.as_ref() == region.pair.end.as_str();
3765 if should_skip {
3766 let anchor = snapshot.anchor_after(selection.end);
3767 new_selections
3768 .push((selection.map(|_| anchor), region.pair.end.len()));
3769 continue;
3770 }
3771 }
3772
3773 let always_treat_brackets_as_autoclosed = snapshot
3774 .language_settings_at(selection.start, cx)
3775 .always_treat_brackets_as_autoclosed;
3776 if always_treat_brackets_as_autoclosed
3777 && is_bracket_pair_end
3778 && snapshot.contains_str_at(selection.end, text.as_ref())
3779 {
3780 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3781 // and the inserted text is a closing bracket and the selection is followed
3782 // by the closing bracket then move the selection past the closing bracket.
3783 let anchor = snapshot.anchor_after(selection.end);
3784 new_selections.push((selection.map(|_| anchor), text.len()));
3785 continue;
3786 }
3787 }
3788 // If an opening bracket is 1 character long and is typed while
3789 // text is selected, then surround that text with the bracket pair.
3790 else if auto_surround
3791 && bracket_pair.surround
3792 && is_bracket_pair_start
3793 && bracket_pair.start.chars().count() == 1
3794 {
3795 edits.push((selection.start..selection.start, text.clone()));
3796 edits.push((
3797 selection.end..selection.end,
3798 bracket_pair.end.as_str().into(),
3799 ));
3800 bracket_inserted = true;
3801 new_selections.push((
3802 Selection {
3803 id: selection.id,
3804 start: snapshot.anchor_after(selection.start),
3805 end: snapshot.anchor_before(selection.end),
3806 reversed: selection.reversed,
3807 goal: selection.goal,
3808 },
3809 0,
3810 ));
3811 continue;
3812 }
3813 }
3814 }
3815
3816 if self.auto_replace_emoji_shortcode
3817 && selection.is_empty()
3818 && text.as_ref().ends_with(':')
3819 {
3820 if let Some(possible_emoji_short_code) =
3821 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3822 {
3823 if !possible_emoji_short_code.is_empty() {
3824 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3825 let emoji_shortcode_start = Point::new(
3826 selection.start.row,
3827 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3828 );
3829
3830 // Remove shortcode from buffer
3831 edits.push((
3832 emoji_shortcode_start..selection.start,
3833 "".to_string().into(),
3834 ));
3835 new_selections.push((
3836 Selection {
3837 id: selection.id,
3838 start: snapshot.anchor_after(emoji_shortcode_start),
3839 end: snapshot.anchor_before(selection.start),
3840 reversed: selection.reversed,
3841 goal: selection.goal,
3842 },
3843 0,
3844 ));
3845
3846 // Insert emoji
3847 let selection_start_anchor = snapshot.anchor_after(selection.start);
3848 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3849 edits.push((selection.start..selection.end, emoji.to_string().into()));
3850
3851 continue;
3852 }
3853 }
3854 }
3855 }
3856
3857 // If not handling any auto-close operation, then just replace the selected
3858 // text with the given input and move the selection to the end of the
3859 // newly inserted text.
3860 let anchor = snapshot.anchor_after(selection.end);
3861 if !self.linked_edit_ranges.is_empty() {
3862 let start_anchor = snapshot.anchor_before(selection.start);
3863
3864 let is_word_char = text.chars().next().map_or(true, |char| {
3865 let classifier = snapshot
3866 .char_classifier_at(start_anchor.to_offset(&snapshot))
3867 .ignore_punctuation(true);
3868 classifier.is_word(char)
3869 });
3870
3871 if is_word_char {
3872 if let Some(ranges) = self
3873 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3874 {
3875 for (buffer, edits) in ranges {
3876 linked_edits
3877 .entry(buffer.clone())
3878 .or_default()
3879 .extend(edits.into_iter().map(|range| (range, text.clone())));
3880 }
3881 }
3882 } else {
3883 clear_linked_edit_ranges = true;
3884 }
3885 }
3886
3887 new_selections.push((selection.map(|_| anchor), 0));
3888 edits.push((selection.start..selection.end, text.clone()));
3889 }
3890
3891 drop(snapshot);
3892
3893 self.transact(window, cx, |this, window, cx| {
3894 if clear_linked_edit_ranges {
3895 this.linked_edit_ranges.clear();
3896 }
3897 let initial_buffer_versions =
3898 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3899
3900 this.buffer.update(cx, |buffer, cx| {
3901 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3902 });
3903 for (buffer, edits) in linked_edits {
3904 buffer.update(cx, |buffer, cx| {
3905 let snapshot = buffer.snapshot();
3906 let edits = edits
3907 .into_iter()
3908 .map(|(range, text)| {
3909 use text::ToPoint as TP;
3910 let end_point = TP::to_point(&range.end, &snapshot);
3911 let start_point = TP::to_point(&range.start, &snapshot);
3912 (start_point..end_point, text)
3913 })
3914 .sorted_by_key(|(range, _)| range.start);
3915 buffer.edit(edits, None, cx);
3916 })
3917 }
3918 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3919 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3920 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3921 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3922 .zip(new_selection_deltas)
3923 .map(|(selection, delta)| Selection {
3924 id: selection.id,
3925 start: selection.start + delta,
3926 end: selection.end + delta,
3927 reversed: selection.reversed,
3928 goal: SelectionGoal::None,
3929 })
3930 .collect::<Vec<_>>();
3931
3932 let mut i = 0;
3933 for (position, delta, selection_id, pair) in new_autoclose_regions {
3934 let position = position.to_offset(&map.buffer_snapshot) + delta;
3935 let start = map.buffer_snapshot.anchor_before(position);
3936 let end = map.buffer_snapshot.anchor_after(position);
3937 while let Some(existing_state) = this.autoclose_regions.get(i) {
3938 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3939 Ordering::Less => i += 1,
3940 Ordering::Greater => break,
3941 Ordering::Equal => {
3942 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3943 Ordering::Less => i += 1,
3944 Ordering::Equal => break,
3945 Ordering::Greater => break,
3946 }
3947 }
3948 }
3949 }
3950 this.autoclose_regions.insert(
3951 i,
3952 AutocloseRegion {
3953 selection_id,
3954 range: start..end,
3955 pair,
3956 },
3957 );
3958 }
3959
3960 let had_active_inline_completion = this.has_active_inline_completion();
3961 this.change_selections_without_showing_completions(
3962 Some(Autoscroll::fit()),
3963 window,
3964 cx,
3965 |s| s.select(new_selections),
3966 );
3967
3968 if !bracket_inserted {
3969 if let Some(on_type_format_task) =
3970 this.trigger_on_type_formatting(text.to_string(), window, cx)
3971 {
3972 on_type_format_task.detach_and_log_err(cx);
3973 }
3974 }
3975
3976 let editor_settings = EditorSettings::get_global(cx);
3977 if bracket_inserted
3978 && (editor_settings.auto_signature_help
3979 || editor_settings.show_signature_help_after_edits)
3980 {
3981 this.show_signature_help(&ShowSignatureHelp, window, cx);
3982 }
3983
3984 let trigger_in_words =
3985 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3986 if this.hard_wrap.is_some() {
3987 let latest: Range<Point> = this.selections.newest(cx).range();
3988 if latest.is_empty()
3989 && this
3990 .buffer()
3991 .read(cx)
3992 .snapshot(cx)
3993 .line_len(MultiBufferRow(latest.start.row))
3994 == latest.start.column
3995 {
3996 this.rewrap_impl(
3997 RewrapOptions {
3998 override_language_settings: true,
3999 preserve_existing_whitespace: true,
4000 },
4001 cx,
4002 )
4003 }
4004 }
4005 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4006 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4007 this.refresh_inline_completion(true, false, window, cx);
4008 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4009 });
4010 }
4011
4012 fn find_possible_emoji_shortcode_at_position(
4013 snapshot: &MultiBufferSnapshot,
4014 position: Point,
4015 ) -> Option<String> {
4016 let mut chars = Vec::new();
4017 let mut found_colon = false;
4018 for char in snapshot.reversed_chars_at(position).take(100) {
4019 // Found a possible emoji shortcode in the middle of the buffer
4020 if found_colon {
4021 if char.is_whitespace() {
4022 chars.reverse();
4023 return Some(chars.iter().collect());
4024 }
4025 // If the previous character is not a whitespace, we are in the middle of a word
4026 // and we only want to complete the shortcode if the word is made up of other emojis
4027 let mut containing_word = String::new();
4028 for ch in snapshot
4029 .reversed_chars_at(position)
4030 .skip(chars.len() + 1)
4031 .take(100)
4032 {
4033 if ch.is_whitespace() {
4034 break;
4035 }
4036 containing_word.push(ch);
4037 }
4038 let containing_word = containing_word.chars().rev().collect::<String>();
4039 if util::word_consists_of_emojis(containing_word.as_str()) {
4040 chars.reverse();
4041 return Some(chars.iter().collect());
4042 }
4043 }
4044
4045 if char.is_whitespace() || !char.is_ascii() {
4046 return None;
4047 }
4048 if char == ':' {
4049 found_colon = true;
4050 } else {
4051 chars.push(char);
4052 }
4053 }
4054 // Found a possible emoji shortcode at the beginning of the buffer
4055 chars.reverse();
4056 Some(chars.iter().collect())
4057 }
4058
4059 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4060 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4061 self.transact(window, cx, |this, window, cx| {
4062 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4063 let selections = this.selections.all::<usize>(cx);
4064 let multi_buffer = this.buffer.read(cx);
4065 let buffer = multi_buffer.snapshot(cx);
4066 selections
4067 .iter()
4068 .map(|selection| {
4069 let start_point = selection.start.to_point(&buffer);
4070 let mut existing_indent =
4071 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4072 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4073 let start = selection.start;
4074 let end = selection.end;
4075 let selection_is_empty = start == end;
4076 let language_scope = buffer.language_scope_at(start);
4077 let (
4078 comment_delimiter,
4079 doc_delimiter,
4080 insert_extra_newline,
4081 indent_on_newline,
4082 indent_on_extra_newline,
4083 ) = if let Some(language) = &language_scope {
4084 let mut insert_extra_newline =
4085 insert_extra_newline_brackets(&buffer, start..end, language)
4086 || insert_extra_newline_tree_sitter(&buffer, start..end);
4087
4088 // Comment extension on newline is allowed only for cursor selections
4089 let comment_delimiter = maybe!({
4090 if !selection_is_empty {
4091 return None;
4092 }
4093
4094 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4095 return None;
4096 }
4097
4098 let delimiters = language.line_comment_prefixes();
4099 let max_len_of_delimiter =
4100 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4101 let (snapshot, range) =
4102 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4103
4104 let num_of_whitespaces = snapshot
4105 .chars_for_range(range.clone())
4106 .take_while(|c| c.is_whitespace())
4107 .count();
4108 let comment_candidate = snapshot
4109 .chars_for_range(range)
4110 .skip(num_of_whitespaces)
4111 .take(max_len_of_delimiter)
4112 .collect::<String>();
4113 let (delimiter, trimmed_len) = delimiters
4114 .iter()
4115 .filter_map(|delimiter| {
4116 let prefix = delimiter.trim_end();
4117 if comment_candidate.starts_with(prefix) {
4118 Some((delimiter, prefix.len()))
4119 } else {
4120 None
4121 }
4122 })
4123 .max_by_key(|(_, len)| *len)?;
4124
4125 let cursor_is_placed_after_comment_marker =
4126 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4127 if cursor_is_placed_after_comment_marker {
4128 Some(delimiter.clone())
4129 } else {
4130 None
4131 }
4132 });
4133
4134 let mut indent_on_newline = IndentSize::spaces(0);
4135 let mut indent_on_extra_newline = IndentSize::spaces(0);
4136
4137 let doc_delimiter = maybe!({
4138 if !selection_is_empty {
4139 return None;
4140 }
4141
4142 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4143 return None;
4144 }
4145
4146 let DocumentationConfig {
4147 start: start_tag,
4148 end: end_tag,
4149 prefix: delimiter,
4150 tab_size: len,
4151 } = language.documentation()?;
4152
4153 let is_within_block_comment = buffer
4154 .language_scope_at(start_point)
4155 .is_some_and(|scope| scope.override_name() == Some("comment"));
4156 if !is_within_block_comment {
4157 return None;
4158 }
4159
4160 let (snapshot, range) =
4161 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4162
4163 let num_of_whitespaces = snapshot
4164 .chars_for_range(range.clone())
4165 .take_while(|c| c.is_whitespace())
4166 .count();
4167
4168 // It is safe to use a column from MultiBufferPoint in context of a single buffer ranges, because we're only ever looking at a single line at a time.
4169 let column = start_point.column;
4170 let cursor_is_after_start_tag = {
4171 let start_tag_len = start_tag.len();
4172 let start_tag_line = snapshot
4173 .chars_for_range(range.clone())
4174 .skip(num_of_whitespaces)
4175 .take(start_tag_len)
4176 .collect::<String>();
4177 if start_tag_line.starts_with(start_tag.as_ref()) {
4178 num_of_whitespaces + start_tag_len <= column as usize
4179 } else {
4180 false
4181 }
4182 };
4183
4184 let cursor_is_after_delimiter = {
4185 let delimiter_trim = delimiter.trim_end();
4186 let delimiter_line = snapshot
4187 .chars_for_range(range.clone())
4188 .skip(num_of_whitespaces)
4189 .take(delimiter_trim.len())
4190 .collect::<String>();
4191 if delimiter_line.starts_with(delimiter_trim) {
4192 num_of_whitespaces + delimiter_trim.len() <= column as usize
4193 } else {
4194 false
4195 }
4196 };
4197
4198 let cursor_is_before_end_tag_if_exists = {
4199 let mut char_position = 0u32;
4200 let mut end_tag_offset = None;
4201
4202 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4203 if let Some(byte_pos) = chunk.find(&**end_tag) {
4204 let chars_before_match =
4205 chunk[..byte_pos].chars().count() as u32;
4206 end_tag_offset =
4207 Some(char_position + chars_before_match);
4208 break 'outer;
4209 }
4210 char_position += chunk.chars().count() as u32;
4211 }
4212
4213 if let Some(end_tag_offset) = end_tag_offset {
4214 let cursor_is_before_end_tag = column <= end_tag_offset;
4215 if cursor_is_after_start_tag {
4216 if cursor_is_before_end_tag {
4217 insert_extra_newline = true;
4218 }
4219 let cursor_is_at_start_of_end_tag =
4220 column == end_tag_offset;
4221 if cursor_is_at_start_of_end_tag {
4222 indent_on_extra_newline.len = (*len).into();
4223 }
4224 }
4225 cursor_is_before_end_tag
4226 } else {
4227 true
4228 }
4229 };
4230
4231 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4232 && cursor_is_before_end_tag_if_exists
4233 {
4234 if cursor_is_after_start_tag {
4235 indent_on_newline.len = (*len).into();
4236 }
4237 Some(delimiter.clone())
4238 } else {
4239 None
4240 }
4241 });
4242
4243 (
4244 comment_delimiter,
4245 doc_delimiter,
4246 insert_extra_newline,
4247 indent_on_newline,
4248 indent_on_extra_newline,
4249 )
4250 } else {
4251 (
4252 None,
4253 None,
4254 false,
4255 IndentSize::default(),
4256 IndentSize::default(),
4257 )
4258 };
4259
4260 let prevent_auto_indent = doc_delimiter.is_some();
4261 let delimiter = comment_delimiter.or(doc_delimiter);
4262
4263 let capacity_for_delimiter =
4264 delimiter.as_deref().map(str::len).unwrap_or_default();
4265 let mut new_text = String::with_capacity(
4266 1 + capacity_for_delimiter
4267 + existing_indent.len as usize
4268 + indent_on_newline.len as usize
4269 + indent_on_extra_newline.len as usize,
4270 );
4271 new_text.push('\n');
4272 new_text.extend(existing_indent.chars());
4273 new_text.extend(indent_on_newline.chars());
4274
4275 if let Some(delimiter) = &delimiter {
4276 new_text.push_str(delimiter);
4277 }
4278
4279 if insert_extra_newline {
4280 new_text.push('\n');
4281 new_text.extend(existing_indent.chars());
4282 new_text.extend(indent_on_extra_newline.chars());
4283 }
4284
4285 let anchor = buffer.anchor_after(end);
4286 let new_selection = selection.map(|_| anchor);
4287 (
4288 ((start..end, new_text), prevent_auto_indent),
4289 (insert_extra_newline, new_selection),
4290 )
4291 })
4292 .unzip()
4293 };
4294
4295 let mut auto_indent_edits = Vec::new();
4296 let mut edits = Vec::new();
4297 for (edit, prevent_auto_indent) in edits_with_flags {
4298 if prevent_auto_indent {
4299 edits.push(edit);
4300 } else {
4301 auto_indent_edits.push(edit);
4302 }
4303 }
4304 if !edits.is_empty() {
4305 this.edit(edits, cx);
4306 }
4307 if !auto_indent_edits.is_empty() {
4308 this.edit_with_autoindent(auto_indent_edits, cx);
4309 }
4310
4311 let buffer = this.buffer.read(cx).snapshot(cx);
4312 let new_selections = selection_info
4313 .into_iter()
4314 .map(|(extra_newline_inserted, new_selection)| {
4315 let mut cursor = new_selection.end.to_point(&buffer);
4316 if extra_newline_inserted {
4317 cursor.row -= 1;
4318 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4319 }
4320 new_selection.map(|_| cursor)
4321 })
4322 .collect();
4323
4324 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4325 s.select(new_selections)
4326 });
4327 this.refresh_inline_completion(true, false, window, cx);
4328 });
4329 }
4330
4331 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4332 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4333
4334 let buffer = self.buffer.read(cx);
4335 let snapshot = buffer.snapshot(cx);
4336
4337 let mut edits = Vec::new();
4338 let mut rows = Vec::new();
4339
4340 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4341 let cursor = selection.head();
4342 let row = cursor.row;
4343
4344 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4345
4346 let newline = "\n".to_string();
4347 edits.push((start_of_line..start_of_line, newline));
4348
4349 rows.push(row + rows_inserted as u32);
4350 }
4351
4352 self.transact(window, cx, |editor, window, cx| {
4353 editor.edit(edits, cx);
4354
4355 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4356 let mut index = 0;
4357 s.move_cursors_with(|map, _, _| {
4358 let row = rows[index];
4359 index += 1;
4360
4361 let point = Point::new(row, 0);
4362 let boundary = map.next_line_boundary(point).1;
4363 let clipped = map.clip_point(boundary, Bias::Left);
4364
4365 (clipped, SelectionGoal::None)
4366 });
4367 });
4368
4369 let mut indent_edits = Vec::new();
4370 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4371 for row in rows {
4372 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4373 for (row, indent) in indents {
4374 if indent.len == 0 {
4375 continue;
4376 }
4377
4378 let text = match indent.kind {
4379 IndentKind::Space => " ".repeat(indent.len as usize),
4380 IndentKind::Tab => "\t".repeat(indent.len as usize),
4381 };
4382 let point = Point::new(row.0, 0);
4383 indent_edits.push((point..point, text));
4384 }
4385 }
4386 editor.edit(indent_edits, cx);
4387 });
4388 }
4389
4390 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4391 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4392
4393 let buffer = self.buffer.read(cx);
4394 let snapshot = buffer.snapshot(cx);
4395
4396 let mut edits = Vec::new();
4397 let mut rows = Vec::new();
4398 let mut rows_inserted = 0;
4399
4400 for selection in self.selections.all_adjusted(cx) {
4401 let cursor = selection.head();
4402 let row = cursor.row;
4403
4404 let point = Point::new(row + 1, 0);
4405 let start_of_line = snapshot.clip_point(point, Bias::Left);
4406
4407 let newline = "\n".to_string();
4408 edits.push((start_of_line..start_of_line, newline));
4409
4410 rows_inserted += 1;
4411 rows.push(row + rows_inserted);
4412 }
4413
4414 self.transact(window, cx, |editor, window, cx| {
4415 editor.edit(edits, cx);
4416
4417 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4418 let mut index = 0;
4419 s.move_cursors_with(|map, _, _| {
4420 let row = rows[index];
4421 index += 1;
4422
4423 let point = Point::new(row, 0);
4424 let boundary = map.next_line_boundary(point).1;
4425 let clipped = map.clip_point(boundary, Bias::Left);
4426
4427 (clipped, SelectionGoal::None)
4428 });
4429 });
4430
4431 let mut indent_edits = Vec::new();
4432 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4433 for row in rows {
4434 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4435 for (row, indent) in indents {
4436 if indent.len == 0 {
4437 continue;
4438 }
4439
4440 let text = match indent.kind {
4441 IndentKind::Space => " ".repeat(indent.len as usize),
4442 IndentKind::Tab => "\t".repeat(indent.len as usize),
4443 };
4444 let point = Point::new(row.0, 0);
4445 indent_edits.push((point..point, text));
4446 }
4447 }
4448 editor.edit(indent_edits, cx);
4449 });
4450 }
4451
4452 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4453 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4454 original_indent_columns: Vec::new(),
4455 });
4456 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4457 }
4458
4459 fn insert_with_autoindent_mode(
4460 &mut self,
4461 text: &str,
4462 autoindent_mode: Option<AutoindentMode>,
4463 window: &mut Window,
4464 cx: &mut Context<Self>,
4465 ) {
4466 if self.read_only(cx) {
4467 return;
4468 }
4469
4470 let text: Arc<str> = text.into();
4471 self.transact(window, cx, |this, window, cx| {
4472 let old_selections = this.selections.all_adjusted(cx);
4473 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4474 let anchors = {
4475 let snapshot = buffer.read(cx);
4476 old_selections
4477 .iter()
4478 .map(|s| {
4479 let anchor = snapshot.anchor_after(s.head());
4480 s.map(|_| anchor)
4481 })
4482 .collect::<Vec<_>>()
4483 };
4484 buffer.edit(
4485 old_selections
4486 .iter()
4487 .map(|s| (s.start..s.end, text.clone())),
4488 autoindent_mode,
4489 cx,
4490 );
4491 anchors
4492 });
4493
4494 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4495 s.select_anchors(selection_anchors);
4496 });
4497
4498 cx.notify();
4499 });
4500 }
4501
4502 fn trigger_completion_on_input(
4503 &mut self,
4504 text: &str,
4505 trigger_in_words: bool,
4506 window: &mut Window,
4507 cx: &mut Context<Self>,
4508 ) {
4509 let ignore_completion_provider = self
4510 .context_menu
4511 .borrow()
4512 .as_ref()
4513 .map(|menu| match menu {
4514 CodeContextMenu::Completions(completions_menu) => {
4515 completions_menu.ignore_completion_provider
4516 }
4517 CodeContextMenu::CodeActions(_) => false,
4518 })
4519 .unwrap_or(false);
4520
4521 if ignore_completion_provider {
4522 self.show_word_completions(&ShowWordCompletions, window, cx);
4523 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
4524 self.show_completions(
4525 &ShowCompletions {
4526 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4527 },
4528 window,
4529 cx,
4530 );
4531 } else {
4532 self.hide_context_menu(window, cx);
4533 }
4534 }
4535
4536 fn is_completion_trigger(
4537 &self,
4538 text: &str,
4539 trigger_in_words: bool,
4540 cx: &mut Context<Self>,
4541 ) -> bool {
4542 let position = self.selections.newest_anchor().head();
4543 let multibuffer = self.buffer.read(cx);
4544 let Some(buffer) = position
4545 .buffer_id
4546 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4547 else {
4548 return false;
4549 };
4550
4551 if let Some(completion_provider) = &self.completion_provider {
4552 completion_provider.is_completion_trigger(
4553 &buffer,
4554 position.text_anchor,
4555 text,
4556 trigger_in_words,
4557 cx,
4558 )
4559 } else {
4560 false
4561 }
4562 }
4563
4564 /// If any empty selections is touching the start of its innermost containing autoclose
4565 /// region, expand it to select the brackets.
4566 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4567 let selections = self.selections.all::<usize>(cx);
4568 let buffer = self.buffer.read(cx).read(cx);
4569 let new_selections = self
4570 .selections_with_autoclose_regions(selections, &buffer)
4571 .map(|(mut selection, region)| {
4572 if !selection.is_empty() {
4573 return selection;
4574 }
4575
4576 if let Some(region) = region {
4577 let mut range = region.range.to_offset(&buffer);
4578 if selection.start == range.start && range.start >= region.pair.start.len() {
4579 range.start -= region.pair.start.len();
4580 if buffer.contains_str_at(range.start, ®ion.pair.start)
4581 && buffer.contains_str_at(range.end, ®ion.pair.end)
4582 {
4583 range.end += region.pair.end.len();
4584 selection.start = range.start;
4585 selection.end = range.end;
4586
4587 return selection;
4588 }
4589 }
4590 }
4591
4592 let always_treat_brackets_as_autoclosed = buffer
4593 .language_settings_at(selection.start, cx)
4594 .always_treat_brackets_as_autoclosed;
4595
4596 if !always_treat_brackets_as_autoclosed {
4597 return selection;
4598 }
4599
4600 if let Some(scope) = buffer.language_scope_at(selection.start) {
4601 for (pair, enabled) in scope.brackets() {
4602 if !enabled || !pair.close {
4603 continue;
4604 }
4605
4606 if buffer.contains_str_at(selection.start, &pair.end) {
4607 let pair_start_len = pair.start.len();
4608 if buffer.contains_str_at(
4609 selection.start.saturating_sub(pair_start_len),
4610 &pair.start,
4611 ) {
4612 selection.start -= pair_start_len;
4613 selection.end += pair.end.len();
4614
4615 return selection;
4616 }
4617 }
4618 }
4619 }
4620
4621 selection
4622 })
4623 .collect();
4624
4625 drop(buffer);
4626 self.change_selections(None, window, cx, |selections| {
4627 selections.select(new_selections)
4628 });
4629 }
4630
4631 /// Iterate the given selections, and for each one, find the smallest surrounding
4632 /// autoclose region. This uses the ordering of the selections and the autoclose
4633 /// regions to avoid repeated comparisons.
4634 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4635 &'a self,
4636 selections: impl IntoIterator<Item = Selection<D>>,
4637 buffer: &'a MultiBufferSnapshot,
4638 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4639 let mut i = 0;
4640 let mut regions = self.autoclose_regions.as_slice();
4641 selections.into_iter().map(move |selection| {
4642 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4643
4644 let mut enclosing = None;
4645 while let Some(pair_state) = regions.get(i) {
4646 if pair_state.range.end.to_offset(buffer) < range.start {
4647 regions = ®ions[i + 1..];
4648 i = 0;
4649 } else if pair_state.range.start.to_offset(buffer) > range.end {
4650 break;
4651 } else {
4652 if pair_state.selection_id == selection.id {
4653 enclosing = Some(pair_state);
4654 }
4655 i += 1;
4656 }
4657 }
4658
4659 (selection, enclosing)
4660 })
4661 }
4662
4663 /// Remove any autoclose regions that no longer contain their selection.
4664 fn invalidate_autoclose_regions(
4665 &mut self,
4666 mut selections: &[Selection<Anchor>],
4667 buffer: &MultiBufferSnapshot,
4668 ) {
4669 self.autoclose_regions.retain(|state| {
4670 let mut i = 0;
4671 while let Some(selection) = selections.get(i) {
4672 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4673 selections = &selections[1..];
4674 continue;
4675 }
4676 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4677 break;
4678 }
4679 if selection.id == state.selection_id {
4680 return true;
4681 } else {
4682 i += 1;
4683 }
4684 }
4685 false
4686 });
4687 }
4688
4689 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4690 let offset = position.to_offset(buffer);
4691 let (word_range, kind) = buffer.surrounding_word(offset, true);
4692 if offset > word_range.start && kind == Some(CharKind::Word) {
4693 Some(
4694 buffer
4695 .text_for_range(word_range.start..offset)
4696 .collect::<String>(),
4697 )
4698 } else {
4699 None
4700 }
4701 }
4702
4703 pub fn toggle_inline_values(
4704 &mut self,
4705 _: &ToggleInlineValues,
4706 _: &mut Window,
4707 cx: &mut Context<Self>,
4708 ) {
4709 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4710
4711 self.refresh_inline_values(cx);
4712 }
4713
4714 pub fn toggle_inlay_hints(
4715 &mut self,
4716 _: &ToggleInlayHints,
4717 _: &mut Window,
4718 cx: &mut Context<Self>,
4719 ) {
4720 self.refresh_inlay_hints(
4721 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4722 cx,
4723 );
4724 }
4725
4726 pub fn inlay_hints_enabled(&self) -> bool {
4727 self.inlay_hint_cache.enabled
4728 }
4729
4730 pub fn inline_values_enabled(&self) -> bool {
4731 self.inline_value_cache.enabled
4732 }
4733
4734 #[cfg(any(test, feature = "test-support"))]
4735 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4736 self.display_map
4737 .read(cx)
4738 .current_inlays()
4739 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4740 .cloned()
4741 .collect()
4742 }
4743
4744 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4745 if self.semantics_provider.is_none() || !self.mode.is_full() {
4746 return;
4747 }
4748
4749 let reason_description = reason.description();
4750 let ignore_debounce = matches!(
4751 reason,
4752 InlayHintRefreshReason::SettingsChange(_)
4753 | InlayHintRefreshReason::Toggle(_)
4754 | InlayHintRefreshReason::ExcerptsRemoved(_)
4755 | InlayHintRefreshReason::ModifiersChanged(_)
4756 );
4757 let (invalidate_cache, required_languages) = match reason {
4758 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4759 match self.inlay_hint_cache.modifiers_override(enabled) {
4760 Some(enabled) => {
4761 if enabled {
4762 (InvalidationStrategy::RefreshRequested, None)
4763 } else {
4764 self.splice_inlays(
4765 &self
4766 .visible_inlay_hints(cx)
4767 .iter()
4768 .map(|inlay| inlay.id)
4769 .collect::<Vec<InlayId>>(),
4770 Vec::new(),
4771 cx,
4772 );
4773 return;
4774 }
4775 }
4776 None => return,
4777 }
4778 }
4779 InlayHintRefreshReason::Toggle(enabled) => {
4780 if self.inlay_hint_cache.toggle(enabled) {
4781 if enabled {
4782 (InvalidationStrategy::RefreshRequested, None)
4783 } else {
4784 self.splice_inlays(
4785 &self
4786 .visible_inlay_hints(cx)
4787 .iter()
4788 .map(|inlay| inlay.id)
4789 .collect::<Vec<InlayId>>(),
4790 Vec::new(),
4791 cx,
4792 );
4793 return;
4794 }
4795 } else {
4796 return;
4797 }
4798 }
4799 InlayHintRefreshReason::SettingsChange(new_settings) => {
4800 match self.inlay_hint_cache.update_settings(
4801 &self.buffer,
4802 new_settings,
4803 self.visible_inlay_hints(cx),
4804 cx,
4805 ) {
4806 ControlFlow::Break(Some(InlaySplice {
4807 to_remove,
4808 to_insert,
4809 })) => {
4810 self.splice_inlays(&to_remove, to_insert, cx);
4811 return;
4812 }
4813 ControlFlow::Break(None) => return,
4814 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4815 }
4816 }
4817 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4818 if let Some(InlaySplice {
4819 to_remove,
4820 to_insert,
4821 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4822 {
4823 self.splice_inlays(&to_remove, to_insert, cx);
4824 }
4825 self.display_map.update(cx, |display_map, _| {
4826 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4827 });
4828 return;
4829 }
4830 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4831 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4832 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4833 }
4834 InlayHintRefreshReason::RefreshRequested => {
4835 (InvalidationStrategy::RefreshRequested, None)
4836 }
4837 };
4838
4839 if let Some(InlaySplice {
4840 to_remove,
4841 to_insert,
4842 }) = self.inlay_hint_cache.spawn_hint_refresh(
4843 reason_description,
4844 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4845 invalidate_cache,
4846 ignore_debounce,
4847 cx,
4848 ) {
4849 self.splice_inlays(&to_remove, to_insert, cx);
4850 }
4851 }
4852
4853 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4854 self.display_map
4855 .read(cx)
4856 .current_inlays()
4857 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4858 .cloned()
4859 .collect()
4860 }
4861
4862 pub fn excerpts_for_inlay_hints_query(
4863 &self,
4864 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4865 cx: &mut Context<Editor>,
4866 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4867 let Some(project) = self.project.as_ref() else {
4868 return HashMap::default();
4869 };
4870 let project = project.read(cx);
4871 let multi_buffer = self.buffer().read(cx);
4872 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4873 let multi_buffer_visible_start = self
4874 .scroll_manager
4875 .anchor()
4876 .anchor
4877 .to_point(&multi_buffer_snapshot);
4878 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4879 multi_buffer_visible_start
4880 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4881 Bias::Left,
4882 );
4883 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4884 multi_buffer_snapshot
4885 .range_to_buffer_ranges(multi_buffer_visible_range)
4886 .into_iter()
4887 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4888 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4889 let buffer_file = project::File::from_dyn(buffer.file())?;
4890 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4891 let worktree_entry = buffer_worktree
4892 .read(cx)
4893 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4894 if worktree_entry.is_ignored {
4895 return None;
4896 }
4897
4898 let language = buffer.language()?;
4899 if let Some(restrict_to_languages) = restrict_to_languages {
4900 if !restrict_to_languages.contains(language) {
4901 return None;
4902 }
4903 }
4904 Some((
4905 excerpt_id,
4906 (
4907 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4908 buffer.version().clone(),
4909 excerpt_visible_range,
4910 ),
4911 ))
4912 })
4913 .collect()
4914 }
4915
4916 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4917 TextLayoutDetails {
4918 text_system: window.text_system().clone(),
4919 editor_style: self.style.clone().unwrap(),
4920 rem_size: window.rem_size(),
4921 scroll_anchor: self.scroll_manager.anchor(),
4922 visible_rows: self.visible_line_count(),
4923 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4924 }
4925 }
4926
4927 pub fn splice_inlays(
4928 &self,
4929 to_remove: &[InlayId],
4930 to_insert: Vec<Inlay>,
4931 cx: &mut Context<Self>,
4932 ) {
4933 self.display_map.update(cx, |display_map, cx| {
4934 display_map.splice_inlays(to_remove, to_insert, cx)
4935 });
4936 cx.notify();
4937 }
4938
4939 fn trigger_on_type_formatting(
4940 &self,
4941 input: String,
4942 window: &mut Window,
4943 cx: &mut Context<Self>,
4944 ) -> Option<Task<Result<()>>> {
4945 if input.len() != 1 {
4946 return None;
4947 }
4948
4949 let project = self.project.as_ref()?;
4950 let position = self.selections.newest_anchor().head();
4951 let (buffer, buffer_position) = self
4952 .buffer
4953 .read(cx)
4954 .text_anchor_for_position(position, cx)?;
4955
4956 let settings = language_settings::language_settings(
4957 buffer
4958 .read(cx)
4959 .language_at(buffer_position)
4960 .map(|l| l.name()),
4961 buffer.read(cx).file(),
4962 cx,
4963 );
4964 if !settings.use_on_type_format {
4965 return None;
4966 }
4967
4968 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4969 // hence we do LSP request & edit on host side only — add formats to host's history.
4970 let push_to_lsp_host_history = true;
4971 // If this is not the host, append its history with new edits.
4972 let push_to_client_history = project.read(cx).is_via_collab();
4973
4974 let on_type_formatting = project.update(cx, |project, cx| {
4975 project.on_type_format(
4976 buffer.clone(),
4977 buffer_position,
4978 input,
4979 push_to_lsp_host_history,
4980 cx,
4981 )
4982 });
4983 Some(cx.spawn_in(window, async move |editor, cx| {
4984 if let Some(transaction) = on_type_formatting.await? {
4985 if push_to_client_history {
4986 buffer
4987 .update(cx, |buffer, _| {
4988 buffer.push_transaction(transaction, Instant::now());
4989 buffer.finalize_last_transaction();
4990 })
4991 .ok();
4992 }
4993 editor.update(cx, |editor, cx| {
4994 editor.refresh_document_highlights(cx);
4995 })?;
4996 }
4997 Ok(())
4998 }))
4999 }
5000
5001 pub fn show_word_completions(
5002 &mut self,
5003 _: &ShowWordCompletions,
5004 window: &mut Window,
5005 cx: &mut Context<Self>,
5006 ) {
5007 self.open_completions_menu(true, None, window, cx);
5008 }
5009
5010 pub fn show_completions(
5011 &mut self,
5012 options: &ShowCompletions,
5013 window: &mut Window,
5014 cx: &mut Context<Self>,
5015 ) {
5016 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
5017 }
5018
5019 fn open_completions_menu(
5020 &mut self,
5021 ignore_completion_provider: bool,
5022 trigger: Option<&str>,
5023 window: &mut Window,
5024 cx: &mut Context<Self>,
5025 ) {
5026 if self.pending_rename.is_some() {
5027 return;
5028 }
5029 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
5030 return;
5031 }
5032
5033 let position = self.selections.newest_anchor().head();
5034 if position.diff_base_anchor.is_some() {
5035 return;
5036 }
5037 let (buffer, buffer_position) =
5038 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5039 output
5040 } else {
5041 return;
5042 };
5043 let buffer_snapshot = buffer.read(cx).snapshot();
5044 let show_completion_documentation = buffer_snapshot
5045 .settings_at(buffer_position, cx)
5046 .show_completion_documentation;
5047
5048 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
5049
5050 let trigger_kind = match trigger {
5051 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5052 CompletionTriggerKind::TRIGGER_CHARACTER
5053 }
5054 _ => CompletionTriggerKind::INVOKED,
5055 };
5056 let completion_context = CompletionContext {
5057 trigger_character: trigger.and_then(|trigger| {
5058 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5059 Some(String::from(trigger))
5060 } else {
5061 None
5062 }
5063 }),
5064 trigger_kind,
5065 };
5066
5067 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
5068 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
5069 let word_to_exclude = buffer_snapshot
5070 .text_for_range(old_range.clone())
5071 .collect::<String>();
5072 (
5073 buffer_snapshot.anchor_before(old_range.start)
5074 ..buffer_snapshot.anchor_after(old_range.end),
5075 Some(word_to_exclude),
5076 )
5077 } else {
5078 (buffer_position..buffer_position, None)
5079 };
5080
5081 let language = buffer_snapshot
5082 .language_at(buffer_position)
5083 .map(|language| language.name());
5084
5085 let completion_settings =
5086 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5087
5088 // The document can be large, so stay in reasonable bounds when searching for words,
5089 // otherwise completion pop-up might be slow to appear.
5090 const WORD_LOOKUP_ROWS: u32 = 5_000;
5091 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5092 let min_word_search = buffer_snapshot.clip_point(
5093 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5094 Bias::Left,
5095 );
5096 let max_word_search = buffer_snapshot.clip_point(
5097 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5098 Bias::Right,
5099 );
5100 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5101 ..buffer_snapshot.point_to_offset(max_word_search);
5102
5103 let provider = if ignore_completion_provider {
5104 None
5105 } else {
5106 self.completion_provider.clone()
5107 };
5108 let skip_digits = query
5109 .as_ref()
5110 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5111
5112 let (mut words, provided_completions) = match &provider {
5113 Some(provider) => {
5114 let completions = provider.completions(
5115 position.excerpt_id,
5116 &buffer,
5117 buffer_position,
5118 completion_context,
5119 window,
5120 cx,
5121 );
5122
5123 let words = match completion_settings.words {
5124 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5125 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5126 .background_spawn(async move {
5127 buffer_snapshot.words_in_range(WordsQuery {
5128 fuzzy_contents: None,
5129 range: word_search_range,
5130 skip_digits,
5131 })
5132 }),
5133 };
5134
5135 (words, completions)
5136 }
5137 None => (
5138 cx.background_spawn(async move {
5139 buffer_snapshot.words_in_range(WordsQuery {
5140 fuzzy_contents: None,
5141 range: word_search_range,
5142 skip_digits,
5143 })
5144 }),
5145 Task::ready(Ok(None)),
5146 ),
5147 };
5148
5149 let sort_completions = provider
5150 .as_ref()
5151 .map_or(false, |provider| provider.sort_completions());
5152
5153 let filter_completions = provider
5154 .as_ref()
5155 .map_or(true, |provider| provider.filter_completions());
5156
5157 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5158
5159 let id = post_inc(&mut self.next_completion_id);
5160 let task = cx.spawn_in(window, async move |editor, cx| {
5161 async move {
5162 editor.update(cx, |this, _| {
5163 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5164 })?;
5165
5166 let mut completions = Vec::new();
5167 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
5168 completions.extend(provided_completions);
5169 if completion_settings.words == WordsCompletionMode::Fallback {
5170 words = Task::ready(BTreeMap::default());
5171 }
5172 }
5173
5174 let mut words = words.await;
5175 if let Some(word_to_exclude) = &word_to_exclude {
5176 words.remove(word_to_exclude);
5177 }
5178 for lsp_completion in &completions {
5179 words.remove(&lsp_completion.new_text);
5180 }
5181 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5182 replace_range: old_range.clone(),
5183 new_text: word.clone(),
5184 label: CodeLabel::plain(word, None),
5185 icon_path: None,
5186 documentation: None,
5187 source: CompletionSource::BufferWord {
5188 word_range,
5189 resolved: false,
5190 },
5191 insert_text_mode: Some(InsertTextMode::AS_IS),
5192 confirm: None,
5193 }));
5194
5195 let menu = if completions.is_empty() {
5196 None
5197 } else {
5198 let mut menu = editor.update(cx, |editor, cx| {
5199 let languages = editor
5200 .workspace
5201 .as_ref()
5202 .and_then(|(workspace, _)| workspace.upgrade())
5203 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5204 CompletionsMenu::new(
5205 id,
5206 sort_completions,
5207 show_completion_documentation,
5208 ignore_completion_provider,
5209 position,
5210 buffer.clone(),
5211 completions.into(),
5212 snippet_sort_order,
5213 languages,
5214 language,
5215 cx,
5216 )
5217 })?;
5218
5219 menu.filter(
5220 if filter_completions {
5221 query.as_deref()
5222 } else {
5223 None
5224 },
5225 provider,
5226 editor.clone(),
5227 cx,
5228 )
5229 .await;
5230
5231 menu.visible().then_some(menu)
5232 };
5233
5234 editor.update_in(cx, |editor, window, cx| {
5235 match editor.context_menu.borrow().as_ref() {
5236 None => {}
5237 Some(CodeContextMenu::Completions(prev_menu)) => {
5238 if prev_menu.id > id {
5239 return;
5240 }
5241 }
5242 _ => return,
5243 }
5244
5245 if editor.focus_handle.is_focused(window) && menu.is_some() {
5246 let mut menu = menu.unwrap();
5247 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
5248 crate::hover_popover::hide_hover(editor, cx);
5249 *editor.context_menu.borrow_mut() =
5250 Some(CodeContextMenu::Completions(menu));
5251
5252 if editor.show_edit_predictions_in_menu() {
5253 editor.update_visible_inline_completion(window, cx);
5254 } else {
5255 editor.discard_inline_completion(false, cx);
5256 }
5257
5258 cx.notify();
5259 } else if editor.completion_tasks.len() <= 1 {
5260 // If there are no more completion tasks and the last menu was
5261 // empty, we should hide it.
5262 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5263 // If it was already hidden and we don't show inline
5264 // completions in the menu, we should also show the
5265 // inline-completion when available.
5266 if was_hidden && editor.show_edit_predictions_in_menu() {
5267 editor.update_visible_inline_completion(window, cx);
5268 }
5269 }
5270 })?;
5271
5272 anyhow::Ok(())
5273 }
5274 .log_err()
5275 .await
5276 });
5277
5278 self.completion_tasks.push((id, task));
5279 }
5280
5281 #[cfg(feature = "test-support")]
5282 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5283 let menu = self.context_menu.borrow();
5284 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5285 let completions = menu.completions.borrow();
5286 Some(completions.to_vec())
5287 } else {
5288 None
5289 }
5290 }
5291
5292 pub fn with_completions_menu_matching_id<R>(
5293 &self,
5294 id: CompletionId,
5295 on_absent: impl FnOnce() -> R,
5296 on_match: impl FnOnce(&mut CompletionsMenu) -> R,
5297 ) -> R {
5298 let mut context_menu = self.context_menu.borrow_mut();
5299 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5300 return on_absent();
5301 };
5302 if completions_menu.id != id {
5303 return on_absent();
5304 }
5305 on_match(completions_menu)
5306 }
5307
5308 pub fn confirm_completion(
5309 &mut self,
5310 action: &ConfirmCompletion,
5311 window: &mut Window,
5312 cx: &mut Context<Self>,
5313 ) -> Option<Task<Result<()>>> {
5314 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5315 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5316 }
5317
5318 pub fn confirm_completion_insert(
5319 &mut self,
5320 _: &ConfirmCompletionInsert,
5321 window: &mut Window,
5322 cx: &mut Context<Self>,
5323 ) -> Option<Task<Result<()>>> {
5324 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5325 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5326 }
5327
5328 pub fn confirm_completion_replace(
5329 &mut self,
5330 _: &ConfirmCompletionReplace,
5331 window: &mut Window,
5332 cx: &mut Context<Self>,
5333 ) -> Option<Task<Result<()>>> {
5334 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5335 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5336 }
5337
5338 pub fn compose_completion(
5339 &mut self,
5340 action: &ComposeCompletion,
5341 window: &mut Window,
5342 cx: &mut Context<Self>,
5343 ) -> Option<Task<Result<()>>> {
5344 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5345 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5346 }
5347
5348 fn do_completion(
5349 &mut self,
5350 item_ix: Option<usize>,
5351 intent: CompletionIntent,
5352 window: &mut Window,
5353 cx: &mut Context<Editor>,
5354 ) -> Option<Task<Result<()>>> {
5355 use language::ToOffset as _;
5356
5357 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5358 else {
5359 return None;
5360 };
5361
5362 let candidate_id = {
5363 let entries = completions_menu.entries.borrow();
5364 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5365 if self.show_edit_predictions_in_menu() {
5366 self.discard_inline_completion(true, cx);
5367 }
5368 mat.candidate_id
5369 };
5370
5371 let buffer_handle = completions_menu.buffer;
5372 let completion = completions_menu
5373 .completions
5374 .borrow()
5375 .get(candidate_id)?
5376 .clone();
5377 cx.stop_propagation();
5378
5379 let snapshot = self.buffer.read(cx).snapshot(cx);
5380 let newest_anchor = self.selections.newest_anchor();
5381
5382 let snippet;
5383 let new_text;
5384 if completion.is_snippet() {
5385 let mut snippet_source = completion.new_text.clone();
5386 if let Some(scope) = snapshot.language_scope_at(newest_anchor.head()) {
5387 if scope.prefers_label_for_snippet_in_completion() {
5388 if let Some(label) = completion.label() {
5389 if matches!(
5390 completion.kind(),
5391 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
5392 ) {
5393 snippet_source = label;
5394 }
5395 }
5396 }
5397 }
5398 snippet = Some(Snippet::parse(&snippet_source).log_err()?);
5399 new_text = snippet.as_ref().unwrap().text.clone();
5400 } else {
5401 snippet = None;
5402 new_text = completion.new_text.clone();
5403 };
5404
5405 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
5406 let buffer = buffer_handle.read(cx);
5407 let replace_range_multibuffer = {
5408 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5409 let multibuffer_anchor = snapshot
5410 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5411 .unwrap()
5412 ..snapshot
5413 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5414 .unwrap();
5415 multibuffer_anchor.start.to_offset(&snapshot)
5416 ..multibuffer_anchor.end.to_offset(&snapshot)
5417 };
5418 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5419 return None;
5420 }
5421
5422 let old_text = buffer
5423 .text_for_range(replace_range.clone())
5424 .collect::<String>();
5425 let lookbehind = newest_anchor
5426 .start
5427 .text_anchor
5428 .to_offset(buffer)
5429 .saturating_sub(replace_range.start);
5430 let lookahead = replace_range
5431 .end
5432 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5433 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5434 let suffix = &old_text[lookbehind.min(old_text.len())..];
5435
5436 let selections = self.selections.all::<usize>(cx);
5437 let mut ranges = Vec::new();
5438 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5439
5440 for selection in &selections {
5441 let range = if selection.id == newest_anchor.id {
5442 replace_range_multibuffer.clone()
5443 } else {
5444 let mut range = selection.range();
5445
5446 // if prefix is present, don't duplicate it
5447 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5448 range.start = range.start.saturating_sub(lookbehind);
5449
5450 // if suffix is also present, mimic the newest cursor and replace it
5451 if selection.id != newest_anchor.id
5452 && snapshot.contains_str_at(range.end, suffix)
5453 {
5454 range.end += lookahead;
5455 }
5456 }
5457 range
5458 };
5459
5460 ranges.push(range.clone());
5461
5462 if !self.linked_edit_ranges.is_empty() {
5463 let start_anchor = snapshot.anchor_before(range.start);
5464 let end_anchor = snapshot.anchor_after(range.end);
5465 if let Some(ranges) = self
5466 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5467 {
5468 for (buffer, edits) in ranges {
5469 linked_edits
5470 .entry(buffer.clone())
5471 .or_default()
5472 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5473 }
5474 }
5475 }
5476 }
5477
5478 cx.emit(EditorEvent::InputHandled {
5479 utf16_range_to_replace: None,
5480 text: new_text.clone().into(),
5481 });
5482
5483 self.transact(window, cx, |this, window, cx| {
5484 if let Some(mut snippet) = snippet {
5485 snippet.text = new_text.to_string();
5486 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5487 } else {
5488 this.buffer.update(cx, |buffer, cx| {
5489 let auto_indent = match completion.insert_text_mode {
5490 Some(InsertTextMode::AS_IS) => None,
5491 _ => this.autoindent_mode.clone(),
5492 };
5493 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5494 buffer.edit(edits, auto_indent, cx);
5495 });
5496 }
5497 for (buffer, edits) in linked_edits {
5498 buffer.update(cx, |buffer, cx| {
5499 let snapshot = buffer.snapshot();
5500 let edits = edits
5501 .into_iter()
5502 .map(|(range, text)| {
5503 use text::ToPoint as TP;
5504 let end_point = TP::to_point(&range.end, &snapshot);
5505 let start_point = TP::to_point(&range.start, &snapshot);
5506 (start_point..end_point, text)
5507 })
5508 .sorted_by_key(|(range, _)| range.start);
5509 buffer.edit(edits, None, cx);
5510 })
5511 }
5512
5513 this.refresh_inline_completion(true, false, window, cx);
5514 });
5515
5516 let show_new_completions_on_confirm = completion
5517 .confirm
5518 .as_ref()
5519 .map_or(false, |confirm| confirm(intent, window, cx));
5520 if show_new_completions_on_confirm {
5521 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5522 }
5523
5524 let provider = self.completion_provider.as_ref()?;
5525 drop(completion);
5526 let apply_edits = provider.apply_additional_edits_for_completion(
5527 buffer_handle,
5528 completions_menu.completions.clone(),
5529 candidate_id,
5530 true,
5531 cx,
5532 );
5533
5534 let editor_settings = EditorSettings::get_global(cx);
5535 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5536 // After the code completion is finished, users often want to know what signatures are needed.
5537 // so we should automatically call signature_help
5538 self.show_signature_help(&ShowSignatureHelp, window, cx);
5539 }
5540
5541 Some(cx.foreground_executor().spawn(async move {
5542 apply_edits.await?;
5543 Ok(())
5544 }))
5545 }
5546
5547 pub fn toggle_code_actions(
5548 &mut self,
5549 action: &ToggleCodeActions,
5550 window: &mut Window,
5551 cx: &mut Context<Self>,
5552 ) {
5553 let quick_launch = action.quick_launch;
5554 let mut context_menu = self.context_menu.borrow_mut();
5555 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5556 if code_actions.deployed_from == action.deployed_from {
5557 // Toggle if we're selecting the same one
5558 *context_menu = None;
5559 cx.notify();
5560 return;
5561 } else {
5562 // Otherwise, clear it and start a new one
5563 *context_menu = None;
5564 cx.notify();
5565 }
5566 }
5567 drop(context_menu);
5568 let snapshot = self.snapshot(window, cx);
5569 let deployed_from = action.deployed_from.clone();
5570 let mut task = self.code_actions_task.take();
5571 let action = action.clone();
5572 cx.spawn_in(window, async move |editor, cx| {
5573 while let Some(prev_task) = task {
5574 prev_task.await.log_err();
5575 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5576 }
5577
5578 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5579 if editor.focus_handle.is_focused(window) {
5580 let multibuffer_point = match &action.deployed_from {
5581 Some(CodeActionSource::Indicator(row)) => {
5582 DisplayPoint::new(*row, 0).to_point(&snapshot)
5583 }
5584 _ => editor.selections.newest::<Point>(cx).head(),
5585 };
5586 let (buffer, buffer_row) = snapshot
5587 .buffer_snapshot
5588 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5589 .and_then(|(buffer_snapshot, range)| {
5590 editor
5591 .buffer
5592 .read(cx)
5593 .buffer(buffer_snapshot.remote_id())
5594 .map(|buffer| (buffer, range.start.row))
5595 })?;
5596 let (_, code_actions) = editor
5597 .available_code_actions
5598 .clone()
5599 .and_then(|(location, code_actions)| {
5600 let snapshot = location.buffer.read(cx).snapshot();
5601 let point_range = location.range.to_point(&snapshot);
5602 let point_range = point_range.start.row..=point_range.end.row;
5603 if point_range.contains(&buffer_row) {
5604 Some((location, code_actions))
5605 } else {
5606 None
5607 }
5608 })
5609 .unzip();
5610 let buffer_id = buffer.read(cx).remote_id();
5611 let tasks = editor
5612 .tasks
5613 .get(&(buffer_id, buffer_row))
5614 .map(|t| Arc::new(t.to_owned()));
5615 if tasks.is_none() && code_actions.is_none() {
5616 return None;
5617 }
5618
5619 editor.completion_tasks.clear();
5620 editor.discard_inline_completion(false, cx);
5621 let task_context =
5622 tasks
5623 .as_ref()
5624 .zip(editor.project.clone())
5625 .map(|(tasks, project)| {
5626 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5627 });
5628
5629 Some(cx.spawn_in(window, async move |editor, cx| {
5630 let task_context = match task_context {
5631 Some(task_context) => task_context.await,
5632 None => None,
5633 };
5634 let resolved_tasks =
5635 tasks
5636 .zip(task_context.clone())
5637 .map(|(tasks, task_context)| ResolvedTasks {
5638 templates: tasks.resolve(&task_context).collect(),
5639 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5640 multibuffer_point.row,
5641 tasks.column,
5642 )),
5643 });
5644 let debug_scenarios = editor.update(cx, |editor, cx| {
5645 if cx.has_flag::<DebuggerFeatureFlag>() {
5646 maybe!({
5647 let project = editor.project.as_ref()?;
5648 let dap_store = project.read(cx).dap_store();
5649 let mut scenarios = vec![];
5650 let resolved_tasks = resolved_tasks.as_ref()?;
5651 let buffer = buffer.read(cx);
5652 let language = buffer.language()?;
5653 let file = buffer.file();
5654 let debug_adapter =
5655 language_settings(language.name().into(), file, cx)
5656 .debuggers
5657 .first()
5658 .map(SharedString::from)
5659 .or_else(|| {
5660 language
5661 .config()
5662 .debuggers
5663 .first()
5664 .map(SharedString::from)
5665 })?;
5666
5667 dap_store.update(cx, |dap_store, cx| {
5668 for (_, task) in &resolved_tasks.templates {
5669 if let Some(scenario) = dap_store
5670 .debug_scenario_for_build_task(
5671 task.original_task().clone(),
5672 debug_adapter.clone().into(),
5673 task.display_label().to_owned().into(),
5674 cx,
5675 )
5676 {
5677 scenarios.push(scenario);
5678 }
5679 }
5680 });
5681 Some(scenarios)
5682 })
5683 .unwrap_or_default()
5684 } else {
5685 vec![]
5686 }
5687 })?;
5688 let spawn_straight_away = quick_launch
5689 && resolved_tasks
5690 .as_ref()
5691 .map_or(false, |tasks| tasks.templates.len() == 1)
5692 && code_actions
5693 .as_ref()
5694 .map_or(true, |actions| actions.is_empty())
5695 && debug_scenarios.is_empty();
5696 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5697 crate::hover_popover::hide_hover(editor, cx);
5698 *editor.context_menu.borrow_mut() =
5699 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5700 buffer,
5701 actions: CodeActionContents::new(
5702 resolved_tasks,
5703 code_actions,
5704 debug_scenarios,
5705 task_context.unwrap_or_default(),
5706 ),
5707 selected_item: Default::default(),
5708 scroll_handle: UniformListScrollHandle::default(),
5709 deployed_from,
5710 }));
5711 if spawn_straight_away {
5712 if let Some(task) = editor.confirm_code_action(
5713 &ConfirmCodeAction { item_ix: Some(0) },
5714 window,
5715 cx,
5716 ) {
5717 cx.notify();
5718 return task;
5719 }
5720 }
5721 cx.notify();
5722 Task::ready(Ok(()))
5723 }) {
5724 task.await
5725 } else {
5726 Ok(())
5727 }
5728 }))
5729 } else {
5730 Some(Task::ready(Ok(())))
5731 }
5732 })?;
5733 if let Some(task) = spawned_test_task {
5734 task.await?;
5735 }
5736
5737 anyhow::Ok(())
5738 })
5739 .detach_and_log_err(cx);
5740 }
5741
5742 pub fn confirm_code_action(
5743 &mut self,
5744 action: &ConfirmCodeAction,
5745 window: &mut Window,
5746 cx: &mut Context<Self>,
5747 ) -> Option<Task<Result<()>>> {
5748 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5749
5750 let actions_menu =
5751 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5752 menu
5753 } else {
5754 return None;
5755 };
5756
5757 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5758 let action = actions_menu.actions.get(action_ix)?;
5759 let title = action.label();
5760 let buffer = actions_menu.buffer;
5761 let workspace = self.workspace()?;
5762
5763 match action {
5764 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5765 workspace.update(cx, |workspace, cx| {
5766 workspace.schedule_resolved_task(
5767 task_source_kind,
5768 resolved_task,
5769 false,
5770 window,
5771 cx,
5772 );
5773
5774 Some(Task::ready(Ok(())))
5775 })
5776 }
5777 CodeActionsItem::CodeAction {
5778 excerpt_id,
5779 action,
5780 provider,
5781 } => {
5782 let apply_code_action =
5783 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5784 let workspace = workspace.downgrade();
5785 Some(cx.spawn_in(window, async move |editor, cx| {
5786 let project_transaction = apply_code_action.await?;
5787 Self::open_project_transaction(
5788 &editor,
5789 workspace,
5790 project_transaction,
5791 title,
5792 cx,
5793 )
5794 .await
5795 }))
5796 }
5797 CodeActionsItem::DebugScenario(scenario) => {
5798 let context = actions_menu.actions.context.clone();
5799
5800 workspace.update(cx, |workspace, cx| {
5801 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
5802 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5803 });
5804 Some(Task::ready(Ok(())))
5805 }
5806 }
5807 }
5808
5809 pub async fn open_project_transaction(
5810 this: &WeakEntity<Editor>,
5811 workspace: WeakEntity<Workspace>,
5812 transaction: ProjectTransaction,
5813 title: String,
5814 cx: &mut AsyncWindowContext,
5815 ) -> Result<()> {
5816 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5817 cx.update(|_, cx| {
5818 entries.sort_unstable_by_key(|(buffer, _)| {
5819 buffer.read(cx).file().map(|f| f.path().clone())
5820 });
5821 })?;
5822
5823 // If the project transaction's edits are all contained within this editor, then
5824 // avoid opening a new editor to display them.
5825
5826 if let Some((buffer, transaction)) = entries.first() {
5827 if entries.len() == 1 {
5828 let excerpt = this.update(cx, |editor, cx| {
5829 editor
5830 .buffer()
5831 .read(cx)
5832 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5833 })?;
5834 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5835 if excerpted_buffer == *buffer {
5836 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5837 let excerpt_range = excerpt_range.to_offset(buffer);
5838 buffer
5839 .edited_ranges_for_transaction::<usize>(transaction)
5840 .all(|range| {
5841 excerpt_range.start <= range.start
5842 && excerpt_range.end >= range.end
5843 })
5844 })?;
5845
5846 if all_edits_within_excerpt {
5847 return Ok(());
5848 }
5849 }
5850 }
5851 }
5852 } else {
5853 return Ok(());
5854 }
5855
5856 let mut ranges_to_highlight = Vec::new();
5857 let excerpt_buffer = cx.new(|cx| {
5858 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5859 for (buffer_handle, transaction) in &entries {
5860 let edited_ranges = buffer_handle
5861 .read(cx)
5862 .edited_ranges_for_transaction::<Point>(transaction)
5863 .collect::<Vec<_>>();
5864 let (ranges, _) = multibuffer.set_excerpts_for_path(
5865 PathKey::for_buffer(buffer_handle, cx),
5866 buffer_handle.clone(),
5867 edited_ranges,
5868 DEFAULT_MULTIBUFFER_CONTEXT,
5869 cx,
5870 );
5871
5872 ranges_to_highlight.extend(ranges);
5873 }
5874 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5875 multibuffer
5876 })?;
5877
5878 workspace.update_in(cx, |workspace, window, cx| {
5879 let project = workspace.project().clone();
5880 let editor =
5881 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5882 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5883 editor.update(cx, |editor, cx| {
5884 editor.highlight_background::<Self>(
5885 &ranges_to_highlight,
5886 |theme| theme.editor_highlighted_line_background,
5887 cx,
5888 );
5889 });
5890 })?;
5891
5892 Ok(())
5893 }
5894
5895 pub fn clear_code_action_providers(&mut self) {
5896 self.code_action_providers.clear();
5897 self.available_code_actions.take();
5898 }
5899
5900 pub fn add_code_action_provider(
5901 &mut self,
5902 provider: Rc<dyn CodeActionProvider>,
5903 window: &mut Window,
5904 cx: &mut Context<Self>,
5905 ) {
5906 if self
5907 .code_action_providers
5908 .iter()
5909 .any(|existing_provider| existing_provider.id() == provider.id())
5910 {
5911 return;
5912 }
5913
5914 self.code_action_providers.push(provider);
5915 self.refresh_code_actions(window, cx);
5916 }
5917
5918 pub fn remove_code_action_provider(
5919 &mut self,
5920 id: Arc<str>,
5921 window: &mut Window,
5922 cx: &mut Context<Self>,
5923 ) {
5924 self.code_action_providers
5925 .retain(|provider| provider.id() != id);
5926 self.refresh_code_actions(window, cx);
5927 }
5928
5929 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
5930 !self.code_action_providers.is_empty()
5931 && EditorSettings::get_global(cx).toolbar.code_actions
5932 }
5933
5934 pub fn has_available_code_actions(&self) -> bool {
5935 self.available_code_actions
5936 .as_ref()
5937 .is_some_and(|(_, actions)| !actions.is_empty())
5938 }
5939
5940 fn render_inline_code_actions(
5941 &self,
5942 icon_size: ui::IconSize,
5943 display_row: DisplayRow,
5944 is_active: bool,
5945 cx: &mut Context<Self>,
5946 ) -> AnyElement {
5947 let show_tooltip = !self.context_menu_visible();
5948 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
5949 .icon_size(icon_size)
5950 .shape(ui::IconButtonShape::Square)
5951 .style(ButtonStyle::Transparent)
5952 .icon_color(ui::Color::Hidden)
5953 .toggle_state(is_active)
5954 .when(show_tooltip, |this| {
5955 this.tooltip({
5956 let focus_handle = self.focus_handle.clone();
5957 move |window, cx| {
5958 Tooltip::for_action_in(
5959 "Toggle Code Actions",
5960 &ToggleCodeActions {
5961 deployed_from: None,
5962 quick_launch: false,
5963 },
5964 &focus_handle,
5965 window,
5966 cx,
5967 )
5968 }
5969 })
5970 })
5971 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
5972 window.focus(&editor.focus_handle(cx));
5973 editor.toggle_code_actions(
5974 &crate::actions::ToggleCodeActions {
5975 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
5976 display_row,
5977 )),
5978 quick_launch: false,
5979 },
5980 window,
5981 cx,
5982 );
5983 }))
5984 .into_any_element()
5985 }
5986
5987 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
5988 &self.context_menu
5989 }
5990
5991 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5992 let newest_selection = self.selections.newest_anchor().clone();
5993 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5994 let buffer = self.buffer.read(cx);
5995 if newest_selection.head().diff_base_anchor.is_some() {
5996 return None;
5997 }
5998 let (start_buffer, start) =
5999 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6000 let (end_buffer, end) =
6001 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6002 if start_buffer != end_buffer {
6003 return None;
6004 }
6005
6006 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6007 cx.background_executor()
6008 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6009 .await;
6010
6011 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6012 let providers = this.code_action_providers.clone();
6013 let tasks = this
6014 .code_action_providers
6015 .iter()
6016 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6017 .collect::<Vec<_>>();
6018 (providers, tasks)
6019 })?;
6020
6021 let mut actions = Vec::new();
6022 for (provider, provider_actions) in
6023 providers.into_iter().zip(future::join_all(tasks).await)
6024 {
6025 if let Some(provider_actions) = provider_actions.log_err() {
6026 actions.extend(provider_actions.into_iter().map(|action| {
6027 AvailableCodeAction {
6028 excerpt_id: newest_selection.start.excerpt_id,
6029 action,
6030 provider: provider.clone(),
6031 }
6032 }));
6033 }
6034 }
6035
6036 this.update(cx, |this, cx| {
6037 this.available_code_actions = if actions.is_empty() {
6038 None
6039 } else {
6040 Some((
6041 Location {
6042 buffer: start_buffer,
6043 range: start..end,
6044 },
6045 actions.into(),
6046 ))
6047 };
6048 cx.notify();
6049 })
6050 }));
6051 None
6052 }
6053
6054 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6055 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6056 self.show_git_blame_inline = false;
6057
6058 self.show_git_blame_inline_delay_task =
6059 Some(cx.spawn_in(window, async move |this, cx| {
6060 cx.background_executor().timer(delay).await;
6061
6062 this.update(cx, |this, cx| {
6063 this.show_git_blame_inline = true;
6064 cx.notify();
6065 })
6066 .log_err();
6067 }));
6068 }
6069 }
6070
6071 fn show_blame_popover(
6072 &mut self,
6073 blame_entry: &BlameEntry,
6074 position: gpui::Point<Pixels>,
6075 cx: &mut Context<Self>,
6076 ) {
6077 if let Some(state) = &mut self.inline_blame_popover {
6078 state.hide_task.take();
6079 cx.notify();
6080 } else {
6081 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6082 let show_task = cx.spawn(async move |editor, cx| {
6083 cx.background_executor()
6084 .timer(std::time::Duration::from_millis(delay))
6085 .await;
6086 editor
6087 .update(cx, |editor, cx| {
6088 if let Some(state) = &mut editor.inline_blame_popover {
6089 state.show_task = None;
6090 cx.notify();
6091 }
6092 })
6093 .ok();
6094 });
6095 let Some(blame) = self.blame.as_ref() else {
6096 return;
6097 };
6098 let blame = blame.read(cx);
6099 let details = blame.details_for_entry(&blame_entry);
6100 let markdown = cx.new(|cx| {
6101 Markdown::new(
6102 details
6103 .as_ref()
6104 .map(|message| message.message.clone())
6105 .unwrap_or_default(),
6106 None,
6107 None,
6108 cx,
6109 )
6110 });
6111 self.inline_blame_popover = Some(InlineBlamePopover {
6112 position,
6113 show_task: Some(show_task),
6114 hide_task: None,
6115 popover_bounds: None,
6116 popover_state: InlineBlamePopoverState {
6117 scroll_handle: ScrollHandle::new(),
6118 commit_message: details,
6119 markdown,
6120 },
6121 });
6122 }
6123 }
6124
6125 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6126 if let Some(state) = &mut self.inline_blame_popover {
6127 if state.show_task.is_some() {
6128 self.inline_blame_popover.take();
6129 cx.notify();
6130 } else {
6131 let hide_task = cx.spawn(async move |editor, cx| {
6132 cx.background_executor()
6133 .timer(std::time::Duration::from_millis(100))
6134 .await;
6135 editor
6136 .update(cx, |editor, cx| {
6137 editor.inline_blame_popover.take();
6138 cx.notify();
6139 })
6140 .ok();
6141 });
6142 state.hide_task = Some(hide_task);
6143 }
6144 }
6145 }
6146
6147 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6148 if self.pending_rename.is_some() {
6149 return None;
6150 }
6151
6152 let provider = self.semantics_provider.clone()?;
6153 let buffer = self.buffer.read(cx);
6154 let newest_selection = self.selections.newest_anchor().clone();
6155 let cursor_position = newest_selection.head();
6156 let (cursor_buffer, cursor_buffer_position) =
6157 buffer.text_anchor_for_position(cursor_position, cx)?;
6158 let (tail_buffer, tail_buffer_position) =
6159 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6160 if cursor_buffer != tail_buffer {
6161 return None;
6162 }
6163
6164 let snapshot = cursor_buffer.read(cx).snapshot();
6165 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6166 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6167 if start_word_range != end_word_range {
6168 self.document_highlights_task.take();
6169 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6170 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6171 return None;
6172 }
6173
6174 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6175 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6176 cx.background_executor()
6177 .timer(Duration::from_millis(debounce))
6178 .await;
6179
6180 let highlights = if let Some(highlights) = cx
6181 .update(|cx| {
6182 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6183 })
6184 .ok()
6185 .flatten()
6186 {
6187 highlights.await.log_err()
6188 } else {
6189 None
6190 };
6191
6192 if let Some(highlights) = highlights {
6193 this.update(cx, |this, cx| {
6194 if this.pending_rename.is_some() {
6195 return;
6196 }
6197
6198 let buffer_id = cursor_position.buffer_id;
6199 let buffer = this.buffer.read(cx);
6200 if !buffer
6201 .text_anchor_for_position(cursor_position, cx)
6202 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6203 {
6204 return;
6205 }
6206
6207 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6208 let mut write_ranges = Vec::new();
6209 let mut read_ranges = Vec::new();
6210 for highlight in highlights {
6211 for (excerpt_id, excerpt_range) in
6212 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6213 {
6214 let start = highlight
6215 .range
6216 .start
6217 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6218 let end = highlight
6219 .range
6220 .end
6221 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6222 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6223 continue;
6224 }
6225
6226 let range = Anchor {
6227 buffer_id,
6228 excerpt_id,
6229 text_anchor: start,
6230 diff_base_anchor: None,
6231 }..Anchor {
6232 buffer_id,
6233 excerpt_id,
6234 text_anchor: end,
6235 diff_base_anchor: None,
6236 };
6237 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6238 write_ranges.push(range);
6239 } else {
6240 read_ranges.push(range);
6241 }
6242 }
6243 }
6244
6245 this.highlight_background::<DocumentHighlightRead>(
6246 &read_ranges,
6247 |theme| theme.editor_document_highlight_read_background,
6248 cx,
6249 );
6250 this.highlight_background::<DocumentHighlightWrite>(
6251 &write_ranges,
6252 |theme| theme.editor_document_highlight_write_background,
6253 cx,
6254 );
6255 cx.notify();
6256 })
6257 .log_err();
6258 }
6259 }));
6260 None
6261 }
6262
6263 fn prepare_highlight_query_from_selection(
6264 &mut self,
6265 cx: &mut Context<Editor>,
6266 ) -> Option<(String, Range<Anchor>)> {
6267 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6268 return None;
6269 }
6270 if !EditorSettings::get_global(cx).selection_highlight {
6271 return None;
6272 }
6273 if self.selections.count() != 1 || self.selections.line_mode {
6274 return None;
6275 }
6276 let selection = self.selections.newest::<Point>(cx);
6277 if selection.is_empty() || selection.start.row != selection.end.row {
6278 return None;
6279 }
6280 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6281 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6282 let query = multi_buffer_snapshot
6283 .text_for_range(selection_anchor_range.clone())
6284 .collect::<String>();
6285 if query.trim().is_empty() {
6286 return None;
6287 }
6288 Some((query, selection_anchor_range))
6289 }
6290
6291 fn update_selection_occurrence_highlights(
6292 &mut self,
6293 query_text: String,
6294 query_range: Range<Anchor>,
6295 multi_buffer_range_to_query: Range<Point>,
6296 use_debounce: bool,
6297 window: &mut Window,
6298 cx: &mut Context<Editor>,
6299 ) -> Task<()> {
6300 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6301 cx.spawn_in(window, async move |editor, cx| {
6302 if use_debounce {
6303 cx.background_executor()
6304 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6305 .await;
6306 }
6307 let match_task = cx.background_spawn(async move {
6308 let buffer_ranges = multi_buffer_snapshot
6309 .range_to_buffer_ranges(multi_buffer_range_to_query)
6310 .into_iter()
6311 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6312 let mut match_ranges = Vec::new();
6313 let Ok(regex) = project::search::SearchQuery::text(
6314 query_text.clone(),
6315 false,
6316 false,
6317 false,
6318 Default::default(),
6319 Default::default(),
6320 false,
6321 None,
6322 ) else {
6323 return Vec::default();
6324 };
6325 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6326 match_ranges.extend(
6327 regex
6328 .search(&buffer_snapshot, Some(search_range.clone()))
6329 .await
6330 .into_iter()
6331 .filter_map(|match_range| {
6332 let match_start = buffer_snapshot
6333 .anchor_after(search_range.start + match_range.start);
6334 let match_end = buffer_snapshot
6335 .anchor_before(search_range.start + match_range.end);
6336 let match_anchor_range = Anchor::range_in_buffer(
6337 excerpt_id,
6338 buffer_snapshot.remote_id(),
6339 match_start..match_end,
6340 );
6341 (match_anchor_range != query_range).then_some(match_anchor_range)
6342 }),
6343 );
6344 }
6345 match_ranges
6346 });
6347 let match_ranges = match_task.await;
6348 editor
6349 .update_in(cx, |editor, _, cx| {
6350 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6351 if !match_ranges.is_empty() {
6352 editor.highlight_background::<SelectedTextHighlight>(
6353 &match_ranges,
6354 |theme| theme.editor_document_highlight_bracket_background,
6355 cx,
6356 )
6357 }
6358 })
6359 .log_err();
6360 })
6361 }
6362
6363 fn refresh_selected_text_highlights(
6364 &mut self,
6365 on_buffer_edit: bool,
6366 window: &mut Window,
6367 cx: &mut Context<Editor>,
6368 ) {
6369 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6370 else {
6371 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6372 self.quick_selection_highlight_task.take();
6373 self.debounced_selection_highlight_task.take();
6374 return;
6375 };
6376 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6377 if on_buffer_edit
6378 || self
6379 .quick_selection_highlight_task
6380 .as_ref()
6381 .map_or(true, |(prev_anchor_range, _)| {
6382 prev_anchor_range != &query_range
6383 })
6384 {
6385 let multi_buffer_visible_start = self
6386 .scroll_manager
6387 .anchor()
6388 .anchor
6389 .to_point(&multi_buffer_snapshot);
6390 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6391 multi_buffer_visible_start
6392 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6393 Bias::Left,
6394 );
6395 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6396 self.quick_selection_highlight_task = Some((
6397 query_range.clone(),
6398 self.update_selection_occurrence_highlights(
6399 query_text.clone(),
6400 query_range.clone(),
6401 multi_buffer_visible_range,
6402 false,
6403 window,
6404 cx,
6405 ),
6406 ));
6407 }
6408 if on_buffer_edit
6409 || self
6410 .debounced_selection_highlight_task
6411 .as_ref()
6412 .map_or(true, |(prev_anchor_range, _)| {
6413 prev_anchor_range != &query_range
6414 })
6415 {
6416 let multi_buffer_start = multi_buffer_snapshot
6417 .anchor_before(0)
6418 .to_point(&multi_buffer_snapshot);
6419 let multi_buffer_end = multi_buffer_snapshot
6420 .anchor_after(multi_buffer_snapshot.len())
6421 .to_point(&multi_buffer_snapshot);
6422 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6423 self.debounced_selection_highlight_task = Some((
6424 query_range.clone(),
6425 self.update_selection_occurrence_highlights(
6426 query_text,
6427 query_range,
6428 multi_buffer_full_range,
6429 true,
6430 window,
6431 cx,
6432 ),
6433 ));
6434 }
6435 }
6436
6437 pub fn refresh_inline_completion(
6438 &mut self,
6439 debounce: bool,
6440 user_requested: bool,
6441 window: &mut Window,
6442 cx: &mut Context<Self>,
6443 ) -> Option<()> {
6444 let provider = self.edit_prediction_provider()?;
6445 let cursor = self.selections.newest_anchor().head();
6446 let (buffer, cursor_buffer_position) =
6447 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6448
6449 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6450 self.discard_inline_completion(false, cx);
6451 return None;
6452 }
6453
6454 if !user_requested
6455 && (!self.should_show_edit_predictions()
6456 || !self.is_focused(window)
6457 || buffer.read(cx).is_empty())
6458 {
6459 self.discard_inline_completion(false, cx);
6460 return None;
6461 }
6462
6463 self.update_visible_inline_completion(window, cx);
6464 provider.refresh(
6465 self.project.clone(),
6466 buffer,
6467 cursor_buffer_position,
6468 debounce,
6469 cx,
6470 );
6471 Some(())
6472 }
6473
6474 fn show_edit_predictions_in_menu(&self) -> bool {
6475 match self.edit_prediction_settings {
6476 EditPredictionSettings::Disabled => false,
6477 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6478 }
6479 }
6480
6481 pub fn edit_predictions_enabled(&self) -> bool {
6482 match self.edit_prediction_settings {
6483 EditPredictionSettings::Disabled => false,
6484 EditPredictionSettings::Enabled { .. } => true,
6485 }
6486 }
6487
6488 fn edit_prediction_requires_modifier(&self) -> bool {
6489 match self.edit_prediction_settings {
6490 EditPredictionSettings::Disabled => false,
6491 EditPredictionSettings::Enabled {
6492 preview_requires_modifier,
6493 ..
6494 } => preview_requires_modifier,
6495 }
6496 }
6497
6498 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6499 if self.edit_prediction_provider.is_none() {
6500 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6501 } else {
6502 let selection = self.selections.newest_anchor();
6503 let cursor = selection.head();
6504
6505 if let Some((buffer, cursor_buffer_position)) =
6506 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6507 {
6508 self.edit_prediction_settings =
6509 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6510 }
6511 }
6512 }
6513
6514 fn edit_prediction_settings_at_position(
6515 &self,
6516 buffer: &Entity<Buffer>,
6517 buffer_position: language::Anchor,
6518 cx: &App,
6519 ) -> EditPredictionSettings {
6520 if !self.mode.is_full()
6521 || !self.show_inline_completions_override.unwrap_or(true)
6522 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6523 {
6524 return EditPredictionSettings::Disabled;
6525 }
6526
6527 let buffer = buffer.read(cx);
6528
6529 let file = buffer.file();
6530
6531 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6532 return EditPredictionSettings::Disabled;
6533 };
6534
6535 let by_provider = matches!(
6536 self.menu_inline_completions_policy,
6537 MenuInlineCompletionsPolicy::ByProvider
6538 );
6539
6540 let show_in_menu = by_provider
6541 && self
6542 .edit_prediction_provider
6543 .as_ref()
6544 .map_or(false, |provider| {
6545 provider.provider.show_completions_in_menu()
6546 });
6547
6548 let preview_requires_modifier =
6549 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6550
6551 EditPredictionSettings::Enabled {
6552 show_in_menu,
6553 preview_requires_modifier,
6554 }
6555 }
6556
6557 fn should_show_edit_predictions(&self) -> bool {
6558 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6559 }
6560
6561 pub fn edit_prediction_preview_is_active(&self) -> bool {
6562 matches!(
6563 self.edit_prediction_preview,
6564 EditPredictionPreview::Active { .. }
6565 )
6566 }
6567
6568 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6569 let cursor = self.selections.newest_anchor().head();
6570 if let Some((buffer, cursor_position)) =
6571 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6572 {
6573 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6574 } else {
6575 false
6576 }
6577 }
6578
6579 pub fn supports_minimap(&self, cx: &App) -> bool {
6580 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6581 }
6582
6583 fn edit_predictions_enabled_in_buffer(
6584 &self,
6585 buffer: &Entity<Buffer>,
6586 buffer_position: language::Anchor,
6587 cx: &App,
6588 ) -> bool {
6589 maybe!({
6590 if self.read_only(cx) {
6591 return Some(false);
6592 }
6593 let provider = self.edit_prediction_provider()?;
6594 if !provider.is_enabled(&buffer, buffer_position, cx) {
6595 return Some(false);
6596 }
6597 let buffer = buffer.read(cx);
6598 let Some(file) = buffer.file() else {
6599 return Some(true);
6600 };
6601 let settings = all_language_settings(Some(file), cx);
6602 Some(settings.edit_predictions_enabled_for_file(file, cx))
6603 })
6604 .unwrap_or(false)
6605 }
6606
6607 fn cycle_inline_completion(
6608 &mut self,
6609 direction: Direction,
6610 window: &mut Window,
6611 cx: &mut Context<Self>,
6612 ) -> Option<()> {
6613 let provider = self.edit_prediction_provider()?;
6614 let cursor = self.selections.newest_anchor().head();
6615 let (buffer, cursor_buffer_position) =
6616 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6617 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6618 return None;
6619 }
6620
6621 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6622 self.update_visible_inline_completion(window, cx);
6623
6624 Some(())
6625 }
6626
6627 pub fn show_inline_completion(
6628 &mut self,
6629 _: &ShowEditPrediction,
6630 window: &mut Window,
6631 cx: &mut Context<Self>,
6632 ) {
6633 if !self.has_active_inline_completion() {
6634 self.refresh_inline_completion(false, true, window, cx);
6635 return;
6636 }
6637
6638 self.update_visible_inline_completion(window, cx);
6639 }
6640
6641 pub fn display_cursor_names(
6642 &mut self,
6643 _: &DisplayCursorNames,
6644 window: &mut Window,
6645 cx: &mut Context<Self>,
6646 ) {
6647 self.show_cursor_names(window, cx);
6648 }
6649
6650 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6651 self.show_cursor_names = true;
6652 cx.notify();
6653 cx.spawn_in(window, async move |this, cx| {
6654 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6655 this.update(cx, |this, cx| {
6656 this.show_cursor_names = false;
6657 cx.notify()
6658 })
6659 .ok()
6660 })
6661 .detach();
6662 }
6663
6664 pub fn next_edit_prediction(
6665 &mut self,
6666 _: &NextEditPrediction,
6667 window: &mut Window,
6668 cx: &mut Context<Self>,
6669 ) {
6670 if self.has_active_inline_completion() {
6671 self.cycle_inline_completion(Direction::Next, window, cx);
6672 } else {
6673 let is_copilot_disabled = self
6674 .refresh_inline_completion(false, true, window, cx)
6675 .is_none();
6676 if is_copilot_disabled {
6677 cx.propagate();
6678 }
6679 }
6680 }
6681
6682 pub fn previous_edit_prediction(
6683 &mut self,
6684 _: &PreviousEditPrediction,
6685 window: &mut Window,
6686 cx: &mut Context<Self>,
6687 ) {
6688 if self.has_active_inline_completion() {
6689 self.cycle_inline_completion(Direction::Prev, window, cx);
6690 } else {
6691 let is_copilot_disabled = self
6692 .refresh_inline_completion(false, true, window, cx)
6693 .is_none();
6694 if is_copilot_disabled {
6695 cx.propagate();
6696 }
6697 }
6698 }
6699
6700 pub fn accept_edit_prediction(
6701 &mut self,
6702 _: &AcceptEditPrediction,
6703 window: &mut Window,
6704 cx: &mut Context<Self>,
6705 ) {
6706 if self.show_edit_predictions_in_menu() {
6707 self.hide_context_menu(window, cx);
6708 }
6709
6710 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6711 return;
6712 };
6713
6714 self.report_inline_completion_event(
6715 active_inline_completion.completion_id.clone(),
6716 true,
6717 cx,
6718 );
6719
6720 match &active_inline_completion.completion {
6721 InlineCompletion::Move { target, .. } => {
6722 let target = *target;
6723
6724 if let Some(position_map) = &self.last_position_map {
6725 if position_map
6726 .visible_row_range
6727 .contains(&target.to_display_point(&position_map.snapshot).row())
6728 || !self.edit_prediction_requires_modifier()
6729 {
6730 self.unfold_ranges(&[target..target], true, false, cx);
6731 // Note that this is also done in vim's handler of the Tab action.
6732 self.change_selections(
6733 Some(Autoscroll::newest()),
6734 window,
6735 cx,
6736 |selections| {
6737 selections.select_anchor_ranges([target..target]);
6738 },
6739 );
6740 self.clear_row_highlights::<EditPredictionPreview>();
6741
6742 self.edit_prediction_preview
6743 .set_previous_scroll_position(None);
6744 } else {
6745 self.edit_prediction_preview
6746 .set_previous_scroll_position(Some(
6747 position_map.snapshot.scroll_anchor,
6748 ));
6749
6750 self.highlight_rows::<EditPredictionPreview>(
6751 target..target,
6752 cx.theme().colors().editor_highlighted_line_background,
6753 RowHighlightOptions {
6754 autoscroll: true,
6755 ..Default::default()
6756 },
6757 cx,
6758 );
6759 self.request_autoscroll(Autoscroll::fit(), cx);
6760 }
6761 }
6762 }
6763 InlineCompletion::Edit { edits, .. } => {
6764 if let Some(provider) = self.edit_prediction_provider() {
6765 provider.accept(cx);
6766 }
6767
6768 // Store the transaction ID and selections before applying the edit
6769 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
6770
6771 let snapshot = self.buffer.read(cx).snapshot(cx);
6772 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6773
6774 self.buffer.update(cx, |buffer, cx| {
6775 buffer.edit(edits.iter().cloned(), None, cx)
6776 });
6777
6778 self.change_selections(None, window, cx, |s| {
6779 s.select_anchor_ranges([last_edit_end..last_edit_end]);
6780 });
6781
6782 let selections = self.selections.disjoint_anchors();
6783 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
6784 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
6785 if has_new_transaction {
6786 self.selection_history
6787 .insert_transaction(transaction_id_now, selections);
6788 }
6789 }
6790
6791 self.update_visible_inline_completion(window, cx);
6792 if self.active_inline_completion.is_none() {
6793 self.refresh_inline_completion(true, true, window, cx);
6794 }
6795
6796 cx.notify();
6797 }
6798 }
6799
6800 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6801 }
6802
6803 pub fn accept_partial_inline_completion(
6804 &mut self,
6805 _: &AcceptPartialEditPrediction,
6806 window: &mut Window,
6807 cx: &mut Context<Self>,
6808 ) {
6809 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6810 return;
6811 };
6812 if self.selections.count() != 1 {
6813 return;
6814 }
6815
6816 self.report_inline_completion_event(
6817 active_inline_completion.completion_id.clone(),
6818 true,
6819 cx,
6820 );
6821
6822 match &active_inline_completion.completion {
6823 InlineCompletion::Move { target, .. } => {
6824 let target = *target;
6825 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6826 selections.select_anchor_ranges([target..target]);
6827 });
6828 }
6829 InlineCompletion::Edit { edits, .. } => {
6830 // Find an insertion that starts at the cursor position.
6831 let snapshot = self.buffer.read(cx).snapshot(cx);
6832 let cursor_offset = self.selections.newest::<usize>(cx).head();
6833 let insertion = edits.iter().find_map(|(range, text)| {
6834 let range = range.to_offset(&snapshot);
6835 if range.is_empty() && range.start == cursor_offset {
6836 Some(text)
6837 } else {
6838 None
6839 }
6840 });
6841
6842 if let Some(text) = insertion {
6843 let mut partial_completion = text
6844 .chars()
6845 .by_ref()
6846 .take_while(|c| c.is_alphabetic())
6847 .collect::<String>();
6848 if partial_completion.is_empty() {
6849 partial_completion = text
6850 .chars()
6851 .by_ref()
6852 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6853 .collect::<String>();
6854 }
6855
6856 cx.emit(EditorEvent::InputHandled {
6857 utf16_range_to_replace: None,
6858 text: partial_completion.clone().into(),
6859 });
6860
6861 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6862
6863 self.refresh_inline_completion(true, true, window, cx);
6864 cx.notify();
6865 } else {
6866 self.accept_edit_prediction(&Default::default(), window, cx);
6867 }
6868 }
6869 }
6870 }
6871
6872 fn discard_inline_completion(
6873 &mut self,
6874 should_report_inline_completion_event: bool,
6875 cx: &mut Context<Self>,
6876 ) -> bool {
6877 if should_report_inline_completion_event {
6878 let completion_id = self
6879 .active_inline_completion
6880 .as_ref()
6881 .and_then(|active_completion| active_completion.completion_id.clone());
6882
6883 self.report_inline_completion_event(completion_id, false, cx);
6884 }
6885
6886 if let Some(provider) = self.edit_prediction_provider() {
6887 provider.discard(cx);
6888 }
6889
6890 self.take_active_inline_completion(cx)
6891 }
6892
6893 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6894 let Some(provider) = self.edit_prediction_provider() else {
6895 return;
6896 };
6897
6898 let Some((_, buffer, _)) = self
6899 .buffer
6900 .read(cx)
6901 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6902 else {
6903 return;
6904 };
6905
6906 let extension = buffer
6907 .read(cx)
6908 .file()
6909 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6910
6911 let event_type = match accepted {
6912 true => "Edit Prediction Accepted",
6913 false => "Edit Prediction Discarded",
6914 };
6915 telemetry::event!(
6916 event_type,
6917 provider = provider.name(),
6918 prediction_id = id,
6919 suggestion_accepted = accepted,
6920 file_extension = extension,
6921 );
6922 }
6923
6924 pub fn has_active_inline_completion(&self) -> bool {
6925 self.active_inline_completion.is_some()
6926 }
6927
6928 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6929 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6930 return false;
6931 };
6932
6933 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6934 self.clear_highlights::<InlineCompletionHighlight>(cx);
6935 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6936 true
6937 }
6938
6939 /// Returns true when we're displaying the edit prediction popover below the cursor
6940 /// like we are not previewing and the LSP autocomplete menu is visible
6941 /// or we are in `when_holding_modifier` mode.
6942 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6943 if self.edit_prediction_preview_is_active()
6944 || !self.show_edit_predictions_in_menu()
6945 || !self.edit_predictions_enabled()
6946 {
6947 return false;
6948 }
6949
6950 if self.has_visible_completions_menu() {
6951 return true;
6952 }
6953
6954 has_completion && self.edit_prediction_requires_modifier()
6955 }
6956
6957 fn handle_modifiers_changed(
6958 &mut self,
6959 modifiers: Modifiers,
6960 position_map: &PositionMap,
6961 window: &mut Window,
6962 cx: &mut Context<Self>,
6963 ) {
6964 if self.show_edit_predictions_in_menu() {
6965 self.update_edit_prediction_preview(&modifiers, window, cx);
6966 }
6967
6968 self.update_selection_mode(&modifiers, position_map, window, cx);
6969
6970 let mouse_position = window.mouse_position();
6971 if !position_map.text_hitbox.is_hovered(window) {
6972 return;
6973 }
6974
6975 self.update_hovered_link(
6976 position_map.point_for_position(mouse_position),
6977 &position_map.snapshot,
6978 modifiers,
6979 window,
6980 cx,
6981 )
6982 }
6983
6984 fn update_selection_mode(
6985 &mut self,
6986 modifiers: &Modifiers,
6987 position_map: &PositionMap,
6988 window: &mut Window,
6989 cx: &mut Context<Self>,
6990 ) {
6991 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6992 return;
6993 }
6994
6995 let mouse_position = window.mouse_position();
6996 let point_for_position = position_map.point_for_position(mouse_position);
6997 let position = point_for_position.previous_valid;
6998
6999 self.select(
7000 SelectPhase::BeginColumnar {
7001 position,
7002 reset: false,
7003 goal_column: point_for_position.exact_unclipped.column(),
7004 },
7005 window,
7006 cx,
7007 );
7008 }
7009
7010 fn update_edit_prediction_preview(
7011 &mut self,
7012 modifiers: &Modifiers,
7013 window: &mut Window,
7014 cx: &mut Context<Self>,
7015 ) {
7016 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
7017 let Some(accept_keystroke) = accept_keybind.keystroke() else {
7018 return;
7019 };
7020
7021 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
7022 if matches!(
7023 self.edit_prediction_preview,
7024 EditPredictionPreview::Inactive { .. }
7025 ) {
7026 self.edit_prediction_preview = EditPredictionPreview::Active {
7027 previous_scroll_position: None,
7028 since: Instant::now(),
7029 };
7030
7031 self.update_visible_inline_completion(window, cx);
7032 cx.notify();
7033 }
7034 } else if let EditPredictionPreview::Active {
7035 previous_scroll_position,
7036 since,
7037 } = self.edit_prediction_preview
7038 {
7039 if let (Some(previous_scroll_position), Some(position_map)) =
7040 (previous_scroll_position, self.last_position_map.as_ref())
7041 {
7042 self.set_scroll_position(
7043 previous_scroll_position
7044 .scroll_position(&position_map.snapshot.display_snapshot),
7045 window,
7046 cx,
7047 );
7048 }
7049
7050 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7051 released_too_fast: since.elapsed() < Duration::from_millis(200),
7052 };
7053 self.clear_row_highlights::<EditPredictionPreview>();
7054 self.update_visible_inline_completion(window, cx);
7055 cx.notify();
7056 }
7057 }
7058
7059 fn update_visible_inline_completion(
7060 &mut self,
7061 _window: &mut Window,
7062 cx: &mut Context<Self>,
7063 ) -> Option<()> {
7064 let selection = self.selections.newest_anchor();
7065 let cursor = selection.head();
7066 let multibuffer = self.buffer.read(cx).snapshot(cx);
7067 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7068 let excerpt_id = cursor.excerpt_id;
7069
7070 let show_in_menu = self.show_edit_predictions_in_menu();
7071 let completions_menu_has_precedence = !show_in_menu
7072 && (self.context_menu.borrow().is_some()
7073 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7074
7075 if completions_menu_has_precedence
7076 || !offset_selection.is_empty()
7077 || self
7078 .active_inline_completion
7079 .as_ref()
7080 .map_or(false, |completion| {
7081 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7082 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7083 !invalidation_range.contains(&offset_selection.head())
7084 })
7085 {
7086 self.discard_inline_completion(false, cx);
7087 return None;
7088 }
7089
7090 self.take_active_inline_completion(cx);
7091 let Some(provider) = self.edit_prediction_provider() else {
7092 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7093 return None;
7094 };
7095
7096 let (buffer, cursor_buffer_position) =
7097 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7098
7099 self.edit_prediction_settings =
7100 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7101
7102 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7103
7104 if self.edit_prediction_indent_conflict {
7105 let cursor_point = cursor.to_point(&multibuffer);
7106
7107 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7108
7109 if let Some((_, indent)) = indents.iter().next() {
7110 if indent.len == cursor_point.column {
7111 self.edit_prediction_indent_conflict = false;
7112 }
7113 }
7114 }
7115
7116 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7117 let edits = inline_completion
7118 .edits
7119 .into_iter()
7120 .flat_map(|(range, new_text)| {
7121 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7122 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7123 Some((start..end, new_text))
7124 })
7125 .collect::<Vec<_>>();
7126 if edits.is_empty() {
7127 return None;
7128 }
7129
7130 let first_edit_start = edits.first().unwrap().0.start;
7131 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7132 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7133
7134 let last_edit_end = edits.last().unwrap().0.end;
7135 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7136 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7137
7138 let cursor_row = cursor.to_point(&multibuffer).row;
7139
7140 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7141
7142 let mut inlay_ids = Vec::new();
7143 let invalidation_row_range;
7144 let move_invalidation_row_range = if cursor_row < edit_start_row {
7145 Some(cursor_row..edit_end_row)
7146 } else if cursor_row > edit_end_row {
7147 Some(edit_start_row..cursor_row)
7148 } else {
7149 None
7150 };
7151 let is_move =
7152 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7153 let completion = if is_move {
7154 invalidation_row_range =
7155 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7156 let target = first_edit_start;
7157 InlineCompletion::Move { target, snapshot }
7158 } else {
7159 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7160 && !self.inline_completions_hidden_for_vim_mode;
7161
7162 if show_completions_in_buffer {
7163 if edits
7164 .iter()
7165 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7166 {
7167 let mut inlays = Vec::new();
7168 for (range, new_text) in &edits {
7169 let inlay = Inlay::inline_completion(
7170 post_inc(&mut self.next_inlay_id),
7171 range.start,
7172 new_text.as_str(),
7173 );
7174 inlay_ids.push(inlay.id);
7175 inlays.push(inlay);
7176 }
7177
7178 self.splice_inlays(&[], inlays, cx);
7179 } else {
7180 let background_color = cx.theme().status().deleted_background;
7181 self.highlight_text::<InlineCompletionHighlight>(
7182 edits.iter().map(|(range, _)| range.clone()).collect(),
7183 HighlightStyle {
7184 background_color: Some(background_color),
7185 ..Default::default()
7186 },
7187 cx,
7188 );
7189 }
7190 }
7191
7192 invalidation_row_range = edit_start_row..edit_end_row;
7193
7194 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7195 if provider.show_tab_accept_marker() {
7196 EditDisplayMode::TabAccept
7197 } else {
7198 EditDisplayMode::Inline
7199 }
7200 } else {
7201 EditDisplayMode::DiffPopover
7202 };
7203
7204 InlineCompletion::Edit {
7205 edits,
7206 edit_preview: inline_completion.edit_preview,
7207 display_mode,
7208 snapshot,
7209 }
7210 };
7211
7212 let invalidation_range = multibuffer
7213 .anchor_before(Point::new(invalidation_row_range.start, 0))
7214 ..multibuffer.anchor_after(Point::new(
7215 invalidation_row_range.end,
7216 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7217 ));
7218
7219 self.stale_inline_completion_in_menu = None;
7220 self.active_inline_completion = Some(InlineCompletionState {
7221 inlay_ids,
7222 completion,
7223 completion_id: inline_completion.id,
7224 invalidation_range,
7225 });
7226
7227 cx.notify();
7228
7229 Some(())
7230 }
7231
7232 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7233 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7234 }
7235
7236 fn clear_tasks(&mut self) {
7237 self.tasks.clear()
7238 }
7239
7240 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7241 if self.tasks.insert(key, value).is_some() {
7242 // This case should hopefully be rare, but just in case...
7243 log::error!(
7244 "multiple different run targets found on a single line, only the last target will be rendered"
7245 )
7246 }
7247 }
7248
7249 /// Get all display points of breakpoints that will be rendered within editor
7250 ///
7251 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7252 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7253 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7254 fn active_breakpoints(
7255 &self,
7256 range: Range<DisplayRow>,
7257 window: &mut Window,
7258 cx: &mut Context<Self>,
7259 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7260 let mut breakpoint_display_points = HashMap::default();
7261
7262 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7263 return breakpoint_display_points;
7264 };
7265
7266 let snapshot = self.snapshot(window, cx);
7267
7268 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7269 let Some(project) = self.project.as_ref() else {
7270 return breakpoint_display_points;
7271 };
7272
7273 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7274 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7275
7276 for (buffer_snapshot, range, excerpt_id) in
7277 multi_buffer_snapshot.range_to_buffer_ranges(range)
7278 {
7279 let Some(buffer) = project
7280 .read(cx)
7281 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7282 else {
7283 continue;
7284 };
7285 let breakpoints = breakpoint_store.read(cx).breakpoints(
7286 &buffer,
7287 Some(
7288 buffer_snapshot.anchor_before(range.start)
7289 ..buffer_snapshot.anchor_after(range.end),
7290 ),
7291 buffer_snapshot,
7292 cx,
7293 );
7294 for (breakpoint, state) in breakpoints {
7295 let multi_buffer_anchor =
7296 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7297 let position = multi_buffer_anchor
7298 .to_point(&multi_buffer_snapshot)
7299 .to_display_point(&snapshot);
7300
7301 breakpoint_display_points.insert(
7302 position.row(),
7303 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7304 );
7305 }
7306 }
7307
7308 breakpoint_display_points
7309 }
7310
7311 fn breakpoint_context_menu(
7312 &self,
7313 anchor: Anchor,
7314 window: &mut Window,
7315 cx: &mut Context<Self>,
7316 ) -> Entity<ui::ContextMenu> {
7317 let weak_editor = cx.weak_entity();
7318 let focus_handle = self.focus_handle(cx);
7319
7320 let row = self
7321 .buffer
7322 .read(cx)
7323 .snapshot(cx)
7324 .summary_for_anchor::<Point>(&anchor)
7325 .row;
7326
7327 let breakpoint = self
7328 .breakpoint_at_row(row, window, cx)
7329 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7330
7331 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7332 "Edit Log Breakpoint"
7333 } else {
7334 "Set Log Breakpoint"
7335 };
7336
7337 let condition_breakpoint_msg = if breakpoint
7338 .as_ref()
7339 .is_some_and(|bp| bp.1.condition.is_some())
7340 {
7341 "Edit Condition Breakpoint"
7342 } else {
7343 "Set Condition Breakpoint"
7344 };
7345
7346 let hit_condition_breakpoint_msg = if breakpoint
7347 .as_ref()
7348 .is_some_and(|bp| bp.1.hit_condition.is_some())
7349 {
7350 "Edit Hit Condition Breakpoint"
7351 } else {
7352 "Set Hit Condition Breakpoint"
7353 };
7354
7355 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7356 "Unset Breakpoint"
7357 } else {
7358 "Set Breakpoint"
7359 };
7360
7361 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
7362 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
7363
7364 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7365 BreakpointState::Enabled => Some("Disable"),
7366 BreakpointState::Disabled => Some("Enable"),
7367 });
7368
7369 let (anchor, breakpoint) =
7370 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7371
7372 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7373 menu.on_blur_subscription(Subscription::new(|| {}))
7374 .context(focus_handle)
7375 .when(run_to_cursor, |this| {
7376 let weak_editor = weak_editor.clone();
7377 this.entry("Run to cursor", None, move |window, cx| {
7378 weak_editor
7379 .update(cx, |editor, cx| {
7380 editor.change_selections(None, window, cx, |s| {
7381 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7382 });
7383 })
7384 .ok();
7385
7386 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
7387 })
7388 .separator()
7389 })
7390 .when_some(toggle_state_msg, |this, msg| {
7391 this.entry(msg, None, {
7392 let weak_editor = weak_editor.clone();
7393 let breakpoint = breakpoint.clone();
7394 move |_window, cx| {
7395 weak_editor
7396 .update(cx, |this, cx| {
7397 this.edit_breakpoint_at_anchor(
7398 anchor,
7399 breakpoint.as_ref().clone(),
7400 BreakpointEditAction::InvertState,
7401 cx,
7402 );
7403 })
7404 .log_err();
7405 }
7406 })
7407 })
7408 .entry(set_breakpoint_msg, None, {
7409 let weak_editor = weak_editor.clone();
7410 let breakpoint = breakpoint.clone();
7411 move |_window, cx| {
7412 weak_editor
7413 .update(cx, |this, cx| {
7414 this.edit_breakpoint_at_anchor(
7415 anchor,
7416 breakpoint.as_ref().clone(),
7417 BreakpointEditAction::Toggle,
7418 cx,
7419 );
7420 })
7421 .log_err();
7422 }
7423 })
7424 .entry(log_breakpoint_msg, None, {
7425 let breakpoint = breakpoint.clone();
7426 let weak_editor = weak_editor.clone();
7427 move |window, cx| {
7428 weak_editor
7429 .update(cx, |this, cx| {
7430 this.add_edit_breakpoint_block(
7431 anchor,
7432 breakpoint.as_ref(),
7433 BreakpointPromptEditAction::Log,
7434 window,
7435 cx,
7436 );
7437 })
7438 .log_err();
7439 }
7440 })
7441 .entry(condition_breakpoint_msg, None, {
7442 let breakpoint = breakpoint.clone();
7443 let weak_editor = weak_editor.clone();
7444 move |window, cx| {
7445 weak_editor
7446 .update(cx, |this, cx| {
7447 this.add_edit_breakpoint_block(
7448 anchor,
7449 breakpoint.as_ref(),
7450 BreakpointPromptEditAction::Condition,
7451 window,
7452 cx,
7453 );
7454 })
7455 .log_err();
7456 }
7457 })
7458 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7459 weak_editor
7460 .update(cx, |this, cx| {
7461 this.add_edit_breakpoint_block(
7462 anchor,
7463 breakpoint.as_ref(),
7464 BreakpointPromptEditAction::HitCondition,
7465 window,
7466 cx,
7467 );
7468 })
7469 .log_err();
7470 })
7471 })
7472 }
7473
7474 fn render_breakpoint(
7475 &self,
7476 position: Anchor,
7477 row: DisplayRow,
7478 breakpoint: &Breakpoint,
7479 state: Option<BreakpointSessionState>,
7480 cx: &mut Context<Self>,
7481 ) -> IconButton {
7482 let is_rejected = state.is_some_and(|s| !s.verified);
7483 // Is it a breakpoint that shows up when hovering over gutter?
7484 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7485 (false, false),
7486 |PhantomBreakpointIndicator {
7487 is_active,
7488 display_row,
7489 collides_with_existing_breakpoint,
7490 }| {
7491 (
7492 is_active && display_row == row,
7493 collides_with_existing_breakpoint,
7494 )
7495 },
7496 );
7497
7498 let (color, icon) = {
7499 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7500 (false, false) => ui::IconName::DebugBreakpoint,
7501 (true, false) => ui::IconName::DebugLogBreakpoint,
7502 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7503 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7504 };
7505
7506 let color = if is_phantom {
7507 Color::Hint
7508 } else if is_rejected {
7509 Color::Disabled
7510 } else {
7511 Color::Debugger
7512 };
7513
7514 (color, icon)
7515 };
7516
7517 let breakpoint = Arc::from(breakpoint.clone());
7518
7519 let alt_as_text = gpui::Keystroke {
7520 modifiers: Modifiers::secondary_key(),
7521 ..Default::default()
7522 };
7523 let primary_action_text = if breakpoint.is_disabled() {
7524 "Enable breakpoint"
7525 } else if is_phantom && !collides_with_existing {
7526 "Set breakpoint"
7527 } else {
7528 "Unset breakpoint"
7529 };
7530 let focus_handle = self.focus_handle.clone();
7531
7532 let meta = if is_rejected {
7533 SharedString::from("No executable code is associated with this line.")
7534 } else if collides_with_existing && !breakpoint.is_disabled() {
7535 SharedString::from(format!(
7536 "{alt_as_text}-click to disable,\nright-click for more options."
7537 ))
7538 } else {
7539 SharedString::from("Right-click for more options.")
7540 };
7541 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7542 .icon_size(IconSize::XSmall)
7543 .size(ui::ButtonSize::None)
7544 .when(is_rejected, |this| {
7545 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7546 })
7547 .icon_color(color)
7548 .style(ButtonStyle::Transparent)
7549 .on_click(cx.listener({
7550 let breakpoint = breakpoint.clone();
7551
7552 move |editor, event: &ClickEvent, window, cx| {
7553 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7554 BreakpointEditAction::InvertState
7555 } else {
7556 BreakpointEditAction::Toggle
7557 };
7558
7559 window.focus(&editor.focus_handle(cx));
7560 editor.edit_breakpoint_at_anchor(
7561 position,
7562 breakpoint.as_ref().clone(),
7563 edit_action,
7564 cx,
7565 );
7566 }
7567 }))
7568 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7569 editor.set_breakpoint_context_menu(
7570 row,
7571 Some(position),
7572 event.down.position,
7573 window,
7574 cx,
7575 );
7576 }))
7577 .tooltip(move |window, cx| {
7578 Tooltip::with_meta_in(
7579 primary_action_text,
7580 Some(&ToggleBreakpoint),
7581 meta.clone(),
7582 &focus_handle,
7583 window,
7584 cx,
7585 )
7586 })
7587 }
7588
7589 fn build_tasks_context(
7590 project: &Entity<Project>,
7591 buffer: &Entity<Buffer>,
7592 buffer_row: u32,
7593 tasks: &Arc<RunnableTasks>,
7594 cx: &mut Context<Self>,
7595 ) -> Task<Option<task::TaskContext>> {
7596 let position = Point::new(buffer_row, tasks.column);
7597 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7598 let location = Location {
7599 buffer: buffer.clone(),
7600 range: range_start..range_start,
7601 };
7602 // Fill in the environmental variables from the tree-sitter captures
7603 let mut captured_task_variables = TaskVariables::default();
7604 for (capture_name, value) in tasks.extra_variables.clone() {
7605 captured_task_variables.insert(
7606 task::VariableName::Custom(capture_name.into()),
7607 value.clone(),
7608 );
7609 }
7610 project.update(cx, |project, cx| {
7611 project.task_store().update(cx, |task_store, cx| {
7612 task_store.task_context_for_location(captured_task_variables, location, cx)
7613 })
7614 })
7615 }
7616
7617 pub fn spawn_nearest_task(
7618 &mut self,
7619 action: &SpawnNearestTask,
7620 window: &mut Window,
7621 cx: &mut Context<Self>,
7622 ) {
7623 let Some((workspace, _)) = self.workspace.clone() else {
7624 return;
7625 };
7626 let Some(project) = self.project.clone() else {
7627 return;
7628 };
7629
7630 // Try to find a closest, enclosing node using tree-sitter that has a
7631 // task
7632 let Some((buffer, buffer_row, tasks)) = self
7633 .find_enclosing_node_task(cx)
7634 // Or find the task that's closest in row-distance.
7635 .or_else(|| self.find_closest_task(cx))
7636 else {
7637 return;
7638 };
7639
7640 let reveal_strategy = action.reveal;
7641 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7642 cx.spawn_in(window, async move |_, cx| {
7643 let context = task_context.await?;
7644 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7645
7646 let resolved = &mut resolved_task.resolved;
7647 resolved.reveal = reveal_strategy;
7648
7649 workspace
7650 .update_in(cx, |workspace, window, cx| {
7651 workspace.schedule_resolved_task(
7652 task_source_kind,
7653 resolved_task,
7654 false,
7655 window,
7656 cx,
7657 );
7658 })
7659 .ok()
7660 })
7661 .detach();
7662 }
7663
7664 fn find_closest_task(
7665 &mut self,
7666 cx: &mut Context<Self>,
7667 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7668 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7669
7670 let ((buffer_id, row), tasks) = self
7671 .tasks
7672 .iter()
7673 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7674
7675 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7676 let tasks = Arc::new(tasks.to_owned());
7677 Some((buffer, *row, tasks))
7678 }
7679
7680 fn find_enclosing_node_task(
7681 &mut self,
7682 cx: &mut Context<Self>,
7683 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7684 let snapshot = self.buffer.read(cx).snapshot(cx);
7685 let offset = self.selections.newest::<usize>(cx).head();
7686 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7687 let buffer_id = excerpt.buffer().remote_id();
7688
7689 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7690 let mut cursor = layer.node().walk();
7691
7692 while cursor.goto_first_child_for_byte(offset).is_some() {
7693 if cursor.node().end_byte() == offset {
7694 cursor.goto_next_sibling();
7695 }
7696 }
7697
7698 // Ascend to the smallest ancestor that contains the range and has a task.
7699 loop {
7700 let node = cursor.node();
7701 let node_range = node.byte_range();
7702 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7703
7704 // Check if this node contains our offset
7705 if node_range.start <= offset && node_range.end >= offset {
7706 // If it contains offset, check for task
7707 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7708 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7709 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7710 }
7711 }
7712
7713 if !cursor.goto_parent() {
7714 break;
7715 }
7716 }
7717 None
7718 }
7719
7720 fn render_run_indicator(
7721 &self,
7722 _style: &EditorStyle,
7723 is_active: bool,
7724 row: DisplayRow,
7725 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7726 cx: &mut Context<Self>,
7727 ) -> IconButton {
7728 let color = Color::Muted;
7729 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7730
7731 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7732 .shape(ui::IconButtonShape::Square)
7733 .icon_size(IconSize::XSmall)
7734 .icon_color(color)
7735 .toggle_state(is_active)
7736 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7737 let quick_launch = e.down.button == MouseButton::Left;
7738 window.focus(&editor.focus_handle(cx));
7739 editor.toggle_code_actions(
7740 &ToggleCodeActions {
7741 deployed_from: Some(CodeActionSource::Indicator(row)),
7742 quick_launch,
7743 },
7744 window,
7745 cx,
7746 );
7747 }))
7748 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7749 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7750 }))
7751 }
7752
7753 pub fn context_menu_visible(&self) -> bool {
7754 !self.edit_prediction_preview_is_active()
7755 && self
7756 .context_menu
7757 .borrow()
7758 .as_ref()
7759 .map_or(false, |menu| menu.visible())
7760 }
7761
7762 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7763 self.context_menu
7764 .borrow()
7765 .as_ref()
7766 .map(|menu| menu.origin())
7767 }
7768
7769 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7770 self.context_menu_options = Some(options);
7771 }
7772
7773 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7774 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7775
7776 fn render_edit_prediction_popover(
7777 &mut self,
7778 text_bounds: &Bounds<Pixels>,
7779 content_origin: gpui::Point<Pixels>,
7780 right_margin: Pixels,
7781 editor_snapshot: &EditorSnapshot,
7782 visible_row_range: Range<DisplayRow>,
7783 scroll_top: f32,
7784 scroll_bottom: f32,
7785 line_layouts: &[LineWithInvisibles],
7786 line_height: Pixels,
7787 scroll_pixel_position: gpui::Point<Pixels>,
7788 newest_selection_head: Option<DisplayPoint>,
7789 editor_width: Pixels,
7790 style: &EditorStyle,
7791 window: &mut Window,
7792 cx: &mut App,
7793 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7794 if self.mode().is_minimap() {
7795 return None;
7796 }
7797 let active_inline_completion = self.active_inline_completion.as_ref()?;
7798
7799 if self.edit_prediction_visible_in_cursor_popover(true) {
7800 return None;
7801 }
7802
7803 match &active_inline_completion.completion {
7804 InlineCompletion::Move { target, .. } => {
7805 let target_display_point = target.to_display_point(editor_snapshot);
7806
7807 if self.edit_prediction_requires_modifier() {
7808 if !self.edit_prediction_preview_is_active() {
7809 return None;
7810 }
7811
7812 self.render_edit_prediction_modifier_jump_popover(
7813 text_bounds,
7814 content_origin,
7815 visible_row_range,
7816 line_layouts,
7817 line_height,
7818 scroll_pixel_position,
7819 newest_selection_head,
7820 target_display_point,
7821 window,
7822 cx,
7823 )
7824 } else {
7825 self.render_edit_prediction_eager_jump_popover(
7826 text_bounds,
7827 content_origin,
7828 editor_snapshot,
7829 visible_row_range,
7830 scroll_top,
7831 scroll_bottom,
7832 line_height,
7833 scroll_pixel_position,
7834 target_display_point,
7835 editor_width,
7836 window,
7837 cx,
7838 )
7839 }
7840 }
7841 InlineCompletion::Edit {
7842 display_mode: EditDisplayMode::Inline,
7843 ..
7844 } => None,
7845 InlineCompletion::Edit {
7846 display_mode: EditDisplayMode::TabAccept,
7847 edits,
7848 ..
7849 } => {
7850 let range = &edits.first()?.0;
7851 let target_display_point = range.end.to_display_point(editor_snapshot);
7852
7853 self.render_edit_prediction_end_of_line_popover(
7854 "Accept",
7855 editor_snapshot,
7856 visible_row_range,
7857 target_display_point,
7858 line_height,
7859 scroll_pixel_position,
7860 content_origin,
7861 editor_width,
7862 window,
7863 cx,
7864 )
7865 }
7866 InlineCompletion::Edit {
7867 edits,
7868 edit_preview,
7869 display_mode: EditDisplayMode::DiffPopover,
7870 snapshot,
7871 } => self.render_edit_prediction_diff_popover(
7872 text_bounds,
7873 content_origin,
7874 right_margin,
7875 editor_snapshot,
7876 visible_row_range,
7877 line_layouts,
7878 line_height,
7879 scroll_pixel_position,
7880 newest_selection_head,
7881 editor_width,
7882 style,
7883 edits,
7884 edit_preview,
7885 snapshot,
7886 window,
7887 cx,
7888 ),
7889 }
7890 }
7891
7892 fn render_edit_prediction_modifier_jump_popover(
7893 &mut self,
7894 text_bounds: &Bounds<Pixels>,
7895 content_origin: gpui::Point<Pixels>,
7896 visible_row_range: Range<DisplayRow>,
7897 line_layouts: &[LineWithInvisibles],
7898 line_height: Pixels,
7899 scroll_pixel_position: gpui::Point<Pixels>,
7900 newest_selection_head: Option<DisplayPoint>,
7901 target_display_point: DisplayPoint,
7902 window: &mut Window,
7903 cx: &mut App,
7904 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7905 let scrolled_content_origin =
7906 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7907
7908 const SCROLL_PADDING_Y: Pixels = px(12.);
7909
7910 if target_display_point.row() < visible_row_range.start {
7911 return self.render_edit_prediction_scroll_popover(
7912 |_| SCROLL_PADDING_Y,
7913 IconName::ArrowUp,
7914 visible_row_range,
7915 line_layouts,
7916 newest_selection_head,
7917 scrolled_content_origin,
7918 window,
7919 cx,
7920 );
7921 } else if target_display_point.row() >= visible_row_range.end {
7922 return self.render_edit_prediction_scroll_popover(
7923 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7924 IconName::ArrowDown,
7925 visible_row_range,
7926 line_layouts,
7927 newest_selection_head,
7928 scrolled_content_origin,
7929 window,
7930 cx,
7931 );
7932 }
7933
7934 const POLE_WIDTH: Pixels = px(2.);
7935
7936 let line_layout =
7937 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7938 let target_column = target_display_point.column() as usize;
7939
7940 let target_x = line_layout.x_for_index(target_column);
7941 let target_y =
7942 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7943
7944 let flag_on_right = target_x < text_bounds.size.width / 2.;
7945
7946 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7947 border_color.l += 0.001;
7948
7949 let mut element = v_flex()
7950 .items_end()
7951 .when(flag_on_right, |el| el.items_start())
7952 .child(if flag_on_right {
7953 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7954 .rounded_bl(px(0.))
7955 .rounded_tl(px(0.))
7956 .border_l_2()
7957 .border_color(border_color)
7958 } else {
7959 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7960 .rounded_br(px(0.))
7961 .rounded_tr(px(0.))
7962 .border_r_2()
7963 .border_color(border_color)
7964 })
7965 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7966 .into_any();
7967
7968 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7969
7970 let mut origin = scrolled_content_origin + point(target_x, target_y)
7971 - point(
7972 if flag_on_right {
7973 POLE_WIDTH
7974 } else {
7975 size.width - POLE_WIDTH
7976 },
7977 size.height - line_height,
7978 );
7979
7980 origin.x = origin.x.max(content_origin.x);
7981
7982 element.prepaint_at(origin, window, cx);
7983
7984 Some((element, origin))
7985 }
7986
7987 fn render_edit_prediction_scroll_popover(
7988 &mut self,
7989 to_y: impl Fn(Size<Pixels>) -> Pixels,
7990 scroll_icon: IconName,
7991 visible_row_range: Range<DisplayRow>,
7992 line_layouts: &[LineWithInvisibles],
7993 newest_selection_head: Option<DisplayPoint>,
7994 scrolled_content_origin: gpui::Point<Pixels>,
7995 window: &mut Window,
7996 cx: &mut App,
7997 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7998 let mut element = self
7999 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8000 .into_any();
8001
8002 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8003
8004 let cursor = newest_selection_head?;
8005 let cursor_row_layout =
8006 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8007 let cursor_column = cursor.column() as usize;
8008
8009 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8010
8011 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8012
8013 element.prepaint_at(origin, window, cx);
8014 Some((element, origin))
8015 }
8016
8017 fn render_edit_prediction_eager_jump_popover(
8018 &mut self,
8019 text_bounds: &Bounds<Pixels>,
8020 content_origin: gpui::Point<Pixels>,
8021 editor_snapshot: &EditorSnapshot,
8022 visible_row_range: Range<DisplayRow>,
8023 scroll_top: f32,
8024 scroll_bottom: f32,
8025 line_height: Pixels,
8026 scroll_pixel_position: gpui::Point<Pixels>,
8027 target_display_point: DisplayPoint,
8028 editor_width: Pixels,
8029 window: &mut Window,
8030 cx: &mut App,
8031 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8032 if target_display_point.row().as_f32() < scroll_top {
8033 let mut element = self
8034 .render_edit_prediction_line_popover(
8035 "Jump to Edit",
8036 Some(IconName::ArrowUp),
8037 window,
8038 cx,
8039 )?
8040 .into_any();
8041
8042 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8043 let offset = point(
8044 (text_bounds.size.width - size.width) / 2.,
8045 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8046 );
8047
8048 let origin = text_bounds.origin + offset;
8049 element.prepaint_at(origin, window, cx);
8050 Some((element, origin))
8051 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8052 let mut element = self
8053 .render_edit_prediction_line_popover(
8054 "Jump to Edit",
8055 Some(IconName::ArrowDown),
8056 window,
8057 cx,
8058 )?
8059 .into_any();
8060
8061 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8062 let offset = point(
8063 (text_bounds.size.width - size.width) / 2.,
8064 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8065 );
8066
8067 let origin = text_bounds.origin + offset;
8068 element.prepaint_at(origin, window, cx);
8069 Some((element, origin))
8070 } else {
8071 self.render_edit_prediction_end_of_line_popover(
8072 "Jump to Edit",
8073 editor_snapshot,
8074 visible_row_range,
8075 target_display_point,
8076 line_height,
8077 scroll_pixel_position,
8078 content_origin,
8079 editor_width,
8080 window,
8081 cx,
8082 )
8083 }
8084 }
8085
8086 fn render_edit_prediction_end_of_line_popover(
8087 self: &mut Editor,
8088 label: &'static str,
8089 editor_snapshot: &EditorSnapshot,
8090 visible_row_range: Range<DisplayRow>,
8091 target_display_point: DisplayPoint,
8092 line_height: Pixels,
8093 scroll_pixel_position: gpui::Point<Pixels>,
8094 content_origin: gpui::Point<Pixels>,
8095 editor_width: Pixels,
8096 window: &mut Window,
8097 cx: &mut App,
8098 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8099 let target_line_end = DisplayPoint::new(
8100 target_display_point.row(),
8101 editor_snapshot.line_len(target_display_point.row()),
8102 );
8103
8104 let mut element = self
8105 .render_edit_prediction_line_popover(label, None, window, cx)?
8106 .into_any();
8107
8108 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8109
8110 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8111
8112 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8113 let mut origin = start_point
8114 + line_origin
8115 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8116 origin.x = origin.x.max(content_origin.x);
8117
8118 let max_x = content_origin.x + editor_width - size.width;
8119
8120 if origin.x > max_x {
8121 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8122
8123 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8124 origin.y += offset;
8125 IconName::ArrowUp
8126 } else {
8127 origin.y -= offset;
8128 IconName::ArrowDown
8129 };
8130
8131 element = self
8132 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8133 .into_any();
8134
8135 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8136
8137 origin.x = content_origin.x + editor_width - size.width - px(2.);
8138 }
8139
8140 element.prepaint_at(origin, window, cx);
8141 Some((element, origin))
8142 }
8143
8144 fn render_edit_prediction_diff_popover(
8145 self: &Editor,
8146 text_bounds: &Bounds<Pixels>,
8147 content_origin: gpui::Point<Pixels>,
8148 right_margin: Pixels,
8149 editor_snapshot: &EditorSnapshot,
8150 visible_row_range: Range<DisplayRow>,
8151 line_layouts: &[LineWithInvisibles],
8152 line_height: Pixels,
8153 scroll_pixel_position: gpui::Point<Pixels>,
8154 newest_selection_head: Option<DisplayPoint>,
8155 editor_width: Pixels,
8156 style: &EditorStyle,
8157 edits: &Vec<(Range<Anchor>, String)>,
8158 edit_preview: &Option<language::EditPreview>,
8159 snapshot: &language::BufferSnapshot,
8160 window: &mut Window,
8161 cx: &mut App,
8162 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8163 let edit_start = edits
8164 .first()
8165 .unwrap()
8166 .0
8167 .start
8168 .to_display_point(editor_snapshot);
8169 let edit_end = edits
8170 .last()
8171 .unwrap()
8172 .0
8173 .end
8174 .to_display_point(editor_snapshot);
8175
8176 let is_visible = visible_row_range.contains(&edit_start.row())
8177 || visible_row_range.contains(&edit_end.row());
8178 if !is_visible {
8179 return None;
8180 }
8181
8182 let highlighted_edits =
8183 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8184
8185 let styled_text = highlighted_edits.to_styled_text(&style.text);
8186 let line_count = highlighted_edits.text.lines().count();
8187
8188 const BORDER_WIDTH: Pixels = px(1.);
8189
8190 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8191 let has_keybind = keybind.is_some();
8192
8193 let mut element = h_flex()
8194 .items_start()
8195 .child(
8196 h_flex()
8197 .bg(cx.theme().colors().editor_background)
8198 .border(BORDER_WIDTH)
8199 .shadow_sm()
8200 .border_color(cx.theme().colors().border)
8201 .rounded_l_lg()
8202 .when(line_count > 1, |el| el.rounded_br_lg())
8203 .pr_1()
8204 .child(styled_text),
8205 )
8206 .child(
8207 h_flex()
8208 .h(line_height + BORDER_WIDTH * 2.)
8209 .px_1p5()
8210 .gap_1()
8211 // Workaround: For some reason, there's a gap if we don't do this
8212 .ml(-BORDER_WIDTH)
8213 .shadow(vec![gpui::BoxShadow {
8214 color: gpui::black().opacity(0.05),
8215 offset: point(px(1.), px(1.)),
8216 blur_radius: px(2.),
8217 spread_radius: px(0.),
8218 }])
8219 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8220 .border(BORDER_WIDTH)
8221 .border_color(cx.theme().colors().border)
8222 .rounded_r_lg()
8223 .id("edit_prediction_diff_popover_keybind")
8224 .when(!has_keybind, |el| {
8225 let status_colors = cx.theme().status();
8226
8227 el.bg(status_colors.error_background)
8228 .border_color(status_colors.error.opacity(0.6))
8229 .child(Icon::new(IconName::Info).color(Color::Error))
8230 .cursor_default()
8231 .hoverable_tooltip(move |_window, cx| {
8232 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8233 })
8234 })
8235 .children(keybind),
8236 )
8237 .into_any();
8238
8239 let longest_row =
8240 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8241 let longest_line_width = if visible_row_range.contains(&longest_row) {
8242 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8243 } else {
8244 layout_line(
8245 longest_row,
8246 editor_snapshot,
8247 style,
8248 editor_width,
8249 |_| false,
8250 window,
8251 cx,
8252 )
8253 .width
8254 };
8255
8256 let viewport_bounds =
8257 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8258 right: -right_margin,
8259 ..Default::default()
8260 });
8261
8262 let x_after_longest =
8263 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8264 - scroll_pixel_position.x;
8265
8266 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8267
8268 // Fully visible if it can be displayed within the window (allow overlapping other
8269 // panes). However, this is only allowed if the popover starts within text_bounds.
8270 let can_position_to_the_right = x_after_longest < text_bounds.right()
8271 && x_after_longest + element_bounds.width < viewport_bounds.right();
8272
8273 let mut origin = if can_position_to_the_right {
8274 point(
8275 x_after_longest,
8276 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8277 - scroll_pixel_position.y,
8278 )
8279 } else {
8280 let cursor_row = newest_selection_head.map(|head| head.row());
8281 let above_edit = edit_start
8282 .row()
8283 .0
8284 .checked_sub(line_count as u32)
8285 .map(DisplayRow);
8286 let below_edit = Some(edit_end.row() + 1);
8287 let above_cursor =
8288 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8289 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8290
8291 // Place the edit popover adjacent to the edit if there is a location
8292 // available that is onscreen and does not obscure the cursor. Otherwise,
8293 // place it adjacent to the cursor.
8294 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8295 .into_iter()
8296 .flatten()
8297 .find(|&start_row| {
8298 let end_row = start_row + line_count as u32;
8299 visible_row_range.contains(&start_row)
8300 && visible_row_range.contains(&end_row)
8301 && cursor_row.map_or(true, |cursor_row| {
8302 !((start_row..end_row).contains(&cursor_row))
8303 })
8304 })?;
8305
8306 content_origin
8307 + point(
8308 -scroll_pixel_position.x,
8309 row_target.as_f32() * line_height - scroll_pixel_position.y,
8310 )
8311 };
8312
8313 origin.x -= BORDER_WIDTH;
8314
8315 window.defer_draw(element, origin, 1);
8316
8317 // Do not return an element, since it will already be drawn due to defer_draw.
8318 None
8319 }
8320
8321 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8322 px(30.)
8323 }
8324
8325 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8326 if self.read_only(cx) {
8327 cx.theme().players().read_only()
8328 } else {
8329 self.style.as_ref().unwrap().local_player
8330 }
8331 }
8332
8333 fn render_edit_prediction_accept_keybind(
8334 &self,
8335 window: &mut Window,
8336 cx: &App,
8337 ) -> Option<AnyElement> {
8338 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
8339 let accept_keystroke = accept_binding.keystroke()?;
8340
8341 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8342
8343 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8344 Color::Accent
8345 } else {
8346 Color::Muted
8347 };
8348
8349 h_flex()
8350 .px_0p5()
8351 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8352 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8353 .text_size(TextSize::XSmall.rems(cx))
8354 .child(h_flex().children(ui::render_modifiers(
8355 &accept_keystroke.modifiers,
8356 PlatformStyle::platform(),
8357 Some(modifiers_color),
8358 Some(IconSize::XSmall.rems().into()),
8359 true,
8360 )))
8361 .when(is_platform_style_mac, |parent| {
8362 parent.child(accept_keystroke.key.clone())
8363 })
8364 .when(!is_platform_style_mac, |parent| {
8365 parent.child(
8366 Key::new(
8367 util::capitalize(&accept_keystroke.key),
8368 Some(Color::Default),
8369 )
8370 .size(Some(IconSize::XSmall.rems().into())),
8371 )
8372 })
8373 .into_any()
8374 .into()
8375 }
8376
8377 fn render_edit_prediction_line_popover(
8378 &self,
8379 label: impl Into<SharedString>,
8380 icon: Option<IconName>,
8381 window: &mut Window,
8382 cx: &App,
8383 ) -> Option<Stateful<Div>> {
8384 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8385
8386 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8387 let has_keybind = keybind.is_some();
8388
8389 let result = h_flex()
8390 .id("ep-line-popover")
8391 .py_0p5()
8392 .pl_1()
8393 .pr(padding_right)
8394 .gap_1()
8395 .rounded_md()
8396 .border_1()
8397 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8398 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8399 .shadow_sm()
8400 .when(!has_keybind, |el| {
8401 let status_colors = cx.theme().status();
8402
8403 el.bg(status_colors.error_background)
8404 .border_color(status_colors.error.opacity(0.6))
8405 .pl_2()
8406 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8407 .cursor_default()
8408 .hoverable_tooltip(move |_window, cx| {
8409 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8410 })
8411 })
8412 .children(keybind)
8413 .child(
8414 Label::new(label)
8415 .size(LabelSize::Small)
8416 .when(!has_keybind, |el| {
8417 el.color(cx.theme().status().error.into()).strikethrough()
8418 }),
8419 )
8420 .when(!has_keybind, |el| {
8421 el.child(
8422 h_flex().ml_1().child(
8423 Icon::new(IconName::Info)
8424 .size(IconSize::Small)
8425 .color(cx.theme().status().error.into()),
8426 ),
8427 )
8428 })
8429 .when_some(icon, |element, icon| {
8430 element.child(
8431 div()
8432 .mt(px(1.5))
8433 .child(Icon::new(icon).size(IconSize::Small)),
8434 )
8435 });
8436
8437 Some(result)
8438 }
8439
8440 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8441 let accent_color = cx.theme().colors().text_accent;
8442 let editor_bg_color = cx.theme().colors().editor_background;
8443 editor_bg_color.blend(accent_color.opacity(0.1))
8444 }
8445
8446 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8447 let accent_color = cx.theme().colors().text_accent;
8448 let editor_bg_color = cx.theme().colors().editor_background;
8449 editor_bg_color.blend(accent_color.opacity(0.6))
8450 }
8451
8452 fn render_edit_prediction_cursor_popover(
8453 &self,
8454 min_width: Pixels,
8455 max_width: Pixels,
8456 cursor_point: Point,
8457 style: &EditorStyle,
8458 accept_keystroke: Option<&gpui::Keystroke>,
8459 _window: &Window,
8460 cx: &mut Context<Editor>,
8461 ) -> Option<AnyElement> {
8462 let provider = self.edit_prediction_provider.as_ref()?;
8463
8464 if provider.provider.needs_terms_acceptance(cx) {
8465 return Some(
8466 h_flex()
8467 .min_w(min_width)
8468 .flex_1()
8469 .px_2()
8470 .py_1()
8471 .gap_3()
8472 .elevation_2(cx)
8473 .hover(|style| style.bg(cx.theme().colors().element_hover))
8474 .id("accept-terms")
8475 .cursor_pointer()
8476 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8477 .on_click(cx.listener(|this, _event, window, cx| {
8478 cx.stop_propagation();
8479 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8480 window.dispatch_action(
8481 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8482 cx,
8483 );
8484 }))
8485 .child(
8486 h_flex()
8487 .flex_1()
8488 .gap_2()
8489 .child(Icon::new(IconName::ZedPredict))
8490 .child(Label::new("Accept Terms of Service"))
8491 .child(div().w_full())
8492 .child(
8493 Icon::new(IconName::ArrowUpRight)
8494 .color(Color::Muted)
8495 .size(IconSize::Small),
8496 )
8497 .into_any_element(),
8498 )
8499 .into_any(),
8500 );
8501 }
8502
8503 let is_refreshing = provider.provider.is_refreshing(cx);
8504
8505 fn pending_completion_container() -> Div {
8506 h_flex()
8507 .h_full()
8508 .flex_1()
8509 .gap_2()
8510 .child(Icon::new(IconName::ZedPredict))
8511 }
8512
8513 let completion = match &self.active_inline_completion {
8514 Some(prediction) => {
8515 if !self.has_visible_completions_menu() {
8516 const RADIUS: Pixels = px(6.);
8517 const BORDER_WIDTH: Pixels = px(1.);
8518
8519 return Some(
8520 h_flex()
8521 .elevation_2(cx)
8522 .border(BORDER_WIDTH)
8523 .border_color(cx.theme().colors().border)
8524 .when(accept_keystroke.is_none(), |el| {
8525 el.border_color(cx.theme().status().error)
8526 })
8527 .rounded(RADIUS)
8528 .rounded_tl(px(0.))
8529 .overflow_hidden()
8530 .child(div().px_1p5().child(match &prediction.completion {
8531 InlineCompletion::Move { target, snapshot } => {
8532 use text::ToPoint as _;
8533 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8534 {
8535 Icon::new(IconName::ZedPredictDown)
8536 } else {
8537 Icon::new(IconName::ZedPredictUp)
8538 }
8539 }
8540 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8541 }))
8542 .child(
8543 h_flex()
8544 .gap_1()
8545 .py_1()
8546 .px_2()
8547 .rounded_r(RADIUS - BORDER_WIDTH)
8548 .border_l_1()
8549 .border_color(cx.theme().colors().border)
8550 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8551 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8552 el.child(
8553 Label::new("Hold")
8554 .size(LabelSize::Small)
8555 .when(accept_keystroke.is_none(), |el| {
8556 el.strikethrough()
8557 })
8558 .line_height_style(LineHeightStyle::UiLabel),
8559 )
8560 })
8561 .id("edit_prediction_cursor_popover_keybind")
8562 .when(accept_keystroke.is_none(), |el| {
8563 let status_colors = cx.theme().status();
8564
8565 el.bg(status_colors.error_background)
8566 .border_color(status_colors.error.opacity(0.6))
8567 .child(Icon::new(IconName::Info).color(Color::Error))
8568 .cursor_default()
8569 .hoverable_tooltip(move |_window, cx| {
8570 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8571 .into()
8572 })
8573 })
8574 .when_some(
8575 accept_keystroke.as_ref(),
8576 |el, accept_keystroke| {
8577 el.child(h_flex().children(ui::render_modifiers(
8578 &accept_keystroke.modifiers,
8579 PlatformStyle::platform(),
8580 Some(Color::Default),
8581 Some(IconSize::XSmall.rems().into()),
8582 false,
8583 )))
8584 },
8585 ),
8586 )
8587 .into_any(),
8588 );
8589 }
8590
8591 self.render_edit_prediction_cursor_popover_preview(
8592 prediction,
8593 cursor_point,
8594 style,
8595 cx,
8596 )?
8597 }
8598
8599 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8600 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8601 stale_completion,
8602 cursor_point,
8603 style,
8604 cx,
8605 )?,
8606
8607 None => {
8608 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8609 }
8610 },
8611
8612 None => pending_completion_container().child(Label::new("No Prediction")),
8613 };
8614
8615 let completion = if is_refreshing {
8616 completion
8617 .with_animation(
8618 "loading-completion",
8619 Animation::new(Duration::from_secs(2))
8620 .repeat()
8621 .with_easing(pulsating_between(0.4, 0.8)),
8622 |label, delta| label.opacity(delta),
8623 )
8624 .into_any_element()
8625 } else {
8626 completion.into_any_element()
8627 };
8628
8629 let has_completion = self.active_inline_completion.is_some();
8630
8631 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8632 Some(
8633 h_flex()
8634 .min_w(min_width)
8635 .max_w(max_width)
8636 .flex_1()
8637 .elevation_2(cx)
8638 .border_color(cx.theme().colors().border)
8639 .child(
8640 div()
8641 .flex_1()
8642 .py_1()
8643 .px_2()
8644 .overflow_hidden()
8645 .child(completion),
8646 )
8647 .when_some(accept_keystroke, |el, accept_keystroke| {
8648 if !accept_keystroke.modifiers.modified() {
8649 return el;
8650 }
8651
8652 el.child(
8653 h_flex()
8654 .h_full()
8655 .border_l_1()
8656 .rounded_r_lg()
8657 .border_color(cx.theme().colors().border)
8658 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8659 .gap_1()
8660 .py_1()
8661 .px_2()
8662 .child(
8663 h_flex()
8664 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8665 .when(is_platform_style_mac, |parent| parent.gap_1())
8666 .child(h_flex().children(ui::render_modifiers(
8667 &accept_keystroke.modifiers,
8668 PlatformStyle::platform(),
8669 Some(if !has_completion {
8670 Color::Muted
8671 } else {
8672 Color::Default
8673 }),
8674 None,
8675 false,
8676 ))),
8677 )
8678 .child(Label::new("Preview").into_any_element())
8679 .opacity(if has_completion { 1.0 } else { 0.4 }),
8680 )
8681 })
8682 .into_any(),
8683 )
8684 }
8685
8686 fn render_edit_prediction_cursor_popover_preview(
8687 &self,
8688 completion: &InlineCompletionState,
8689 cursor_point: Point,
8690 style: &EditorStyle,
8691 cx: &mut Context<Editor>,
8692 ) -> Option<Div> {
8693 use text::ToPoint as _;
8694
8695 fn render_relative_row_jump(
8696 prefix: impl Into<String>,
8697 current_row: u32,
8698 target_row: u32,
8699 ) -> Div {
8700 let (row_diff, arrow) = if target_row < current_row {
8701 (current_row - target_row, IconName::ArrowUp)
8702 } else {
8703 (target_row - current_row, IconName::ArrowDown)
8704 };
8705
8706 h_flex()
8707 .child(
8708 Label::new(format!("{}{}", prefix.into(), row_diff))
8709 .color(Color::Muted)
8710 .size(LabelSize::Small),
8711 )
8712 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8713 }
8714
8715 match &completion.completion {
8716 InlineCompletion::Move {
8717 target, snapshot, ..
8718 } => Some(
8719 h_flex()
8720 .px_2()
8721 .gap_2()
8722 .flex_1()
8723 .child(
8724 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8725 Icon::new(IconName::ZedPredictDown)
8726 } else {
8727 Icon::new(IconName::ZedPredictUp)
8728 },
8729 )
8730 .child(Label::new("Jump to Edit")),
8731 ),
8732
8733 InlineCompletion::Edit {
8734 edits,
8735 edit_preview,
8736 snapshot,
8737 display_mode: _,
8738 } => {
8739 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8740
8741 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8742 &snapshot,
8743 &edits,
8744 edit_preview.as_ref()?,
8745 true,
8746 cx,
8747 )
8748 .first_line_preview();
8749
8750 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8751 .with_default_highlights(&style.text, highlighted_edits.highlights);
8752
8753 let preview = h_flex()
8754 .gap_1()
8755 .min_w_16()
8756 .child(styled_text)
8757 .when(has_more_lines, |parent| parent.child("…"));
8758
8759 let left = if first_edit_row != cursor_point.row {
8760 render_relative_row_jump("", cursor_point.row, first_edit_row)
8761 .into_any_element()
8762 } else {
8763 Icon::new(IconName::ZedPredict).into_any_element()
8764 };
8765
8766 Some(
8767 h_flex()
8768 .h_full()
8769 .flex_1()
8770 .gap_2()
8771 .pr_1()
8772 .overflow_x_hidden()
8773 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8774 .child(left)
8775 .child(preview),
8776 )
8777 }
8778 }
8779 }
8780
8781 pub fn render_context_menu(
8782 &self,
8783 style: &EditorStyle,
8784 max_height_in_lines: u32,
8785 window: &mut Window,
8786 cx: &mut Context<Editor>,
8787 ) -> Option<AnyElement> {
8788 let menu = self.context_menu.borrow();
8789 let menu = menu.as_ref()?;
8790 if !menu.visible() {
8791 return None;
8792 };
8793 Some(menu.render(style, max_height_in_lines, window, cx))
8794 }
8795
8796 fn render_context_menu_aside(
8797 &mut self,
8798 max_size: Size<Pixels>,
8799 window: &mut Window,
8800 cx: &mut Context<Editor>,
8801 ) -> Option<AnyElement> {
8802 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8803 if menu.visible() {
8804 menu.render_aside(max_size, window, cx)
8805 } else {
8806 None
8807 }
8808 })
8809 }
8810
8811 fn hide_context_menu(
8812 &mut self,
8813 window: &mut Window,
8814 cx: &mut Context<Self>,
8815 ) -> Option<CodeContextMenu> {
8816 cx.notify();
8817 self.completion_tasks.clear();
8818 let context_menu = self.context_menu.borrow_mut().take();
8819 self.stale_inline_completion_in_menu.take();
8820 self.update_visible_inline_completion(window, cx);
8821 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
8822 if let Some(completion_provider) = &self.completion_provider {
8823 completion_provider.selection_changed(None, window, cx);
8824 }
8825 }
8826 context_menu
8827 }
8828
8829 fn show_snippet_choices(
8830 &mut self,
8831 choices: &Vec<String>,
8832 selection: Range<Anchor>,
8833 cx: &mut Context<Self>,
8834 ) {
8835 if selection.start.buffer_id.is_none() {
8836 return;
8837 }
8838 let buffer_id = selection.start.buffer_id.unwrap();
8839 let buffer = self.buffer().read(cx).buffer(buffer_id);
8840 let id = post_inc(&mut self.next_completion_id);
8841 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8842
8843 if let Some(buffer) = buffer {
8844 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8845 CompletionsMenu::new_snippet_choices(
8846 id,
8847 true,
8848 choices,
8849 selection,
8850 buffer,
8851 snippet_sort_order,
8852 ),
8853 ));
8854 }
8855 }
8856
8857 pub fn insert_snippet(
8858 &mut self,
8859 insertion_ranges: &[Range<usize>],
8860 snippet: Snippet,
8861 window: &mut Window,
8862 cx: &mut Context<Self>,
8863 ) -> Result<()> {
8864 struct Tabstop<T> {
8865 is_end_tabstop: bool,
8866 ranges: Vec<Range<T>>,
8867 choices: Option<Vec<String>>,
8868 }
8869
8870 let tabstops = self.buffer.update(cx, |buffer, cx| {
8871 let snippet_text: Arc<str> = snippet.text.clone().into();
8872 let edits = insertion_ranges
8873 .iter()
8874 .cloned()
8875 .map(|range| (range, snippet_text.clone()));
8876 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8877
8878 let snapshot = &*buffer.read(cx);
8879 let snippet = &snippet;
8880 snippet
8881 .tabstops
8882 .iter()
8883 .map(|tabstop| {
8884 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8885 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8886 });
8887 let mut tabstop_ranges = tabstop
8888 .ranges
8889 .iter()
8890 .flat_map(|tabstop_range| {
8891 let mut delta = 0_isize;
8892 insertion_ranges.iter().map(move |insertion_range| {
8893 let insertion_start = insertion_range.start as isize + delta;
8894 delta +=
8895 snippet.text.len() as isize - insertion_range.len() as isize;
8896
8897 let start = ((insertion_start + tabstop_range.start) as usize)
8898 .min(snapshot.len());
8899 let end = ((insertion_start + tabstop_range.end) as usize)
8900 .min(snapshot.len());
8901 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8902 })
8903 })
8904 .collect::<Vec<_>>();
8905 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8906
8907 Tabstop {
8908 is_end_tabstop,
8909 ranges: tabstop_ranges,
8910 choices: tabstop.choices.clone(),
8911 }
8912 })
8913 .collect::<Vec<_>>()
8914 });
8915 if let Some(tabstop) = tabstops.first() {
8916 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8917 s.select_ranges(tabstop.ranges.iter().cloned());
8918 });
8919
8920 if let Some(choices) = &tabstop.choices {
8921 if let Some(selection) = tabstop.ranges.first() {
8922 self.show_snippet_choices(choices, selection.clone(), cx)
8923 }
8924 }
8925
8926 // If we're already at the last tabstop and it's at the end of the snippet,
8927 // we're done, we don't need to keep the state around.
8928 if !tabstop.is_end_tabstop {
8929 let choices = tabstops
8930 .iter()
8931 .map(|tabstop| tabstop.choices.clone())
8932 .collect();
8933
8934 let ranges = tabstops
8935 .into_iter()
8936 .map(|tabstop| tabstop.ranges)
8937 .collect::<Vec<_>>();
8938
8939 self.snippet_stack.push(SnippetState {
8940 active_index: 0,
8941 ranges,
8942 choices,
8943 });
8944 }
8945
8946 // Check whether the just-entered snippet ends with an auto-closable bracket.
8947 if self.autoclose_regions.is_empty() {
8948 let snapshot = self.buffer.read(cx).snapshot(cx);
8949 for selection in &mut self.selections.all::<Point>(cx) {
8950 let selection_head = selection.head();
8951 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8952 continue;
8953 };
8954
8955 let mut bracket_pair = None;
8956 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8957 let prev_chars = snapshot
8958 .reversed_chars_at(selection_head)
8959 .collect::<String>();
8960 for (pair, enabled) in scope.brackets() {
8961 if enabled
8962 && pair.close
8963 && prev_chars.starts_with(pair.start.as_str())
8964 && next_chars.starts_with(pair.end.as_str())
8965 {
8966 bracket_pair = Some(pair.clone());
8967 break;
8968 }
8969 }
8970 if let Some(pair) = bracket_pair {
8971 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8972 let autoclose_enabled =
8973 self.use_autoclose && snapshot_settings.use_autoclose;
8974 if autoclose_enabled {
8975 let start = snapshot.anchor_after(selection_head);
8976 let end = snapshot.anchor_after(selection_head);
8977 self.autoclose_regions.push(AutocloseRegion {
8978 selection_id: selection.id,
8979 range: start..end,
8980 pair,
8981 });
8982 }
8983 }
8984 }
8985 }
8986 }
8987 Ok(())
8988 }
8989
8990 pub fn move_to_next_snippet_tabstop(
8991 &mut self,
8992 window: &mut Window,
8993 cx: &mut Context<Self>,
8994 ) -> bool {
8995 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8996 }
8997
8998 pub fn move_to_prev_snippet_tabstop(
8999 &mut self,
9000 window: &mut Window,
9001 cx: &mut Context<Self>,
9002 ) -> bool {
9003 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9004 }
9005
9006 pub fn move_to_snippet_tabstop(
9007 &mut self,
9008 bias: Bias,
9009 window: &mut Window,
9010 cx: &mut Context<Self>,
9011 ) -> bool {
9012 if let Some(mut snippet) = self.snippet_stack.pop() {
9013 match bias {
9014 Bias::Left => {
9015 if snippet.active_index > 0 {
9016 snippet.active_index -= 1;
9017 } else {
9018 self.snippet_stack.push(snippet);
9019 return false;
9020 }
9021 }
9022 Bias::Right => {
9023 if snippet.active_index + 1 < snippet.ranges.len() {
9024 snippet.active_index += 1;
9025 } else {
9026 self.snippet_stack.push(snippet);
9027 return false;
9028 }
9029 }
9030 }
9031 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9032 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9033 s.select_anchor_ranges(current_ranges.iter().cloned())
9034 });
9035
9036 if let Some(choices) = &snippet.choices[snippet.active_index] {
9037 if let Some(selection) = current_ranges.first() {
9038 self.show_snippet_choices(&choices, selection.clone(), cx);
9039 }
9040 }
9041
9042 // If snippet state is not at the last tabstop, push it back on the stack
9043 if snippet.active_index + 1 < snippet.ranges.len() {
9044 self.snippet_stack.push(snippet);
9045 }
9046 return true;
9047 }
9048 }
9049
9050 false
9051 }
9052
9053 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9054 self.transact(window, cx, |this, window, cx| {
9055 this.select_all(&SelectAll, window, cx);
9056 this.insert("", window, cx);
9057 });
9058 }
9059
9060 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9061 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9062 self.transact(window, cx, |this, window, cx| {
9063 this.select_autoclose_pair(window, cx);
9064 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9065 if !this.linked_edit_ranges.is_empty() {
9066 let selections = this.selections.all::<MultiBufferPoint>(cx);
9067 let snapshot = this.buffer.read(cx).snapshot(cx);
9068
9069 for selection in selections.iter() {
9070 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9071 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9072 if selection_start.buffer_id != selection_end.buffer_id {
9073 continue;
9074 }
9075 if let Some(ranges) =
9076 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9077 {
9078 for (buffer, entries) in ranges {
9079 linked_ranges.entry(buffer).or_default().extend(entries);
9080 }
9081 }
9082 }
9083 }
9084
9085 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9086 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9087 for selection in &mut selections {
9088 if selection.is_empty() {
9089 let old_head = selection.head();
9090 let mut new_head =
9091 movement::left(&display_map, old_head.to_display_point(&display_map))
9092 .to_point(&display_map);
9093 if let Some((buffer, line_buffer_range)) = display_map
9094 .buffer_snapshot
9095 .buffer_line_for_row(MultiBufferRow(old_head.row))
9096 {
9097 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9098 let indent_len = match indent_size.kind {
9099 IndentKind::Space => {
9100 buffer.settings_at(line_buffer_range.start, cx).tab_size
9101 }
9102 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9103 };
9104 if old_head.column <= indent_size.len && old_head.column > 0 {
9105 let indent_len = indent_len.get();
9106 new_head = cmp::min(
9107 new_head,
9108 MultiBufferPoint::new(
9109 old_head.row,
9110 ((old_head.column - 1) / indent_len) * indent_len,
9111 ),
9112 );
9113 }
9114 }
9115
9116 selection.set_head(new_head, SelectionGoal::None);
9117 }
9118 }
9119
9120 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9121 s.select(selections)
9122 });
9123 this.insert("", window, cx);
9124 let empty_str: Arc<str> = Arc::from("");
9125 for (buffer, edits) in linked_ranges {
9126 let snapshot = buffer.read(cx).snapshot();
9127 use text::ToPoint as TP;
9128
9129 let edits = edits
9130 .into_iter()
9131 .map(|range| {
9132 let end_point = TP::to_point(&range.end, &snapshot);
9133 let mut start_point = TP::to_point(&range.start, &snapshot);
9134
9135 if end_point == start_point {
9136 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9137 .saturating_sub(1);
9138 start_point =
9139 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9140 };
9141
9142 (start_point..end_point, empty_str.clone())
9143 })
9144 .sorted_by_key(|(range, _)| range.start)
9145 .collect::<Vec<_>>();
9146 buffer.update(cx, |this, cx| {
9147 this.edit(edits, None, cx);
9148 })
9149 }
9150 this.refresh_inline_completion(true, false, window, cx);
9151 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9152 });
9153 }
9154
9155 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9156 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9157 self.transact(window, cx, |this, window, cx| {
9158 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9159 s.move_with(|map, selection| {
9160 if selection.is_empty() {
9161 let cursor = movement::right(map, selection.head());
9162 selection.end = cursor;
9163 selection.reversed = true;
9164 selection.goal = SelectionGoal::None;
9165 }
9166 })
9167 });
9168 this.insert("", window, cx);
9169 this.refresh_inline_completion(true, false, window, cx);
9170 });
9171 }
9172
9173 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9174 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9175 if self.move_to_prev_snippet_tabstop(window, cx) {
9176 return;
9177 }
9178 self.outdent(&Outdent, window, cx);
9179 }
9180
9181 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9182 if self.move_to_next_snippet_tabstop(window, cx) {
9183 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9184 return;
9185 }
9186 if self.read_only(cx) {
9187 return;
9188 }
9189 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9190 let mut selections = self.selections.all_adjusted(cx);
9191 let buffer = self.buffer.read(cx);
9192 let snapshot = buffer.snapshot(cx);
9193 let rows_iter = selections.iter().map(|s| s.head().row);
9194 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9195
9196 let has_some_cursor_in_whitespace = selections
9197 .iter()
9198 .filter(|selection| selection.is_empty())
9199 .any(|selection| {
9200 let cursor = selection.head();
9201 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9202 cursor.column < current_indent.len
9203 });
9204
9205 let mut edits = Vec::new();
9206 let mut prev_edited_row = 0;
9207 let mut row_delta = 0;
9208 for selection in &mut selections {
9209 if selection.start.row != prev_edited_row {
9210 row_delta = 0;
9211 }
9212 prev_edited_row = selection.end.row;
9213
9214 // If the selection is non-empty, then increase the indentation of the selected lines.
9215 if !selection.is_empty() {
9216 row_delta =
9217 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9218 continue;
9219 }
9220
9221 let cursor = selection.head();
9222 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9223 if let Some(suggested_indent) =
9224 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9225 {
9226 // Don't do anything if already at suggested indent
9227 // and there is any other cursor which is not
9228 if has_some_cursor_in_whitespace
9229 && cursor.column == current_indent.len
9230 && current_indent.len == suggested_indent.len
9231 {
9232 continue;
9233 }
9234
9235 // Adjust line and move cursor to suggested indent
9236 // if cursor is not at suggested indent
9237 if cursor.column < suggested_indent.len
9238 && cursor.column <= current_indent.len
9239 && current_indent.len <= suggested_indent.len
9240 {
9241 selection.start = Point::new(cursor.row, suggested_indent.len);
9242 selection.end = selection.start;
9243 if row_delta == 0 {
9244 edits.extend(Buffer::edit_for_indent_size_adjustment(
9245 cursor.row,
9246 current_indent,
9247 suggested_indent,
9248 ));
9249 row_delta = suggested_indent.len - current_indent.len;
9250 }
9251 continue;
9252 }
9253
9254 // If current indent is more than suggested indent
9255 // only move cursor to current indent and skip indent
9256 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9257 selection.start = Point::new(cursor.row, current_indent.len);
9258 selection.end = selection.start;
9259 continue;
9260 }
9261 }
9262
9263 // Otherwise, insert a hard or soft tab.
9264 let settings = buffer.language_settings_at(cursor, cx);
9265 let tab_size = if settings.hard_tabs {
9266 IndentSize::tab()
9267 } else {
9268 let tab_size = settings.tab_size.get();
9269 let indent_remainder = snapshot
9270 .text_for_range(Point::new(cursor.row, 0)..cursor)
9271 .flat_map(str::chars)
9272 .fold(row_delta % tab_size, |counter: u32, c| {
9273 if c == '\t' {
9274 0
9275 } else {
9276 (counter + 1) % tab_size
9277 }
9278 });
9279
9280 let chars_to_next_tab_stop = tab_size - indent_remainder;
9281 IndentSize::spaces(chars_to_next_tab_stop)
9282 };
9283 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9284 selection.end = selection.start;
9285 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9286 row_delta += tab_size.len;
9287 }
9288
9289 self.transact(window, cx, |this, window, cx| {
9290 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9291 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9292 s.select(selections)
9293 });
9294 this.refresh_inline_completion(true, false, window, cx);
9295 });
9296 }
9297
9298 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9299 if self.read_only(cx) {
9300 return;
9301 }
9302 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9303 let mut selections = self.selections.all::<Point>(cx);
9304 let mut prev_edited_row = 0;
9305 let mut row_delta = 0;
9306 let mut edits = Vec::new();
9307 let buffer = self.buffer.read(cx);
9308 let snapshot = buffer.snapshot(cx);
9309 for selection in &mut selections {
9310 if selection.start.row != prev_edited_row {
9311 row_delta = 0;
9312 }
9313 prev_edited_row = selection.end.row;
9314
9315 row_delta =
9316 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9317 }
9318
9319 self.transact(window, cx, |this, window, cx| {
9320 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9321 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9322 s.select(selections)
9323 });
9324 });
9325 }
9326
9327 fn indent_selection(
9328 buffer: &MultiBuffer,
9329 snapshot: &MultiBufferSnapshot,
9330 selection: &mut Selection<Point>,
9331 edits: &mut Vec<(Range<Point>, String)>,
9332 delta_for_start_row: u32,
9333 cx: &App,
9334 ) -> u32 {
9335 let settings = buffer.language_settings_at(selection.start, cx);
9336 let tab_size = settings.tab_size.get();
9337 let indent_kind = if settings.hard_tabs {
9338 IndentKind::Tab
9339 } else {
9340 IndentKind::Space
9341 };
9342 let mut start_row = selection.start.row;
9343 let mut end_row = selection.end.row + 1;
9344
9345 // If a selection ends at the beginning of a line, don't indent
9346 // that last line.
9347 if selection.end.column == 0 && selection.end.row > selection.start.row {
9348 end_row -= 1;
9349 }
9350
9351 // Avoid re-indenting a row that has already been indented by a
9352 // previous selection, but still update this selection's column
9353 // to reflect that indentation.
9354 if delta_for_start_row > 0 {
9355 start_row += 1;
9356 selection.start.column += delta_for_start_row;
9357 if selection.end.row == selection.start.row {
9358 selection.end.column += delta_for_start_row;
9359 }
9360 }
9361
9362 let mut delta_for_end_row = 0;
9363 let has_multiple_rows = start_row + 1 != end_row;
9364 for row in start_row..end_row {
9365 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9366 let indent_delta = match (current_indent.kind, indent_kind) {
9367 (IndentKind::Space, IndentKind::Space) => {
9368 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9369 IndentSize::spaces(columns_to_next_tab_stop)
9370 }
9371 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9372 (_, IndentKind::Tab) => IndentSize::tab(),
9373 };
9374
9375 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9376 0
9377 } else {
9378 selection.start.column
9379 };
9380 let row_start = Point::new(row, start);
9381 edits.push((
9382 row_start..row_start,
9383 indent_delta.chars().collect::<String>(),
9384 ));
9385
9386 // Update this selection's endpoints to reflect the indentation.
9387 if row == selection.start.row {
9388 selection.start.column += indent_delta.len;
9389 }
9390 if row == selection.end.row {
9391 selection.end.column += indent_delta.len;
9392 delta_for_end_row = indent_delta.len;
9393 }
9394 }
9395
9396 if selection.start.row == selection.end.row {
9397 delta_for_start_row + delta_for_end_row
9398 } else {
9399 delta_for_end_row
9400 }
9401 }
9402
9403 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9404 if self.read_only(cx) {
9405 return;
9406 }
9407 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9408 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9409 let selections = self.selections.all::<Point>(cx);
9410 let mut deletion_ranges = Vec::new();
9411 let mut last_outdent = None;
9412 {
9413 let buffer = self.buffer.read(cx);
9414 let snapshot = buffer.snapshot(cx);
9415 for selection in &selections {
9416 let settings = buffer.language_settings_at(selection.start, cx);
9417 let tab_size = settings.tab_size.get();
9418 let mut rows = selection.spanned_rows(false, &display_map);
9419
9420 // Avoid re-outdenting a row that has already been outdented by a
9421 // previous selection.
9422 if let Some(last_row) = last_outdent {
9423 if last_row == rows.start {
9424 rows.start = rows.start.next_row();
9425 }
9426 }
9427 let has_multiple_rows = rows.len() > 1;
9428 for row in rows.iter_rows() {
9429 let indent_size = snapshot.indent_size_for_line(row);
9430 if indent_size.len > 0 {
9431 let deletion_len = match indent_size.kind {
9432 IndentKind::Space => {
9433 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9434 if columns_to_prev_tab_stop == 0 {
9435 tab_size
9436 } else {
9437 columns_to_prev_tab_stop
9438 }
9439 }
9440 IndentKind::Tab => 1,
9441 };
9442 let start = if has_multiple_rows
9443 || deletion_len > selection.start.column
9444 || indent_size.len < selection.start.column
9445 {
9446 0
9447 } else {
9448 selection.start.column - deletion_len
9449 };
9450 deletion_ranges.push(
9451 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9452 );
9453 last_outdent = Some(row);
9454 }
9455 }
9456 }
9457 }
9458
9459 self.transact(window, cx, |this, window, cx| {
9460 this.buffer.update(cx, |buffer, cx| {
9461 let empty_str: Arc<str> = Arc::default();
9462 buffer.edit(
9463 deletion_ranges
9464 .into_iter()
9465 .map(|range| (range, empty_str.clone())),
9466 None,
9467 cx,
9468 );
9469 });
9470 let selections = this.selections.all::<usize>(cx);
9471 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9472 s.select(selections)
9473 });
9474 });
9475 }
9476
9477 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9478 if self.read_only(cx) {
9479 return;
9480 }
9481 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9482 let selections = self
9483 .selections
9484 .all::<usize>(cx)
9485 .into_iter()
9486 .map(|s| s.range());
9487
9488 self.transact(window, cx, |this, window, cx| {
9489 this.buffer.update(cx, |buffer, cx| {
9490 buffer.autoindent_ranges(selections, cx);
9491 });
9492 let selections = this.selections.all::<usize>(cx);
9493 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9494 s.select(selections)
9495 });
9496 });
9497 }
9498
9499 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9500 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9501 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9502 let selections = self.selections.all::<Point>(cx);
9503
9504 let mut new_cursors = Vec::new();
9505 let mut edit_ranges = Vec::new();
9506 let mut selections = selections.iter().peekable();
9507 while let Some(selection) = selections.next() {
9508 let mut rows = selection.spanned_rows(false, &display_map);
9509 let goal_display_column = selection.head().to_display_point(&display_map).column();
9510
9511 // Accumulate contiguous regions of rows that we want to delete.
9512 while let Some(next_selection) = selections.peek() {
9513 let next_rows = next_selection.spanned_rows(false, &display_map);
9514 if next_rows.start <= rows.end {
9515 rows.end = next_rows.end;
9516 selections.next().unwrap();
9517 } else {
9518 break;
9519 }
9520 }
9521
9522 let buffer = &display_map.buffer_snapshot;
9523 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9524 let edit_end;
9525 let cursor_buffer_row;
9526 if buffer.max_point().row >= rows.end.0 {
9527 // If there's a line after the range, delete the \n from the end of the row range
9528 // and position the cursor on the next line.
9529 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9530 cursor_buffer_row = rows.end;
9531 } else {
9532 // If there isn't a line after the range, delete the \n from the line before the
9533 // start of the row range and position the cursor there.
9534 edit_start = edit_start.saturating_sub(1);
9535 edit_end = buffer.len();
9536 cursor_buffer_row = rows.start.previous_row();
9537 }
9538
9539 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9540 *cursor.column_mut() =
9541 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9542
9543 new_cursors.push((
9544 selection.id,
9545 buffer.anchor_after(cursor.to_point(&display_map)),
9546 ));
9547 edit_ranges.push(edit_start..edit_end);
9548 }
9549
9550 self.transact(window, cx, |this, window, cx| {
9551 let buffer = this.buffer.update(cx, |buffer, cx| {
9552 let empty_str: Arc<str> = Arc::default();
9553 buffer.edit(
9554 edit_ranges
9555 .into_iter()
9556 .map(|range| (range, empty_str.clone())),
9557 None,
9558 cx,
9559 );
9560 buffer.snapshot(cx)
9561 });
9562 let new_selections = new_cursors
9563 .into_iter()
9564 .map(|(id, cursor)| {
9565 let cursor = cursor.to_point(&buffer);
9566 Selection {
9567 id,
9568 start: cursor,
9569 end: cursor,
9570 reversed: false,
9571 goal: SelectionGoal::None,
9572 }
9573 })
9574 .collect();
9575
9576 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9577 s.select(new_selections);
9578 });
9579 });
9580 }
9581
9582 pub fn join_lines_impl(
9583 &mut self,
9584 insert_whitespace: bool,
9585 window: &mut Window,
9586 cx: &mut Context<Self>,
9587 ) {
9588 if self.read_only(cx) {
9589 return;
9590 }
9591 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9592 for selection in self.selections.all::<Point>(cx) {
9593 let start = MultiBufferRow(selection.start.row);
9594 // Treat single line selections as if they include the next line. Otherwise this action
9595 // would do nothing for single line selections individual cursors.
9596 let end = if selection.start.row == selection.end.row {
9597 MultiBufferRow(selection.start.row + 1)
9598 } else {
9599 MultiBufferRow(selection.end.row)
9600 };
9601
9602 if let Some(last_row_range) = row_ranges.last_mut() {
9603 if start <= last_row_range.end {
9604 last_row_range.end = end;
9605 continue;
9606 }
9607 }
9608 row_ranges.push(start..end);
9609 }
9610
9611 let snapshot = self.buffer.read(cx).snapshot(cx);
9612 let mut cursor_positions = Vec::new();
9613 for row_range in &row_ranges {
9614 let anchor = snapshot.anchor_before(Point::new(
9615 row_range.end.previous_row().0,
9616 snapshot.line_len(row_range.end.previous_row()),
9617 ));
9618 cursor_positions.push(anchor..anchor);
9619 }
9620
9621 self.transact(window, cx, |this, window, cx| {
9622 for row_range in row_ranges.into_iter().rev() {
9623 for row in row_range.iter_rows().rev() {
9624 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9625 let next_line_row = row.next_row();
9626 let indent = snapshot.indent_size_for_line(next_line_row);
9627 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9628
9629 let replace =
9630 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9631 " "
9632 } else {
9633 ""
9634 };
9635
9636 this.buffer.update(cx, |buffer, cx| {
9637 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9638 });
9639 }
9640 }
9641
9642 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9643 s.select_anchor_ranges(cursor_positions)
9644 });
9645 });
9646 }
9647
9648 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9649 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9650 self.join_lines_impl(true, window, cx);
9651 }
9652
9653 pub fn sort_lines_case_sensitive(
9654 &mut self,
9655 _: &SortLinesCaseSensitive,
9656 window: &mut Window,
9657 cx: &mut Context<Self>,
9658 ) {
9659 self.manipulate_lines(window, cx, |lines| lines.sort())
9660 }
9661
9662 pub fn sort_lines_case_insensitive(
9663 &mut self,
9664 _: &SortLinesCaseInsensitive,
9665 window: &mut Window,
9666 cx: &mut Context<Self>,
9667 ) {
9668 self.manipulate_lines(window, cx, |lines| {
9669 lines.sort_by_key(|line| line.to_lowercase())
9670 })
9671 }
9672
9673 pub fn unique_lines_case_insensitive(
9674 &mut self,
9675 _: &UniqueLinesCaseInsensitive,
9676 window: &mut Window,
9677 cx: &mut Context<Self>,
9678 ) {
9679 self.manipulate_lines(window, cx, |lines| {
9680 let mut seen = HashSet::default();
9681 lines.retain(|line| seen.insert(line.to_lowercase()));
9682 })
9683 }
9684
9685 pub fn unique_lines_case_sensitive(
9686 &mut self,
9687 _: &UniqueLinesCaseSensitive,
9688 window: &mut Window,
9689 cx: &mut Context<Self>,
9690 ) {
9691 self.manipulate_lines(window, cx, |lines| {
9692 let mut seen = HashSet::default();
9693 lines.retain(|line| seen.insert(*line));
9694 })
9695 }
9696
9697 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9698 let Some(project) = self.project.clone() else {
9699 return;
9700 };
9701 self.reload(project, window, cx)
9702 .detach_and_notify_err(window, cx);
9703 }
9704
9705 pub fn restore_file(
9706 &mut self,
9707 _: &::git::RestoreFile,
9708 window: &mut Window,
9709 cx: &mut Context<Self>,
9710 ) {
9711 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9712 let mut buffer_ids = HashSet::default();
9713 let snapshot = self.buffer().read(cx).snapshot(cx);
9714 for selection in self.selections.all::<usize>(cx) {
9715 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9716 }
9717
9718 let buffer = self.buffer().read(cx);
9719 let ranges = buffer_ids
9720 .into_iter()
9721 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9722 .collect::<Vec<_>>();
9723
9724 self.restore_hunks_in_ranges(ranges, window, cx);
9725 }
9726
9727 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9728 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9729 let selections = self
9730 .selections
9731 .all(cx)
9732 .into_iter()
9733 .map(|s| s.range())
9734 .collect();
9735 self.restore_hunks_in_ranges(selections, window, cx);
9736 }
9737
9738 pub fn restore_hunks_in_ranges(
9739 &mut self,
9740 ranges: Vec<Range<Point>>,
9741 window: &mut Window,
9742 cx: &mut Context<Editor>,
9743 ) {
9744 let mut revert_changes = HashMap::default();
9745 let chunk_by = self
9746 .snapshot(window, cx)
9747 .hunks_for_ranges(ranges)
9748 .into_iter()
9749 .chunk_by(|hunk| hunk.buffer_id);
9750 for (buffer_id, hunks) in &chunk_by {
9751 let hunks = hunks.collect::<Vec<_>>();
9752 for hunk in &hunks {
9753 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9754 }
9755 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9756 }
9757 drop(chunk_by);
9758 if !revert_changes.is_empty() {
9759 self.transact(window, cx, |editor, window, cx| {
9760 editor.restore(revert_changes, window, cx);
9761 });
9762 }
9763 }
9764
9765 pub fn open_active_item_in_terminal(
9766 &mut self,
9767 _: &OpenInTerminal,
9768 window: &mut Window,
9769 cx: &mut Context<Self>,
9770 ) {
9771 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9772 let project_path = buffer.read(cx).project_path(cx)?;
9773 let project = self.project.as_ref()?.read(cx);
9774 let entry = project.entry_for_path(&project_path, cx)?;
9775 let parent = match &entry.canonical_path {
9776 Some(canonical_path) => canonical_path.to_path_buf(),
9777 None => project.absolute_path(&project_path, cx)?,
9778 }
9779 .parent()?
9780 .to_path_buf();
9781 Some(parent)
9782 }) {
9783 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9784 }
9785 }
9786
9787 fn set_breakpoint_context_menu(
9788 &mut self,
9789 display_row: DisplayRow,
9790 position: Option<Anchor>,
9791 clicked_point: gpui::Point<Pixels>,
9792 window: &mut Window,
9793 cx: &mut Context<Self>,
9794 ) {
9795 if !cx.has_flag::<DebuggerFeatureFlag>() {
9796 return;
9797 }
9798 let source = self
9799 .buffer
9800 .read(cx)
9801 .snapshot(cx)
9802 .anchor_before(Point::new(display_row.0, 0u32));
9803
9804 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9805
9806 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9807 self,
9808 source,
9809 clicked_point,
9810 context_menu,
9811 window,
9812 cx,
9813 );
9814 }
9815
9816 fn add_edit_breakpoint_block(
9817 &mut self,
9818 anchor: Anchor,
9819 breakpoint: &Breakpoint,
9820 edit_action: BreakpointPromptEditAction,
9821 window: &mut Window,
9822 cx: &mut Context<Self>,
9823 ) {
9824 let weak_editor = cx.weak_entity();
9825 let bp_prompt = cx.new(|cx| {
9826 BreakpointPromptEditor::new(
9827 weak_editor,
9828 anchor,
9829 breakpoint.clone(),
9830 edit_action,
9831 window,
9832 cx,
9833 )
9834 });
9835
9836 let height = bp_prompt.update(cx, |this, cx| {
9837 this.prompt
9838 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9839 });
9840 let cloned_prompt = bp_prompt.clone();
9841 let blocks = vec![BlockProperties {
9842 style: BlockStyle::Sticky,
9843 placement: BlockPlacement::Above(anchor),
9844 height: Some(height),
9845 render: Arc::new(move |cx| {
9846 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9847 cloned_prompt.clone().into_any_element()
9848 }),
9849 priority: 0,
9850 render_in_minimap: true,
9851 }];
9852
9853 let focus_handle = bp_prompt.focus_handle(cx);
9854 window.focus(&focus_handle);
9855
9856 let block_ids = self.insert_blocks(blocks, None, cx);
9857 bp_prompt.update(cx, |prompt, _| {
9858 prompt.add_block_ids(block_ids);
9859 });
9860 }
9861
9862 pub(crate) fn breakpoint_at_row(
9863 &self,
9864 row: u32,
9865 window: &mut Window,
9866 cx: &mut Context<Self>,
9867 ) -> Option<(Anchor, Breakpoint)> {
9868 let snapshot = self.snapshot(window, cx);
9869 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9870
9871 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9872 }
9873
9874 pub(crate) fn breakpoint_at_anchor(
9875 &self,
9876 breakpoint_position: Anchor,
9877 snapshot: &EditorSnapshot,
9878 cx: &mut Context<Self>,
9879 ) -> Option<(Anchor, Breakpoint)> {
9880 let project = self.project.clone()?;
9881
9882 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9883 snapshot
9884 .buffer_snapshot
9885 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9886 })?;
9887
9888 let enclosing_excerpt = breakpoint_position.excerpt_id;
9889 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
9890 let buffer_snapshot = buffer.read(cx).snapshot();
9891
9892 let row = buffer_snapshot
9893 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9894 .row;
9895
9896 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9897 let anchor_end = snapshot
9898 .buffer_snapshot
9899 .anchor_after(Point::new(row, line_len));
9900
9901 let bp = self
9902 .breakpoint_store
9903 .as_ref()?
9904 .read_with(cx, |breakpoint_store, cx| {
9905 breakpoint_store
9906 .breakpoints(
9907 &buffer,
9908 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9909 &buffer_snapshot,
9910 cx,
9911 )
9912 .next()
9913 .and_then(|(bp, _)| {
9914 let breakpoint_row = buffer_snapshot
9915 .summary_for_anchor::<text::PointUtf16>(&bp.position)
9916 .row;
9917
9918 if breakpoint_row == row {
9919 snapshot
9920 .buffer_snapshot
9921 .anchor_in_excerpt(enclosing_excerpt, bp.position)
9922 .map(|position| (position, bp.bp.clone()))
9923 } else {
9924 None
9925 }
9926 })
9927 });
9928 bp
9929 }
9930
9931 pub fn edit_log_breakpoint(
9932 &mut self,
9933 _: &EditLogBreakpoint,
9934 window: &mut Window,
9935 cx: &mut Context<Self>,
9936 ) {
9937 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9938 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9939 message: None,
9940 state: BreakpointState::Enabled,
9941 condition: None,
9942 hit_condition: None,
9943 });
9944
9945 self.add_edit_breakpoint_block(
9946 anchor,
9947 &breakpoint,
9948 BreakpointPromptEditAction::Log,
9949 window,
9950 cx,
9951 );
9952 }
9953 }
9954
9955 fn breakpoints_at_cursors(
9956 &self,
9957 window: &mut Window,
9958 cx: &mut Context<Self>,
9959 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9960 let snapshot = self.snapshot(window, cx);
9961 let cursors = self
9962 .selections
9963 .disjoint_anchors()
9964 .into_iter()
9965 .map(|selection| {
9966 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9967
9968 let breakpoint_position = self
9969 .breakpoint_at_row(cursor_position.row, window, cx)
9970 .map(|bp| bp.0)
9971 .unwrap_or_else(|| {
9972 snapshot
9973 .display_snapshot
9974 .buffer_snapshot
9975 .anchor_after(Point::new(cursor_position.row, 0))
9976 });
9977
9978 let breakpoint = self
9979 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9980 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9981
9982 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9983 })
9984 // 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.
9985 .collect::<HashMap<Anchor, _>>();
9986
9987 cursors.into_iter().collect()
9988 }
9989
9990 pub fn enable_breakpoint(
9991 &mut self,
9992 _: &crate::actions::EnableBreakpoint,
9993 window: &mut Window,
9994 cx: &mut Context<Self>,
9995 ) {
9996 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9997 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9998 continue;
9999 };
10000 self.edit_breakpoint_at_anchor(
10001 anchor,
10002 breakpoint,
10003 BreakpointEditAction::InvertState,
10004 cx,
10005 );
10006 }
10007 }
10008
10009 pub fn disable_breakpoint(
10010 &mut self,
10011 _: &crate::actions::DisableBreakpoint,
10012 window: &mut Window,
10013 cx: &mut Context<Self>,
10014 ) {
10015 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10016 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10017 continue;
10018 };
10019 self.edit_breakpoint_at_anchor(
10020 anchor,
10021 breakpoint,
10022 BreakpointEditAction::InvertState,
10023 cx,
10024 );
10025 }
10026 }
10027
10028 pub fn toggle_breakpoint(
10029 &mut self,
10030 _: &crate::actions::ToggleBreakpoint,
10031 window: &mut Window,
10032 cx: &mut Context<Self>,
10033 ) {
10034 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10035 if let Some(breakpoint) = breakpoint {
10036 self.edit_breakpoint_at_anchor(
10037 anchor,
10038 breakpoint,
10039 BreakpointEditAction::Toggle,
10040 cx,
10041 );
10042 } else {
10043 self.edit_breakpoint_at_anchor(
10044 anchor,
10045 Breakpoint::new_standard(),
10046 BreakpointEditAction::Toggle,
10047 cx,
10048 );
10049 }
10050 }
10051 }
10052
10053 pub fn edit_breakpoint_at_anchor(
10054 &mut self,
10055 breakpoint_position: Anchor,
10056 breakpoint: Breakpoint,
10057 edit_action: BreakpointEditAction,
10058 cx: &mut Context<Self>,
10059 ) {
10060 let Some(breakpoint_store) = &self.breakpoint_store else {
10061 return;
10062 };
10063
10064 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10065 if breakpoint_position == Anchor::min() {
10066 self.buffer()
10067 .read(cx)
10068 .excerpt_buffer_ids()
10069 .into_iter()
10070 .next()
10071 } else {
10072 None
10073 }
10074 }) else {
10075 return;
10076 };
10077
10078 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10079 return;
10080 };
10081
10082 breakpoint_store.update(cx, |breakpoint_store, cx| {
10083 breakpoint_store.toggle_breakpoint(
10084 buffer,
10085 BreakpointWithPosition {
10086 position: breakpoint_position.text_anchor,
10087 bp: breakpoint,
10088 },
10089 edit_action,
10090 cx,
10091 );
10092 });
10093
10094 cx.notify();
10095 }
10096
10097 #[cfg(any(test, feature = "test-support"))]
10098 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10099 self.breakpoint_store.clone()
10100 }
10101
10102 pub fn prepare_restore_change(
10103 &self,
10104 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10105 hunk: &MultiBufferDiffHunk,
10106 cx: &mut App,
10107 ) -> Option<()> {
10108 if hunk.is_created_file() {
10109 return None;
10110 }
10111 let buffer = self.buffer.read(cx);
10112 let diff = buffer.diff_for(hunk.buffer_id)?;
10113 let buffer = buffer.buffer(hunk.buffer_id)?;
10114 let buffer = buffer.read(cx);
10115 let original_text = diff
10116 .read(cx)
10117 .base_text()
10118 .as_rope()
10119 .slice(hunk.diff_base_byte_range.clone());
10120 let buffer_snapshot = buffer.snapshot();
10121 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10122 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10123 probe
10124 .0
10125 .start
10126 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10127 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10128 }) {
10129 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10130 Some(())
10131 } else {
10132 None
10133 }
10134 }
10135
10136 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10137 self.manipulate_lines(window, cx, |lines| lines.reverse())
10138 }
10139
10140 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10141 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10142 }
10143
10144 fn manipulate_lines<Fn>(
10145 &mut self,
10146 window: &mut Window,
10147 cx: &mut Context<Self>,
10148 mut callback: Fn,
10149 ) where
10150 Fn: FnMut(&mut Vec<&str>),
10151 {
10152 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10153
10154 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10155 let buffer = self.buffer.read(cx).snapshot(cx);
10156
10157 let mut edits = Vec::new();
10158
10159 let selections = self.selections.all::<Point>(cx);
10160 let mut selections = selections.iter().peekable();
10161 let mut contiguous_row_selections = Vec::new();
10162 let mut new_selections = Vec::new();
10163 let mut added_lines = 0;
10164 let mut removed_lines = 0;
10165
10166 while let Some(selection) = selections.next() {
10167 let (start_row, end_row) = consume_contiguous_rows(
10168 &mut contiguous_row_selections,
10169 selection,
10170 &display_map,
10171 &mut selections,
10172 );
10173
10174 let start_point = Point::new(start_row.0, 0);
10175 let end_point = Point::new(
10176 end_row.previous_row().0,
10177 buffer.line_len(end_row.previous_row()),
10178 );
10179 let text = buffer
10180 .text_for_range(start_point..end_point)
10181 .collect::<String>();
10182
10183 let mut lines = text.split('\n').collect_vec();
10184
10185 let lines_before = lines.len();
10186 callback(&mut lines);
10187 let lines_after = lines.len();
10188
10189 edits.push((start_point..end_point, lines.join("\n")));
10190
10191 // Selections must change based on added and removed line count
10192 let start_row =
10193 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10194 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10195 new_selections.push(Selection {
10196 id: selection.id,
10197 start: start_row,
10198 end: end_row,
10199 goal: SelectionGoal::None,
10200 reversed: selection.reversed,
10201 });
10202
10203 if lines_after > lines_before {
10204 added_lines += lines_after - lines_before;
10205 } else if lines_before > lines_after {
10206 removed_lines += lines_before - lines_after;
10207 }
10208 }
10209
10210 self.transact(window, cx, |this, window, cx| {
10211 let buffer = this.buffer.update(cx, |buffer, cx| {
10212 buffer.edit(edits, None, cx);
10213 buffer.snapshot(cx)
10214 });
10215
10216 // Recalculate offsets on newly edited buffer
10217 let new_selections = new_selections
10218 .iter()
10219 .map(|s| {
10220 let start_point = Point::new(s.start.0, 0);
10221 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10222 Selection {
10223 id: s.id,
10224 start: buffer.point_to_offset(start_point),
10225 end: buffer.point_to_offset(end_point),
10226 goal: s.goal,
10227 reversed: s.reversed,
10228 }
10229 })
10230 .collect();
10231
10232 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10233 s.select(new_selections);
10234 });
10235
10236 this.request_autoscroll(Autoscroll::fit(), cx);
10237 });
10238 }
10239
10240 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10241 self.manipulate_text(window, cx, |text| {
10242 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10243 if has_upper_case_characters {
10244 text.to_lowercase()
10245 } else {
10246 text.to_uppercase()
10247 }
10248 })
10249 }
10250
10251 pub fn convert_to_upper_case(
10252 &mut self,
10253 _: &ConvertToUpperCase,
10254 window: &mut Window,
10255 cx: &mut Context<Self>,
10256 ) {
10257 self.manipulate_text(window, cx, |text| text.to_uppercase())
10258 }
10259
10260 pub fn convert_to_lower_case(
10261 &mut self,
10262 _: &ConvertToLowerCase,
10263 window: &mut Window,
10264 cx: &mut Context<Self>,
10265 ) {
10266 self.manipulate_text(window, cx, |text| text.to_lowercase())
10267 }
10268
10269 pub fn convert_to_title_case(
10270 &mut self,
10271 _: &ConvertToTitleCase,
10272 window: &mut Window,
10273 cx: &mut Context<Self>,
10274 ) {
10275 self.manipulate_text(window, cx, |text| {
10276 text.split('\n')
10277 .map(|line| line.to_case(Case::Title))
10278 .join("\n")
10279 })
10280 }
10281
10282 pub fn convert_to_snake_case(
10283 &mut self,
10284 _: &ConvertToSnakeCase,
10285 window: &mut Window,
10286 cx: &mut Context<Self>,
10287 ) {
10288 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10289 }
10290
10291 pub fn convert_to_kebab_case(
10292 &mut self,
10293 _: &ConvertToKebabCase,
10294 window: &mut Window,
10295 cx: &mut Context<Self>,
10296 ) {
10297 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10298 }
10299
10300 pub fn convert_to_upper_camel_case(
10301 &mut self,
10302 _: &ConvertToUpperCamelCase,
10303 window: &mut Window,
10304 cx: &mut Context<Self>,
10305 ) {
10306 self.manipulate_text(window, cx, |text| {
10307 text.split('\n')
10308 .map(|line| line.to_case(Case::UpperCamel))
10309 .join("\n")
10310 })
10311 }
10312
10313 pub fn convert_to_lower_camel_case(
10314 &mut self,
10315 _: &ConvertToLowerCamelCase,
10316 window: &mut Window,
10317 cx: &mut Context<Self>,
10318 ) {
10319 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10320 }
10321
10322 pub fn convert_to_opposite_case(
10323 &mut self,
10324 _: &ConvertToOppositeCase,
10325 window: &mut Window,
10326 cx: &mut Context<Self>,
10327 ) {
10328 self.manipulate_text(window, cx, |text| {
10329 text.chars()
10330 .fold(String::with_capacity(text.len()), |mut t, c| {
10331 if c.is_uppercase() {
10332 t.extend(c.to_lowercase());
10333 } else {
10334 t.extend(c.to_uppercase());
10335 }
10336 t
10337 })
10338 })
10339 }
10340
10341 pub fn convert_to_rot13(
10342 &mut self,
10343 _: &ConvertToRot13,
10344 window: &mut Window,
10345 cx: &mut Context<Self>,
10346 ) {
10347 self.manipulate_text(window, cx, |text| {
10348 text.chars()
10349 .map(|c| match c {
10350 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10351 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10352 _ => c,
10353 })
10354 .collect()
10355 })
10356 }
10357
10358 pub fn convert_to_rot47(
10359 &mut self,
10360 _: &ConvertToRot47,
10361 window: &mut Window,
10362 cx: &mut Context<Self>,
10363 ) {
10364 self.manipulate_text(window, cx, |text| {
10365 text.chars()
10366 .map(|c| {
10367 let code_point = c as u32;
10368 if code_point >= 33 && code_point <= 126 {
10369 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10370 }
10371 c
10372 })
10373 .collect()
10374 })
10375 }
10376
10377 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10378 where
10379 Fn: FnMut(&str) -> String,
10380 {
10381 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10382 let buffer = self.buffer.read(cx).snapshot(cx);
10383
10384 let mut new_selections = Vec::new();
10385 let mut edits = Vec::new();
10386 let mut selection_adjustment = 0i32;
10387
10388 for selection in self.selections.all::<usize>(cx) {
10389 let selection_is_empty = selection.is_empty();
10390
10391 let (start, end) = if selection_is_empty {
10392 let word_range = movement::surrounding_word(
10393 &display_map,
10394 selection.start.to_display_point(&display_map),
10395 );
10396 let start = word_range.start.to_offset(&display_map, Bias::Left);
10397 let end = word_range.end.to_offset(&display_map, Bias::Left);
10398 (start, end)
10399 } else {
10400 (selection.start, selection.end)
10401 };
10402
10403 let text = buffer.text_for_range(start..end).collect::<String>();
10404 let old_length = text.len() as i32;
10405 let text = callback(&text);
10406
10407 new_selections.push(Selection {
10408 start: (start as i32 - selection_adjustment) as usize,
10409 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10410 goal: SelectionGoal::None,
10411 ..selection
10412 });
10413
10414 selection_adjustment += old_length - text.len() as i32;
10415
10416 edits.push((start..end, text));
10417 }
10418
10419 self.transact(window, cx, |this, window, cx| {
10420 this.buffer.update(cx, |buffer, cx| {
10421 buffer.edit(edits, None, cx);
10422 });
10423
10424 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10425 s.select(new_selections);
10426 });
10427
10428 this.request_autoscroll(Autoscroll::fit(), cx);
10429 });
10430 }
10431
10432 pub fn duplicate(
10433 &mut self,
10434 upwards: bool,
10435 whole_lines: bool,
10436 window: &mut Window,
10437 cx: &mut Context<Self>,
10438 ) {
10439 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10440
10441 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10442 let buffer = &display_map.buffer_snapshot;
10443 let selections = self.selections.all::<Point>(cx);
10444
10445 let mut edits = Vec::new();
10446 let mut selections_iter = selections.iter().peekable();
10447 while let Some(selection) = selections_iter.next() {
10448 let mut rows = selection.spanned_rows(false, &display_map);
10449 // duplicate line-wise
10450 if whole_lines || selection.start == selection.end {
10451 // Avoid duplicating the same lines twice.
10452 while let Some(next_selection) = selections_iter.peek() {
10453 let next_rows = next_selection.spanned_rows(false, &display_map);
10454 if next_rows.start < rows.end {
10455 rows.end = next_rows.end;
10456 selections_iter.next().unwrap();
10457 } else {
10458 break;
10459 }
10460 }
10461
10462 // Copy the text from the selected row region and splice it either at the start
10463 // or end of the region.
10464 let start = Point::new(rows.start.0, 0);
10465 let end = Point::new(
10466 rows.end.previous_row().0,
10467 buffer.line_len(rows.end.previous_row()),
10468 );
10469 let text = buffer
10470 .text_for_range(start..end)
10471 .chain(Some("\n"))
10472 .collect::<String>();
10473 let insert_location = if upwards {
10474 Point::new(rows.end.0, 0)
10475 } else {
10476 start
10477 };
10478 edits.push((insert_location..insert_location, text));
10479 } else {
10480 // duplicate character-wise
10481 let start = selection.start;
10482 let end = selection.end;
10483 let text = buffer.text_for_range(start..end).collect::<String>();
10484 edits.push((selection.end..selection.end, text));
10485 }
10486 }
10487
10488 self.transact(window, cx, |this, _, cx| {
10489 this.buffer.update(cx, |buffer, cx| {
10490 buffer.edit(edits, None, cx);
10491 });
10492
10493 this.request_autoscroll(Autoscroll::fit(), cx);
10494 });
10495 }
10496
10497 pub fn duplicate_line_up(
10498 &mut self,
10499 _: &DuplicateLineUp,
10500 window: &mut Window,
10501 cx: &mut Context<Self>,
10502 ) {
10503 self.duplicate(true, true, window, cx);
10504 }
10505
10506 pub fn duplicate_line_down(
10507 &mut self,
10508 _: &DuplicateLineDown,
10509 window: &mut Window,
10510 cx: &mut Context<Self>,
10511 ) {
10512 self.duplicate(false, true, window, cx);
10513 }
10514
10515 pub fn duplicate_selection(
10516 &mut self,
10517 _: &DuplicateSelection,
10518 window: &mut Window,
10519 cx: &mut Context<Self>,
10520 ) {
10521 self.duplicate(false, false, window, cx);
10522 }
10523
10524 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10525 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10526
10527 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10528 let buffer = self.buffer.read(cx).snapshot(cx);
10529
10530 let mut edits = Vec::new();
10531 let mut unfold_ranges = Vec::new();
10532 let mut refold_creases = Vec::new();
10533
10534 let selections = self.selections.all::<Point>(cx);
10535 let mut selections = selections.iter().peekable();
10536 let mut contiguous_row_selections = Vec::new();
10537 let mut new_selections = Vec::new();
10538
10539 while let Some(selection) = selections.next() {
10540 // Find all the selections that span a contiguous row range
10541 let (start_row, end_row) = consume_contiguous_rows(
10542 &mut contiguous_row_selections,
10543 selection,
10544 &display_map,
10545 &mut selections,
10546 );
10547
10548 // Move the text spanned by the row range to be before the line preceding the row range
10549 if start_row.0 > 0 {
10550 let range_to_move = Point::new(
10551 start_row.previous_row().0,
10552 buffer.line_len(start_row.previous_row()),
10553 )
10554 ..Point::new(
10555 end_row.previous_row().0,
10556 buffer.line_len(end_row.previous_row()),
10557 );
10558 let insertion_point = display_map
10559 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10560 .0;
10561
10562 // Don't move lines across excerpts
10563 if buffer
10564 .excerpt_containing(insertion_point..range_to_move.end)
10565 .is_some()
10566 {
10567 let text = buffer
10568 .text_for_range(range_to_move.clone())
10569 .flat_map(|s| s.chars())
10570 .skip(1)
10571 .chain(['\n'])
10572 .collect::<String>();
10573
10574 edits.push((
10575 buffer.anchor_after(range_to_move.start)
10576 ..buffer.anchor_before(range_to_move.end),
10577 String::new(),
10578 ));
10579 let insertion_anchor = buffer.anchor_after(insertion_point);
10580 edits.push((insertion_anchor..insertion_anchor, text));
10581
10582 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10583
10584 // Move selections up
10585 new_selections.extend(contiguous_row_selections.drain(..).map(
10586 |mut selection| {
10587 selection.start.row -= row_delta;
10588 selection.end.row -= row_delta;
10589 selection
10590 },
10591 ));
10592
10593 // Move folds up
10594 unfold_ranges.push(range_to_move.clone());
10595 for fold in display_map.folds_in_range(
10596 buffer.anchor_before(range_to_move.start)
10597 ..buffer.anchor_after(range_to_move.end),
10598 ) {
10599 let mut start = fold.range.start.to_point(&buffer);
10600 let mut end = fold.range.end.to_point(&buffer);
10601 start.row -= row_delta;
10602 end.row -= row_delta;
10603 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10604 }
10605 }
10606 }
10607
10608 // If we didn't move line(s), preserve the existing selections
10609 new_selections.append(&mut contiguous_row_selections);
10610 }
10611
10612 self.transact(window, cx, |this, window, cx| {
10613 this.unfold_ranges(&unfold_ranges, true, true, cx);
10614 this.buffer.update(cx, |buffer, cx| {
10615 for (range, text) in edits {
10616 buffer.edit([(range, text)], None, cx);
10617 }
10618 });
10619 this.fold_creases(refold_creases, true, window, cx);
10620 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10621 s.select(new_selections);
10622 })
10623 });
10624 }
10625
10626 pub fn move_line_down(
10627 &mut self,
10628 _: &MoveLineDown,
10629 window: &mut Window,
10630 cx: &mut Context<Self>,
10631 ) {
10632 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10633
10634 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10635 let buffer = self.buffer.read(cx).snapshot(cx);
10636
10637 let mut edits = Vec::new();
10638 let mut unfold_ranges = Vec::new();
10639 let mut refold_creases = Vec::new();
10640
10641 let selections = self.selections.all::<Point>(cx);
10642 let mut selections = selections.iter().peekable();
10643 let mut contiguous_row_selections = Vec::new();
10644 let mut new_selections = Vec::new();
10645
10646 while let Some(selection) = selections.next() {
10647 // Find all the selections that span a contiguous row range
10648 let (start_row, end_row) = consume_contiguous_rows(
10649 &mut contiguous_row_selections,
10650 selection,
10651 &display_map,
10652 &mut selections,
10653 );
10654
10655 // Move the text spanned by the row range to be after the last line of the row range
10656 if end_row.0 <= buffer.max_point().row {
10657 let range_to_move =
10658 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10659 let insertion_point = display_map
10660 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10661 .0;
10662
10663 // Don't move lines across excerpt boundaries
10664 if buffer
10665 .excerpt_containing(range_to_move.start..insertion_point)
10666 .is_some()
10667 {
10668 let mut text = String::from("\n");
10669 text.extend(buffer.text_for_range(range_to_move.clone()));
10670 text.pop(); // Drop trailing newline
10671 edits.push((
10672 buffer.anchor_after(range_to_move.start)
10673 ..buffer.anchor_before(range_to_move.end),
10674 String::new(),
10675 ));
10676 let insertion_anchor = buffer.anchor_after(insertion_point);
10677 edits.push((insertion_anchor..insertion_anchor, text));
10678
10679 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10680
10681 // Move selections down
10682 new_selections.extend(contiguous_row_selections.drain(..).map(
10683 |mut selection| {
10684 selection.start.row += row_delta;
10685 selection.end.row += row_delta;
10686 selection
10687 },
10688 ));
10689
10690 // Move folds down
10691 unfold_ranges.push(range_to_move.clone());
10692 for fold in display_map.folds_in_range(
10693 buffer.anchor_before(range_to_move.start)
10694 ..buffer.anchor_after(range_to_move.end),
10695 ) {
10696 let mut start = fold.range.start.to_point(&buffer);
10697 let mut end = fold.range.end.to_point(&buffer);
10698 start.row += row_delta;
10699 end.row += row_delta;
10700 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10701 }
10702 }
10703 }
10704
10705 // If we didn't move line(s), preserve the existing selections
10706 new_selections.append(&mut contiguous_row_selections);
10707 }
10708
10709 self.transact(window, cx, |this, window, cx| {
10710 this.unfold_ranges(&unfold_ranges, true, true, cx);
10711 this.buffer.update(cx, |buffer, cx| {
10712 for (range, text) in edits {
10713 buffer.edit([(range, text)], None, cx);
10714 }
10715 });
10716 this.fold_creases(refold_creases, true, window, cx);
10717 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10718 s.select(new_selections)
10719 });
10720 });
10721 }
10722
10723 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10724 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10725 let text_layout_details = &self.text_layout_details(window);
10726 self.transact(window, cx, |this, window, cx| {
10727 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10728 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10729 s.move_with(|display_map, selection| {
10730 if !selection.is_empty() {
10731 return;
10732 }
10733
10734 let mut head = selection.head();
10735 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10736 if head.column() == display_map.line_len(head.row()) {
10737 transpose_offset = display_map
10738 .buffer_snapshot
10739 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10740 }
10741
10742 if transpose_offset == 0 {
10743 return;
10744 }
10745
10746 *head.column_mut() += 1;
10747 head = display_map.clip_point(head, Bias::Right);
10748 let goal = SelectionGoal::HorizontalPosition(
10749 display_map
10750 .x_for_display_point(head, text_layout_details)
10751 .into(),
10752 );
10753 selection.collapse_to(head, goal);
10754
10755 let transpose_start = display_map
10756 .buffer_snapshot
10757 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10758 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10759 let transpose_end = display_map
10760 .buffer_snapshot
10761 .clip_offset(transpose_offset + 1, Bias::Right);
10762 if let Some(ch) =
10763 display_map.buffer_snapshot.chars_at(transpose_start).next()
10764 {
10765 edits.push((transpose_start..transpose_offset, String::new()));
10766 edits.push((transpose_end..transpose_end, ch.to_string()));
10767 }
10768 }
10769 });
10770 edits
10771 });
10772 this.buffer
10773 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10774 let selections = this.selections.all::<usize>(cx);
10775 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10776 s.select(selections);
10777 });
10778 });
10779 }
10780
10781 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10782 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10783 self.rewrap_impl(RewrapOptions::default(), cx)
10784 }
10785
10786 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10787 let buffer = self.buffer.read(cx).snapshot(cx);
10788 let selections = self.selections.all::<Point>(cx);
10789 let mut selections = selections.iter().peekable();
10790
10791 let mut edits = Vec::new();
10792 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10793
10794 while let Some(selection) = selections.next() {
10795 let mut start_row = selection.start.row;
10796 let mut end_row = selection.end.row;
10797
10798 // Skip selections that overlap with a range that has already been rewrapped.
10799 let selection_range = start_row..end_row;
10800 if rewrapped_row_ranges
10801 .iter()
10802 .any(|range| range.overlaps(&selection_range))
10803 {
10804 continue;
10805 }
10806
10807 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10808
10809 // Since not all lines in the selection may be at the same indent
10810 // level, choose the indent size that is the most common between all
10811 // of the lines.
10812 //
10813 // If there is a tie, we use the deepest indent.
10814 let (indent_size, indent_end) = {
10815 let mut indent_size_occurrences = HashMap::default();
10816 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10817
10818 for row in start_row..=end_row {
10819 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10820 rows_by_indent_size.entry(indent).or_default().push(row);
10821 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10822 }
10823
10824 let indent_size = indent_size_occurrences
10825 .into_iter()
10826 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10827 .map(|(indent, _)| indent)
10828 .unwrap_or_default();
10829 let row = rows_by_indent_size[&indent_size][0];
10830 let indent_end = Point::new(row, indent_size.len);
10831
10832 (indent_size, indent_end)
10833 };
10834
10835 let mut line_prefix = indent_size.chars().collect::<String>();
10836
10837 let mut inside_comment = false;
10838 if let Some(comment_prefix) =
10839 buffer
10840 .language_scope_at(selection.head())
10841 .and_then(|language| {
10842 language
10843 .line_comment_prefixes()
10844 .iter()
10845 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10846 .cloned()
10847 })
10848 {
10849 line_prefix.push_str(&comment_prefix);
10850 inside_comment = true;
10851 }
10852
10853 let language_settings = buffer.language_settings_at(selection.head(), cx);
10854 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10855 RewrapBehavior::InComments => inside_comment,
10856 RewrapBehavior::InSelections => !selection.is_empty(),
10857 RewrapBehavior::Anywhere => true,
10858 };
10859
10860 let should_rewrap = options.override_language_settings
10861 || allow_rewrap_based_on_language
10862 || self.hard_wrap.is_some();
10863 if !should_rewrap {
10864 continue;
10865 }
10866
10867 if selection.is_empty() {
10868 'expand_upwards: while start_row > 0 {
10869 let prev_row = start_row - 1;
10870 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10871 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10872 {
10873 start_row = prev_row;
10874 } else {
10875 break 'expand_upwards;
10876 }
10877 }
10878
10879 'expand_downwards: while end_row < buffer.max_point().row {
10880 let next_row = end_row + 1;
10881 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10882 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10883 {
10884 end_row = next_row;
10885 } else {
10886 break 'expand_downwards;
10887 }
10888 }
10889 }
10890
10891 let start = Point::new(start_row, 0);
10892 let start_offset = start.to_offset(&buffer);
10893 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10894 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10895 let Some(lines_without_prefixes) = selection_text
10896 .lines()
10897 .map(|line| {
10898 line.strip_prefix(&line_prefix)
10899 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10900 .with_context(|| {
10901 format!("line did not start with prefix {line_prefix:?}: {line:?}")
10902 })
10903 })
10904 .collect::<Result<Vec<_>, _>>()
10905 .log_err()
10906 else {
10907 continue;
10908 };
10909
10910 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10911 buffer
10912 .language_settings_at(Point::new(start_row, 0), cx)
10913 .preferred_line_length as usize
10914 });
10915 let wrapped_text = wrap_with_prefix(
10916 line_prefix,
10917 lines_without_prefixes.join("\n"),
10918 wrap_column,
10919 tab_size,
10920 options.preserve_existing_whitespace,
10921 );
10922
10923 // TODO: should always use char-based diff while still supporting cursor behavior that
10924 // matches vim.
10925 let mut diff_options = DiffOptions::default();
10926 if options.override_language_settings {
10927 diff_options.max_word_diff_len = 0;
10928 diff_options.max_word_diff_line_count = 0;
10929 } else {
10930 diff_options.max_word_diff_len = usize::MAX;
10931 diff_options.max_word_diff_line_count = usize::MAX;
10932 }
10933
10934 for (old_range, new_text) in
10935 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10936 {
10937 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10938 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10939 edits.push((edit_start..edit_end, new_text));
10940 }
10941
10942 rewrapped_row_ranges.push(start_row..=end_row);
10943 }
10944
10945 self.buffer
10946 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10947 }
10948
10949 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10950 let mut text = String::new();
10951 let buffer = self.buffer.read(cx).snapshot(cx);
10952 let mut selections = self.selections.all::<Point>(cx);
10953 let mut clipboard_selections = Vec::with_capacity(selections.len());
10954 {
10955 let max_point = buffer.max_point();
10956 let mut is_first = true;
10957 for selection in &mut selections {
10958 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10959 if is_entire_line {
10960 selection.start = Point::new(selection.start.row, 0);
10961 if !selection.is_empty() && selection.end.column == 0 {
10962 selection.end = cmp::min(max_point, selection.end);
10963 } else {
10964 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10965 }
10966 selection.goal = SelectionGoal::None;
10967 }
10968 if is_first {
10969 is_first = false;
10970 } else {
10971 text += "\n";
10972 }
10973 let mut len = 0;
10974 for chunk in buffer.text_for_range(selection.start..selection.end) {
10975 text.push_str(chunk);
10976 len += chunk.len();
10977 }
10978 clipboard_selections.push(ClipboardSelection {
10979 len,
10980 is_entire_line,
10981 first_line_indent: buffer
10982 .indent_size_for_line(MultiBufferRow(selection.start.row))
10983 .len,
10984 });
10985 }
10986 }
10987
10988 self.transact(window, cx, |this, window, cx| {
10989 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10990 s.select(selections);
10991 });
10992 this.insert("", window, cx);
10993 });
10994 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10995 }
10996
10997 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10998 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10999 let item = self.cut_common(window, cx);
11000 cx.write_to_clipboard(item);
11001 }
11002
11003 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11004 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11005 self.change_selections(None, window, cx, |s| {
11006 s.move_with(|snapshot, sel| {
11007 if sel.is_empty() {
11008 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11009 }
11010 });
11011 });
11012 let item = self.cut_common(window, cx);
11013 cx.set_global(KillRing(item))
11014 }
11015
11016 pub fn kill_ring_yank(
11017 &mut self,
11018 _: &KillRingYank,
11019 window: &mut Window,
11020 cx: &mut Context<Self>,
11021 ) {
11022 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11023 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11024 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11025 (kill_ring.text().to_string(), kill_ring.metadata_json())
11026 } else {
11027 return;
11028 }
11029 } else {
11030 return;
11031 };
11032 self.do_paste(&text, metadata, false, window, cx);
11033 }
11034
11035 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11036 self.do_copy(true, cx);
11037 }
11038
11039 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11040 self.do_copy(false, cx);
11041 }
11042
11043 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11044 let selections = self.selections.all::<Point>(cx);
11045 let buffer = self.buffer.read(cx).read(cx);
11046 let mut text = String::new();
11047
11048 let mut clipboard_selections = Vec::with_capacity(selections.len());
11049 {
11050 let max_point = buffer.max_point();
11051 let mut is_first = true;
11052 for selection in &selections {
11053 let mut start = selection.start;
11054 let mut end = selection.end;
11055 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11056 if is_entire_line {
11057 start = Point::new(start.row, 0);
11058 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11059 }
11060
11061 let mut trimmed_selections = Vec::new();
11062 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11063 let row = MultiBufferRow(start.row);
11064 let first_indent = buffer.indent_size_for_line(row);
11065 if first_indent.len == 0 || start.column > first_indent.len {
11066 trimmed_selections.push(start..end);
11067 } else {
11068 trimmed_selections.push(
11069 Point::new(row.0, first_indent.len)
11070 ..Point::new(row.0, buffer.line_len(row)),
11071 );
11072 for row in start.row + 1..=end.row {
11073 let mut line_len = buffer.line_len(MultiBufferRow(row));
11074 if row == end.row {
11075 line_len = end.column;
11076 }
11077 if line_len == 0 {
11078 trimmed_selections
11079 .push(Point::new(row, 0)..Point::new(row, line_len));
11080 continue;
11081 }
11082 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11083 if row_indent_size.len >= first_indent.len {
11084 trimmed_selections.push(
11085 Point::new(row, first_indent.len)..Point::new(row, line_len),
11086 );
11087 } else {
11088 trimmed_selections.clear();
11089 trimmed_selections.push(start..end);
11090 break;
11091 }
11092 }
11093 }
11094 } else {
11095 trimmed_selections.push(start..end);
11096 }
11097
11098 for trimmed_range in trimmed_selections {
11099 if is_first {
11100 is_first = false;
11101 } else {
11102 text += "\n";
11103 }
11104 let mut len = 0;
11105 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11106 text.push_str(chunk);
11107 len += chunk.len();
11108 }
11109 clipboard_selections.push(ClipboardSelection {
11110 len,
11111 is_entire_line,
11112 first_line_indent: buffer
11113 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11114 .len,
11115 });
11116 }
11117 }
11118 }
11119
11120 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11121 text,
11122 clipboard_selections,
11123 ));
11124 }
11125
11126 pub fn do_paste(
11127 &mut self,
11128 text: &String,
11129 clipboard_selections: Option<Vec<ClipboardSelection>>,
11130 handle_entire_lines: bool,
11131 window: &mut Window,
11132 cx: &mut Context<Self>,
11133 ) {
11134 if self.read_only(cx) {
11135 return;
11136 }
11137
11138 let clipboard_text = Cow::Borrowed(text);
11139
11140 self.transact(window, cx, |this, window, cx| {
11141 if let Some(mut clipboard_selections) = clipboard_selections {
11142 let old_selections = this.selections.all::<usize>(cx);
11143 let all_selections_were_entire_line =
11144 clipboard_selections.iter().all(|s| s.is_entire_line);
11145 let first_selection_indent_column =
11146 clipboard_selections.first().map(|s| s.first_line_indent);
11147 if clipboard_selections.len() != old_selections.len() {
11148 clipboard_selections.drain(..);
11149 }
11150 let cursor_offset = this.selections.last::<usize>(cx).head();
11151 let mut auto_indent_on_paste = true;
11152
11153 this.buffer.update(cx, |buffer, cx| {
11154 let snapshot = buffer.read(cx);
11155 auto_indent_on_paste = snapshot
11156 .language_settings_at(cursor_offset, cx)
11157 .auto_indent_on_paste;
11158
11159 let mut start_offset = 0;
11160 let mut edits = Vec::new();
11161 let mut original_indent_columns = Vec::new();
11162 for (ix, selection) in old_selections.iter().enumerate() {
11163 let to_insert;
11164 let entire_line;
11165 let original_indent_column;
11166 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11167 let end_offset = start_offset + clipboard_selection.len;
11168 to_insert = &clipboard_text[start_offset..end_offset];
11169 entire_line = clipboard_selection.is_entire_line;
11170 start_offset = end_offset + 1;
11171 original_indent_column = Some(clipboard_selection.first_line_indent);
11172 } else {
11173 to_insert = clipboard_text.as_str();
11174 entire_line = all_selections_were_entire_line;
11175 original_indent_column = first_selection_indent_column
11176 }
11177
11178 // If the corresponding selection was empty when this slice of the
11179 // clipboard text was written, then the entire line containing the
11180 // selection was copied. If this selection is also currently empty,
11181 // then paste the line before the current line of the buffer.
11182 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11183 let column = selection.start.to_point(&snapshot).column as usize;
11184 let line_start = selection.start - column;
11185 line_start..line_start
11186 } else {
11187 selection.range()
11188 };
11189
11190 edits.push((range, to_insert));
11191 original_indent_columns.push(original_indent_column);
11192 }
11193 drop(snapshot);
11194
11195 buffer.edit(
11196 edits,
11197 if auto_indent_on_paste {
11198 Some(AutoindentMode::Block {
11199 original_indent_columns,
11200 })
11201 } else {
11202 None
11203 },
11204 cx,
11205 );
11206 });
11207
11208 let selections = this.selections.all::<usize>(cx);
11209 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11210 s.select(selections)
11211 });
11212 } else {
11213 this.insert(&clipboard_text, window, cx);
11214 }
11215 });
11216 }
11217
11218 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11219 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11220 if let Some(item) = cx.read_from_clipboard() {
11221 let entries = item.entries();
11222
11223 match entries.first() {
11224 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11225 // of all the pasted entries.
11226 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11227 .do_paste(
11228 clipboard_string.text(),
11229 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11230 true,
11231 window,
11232 cx,
11233 ),
11234 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11235 }
11236 }
11237 }
11238
11239 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11240 if self.read_only(cx) {
11241 return;
11242 }
11243
11244 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11245
11246 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11247 if let Some((selections, _)) =
11248 self.selection_history.transaction(transaction_id).cloned()
11249 {
11250 self.change_selections(None, window, cx, |s| {
11251 s.select_anchors(selections.to_vec());
11252 });
11253 } else {
11254 log::error!(
11255 "No entry in selection_history found for undo. \
11256 This may correspond to a bug where undo does not update the selection. \
11257 If this is occurring, please add details to \
11258 https://github.com/zed-industries/zed/issues/22692"
11259 );
11260 }
11261 self.request_autoscroll(Autoscroll::fit(), cx);
11262 self.unmark_text(window, cx);
11263 self.refresh_inline_completion(true, false, window, cx);
11264 cx.emit(EditorEvent::Edited { transaction_id });
11265 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11266 }
11267 }
11268
11269 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11270 if self.read_only(cx) {
11271 return;
11272 }
11273
11274 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11275
11276 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11277 if let Some((_, Some(selections))) =
11278 self.selection_history.transaction(transaction_id).cloned()
11279 {
11280 self.change_selections(None, window, cx, |s| {
11281 s.select_anchors(selections.to_vec());
11282 });
11283 } else {
11284 log::error!(
11285 "No entry in selection_history found for redo. \
11286 This may correspond to a bug where undo does not update the selection. \
11287 If this is occurring, please add details to \
11288 https://github.com/zed-industries/zed/issues/22692"
11289 );
11290 }
11291 self.request_autoscroll(Autoscroll::fit(), cx);
11292 self.unmark_text(window, cx);
11293 self.refresh_inline_completion(true, false, window, cx);
11294 cx.emit(EditorEvent::Edited { transaction_id });
11295 }
11296 }
11297
11298 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11299 self.buffer
11300 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11301 }
11302
11303 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11304 self.buffer
11305 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11306 }
11307
11308 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11309 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11310 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11311 s.move_with(|map, selection| {
11312 let cursor = if selection.is_empty() {
11313 movement::left(map, selection.start)
11314 } else {
11315 selection.start
11316 };
11317 selection.collapse_to(cursor, SelectionGoal::None);
11318 });
11319 })
11320 }
11321
11322 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11323 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11324 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11325 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11326 })
11327 }
11328
11329 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11330 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11331 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11332 s.move_with(|map, selection| {
11333 let cursor = if selection.is_empty() {
11334 movement::right(map, selection.end)
11335 } else {
11336 selection.end
11337 };
11338 selection.collapse_to(cursor, SelectionGoal::None)
11339 });
11340 })
11341 }
11342
11343 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11344 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11345 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11346 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11347 })
11348 }
11349
11350 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11351 if self.take_rename(true, window, cx).is_some() {
11352 return;
11353 }
11354
11355 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11356 cx.propagate();
11357 return;
11358 }
11359
11360 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11361
11362 let text_layout_details = &self.text_layout_details(window);
11363 let selection_count = self.selections.count();
11364 let first_selection = self.selections.first_anchor();
11365
11366 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11367 s.move_with(|map, selection| {
11368 if !selection.is_empty() {
11369 selection.goal = SelectionGoal::None;
11370 }
11371 let (cursor, goal) = movement::up(
11372 map,
11373 selection.start,
11374 selection.goal,
11375 false,
11376 text_layout_details,
11377 );
11378 selection.collapse_to(cursor, goal);
11379 });
11380 });
11381
11382 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11383 {
11384 cx.propagate();
11385 }
11386 }
11387
11388 pub fn move_up_by_lines(
11389 &mut self,
11390 action: &MoveUpByLines,
11391 window: &mut Window,
11392 cx: &mut Context<Self>,
11393 ) {
11394 if self.take_rename(true, window, cx).is_some() {
11395 return;
11396 }
11397
11398 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11399 cx.propagate();
11400 return;
11401 }
11402
11403 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11404
11405 let text_layout_details = &self.text_layout_details(window);
11406
11407 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11408 s.move_with(|map, selection| {
11409 if !selection.is_empty() {
11410 selection.goal = SelectionGoal::None;
11411 }
11412 let (cursor, goal) = movement::up_by_rows(
11413 map,
11414 selection.start,
11415 action.lines,
11416 selection.goal,
11417 false,
11418 text_layout_details,
11419 );
11420 selection.collapse_to(cursor, goal);
11421 });
11422 })
11423 }
11424
11425 pub fn move_down_by_lines(
11426 &mut self,
11427 action: &MoveDownByLines,
11428 window: &mut Window,
11429 cx: &mut Context<Self>,
11430 ) {
11431 if self.take_rename(true, window, cx).is_some() {
11432 return;
11433 }
11434
11435 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11436 cx.propagate();
11437 return;
11438 }
11439
11440 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11441
11442 let text_layout_details = &self.text_layout_details(window);
11443
11444 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11445 s.move_with(|map, selection| {
11446 if !selection.is_empty() {
11447 selection.goal = SelectionGoal::None;
11448 }
11449 let (cursor, goal) = movement::down_by_rows(
11450 map,
11451 selection.start,
11452 action.lines,
11453 selection.goal,
11454 false,
11455 text_layout_details,
11456 );
11457 selection.collapse_to(cursor, goal);
11458 });
11459 })
11460 }
11461
11462 pub fn select_down_by_lines(
11463 &mut self,
11464 action: &SelectDownByLines,
11465 window: &mut Window,
11466 cx: &mut Context<Self>,
11467 ) {
11468 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11469 let text_layout_details = &self.text_layout_details(window);
11470 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11471 s.move_heads_with(|map, head, goal| {
11472 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11473 })
11474 })
11475 }
11476
11477 pub fn select_up_by_lines(
11478 &mut self,
11479 action: &SelectUpByLines,
11480 window: &mut Window,
11481 cx: &mut Context<Self>,
11482 ) {
11483 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11484 let text_layout_details = &self.text_layout_details(window);
11485 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11486 s.move_heads_with(|map, head, goal| {
11487 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11488 })
11489 })
11490 }
11491
11492 pub fn select_page_up(
11493 &mut self,
11494 _: &SelectPageUp,
11495 window: &mut Window,
11496 cx: &mut Context<Self>,
11497 ) {
11498 let Some(row_count) = self.visible_row_count() else {
11499 return;
11500 };
11501
11502 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11503
11504 let text_layout_details = &self.text_layout_details(window);
11505
11506 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11507 s.move_heads_with(|map, head, goal| {
11508 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11509 })
11510 })
11511 }
11512
11513 pub fn move_page_up(
11514 &mut self,
11515 action: &MovePageUp,
11516 window: &mut Window,
11517 cx: &mut Context<Self>,
11518 ) {
11519 if self.take_rename(true, window, cx).is_some() {
11520 return;
11521 }
11522
11523 if self
11524 .context_menu
11525 .borrow_mut()
11526 .as_mut()
11527 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11528 .unwrap_or(false)
11529 {
11530 return;
11531 }
11532
11533 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11534 cx.propagate();
11535 return;
11536 }
11537
11538 let Some(row_count) = self.visible_row_count() else {
11539 return;
11540 };
11541
11542 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11543
11544 let autoscroll = if action.center_cursor {
11545 Autoscroll::center()
11546 } else {
11547 Autoscroll::fit()
11548 };
11549
11550 let text_layout_details = &self.text_layout_details(window);
11551
11552 self.change_selections(Some(autoscroll), window, cx, |s| {
11553 s.move_with(|map, selection| {
11554 if !selection.is_empty() {
11555 selection.goal = SelectionGoal::None;
11556 }
11557 let (cursor, goal) = movement::up_by_rows(
11558 map,
11559 selection.end,
11560 row_count,
11561 selection.goal,
11562 false,
11563 text_layout_details,
11564 );
11565 selection.collapse_to(cursor, goal);
11566 });
11567 });
11568 }
11569
11570 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11571 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11572 let text_layout_details = &self.text_layout_details(window);
11573 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11574 s.move_heads_with(|map, head, goal| {
11575 movement::up(map, head, goal, false, text_layout_details)
11576 })
11577 })
11578 }
11579
11580 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11581 self.take_rename(true, window, cx);
11582
11583 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11584 cx.propagate();
11585 return;
11586 }
11587
11588 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11589
11590 let text_layout_details = &self.text_layout_details(window);
11591 let selection_count = self.selections.count();
11592 let first_selection = self.selections.first_anchor();
11593
11594 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11595 s.move_with(|map, selection| {
11596 if !selection.is_empty() {
11597 selection.goal = SelectionGoal::None;
11598 }
11599 let (cursor, goal) = movement::down(
11600 map,
11601 selection.end,
11602 selection.goal,
11603 false,
11604 text_layout_details,
11605 );
11606 selection.collapse_to(cursor, goal);
11607 });
11608 });
11609
11610 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11611 {
11612 cx.propagate();
11613 }
11614 }
11615
11616 pub fn select_page_down(
11617 &mut self,
11618 _: &SelectPageDown,
11619 window: &mut Window,
11620 cx: &mut Context<Self>,
11621 ) {
11622 let Some(row_count) = self.visible_row_count() else {
11623 return;
11624 };
11625
11626 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11627
11628 let text_layout_details = &self.text_layout_details(window);
11629
11630 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11631 s.move_heads_with(|map, head, goal| {
11632 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11633 })
11634 })
11635 }
11636
11637 pub fn move_page_down(
11638 &mut self,
11639 action: &MovePageDown,
11640 window: &mut Window,
11641 cx: &mut Context<Self>,
11642 ) {
11643 if self.take_rename(true, window, cx).is_some() {
11644 return;
11645 }
11646
11647 if self
11648 .context_menu
11649 .borrow_mut()
11650 .as_mut()
11651 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
11652 .unwrap_or(false)
11653 {
11654 return;
11655 }
11656
11657 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11658 cx.propagate();
11659 return;
11660 }
11661
11662 let Some(row_count) = self.visible_row_count() else {
11663 return;
11664 };
11665
11666 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11667
11668 let autoscroll = if action.center_cursor {
11669 Autoscroll::center()
11670 } else {
11671 Autoscroll::fit()
11672 };
11673
11674 let text_layout_details = &self.text_layout_details(window);
11675 self.change_selections(Some(autoscroll), window, cx, |s| {
11676 s.move_with(|map, selection| {
11677 if !selection.is_empty() {
11678 selection.goal = SelectionGoal::None;
11679 }
11680 let (cursor, goal) = movement::down_by_rows(
11681 map,
11682 selection.end,
11683 row_count,
11684 selection.goal,
11685 false,
11686 text_layout_details,
11687 );
11688 selection.collapse_to(cursor, goal);
11689 });
11690 });
11691 }
11692
11693 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11694 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11695 let text_layout_details = &self.text_layout_details(window);
11696 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11697 s.move_heads_with(|map, head, goal| {
11698 movement::down(map, head, goal, false, text_layout_details)
11699 })
11700 });
11701 }
11702
11703 pub fn context_menu_first(
11704 &mut self,
11705 _: &ContextMenuFirst,
11706 window: &mut Window,
11707 cx: &mut Context<Self>,
11708 ) {
11709 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11710 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
11711 }
11712 }
11713
11714 pub fn context_menu_prev(
11715 &mut self,
11716 _: &ContextMenuPrevious,
11717 window: &mut Window,
11718 cx: &mut Context<Self>,
11719 ) {
11720 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11721 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
11722 }
11723 }
11724
11725 pub fn context_menu_next(
11726 &mut self,
11727 _: &ContextMenuNext,
11728 window: &mut Window,
11729 cx: &mut Context<Self>,
11730 ) {
11731 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11732 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
11733 }
11734 }
11735
11736 pub fn context_menu_last(
11737 &mut self,
11738 _: &ContextMenuLast,
11739 window: &mut Window,
11740 cx: &mut Context<Self>,
11741 ) {
11742 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11743 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
11744 }
11745 }
11746
11747 pub fn move_to_previous_word_start(
11748 &mut self,
11749 _: &MoveToPreviousWordStart,
11750 window: &mut Window,
11751 cx: &mut Context<Self>,
11752 ) {
11753 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11754 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11755 s.move_cursors_with(|map, head, _| {
11756 (
11757 movement::previous_word_start(map, head),
11758 SelectionGoal::None,
11759 )
11760 });
11761 })
11762 }
11763
11764 pub fn move_to_previous_subword_start(
11765 &mut self,
11766 _: &MoveToPreviousSubwordStart,
11767 window: &mut Window,
11768 cx: &mut Context<Self>,
11769 ) {
11770 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11771 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11772 s.move_cursors_with(|map, head, _| {
11773 (
11774 movement::previous_subword_start(map, head),
11775 SelectionGoal::None,
11776 )
11777 });
11778 })
11779 }
11780
11781 pub fn select_to_previous_word_start(
11782 &mut self,
11783 _: &SelectToPreviousWordStart,
11784 window: &mut Window,
11785 cx: &mut Context<Self>,
11786 ) {
11787 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11788 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11789 s.move_heads_with(|map, head, _| {
11790 (
11791 movement::previous_word_start(map, head),
11792 SelectionGoal::None,
11793 )
11794 });
11795 })
11796 }
11797
11798 pub fn select_to_previous_subword_start(
11799 &mut self,
11800 _: &SelectToPreviousSubwordStart,
11801 window: &mut Window,
11802 cx: &mut Context<Self>,
11803 ) {
11804 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11805 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11806 s.move_heads_with(|map, head, _| {
11807 (
11808 movement::previous_subword_start(map, head),
11809 SelectionGoal::None,
11810 )
11811 });
11812 })
11813 }
11814
11815 pub fn delete_to_previous_word_start(
11816 &mut self,
11817 action: &DeleteToPreviousWordStart,
11818 window: &mut Window,
11819 cx: &mut Context<Self>,
11820 ) {
11821 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11822 self.transact(window, cx, |this, window, cx| {
11823 this.select_autoclose_pair(window, cx);
11824 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11825 s.move_with(|map, selection| {
11826 if selection.is_empty() {
11827 let cursor = if action.ignore_newlines {
11828 movement::previous_word_start(map, selection.head())
11829 } else {
11830 movement::previous_word_start_or_newline(map, selection.head())
11831 };
11832 selection.set_head(cursor, SelectionGoal::None);
11833 }
11834 });
11835 });
11836 this.insert("", window, cx);
11837 });
11838 }
11839
11840 pub fn delete_to_previous_subword_start(
11841 &mut self,
11842 _: &DeleteToPreviousSubwordStart,
11843 window: &mut Window,
11844 cx: &mut Context<Self>,
11845 ) {
11846 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11847 self.transact(window, cx, |this, window, cx| {
11848 this.select_autoclose_pair(window, cx);
11849 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11850 s.move_with(|map, selection| {
11851 if selection.is_empty() {
11852 let cursor = movement::previous_subword_start(map, selection.head());
11853 selection.set_head(cursor, SelectionGoal::None);
11854 }
11855 });
11856 });
11857 this.insert("", window, cx);
11858 });
11859 }
11860
11861 pub fn move_to_next_word_end(
11862 &mut self,
11863 _: &MoveToNextWordEnd,
11864 window: &mut Window,
11865 cx: &mut Context<Self>,
11866 ) {
11867 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11868 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11869 s.move_cursors_with(|map, head, _| {
11870 (movement::next_word_end(map, head), SelectionGoal::None)
11871 });
11872 })
11873 }
11874
11875 pub fn move_to_next_subword_end(
11876 &mut self,
11877 _: &MoveToNextSubwordEnd,
11878 window: &mut Window,
11879 cx: &mut Context<Self>,
11880 ) {
11881 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11882 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11883 s.move_cursors_with(|map, head, _| {
11884 (movement::next_subword_end(map, head), SelectionGoal::None)
11885 });
11886 })
11887 }
11888
11889 pub fn select_to_next_word_end(
11890 &mut self,
11891 _: &SelectToNextWordEnd,
11892 window: &mut Window,
11893 cx: &mut Context<Self>,
11894 ) {
11895 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11896 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11897 s.move_heads_with(|map, head, _| {
11898 (movement::next_word_end(map, head), SelectionGoal::None)
11899 });
11900 })
11901 }
11902
11903 pub fn select_to_next_subword_end(
11904 &mut self,
11905 _: &SelectToNextSubwordEnd,
11906 window: &mut Window,
11907 cx: &mut Context<Self>,
11908 ) {
11909 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11910 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11911 s.move_heads_with(|map, head, _| {
11912 (movement::next_subword_end(map, head), SelectionGoal::None)
11913 });
11914 })
11915 }
11916
11917 pub fn delete_to_next_word_end(
11918 &mut self,
11919 action: &DeleteToNextWordEnd,
11920 window: &mut Window,
11921 cx: &mut Context<Self>,
11922 ) {
11923 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11924 self.transact(window, cx, |this, window, cx| {
11925 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11926 s.move_with(|map, selection| {
11927 if selection.is_empty() {
11928 let cursor = if action.ignore_newlines {
11929 movement::next_word_end(map, selection.head())
11930 } else {
11931 movement::next_word_end_or_newline(map, selection.head())
11932 };
11933 selection.set_head(cursor, SelectionGoal::None);
11934 }
11935 });
11936 });
11937 this.insert("", window, cx);
11938 });
11939 }
11940
11941 pub fn delete_to_next_subword_end(
11942 &mut self,
11943 _: &DeleteToNextSubwordEnd,
11944 window: &mut Window,
11945 cx: &mut Context<Self>,
11946 ) {
11947 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11948 self.transact(window, cx, |this, window, cx| {
11949 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11950 s.move_with(|map, selection| {
11951 if selection.is_empty() {
11952 let cursor = movement::next_subword_end(map, selection.head());
11953 selection.set_head(cursor, SelectionGoal::None);
11954 }
11955 });
11956 });
11957 this.insert("", window, cx);
11958 });
11959 }
11960
11961 pub fn move_to_beginning_of_line(
11962 &mut self,
11963 action: &MoveToBeginningOfLine,
11964 window: &mut Window,
11965 cx: &mut Context<Self>,
11966 ) {
11967 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11968 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11969 s.move_cursors_with(|map, head, _| {
11970 (
11971 movement::indented_line_beginning(
11972 map,
11973 head,
11974 action.stop_at_soft_wraps,
11975 action.stop_at_indent,
11976 ),
11977 SelectionGoal::None,
11978 )
11979 });
11980 })
11981 }
11982
11983 pub fn select_to_beginning_of_line(
11984 &mut self,
11985 action: &SelectToBeginningOfLine,
11986 window: &mut Window,
11987 cx: &mut Context<Self>,
11988 ) {
11989 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11990 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11991 s.move_heads_with(|map, head, _| {
11992 (
11993 movement::indented_line_beginning(
11994 map,
11995 head,
11996 action.stop_at_soft_wraps,
11997 action.stop_at_indent,
11998 ),
11999 SelectionGoal::None,
12000 )
12001 });
12002 });
12003 }
12004
12005 pub fn delete_to_beginning_of_line(
12006 &mut self,
12007 action: &DeleteToBeginningOfLine,
12008 window: &mut Window,
12009 cx: &mut Context<Self>,
12010 ) {
12011 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12012 self.transact(window, cx, |this, window, cx| {
12013 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12014 s.move_with(|_, selection| {
12015 selection.reversed = true;
12016 });
12017 });
12018
12019 this.select_to_beginning_of_line(
12020 &SelectToBeginningOfLine {
12021 stop_at_soft_wraps: false,
12022 stop_at_indent: action.stop_at_indent,
12023 },
12024 window,
12025 cx,
12026 );
12027 this.backspace(&Backspace, window, cx);
12028 });
12029 }
12030
12031 pub fn move_to_end_of_line(
12032 &mut self,
12033 action: &MoveToEndOfLine,
12034 window: &mut Window,
12035 cx: &mut Context<Self>,
12036 ) {
12037 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12038 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12039 s.move_cursors_with(|map, head, _| {
12040 (
12041 movement::line_end(map, head, action.stop_at_soft_wraps),
12042 SelectionGoal::None,
12043 )
12044 });
12045 })
12046 }
12047
12048 pub fn select_to_end_of_line(
12049 &mut self,
12050 action: &SelectToEndOfLine,
12051 window: &mut Window,
12052 cx: &mut Context<Self>,
12053 ) {
12054 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12055 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12056 s.move_heads_with(|map, head, _| {
12057 (
12058 movement::line_end(map, head, action.stop_at_soft_wraps),
12059 SelectionGoal::None,
12060 )
12061 });
12062 })
12063 }
12064
12065 pub fn delete_to_end_of_line(
12066 &mut self,
12067 _: &DeleteToEndOfLine,
12068 window: &mut Window,
12069 cx: &mut Context<Self>,
12070 ) {
12071 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12072 self.transact(window, cx, |this, window, cx| {
12073 this.select_to_end_of_line(
12074 &SelectToEndOfLine {
12075 stop_at_soft_wraps: false,
12076 },
12077 window,
12078 cx,
12079 );
12080 this.delete(&Delete, window, cx);
12081 });
12082 }
12083
12084 pub fn cut_to_end_of_line(
12085 &mut self,
12086 _: &CutToEndOfLine,
12087 window: &mut Window,
12088 cx: &mut Context<Self>,
12089 ) {
12090 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12091 self.transact(window, cx, |this, window, cx| {
12092 this.select_to_end_of_line(
12093 &SelectToEndOfLine {
12094 stop_at_soft_wraps: false,
12095 },
12096 window,
12097 cx,
12098 );
12099 this.cut(&Cut, window, cx);
12100 });
12101 }
12102
12103 pub fn move_to_start_of_paragraph(
12104 &mut self,
12105 _: &MoveToStartOfParagraph,
12106 window: &mut Window,
12107 cx: &mut Context<Self>,
12108 ) {
12109 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12110 cx.propagate();
12111 return;
12112 }
12113 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12114 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12115 s.move_with(|map, selection| {
12116 selection.collapse_to(
12117 movement::start_of_paragraph(map, selection.head(), 1),
12118 SelectionGoal::None,
12119 )
12120 });
12121 })
12122 }
12123
12124 pub fn move_to_end_of_paragraph(
12125 &mut self,
12126 _: &MoveToEndOfParagraph,
12127 window: &mut Window,
12128 cx: &mut Context<Self>,
12129 ) {
12130 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12131 cx.propagate();
12132 return;
12133 }
12134 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12135 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12136 s.move_with(|map, selection| {
12137 selection.collapse_to(
12138 movement::end_of_paragraph(map, selection.head(), 1),
12139 SelectionGoal::None,
12140 )
12141 });
12142 })
12143 }
12144
12145 pub fn select_to_start_of_paragraph(
12146 &mut self,
12147 _: &SelectToStartOfParagraph,
12148 window: &mut Window,
12149 cx: &mut Context<Self>,
12150 ) {
12151 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12152 cx.propagate();
12153 return;
12154 }
12155 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12156 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12157 s.move_heads_with(|map, head, _| {
12158 (
12159 movement::start_of_paragraph(map, head, 1),
12160 SelectionGoal::None,
12161 )
12162 });
12163 })
12164 }
12165
12166 pub fn select_to_end_of_paragraph(
12167 &mut self,
12168 _: &SelectToEndOfParagraph,
12169 window: &mut Window,
12170 cx: &mut Context<Self>,
12171 ) {
12172 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12173 cx.propagate();
12174 return;
12175 }
12176 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12177 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12178 s.move_heads_with(|map, head, _| {
12179 (
12180 movement::end_of_paragraph(map, head, 1),
12181 SelectionGoal::None,
12182 )
12183 });
12184 })
12185 }
12186
12187 pub fn move_to_start_of_excerpt(
12188 &mut self,
12189 _: &MoveToStartOfExcerpt,
12190 window: &mut Window,
12191 cx: &mut Context<Self>,
12192 ) {
12193 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12194 cx.propagate();
12195 return;
12196 }
12197 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12198 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12199 s.move_with(|map, selection| {
12200 selection.collapse_to(
12201 movement::start_of_excerpt(
12202 map,
12203 selection.head(),
12204 workspace::searchable::Direction::Prev,
12205 ),
12206 SelectionGoal::None,
12207 )
12208 });
12209 })
12210 }
12211
12212 pub fn move_to_start_of_next_excerpt(
12213 &mut self,
12214 _: &MoveToStartOfNextExcerpt,
12215 window: &mut Window,
12216 cx: &mut Context<Self>,
12217 ) {
12218 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12219 cx.propagate();
12220 return;
12221 }
12222
12223 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12224 s.move_with(|map, selection| {
12225 selection.collapse_to(
12226 movement::start_of_excerpt(
12227 map,
12228 selection.head(),
12229 workspace::searchable::Direction::Next,
12230 ),
12231 SelectionGoal::None,
12232 )
12233 });
12234 })
12235 }
12236
12237 pub fn move_to_end_of_excerpt(
12238 &mut self,
12239 _: &MoveToEndOfExcerpt,
12240 window: &mut Window,
12241 cx: &mut Context<Self>,
12242 ) {
12243 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12244 cx.propagate();
12245 return;
12246 }
12247 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12248 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12249 s.move_with(|map, selection| {
12250 selection.collapse_to(
12251 movement::end_of_excerpt(
12252 map,
12253 selection.head(),
12254 workspace::searchable::Direction::Next,
12255 ),
12256 SelectionGoal::None,
12257 )
12258 });
12259 })
12260 }
12261
12262 pub fn move_to_end_of_previous_excerpt(
12263 &mut self,
12264 _: &MoveToEndOfPreviousExcerpt,
12265 window: &mut Window,
12266 cx: &mut Context<Self>,
12267 ) {
12268 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12269 cx.propagate();
12270 return;
12271 }
12272 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12273 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12274 s.move_with(|map, selection| {
12275 selection.collapse_to(
12276 movement::end_of_excerpt(
12277 map,
12278 selection.head(),
12279 workspace::searchable::Direction::Prev,
12280 ),
12281 SelectionGoal::None,
12282 )
12283 });
12284 })
12285 }
12286
12287 pub fn select_to_start_of_excerpt(
12288 &mut self,
12289 _: &SelectToStartOfExcerpt,
12290 window: &mut Window,
12291 cx: &mut Context<Self>,
12292 ) {
12293 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12294 cx.propagate();
12295 return;
12296 }
12297 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12298 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12299 s.move_heads_with(|map, head, _| {
12300 (
12301 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12302 SelectionGoal::None,
12303 )
12304 });
12305 })
12306 }
12307
12308 pub fn select_to_start_of_next_excerpt(
12309 &mut self,
12310 _: &SelectToStartOfNextExcerpt,
12311 window: &mut Window,
12312 cx: &mut Context<Self>,
12313 ) {
12314 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12315 cx.propagate();
12316 return;
12317 }
12318 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12319 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12320 s.move_heads_with(|map, head, _| {
12321 (
12322 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12323 SelectionGoal::None,
12324 )
12325 });
12326 })
12327 }
12328
12329 pub fn select_to_end_of_excerpt(
12330 &mut self,
12331 _: &SelectToEndOfExcerpt,
12332 window: &mut Window,
12333 cx: &mut Context<Self>,
12334 ) {
12335 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12336 cx.propagate();
12337 return;
12338 }
12339 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12340 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12341 s.move_heads_with(|map, head, _| {
12342 (
12343 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12344 SelectionGoal::None,
12345 )
12346 });
12347 })
12348 }
12349
12350 pub fn select_to_end_of_previous_excerpt(
12351 &mut self,
12352 _: &SelectToEndOfPreviousExcerpt,
12353 window: &mut Window,
12354 cx: &mut Context<Self>,
12355 ) {
12356 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12357 cx.propagate();
12358 return;
12359 }
12360 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12361 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12362 s.move_heads_with(|map, head, _| {
12363 (
12364 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12365 SelectionGoal::None,
12366 )
12367 });
12368 })
12369 }
12370
12371 pub fn move_to_beginning(
12372 &mut self,
12373 _: &MoveToBeginning,
12374 window: &mut Window,
12375 cx: &mut Context<Self>,
12376 ) {
12377 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12378 cx.propagate();
12379 return;
12380 }
12381 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12382 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12383 s.select_ranges(vec![0..0]);
12384 });
12385 }
12386
12387 pub fn select_to_beginning(
12388 &mut self,
12389 _: &SelectToBeginning,
12390 window: &mut Window,
12391 cx: &mut Context<Self>,
12392 ) {
12393 let mut selection = self.selections.last::<Point>(cx);
12394 selection.set_head(Point::zero(), SelectionGoal::None);
12395 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12396 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12397 s.select(vec![selection]);
12398 });
12399 }
12400
12401 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12402 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12403 cx.propagate();
12404 return;
12405 }
12406 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12407 let cursor = self.buffer.read(cx).read(cx).len();
12408 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12409 s.select_ranges(vec![cursor..cursor])
12410 });
12411 }
12412
12413 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12414 self.nav_history = nav_history;
12415 }
12416
12417 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12418 self.nav_history.as_ref()
12419 }
12420
12421 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12422 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12423 }
12424
12425 fn push_to_nav_history(
12426 &mut self,
12427 cursor_anchor: Anchor,
12428 new_position: Option<Point>,
12429 is_deactivate: bool,
12430 cx: &mut Context<Self>,
12431 ) {
12432 if let Some(nav_history) = self.nav_history.as_mut() {
12433 let buffer = self.buffer.read(cx).read(cx);
12434 let cursor_position = cursor_anchor.to_point(&buffer);
12435 let scroll_state = self.scroll_manager.anchor();
12436 let scroll_top_row = scroll_state.top_row(&buffer);
12437 drop(buffer);
12438
12439 if let Some(new_position) = new_position {
12440 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12441 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12442 return;
12443 }
12444 }
12445
12446 nav_history.push(
12447 Some(NavigationData {
12448 cursor_anchor,
12449 cursor_position,
12450 scroll_anchor: scroll_state,
12451 scroll_top_row,
12452 }),
12453 cx,
12454 );
12455 cx.emit(EditorEvent::PushedToNavHistory {
12456 anchor: cursor_anchor,
12457 is_deactivate,
12458 })
12459 }
12460 }
12461
12462 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12463 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12464 let buffer = self.buffer.read(cx).snapshot(cx);
12465 let mut selection = self.selections.first::<usize>(cx);
12466 selection.set_head(buffer.len(), SelectionGoal::None);
12467 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12468 s.select(vec![selection]);
12469 });
12470 }
12471
12472 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12473 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12474 let end = self.buffer.read(cx).read(cx).len();
12475 self.change_selections(None, window, cx, |s| {
12476 s.select_ranges(vec![0..end]);
12477 });
12478 }
12479
12480 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12481 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12482 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12483 let mut selections = self.selections.all::<Point>(cx);
12484 let max_point = display_map.buffer_snapshot.max_point();
12485 for selection in &mut selections {
12486 let rows = selection.spanned_rows(true, &display_map);
12487 selection.start = Point::new(rows.start.0, 0);
12488 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12489 selection.reversed = false;
12490 }
12491 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12492 s.select(selections);
12493 });
12494 }
12495
12496 pub fn split_selection_into_lines(
12497 &mut self,
12498 _: &SplitSelectionIntoLines,
12499 window: &mut Window,
12500 cx: &mut Context<Self>,
12501 ) {
12502 let selections = self
12503 .selections
12504 .all::<Point>(cx)
12505 .into_iter()
12506 .map(|selection| selection.start..selection.end)
12507 .collect::<Vec<_>>();
12508 self.unfold_ranges(&selections, true, true, cx);
12509
12510 let mut new_selection_ranges = Vec::new();
12511 {
12512 let buffer = self.buffer.read(cx).read(cx);
12513 for selection in selections {
12514 for row in selection.start.row..selection.end.row {
12515 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12516 new_selection_ranges.push(cursor..cursor);
12517 }
12518
12519 let is_multiline_selection = selection.start.row != selection.end.row;
12520 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12521 // so this action feels more ergonomic when paired with other selection operations
12522 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12523 if !should_skip_last {
12524 new_selection_ranges.push(selection.end..selection.end);
12525 }
12526 }
12527 }
12528 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12529 s.select_ranges(new_selection_ranges);
12530 });
12531 }
12532
12533 pub fn add_selection_above(
12534 &mut self,
12535 _: &AddSelectionAbove,
12536 window: &mut Window,
12537 cx: &mut Context<Self>,
12538 ) {
12539 self.add_selection(true, window, cx);
12540 }
12541
12542 pub fn add_selection_below(
12543 &mut self,
12544 _: &AddSelectionBelow,
12545 window: &mut Window,
12546 cx: &mut Context<Self>,
12547 ) {
12548 self.add_selection(false, window, cx);
12549 }
12550
12551 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12552 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12553
12554 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12555 let mut selections = self.selections.all::<Point>(cx);
12556 let text_layout_details = self.text_layout_details(window);
12557 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12558 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12559 let range = oldest_selection.display_range(&display_map).sorted();
12560
12561 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12562 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12563 let positions = start_x.min(end_x)..start_x.max(end_x);
12564
12565 selections.clear();
12566 let mut stack = Vec::new();
12567 for row in range.start.row().0..=range.end.row().0 {
12568 if let Some(selection) = self.selections.build_columnar_selection(
12569 &display_map,
12570 DisplayRow(row),
12571 &positions,
12572 oldest_selection.reversed,
12573 &text_layout_details,
12574 ) {
12575 stack.push(selection.id);
12576 selections.push(selection);
12577 }
12578 }
12579
12580 if above {
12581 stack.reverse();
12582 }
12583
12584 AddSelectionsState { above, stack }
12585 });
12586
12587 let last_added_selection = *state.stack.last().unwrap();
12588 let mut new_selections = Vec::new();
12589 if above == state.above {
12590 let end_row = if above {
12591 DisplayRow(0)
12592 } else {
12593 display_map.max_point().row()
12594 };
12595
12596 'outer: for selection in selections {
12597 if selection.id == last_added_selection {
12598 let range = selection.display_range(&display_map).sorted();
12599 debug_assert_eq!(range.start.row(), range.end.row());
12600 let mut row = range.start.row();
12601 let positions =
12602 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12603 px(start)..px(end)
12604 } else {
12605 let start_x =
12606 display_map.x_for_display_point(range.start, &text_layout_details);
12607 let end_x =
12608 display_map.x_for_display_point(range.end, &text_layout_details);
12609 start_x.min(end_x)..start_x.max(end_x)
12610 };
12611
12612 while row != end_row {
12613 if above {
12614 row.0 -= 1;
12615 } else {
12616 row.0 += 1;
12617 }
12618
12619 if let Some(new_selection) = self.selections.build_columnar_selection(
12620 &display_map,
12621 row,
12622 &positions,
12623 selection.reversed,
12624 &text_layout_details,
12625 ) {
12626 state.stack.push(new_selection.id);
12627 if above {
12628 new_selections.push(new_selection);
12629 new_selections.push(selection);
12630 } else {
12631 new_selections.push(selection);
12632 new_selections.push(new_selection);
12633 }
12634
12635 continue 'outer;
12636 }
12637 }
12638 }
12639
12640 new_selections.push(selection);
12641 }
12642 } else {
12643 new_selections = selections;
12644 new_selections.retain(|s| s.id != last_added_selection);
12645 state.stack.pop();
12646 }
12647
12648 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12649 s.select(new_selections);
12650 });
12651 if state.stack.len() > 1 {
12652 self.add_selections_state = Some(state);
12653 }
12654 }
12655
12656 fn select_match_ranges(
12657 &mut self,
12658 range: Range<usize>,
12659 reversed: bool,
12660 replace_newest: bool,
12661 auto_scroll: Option<Autoscroll>,
12662 window: &mut Window,
12663 cx: &mut Context<Editor>,
12664 ) {
12665 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12666 self.change_selections(auto_scroll, window, cx, |s| {
12667 if replace_newest {
12668 s.delete(s.newest_anchor().id);
12669 }
12670 if reversed {
12671 s.insert_range(range.end..range.start);
12672 } else {
12673 s.insert_range(range);
12674 }
12675 });
12676 }
12677
12678 pub fn select_next_match_internal(
12679 &mut self,
12680 display_map: &DisplaySnapshot,
12681 replace_newest: bool,
12682 autoscroll: Option<Autoscroll>,
12683 window: &mut Window,
12684 cx: &mut Context<Self>,
12685 ) -> Result<()> {
12686 let buffer = &display_map.buffer_snapshot;
12687 let mut selections = self.selections.all::<usize>(cx);
12688 if let Some(mut select_next_state) = self.select_next_state.take() {
12689 let query = &select_next_state.query;
12690 if !select_next_state.done {
12691 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12692 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12693 let mut next_selected_range = None;
12694
12695 let bytes_after_last_selection =
12696 buffer.bytes_in_range(last_selection.end..buffer.len());
12697 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12698 let query_matches = query
12699 .stream_find_iter(bytes_after_last_selection)
12700 .map(|result| (last_selection.end, result))
12701 .chain(
12702 query
12703 .stream_find_iter(bytes_before_first_selection)
12704 .map(|result| (0, result)),
12705 );
12706
12707 for (start_offset, query_match) in query_matches {
12708 let query_match = query_match.unwrap(); // can only fail due to I/O
12709 let offset_range =
12710 start_offset + query_match.start()..start_offset + query_match.end();
12711 let display_range = offset_range.start.to_display_point(display_map)
12712 ..offset_range.end.to_display_point(display_map);
12713
12714 if !select_next_state.wordwise
12715 || (!movement::is_inside_word(display_map, display_range.start)
12716 && !movement::is_inside_word(display_map, display_range.end))
12717 {
12718 // TODO: This is n^2, because we might check all the selections
12719 if !selections
12720 .iter()
12721 .any(|selection| selection.range().overlaps(&offset_range))
12722 {
12723 next_selected_range = Some(offset_range);
12724 break;
12725 }
12726 }
12727 }
12728
12729 if let Some(next_selected_range) = next_selected_range {
12730 self.select_match_ranges(
12731 next_selected_range,
12732 last_selection.reversed,
12733 replace_newest,
12734 autoscroll,
12735 window,
12736 cx,
12737 );
12738 } else {
12739 select_next_state.done = true;
12740 }
12741 }
12742
12743 self.select_next_state = Some(select_next_state);
12744 } else {
12745 let mut only_carets = true;
12746 let mut same_text_selected = true;
12747 let mut selected_text = None;
12748
12749 let mut selections_iter = selections.iter().peekable();
12750 while let Some(selection) = selections_iter.next() {
12751 if selection.start != selection.end {
12752 only_carets = false;
12753 }
12754
12755 if same_text_selected {
12756 if selected_text.is_none() {
12757 selected_text =
12758 Some(buffer.text_for_range(selection.range()).collect::<String>());
12759 }
12760
12761 if let Some(next_selection) = selections_iter.peek() {
12762 if next_selection.range().len() == selection.range().len() {
12763 let next_selected_text = buffer
12764 .text_for_range(next_selection.range())
12765 .collect::<String>();
12766 if Some(next_selected_text) != selected_text {
12767 same_text_selected = false;
12768 selected_text = None;
12769 }
12770 } else {
12771 same_text_selected = false;
12772 selected_text = None;
12773 }
12774 }
12775 }
12776 }
12777
12778 if only_carets {
12779 for selection in &mut selections {
12780 let word_range = movement::surrounding_word(
12781 display_map,
12782 selection.start.to_display_point(display_map),
12783 );
12784 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12785 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12786 selection.goal = SelectionGoal::None;
12787 selection.reversed = false;
12788 self.select_match_ranges(
12789 selection.start..selection.end,
12790 selection.reversed,
12791 replace_newest,
12792 autoscroll,
12793 window,
12794 cx,
12795 );
12796 }
12797
12798 if selections.len() == 1 {
12799 let selection = selections
12800 .last()
12801 .expect("ensured that there's only one selection");
12802 let query = buffer
12803 .text_for_range(selection.start..selection.end)
12804 .collect::<String>();
12805 let is_empty = query.is_empty();
12806 let select_state = SelectNextState {
12807 query: AhoCorasick::new(&[query])?,
12808 wordwise: true,
12809 done: is_empty,
12810 };
12811 self.select_next_state = Some(select_state);
12812 } else {
12813 self.select_next_state = None;
12814 }
12815 } else if let Some(selected_text) = selected_text {
12816 self.select_next_state = Some(SelectNextState {
12817 query: AhoCorasick::new(&[selected_text])?,
12818 wordwise: false,
12819 done: false,
12820 });
12821 self.select_next_match_internal(
12822 display_map,
12823 replace_newest,
12824 autoscroll,
12825 window,
12826 cx,
12827 )?;
12828 }
12829 }
12830 Ok(())
12831 }
12832
12833 pub fn select_all_matches(
12834 &mut self,
12835 _action: &SelectAllMatches,
12836 window: &mut Window,
12837 cx: &mut Context<Self>,
12838 ) -> Result<()> {
12839 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12840
12841 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12842
12843 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12844 let Some(select_next_state) = self.select_next_state.as_mut() else {
12845 return Ok(());
12846 };
12847 if select_next_state.done {
12848 return Ok(());
12849 }
12850
12851 let mut new_selections = Vec::new();
12852
12853 let reversed = self.selections.oldest::<usize>(cx).reversed;
12854 let buffer = &display_map.buffer_snapshot;
12855 let query_matches = select_next_state
12856 .query
12857 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12858
12859 for query_match in query_matches.into_iter() {
12860 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12861 let offset_range = if reversed {
12862 query_match.end()..query_match.start()
12863 } else {
12864 query_match.start()..query_match.end()
12865 };
12866 let display_range = offset_range.start.to_display_point(&display_map)
12867 ..offset_range.end.to_display_point(&display_map);
12868
12869 if !select_next_state.wordwise
12870 || (!movement::is_inside_word(&display_map, display_range.start)
12871 && !movement::is_inside_word(&display_map, display_range.end))
12872 {
12873 new_selections.push(offset_range.start..offset_range.end);
12874 }
12875 }
12876
12877 select_next_state.done = true;
12878 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12879 self.change_selections(None, window, cx, |selections| {
12880 selections.select_ranges(new_selections)
12881 });
12882
12883 Ok(())
12884 }
12885
12886 pub fn select_next(
12887 &mut self,
12888 action: &SelectNext,
12889 window: &mut Window,
12890 cx: &mut Context<Self>,
12891 ) -> Result<()> {
12892 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12893 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12894 self.select_next_match_internal(
12895 &display_map,
12896 action.replace_newest,
12897 Some(Autoscroll::newest()),
12898 window,
12899 cx,
12900 )?;
12901 Ok(())
12902 }
12903
12904 pub fn select_previous(
12905 &mut self,
12906 action: &SelectPrevious,
12907 window: &mut Window,
12908 cx: &mut Context<Self>,
12909 ) -> Result<()> {
12910 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12911 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12912 let buffer = &display_map.buffer_snapshot;
12913 let mut selections = self.selections.all::<usize>(cx);
12914 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12915 let query = &select_prev_state.query;
12916 if !select_prev_state.done {
12917 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12918 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12919 let mut next_selected_range = None;
12920 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12921 let bytes_before_last_selection =
12922 buffer.reversed_bytes_in_range(0..last_selection.start);
12923 let bytes_after_first_selection =
12924 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12925 let query_matches = query
12926 .stream_find_iter(bytes_before_last_selection)
12927 .map(|result| (last_selection.start, result))
12928 .chain(
12929 query
12930 .stream_find_iter(bytes_after_first_selection)
12931 .map(|result| (buffer.len(), result)),
12932 );
12933 for (end_offset, query_match) in query_matches {
12934 let query_match = query_match.unwrap(); // can only fail due to I/O
12935 let offset_range =
12936 end_offset - query_match.end()..end_offset - query_match.start();
12937 let display_range = offset_range.start.to_display_point(&display_map)
12938 ..offset_range.end.to_display_point(&display_map);
12939
12940 if !select_prev_state.wordwise
12941 || (!movement::is_inside_word(&display_map, display_range.start)
12942 && !movement::is_inside_word(&display_map, display_range.end))
12943 {
12944 next_selected_range = Some(offset_range);
12945 break;
12946 }
12947 }
12948
12949 if let Some(next_selected_range) = next_selected_range {
12950 self.select_match_ranges(
12951 next_selected_range,
12952 last_selection.reversed,
12953 action.replace_newest,
12954 Some(Autoscroll::newest()),
12955 window,
12956 cx,
12957 );
12958 } else {
12959 select_prev_state.done = true;
12960 }
12961 }
12962
12963 self.select_prev_state = Some(select_prev_state);
12964 } else {
12965 let mut only_carets = true;
12966 let mut same_text_selected = true;
12967 let mut selected_text = None;
12968
12969 let mut selections_iter = selections.iter().peekable();
12970 while let Some(selection) = selections_iter.next() {
12971 if selection.start != selection.end {
12972 only_carets = false;
12973 }
12974
12975 if same_text_selected {
12976 if selected_text.is_none() {
12977 selected_text =
12978 Some(buffer.text_for_range(selection.range()).collect::<String>());
12979 }
12980
12981 if let Some(next_selection) = selections_iter.peek() {
12982 if next_selection.range().len() == selection.range().len() {
12983 let next_selected_text = buffer
12984 .text_for_range(next_selection.range())
12985 .collect::<String>();
12986 if Some(next_selected_text) != selected_text {
12987 same_text_selected = false;
12988 selected_text = None;
12989 }
12990 } else {
12991 same_text_selected = false;
12992 selected_text = None;
12993 }
12994 }
12995 }
12996 }
12997
12998 if only_carets {
12999 for selection in &mut selections {
13000 let word_range = movement::surrounding_word(
13001 &display_map,
13002 selection.start.to_display_point(&display_map),
13003 );
13004 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
13005 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
13006 selection.goal = SelectionGoal::None;
13007 selection.reversed = false;
13008 self.select_match_ranges(
13009 selection.start..selection.end,
13010 selection.reversed,
13011 action.replace_newest,
13012 Some(Autoscroll::newest()),
13013 window,
13014 cx,
13015 );
13016 }
13017 if selections.len() == 1 {
13018 let selection = selections
13019 .last()
13020 .expect("ensured that there's only one selection");
13021 let query = buffer
13022 .text_for_range(selection.start..selection.end)
13023 .collect::<String>();
13024 let is_empty = query.is_empty();
13025 let select_state = SelectNextState {
13026 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13027 wordwise: true,
13028 done: is_empty,
13029 };
13030 self.select_prev_state = Some(select_state);
13031 } else {
13032 self.select_prev_state = None;
13033 }
13034 } else if let Some(selected_text) = selected_text {
13035 self.select_prev_state = Some(SelectNextState {
13036 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13037 wordwise: false,
13038 done: false,
13039 });
13040 self.select_previous(action, window, cx)?;
13041 }
13042 }
13043 Ok(())
13044 }
13045
13046 pub fn find_next_match(
13047 &mut self,
13048 _: &FindNextMatch,
13049 window: &mut Window,
13050 cx: &mut Context<Self>,
13051 ) -> Result<()> {
13052 let selections = self.selections.disjoint_anchors();
13053 match selections.first() {
13054 Some(first) if selections.len() >= 2 => {
13055 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13056 s.select_ranges([first.range()]);
13057 });
13058 }
13059 _ => self.select_next(
13060 &SelectNext {
13061 replace_newest: true,
13062 },
13063 window,
13064 cx,
13065 )?,
13066 }
13067 Ok(())
13068 }
13069
13070 pub fn find_previous_match(
13071 &mut self,
13072 _: &FindPreviousMatch,
13073 window: &mut Window,
13074 cx: &mut Context<Self>,
13075 ) -> Result<()> {
13076 let selections = self.selections.disjoint_anchors();
13077 match selections.last() {
13078 Some(last) if selections.len() >= 2 => {
13079 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13080 s.select_ranges([last.range()]);
13081 });
13082 }
13083 _ => self.select_previous(
13084 &SelectPrevious {
13085 replace_newest: true,
13086 },
13087 window,
13088 cx,
13089 )?,
13090 }
13091 Ok(())
13092 }
13093
13094 pub fn toggle_comments(
13095 &mut self,
13096 action: &ToggleComments,
13097 window: &mut Window,
13098 cx: &mut Context<Self>,
13099 ) {
13100 if self.read_only(cx) {
13101 return;
13102 }
13103 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13104 let text_layout_details = &self.text_layout_details(window);
13105 self.transact(window, cx, |this, window, cx| {
13106 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13107 let mut edits = Vec::new();
13108 let mut selection_edit_ranges = Vec::new();
13109 let mut last_toggled_row = None;
13110 let snapshot = this.buffer.read(cx).read(cx);
13111 let empty_str: Arc<str> = Arc::default();
13112 let mut suffixes_inserted = Vec::new();
13113 let ignore_indent = action.ignore_indent;
13114
13115 fn comment_prefix_range(
13116 snapshot: &MultiBufferSnapshot,
13117 row: MultiBufferRow,
13118 comment_prefix: &str,
13119 comment_prefix_whitespace: &str,
13120 ignore_indent: bool,
13121 ) -> Range<Point> {
13122 let indent_size = if ignore_indent {
13123 0
13124 } else {
13125 snapshot.indent_size_for_line(row).len
13126 };
13127
13128 let start = Point::new(row.0, indent_size);
13129
13130 let mut line_bytes = snapshot
13131 .bytes_in_range(start..snapshot.max_point())
13132 .flatten()
13133 .copied();
13134
13135 // If this line currently begins with the line comment prefix, then record
13136 // the range containing the prefix.
13137 if line_bytes
13138 .by_ref()
13139 .take(comment_prefix.len())
13140 .eq(comment_prefix.bytes())
13141 {
13142 // Include any whitespace that matches the comment prefix.
13143 let matching_whitespace_len = line_bytes
13144 .zip(comment_prefix_whitespace.bytes())
13145 .take_while(|(a, b)| a == b)
13146 .count() as u32;
13147 let end = Point::new(
13148 start.row,
13149 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13150 );
13151 start..end
13152 } else {
13153 start..start
13154 }
13155 }
13156
13157 fn comment_suffix_range(
13158 snapshot: &MultiBufferSnapshot,
13159 row: MultiBufferRow,
13160 comment_suffix: &str,
13161 comment_suffix_has_leading_space: bool,
13162 ) -> Range<Point> {
13163 let end = Point::new(row.0, snapshot.line_len(row));
13164 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13165
13166 let mut line_end_bytes = snapshot
13167 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13168 .flatten()
13169 .copied();
13170
13171 let leading_space_len = if suffix_start_column > 0
13172 && line_end_bytes.next() == Some(b' ')
13173 && comment_suffix_has_leading_space
13174 {
13175 1
13176 } else {
13177 0
13178 };
13179
13180 // If this line currently begins with the line comment prefix, then record
13181 // the range containing the prefix.
13182 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13183 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13184 start..end
13185 } else {
13186 end..end
13187 }
13188 }
13189
13190 // TODO: Handle selections that cross excerpts
13191 for selection in &mut selections {
13192 let start_column = snapshot
13193 .indent_size_for_line(MultiBufferRow(selection.start.row))
13194 .len;
13195 let language = if let Some(language) =
13196 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13197 {
13198 language
13199 } else {
13200 continue;
13201 };
13202
13203 selection_edit_ranges.clear();
13204
13205 // If multiple selections contain a given row, avoid processing that
13206 // row more than once.
13207 let mut start_row = MultiBufferRow(selection.start.row);
13208 if last_toggled_row == Some(start_row) {
13209 start_row = start_row.next_row();
13210 }
13211 let end_row =
13212 if selection.end.row > selection.start.row && selection.end.column == 0 {
13213 MultiBufferRow(selection.end.row - 1)
13214 } else {
13215 MultiBufferRow(selection.end.row)
13216 };
13217 last_toggled_row = Some(end_row);
13218
13219 if start_row > end_row {
13220 continue;
13221 }
13222
13223 // If the language has line comments, toggle those.
13224 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13225
13226 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13227 if ignore_indent {
13228 full_comment_prefixes = full_comment_prefixes
13229 .into_iter()
13230 .map(|s| Arc::from(s.trim_end()))
13231 .collect();
13232 }
13233
13234 if !full_comment_prefixes.is_empty() {
13235 let first_prefix = full_comment_prefixes
13236 .first()
13237 .expect("prefixes is non-empty");
13238 let prefix_trimmed_lengths = full_comment_prefixes
13239 .iter()
13240 .map(|p| p.trim_end_matches(' ').len())
13241 .collect::<SmallVec<[usize; 4]>>();
13242
13243 let mut all_selection_lines_are_comments = true;
13244
13245 for row in start_row.0..=end_row.0 {
13246 let row = MultiBufferRow(row);
13247 if start_row < end_row && snapshot.is_line_blank(row) {
13248 continue;
13249 }
13250
13251 let prefix_range = full_comment_prefixes
13252 .iter()
13253 .zip(prefix_trimmed_lengths.iter().copied())
13254 .map(|(prefix, trimmed_prefix_len)| {
13255 comment_prefix_range(
13256 snapshot.deref(),
13257 row,
13258 &prefix[..trimmed_prefix_len],
13259 &prefix[trimmed_prefix_len..],
13260 ignore_indent,
13261 )
13262 })
13263 .max_by_key(|range| range.end.column - range.start.column)
13264 .expect("prefixes is non-empty");
13265
13266 if prefix_range.is_empty() {
13267 all_selection_lines_are_comments = false;
13268 }
13269
13270 selection_edit_ranges.push(prefix_range);
13271 }
13272
13273 if all_selection_lines_are_comments {
13274 edits.extend(
13275 selection_edit_ranges
13276 .iter()
13277 .cloned()
13278 .map(|range| (range, empty_str.clone())),
13279 );
13280 } else {
13281 let min_column = selection_edit_ranges
13282 .iter()
13283 .map(|range| range.start.column)
13284 .min()
13285 .unwrap_or(0);
13286 edits.extend(selection_edit_ranges.iter().map(|range| {
13287 let position = Point::new(range.start.row, min_column);
13288 (position..position, first_prefix.clone())
13289 }));
13290 }
13291 } else if let Some((full_comment_prefix, comment_suffix)) =
13292 language.block_comment_delimiters()
13293 {
13294 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13295 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13296 let prefix_range = comment_prefix_range(
13297 snapshot.deref(),
13298 start_row,
13299 comment_prefix,
13300 comment_prefix_whitespace,
13301 ignore_indent,
13302 );
13303 let suffix_range = comment_suffix_range(
13304 snapshot.deref(),
13305 end_row,
13306 comment_suffix.trim_start_matches(' '),
13307 comment_suffix.starts_with(' '),
13308 );
13309
13310 if prefix_range.is_empty() || suffix_range.is_empty() {
13311 edits.push((
13312 prefix_range.start..prefix_range.start,
13313 full_comment_prefix.clone(),
13314 ));
13315 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13316 suffixes_inserted.push((end_row, comment_suffix.len()));
13317 } else {
13318 edits.push((prefix_range, empty_str.clone()));
13319 edits.push((suffix_range, empty_str.clone()));
13320 }
13321 } else {
13322 continue;
13323 }
13324 }
13325
13326 drop(snapshot);
13327 this.buffer.update(cx, |buffer, cx| {
13328 buffer.edit(edits, None, cx);
13329 });
13330
13331 // Adjust selections so that they end before any comment suffixes that
13332 // were inserted.
13333 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13334 let mut selections = this.selections.all::<Point>(cx);
13335 let snapshot = this.buffer.read(cx).read(cx);
13336 for selection in &mut selections {
13337 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13338 match row.cmp(&MultiBufferRow(selection.end.row)) {
13339 Ordering::Less => {
13340 suffixes_inserted.next();
13341 continue;
13342 }
13343 Ordering::Greater => break,
13344 Ordering::Equal => {
13345 if selection.end.column == snapshot.line_len(row) {
13346 if selection.is_empty() {
13347 selection.start.column -= suffix_len as u32;
13348 }
13349 selection.end.column -= suffix_len as u32;
13350 }
13351 break;
13352 }
13353 }
13354 }
13355 }
13356
13357 drop(snapshot);
13358 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13359 s.select(selections)
13360 });
13361
13362 let selections = this.selections.all::<Point>(cx);
13363 let selections_on_single_row = selections.windows(2).all(|selections| {
13364 selections[0].start.row == selections[1].start.row
13365 && selections[0].end.row == selections[1].end.row
13366 && selections[0].start.row == selections[0].end.row
13367 });
13368 let selections_selecting = selections
13369 .iter()
13370 .any(|selection| selection.start != selection.end);
13371 let advance_downwards = action.advance_downwards
13372 && selections_on_single_row
13373 && !selections_selecting
13374 && !matches!(this.mode, EditorMode::SingleLine { .. });
13375
13376 if advance_downwards {
13377 let snapshot = this.buffer.read(cx).snapshot(cx);
13378
13379 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13380 s.move_cursors_with(|display_snapshot, display_point, _| {
13381 let mut point = display_point.to_point(display_snapshot);
13382 point.row += 1;
13383 point = snapshot.clip_point(point, Bias::Left);
13384 let display_point = point.to_display_point(display_snapshot);
13385 let goal = SelectionGoal::HorizontalPosition(
13386 display_snapshot
13387 .x_for_display_point(display_point, text_layout_details)
13388 .into(),
13389 );
13390 (display_point, goal)
13391 })
13392 });
13393 }
13394 });
13395 }
13396
13397 pub fn select_enclosing_symbol(
13398 &mut self,
13399 _: &SelectEnclosingSymbol,
13400 window: &mut Window,
13401 cx: &mut Context<Self>,
13402 ) {
13403 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13404
13405 let buffer = self.buffer.read(cx).snapshot(cx);
13406 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13407
13408 fn update_selection(
13409 selection: &Selection<usize>,
13410 buffer_snap: &MultiBufferSnapshot,
13411 ) -> Option<Selection<usize>> {
13412 let cursor = selection.head();
13413 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13414 for symbol in symbols.iter().rev() {
13415 let start = symbol.range.start.to_offset(buffer_snap);
13416 let end = symbol.range.end.to_offset(buffer_snap);
13417 let new_range = start..end;
13418 if start < selection.start || end > selection.end {
13419 return Some(Selection {
13420 id: selection.id,
13421 start: new_range.start,
13422 end: new_range.end,
13423 goal: SelectionGoal::None,
13424 reversed: selection.reversed,
13425 });
13426 }
13427 }
13428 None
13429 }
13430
13431 let mut selected_larger_symbol = false;
13432 let new_selections = old_selections
13433 .iter()
13434 .map(|selection| match update_selection(selection, &buffer) {
13435 Some(new_selection) => {
13436 if new_selection.range() != selection.range() {
13437 selected_larger_symbol = true;
13438 }
13439 new_selection
13440 }
13441 None => selection.clone(),
13442 })
13443 .collect::<Vec<_>>();
13444
13445 if selected_larger_symbol {
13446 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13447 s.select(new_selections);
13448 });
13449 }
13450 }
13451
13452 pub fn select_larger_syntax_node(
13453 &mut self,
13454 _: &SelectLargerSyntaxNode,
13455 window: &mut Window,
13456 cx: &mut Context<Self>,
13457 ) {
13458 let Some(visible_row_count) = self.visible_row_count() else {
13459 return;
13460 };
13461 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13462 if old_selections.is_empty() {
13463 return;
13464 }
13465
13466 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13467
13468 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13469 let buffer = self.buffer.read(cx).snapshot(cx);
13470
13471 let mut selected_larger_node = false;
13472 let mut new_selections = old_selections
13473 .iter()
13474 .map(|selection| {
13475 let old_range = selection.start..selection.end;
13476
13477 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13478 // manually select word at selection
13479 if ["string_content", "inline"].contains(&node.kind()) {
13480 let word_range = {
13481 let display_point = buffer
13482 .offset_to_point(old_range.start)
13483 .to_display_point(&display_map);
13484 let Range { start, end } =
13485 movement::surrounding_word(&display_map, display_point);
13486 start.to_point(&display_map).to_offset(&buffer)
13487 ..end.to_point(&display_map).to_offset(&buffer)
13488 };
13489 // ignore if word is already selected
13490 if !word_range.is_empty() && old_range != word_range {
13491 let last_word_range = {
13492 let display_point = buffer
13493 .offset_to_point(old_range.end)
13494 .to_display_point(&display_map);
13495 let Range { start, end } =
13496 movement::surrounding_word(&display_map, display_point);
13497 start.to_point(&display_map).to_offset(&buffer)
13498 ..end.to_point(&display_map).to_offset(&buffer)
13499 };
13500 // only select word if start and end point belongs to same word
13501 if word_range == last_word_range {
13502 selected_larger_node = true;
13503 return Selection {
13504 id: selection.id,
13505 start: word_range.start,
13506 end: word_range.end,
13507 goal: SelectionGoal::None,
13508 reversed: selection.reversed,
13509 };
13510 }
13511 }
13512 }
13513 }
13514
13515 let mut new_range = old_range.clone();
13516 while let Some((_node, containing_range)) =
13517 buffer.syntax_ancestor(new_range.clone())
13518 {
13519 new_range = match containing_range {
13520 MultiOrSingleBufferOffsetRange::Single(_) => break,
13521 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13522 };
13523 if !display_map.intersects_fold(new_range.start)
13524 && !display_map.intersects_fold(new_range.end)
13525 {
13526 break;
13527 }
13528 }
13529
13530 selected_larger_node |= new_range != old_range;
13531 Selection {
13532 id: selection.id,
13533 start: new_range.start,
13534 end: new_range.end,
13535 goal: SelectionGoal::None,
13536 reversed: selection.reversed,
13537 }
13538 })
13539 .collect::<Vec<_>>();
13540
13541 if !selected_larger_node {
13542 return; // don't put this call in the history
13543 }
13544
13545 // scroll based on transformation done to the last selection created by the user
13546 let (last_old, last_new) = old_selections
13547 .last()
13548 .zip(new_selections.last().cloned())
13549 .expect("old_selections isn't empty");
13550
13551 // revert selection
13552 let is_selection_reversed = {
13553 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13554 new_selections.last_mut().expect("checked above").reversed =
13555 should_newest_selection_be_reversed;
13556 should_newest_selection_be_reversed
13557 };
13558
13559 if selected_larger_node {
13560 self.select_syntax_node_history.disable_clearing = true;
13561 self.change_selections(None, window, cx, |s| {
13562 s.select(new_selections.clone());
13563 });
13564 self.select_syntax_node_history.disable_clearing = false;
13565 }
13566
13567 let start_row = last_new.start.to_display_point(&display_map).row().0;
13568 let end_row = last_new.end.to_display_point(&display_map).row().0;
13569 let selection_height = end_row - start_row + 1;
13570 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13571
13572 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13573 let scroll_behavior = if fits_on_the_screen {
13574 self.request_autoscroll(Autoscroll::fit(), cx);
13575 SelectSyntaxNodeScrollBehavior::FitSelection
13576 } else if is_selection_reversed {
13577 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13578 SelectSyntaxNodeScrollBehavior::CursorTop
13579 } else {
13580 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13581 SelectSyntaxNodeScrollBehavior::CursorBottom
13582 };
13583
13584 self.select_syntax_node_history.push((
13585 old_selections,
13586 scroll_behavior,
13587 is_selection_reversed,
13588 ));
13589 }
13590
13591 pub fn select_smaller_syntax_node(
13592 &mut self,
13593 _: &SelectSmallerSyntaxNode,
13594 window: &mut Window,
13595 cx: &mut Context<Self>,
13596 ) {
13597 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13598
13599 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13600 self.select_syntax_node_history.pop()
13601 {
13602 if let Some(selection) = selections.last_mut() {
13603 selection.reversed = is_selection_reversed;
13604 }
13605
13606 self.select_syntax_node_history.disable_clearing = true;
13607 self.change_selections(None, window, cx, |s| {
13608 s.select(selections.to_vec());
13609 });
13610 self.select_syntax_node_history.disable_clearing = false;
13611
13612 match scroll_behavior {
13613 SelectSyntaxNodeScrollBehavior::CursorTop => {
13614 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13615 }
13616 SelectSyntaxNodeScrollBehavior::FitSelection => {
13617 self.request_autoscroll(Autoscroll::fit(), cx);
13618 }
13619 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13620 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13621 }
13622 }
13623 }
13624 }
13625
13626 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13627 if !EditorSettings::get_global(cx).gutter.runnables {
13628 self.clear_tasks();
13629 return Task::ready(());
13630 }
13631 let project = self.project.as_ref().map(Entity::downgrade);
13632 let task_sources = self.lsp_task_sources(cx);
13633 let multi_buffer = self.buffer.downgrade();
13634 cx.spawn_in(window, async move |editor, cx| {
13635 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13636 let Some(project) = project.and_then(|p| p.upgrade()) else {
13637 return;
13638 };
13639 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13640 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13641 }) else {
13642 return;
13643 };
13644
13645 let hide_runnables = project
13646 .update(cx, |project, cx| {
13647 // Do not display any test indicators in non-dev server remote projects.
13648 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13649 })
13650 .unwrap_or(true);
13651 if hide_runnables {
13652 return;
13653 }
13654 let new_rows =
13655 cx.background_spawn({
13656 let snapshot = display_snapshot.clone();
13657 async move {
13658 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13659 }
13660 })
13661 .await;
13662 let Ok(lsp_tasks) =
13663 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13664 else {
13665 return;
13666 };
13667 let lsp_tasks = lsp_tasks.await;
13668
13669 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13670 lsp_tasks
13671 .into_iter()
13672 .flat_map(|(kind, tasks)| {
13673 tasks.into_iter().filter_map(move |(location, task)| {
13674 Some((kind.clone(), location?, task))
13675 })
13676 })
13677 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13678 let buffer = location.target.buffer;
13679 let buffer_snapshot = buffer.read(cx).snapshot();
13680 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13681 |(excerpt_id, snapshot, _)| {
13682 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13683 display_snapshot
13684 .buffer_snapshot
13685 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13686 } else {
13687 None
13688 }
13689 },
13690 );
13691 if let Some(offset) = offset {
13692 let task_buffer_range =
13693 location.target.range.to_point(&buffer_snapshot);
13694 let context_buffer_range =
13695 task_buffer_range.to_offset(&buffer_snapshot);
13696 let context_range = BufferOffset(context_buffer_range.start)
13697 ..BufferOffset(context_buffer_range.end);
13698
13699 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13700 .or_insert_with(|| RunnableTasks {
13701 templates: Vec::new(),
13702 offset,
13703 column: task_buffer_range.start.column,
13704 extra_variables: HashMap::default(),
13705 context_range,
13706 })
13707 .templates
13708 .push((kind, task.original_task().clone()));
13709 }
13710
13711 acc
13712 })
13713 }) else {
13714 return;
13715 };
13716
13717 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
13718 buffer.language_settings(cx).tasks.prefer_lsp
13719 }) else {
13720 return;
13721 };
13722
13723 let rows = Self::runnable_rows(
13724 project,
13725 display_snapshot,
13726 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
13727 new_rows,
13728 cx.clone(),
13729 );
13730 editor
13731 .update(cx, |editor, _| {
13732 editor.clear_tasks();
13733 for (key, mut value) in rows {
13734 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13735 value.templates.extend(lsp_tasks.templates);
13736 }
13737
13738 editor.insert_tasks(key, value);
13739 }
13740 for (key, value) in lsp_tasks_by_rows {
13741 editor.insert_tasks(key, value);
13742 }
13743 })
13744 .ok();
13745 })
13746 }
13747 fn fetch_runnable_ranges(
13748 snapshot: &DisplaySnapshot,
13749 range: Range<Anchor>,
13750 ) -> Vec<language::RunnableRange> {
13751 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13752 }
13753
13754 fn runnable_rows(
13755 project: Entity<Project>,
13756 snapshot: DisplaySnapshot,
13757 prefer_lsp: bool,
13758 runnable_ranges: Vec<RunnableRange>,
13759 mut cx: AsyncWindowContext,
13760 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13761 runnable_ranges
13762 .into_iter()
13763 .filter_map(|mut runnable| {
13764 let mut tasks = cx
13765 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13766 .ok()?;
13767 if prefer_lsp {
13768 tasks.retain(|(task_kind, _)| {
13769 !matches!(task_kind, TaskSourceKind::Language { .. })
13770 });
13771 }
13772 if tasks.is_empty() {
13773 return None;
13774 }
13775
13776 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13777
13778 let row = snapshot
13779 .buffer_snapshot
13780 .buffer_line_for_row(MultiBufferRow(point.row))?
13781 .1
13782 .start
13783 .row;
13784
13785 let context_range =
13786 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13787 Some((
13788 (runnable.buffer_id, row),
13789 RunnableTasks {
13790 templates: tasks,
13791 offset: snapshot
13792 .buffer_snapshot
13793 .anchor_before(runnable.run_range.start),
13794 context_range,
13795 column: point.column,
13796 extra_variables: runnable.extra_captures,
13797 },
13798 ))
13799 })
13800 .collect()
13801 }
13802
13803 fn templates_with_tags(
13804 project: &Entity<Project>,
13805 runnable: &mut Runnable,
13806 cx: &mut App,
13807 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13808 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13809 let (worktree_id, file) = project
13810 .buffer_for_id(runnable.buffer, cx)
13811 .and_then(|buffer| buffer.read(cx).file())
13812 .map(|file| (file.worktree_id(cx), file.clone()))
13813 .unzip();
13814
13815 (
13816 project.task_store().read(cx).task_inventory().cloned(),
13817 worktree_id,
13818 file,
13819 )
13820 });
13821
13822 let mut templates_with_tags = mem::take(&mut runnable.tags)
13823 .into_iter()
13824 .flat_map(|RunnableTag(tag)| {
13825 inventory
13826 .as_ref()
13827 .into_iter()
13828 .flat_map(|inventory| {
13829 inventory.read(cx).list_tasks(
13830 file.clone(),
13831 Some(runnable.language.clone()),
13832 worktree_id,
13833 cx,
13834 )
13835 })
13836 .filter(move |(_, template)| {
13837 template.tags.iter().any(|source_tag| source_tag == &tag)
13838 })
13839 })
13840 .sorted_by_key(|(kind, _)| kind.to_owned())
13841 .collect::<Vec<_>>();
13842 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13843 // Strongest source wins; if we have worktree tag binding, prefer that to
13844 // global and language bindings;
13845 // if we have a global binding, prefer that to language binding.
13846 let first_mismatch = templates_with_tags
13847 .iter()
13848 .position(|(tag_source, _)| tag_source != leading_tag_source);
13849 if let Some(index) = first_mismatch {
13850 templates_with_tags.truncate(index);
13851 }
13852 }
13853
13854 templates_with_tags
13855 }
13856
13857 pub fn move_to_enclosing_bracket(
13858 &mut self,
13859 _: &MoveToEnclosingBracket,
13860 window: &mut Window,
13861 cx: &mut Context<Self>,
13862 ) {
13863 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13864 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13865 s.move_offsets_with(|snapshot, selection| {
13866 let Some(enclosing_bracket_ranges) =
13867 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13868 else {
13869 return;
13870 };
13871
13872 let mut best_length = usize::MAX;
13873 let mut best_inside = false;
13874 let mut best_in_bracket_range = false;
13875 let mut best_destination = None;
13876 for (open, close) in enclosing_bracket_ranges {
13877 let close = close.to_inclusive();
13878 let length = close.end() - open.start;
13879 let inside = selection.start >= open.end && selection.end <= *close.start();
13880 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13881 || close.contains(&selection.head());
13882
13883 // If best is next to a bracket and current isn't, skip
13884 if !in_bracket_range && best_in_bracket_range {
13885 continue;
13886 }
13887
13888 // Prefer smaller lengths unless best is inside and current isn't
13889 if length > best_length && (best_inside || !inside) {
13890 continue;
13891 }
13892
13893 best_length = length;
13894 best_inside = inside;
13895 best_in_bracket_range = in_bracket_range;
13896 best_destination = Some(
13897 if close.contains(&selection.start) && close.contains(&selection.end) {
13898 if inside { open.end } else { open.start }
13899 } else if inside {
13900 *close.start()
13901 } else {
13902 *close.end()
13903 },
13904 );
13905 }
13906
13907 if let Some(destination) = best_destination {
13908 selection.collapse_to(destination, SelectionGoal::None);
13909 }
13910 })
13911 });
13912 }
13913
13914 pub fn undo_selection(
13915 &mut self,
13916 _: &UndoSelection,
13917 window: &mut Window,
13918 cx: &mut Context<Self>,
13919 ) {
13920 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13921 self.end_selection(window, cx);
13922 self.selection_history.mode = SelectionHistoryMode::Undoing;
13923 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13924 self.change_selections(None, window, cx, |s| {
13925 s.select_anchors(entry.selections.to_vec())
13926 });
13927 self.select_next_state = entry.select_next_state;
13928 self.select_prev_state = entry.select_prev_state;
13929 self.add_selections_state = entry.add_selections_state;
13930 self.request_autoscroll(Autoscroll::newest(), cx);
13931 }
13932 self.selection_history.mode = SelectionHistoryMode::Normal;
13933 }
13934
13935 pub fn redo_selection(
13936 &mut self,
13937 _: &RedoSelection,
13938 window: &mut Window,
13939 cx: &mut Context<Self>,
13940 ) {
13941 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13942 self.end_selection(window, cx);
13943 self.selection_history.mode = SelectionHistoryMode::Redoing;
13944 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13945 self.change_selections(None, window, cx, |s| {
13946 s.select_anchors(entry.selections.to_vec())
13947 });
13948 self.select_next_state = entry.select_next_state;
13949 self.select_prev_state = entry.select_prev_state;
13950 self.add_selections_state = entry.add_selections_state;
13951 self.request_autoscroll(Autoscroll::newest(), cx);
13952 }
13953 self.selection_history.mode = SelectionHistoryMode::Normal;
13954 }
13955
13956 pub fn expand_excerpts(
13957 &mut self,
13958 action: &ExpandExcerpts,
13959 _: &mut Window,
13960 cx: &mut Context<Self>,
13961 ) {
13962 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13963 }
13964
13965 pub fn expand_excerpts_down(
13966 &mut self,
13967 action: &ExpandExcerptsDown,
13968 _: &mut Window,
13969 cx: &mut Context<Self>,
13970 ) {
13971 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13972 }
13973
13974 pub fn expand_excerpts_up(
13975 &mut self,
13976 action: &ExpandExcerptsUp,
13977 _: &mut Window,
13978 cx: &mut Context<Self>,
13979 ) {
13980 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13981 }
13982
13983 pub fn expand_excerpts_for_direction(
13984 &mut self,
13985 lines: u32,
13986 direction: ExpandExcerptDirection,
13987
13988 cx: &mut Context<Self>,
13989 ) {
13990 let selections = self.selections.disjoint_anchors();
13991
13992 let lines = if lines == 0 {
13993 EditorSettings::get_global(cx).expand_excerpt_lines
13994 } else {
13995 lines
13996 };
13997
13998 self.buffer.update(cx, |buffer, cx| {
13999 let snapshot = buffer.snapshot(cx);
14000 let mut excerpt_ids = selections
14001 .iter()
14002 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14003 .collect::<Vec<_>>();
14004 excerpt_ids.sort();
14005 excerpt_ids.dedup();
14006 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14007 })
14008 }
14009
14010 pub fn expand_excerpt(
14011 &mut self,
14012 excerpt: ExcerptId,
14013 direction: ExpandExcerptDirection,
14014 window: &mut Window,
14015 cx: &mut Context<Self>,
14016 ) {
14017 let current_scroll_position = self.scroll_position(cx);
14018 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14019 let mut should_scroll_up = false;
14020
14021 if direction == ExpandExcerptDirection::Down {
14022 let multi_buffer = self.buffer.read(cx);
14023 let snapshot = multi_buffer.snapshot(cx);
14024 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14025 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14026 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14027 let buffer_snapshot = buffer.read(cx).snapshot();
14028 let excerpt_end_row =
14029 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14030 let last_row = buffer_snapshot.max_point().row;
14031 let lines_below = last_row.saturating_sub(excerpt_end_row);
14032 should_scroll_up = lines_below >= lines_to_expand;
14033 }
14034 }
14035 }
14036 }
14037
14038 self.buffer.update(cx, |buffer, cx| {
14039 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14040 });
14041
14042 if should_scroll_up {
14043 let new_scroll_position =
14044 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14045 self.set_scroll_position(new_scroll_position, window, cx);
14046 }
14047 }
14048
14049 pub fn go_to_singleton_buffer_point(
14050 &mut self,
14051 point: Point,
14052 window: &mut Window,
14053 cx: &mut Context<Self>,
14054 ) {
14055 self.go_to_singleton_buffer_range(point..point, window, cx);
14056 }
14057
14058 pub fn go_to_singleton_buffer_range(
14059 &mut self,
14060 range: Range<Point>,
14061 window: &mut Window,
14062 cx: &mut Context<Self>,
14063 ) {
14064 let multibuffer = self.buffer().read(cx);
14065 let Some(buffer) = multibuffer.as_singleton() else {
14066 return;
14067 };
14068 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14069 return;
14070 };
14071 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14072 return;
14073 };
14074 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14075 s.select_anchor_ranges([start..end])
14076 });
14077 }
14078
14079 pub fn go_to_diagnostic(
14080 &mut self,
14081 _: &GoToDiagnostic,
14082 window: &mut Window,
14083 cx: &mut Context<Self>,
14084 ) {
14085 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14086 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14087 }
14088
14089 pub fn go_to_prev_diagnostic(
14090 &mut self,
14091 _: &GoToPreviousDiagnostic,
14092 window: &mut Window,
14093 cx: &mut Context<Self>,
14094 ) {
14095 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14096 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14097 }
14098
14099 pub fn go_to_diagnostic_impl(
14100 &mut self,
14101 direction: Direction,
14102 window: &mut Window,
14103 cx: &mut Context<Self>,
14104 ) {
14105 let buffer = self.buffer.read(cx).snapshot(cx);
14106 let selection = self.selections.newest::<usize>(cx);
14107
14108 let mut active_group_id = None;
14109 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14110 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14111 active_group_id = Some(active_group.group_id);
14112 }
14113 }
14114
14115 fn filtered(
14116 snapshot: EditorSnapshot,
14117 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14118 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14119 diagnostics
14120 .filter(|entry| entry.range.start != entry.range.end)
14121 .filter(|entry| !entry.diagnostic.is_unnecessary)
14122 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14123 }
14124
14125 let snapshot = self.snapshot(window, cx);
14126 let before = filtered(
14127 snapshot.clone(),
14128 buffer
14129 .diagnostics_in_range(0..selection.start)
14130 .filter(|entry| entry.range.start <= selection.start),
14131 );
14132 let after = filtered(
14133 snapshot,
14134 buffer
14135 .diagnostics_in_range(selection.start..buffer.len())
14136 .filter(|entry| entry.range.start >= selection.start),
14137 );
14138
14139 let mut found: Option<DiagnosticEntry<usize>> = None;
14140 if direction == Direction::Prev {
14141 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14142 {
14143 for diagnostic in prev_diagnostics.into_iter().rev() {
14144 if diagnostic.range.start != selection.start
14145 || active_group_id
14146 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14147 {
14148 found = Some(diagnostic);
14149 break 'outer;
14150 }
14151 }
14152 }
14153 } else {
14154 for diagnostic in after.chain(before) {
14155 if diagnostic.range.start != selection.start
14156 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14157 {
14158 found = Some(diagnostic);
14159 break;
14160 }
14161 }
14162 }
14163 let Some(next_diagnostic) = found else {
14164 return;
14165 };
14166
14167 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14168 return;
14169 };
14170 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14171 s.select_ranges(vec![
14172 next_diagnostic.range.start..next_diagnostic.range.start,
14173 ])
14174 });
14175 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14176 self.refresh_inline_completion(false, true, window, cx);
14177 }
14178
14179 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14180 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14181 let snapshot = self.snapshot(window, cx);
14182 let selection = self.selections.newest::<Point>(cx);
14183 self.go_to_hunk_before_or_after_position(
14184 &snapshot,
14185 selection.head(),
14186 Direction::Next,
14187 window,
14188 cx,
14189 );
14190 }
14191
14192 pub fn go_to_hunk_before_or_after_position(
14193 &mut self,
14194 snapshot: &EditorSnapshot,
14195 position: Point,
14196 direction: Direction,
14197 window: &mut Window,
14198 cx: &mut Context<Editor>,
14199 ) {
14200 let row = if direction == Direction::Next {
14201 self.hunk_after_position(snapshot, position)
14202 .map(|hunk| hunk.row_range.start)
14203 } else {
14204 self.hunk_before_position(snapshot, position)
14205 };
14206
14207 if let Some(row) = row {
14208 let destination = Point::new(row.0, 0);
14209 let autoscroll = Autoscroll::center();
14210
14211 self.unfold_ranges(&[destination..destination], false, false, cx);
14212 self.change_selections(Some(autoscroll), window, cx, |s| {
14213 s.select_ranges([destination..destination]);
14214 });
14215 }
14216 }
14217
14218 fn hunk_after_position(
14219 &mut self,
14220 snapshot: &EditorSnapshot,
14221 position: Point,
14222 ) -> Option<MultiBufferDiffHunk> {
14223 snapshot
14224 .buffer_snapshot
14225 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14226 .find(|hunk| hunk.row_range.start.0 > position.row)
14227 .or_else(|| {
14228 snapshot
14229 .buffer_snapshot
14230 .diff_hunks_in_range(Point::zero()..position)
14231 .find(|hunk| hunk.row_range.end.0 < position.row)
14232 })
14233 }
14234
14235 fn go_to_prev_hunk(
14236 &mut self,
14237 _: &GoToPreviousHunk,
14238 window: &mut Window,
14239 cx: &mut Context<Self>,
14240 ) {
14241 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14242 let snapshot = self.snapshot(window, cx);
14243 let selection = self.selections.newest::<Point>(cx);
14244 self.go_to_hunk_before_or_after_position(
14245 &snapshot,
14246 selection.head(),
14247 Direction::Prev,
14248 window,
14249 cx,
14250 );
14251 }
14252
14253 fn hunk_before_position(
14254 &mut self,
14255 snapshot: &EditorSnapshot,
14256 position: Point,
14257 ) -> Option<MultiBufferRow> {
14258 snapshot
14259 .buffer_snapshot
14260 .diff_hunk_before(position)
14261 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14262 }
14263
14264 fn go_to_next_change(
14265 &mut self,
14266 _: &GoToNextChange,
14267 window: &mut Window,
14268 cx: &mut Context<Self>,
14269 ) {
14270 if let Some(selections) = self
14271 .change_list
14272 .next_change(1, Direction::Next)
14273 .map(|s| s.to_vec())
14274 {
14275 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14276 let map = s.display_map();
14277 s.select_display_ranges(selections.iter().map(|a| {
14278 let point = a.to_display_point(&map);
14279 point..point
14280 }))
14281 })
14282 }
14283 }
14284
14285 fn go_to_previous_change(
14286 &mut self,
14287 _: &GoToPreviousChange,
14288 window: &mut Window,
14289 cx: &mut Context<Self>,
14290 ) {
14291 if let Some(selections) = self
14292 .change_list
14293 .next_change(1, Direction::Prev)
14294 .map(|s| s.to_vec())
14295 {
14296 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14297 let map = s.display_map();
14298 s.select_display_ranges(selections.iter().map(|a| {
14299 let point = a.to_display_point(&map);
14300 point..point
14301 }))
14302 })
14303 }
14304 }
14305
14306 fn go_to_line<T: 'static>(
14307 &mut self,
14308 position: Anchor,
14309 highlight_color: Option<Hsla>,
14310 window: &mut Window,
14311 cx: &mut Context<Self>,
14312 ) {
14313 let snapshot = self.snapshot(window, cx).display_snapshot;
14314 let position = position.to_point(&snapshot.buffer_snapshot);
14315 let start = snapshot
14316 .buffer_snapshot
14317 .clip_point(Point::new(position.row, 0), Bias::Left);
14318 let end = start + Point::new(1, 0);
14319 let start = snapshot.buffer_snapshot.anchor_before(start);
14320 let end = snapshot.buffer_snapshot.anchor_before(end);
14321
14322 self.highlight_rows::<T>(
14323 start..end,
14324 highlight_color
14325 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14326 Default::default(),
14327 cx,
14328 );
14329
14330 if self.buffer.read(cx).is_singleton() {
14331 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14332 }
14333 }
14334
14335 pub fn go_to_definition(
14336 &mut self,
14337 _: &GoToDefinition,
14338 window: &mut Window,
14339 cx: &mut Context<Self>,
14340 ) -> Task<Result<Navigated>> {
14341 let definition =
14342 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14343 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14344 cx.spawn_in(window, async move |editor, cx| {
14345 if definition.await? == Navigated::Yes {
14346 return Ok(Navigated::Yes);
14347 }
14348 match fallback_strategy {
14349 GoToDefinitionFallback::None => Ok(Navigated::No),
14350 GoToDefinitionFallback::FindAllReferences => {
14351 match editor.update_in(cx, |editor, window, cx| {
14352 editor.find_all_references(&FindAllReferences, window, cx)
14353 })? {
14354 Some(references) => references.await,
14355 None => Ok(Navigated::No),
14356 }
14357 }
14358 }
14359 })
14360 }
14361
14362 pub fn go_to_declaration(
14363 &mut self,
14364 _: &GoToDeclaration,
14365 window: &mut Window,
14366 cx: &mut Context<Self>,
14367 ) -> Task<Result<Navigated>> {
14368 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14369 }
14370
14371 pub fn go_to_declaration_split(
14372 &mut self,
14373 _: &GoToDeclaration,
14374 window: &mut Window,
14375 cx: &mut Context<Self>,
14376 ) -> Task<Result<Navigated>> {
14377 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14378 }
14379
14380 pub fn go_to_implementation(
14381 &mut self,
14382 _: &GoToImplementation,
14383 window: &mut Window,
14384 cx: &mut Context<Self>,
14385 ) -> Task<Result<Navigated>> {
14386 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14387 }
14388
14389 pub fn go_to_implementation_split(
14390 &mut self,
14391 _: &GoToImplementationSplit,
14392 window: &mut Window,
14393 cx: &mut Context<Self>,
14394 ) -> Task<Result<Navigated>> {
14395 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14396 }
14397
14398 pub fn go_to_type_definition(
14399 &mut self,
14400 _: &GoToTypeDefinition,
14401 window: &mut Window,
14402 cx: &mut Context<Self>,
14403 ) -> Task<Result<Navigated>> {
14404 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14405 }
14406
14407 pub fn go_to_definition_split(
14408 &mut self,
14409 _: &GoToDefinitionSplit,
14410 window: &mut Window,
14411 cx: &mut Context<Self>,
14412 ) -> Task<Result<Navigated>> {
14413 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14414 }
14415
14416 pub fn go_to_type_definition_split(
14417 &mut self,
14418 _: &GoToTypeDefinitionSplit,
14419 window: &mut Window,
14420 cx: &mut Context<Self>,
14421 ) -> Task<Result<Navigated>> {
14422 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14423 }
14424
14425 fn go_to_definition_of_kind(
14426 &mut self,
14427 kind: GotoDefinitionKind,
14428 split: bool,
14429 window: &mut Window,
14430 cx: &mut Context<Self>,
14431 ) -> Task<Result<Navigated>> {
14432 let Some(provider) = self.semantics_provider.clone() else {
14433 return Task::ready(Ok(Navigated::No));
14434 };
14435 let head = self.selections.newest::<usize>(cx).head();
14436 let buffer = self.buffer.read(cx);
14437 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14438 text_anchor
14439 } else {
14440 return Task::ready(Ok(Navigated::No));
14441 };
14442
14443 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14444 return Task::ready(Ok(Navigated::No));
14445 };
14446
14447 cx.spawn_in(window, async move |editor, cx| {
14448 let definitions = definitions.await?;
14449 let navigated = editor
14450 .update_in(cx, |editor, window, cx| {
14451 editor.navigate_to_hover_links(
14452 Some(kind),
14453 definitions
14454 .into_iter()
14455 .filter(|location| {
14456 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14457 })
14458 .map(HoverLink::Text)
14459 .collect::<Vec<_>>(),
14460 split,
14461 window,
14462 cx,
14463 )
14464 })?
14465 .await?;
14466 anyhow::Ok(navigated)
14467 })
14468 }
14469
14470 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14471 let selection = self.selections.newest_anchor();
14472 let head = selection.head();
14473 let tail = selection.tail();
14474
14475 let Some((buffer, start_position)) =
14476 self.buffer.read(cx).text_anchor_for_position(head, cx)
14477 else {
14478 return;
14479 };
14480
14481 let end_position = if head != tail {
14482 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14483 return;
14484 };
14485 Some(pos)
14486 } else {
14487 None
14488 };
14489
14490 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14491 let url = if let Some(end_pos) = end_position {
14492 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14493 } else {
14494 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14495 };
14496
14497 if let Some(url) = url {
14498 editor.update(cx, |_, cx| {
14499 cx.open_url(&url);
14500 })
14501 } else {
14502 Ok(())
14503 }
14504 });
14505
14506 url_finder.detach();
14507 }
14508
14509 pub fn open_selected_filename(
14510 &mut self,
14511 _: &OpenSelectedFilename,
14512 window: &mut Window,
14513 cx: &mut Context<Self>,
14514 ) {
14515 let Some(workspace) = self.workspace() else {
14516 return;
14517 };
14518
14519 let position = self.selections.newest_anchor().head();
14520
14521 let Some((buffer, buffer_position)) =
14522 self.buffer.read(cx).text_anchor_for_position(position, cx)
14523 else {
14524 return;
14525 };
14526
14527 let project = self.project.clone();
14528
14529 cx.spawn_in(window, async move |_, cx| {
14530 let result = find_file(&buffer, project, buffer_position, cx).await;
14531
14532 if let Some((_, path)) = result {
14533 workspace
14534 .update_in(cx, |workspace, window, cx| {
14535 workspace.open_resolved_path(path, window, cx)
14536 })?
14537 .await?;
14538 }
14539 anyhow::Ok(())
14540 })
14541 .detach();
14542 }
14543
14544 pub(crate) fn navigate_to_hover_links(
14545 &mut self,
14546 kind: Option<GotoDefinitionKind>,
14547 mut definitions: Vec<HoverLink>,
14548 split: bool,
14549 window: &mut Window,
14550 cx: &mut Context<Editor>,
14551 ) -> Task<Result<Navigated>> {
14552 // If there is one definition, just open it directly
14553 if definitions.len() == 1 {
14554 let definition = definitions.pop().unwrap();
14555
14556 enum TargetTaskResult {
14557 Location(Option<Location>),
14558 AlreadyNavigated,
14559 }
14560
14561 let target_task = match definition {
14562 HoverLink::Text(link) => {
14563 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14564 }
14565 HoverLink::InlayHint(lsp_location, server_id) => {
14566 let computation =
14567 self.compute_target_location(lsp_location, server_id, window, cx);
14568 cx.background_spawn(async move {
14569 let location = computation.await?;
14570 Ok(TargetTaskResult::Location(location))
14571 })
14572 }
14573 HoverLink::Url(url) => {
14574 cx.open_url(&url);
14575 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14576 }
14577 HoverLink::File(path) => {
14578 if let Some(workspace) = self.workspace() {
14579 cx.spawn_in(window, async move |_, cx| {
14580 workspace
14581 .update_in(cx, |workspace, window, cx| {
14582 workspace.open_resolved_path(path, window, cx)
14583 })?
14584 .await
14585 .map(|_| TargetTaskResult::AlreadyNavigated)
14586 })
14587 } else {
14588 Task::ready(Ok(TargetTaskResult::Location(None)))
14589 }
14590 }
14591 };
14592 cx.spawn_in(window, async move |editor, cx| {
14593 let target = match target_task.await.context("target resolution task")? {
14594 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14595 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14596 TargetTaskResult::Location(Some(target)) => target,
14597 };
14598
14599 editor.update_in(cx, |editor, window, cx| {
14600 let Some(workspace) = editor.workspace() else {
14601 return Navigated::No;
14602 };
14603 let pane = workspace.read(cx).active_pane().clone();
14604
14605 let range = target.range.to_point(target.buffer.read(cx));
14606 let range = editor.range_for_match(&range);
14607 let range = collapse_multiline_range(range);
14608
14609 if !split
14610 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14611 {
14612 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14613 } else {
14614 window.defer(cx, move |window, cx| {
14615 let target_editor: Entity<Self> =
14616 workspace.update(cx, |workspace, cx| {
14617 let pane = if split {
14618 workspace.adjacent_pane(window, cx)
14619 } else {
14620 workspace.active_pane().clone()
14621 };
14622
14623 workspace.open_project_item(
14624 pane,
14625 target.buffer.clone(),
14626 true,
14627 true,
14628 window,
14629 cx,
14630 )
14631 });
14632 target_editor.update(cx, |target_editor, cx| {
14633 // When selecting a definition in a different buffer, disable the nav history
14634 // to avoid creating a history entry at the previous cursor location.
14635 pane.update(cx, |pane, _| pane.disable_history());
14636 target_editor.go_to_singleton_buffer_range(range, window, cx);
14637 pane.update(cx, |pane, _| pane.enable_history());
14638 });
14639 });
14640 }
14641 Navigated::Yes
14642 })
14643 })
14644 } else if !definitions.is_empty() {
14645 cx.spawn_in(window, async move |editor, cx| {
14646 let (title, location_tasks, workspace) = editor
14647 .update_in(cx, |editor, window, cx| {
14648 let tab_kind = match kind {
14649 Some(GotoDefinitionKind::Implementation) => "Implementations",
14650 _ => "Definitions",
14651 };
14652 let title = definitions
14653 .iter()
14654 .find_map(|definition| match definition {
14655 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14656 let buffer = origin.buffer.read(cx);
14657 format!(
14658 "{} for {}",
14659 tab_kind,
14660 buffer
14661 .text_for_range(origin.range.clone())
14662 .collect::<String>()
14663 )
14664 }),
14665 HoverLink::InlayHint(_, _) => None,
14666 HoverLink::Url(_) => None,
14667 HoverLink::File(_) => None,
14668 })
14669 .unwrap_or(tab_kind.to_string());
14670 let location_tasks = definitions
14671 .into_iter()
14672 .map(|definition| match definition {
14673 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14674 HoverLink::InlayHint(lsp_location, server_id) => editor
14675 .compute_target_location(lsp_location, server_id, window, cx),
14676 HoverLink::Url(_) => Task::ready(Ok(None)),
14677 HoverLink::File(_) => Task::ready(Ok(None)),
14678 })
14679 .collect::<Vec<_>>();
14680 (title, location_tasks, editor.workspace().clone())
14681 })
14682 .context("location tasks preparation")?;
14683
14684 let locations = future::join_all(location_tasks)
14685 .await
14686 .into_iter()
14687 .filter_map(|location| location.transpose())
14688 .collect::<Result<_>>()
14689 .context("location tasks")?;
14690
14691 let Some(workspace) = workspace else {
14692 return Ok(Navigated::No);
14693 };
14694 let opened = workspace
14695 .update_in(cx, |workspace, window, cx| {
14696 Self::open_locations_in_multibuffer(
14697 workspace,
14698 locations,
14699 title,
14700 split,
14701 MultibufferSelectionMode::First,
14702 window,
14703 cx,
14704 )
14705 })
14706 .ok();
14707
14708 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14709 })
14710 } else {
14711 Task::ready(Ok(Navigated::No))
14712 }
14713 }
14714
14715 fn compute_target_location(
14716 &self,
14717 lsp_location: lsp::Location,
14718 server_id: LanguageServerId,
14719 window: &mut Window,
14720 cx: &mut Context<Self>,
14721 ) -> Task<anyhow::Result<Option<Location>>> {
14722 let Some(project) = self.project.clone() else {
14723 return Task::ready(Ok(None));
14724 };
14725
14726 cx.spawn_in(window, async move |editor, cx| {
14727 let location_task = editor.update(cx, |_, cx| {
14728 project.update(cx, |project, cx| {
14729 let language_server_name = project
14730 .language_server_statuses(cx)
14731 .find(|(id, _)| server_id == *id)
14732 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14733 language_server_name.map(|language_server_name| {
14734 project.open_local_buffer_via_lsp(
14735 lsp_location.uri.clone(),
14736 server_id,
14737 language_server_name,
14738 cx,
14739 )
14740 })
14741 })
14742 })?;
14743 let location = match location_task {
14744 Some(task) => Some({
14745 let target_buffer_handle = task.await.context("open local buffer")?;
14746 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
14747 let target_start = target_buffer
14748 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14749 let target_end = target_buffer
14750 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14751 target_buffer.anchor_after(target_start)
14752 ..target_buffer.anchor_before(target_end)
14753 })?;
14754 Location {
14755 buffer: target_buffer_handle,
14756 range,
14757 }
14758 }),
14759 None => None,
14760 };
14761 Ok(location)
14762 })
14763 }
14764
14765 pub fn find_all_references(
14766 &mut self,
14767 _: &FindAllReferences,
14768 window: &mut Window,
14769 cx: &mut Context<Self>,
14770 ) -> Option<Task<Result<Navigated>>> {
14771 let selection = self.selections.newest::<usize>(cx);
14772 let multi_buffer = self.buffer.read(cx);
14773 let head = selection.head();
14774
14775 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14776 let head_anchor = multi_buffer_snapshot.anchor_at(
14777 head,
14778 if head < selection.tail() {
14779 Bias::Right
14780 } else {
14781 Bias::Left
14782 },
14783 );
14784
14785 match self
14786 .find_all_references_task_sources
14787 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14788 {
14789 Ok(_) => {
14790 log::info!(
14791 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14792 );
14793 return None;
14794 }
14795 Err(i) => {
14796 self.find_all_references_task_sources.insert(i, head_anchor);
14797 }
14798 }
14799
14800 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14801 let workspace = self.workspace()?;
14802 let project = workspace.read(cx).project().clone();
14803 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14804 Some(cx.spawn_in(window, async move |editor, cx| {
14805 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14806 if let Ok(i) = editor
14807 .find_all_references_task_sources
14808 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14809 {
14810 editor.find_all_references_task_sources.remove(i);
14811 }
14812 });
14813
14814 let locations = references.await?;
14815 if locations.is_empty() {
14816 return anyhow::Ok(Navigated::No);
14817 }
14818
14819 workspace.update_in(cx, |workspace, window, cx| {
14820 let title = locations
14821 .first()
14822 .as_ref()
14823 .map(|location| {
14824 let buffer = location.buffer.read(cx);
14825 format!(
14826 "References to `{}`",
14827 buffer
14828 .text_for_range(location.range.clone())
14829 .collect::<String>()
14830 )
14831 })
14832 .unwrap();
14833 Self::open_locations_in_multibuffer(
14834 workspace,
14835 locations,
14836 title,
14837 false,
14838 MultibufferSelectionMode::First,
14839 window,
14840 cx,
14841 );
14842 Navigated::Yes
14843 })
14844 }))
14845 }
14846
14847 /// Opens a multibuffer with the given project locations in it
14848 pub fn open_locations_in_multibuffer(
14849 workspace: &mut Workspace,
14850 mut locations: Vec<Location>,
14851 title: String,
14852 split: bool,
14853 multibuffer_selection_mode: MultibufferSelectionMode,
14854 window: &mut Window,
14855 cx: &mut Context<Workspace>,
14856 ) {
14857 // If there are multiple definitions, open them in a multibuffer
14858 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14859 let mut locations = locations.into_iter().peekable();
14860 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14861 let capability = workspace.project().read(cx).capability();
14862
14863 let excerpt_buffer = cx.new(|cx| {
14864 let mut multibuffer = MultiBuffer::new(capability);
14865 while let Some(location) = locations.next() {
14866 let buffer = location.buffer.read(cx);
14867 let mut ranges_for_buffer = Vec::new();
14868 let range = location.range.to_point(buffer);
14869 ranges_for_buffer.push(range.clone());
14870
14871 while let Some(next_location) = locations.peek() {
14872 if next_location.buffer == location.buffer {
14873 ranges_for_buffer.push(next_location.range.to_point(buffer));
14874 locations.next();
14875 } else {
14876 break;
14877 }
14878 }
14879
14880 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14881 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14882 PathKey::for_buffer(&location.buffer, cx),
14883 location.buffer.clone(),
14884 ranges_for_buffer,
14885 DEFAULT_MULTIBUFFER_CONTEXT,
14886 cx,
14887 );
14888 ranges.extend(new_ranges)
14889 }
14890
14891 multibuffer.with_title(title)
14892 });
14893
14894 let editor = cx.new(|cx| {
14895 Editor::for_multibuffer(
14896 excerpt_buffer,
14897 Some(workspace.project().clone()),
14898 window,
14899 cx,
14900 )
14901 });
14902 editor.update(cx, |editor, cx| {
14903 match multibuffer_selection_mode {
14904 MultibufferSelectionMode::First => {
14905 if let Some(first_range) = ranges.first() {
14906 editor.change_selections(None, window, cx, |selections| {
14907 selections.clear_disjoint();
14908 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14909 });
14910 }
14911 editor.highlight_background::<Self>(
14912 &ranges,
14913 |theme| theme.editor_highlighted_line_background,
14914 cx,
14915 );
14916 }
14917 MultibufferSelectionMode::All => {
14918 editor.change_selections(None, window, cx, |selections| {
14919 selections.clear_disjoint();
14920 selections.select_anchor_ranges(ranges);
14921 });
14922 }
14923 }
14924 editor.register_buffers_with_language_servers(cx);
14925 });
14926
14927 let item = Box::new(editor);
14928 let item_id = item.item_id();
14929
14930 if split {
14931 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14932 } else {
14933 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14934 let (preview_item_id, preview_item_idx) =
14935 workspace.active_pane().read_with(cx, |pane, _| {
14936 (pane.preview_item_id(), pane.preview_item_idx())
14937 });
14938
14939 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14940
14941 if let Some(preview_item_id) = preview_item_id {
14942 workspace.active_pane().update(cx, |pane, cx| {
14943 pane.remove_item(preview_item_id, false, false, window, cx);
14944 });
14945 }
14946 } else {
14947 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14948 }
14949 }
14950 workspace.active_pane().update(cx, |pane, cx| {
14951 pane.set_preview_item_id(Some(item_id), cx);
14952 });
14953 }
14954
14955 pub fn rename(
14956 &mut self,
14957 _: &Rename,
14958 window: &mut Window,
14959 cx: &mut Context<Self>,
14960 ) -> Option<Task<Result<()>>> {
14961 use language::ToOffset as _;
14962
14963 let provider = self.semantics_provider.clone()?;
14964 let selection = self.selections.newest_anchor().clone();
14965 let (cursor_buffer, cursor_buffer_position) = self
14966 .buffer
14967 .read(cx)
14968 .text_anchor_for_position(selection.head(), cx)?;
14969 let (tail_buffer, cursor_buffer_position_end) = self
14970 .buffer
14971 .read(cx)
14972 .text_anchor_for_position(selection.tail(), cx)?;
14973 if tail_buffer != cursor_buffer {
14974 return None;
14975 }
14976
14977 let snapshot = cursor_buffer.read(cx).snapshot();
14978 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14979 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14980 let prepare_rename = provider
14981 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14982 .unwrap_or_else(|| Task::ready(Ok(None)));
14983 drop(snapshot);
14984
14985 Some(cx.spawn_in(window, async move |this, cx| {
14986 let rename_range = if let Some(range) = prepare_rename.await? {
14987 Some(range)
14988 } else {
14989 this.update(cx, |this, cx| {
14990 let buffer = this.buffer.read(cx).snapshot(cx);
14991 let mut buffer_highlights = this
14992 .document_highlights_for_position(selection.head(), &buffer)
14993 .filter(|highlight| {
14994 highlight.start.excerpt_id == selection.head().excerpt_id
14995 && highlight.end.excerpt_id == selection.head().excerpt_id
14996 });
14997 buffer_highlights
14998 .next()
14999 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15000 })?
15001 };
15002 if let Some(rename_range) = rename_range {
15003 this.update_in(cx, |this, window, cx| {
15004 let snapshot = cursor_buffer.read(cx).snapshot();
15005 let rename_buffer_range = rename_range.to_offset(&snapshot);
15006 let cursor_offset_in_rename_range =
15007 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15008 let cursor_offset_in_rename_range_end =
15009 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15010
15011 this.take_rename(false, window, cx);
15012 let buffer = this.buffer.read(cx).read(cx);
15013 let cursor_offset = selection.head().to_offset(&buffer);
15014 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15015 let rename_end = rename_start + rename_buffer_range.len();
15016 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15017 let mut old_highlight_id = None;
15018 let old_name: Arc<str> = buffer
15019 .chunks(rename_start..rename_end, true)
15020 .map(|chunk| {
15021 if old_highlight_id.is_none() {
15022 old_highlight_id = chunk.syntax_highlight_id;
15023 }
15024 chunk.text
15025 })
15026 .collect::<String>()
15027 .into();
15028
15029 drop(buffer);
15030
15031 // Position the selection in the rename editor so that it matches the current selection.
15032 this.show_local_selections = false;
15033 let rename_editor = cx.new(|cx| {
15034 let mut editor = Editor::single_line(window, cx);
15035 editor.buffer.update(cx, |buffer, cx| {
15036 buffer.edit([(0..0, old_name.clone())], None, cx)
15037 });
15038 let rename_selection_range = match cursor_offset_in_rename_range
15039 .cmp(&cursor_offset_in_rename_range_end)
15040 {
15041 Ordering::Equal => {
15042 editor.select_all(&SelectAll, window, cx);
15043 return editor;
15044 }
15045 Ordering::Less => {
15046 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15047 }
15048 Ordering::Greater => {
15049 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15050 }
15051 };
15052 if rename_selection_range.end > old_name.len() {
15053 editor.select_all(&SelectAll, window, cx);
15054 } else {
15055 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15056 s.select_ranges([rename_selection_range]);
15057 });
15058 }
15059 editor
15060 });
15061 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15062 if e == &EditorEvent::Focused {
15063 cx.emit(EditorEvent::FocusedIn)
15064 }
15065 })
15066 .detach();
15067
15068 let write_highlights =
15069 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15070 let read_highlights =
15071 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15072 let ranges = write_highlights
15073 .iter()
15074 .flat_map(|(_, ranges)| ranges.iter())
15075 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15076 .cloned()
15077 .collect();
15078
15079 this.highlight_text::<Rename>(
15080 ranges,
15081 HighlightStyle {
15082 fade_out: Some(0.6),
15083 ..Default::default()
15084 },
15085 cx,
15086 );
15087 let rename_focus_handle = rename_editor.focus_handle(cx);
15088 window.focus(&rename_focus_handle);
15089 let block_id = this.insert_blocks(
15090 [BlockProperties {
15091 style: BlockStyle::Flex,
15092 placement: BlockPlacement::Below(range.start),
15093 height: Some(1),
15094 render: Arc::new({
15095 let rename_editor = rename_editor.clone();
15096 move |cx: &mut BlockContext| {
15097 let mut text_style = cx.editor_style.text.clone();
15098 if let Some(highlight_style) = old_highlight_id
15099 .and_then(|h| h.style(&cx.editor_style.syntax))
15100 {
15101 text_style = text_style.highlight(highlight_style);
15102 }
15103 div()
15104 .block_mouse_except_scroll()
15105 .pl(cx.anchor_x)
15106 .child(EditorElement::new(
15107 &rename_editor,
15108 EditorStyle {
15109 background: cx.theme().system().transparent,
15110 local_player: cx.editor_style.local_player,
15111 text: text_style,
15112 scrollbar_width: cx.editor_style.scrollbar_width,
15113 syntax: cx.editor_style.syntax.clone(),
15114 status: cx.editor_style.status.clone(),
15115 inlay_hints_style: HighlightStyle {
15116 font_weight: Some(FontWeight::BOLD),
15117 ..make_inlay_hints_style(cx.app)
15118 },
15119 inline_completion_styles: make_suggestion_styles(
15120 cx.app,
15121 ),
15122 ..EditorStyle::default()
15123 },
15124 ))
15125 .into_any_element()
15126 }
15127 }),
15128 priority: 0,
15129 render_in_minimap: true,
15130 }],
15131 Some(Autoscroll::fit()),
15132 cx,
15133 )[0];
15134 this.pending_rename = Some(RenameState {
15135 range,
15136 old_name,
15137 editor: rename_editor,
15138 block_id,
15139 });
15140 })?;
15141 }
15142
15143 Ok(())
15144 }))
15145 }
15146
15147 pub fn confirm_rename(
15148 &mut self,
15149 _: &ConfirmRename,
15150 window: &mut Window,
15151 cx: &mut Context<Self>,
15152 ) -> Option<Task<Result<()>>> {
15153 let rename = self.take_rename(false, window, cx)?;
15154 let workspace = self.workspace()?.downgrade();
15155 let (buffer, start) = self
15156 .buffer
15157 .read(cx)
15158 .text_anchor_for_position(rename.range.start, cx)?;
15159 let (end_buffer, _) = self
15160 .buffer
15161 .read(cx)
15162 .text_anchor_for_position(rename.range.end, cx)?;
15163 if buffer != end_buffer {
15164 return None;
15165 }
15166
15167 let old_name = rename.old_name;
15168 let new_name = rename.editor.read(cx).text(cx);
15169
15170 let rename = self.semantics_provider.as_ref()?.perform_rename(
15171 &buffer,
15172 start,
15173 new_name.clone(),
15174 cx,
15175 )?;
15176
15177 Some(cx.spawn_in(window, async move |editor, cx| {
15178 let project_transaction = rename.await?;
15179 Self::open_project_transaction(
15180 &editor,
15181 workspace,
15182 project_transaction,
15183 format!("Rename: {} → {}", old_name, new_name),
15184 cx,
15185 )
15186 .await?;
15187
15188 editor.update(cx, |editor, cx| {
15189 editor.refresh_document_highlights(cx);
15190 })?;
15191 Ok(())
15192 }))
15193 }
15194
15195 fn take_rename(
15196 &mut self,
15197 moving_cursor: bool,
15198 window: &mut Window,
15199 cx: &mut Context<Self>,
15200 ) -> Option<RenameState> {
15201 let rename = self.pending_rename.take()?;
15202 if rename.editor.focus_handle(cx).is_focused(window) {
15203 window.focus(&self.focus_handle);
15204 }
15205
15206 self.remove_blocks(
15207 [rename.block_id].into_iter().collect(),
15208 Some(Autoscroll::fit()),
15209 cx,
15210 );
15211 self.clear_highlights::<Rename>(cx);
15212 self.show_local_selections = true;
15213
15214 if moving_cursor {
15215 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15216 editor.selections.newest::<usize>(cx).head()
15217 });
15218
15219 // Update the selection to match the position of the selection inside
15220 // the rename editor.
15221 let snapshot = self.buffer.read(cx).read(cx);
15222 let rename_range = rename.range.to_offset(&snapshot);
15223 let cursor_in_editor = snapshot
15224 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15225 .min(rename_range.end);
15226 drop(snapshot);
15227
15228 self.change_selections(None, window, cx, |s| {
15229 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15230 });
15231 } else {
15232 self.refresh_document_highlights(cx);
15233 }
15234
15235 Some(rename)
15236 }
15237
15238 pub fn pending_rename(&self) -> Option<&RenameState> {
15239 self.pending_rename.as_ref()
15240 }
15241
15242 fn format(
15243 &mut self,
15244 _: &Format,
15245 window: &mut Window,
15246 cx: &mut Context<Self>,
15247 ) -> Option<Task<Result<()>>> {
15248 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15249
15250 let project = match &self.project {
15251 Some(project) => project.clone(),
15252 None => return None,
15253 };
15254
15255 Some(self.perform_format(
15256 project,
15257 FormatTrigger::Manual,
15258 FormatTarget::Buffers,
15259 window,
15260 cx,
15261 ))
15262 }
15263
15264 fn format_selections(
15265 &mut self,
15266 _: &FormatSelections,
15267 window: &mut Window,
15268 cx: &mut Context<Self>,
15269 ) -> Option<Task<Result<()>>> {
15270 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15271
15272 let project = match &self.project {
15273 Some(project) => project.clone(),
15274 None => return None,
15275 };
15276
15277 let ranges = self
15278 .selections
15279 .all_adjusted(cx)
15280 .into_iter()
15281 .map(|selection| selection.range())
15282 .collect_vec();
15283
15284 Some(self.perform_format(
15285 project,
15286 FormatTrigger::Manual,
15287 FormatTarget::Ranges(ranges),
15288 window,
15289 cx,
15290 ))
15291 }
15292
15293 fn perform_format(
15294 &mut self,
15295 project: Entity<Project>,
15296 trigger: FormatTrigger,
15297 target: FormatTarget,
15298 window: &mut Window,
15299 cx: &mut Context<Self>,
15300 ) -> Task<Result<()>> {
15301 let buffer = self.buffer.clone();
15302 let (buffers, target) = match target {
15303 FormatTarget::Buffers => {
15304 let mut buffers = buffer.read(cx).all_buffers();
15305 if trigger == FormatTrigger::Save {
15306 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15307 }
15308 (buffers, LspFormatTarget::Buffers)
15309 }
15310 FormatTarget::Ranges(selection_ranges) => {
15311 let multi_buffer = buffer.read(cx);
15312 let snapshot = multi_buffer.read(cx);
15313 let mut buffers = HashSet::default();
15314 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15315 BTreeMap::new();
15316 for selection_range in selection_ranges {
15317 for (buffer, buffer_range, _) in
15318 snapshot.range_to_buffer_ranges(selection_range)
15319 {
15320 let buffer_id = buffer.remote_id();
15321 let start = buffer.anchor_before(buffer_range.start);
15322 let end = buffer.anchor_after(buffer_range.end);
15323 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15324 buffer_id_to_ranges
15325 .entry(buffer_id)
15326 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15327 .or_insert_with(|| vec![start..end]);
15328 }
15329 }
15330 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15331 }
15332 };
15333
15334 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15335 let selections_prev = transaction_id_prev
15336 .and_then(|transaction_id_prev| {
15337 // default to selections as they were after the last edit, if we have them,
15338 // instead of how they are now.
15339 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15340 // will take you back to where you made the last edit, instead of staying where you scrolled
15341 self.selection_history
15342 .transaction(transaction_id_prev)
15343 .map(|t| t.0.clone())
15344 })
15345 .unwrap_or_else(|| {
15346 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15347 self.selections.disjoint_anchors()
15348 });
15349
15350 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15351 let format = project.update(cx, |project, cx| {
15352 project.format(buffers, target, true, trigger, cx)
15353 });
15354
15355 cx.spawn_in(window, async move |editor, cx| {
15356 let transaction = futures::select_biased! {
15357 transaction = format.log_err().fuse() => transaction,
15358 () = timeout => {
15359 log::warn!("timed out waiting for formatting");
15360 None
15361 }
15362 };
15363
15364 buffer
15365 .update(cx, |buffer, cx| {
15366 if let Some(transaction) = transaction {
15367 if !buffer.is_singleton() {
15368 buffer.push_transaction(&transaction.0, cx);
15369 }
15370 }
15371 cx.notify();
15372 })
15373 .ok();
15374
15375 if let Some(transaction_id_now) =
15376 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15377 {
15378 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15379 if has_new_transaction {
15380 _ = editor.update(cx, |editor, _| {
15381 editor
15382 .selection_history
15383 .insert_transaction(transaction_id_now, selections_prev);
15384 });
15385 }
15386 }
15387
15388 Ok(())
15389 })
15390 }
15391
15392 fn organize_imports(
15393 &mut self,
15394 _: &OrganizeImports,
15395 window: &mut Window,
15396 cx: &mut Context<Self>,
15397 ) -> Option<Task<Result<()>>> {
15398 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15399 let project = match &self.project {
15400 Some(project) => project.clone(),
15401 None => return None,
15402 };
15403 Some(self.perform_code_action_kind(
15404 project,
15405 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15406 window,
15407 cx,
15408 ))
15409 }
15410
15411 fn perform_code_action_kind(
15412 &mut self,
15413 project: Entity<Project>,
15414 kind: CodeActionKind,
15415 window: &mut Window,
15416 cx: &mut Context<Self>,
15417 ) -> Task<Result<()>> {
15418 let buffer = self.buffer.clone();
15419 let buffers = buffer.read(cx).all_buffers();
15420 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15421 let apply_action = project.update(cx, |project, cx| {
15422 project.apply_code_action_kind(buffers, kind, true, cx)
15423 });
15424 cx.spawn_in(window, async move |_, cx| {
15425 let transaction = futures::select_biased! {
15426 () = timeout => {
15427 log::warn!("timed out waiting for executing code action");
15428 None
15429 }
15430 transaction = apply_action.log_err().fuse() => transaction,
15431 };
15432 buffer
15433 .update(cx, |buffer, cx| {
15434 // check if we need this
15435 if let Some(transaction) = transaction {
15436 if !buffer.is_singleton() {
15437 buffer.push_transaction(&transaction.0, cx);
15438 }
15439 }
15440 cx.notify();
15441 })
15442 .ok();
15443 Ok(())
15444 })
15445 }
15446
15447 fn restart_language_server(
15448 &mut self,
15449 _: &RestartLanguageServer,
15450 _: &mut Window,
15451 cx: &mut Context<Self>,
15452 ) {
15453 if let Some(project) = self.project.clone() {
15454 self.buffer.update(cx, |multi_buffer, cx| {
15455 project.update(cx, |project, cx| {
15456 project.restart_language_servers_for_buffers(
15457 multi_buffer.all_buffers().into_iter().collect(),
15458 cx,
15459 );
15460 });
15461 })
15462 }
15463 }
15464
15465 fn stop_language_server(
15466 &mut self,
15467 _: &StopLanguageServer,
15468 _: &mut Window,
15469 cx: &mut Context<Self>,
15470 ) {
15471 if let Some(project) = self.project.clone() {
15472 self.buffer.update(cx, |multi_buffer, cx| {
15473 project.update(cx, |project, cx| {
15474 project.stop_language_servers_for_buffers(
15475 multi_buffer.all_buffers().into_iter().collect(),
15476 cx,
15477 );
15478 cx.emit(project::Event::RefreshInlayHints);
15479 });
15480 });
15481 }
15482 }
15483
15484 fn cancel_language_server_work(
15485 workspace: &mut Workspace,
15486 _: &actions::CancelLanguageServerWork,
15487 _: &mut Window,
15488 cx: &mut Context<Workspace>,
15489 ) {
15490 let project = workspace.project();
15491 let buffers = workspace
15492 .active_item(cx)
15493 .and_then(|item| item.act_as::<Editor>(cx))
15494 .map_or(HashSet::default(), |editor| {
15495 editor.read(cx).buffer.read(cx).all_buffers()
15496 });
15497 project.update(cx, |project, cx| {
15498 project.cancel_language_server_work_for_buffers(buffers, cx);
15499 });
15500 }
15501
15502 fn show_character_palette(
15503 &mut self,
15504 _: &ShowCharacterPalette,
15505 window: &mut Window,
15506 _: &mut Context<Self>,
15507 ) {
15508 window.show_character_palette();
15509 }
15510
15511 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15512 if self.mode.is_minimap() {
15513 return;
15514 }
15515
15516 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15517 let buffer = self.buffer.read(cx).snapshot(cx);
15518 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15519 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15520 let is_valid = buffer
15521 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15522 .any(|entry| {
15523 entry.diagnostic.is_primary
15524 && !entry.range.is_empty()
15525 && entry.range.start == primary_range_start
15526 && entry.diagnostic.message == active_diagnostics.active_message
15527 });
15528
15529 if !is_valid {
15530 self.dismiss_diagnostics(cx);
15531 }
15532 }
15533 }
15534
15535 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15536 match &self.active_diagnostics {
15537 ActiveDiagnostic::Group(group) => Some(group),
15538 _ => None,
15539 }
15540 }
15541
15542 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15543 self.dismiss_diagnostics(cx);
15544 self.active_diagnostics = ActiveDiagnostic::All;
15545 }
15546
15547 fn activate_diagnostics(
15548 &mut self,
15549 buffer_id: BufferId,
15550 diagnostic: DiagnosticEntry<usize>,
15551 window: &mut Window,
15552 cx: &mut Context<Self>,
15553 ) {
15554 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15555 return;
15556 }
15557 self.dismiss_diagnostics(cx);
15558 let snapshot = self.snapshot(window, cx);
15559 let buffer = self.buffer.read(cx).snapshot(cx);
15560 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15561 return;
15562 };
15563
15564 let diagnostic_group = buffer
15565 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15566 .collect::<Vec<_>>();
15567
15568 let blocks =
15569 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15570
15571 let blocks = self.display_map.update(cx, |display_map, cx| {
15572 display_map.insert_blocks(blocks, cx).into_iter().collect()
15573 });
15574 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15575 active_range: buffer.anchor_before(diagnostic.range.start)
15576 ..buffer.anchor_after(diagnostic.range.end),
15577 active_message: diagnostic.diagnostic.message.clone(),
15578 group_id: diagnostic.diagnostic.group_id,
15579 blocks,
15580 });
15581 cx.notify();
15582 }
15583
15584 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15585 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15586 return;
15587 };
15588
15589 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15590 if let ActiveDiagnostic::Group(group) = prev {
15591 self.display_map.update(cx, |display_map, cx| {
15592 display_map.remove_blocks(group.blocks, cx);
15593 });
15594 cx.notify();
15595 }
15596 }
15597
15598 /// Disable inline diagnostics rendering for this editor.
15599 pub fn disable_inline_diagnostics(&mut self) {
15600 self.inline_diagnostics_enabled = false;
15601 self.inline_diagnostics_update = Task::ready(());
15602 self.inline_diagnostics.clear();
15603 }
15604
15605 pub fn diagnostics_enabled(&self) -> bool {
15606 self.mode.is_full()
15607 }
15608
15609 pub fn inline_diagnostics_enabled(&self) -> bool {
15610 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15611 }
15612
15613 pub fn show_inline_diagnostics(&self) -> bool {
15614 self.show_inline_diagnostics
15615 }
15616
15617 pub fn toggle_inline_diagnostics(
15618 &mut self,
15619 _: &ToggleInlineDiagnostics,
15620 window: &mut Window,
15621 cx: &mut Context<Editor>,
15622 ) {
15623 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15624 self.refresh_inline_diagnostics(false, window, cx);
15625 }
15626
15627 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15628 self.diagnostics_max_severity = severity;
15629 self.display_map.update(cx, |display_map, _| {
15630 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15631 });
15632 }
15633
15634 pub fn toggle_diagnostics(
15635 &mut self,
15636 _: &ToggleDiagnostics,
15637 window: &mut Window,
15638 cx: &mut Context<Editor>,
15639 ) {
15640 if !self.diagnostics_enabled() {
15641 return;
15642 }
15643
15644 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15645 EditorSettings::get_global(cx)
15646 .diagnostics_max_severity
15647 .filter(|severity| severity != &DiagnosticSeverity::Off)
15648 .unwrap_or(DiagnosticSeverity::Hint)
15649 } else {
15650 DiagnosticSeverity::Off
15651 };
15652 self.set_max_diagnostics_severity(new_severity, cx);
15653 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15654 self.active_diagnostics = ActiveDiagnostic::None;
15655 self.inline_diagnostics_update = Task::ready(());
15656 self.inline_diagnostics.clear();
15657 } else {
15658 self.refresh_inline_diagnostics(false, window, cx);
15659 }
15660
15661 cx.notify();
15662 }
15663
15664 pub fn toggle_minimap(
15665 &mut self,
15666 _: &ToggleMinimap,
15667 window: &mut Window,
15668 cx: &mut Context<Editor>,
15669 ) {
15670 if self.supports_minimap(cx) {
15671 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15672 }
15673 }
15674
15675 fn refresh_inline_diagnostics(
15676 &mut self,
15677 debounce: bool,
15678 window: &mut Window,
15679 cx: &mut Context<Self>,
15680 ) {
15681 let max_severity = ProjectSettings::get_global(cx)
15682 .diagnostics
15683 .inline
15684 .max_severity
15685 .unwrap_or(self.diagnostics_max_severity);
15686
15687 if !self.inline_diagnostics_enabled()
15688 || !self.show_inline_diagnostics
15689 || max_severity == DiagnosticSeverity::Off
15690 {
15691 self.inline_diagnostics_update = Task::ready(());
15692 self.inline_diagnostics.clear();
15693 return;
15694 }
15695
15696 let debounce_ms = ProjectSettings::get_global(cx)
15697 .diagnostics
15698 .inline
15699 .update_debounce_ms;
15700 let debounce = if debounce && debounce_ms > 0 {
15701 Some(Duration::from_millis(debounce_ms))
15702 } else {
15703 None
15704 };
15705 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15706 let editor = editor.upgrade().unwrap();
15707
15708 if let Some(debounce) = debounce {
15709 cx.background_executor().timer(debounce).await;
15710 }
15711 let Some(snapshot) = editor
15712 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15713 .ok()
15714 else {
15715 return;
15716 };
15717
15718 let new_inline_diagnostics = cx
15719 .background_spawn(async move {
15720 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15721 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15722 let message = diagnostic_entry
15723 .diagnostic
15724 .message
15725 .split_once('\n')
15726 .map(|(line, _)| line)
15727 .map(SharedString::new)
15728 .unwrap_or_else(|| {
15729 SharedString::from(diagnostic_entry.diagnostic.message)
15730 });
15731 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15732 let (Ok(i) | Err(i)) = inline_diagnostics
15733 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15734 inline_diagnostics.insert(
15735 i,
15736 (
15737 start_anchor,
15738 InlineDiagnostic {
15739 message,
15740 group_id: diagnostic_entry.diagnostic.group_id,
15741 start: diagnostic_entry.range.start.to_point(&snapshot),
15742 is_primary: diagnostic_entry.diagnostic.is_primary,
15743 severity: diagnostic_entry.diagnostic.severity,
15744 },
15745 ),
15746 );
15747 }
15748 inline_diagnostics
15749 })
15750 .await;
15751
15752 editor
15753 .update(cx, |editor, cx| {
15754 editor.inline_diagnostics = new_inline_diagnostics;
15755 cx.notify();
15756 })
15757 .ok();
15758 });
15759 }
15760
15761 pub fn set_selections_from_remote(
15762 &mut self,
15763 selections: Vec<Selection<Anchor>>,
15764 pending_selection: Option<Selection<Anchor>>,
15765 window: &mut Window,
15766 cx: &mut Context<Self>,
15767 ) {
15768 let old_cursor_position = self.selections.newest_anchor().head();
15769 self.selections.change_with(cx, |s| {
15770 s.select_anchors(selections);
15771 if let Some(pending_selection) = pending_selection {
15772 s.set_pending(pending_selection, SelectMode::Character);
15773 } else {
15774 s.clear_pending();
15775 }
15776 });
15777 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15778 }
15779
15780 pub fn transact(
15781 &mut self,
15782 window: &mut Window,
15783 cx: &mut Context<Self>,
15784 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15785 ) -> Option<TransactionId> {
15786 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15787 this.start_transaction_at(Instant::now(), window, cx);
15788 update(this, window, cx);
15789 this.end_transaction_at(Instant::now(), cx)
15790 })
15791 }
15792
15793 pub fn start_transaction_at(
15794 &mut self,
15795 now: Instant,
15796 window: &mut Window,
15797 cx: &mut Context<Self>,
15798 ) {
15799 self.end_selection(window, cx);
15800 if let Some(tx_id) = self
15801 .buffer
15802 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15803 {
15804 self.selection_history
15805 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15806 cx.emit(EditorEvent::TransactionBegun {
15807 transaction_id: tx_id,
15808 })
15809 }
15810 }
15811
15812 pub fn end_transaction_at(
15813 &mut self,
15814 now: Instant,
15815 cx: &mut Context<Self>,
15816 ) -> Option<TransactionId> {
15817 if let Some(transaction_id) = self
15818 .buffer
15819 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15820 {
15821 if let Some((_, end_selections)) =
15822 self.selection_history.transaction_mut(transaction_id)
15823 {
15824 *end_selections = Some(self.selections.disjoint_anchors());
15825 } else {
15826 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15827 }
15828
15829 cx.emit(EditorEvent::Edited { transaction_id });
15830 Some(transaction_id)
15831 } else {
15832 None
15833 }
15834 }
15835
15836 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15837 if self.selection_mark_mode {
15838 self.change_selections(None, window, cx, |s| {
15839 s.move_with(|_, sel| {
15840 sel.collapse_to(sel.head(), SelectionGoal::None);
15841 });
15842 })
15843 }
15844 self.selection_mark_mode = true;
15845 cx.notify();
15846 }
15847
15848 pub fn swap_selection_ends(
15849 &mut self,
15850 _: &actions::SwapSelectionEnds,
15851 window: &mut Window,
15852 cx: &mut Context<Self>,
15853 ) {
15854 self.change_selections(None, window, cx, |s| {
15855 s.move_with(|_, sel| {
15856 if sel.start != sel.end {
15857 sel.reversed = !sel.reversed
15858 }
15859 });
15860 });
15861 self.request_autoscroll(Autoscroll::newest(), cx);
15862 cx.notify();
15863 }
15864
15865 pub fn toggle_fold(
15866 &mut self,
15867 _: &actions::ToggleFold,
15868 window: &mut Window,
15869 cx: &mut Context<Self>,
15870 ) {
15871 if self.is_singleton(cx) {
15872 let selection = self.selections.newest::<Point>(cx);
15873
15874 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15875 let range = if selection.is_empty() {
15876 let point = selection.head().to_display_point(&display_map);
15877 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15878 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15879 .to_point(&display_map);
15880 start..end
15881 } else {
15882 selection.range()
15883 };
15884 if display_map.folds_in_range(range).next().is_some() {
15885 self.unfold_lines(&Default::default(), window, cx)
15886 } else {
15887 self.fold(&Default::default(), window, cx)
15888 }
15889 } else {
15890 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15891 let buffer_ids: HashSet<_> = self
15892 .selections
15893 .disjoint_anchor_ranges()
15894 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15895 .collect();
15896
15897 let should_unfold = buffer_ids
15898 .iter()
15899 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15900
15901 for buffer_id in buffer_ids {
15902 if should_unfold {
15903 self.unfold_buffer(buffer_id, cx);
15904 } else {
15905 self.fold_buffer(buffer_id, cx);
15906 }
15907 }
15908 }
15909 }
15910
15911 pub fn toggle_fold_recursive(
15912 &mut self,
15913 _: &actions::ToggleFoldRecursive,
15914 window: &mut Window,
15915 cx: &mut Context<Self>,
15916 ) {
15917 let selection = self.selections.newest::<Point>(cx);
15918
15919 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15920 let range = if selection.is_empty() {
15921 let point = selection.head().to_display_point(&display_map);
15922 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15923 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15924 .to_point(&display_map);
15925 start..end
15926 } else {
15927 selection.range()
15928 };
15929 if display_map.folds_in_range(range).next().is_some() {
15930 self.unfold_recursive(&Default::default(), window, cx)
15931 } else {
15932 self.fold_recursive(&Default::default(), window, cx)
15933 }
15934 }
15935
15936 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15937 if self.is_singleton(cx) {
15938 let mut to_fold = Vec::new();
15939 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15940 let selections = self.selections.all_adjusted(cx);
15941
15942 for selection in selections {
15943 let range = selection.range().sorted();
15944 let buffer_start_row = range.start.row;
15945
15946 if range.start.row != range.end.row {
15947 let mut found = false;
15948 let mut row = range.start.row;
15949 while row <= range.end.row {
15950 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15951 {
15952 found = true;
15953 row = crease.range().end.row + 1;
15954 to_fold.push(crease);
15955 } else {
15956 row += 1
15957 }
15958 }
15959 if found {
15960 continue;
15961 }
15962 }
15963
15964 for row in (0..=range.start.row).rev() {
15965 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15966 if crease.range().end.row >= buffer_start_row {
15967 to_fold.push(crease);
15968 if row <= range.start.row {
15969 break;
15970 }
15971 }
15972 }
15973 }
15974 }
15975
15976 self.fold_creases(to_fold, true, window, cx);
15977 } else {
15978 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15979 let buffer_ids = self
15980 .selections
15981 .disjoint_anchor_ranges()
15982 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15983 .collect::<HashSet<_>>();
15984 for buffer_id in buffer_ids {
15985 self.fold_buffer(buffer_id, cx);
15986 }
15987 }
15988 }
15989
15990 fn fold_at_level(
15991 &mut self,
15992 fold_at: &FoldAtLevel,
15993 window: &mut Window,
15994 cx: &mut Context<Self>,
15995 ) {
15996 if !self.buffer.read(cx).is_singleton() {
15997 return;
15998 }
15999
16000 let fold_at_level = fold_at.0;
16001 let snapshot = self.buffer.read(cx).snapshot(cx);
16002 let mut to_fold = Vec::new();
16003 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16004
16005 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16006 while start_row < end_row {
16007 match self
16008 .snapshot(window, cx)
16009 .crease_for_buffer_row(MultiBufferRow(start_row))
16010 {
16011 Some(crease) => {
16012 let nested_start_row = crease.range().start.row + 1;
16013 let nested_end_row = crease.range().end.row;
16014
16015 if current_level < fold_at_level {
16016 stack.push((nested_start_row, nested_end_row, current_level + 1));
16017 } else if current_level == fold_at_level {
16018 to_fold.push(crease);
16019 }
16020
16021 start_row = nested_end_row + 1;
16022 }
16023 None => start_row += 1,
16024 }
16025 }
16026 }
16027
16028 self.fold_creases(to_fold, true, window, cx);
16029 }
16030
16031 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16032 if self.buffer.read(cx).is_singleton() {
16033 let mut fold_ranges = Vec::new();
16034 let snapshot = self.buffer.read(cx).snapshot(cx);
16035
16036 for row in 0..snapshot.max_row().0 {
16037 if let Some(foldable_range) = self
16038 .snapshot(window, cx)
16039 .crease_for_buffer_row(MultiBufferRow(row))
16040 {
16041 fold_ranges.push(foldable_range);
16042 }
16043 }
16044
16045 self.fold_creases(fold_ranges, true, window, cx);
16046 } else {
16047 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16048 editor
16049 .update_in(cx, |editor, _, cx| {
16050 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16051 editor.fold_buffer(buffer_id, cx);
16052 }
16053 })
16054 .ok();
16055 });
16056 }
16057 }
16058
16059 pub fn fold_function_bodies(
16060 &mut self,
16061 _: &actions::FoldFunctionBodies,
16062 window: &mut Window,
16063 cx: &mut Context<Self>,
16064 ) {
16065 let snapshot = self.buffer.read(cx).snapshot(cx);
16066
16067 let ranges = snapshot
16068 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16069 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16070 .collect::<Vec<_>>();
16071
16072 let creases = ranges
16073 .into_iter()
16074 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16075 .collect();
16076
16077 self.fold_creases(creases, true, window, cx);
16078 }
16079
16080 pub fn fold_recursive(
16081 &mut self,
16082 _: &actions::FoldRecursive,
16083 window: &mut Window,
16084 cx: &mut Context<Self>,
16085 ) {
16086 let mut to_fold = Vec::new();
16087 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16088 let selections = self.selections.all_adjusted(cx);
16089
16090 for selection in selections {
16091 let range = selection.range().sorted();
16092 let buffer_start_row = range.start.row;
16093
16094 if range.start.row != range.end.row {
16095 let mut found = false;
16096 for row in range.start.row..=range.end.row {
16097 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16098 found = true;
16099 to_fold.push(crease);
16100 }
16101 }
16102 if found {
16103 continue;
16104 }
16105 }
16106
16107 for row in (0..=range.start.row).rev() {
16108 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16109 if crease.range().end.row >= buffer_start_row {
16110 to_fold.push(crease);
16111 } else {
16112 break;
16113 }
16114 }
16115 }
16116 }
16117
16118 self.fold_creases(to_fold, true, window, cx);
16119 }
16120
16121 pub fn fold_at(
16122 &mut self,
16123 buffer_row: MultiBufferRow,
16124 window: &mut Window,
16125 cx: &mut Context<Self>,
16126 ) {
16127 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16128
16129 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16130 let autoscroll = self
16131 .selections
16132 .all::<Point>(cx)
16133 .iter()
16134 .any(|selection| crease.range().overlaps(&selection.range()));
16135
16136 self.fold_creases(vec![crease], autoscroll, window, cx);
16137 }
16138 }
16139
16140 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16141 if self.is_singleton(cx) {
16142 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16143 let buffer = &display_map.buffer_snapshot;
16144 let selections = self.selections.all::<Point>(cx);
16145 let ranges = selections
16146 .iter()
16147 .map(|s| {
16148 let range = s.display_range(&display_map).sorted();
16149 let mut start = range.start.to_point(&display_map);
16150 let mut end = range.end.to_point(&display_map);
16151 start.column = 0;
16152 end.column = buffer.line_len(MultiBufferRow(end.row));
16153 start..end
16154 })
16155 .collect::<Vec<_>>();
16156
16157 self.unfold_ranges(&ranges, true, true, cx);
16158 } else {
16159 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16160 let buffer_ids = self
16161 .selections
16162 .disjoint_anchor_ranges()
16163 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16164 .collect::<HashSet<_>>();
16165 for buffer_id in buffer_ids {
16166 self.unfold_buffer(buffer_id, cx);
16167 }
16168 }
16169 }
16170
16171 pub fn unfold_recursive(
16172 &mut self,
16173 _: &UnfoldRecursive,
16174 _window: &mut Window,
16175 cx: &mut Context<Self>,
16176 ) {
16177 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16178 let selections = self.selections.all::<Point>(cx);
16179 let ranges = selections
16180 .iter()
16181 .map(|s| {
16182 let mut range = s.display_range(&display_map).sorted();
16183 *range.start.column_mut() = 0;
16184 *range.end.column_mut() = display_map.line_len(range.end.row());
16185 let start = range.start.to_point(&display_map);
16186 let end = range.end.to_point(&display_map);
16187 start..end
16188 })
16189 .collect::<Vec<_>>();
16190
16191 self.unfold_ranges(&ranges, true, true, cx);
16192 }
16193
16194 pub fn unfold_at(
16195 &mut self,
16196 buffer_row: MultiBufferRow,
16197 _window: &mut Window,
16198 cx: &mut Context<Self>,
16199 ) {
16200 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16201
16202 let intersection_range = Point::new(buffer_row.0, 0)
16203 ..Point::new(
16204 buffer_row.0,
16205 display_map.buffer_snapshot.line_len(buffer_row),
16206 );
16207
16208 let autoscroll = self
16209 .selections
16210 .all::<Point>(cx)
16211 .iter()
16212 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16213
16214 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16215 }
16216
16217 pub fn unfold_all(
16218 &mut self,
16219 _: &actions::UnfoldAll,
16220 _window: &mut Window,
16221 cx: &mut Context<Self>,
16222 ) {
16223 if self.buffer.read(cx).is_singleton() {
16224 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16225 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16226 } else {
16227 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16228 editor
16229 .update(cx, |editor, cx| {
16230 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16231 editor.unfold_buffer(buffer_id, cx);
16232 }
16233 })
16234 .ok();
16235 });
16236 }
16237 }
16238
16239 pub fn fold_selected_ranges(
16240 &mut self,
16241 _: &FoldSelectedRanges,
16242 window: &mut Window,
16243 cx: &mut Context<Self>,
16244 ) {
16245 let selections = self.selections.all_adjusted(cx);
16246 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16247 let ranges = selections
16248 .into_iter()
16249 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16250 .collect::<Vec<_>>();
16251 self.fold_creases(ranges, true, window, cx);
16252 }
16253
16254 pub fn fold_ranges<T: ToOffset + Clone>(
16255 &mut self,
16256 ranges: Vec<Range<T>>,
16257 auto_scroll: bool,
16258 window: &mut Window,
16259 cx: &mut Context<Self>,
16260 ) {
16261 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16262 let ranges = ranges
16263 .into_iter()
16264 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16265 .collect::<Vec<_>>();
16266 self.fold_creases(ranges, auto_scroll, window, cx);
16267 }
16268
16269 pub fn fold_creases<T: ToOffset + Clone>(
16270 &mut self,
16271 creases: Vec<Crease<T>>,
16272 auto_scroll: bool,
16273 _window: &mut Window,
16274 cx: &mut Context<Self>,
16275 ) {
16276 if creases.is_empty() {
16277 return;
16278 }
16279
16280 let mut buffers_affected = HashSet::default();
16281 let multi_buffer = self.buffer().read(cx);
16282 for crease in &creases {
16283 if let Some((_, buffer, _)) =
16284 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16285 {
16286 buffers_affected.insert(buffer.read(cx).remote_id());
16287 };
16288 }
16289
16290 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16291
16292 if auto_scroll {
16293 self.request_autoscroll(Autoscroll::fit(), cx);
16294 }
16295
16296 cx.notify();
16297
16298 self.scrollbar_marker_state.dirty = true;
16299 self.folds_did_change(cx);
16300 }
16301
16302 /// Removes any folds whose ranges intersect any of the given ranges.
16303 pub fn unfold_ranges<T: ToOffset + Clone>(
16304 &mut self,
16305 ranges: &[Range<T>],
16306 inclusive: bool,
16307 auto_scroll: bool,
16308 cx: &mut Context<Self>,
16309 ) {
16310 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16311 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16312 });
16313 self.folds_did_change(cx);
16314 }
16315
16316 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16317 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16318 return;
16319 }
16320 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16321 self.display_map.update(cx, |display_map, cx| {
16322 display_map.fold_buffers([buffer_id], cx)
16323 });
16324 cx.emit(EditorEvent::BufferFoldToggled {
16325 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16326 folded: true,
16327 });
16328 cx.notify();
16329 }
16330
16331 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16332 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16333 return;
16334 }
16335 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16336 self.display_map.update(cx, |display_map, cx| {
16337 display_map.unfold_buffers([buffer_id], cx);
16338 });
16339 cx.emit(EditorEvent::BufferFoldToggled {
16340 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16341 folded: false,
16342 });
16343 cx.notify();
16344 }
16345
16346 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16347 self.display_map.read(cx).is_buffer_folded(buffer)
16348 }
16349
16350 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16351 self.display_map.read(cx).folded_buffers()
16352 }
16353
16354 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16355 self.display_map.update(cx, |display_map, cx| {
16356 display_map.disable_header_for_buffer(buffer_id, cx);
16357 });
16358 cx.notify();
16359 }
16360
16361 /// Removes any folds with the given ranges.
16362 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16363 &mut self,
16364 ranges: &[Range<T>],
16365 type_id: TypeId,
16366 auto_scroll: bool,
16367 cx: &mut Context<Self>,
16368 ) {
16369 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16370 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16371 });
16372 self.folds_did_change(cx);
16373 }
16374
16375 fn remove_folds_with<T: ToOffset + Clone>(
16376 &mut self,
16377 ranges: &[Range<T>],
16378 auto_scroll: bool,
16379 cx: &mut Context<Self>,
16380 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16381 ) {
16382 if ranges.is_empty() {
16383 return;
16384 }
16385
16386 let mut buffers_affected = HashSet::default();
16387 let multi_buffer = self.buffer().read(cx);
16388 for range in ranges {
16389 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16390 buffers_affected.insert(buffer.read(cx).remote_id());
16391 };
16392 }
16393
16394 self.display_map.update(cx, update);
16395
16396 if auto_scroll {
16397 self.request_autoscroll(Autoscroll::fit(), cx);
16398 }
16399
16400 cx.notify();
16401 self.scrollbar_marker_state.dirty = true;
16402 self.active_indent_guides_state.dirty = true;
16403 }
16404
16405 pub fn update_fold_widths(
16406 &mut self,
16407 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16408 cx: &mut Context<Self>,
16409 ) -> bool {
16410 self.display_map
16411 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16412 }
16413
16414 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16415 self.display_map.read(cx).fold_placeholder.clone()
16416 }
16417
16418 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16419 self.buffer.update(cx, |buffer, cx| {
16420 buffer.set_all_diff_hunks_expanded(cx);
16421 });
16422 }
16423
16424 pub fn expand_all_diff_hunks(
16425 &mut self,
16426 _: &ExpandAllDiffHunks,
16427 _window: &mut Window,
16428 cx: &mut Context<Self>,
16429 ) {
16430 self.buffer.update(cx, |buffer, cx| {
16431 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16432 });
16433 }
16434
16435 pub fn toggle_selected_diff_hunks(
16436 &mut self,
16437 _: &ToggleSelectedDiffHunks,
16438 _window: &mut Window,
16439 cx: &mut Context<Self>,
16440 ) {
16441 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16442 self.toggle_diff_hunks_in_ranges(ranges, cx);
16443 }
16444
16445 pub fn diff_hunks_in_ranges<'a>(
16446 &'a self,
16447 ranges: &'a [Range<Anchor>],
16448 buffer: &'a MultiBufferSnapshot,
16449 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16450 ranges.iter().flat_map(move |range| {
16451 let end_excerpt_id = range.end.excerpt_id;
16452 let range = range.to_point(buffer);
16453 let mut peek_end = range.end;
16454 if range.end.row < buffer.max_row().0 {
16455 peek_end = Point::new(range.end.row + 1, 0);
16456 }
16457 buffer
16458 .diff_hunks_in_range(range.start..peek_end)
16459 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16460 })
16461 }
16462
16463 pub fn has_stageable_diff_hunks_in_ranges(
16464 &self,
16465 ranges: &[Range<Anchor>],
16466 snapshot: &MultiBufferSnapshot,
16467 ) -> bool {
16468 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16469 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16470 }
16471
16472 pub fn toggle_staged_selected_diff_hunks(
16473 &mut self,
16474 _: &::git::ToggleStaged,
16475 _: &mut Window,
16476 cx: &mut Context<Self>,
16477 ) {
16478 let snapshot = self.buffer.read(cx).snapshot(cx);
16479 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16480 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16481 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16482 }
16483
16484 pub fn set_render_diff_hunk_controls(
16485 &mut self,
16486 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16487 cx: &mut Context<Self>,
16488 ) {
16489 self.render_diff_hunk_controls = render_diff_hunk_controls;
16490 cx.notify();
16491 }
16492
16493 pub fn stage_and_next(
16494 &mut self,
16495 _: &::git::StageAndNext,
16496 window: &mut Window,
16497 cx: &mut Context<Self>,
16498 ) {
16499 self.do_stage_or_unstage_and_next(true, window, cx);
16500 }
16501
16502 pub fn unstage_and_next(
16503 &mut self,
16504 _: &::git::UnstageAndNext,
16505 window: &mut Window,
16506 cx: &mut Context<Self>,
16507 ) {
16508 self.do_stage_or_unstage_and_next(false, window, cx);
16509 }
16510
16511 pub fn stage_or_unstage_diff_hunks(
16512 &mut self,
16513 stage: bool,
16514 ranges: Vec<Range<Anchor>>,
16515 cx: &mut Context<Self>,
16516 ) {
16517 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16518 cx.spawn(async move |this, cx| {
16519 task.await?;
16520 this.update(cx, |this, cx| {
16521 let snapshot = this.buffer.read(cx).snapshot(cx);
16522 let chunk_by = this
16523 .diff_hunks_in_ranges(&ranges, &snapshot)
16524 .chunk_by(|hunk| hunk.buffer_id);
16525 for (buffer_id, hunks) in &chunk_by {
16526 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16527 }
16528 })
16529 })
16530 .detach_and_log_err(cx);
16531 }
16532
16533 fn save_buffers_for_ranges_if_needed(
16534 &mut self,
16535 ranges: &[Range<Anchor>],
16536 cx: &mut Context<Editor>,
16537 ) -> Task<Result<()>> {
16538 let multibuffer = self.buffer.read(cx);
16539 let snapshot = multibuffer.read(cx);
16540 let buffer_ids: HashSet<_> = ranges
16541 .iter()
16542 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16543 .collect();
16544 drop(snapshot);
16545
16546 let mut buffers = HashSet::default();
16547 for buffer_id in buffer_ids {
16548 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16549 let buffer = buffer_entity.read(cx);
16550 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16551 {
16552 buffers.insert(buffer_entity);
16553 }
16554 }
16555 }
16556
16557 if let Some(project) = &self.project {
16558 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16559 } else {
16560 Task::ready(Ok(()))
16561 }
16562 }
16563
16564 fn do_stage_or_unstage_and_next(
16565 &mut self,
16566 stage: bool,
16567 window: &mut Window,
16568 cx: &mut Context<Self>,
16569 ) {
16570 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16571
16572 if ranges.iter().any(|range| range.start != range.end) {
16573 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16574 return;
16575 }
16576
16577 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16578 let snapshot = self.snapshot(window, cx);
16579 let position = self.selections.newest::<Point>(cx).head();
16580 let mut row = snapshot
16581 .buffer_snapshot
16582 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16583 .find(|hunk| hunk.row_range.start.0 > position.row)
16584 .map(|hunk| hunk.row_range.start);
16585
16586 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16587 // Outside of the project diff editor, wrap around to the beginning.
16588 if !all_diff_hunks_expanded {
16589 row = row.or_else(|| {
16590 snapshot
16591 .buffer_snapshot
16592 .diff_hunks_in_range(Point::zero()..position)
16593 .find(|hunk| hunk.row_range.end.0 < position.row)
16594 .map(|hunk| hunk.row_range.start)
16595 });
16596 }
16597
16598 if let Some(row) = row {
16599 let destination = Point::new(row.0, 0);
16600 let autoscroll = Autoscroll::center();
16601
16602 self.unfold_ranges(&[destination..destination], false, false, cx);
16603 self.change_selections(Some(autoscroll), window, cx, |s| {
16604 s.select_ranges([destination..destination]);
16605 });
16606 }
16607 }
16608
16609 fn do_stage_or_unstage(
16610 &self,
16611 stage: bool,
16612 buffer_id: BufferId,
16613 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16614 cx: &mut App,
16615 ) -> Option<()> {
16616 let project = self.project.as_ref()?;
16617 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16618 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16619 let buffer_snapshot = buffer.read(cx).snapshot();
16620 let file_exists = buffer_snapshot
16621 .file()
16622 .is_some_and(|file| file.disk_state().exists());
16623 diff.update(cx, |diff, cx| {
16624 diff.stage_or_unstage_hunks(
16625 stage,
16626 &hunks
16627 .map(|hunk| buffer_diff::DiffHunk {
16628 buffer_range: hunk.buffer_range,
16629 diff_base_byte_range: hunk.diff_base_byte_range,
16630 secondary_status: hunk.secondary_status,
16631 range: Point::zero()..Point::zero(), // unused
16632 })
16633 .collect::<Vec<_>>(),
16634 &buffer_snapshot,
16635 file_exists,
16636 cx,
16637 )
16638 });
16639 None
16640 }
16641
16642 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16643 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16644 self.buffer
16645 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16646 }
16647
16648 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16649 self.buffer.update(cx, |buffer, cx| {
16650 let ranges = vec![Anchor::min()..Anchor::max()];
16651 if !buffer.all_diff_hunks_expanded()
16652 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16653 {
16654 buffer.collapse_diff_hunks(ranges, cx);
16655 true
16656 } else {
16657 false
16658 }
16659 })
16660 }
16661
16662 fn toggle_diff_hunks_in_ranges(
16663 &mut self,
16664 ranges: Vec<Range<Anchor>>,
16665 cx: &mut Context<Editor>,
16666 ) {
16667 self.buffer.update(cx, |buffer, cx| {
16668 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16669 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16670 })
16671 }
16672
16673 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16674 self.buffer.update(cx, |buffer, cx| {
16675 let snapshot = buffer.snapshot(cx);
16676 let excerpt_id = range.end.excerpt_id;
16677 let point_range = range.to_point(&snapshot);
16678 let expand = !buffer.single_hunk_is_expanded(range, cx);
16679 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16680 })
16681 }
16682
16683 pub(crate) fn apply_all_diff_hunks(
16684 &mut self,
16685 _: &ApplyAllDiffHunks,
16686 window: &mut Window,
16687 cx: &mut Context<Self>,
16688 ) {
16689 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16690
16691 let buffers = self.buffer.read(cx).all_buffers();
16692 for branch_buffer in buffers {
16693 branch_buffer.update(cx, |branch_buffer, cx| {
16694 branch_buffer.merge_into_base(Vec::new(), cx);
16695 });
16696 }
16697
16698 if let Some(project) = self.project.clone() {
16699 self.save(true, project, window, cx).detach_and_log_err(cx);
16700 }
16701 }
16702
16703 pub(crate) fn apply_selected_diff_hunks(
16704 &mut self,
16705 _: &ApplyDiffHunk,
16706 window: &mut Window,
16707 cx: &mut Context<Self>,
16708 ) {
16709 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16710 let snapshot = self.snapshot(window, cx);
16711 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16712 let mut ranges_by_buffer = HashMap::default();
16713 self.transact(window, cx, |editor, _window, cx| {
16714 for hunk in hunks {
16715 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16716 ranges_by_buffer
16717 .entry(buffer.clone())
16718 .or_insert_with(Vec::new)
16719 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16720 }
16721 }
16722
16723 for (buffer, ranges) in ranges_by_buffer {
16724 buffer.update(cx, |buffer, cx| {
16725 buffer.merge_into_base(ranges, cx);
16726 });
16727 }
16728 });
16729
16730 if let Some(project) = self.project.clone() {
16731 self.save(true, project, window, cx).detach_and_log_err(cx);
16732 }
16733 }
16734
16735 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16736 if hovered != self.gutter_hovered {
16737 self.gutter_hovered = hovered;
16738 cx.notify();
16739 }
16740 }
16741
16742 pub fn insert_blocks(
16743 &mut self,
16744 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16745 autoscroll: Option<Autoscroll>,
16746 cx: &mut Context<Self>,
16747 ) -> Vec<CustomBlockId> {
16748 let blocks = self
16749 .display_map
16750 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16751 if let Some(autoscroll) = autoscroll {
16752 self.request_autoscroll(autoscroll, cx);
16753 }
16754 cx.notify();
16755 blocks
16756 }
16757
16758 pub fn resize_blocks(
16759 &mut self,
16760 heights: HashMap<CustomBlockId, u32>,
16761 autoscroll: Option<Autoscroll>,
16762 cx: &mut Context<Self>,
16763 ) {
16764 self.display_map
16765 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16766 if let Some(autoscroll) = autoscroll {
16767 self.request_autoscroll(autoscroll, cx);
16768 }
16769 cx.notify();
16770 }
16771
16772 pub fn replace_blocks(
16773 &mut self,
16774 renderers: HashMap<CustomBlockId, RenderBlock>,
16775 autoscroll: Option<Autoscroll>,
16776 cx: &mut Context<Self>,
16777 ) {
16778 self.display_map
16779 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16780 if let Some(autoscroll) = autoscroll {
16781 self.request_autoscroll(autoscroll, cx);
16782 }
16783 cx.notify();
16784 }
16785
16786 pub fn remove_blocks(
16787 &mut self,
16788 block_ids: HashSet<CustomBlockId>,
16789 autoscroll: Option<Autoscroll>,
16790 cx: &mut Context<Self>,
16791 ) {
16792 self.display_map.update(cx, |display_map, cx| {
16793 display_map.remove_blocks(block_ids, cx)
16794 });
16795 if let Some(autoscroll) = autoscroll {
16796 self.request_autoscroll(autoscroll, cx);
16797 }
16798 cx.notify();
16799 }
16800
16801 pub fn row_for_block(
16802 &self,
16803 block_id: CustomBlockId,
16804 cx: &mut Context<Self>,
16805 ) -> Option<DisplayRow> {
16806 self.display_map
16807 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16808 }
16809
16810 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16811 self.focused_block = Some(focused_block);
16812 }
16813
16814 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16815 self.focused_block.take()
16816 }
16817
16818 pub fn insert_creases(
16819 &mut self,
16820 creases: impl IntoIterator<Item = Crease<Anchor>>,
16821 cx: &mut Context<Self>,
16822 ) -> Vec<CreaseId> {
16823 self.display_map
16824 .update(cx, |map, cx| map.insert_creases(creases, cx))
16825 }
16826
16827 pub fn remove_creases(
16828 &mut self,
16829 ids: impl IntoIterator<Item = CreaseId>,
16830 cx: &mut Context<Self>,
16831 ) -> Vec<(CreaseId, Range<Anchor>)> {
16832 self.display_map
16833 .update(cx, |map, cx| map.remove_creases(ids, cx))
16834 }
16835
16836 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16837 self.display_map
16838 .update(cx, |map, cx| map.snapshot(cx))
16839 .longest_row()
16840 }
16841
16842 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16843 self.display_map
16844 .update(cx, |map, cx| map.snapshot(cx))
16845 .max_point()
16846 }
16847
16848 pub fn text(&self, cx: &App) -> String {
16849 self.buffer.read(cx).read(cx).text()
16850 }
16851
16852 pub fn is_empty(&self, cx: &App) -> bool {
16853 self.buffer.read(cx).read(cx).is_empty()
16854 }
16855
16856 pub fn text_option(&self, cx: &App) -> Option<String> {
16857 let text = self.text(cx);
16858 let text = text.trim();
16859
16860 if text.is_empty() {
16861 return None;
16862 }
16863
16864 Some(text.to_string())
16865 }
16866
16867 pub fn set_text(
16868 &mut self,
16869 text: impl Into<Arc<str>>,
16870 window: &mut Window,
16871 cx: &mut Context<Self>,
16872 ) {
16873 self.transact(window, cx, |this, _, cx| {
16874 this.buffer
16875 .read(cx)
16876 .as_singleton()
16877 .expect("you can only call set_text on editors for singleton buffers")
16878 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16879 });
16880 }
16881
16882 pub fn display_text(&self, cx: &mut App) -> String {
16883 self.display_map
16884 .update(cx, |map, cx| map.snapshot(cx))
16885 .text()
16886 }
16887
16888 fn create_minimap(
16889 &self,
16890 minimap_settings: MinimapSettings,
16891 window: &mut Window,
16892 cx: &mut Context<Self>,
16893 ) -> Option<Entity<Self>> {
16894 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
16895 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
16896 }
16897
16898 fn initialize_new_minimap(
16899 &self,
16900 minimap_settings: MinimapSettings,
16901 window: &mut Window,
16902 cx: &mut Context<Self>,
16903 ) -> Entity<Self> {
16904 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
16905
16906 let mut minimap = Editor::new_internal(
16907 EditorMode::Minimap {
16908 parent: cx.weak_entity(),
16909 },
16910 self.buffer.clone(),
16911 self.project.clone(),
16912 Some(self.display_map.clone()),
16913 window,
16914 cx,
16915 );
16916 minimap.scroll_manager.clone_state(&self.scroll_manager);
16917 minimap.set_text_style_refinement(TextStyleRefinement {
16918 font_size: Some(MINIMAP_FONT_SIZE),
16919 font_weight: Some(MINIMAP_FONT_WEIGHT),
16920 ..Default::default()
16921 });
16922 minimap.update_minimap_configuration(minimap_settings, cx);
16923 cx.new(|_| minimap)
16924 }
16925
16926 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
16927 let current_line_highlight = minimap_settings
16928 .current_line_highlight
16929 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
16930 self.set_current_line_highlight(Some(current_line_highlight));
16931 }
16932
16933 pub fn minimap(&self) -> Option<&Entity<Self>> {
16934 self.minimap
16935 .as_ref()
16936 .filter(|_| self.minimap_visibility.visible())
16937 }
16938
16939 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16940 let mut wrap_guides = smallvec![];
16941
16942 if self.show_wrap_guides == Some(false) {
16943 return wrap_guides;
16944 }
16945
16946 let settings = self.buffer.read(cx).language_settings(cx);
16947 if settings.show_wrap_guides {
16948 match self.soft_wrap_mode(cx) {
16949 SoftWrap::Column(soft_wrap) => {
16950 wrap_guides.push((soft_wrap as usize, true));
16951 }
16952 SoftWrap::Bounded(soft_wrap) => {
16953 wrap_guides.push((soft_wrap as usize, true));
16954 }
16955 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16956 }
16957 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16958 }
16959
16960 wrap_guides
16961 }
16962
16963 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16964 let settings = self.buffer.read(cx).language_settings(cx);
16965 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16966 match mode {
16967 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16968 SoftWrap::None
16969 }
16970 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16971 language_settings::SoftWrap::PreferredLineLength => {
16972 SoftWrap::Column(settings.preferred_line_length)
16973 }
16974 language_settings::SoftWrap::Bounded => {
16975 SoftWrap::Bounded(settings.preferred_line_length)
16976 }
16977 }
16978 }
16979
16980 pub fn set_soft_wrap_mode(
16981 &mut self,
16982 mode: language_settings::SoftWrap,
16983
16984 cx: &mut Context<Self>,
16985 ) {
16986 self.soft_wrap_mode_override = Some(mode);
16987 cx.notify();
16988 }
16989
16990 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16991 self.hard_wrap = hard_wrap;
16992 cx.notify();
16993 }
16994
16995 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16996 self.text_style_refinement = Some(style);
16997 }
16998
16999 /// called by the Element so we know what style we were most recently rendered with.
17000 pub(crate) fn set_style(
17001 &mut self,
17002 style: EditorStyle,
17003 window: &mut Window,
17004 cx: &mut Context<Self>,
17005 ) {
17006 // We intentionally do not inform the display map about the minimap style
17007 // so that wrapping is not recalculated and stays consistent for the editor
17008 // and its linked minimap.
17009 if !self.mode.is_minimap() {
17010 let rem_size = window.rem_size();
17011 self.display_map.update(cx, |map, cx| {
17012 map.set_font(
17013 style.text.font(),
17014 style.text.font_size.to_pixels(rem_size),
17015 cx,
17016 )
17017 });
17018 }
17019 self.style = Some(style);
17020 }
17021
17022 pub fn style(&self) -> Option<&EditorStyle> {
17023 self.style.as_ref()
17024 }
17025
17026 // Called by the element. This method is not designed to be called outside of the editor
17027 // element's layout code because it does not notify when rewrapping is computed synchronously.
17028 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17029 self.display_map
17030 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17031 }
17032
17033 pub fn set_soft_wrap(&mut self) {
17034 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17035 }
17036
17037 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17038 if self.soft_wrap_mode_override.is_some() {
17039 self.soft_wrap_mode_override.take();
17040 } else {
17041 let soft_wrap = match self.soft_wrap_mode(cx) {
17042 SoftWrap::GitDiff => return,
17043 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17044 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17045 language_settings::SoftWrap::None
17046 }
17047 };
17048 self.soft_wrap_mode_override = Some(soft_wrap);
17049 }
17050 cx.notify();
17051 }
17052
17053 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17054 let Some(workspace) = self.workspace() else {
17055 return;
17056 };
17057 let fs = workspace.read(cx).app_state().fs.clone();
17058 let current_show = TabBarSettings::get_global(cx).show;
17059 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17060 setting.show = Some(!current_show);
17061 });
17062 }
17063
17064 pub fn toggle_indent_guides(
17065 &mut self,
17066 _: &ToggleIndentGuides,
17067 _: &mut Window,
17068 cx: &mut Context<Self>,
17069 ) {
17070 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17071 self.buffer
17072 .read(cx)
17073 .language_settings(cx)
17074 .indent_guides
17075 .enabled
17076 });
17077 self.show_indent_guides = Some(!currently_enabled);
17078 cx.notify();
17079 }
17080
17081 fn should_show_indent_guides(&self) -> Option<bool> {
17082 self.show_indent_guides
17083 }
17084
17085 pub fn toggle_line_numbers(
17086 &mut self,
17087 _: &ToggleLineNumbers,
17088 _: &mut Window,
17089 cx: &mut Context<Self>,
17090 ) {
17091 let mut editor_settings = EditorSettings::get_global(cx).clone();
17092 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17093 EditorSettings::override_global(editor_settings, cx);
17094 }
17095
17096 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17097 if let Some(show_line_numbers) = self.show_line_numbers {
17098 return show_line_numbers;
17099 }
17100 EditorSettings::get_global(cx).gutter.line_numbers
17101 }
17102
17103 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17104 self.use_relative_line_numbers
17105 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17106 }
17107
17108 pub fn toggle_relative_line_numbers(
17109 &mut self,
17110 _: &ToggleRelativeLineNumbers,
17111 _: &mut Window,
17112 cx: &mut Context<Self>,
17113 ) {
17114 let is_relative = self.should_use_relative_line_numbers(cx);
17115 self.set_relative_line_number(Some(!is_relative), cx)
17116 }
17117
17118 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17119 self.use_relative_line_numbers = is_relative;
17120 cx.notify();
17121 }
17122
17123 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17124 self.show_gutter = show_gutter;
17125 cx.notify();
17126 }
17127
17128 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17129 self.show_scrollbars = ScrollbarAxes {
17130 horizontal: show,
17131 vertical: show,
17132 };
17133 cx.notify();
17134 }
17135
17136 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17137 self.show_scrollbars.vertical = show;
17138 cx.notify();
17139 }
17140
17141 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17142 self.show_scrollbars.horizontal = show;
17143 cx.notify();
17144 }
17145
17146 pub fn set_minimap_visibility(
17147 &mut self,
17148 minimap_visibility: MinimapVisibility,
17149 window: &mut Window,
17150 cx: &mut Context<Self>,
17151 ) {
17152 if self.minimap_visibility != minimap_visibility {
17153 if minimap_visibility.visible() && self.minimap.is_none() {
17154 let minimap_settings = EditorSettings::get_global(cx).minimap;
17155 self.minimap =
17156 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17157 }
17158 self.minimap_visibility = minimap_visibility;
17159 cx.notify();
17160 }
17161 }
17162
17163 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17164 self.set_show_scrollbars(false, cx);
17165 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17166 }
17167
17168 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17169 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17170 }
17171
17172 /// Normally the text in full mode and auto height editors is padded on the
17173 /// left side by roughly half a character width for improved hit testing.
17174 ///
17175 /// Use this method to disable this for cases where this is not wanted (e.g.
17176 /// if you want to align the editor text with some other text above or below)
17177 /// or if you want to add this padding to single-line editors.
17178 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17179 self.offset_content = offset_content;
17180 cx.notify();
17181 }
17182
17183 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17184 self.show_line_numbers = Some(show_line_numbers);
17185 cx.notify();
17186 }
17187
17188 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17189 self.disable_expand_excerpt_buttons = true;
17190 cx.notify();
17191 }
17192
17193 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17194 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17195 cx.notify();
17196 }
17197
17198 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17199 self.show_code_actions = Some(show_code_actions);
17200 cx.notify();
17201 }
17202
17203 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17204 self.show_runnables = Some(show_runnables);
17205 cx.notify();
17206 }
17207
17208 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17209 self.show_breakpoints = Some(show_breakpoints);
17210 cx.notify();
17211 }
17212
17213 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17214 if self.display_map.read(cx).masked != masked {
17215 self.display_map.update(cx, |map, _| map.masked = masked);
17216 }
17217 cx.notify()
17218 }
17219
17220 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17221 self.show_wrap_guides = Some(show_wrap_guides);
17222 cx.notify();
17223 }
17224
17225 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17226 self.show_indent_guides = Some(show_indent_guides);
17227 cx.notify();
17228 }
17229
17230 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17231 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17232 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17233 if let Some(dir) = file.abs_path(cx).parent() {
17234 return Some(dir.to_owned());
17235 }
17236 }
17237
17238 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17239 return Some(project_path.path.to_path_buf());
17240 }
17241 }
17242
17243 None
17244 }
17245
17246 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17247 self.active_excerpt(cx)?
17248 .1
17249 .read(cx)
17250 .file()
17251 .and_then(|f| f.as_local())
17252 }
17253
17254 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17255 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17256 let buffer = buffer.read(cx);
17257 if let Some(project_path) = buffer.project_path(cx) {
17258 let project = self.project.as_ref()?.read(cx);
17259 project.absolute_path(&project_path, cx)
17260 } else {
17261 buffer
17262 .file()
17263 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17264 }
17265 })
17266 }
17267
17268 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17269 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17270 let project_path = buffer.read(cx).project_path(cx)?;
17271 let project = self.project.as_ref()?.read(cx);
17272 let entry = project.entry_for_path(&project_path, cx)?;
17273 let path = entry.path.to_path_buf();
17274 Some(path)
17275 })
17276 }
17277
17278 pub fn reveal_in_finder(
17279 &mut self,
17280 _: &RevealInFileManager,
17281 _window: &mut Window,
17282 cx: &mut Context<Self>,
17283 ) {
17284 if let Some(target) = self.target_file(cx) {
17285 cx.reveal_path(&target.abs_path(cx));
17286 }
17287 }
17288
17289 pub fn copy_path(
17290 &mut self,
17291 _: &zed_actions::workspace::CopyPath,
17292 _window: &mut Window,
17293 cx: &mut Context<Self>,
17294 ) {
17295 if let Some(path) = self.target_file_abs_path(cx) {
17296 if let Some(path) = path.to_str() {
17297 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17298 }
17299 }
17300 }
17301
17302 pub fn copy_relative_path(
17303 &mut self,
17304 _: &zed_actions::workspace::CopyRelativePath,
17305 _window: &mut Window,
17306 cx: &mut Context<Self>,
17307 ) {
17308 if let Some(path) = self.target_file_path(cx) {
17309 if let Some(path) = path.to_str() {
17310 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17311 }
17312 }
17313 }
17314
17315 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17316 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17317 buffer.read(cx).project_path(cx)
17318 } else {
17319 None
17320 }
17321 }
17322
17323 // Returns true if the editor handled a go-to-line request
17324 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17325 maybe!({
17326 let breakpoint_store = self.breakpoint_store.as_ref()?;
17327
17328 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17329 else {
17330 self.clear_row_highlights::<ActiveDebugLine>();
17331 return None;
17332 };
17333
17334 let position = active_stack_frame.position;
17335 let buffer_id = position.buffer_id?;
17336 let snapshot = self
17337 .project
17338 .as_ref()?
17339 .read(cx)
17340 .buffer_for_id(buffer_id, cx)?
17341 .read(cx)
17342 .snapshot();
17343
17344 let mut handled = false;
17345 for (id, ExcerptRange { context, .. }) in
17346 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17347 {
17348 if context.start.cmp(&position, &snapshot).is_ge()
17349 || context.end.cmp(&position, &snapshot).is_lt()
17350 {
17351 continue;
17352 }
17353 let snapshot = self.buffer.read(cx).snapshot(cx);
17354 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17355
17356 handled = true;
17357 self.clear_row_highlights::<ActiveDebugLine>();
17358
17359 self.go_to_line::<ActiveDebugLine>(
17360 multibuffer_anchor,
17361 Some(cx.theme().colors().editor_debugger_active_line_background),
17362 window,
17363 cx,
17364 );
17365
17366 cx.notify();
17367 }
17368
17369 handled.then_some(())
17370 })
17371 .is_some()
17372 }
17373
17374 pub fn copy_file_name_without_extension(
17375 &mut self,
17376 _: &CopyFileNameWithoutExtension,
17377 _: &mut Window,
17378 cx: &mut Context<Self>,
17379 ) {
17380 if let Some(file) = self.target_file(cx) {
17381 if let Some(file_stem) = file.path().file_stem() {
17382 if let Some(name) = file_stem.to_str() {
17383 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17384 }
17385 }
17386 }
17387 }
17388
17389 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17390 if let Some(file) = self.target_file(cx) {
17391 if let Some(file_name) = file.path().file_name() {
17392 if let Some(name) = file_name.to_str() {
17393 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17394 }
17395 }
17396 }
17397 }
17398
17399 pub fn toggle_git_blame(
17400 &mut self,
17401 _: &::git::Blame,
17402 window: &mut Window,
17403 cx: &mut Context<Self>,
17404 ) {
17405 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17406
17407 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17408 self.start_git_blame(true, window, cx);
17409 }
17410
17411 cx.notify();
17412 }
17413
17414 pub fn toggle_git_blame_inline(
17415 &mut self,
17416 _: &ToggleGitBlameInline,
17417 window: &mut Window,
17418 cx: &mut Context<Self>,
17419 ) {
17420 self.toggle_git_blame_inline_internal(true, window, cx);
17421 cx.notify();
17422 }
17423
17424 pub fn open_git_blame_commit(
17425 &mut self,
17426 _: &OpenGitBlameCommit,
17427 window: &mut Window,
17428 cx: &mut Context<Self>,
17429 ) {
17430 self.open_git_blame_commit_internal(window, cx);
17431 }
17432
17433 fn open_git_blame_commit_internal(
17434 &mut self,
17435 window: &mut Window,
17436 cx: &mut Context<Self>,
17437 ) -> Option<()> {
17438 let blame = self.blame.as_ref()?;
17439 let snapshot = self.snapshot(window, cx);
17440 let cursor = self.selections.newest::<Point>(cx).head();
17441 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17442 let blame_entry = blame
17443 .update(cx, |blame, cx| {
17444 blame
17445 .blame_for_rows(
17446 &[RowInfo {
17447 buffer_id: Some(buffer.remote_id()),
17448 buffer_row: Some(point.row),
17449 ..Default::default()
17450 }],
17451 cx,
17452 )
17453 .next()
17454 })
17455 .flatten()?;
17456 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17457 let repo = blame.read(cx).repository(cx)?;
17458 let workspace = self.workspace()?.downgrade();
17459 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17460 None
17461 }
17462
17463 pub fn git_blame_inline_enabled(&self) -> bool {
17464 self.git_blame_inline_enabled
17465 }
17466
17467 pub fn toggle_selection_menu(
17468 &mut self,
17469 _: &ToggleSelectionMenu,
17470 _: &mut Window,
17471 cx: &mut Context<Self>,
17472 ) {
17473 self.show_selection_menu = self
17474 .show_selection_menu
17475 .map(|show_selections_menu| !show_selections_menu)
17476 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17477
17478 cx.notify();
17479 }
17480
17481 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17482 self.show_selection_menu
17483 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17484 }
17485
17486 fn start_git_blame(
17487 &mut self,
17488 user_triggered: bool,
17489 window: &mut Window,
17490 cx: &mut Context<Self>,
17491 ) {
17492 if let Some(project) = self.project.as_ref() {
17493 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17494 return;
17495 };
17496
17497 if buffer.read(cx).file().is_none() {
17498 return;
17499 }
17500
17501 let focused = self.focus_handle(cx).contains_focused(window, cx);
17502
17503 let project = project.clone();
17504 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17505 self.blame_subscription =
17506 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17507 self.blame = Some(blame);
17508 }
17509 }
17510
17511 fn toggle_git_blame_inline_internal(
17512 &mut self,
17513 user_triggered: bool,
17514 window: &mut Window,
17515 cx: &mut Context<Self>,
17516 ) {
17517 if self.git_blame_inline_enabled {
17518 self.git_blame_inline_enabled = false;
17519 self.show_git_blame_inline = false;
17520 self.show_git_blame_inline_delay_task.take();
17521 } else {
17522 self.git_blame_inline_enabled = true;
17523 self.start_git_blame_inline(user_triggered, window, cx);
17524 }
17525
17526 cx.notify();
17527 }
17528
17529 fn start_git_blame_inline(
17530 &mut self,
17531 user_triggered: bool,
17532 window: &mut Window,
17533 cx: &mut Context<Self>,
17534 ) {
17535 self.start_git_blame(user_triggered, window, cx);
17536
17537 if ProjectSettings::get_global(cx)
17538 .git
17539 .inline_blame_delay()
17540 .is_some()
17541 {
17542 self.start_inline_blame_timer(window, cx);
17543 } else {
17544 self.show_git_blame_inline = true
17545 }
17546 }
17547
17548 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17549 self.blame.as_ref()
17550 }
17551
17552 pub fn show_git_blame_gutter(&self) -> bool {
17553 self.show_git_blame_gutter
17554 }
17555
17556 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17557 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17558 }
17559
17560 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17561 self.show_git_blame_inline
17562 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17563 && !self.newest_selection_head_on_empty_line(cx)
17564 && self.has_blame_entries(cx)
17565 }
17566
17567 fn has_blame_entries(&self, cx: &App) -> bool {
17568 self.blame()
17569 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17570 }
17571
17572 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17573 let cursor_anchor = self.selections.newest_anchor().head();
17574
17575 let snapshot = self.buffer.read(cx).snapshot(cx);
17576 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17577
17578 snapshot.line_len(buffer_row) == 0
17579 }
17580
17581 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17582 let buffer_and_selection = maybe!({
17583 let selection = self.selections.newest::<Point>(cx);
17584 let selection_range = selection.range();
17585
17586 let multi_buffer = self.buffer().read(cx);
17587 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17588 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17589
17590 let (buffer, range, _) = if selection.reversed {
17591 buffer_ranges.first()
17592 } else {
17593 buffer_ranges.last()
17594 }?;
17595
17596 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17597 ..text::ToPoint::to_point(&range.end, &buffer).row;
17598 Some((
17599 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17600 selection,
17601 ))
17602 });
17603
17604 let Some((buffer, selection)) = buffer_and_selection else {
17605 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17606 };
17607
17608 let Some(project) = self.project.as_ref() else {
17609 return Task::ready(Err(anyhow!("editor does not have project")));
17610 };
17611
17612 project.update(cx, |project, cx| {
17613 project.get_permalink_to_line(&buffer, selection, cx)
17614 })
17615 }
17616
17617 pub fn copy_permalink_to_line(
17618 &mut self,
17619 _: &CopyPermalinkToLine,
17620 window: &mut Window,
17621 cx: &mut Context<Self>,
17622 ) {
17623 let permalink_task = self.get_permalink_to_line(cx);
17624 let workspace = self.workspace();
17625
17626 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17627 Ok(permalink) => {
17628 cx.update(|_, cx| {
17629 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17630 })
17631 .ok();
17632 }
17633 Err(err) => {
17634 let message = format!("Failed to copy permalink: {err}");
17635
17636 anyhow::Result::<()>::Err(err).log_err();
17637
17638 if let Some(workspace) = workspace {
17639 workspace
17640 .update_in(cx, |workspace, _, cx| {
17641 struct CopyPermalinkToLine;
17642
17643 workspace.show_toast(
17644 Toast::new(
17645 NotificationId::unique::<CopyPermalinkToLine>(),
17646 message,
17647 ),
17648 cx,
17649 )
17650 })
17651 .ok();
17652 }
17653 }
17654 })
17655 .detach();
17656 }
17657
17658 pub fn copy_file_location(
17659 &mut self,
17660 _: &CopyFileLocation,
17661 _: &mut Window,
17662 cx: &mut Context<Self>,
17663 ) {
17664 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17665 if let Some(file) = self.target_file(cx) {
17666 if let Some(path) = file.path().to_str() {
17667 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17668 }
17669 }
17670 }
17671
17672 pub fn open_permalink_to_line(
17673 &mut self,
17674 _: &OpenPermalinkToLine,
17675 window: &mut Window,
17676 cx: &mut Context<Self>,
17677 ) {
17678 let permalink_task = self.get_permalink_to_line(cx);
17679 let workspace = self.workspace();
17680
17681 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17682 Ok(permalink) => {
17683 cx.update(|_, cx| {
17684 cx.open_url(permalink.as_ref());
17685 })
17686 .ok();
17687 }
17688 Err(err) => {
17689 let message = format!("Failed to open permalink: {err}");
17690
17691 anyhow::Result::<()>::Err(err).log_err();
17692
17693 if let Some(workspace) = workspace {
17694 workspace
17695 .update(cx, |workspace, cx| {
17696 struct OpenPermalinkToLine;
17697
17698 workspace.show_toast(
17699 Toast::new(
17700 NotificationId::unique::<OpenPermalinkToLine>(),
17701 message,
17702 ),
17703 cx,
17704 )
17705 })
17706 .ok();
17707 }
17708 }
17709 })
17710 .detach();
17711 }
17712
17713 pub fn insert_uuid_v4(
17714 &mut self,
17715 _: &InsertUuidV4,
17716 window: &mut Window,
17717 cx: &mut Context<Self>,
17718 ) {
17719 self.insert_uuid(UuidVersion::V4, window, cx);
17720 }
17721
17722 pub fn insert_uuid_v7(
17723 &mut self,
17724 _: &InsertUuidV7,
17725 window: &mut Window,
17726 cx: &mut Context<Self>,
17727 ) {
17728 self.insert_uuid(UuidVersion::V7, window, cx);
17729 }
17730
17731 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17732 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17733 self.transact(window, cx, |this, window, cx| {
17734 let edits = this
17735 .selections
17736 .all::<Point>(cx)
17737 .into_iter()
17738 .map(|selection| {
17739 let uuid = match version {
17740 UuidVersion::V4 => uuid::Uuid::new_v4(),
17741 UuidVersion::V7 => uuid::Uuid::now_v7(),
17742 };
17743
17744 (selection.range(), uuid.to_string())
17745 });
17746 this.edit(edits, cx);
17747 this.refresh_inline_completion(true, false, window, cx);
17748 });
17749 }
17750
17751 pub fn open_selections_in_multibuffer(
17752 &mut self,
17753 _: &OpenSelectionsInMultibuffer,
17754 window: &mut Window,
17755 cx: &mut Context<Self>,
17756 ) {
17757 let multibuffer = self.buffer.read(cx);
17758
17759 let Some(buffer) = multibuffer.as_singleton() else {
17760 return;
17761 };
17762
17763 let Some(workspace) = self.workspace() else {
17764 return;
17765 };
17766
17767 let locations = self
17768 .selections
17769 .disjoint_anchors()
17770 .iter()
17771 .map(|selection| {
17772 let range = if selection.reversed {
17773 selection.end.text_anchor..selection.start.text_anchor
17774 } else {
17775 selection.start.text_anchor..selection.end.text_anchor
17776 };
17777 Location {
17778 buffer: buffer.clone(),
17779 range,
17780 }
17781 })
17782 .collect::<Vec<_>>();
17783
17784 let title = multibuffer.title(cx).to_string();
17785
17786 cx.spawn_in(window, async move |_, cx| {
17787 workspace.update_in(cx, |workspace, window, cx| {
17788 Self::open_locations_in_multibuffer(
17789 workspace,
17790 locations,
17791 format!("Selections for '{title}'"),
17792 false,
17793 MultibufferSelectionMode::All,
17794 window,
17795 cx,
17796 );
17797 })
17798 })
17799 .detach();
17800 }
17801
17802 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17803 /// last highlight added will be used.
17804 ///
17805 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17806 pub fn highlight_rows<T: 'static>(
17807 &mut self,
17808 range: Range<Anchor>,
17809 color: Hsla,
17810 options: RowHighlightOptions,
17811 cx: &mut Context<Self>,
17812 ) {
17813 let snapshot = self.buffer().read(cx).snapshot(cx);
17814 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17815 let ix = row_highlights.binary_search_by(|highlight| {
17816 Ordering::Equal
17817 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17818 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17819 });
17820
17821 if let Err(mut ix) = ix {
17822 let index = post_inc(&mut self.highlight_order);
17823
17824 // If this range intersects with the preceding highlight, then merge it with
17825 // the preceding highlight. Otherwise insert a new highlight.
17826 let mut merged = false;
17827 if ix > 0 {
17828 let prev_highlight = &mut row_highlights[ix - 1];
17829 if prev_highlight
17830 .range
17831 .end
17832 .cmp(&range.start, &snapshot)
17833 .is_ge()
17834 {
17835 ix -= 1;
17836 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17837 prev_highlight.range.end = range.end;
17838 }
17839 merged = true;
17840 prev_highlight.index = index;
17841 prev_highlight.color = color;
17842 prev_highlight.options = options;
17843 }
17844 }
17845
17846 if !merged {
17847 row_highlights.insert(
17848 ix,
17849 RowHighlight {
17850 range: range.clone(),
17851 index,
17852 color,
17853 options,
17854 type_id: TypeId::of::<T>(),
17855 },
17856 );
17857 }
17858
17859 // If any of the following highlights intersect with this one, merge them.
17860 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17861 let highlight = &row_highlights[ix];
17862 if next_highlight
17863 .range
17864 .start
17865 .cmp(&highlight.range.end, &snapshot)
17866 .is_le()
17867 {
17868 if next_highlight
17869 .range
17870 .end
17871 .cmp(&highlight.range.end, &snapshot)
17872 .is_gt()
17873 {
17874 row_highlights[ix].range.end = next_highlight.range.end;
17875 }
17876 row_highlights.remove(ix + 1);
17877 } else {
17878 break;
17879 }
17880 }
17881 }
17882 }
17883
17884 /// Remove any highlighted row ranges of the given type that intersect the
17885 /// given ranges.
17886 pub fn remove_highlighted_rows<T: 'static>(
17887 &mut self,
17888 ranges_to_remove: Vec<Range<Anchor>>,
17889 cx: &mut Context<Self>,
17890 ) {
17891 let snapshot = self.buffer().read(cx).snapshot(cx);
17892 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17893 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17894 row_highlights.retain(|highlight| {
17895 while let Some(range_to_remove) = ranges_to_remove.peek() {
17896 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17897 Ordering::Less | Ordering::Equal => {
17898 ranges_to_remove.next();
17899 }
17900 Ordering::Greater => {
17901 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17902 Ordering::Less | Ordering::Equal => {
17903 return false;
17904 }
17905 Ordering::Greater => break,
17906 }
17907 }
17908 }
17909 }
17910
17911 true
17912 })
17913 }
17914
17915 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17916 pub fn clear_row_highlights<T: 'static>(&mut self) {
17917 self.highlighted_rows.remove(&TypeId::of::<T>());
17918 }
17919
17920 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17921 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17922 self.highlighted_rows
17923 .get(&TypeId::of::<T>())
17924 .map_or(&[] as &[_], |vec| vec.as_slice())
17925 .iter()
17926 .map(|highlight| (highlight.range.clone(), highlight.color))
17927 }
17928
17929 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17930 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17931 /// Allows to ignore certain kinds of highlights.
17932 pub fn highlighted_display_rows(
17933 &self,
17934 window: &mut Window,
17935 cx: &mut App,
17936 ) -> BTreeMap<DisplayRow, LineHighlight> {
17937 let snapshot = self.snapshot(window, cx);
17938 let mut used_highlight_orders = HashMap::default();
17939 self.highlighted_rows
17940 .iter()
17941 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17942 .fold(
17943 BTreeMap::<DisplayRow, LineHighlight>::new(),
17944 |mut unique_rows, highlight| {
17945 let start = highlight.range.start.to_display_point(&snapshot);
17946 let end = highlight.range.end.to_display_point(&snapshot);
17947 let start_row = start.row().0;
17948 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17949 && end.column() == 0
17950 {
17951 end.row().0.saturating_sub(1)
17952 } else {
17953 end.row().0
17954 };
17955 for row in start_row..=end_row {
17956 let used_index =
17957 used_highlight_orders.entry(row).or_insert(highlight.index);
17958 if highlight.index >= *used_index {
17959 *used_index = highlight.index;
17960 unique_rows.insert(
17961 DisplayRow(row),
17962 LineHighlight {
17963 include_gutter: highlight.options.include_gutter,
17964 border: None,
17965 background: highlight.color.into(),
17966 type_id: Some(highlight.type_id),
17967 },
17968 );
17969 }
17970 }
17971 unique_rows
17972 },
17973 )
17974 }
17975
17976 pub fn highlighted_display_row_for_autoscroll(
17977 &self,
17978 snapshot: &DisplaySnapshot,
17979 ) -> Option<DisplayRow> {
17980 self.highlighted_rows
17981 .values()
17982 .flat_map(|highlighted_rows| highlighted_rows.iter())
17983 .filter_map(|highlight| {
17984 if highlight.options.autoscroll {
17985 Some(highlight.range.start.to_display_point(snapshot).row())
17986 } else {
17987 None
17988 }
17989 })
17990 .min()
17991 }
17992
17993 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17994 self.highlight_background::<SearchWithinRange>(
17995 ranges,
17996 |colors| colors.editor_document_highlight_read_background,
17997 cx,
17998 )
17999 }
18000
18001 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18002 self.breadcrumb_header = Some(new_header);
18003 }
18004
18005 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18006 self.clear_background_highlights::<SearchWithinRange>(cx);
18007 }
18008
18009 pub fn highlight_background<T: 'static>(
18010 &mut self,
18011 ranges: &[Range<Anchor>],
18012 color_fetcher: fn(&ThemeColors) -> Hsla,
18013 cx: &mut Context<Self>,
18014 ) {
18015 self.background_highlights
18016 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18017 self.scrollbar_marker_state.dirty = true;
18018 cx.notify();
18019 }
18020
18021 pub fn clear_background_highlights<T: 'static>(
18022 &mut self,
18023 cx: &mut Context<Self>,
18024 ) -> Option<BackgroundHighlight> {
18025 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
18026 if !text_highlights.1.is_empty() {
18027 self.scrollbar_marker_state.dirty = true;
18028 cx.notify();
18029 }
18030 Some(text_highlights)
18031 }
18032
18033 pub fn highlight_gutter<T: 'static>(
18034 &mut self,
18035 ranges: &[Range<Anchor>],
18036 color_fetcher: fn(&App) -> Hsla,
18037 cx: &mut Context<Self>,
18038 ) {
18039 self.gutter_highlights
18040 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18041 cx.notify();
18042 }
18043
18044 pub fn clear_gutter_highlights<T: 'static>(
18045 &mut self,
18046 cx: &mut Context<Self>,
18047 ) -> Option<GutterHighlight> {
18048 cx.notify();
18049 self.gutter_highlights.remove(&TypeId::of::<T>())
18050 }
18051
18052 #[cfg(feature = "test-support")]
18053 pub fn all_text_background_highlights(
18054 &self,
18055 window: &mut Window,
18056 cx: &mut Context<Self>,
18057 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18058 let snapshot = self.snapshot(window, cx);
18059 let buffer = &snapshot.buffer_snapshot;
18060 let start = buffer.anchor_before(0);
18061 let end = buffer.anchor_after(buffer.len());
18062 let theme = cx.theme().colors();
18063 self.background_highlights_in_range(start..end, &snapshot, theme)
18064 }
18065
18066 #[cfg(feature = "test-support")]
18067 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18068 let snapshot = self.buffer().read(cx).snapshot(cx);
18069
18070 let highlights = self
18071 .background_highlights
18072 .get(&TypeId::of::<items::BufferSearchHighlights>());
18073
18074 if let Some((_color, ranges)) = highlights {
18075 ranges
18076 .iter()
18077 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18078 .collect_vec()
18079 } else {
18080 vec![]
18081 }
18082 }
18083
18084 fn document_highlights_for_position<'a>(
18085 &'a self,
18086 position: Anchor,
18087 buffer: &'a MultiBufferSnapshot,
18088 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18089 let read_highlights = self
18090 .background_highlights
18091 .get(&TypeId::of::<DocumentHighlightRead>())
18092 .map(|h| &h.1);
18093 let write_highlights = self
18094 .background_highlights
18095 .get(&TypeId::of::<DocumentHighlightWrite>())
18096 .map(|h| &h.1);
18097 let left_position = position.bias_left(buffer);
18098 let right_position = position.bias_right(buffer);
18099 read_highlights
18100 .into_iter()
18101 .chain(write_highlights)
18102 .flat_map(move |ranges| {
18103 let start_ix = match ranges.binary_search_by(|probe| {
18104 let cmp = probe.end.cmp(&left_position, buffer);
18105 if cmp.is_ge() {
18106 Ordering::Greater
18107 } else {
18108 Ordering::Less
18109 }
18110 }) {
18111 Ok(i) | Err(i) => i,
18112 };
18113
18114 ranges[start_ix..]
18115 .iter()
18116 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
18117 })
18118 }
18119
18120 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18121 self.background_highlights
18122 .get(&TypeId::of::<T>())
18123 .map_or(false, |(_, highlights)| !highlights.is_empty())
18124 }
18125
18126 pub fn background_highlights_in_range(
18127 &self,
18128 search_range: Range<Anchor>,
18129 display_snapshot: &DisplaySnapshot,
18130 theme: &ThemeColors,
18131 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18132 let mut results = Vec::new();
18133 for (color_fetcher, ranges) in self.background_highlights.values() {
18134 let color = color_fetcher(theme);
18135 let start_ix = match ranges.binary_search_by(|probe| {
18136 let cmp = probe
18137 .end
18138 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18139 if cmp.is_gt() {
18140 Ordering::Greater
18141 } else {
18142 Ordering::Less
18143 }
18144 }) {
18145 Ok(i) | Err(i) => i,
18146 };
18147 for range in &ranges[start_ix..] {
18148 if range
18149 .start
18150 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18151 .is_ge()
18152 {
18153 break;
18154 }
18155
18156 let start = range.start.to_display_point(display_snapshot);
18157 let end = range.end.to_display_point(display_snapshot);
18158 results.push((start..end, color))
18159 }
18160 }
18161 results
18162 }
18163
18164 pub fn background_highlight_row_ranges<T: 'static>(
18165 &self,
18166 search_range: Range<Anchor>,
18167 display_snapshot: &DisplaySnapshot,
18168 count: usize,
18169 ) -> Vec<RangeInclusive<DisplayPoint>> {
18170 let mut results = Vec::new();
18171 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
18172 return vec![];
18173 };
18174
18175 let start_ix = match ranges.binary_search_by(|probe| {
18176 let cmp = probe
18177 .end
18178 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18179 if cmp.is_gt() {
18180 Ordering::Greater
18181 } else {
18182 Ordering::Less
18183 }
18184 }) {
18185 Ok(i) | Err(i) => i,
18186 };
18187 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18188 if let (Some(start_display), Some(end_display)) = (start, end) {
18189 results.push(
18190 start_display.to_display_point(display_snapshot)
18191 ..=end_display.to_display_point(display_snapshot),
18192 );
18193 }
18194 };
18195 let mut start_row: Option<Point> = None;
18196 let mut end_row: Option<Point> = None;
18197 if ranges.len() > count {
18198 return Vec::new();
18199 }
18200 for range in &ranges[start_ix..] {
18201 if range
18202 .start
18203 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18204 .is_ge()
18205 {
18206 break;
18207 }
18208 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18209 if let Some(current_row) = &end_row {
18210 if end.row == current_row.row {
18211 continue;
18212 }
18213 }
18214 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18215 if start_row.is_none() {
18216 assert_eq!(end_row, None);
18217 start_row = Some(start);
18218 end_row = Some(end);
18219 continue;
18220 }
18221 if let Some(current_end) = end_row.as_mut() {
18222 if start.row > current_end.row + 1 {
18223 push_region(start_row, end_row);
18224 start_row = Some(start);
18225 end_row = Some(end);
18226 } else {
18227 // Merge two hunks.
18228 *current_end = end;
18229 }
18230 } else {
18231 unreachable!();
18232 }
18233 }
18234 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18235 push_region(start_row, end_row);
18236 results
18237 }
18238
18239 pub fn gutter_highlights_in_range(
18240 &self,
18241 search_range: Range<Anchor>,
18242 display_snapshot: &DisplaySnapshot,
18243 cx: &App,
18244 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18245 let mut results = Vec::new();
18246 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18247 let color = color_fetcher(cx);
18248 let start_ix = match ranges.binary_search_by(|probe| {
18249 let cmp = probe
18250 .end
18251 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18252 if cmp.is_gt() {
18253 Ordering::Greater
18254 } else {
18255 Ordering::Less
18256 }
18257 }) {
18258 Ok(i) | Err(i) => i,
18259 };
18260 for range in &ranges[start_ix..] {
18261 if range
18262 .start
18263 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18264 .is_ge()
18265 {
18266 break;
18267 }
18268
18269 let start = range.start.to_display_point(display_snapshot);
18270 let end = range.end.to_display_point(display_snapshot);
18271 results.push((start..end, color))
18272 }
18273 }
18274 results
18275 }
18276
18277 /// Get the text ranges corresponding to the redaction query
18278 pub fn redacted_ranges(
18279 &self,
18280 search_range: Range<Anchor>,
18281 display_snapshot: &DisplaySnapshot,
18282 cx: &App,
18283 ) -> Vec<Range<DisplayPoint>> {
18284 display_snapshot
18285 .buffer_snapshot
18286 .redacted_ranges(search_range, |file| {
18287 if let Some(file) = file {
18288 file.is_private()
18289 && EditorSettings::get(
18290 Some(SettingsLocation {
18291 worktree_id: file.worktree_id(cx),
18292 path: file.path().as_ref(),
18293 }),
18294 cx,
18295 )
18296 .redact_private_values
18297 } else {
18298 false
18299 }
18300 })
18301 .map(|range| {
18302 range.start.to_display_point(display_snapshot)
18303 ..range.end.to_display_point(display_snapshot)
18304 })
18305 .collect()
18306 }
18307
18308 pub fn highlight_text<T: 'static>(
18309 &mut self,
18310 ranges: Vec<Range<Anchor>>,
18311 style: HighlightStyle,
18312 cx: &mut Context<Self>,
18313 ) {
18314 self.display_map.update(cx, |map, _| {
18315 map.highlight_text(TypeId::of::<T>(), ranges, style)
18316 });
18317 cx.notify();
18318 }
18319
18320 pub(crate) fn highlight_inlays<T: 'static>(
18321 &mut self,
18322 highlights: Vec<InlayHighlight>,
18323 style: HighlightStyle,
18324 cx: &mut Context<Self>,
18325 ) {
18326 self.display_map.update(cx, |map, _| {
18327 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18328 });
18329 cx.notify();
18330 }
18331
18332 pub fn text_highlights<'a, T: 'static>(
18333 &'a self,
18334 cx: &'a App,
18335 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18336 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18337 }
18338
18339 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18340 let cleared = self
18341 .display_map
18342 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18343 if cleared {
18344 cx.notify();
18345 }
18346 }
18347
18348 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18349 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18350 && self.focus_handle.is_focused(window)
18351 }
18352
18353 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18354 self.show_cursor_when_unfocused = is_enabled;
18355 cx.notify();
18356 }
18357
18358 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18359 cx.notify();
18360 }
18361
18362 fn on_debug_session_event(
18363 &mut self,
18364 _session: Entity<Session>,
18365 event: &SessionEvent,
18366 cx: &mut Context<Self>,
18367 ) {
18368 match event {
18369 SessionEvent::InvalidateInlineValue => {
18370 self.refresh_inline_values(cx);
18371 }
18372 _ => {}
18373 }
18374 }
18375
18376 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18377 let Some(project) = self.project.clone() else {
18378 return;
18379 };
18380
18381 if !self.inline_value_cache.enabled {
18382 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18383 self.splice_inlays(&inlays, Vec::new(), cx);
18384 return;
18385 }
18386
18387 let current_execution_position = self
18388 .highlighted_rows
18389 .get(&TypeId::of::<ActiveDebugLine>())
18390 .and_then(|lines| lines.last().map(|line| line.range.start));
18391
18392 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18393 let inline_values = editor
18394 .update(cx, |editor, cx| {
18395 let Some(current_execution_position) = current_execution_position else {
18396 return Some(Task::ready(Ok(Vec::new())));
18397 };
18398
18399 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18400 let snapshot = buffer.snapshot(cx);
18401
18402 let excerpt = snapshot.excerpt_containing(
18403 current_execution_position..current_execution_position,
18404 )?;
18405
18406 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18407 })?;
18408
18409 let range =
18410 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18411
18412 project.inline_values(buffer, range, cx)
18413 })
18414 .ok()
18415 .flatten()?
18416 .await
18417 .context("refreshing debugger inlays")
18418 .log_err()?;
18419
18420 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18421
18422 for (buffer_id, inline_value) in inline_values
18423 .into_iter()
18424 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18425 {
18426 buffer_inline_values
18427 .entry(buffer_id)
18428 .or_default()
18429 .push(inline_value);
18430 }
18431
18432 editor
18433 .update(cx, |editor, cx| {
18434 let snapshot = editor.buffer.read(cx).snapshot(cx);
18435 let mut new_inlays = Vec::default();
18436
18437 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18438 let buffer_id = buffer_snapshot.remote_id();
18439 buffer_inline_values
18440 .get(&buffer_id)
18441 .into_iter()
18442 .flatten()
18443 .for_each(|hint| {
18444 let inlay = Inlay::debugger_hint(
18445 post_inc(&mut editor.next_inlay_id),
18446 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18447 hint.text(),
18448 );
18449
18450 new_inlays.push(inlay);
18451 });
18452 }
18453
18454 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18455 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18456
18457 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18458 })
18459 .ok()?;
18460 Some(())
18461 });
18462 }
18463
18464 fn on_buffer_event(
18465 &mut self,
18466 multibuffer: &Entity<MultiBuffer>,
18467 event: &multi_buffer::Event,
18468 window: &mut Window,
18469 cx: &mut Context<Self>,
18470 ) {
18471 match event {
18472 multi_buffer::Event::Edited {
18473 singleton_buffer_edited,
18474 edited_buffer: buffer_edited,
18475 } => {
18476 self.scrollbar_marker_state.dirty = true;
18477 self.active_indent_guides_state.dirty = true;
18478 self.refresh_active_diagnostics(cx);
18479 self.refresh_code_actions(window, cx);
18480 self.refresh_selected_text_highlights(true, window, cx);
18481 refresh_matching_bracket_highlights(self, window, cx);
18482 if self.has_active_inline_completion() {
18483 self.update_visible_inline_completion(window, cx);
18484 }
18485 if let Some(buffer) = buffer_edited {
18486 let buffer_id = buffer.read(cx).remote_id();
18487 if !self.registered_buffers.contains_key(&buffer_id) {
18488 if let Some(project) = self.project.as_ref() {
18489 project.update(cx, |project, cx| {
18490 self.registered_buffers.insert(
18491 buffer_id,
18492 project.register_buffer_with_language_servers(&buffer, cx),
18493 );
18494 })
18495 }
18496 }
18497 }
18498 cx.emit(EditorEvent::BufferEdited);
18499 cx.emit(SearchEvent::MatchesInvalidated);
18500 if *singleton_buffer_edited {
18501 if let Some(project) = &self.project {
18502 #[allow(clippy::mutable_key_type)]
18503 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18504 multibuffer
18505 .all_buffers()
18506 .into_iter()
18507 .filter_map(|buffer| {
18508 buffer.update(cx, |buffer, cx| {
18509 let language = buffer.language()?;
18510 let should_discard = project.update(cx, |project, cx| {
18511 project.is_local()
18512 && !project.has_language_servers_for(buffer, cx)
18513 });
18514 should_discard.not().then_some(language.clone())
18515 })
18516 })
18517 .collect::<HashSet<_>>()
18518 });
18519 if !languages_affected.is_empty() {
18520 self.refresh_inlay_hints(
18521 InlayHintRefreshReason::BufferEdited(languages_affected),
18522 cx,
18523 );
18524 }
18525 }
18526 }
18527
18528 let Some(project) = &self.project else { return };
18529 let (telemetry, is_via_ssh) = {
18530 let project = project.read(cx);
18531 let telemetry = project.client().telemetry().clone();
18532 let is_via_ssh = project.is_via_ssh();
18533 (telemetry, is_via_ssh)
18534 };
18535 refresh_linked_ranges(self, window, cx);
18536 telemetry.log_edit_event("editor", is_via_ssh);
18537 }
18538 multi_buffer::Event::ExcerptsAdded {
18539 buffer,
18540 predecessor,
18541 excerpts,
18542 } => {
18543 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18544 let buffer_id = buffer.read(cx).remote_id();
18545 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18546 if let Some(project) = &self.project {
18547 update_uncommitted_diff_for_buffer(
18548 cx.entity(),
18549 project,
18550 [buffer.clone()],
18551 self.buffer.clone(),
18552 cx,
18553 )
18554 .detach();
18555 }
18556 }
18557 cx.emit(EditorEvent::ExcerptsAdded {
18558 buffer: buffer.clone(),
18559 predecessor: *predecessor,
18560 excerpts: excerpts.clone(),
18561 });
18562 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18563 }
18564 multi_buffer::Event::ExcerptsRemoved {
18565 ids,
18566 removed_buffer_ids,
18567 } => {
18568 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18569 let buffer = self.buffer.read(cx);
18570 self.registered_buffers
18571 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18572 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18573 cx.emit(EditorEvent::ExcerptsRemoved {
18574 ids: ids.clone(),
18575 removed_buffer_ids: removed_buffer_ids.clone(),
18576 })
18577 }
18578 multi_buffer::Event::ExcerptsEdited {
18579 excerpt_ids,
18580 buffer_ids,
18581 } => {
18582 self.display_map.update(cx, |map, cx| {
18583 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18584 });
18585 cx.emit(EditorEvent::ExcerptsEdited {
18586 ids: excerpt_ids.clone(),
18587 })
18588 }
18589 multi_buffer::Event::ExcerptsExpanded { ids } => {
18590 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18591 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18592 }
18593 multi_buffer::Event::Reparsed(buffer_id) => {
18594 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18595 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18596
18597 cx.emit(EditorEvent::Reparsed(*buffer_id));
18598 }
18599 multi_buffer::Event::DiffHunksToggled => {
18600 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18601 }
18602 multi_buffer::Event::LanguageChanged(buffer_id) => {
18603 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18604 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18605 cx.emit(EditorEvent::Reparsed(*buffer_id));
18606 cx.notify();
18607 }
18608 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18609 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18610 multi_buffer::Event::FileHandleChanged
18611 | multi_buffer::Event::Reloaded
18612 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18613 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18614 multi_buffer::Event::DiagnosticsUpdated => {
18615 self.refresh_active_diagnostics(cx);
18616 self.refresh_inline_diagnostics(true, window, cx);
18617 self.scrollbar_marker_state.dirty = true;
18618 cx.notify();
18619 }
18620 _ => {}
18621 };
18622 }
18623
18624 pub fn start_temporary_diff_override(&mut self) {
18625 self.load_diff_task.take();
18626 self.temporary_diff_override = true;
18627 }
18628
18629 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18630 self.temporary_diff_override = false;
18631 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18632 self.buffer.update(cx, |buffer, cx| {
18633 buffer.set_all_diff_hunks_collapsed(cx);
18634 });
18635
18636 if let Some(project) = self.project.clone() {
18637 self.load_diff_task = Some(
18638 update_uncommitted_diff_for_buffer(
18639 cx.entity(),
18640 &project,
18641 self.buffer.read(cx).all_buffers(),
18642 self.buffer.clone(),
18643 cx,
18644 )
18645 .shared(),
18646 );
18647 }
18648 }
18649
18650 fn on_display_map_changed(
18651 &mut self,
18652 _: Entity<DisplayMap>,
18653 _: &mut Window,
18654 cx: &mut Context<Self>,
18655 ) {
18656 cx.notify();
18657 }
18658
18659 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18660 let new_severity = if self.diagnostics_enabled() {
18661 EditorSettings::get_global(cx)
18662 .diagnostics_max_severity
18663 .unwrap_or(DiagnosticSeverity::Hint)
18664 } else {
18665 DiagnosticSeverity::Off
18666 };
18667 self.set_max_diagnostics_severity(new_severity, cx);
18668 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18669 self.update_edit_prediction_settings(cx);
18670 self.refresh_inline_completion(true, false, window, cx);
18671 self.refresh_inlay_hints(
18672 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18673 self.selections.newest_anchor().head(),
18674 &self.buffer.read(cx).snapshot(cx),
18675 cx,
18676 )),
18677 cx,
18678 );
18679
18680 let old_cursor_shape = self.cursor_shape;
18681
18682 {
18683 let editor_settings = EditorSettings::get_global(cx);
18684 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18685 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18686 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18687 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18688 }
18689
18690 if old_cursor_shape != self.cursor_shape {
18691 cx.emit(EditorEvent::CursorShapeChanged);
18692 }
18693
18694 let project_settings = ProjectSettings::get_global(cx);
18695 self.serialize_dirty_buffers =
18696 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18697
18698 if self.mode.is_full() {
18699 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18700 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18701 if self.show_inline_diagnostics != show_inline_diagnostics {
18702 self.show_inline_diagnostics = show_inline_diagnostics;
18703 self.refresh_inline_diagnostics(false, window, cx);
18704 }
18705
18706 if self.git_blame_inline_enabled != inline_blame_enabled {
18707 self.toggle_git_blame_inline_internal(false, window, cx);
18708 }
18709
18710 let minimap_settings = EditorSettings::get_global(cx).minimap;
18711 if self.minimap_visibility != MinimapVisibility::Disabled {
18712 if self.minimap_visibility.settings_visibility()
18713 != minimap_settings.minimap_enabled()
18714 {
18715 self.set_minimap_visibility(
18716 MinimapVisibility::for_mode(self.mode(), cx),
18717 window,
18718 cx,
18719 );
18720 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18721 minimap_entity.update(cx, |minimap_editor, cx| {
18722 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18723 })
18724 }
18725 }
18726 }
18727
18728 cx.notify();
18729 }
18730
18731 pub fn set_searchable(&mut self, searchable: bool) {
18732 self.searchable = searchable;
18733 }
18734
18735 pub fn searchable(&self) -> bool {
18736 self.searchable
18737 }
18738
18739 fn open_proposed_changes_editor(
18740 &mut self,
18741 _: &OpenProposedChangesEditor,
18742 window: &mut Window,
18743 cx: &mut Context<Self>,
18744 ) {
18745 let Some(workspace) = self.workspace() else {
18746 cx.propagate();
18747 return;
18748 };
18749
18750 let selections = self.selections.all::<usize>(cx);
18751 let multi_buffer = self.buffer.read(cx);
18752 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18753 let mut new_selections_by_buffer = HashMap::default();
18754 for selection in selections {
18755 for (buffer, range, _) in
18756 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18757 {
18758 let mut range = range.to_point(buffer);
18759 range.start.column = 0;
18760 range.end.column = buffer.line_len(range.end.row);
18761 new_selections_by_buffer
18762 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18763 .or_insert(Vec::new())
18764 .push(range)
18765 }
18766 }
18767
18768 let proposed_changes_buffers = new_selections_by_buffer
18769 .into_iter()
18770 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18771 .collect::<Vec<_>>();
18772 let proposed_changes_editor = cx.new(|cx| {
18773 ProposedChangesEditor::new(
18774 "Proposed changes",
18775 proposed_changes_buffers,
18776 self.project.clone(),
18777 window,
18778 cx,
18779 )
18780 });
18781
18782 window.defer(cx, move |window, cx| {
18783 workspace.update(cx, |workspace, cx| {
18784 workspace.active_pane().update(cx, |pane, cx| {
18785 pane.add_item(
18786 Box::new(proposed_changes_editor),
18787 true,
18788 true,
18789 None,
18790 window,
18791 cx,
18792 );
18793 });
18794 });
18795 });
18796 }
18797
18798 pub fn open_excerpts_in_split(
18799 &mut self,
18800 _: &OpenExcerptsSplit,
18801 window: &mut Window,
18802 cx: &mut Context<Self>,
18803 ) {
18804 self.open_excerpts_common(None, true, window, cx)
18805 }
18806
18807 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18808 self.open_excerpts_common(None, false, window, cx)
18809 }
18810
18811 fn open_excerpts_common(
18812 &mut self,
18813 jump_data: Option<JumpData>,
18814 split: bool,
18815 window: &mut Window,
18816 cx: &mut Context<Self>,
18817 ) {
18818 let Some(workspace) = self.workspace() else {
18819 cx.propagate();
18820 return;
18821 };
18822
18823 if self.buffer.read(cx).is_singleton() {
18824 cx.propagate();
18825 return;
18826 }
18827
18828 let mut new_selections_by_buffer = HashMap::default();
18829 match &jump_data {
18830 Some(JumpData::MultiBufferPoint {
18831 excerpt_id,
18832 position,
18833 anchor,
18834 line_offset_from_top,
18835 }) => {
18836 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18837 if let Some(buffer) = multi_buffer_snapshot
18838 .buffer_id_for_excerpt(*excerpt_id)
18839 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18840 {
18841 let buffer_snapshot = buffer.read(cx).snapshot();
18842 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18843 language::ToPoint::to_point(anchor, &buffer_snapshot)
18844 } else {
18845 buffer_snapshot.clip_point(*position, Bias::Left)
18846 };
18847 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18848 new_selections_by_buffer.insert(
18849 buffer,
18850 (
18851 vec![jump_to_offset..jump_to_offset],
18852 Some(*line_offset_from_top),
18853 ),
18854 );
18855 }
18856 }
18857 Some(JumpData::MultiBufferRow {
18858 row,
18859 line_offset_from_top,
18860 }) => {
18861 let point = MultiBufferPoint::new(row.0, 0);
18862 if let Some((buffer, buffer_point, _)) =
18863 self.buffer.read(cx).point_to_buffer_point(point, cx)
18864 {
18865 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18866 new_selections_by_buffer
18867 .entry(buffer)
18868 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18869 .0
18870 .push(buffer_offset..buffer_offset)
18871 }
18872 }
18873 None => {
18874 let selections = self.selections.all::<usize>(cx);
18875 let multi_buffer = self.buffer.read(cx);
18876 for selection in selections {
18877 for (snapshot, range, _, anchor) in multi_buffer
18878 .snapshot(cx)
18879 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18880 {
18881 if let Some(anchor) = anchor {
18882 // selection is in a deleted hunk
18883 let Some(buffer_id) = anchor.buffer_id else {
18884 continue;
18885 };
18886 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18887 continue;
18888 };
18889 let offset = text::ToOffset::to_offset(
18890 &anchor.text_anchor,
18891 &buffer_handle.read(cx).snapshot(),
18892 );
18893 let range = offset..offset;
18894 new_selections_by_buffer
18895 .entry(buffer_handle)
18896 .or_insert((Vec::new(), None))
18897 .0
18898 .push(range)
18899 } else {
18900 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18901 else {
18902 continue;
18903 };
18904 new_selections_by_buffer
18905 .entry(buffer_handle)
18906 .or_insert((Vec::new(), None))
18907 .0
18908 .push(range)
18909 }
18910 }
18911 }
18912 }
18913 }
18914
18915 new_selections_by_buffer
18916 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18917
18918 if new_selections_by_buffer.is_empty() {
18919 return;
18920 }
18921
18922 // We defer the pane interaction because we ourselves are a workspace item
18923 // and activating a new item causes the pane to call a method on us reentrantly,
18924 // which panics if we're on the stack.
18925 window.defer(cx, move |window, cx| {
18926 workspace.update(cx, |workspace, cx| {
18927 let pane = if split {
18928 workspace.adjacent_pane(window, cx)
18929 } else {
18930 workspace.active_pane().clone()
18931 };
18932
18933 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18934 let editor = buffer
18935 .read(cx)
18936 .file()
18937 .is_none()
18938 .then(|| {
18939 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18940 // so `workspace.open_project_item` will never find them, always opening a new editor.
18941 // Instead, we try to activate the existing editor in the pane first.
18942 let (editor, pane_item_index) =
18943 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18944 let editor = item.downcast::<Editor>()?;
18945 let singleton_buffer =
18946 editor.read(cx).buffer().read(cx).as_singleton()?;
18947 if singleton_buffer == buffer {
18948 Some((editor, i))
18949 } else {
18950 None
18951 }
18952 })?;
18953 pane.update(cx, |pane, cx| {
18954 pane.activate_item(pane_item_index, true, true, window, cx)
18955 });
18956 Some(editor)
18957 })
18958 .flatten()
18959 .unwrap_or_else(|| {
18960 workspace.open_project_item::<Self>(
18961 pane.clone(),
18962 buffer,
18963 true,
18964 true,
18965 window,
18966 cx,
18967 )
18968 });
18969
18970 editor.update(cx, |editor, cx| {
18971 let autoscroll = match scroll_offset {
18972 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18973 None => Autoscroll::newest(),
18974 };
18975 let nav_history = editor.nav_history.take();
18976 editor.change_selections(Some(autoscroll), window, cx, |s| {
18977 s.select_ranges(ranges);
18978 });
18979 editor.nav_history = nav_history;
18980 });
18981 }
18982 })
18983 });
18984 }
18985
18986 // For now, don't allow opening excerpts in buffers that aren't backed by
18987 // regular project files.
18988 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18989 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18990 }
18991
18992 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18993 let snapshot = self.buffer.read(cx).read(cx);
18994 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18995 Some(
18996 ranges
18997 .iter()
18998 .map(move |range| {
18999 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19000 })
19001 .collect(),
19002 )
19003 }
19004
19005 fn selection_replacement_ranges(
19006 &self,
19007 range: Range<OffsetUtf16>,
19008 cx: &mut App,
19009 ) -> Vec<Range<OffsetUtf16>> {
19010 let selections = self.selections.all::<OffsetUtf16>(cx);
19011 let newest_selection = selections
19012 .iter()
19013 .max_by_key(|selection| selection.id)
19014 .unwrap();
19015 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19016 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19017 let snapshot = self.buffer.read(cx).read(cx);
19018 selections
19019 .into_iter()
19020 .map(|mut selection| {
19021 selection.start.0 =
19022 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19023 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19024 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19025 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19026 })
19027 .collect()
19028 }
19029
19030 fn report_editor_event(
19031 &self,
19032 event_type: &'static str,
19033 file_extension: Option<String>,
19034 cx: &App,
19035 ) {
19036 if cfg!(any(test, feature = "test-support")) {
19037 return;
19038 }
19039
19040 let Some(project) = &self.project else { return };
19041
19042 // If None, we are in a file without an extension
19043 let file = self
19044 .buffer
19045 .read(cx)
19046 .as_singleton()
19047 .and_then(|b| b.read(cx).file());
19048 let file_extension = file_extension.or(file
19049 .as_ref()
19050 .and_then(|file| Path::new(file.file_name(cx)).extension())
19051 .and_then(|e| e.to_str())
19052 .map(|a| a.to_string()));
19053
19054 let vim_mode = vim_enabled(cx);
19055
19056 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19057 let copilot_enabled = edit_predictions_provider
19058 == language::language_settings::EditPredictionProvider::Copilot;
19059 let copilot_enabled_for_language = self
19060 .buffer
19061 .read(cx)
19062 .language_settings(cx)
19063 .show_edit_predictions;
19064
19065 let project = project.read(cx);
19066 telemetry::event!(
19067 event_type,
19068 file_extension,
19069 vim_mode,
19070 copilot_enabled,
19071 copilot_enabled_for_language,
19072 edit_predictions_provider,
19073 is_via_ssh = project.is_via_ssh(),
19074 );
19075 }
19076
19077 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19078 /// with each line being an array of {text, highlight} objects.
19079 fn copy_highlight_json(
19080 &mut self,
19081 _: &CopyHighlightJson,
19082 window: &mut Window,
19083 cx: &mut Context<Self>,
19084 ) {
19085 #[derive(Serialize)]
19086 struct Chunk<'a> {
19087 text: String,
19088 highlight: Option<&'a str>,
19089 }
19090
19091 let snapshot = self.buffer.read(cx).snapshot(cx);
19092 let range = self
19093 .selected_text_range(false, window, cx)
19094 .and_then(|selection| {
19095 if selection.range.is_empty() {
19096 None
19097 } else {
19098 Some(selection.range)
19099 }
19100 })
19101 .unwrap_or_else(|| 0..snapshot.len());
19102
19103 let chunks = snapshot.chunks(range, true);
19104 let mut lines = Vec::new();
19105 let mut line: VecDeque<Chunk> = VecDeque::new();
19106
19107 let Some(style) = self.style.as_ref() else {
19108 return;
19109 };
19110
19111 for chunk in chunks {
19112 let highlight = chunk
19113 .syntax_highlight_id
19114 .and_then(|id| id.name(&style.syntax));
19115 let mut chunk_lines = chunk.text.split('\n').peekable();
19116 while let Some(text) = chunk_lines.next() {
19117 let mut merged_with_last_token = false;
19118 if let Some(last_token) = line.back_mut() {
19119 if last_token.highlight == highlight {
19120 last_token.text.push_str(text);
19121 merged_with_last_token = true;
19122 }
19123 }
19124
19125 if !merged_with_last_token {
19126 line.push_back(Chunk {
19127 text: text.into(),
19128 highlight,
19129 });
19130 }
19131
19132 if chunk_lines.peek().is_some() {
19133 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19134 line.pop_front();
19135 }
19136 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19137 line.pop_back();
19138 }
19139
19140 lines.push(mem::take(&mut line));
19141 }
19142 }
19143 }
19144
19145 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19146 return;
19147 };
19148 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19149 }
19150
19151 pub fn open_context_menu(
19152 &mut self,
19153 _: &OpenContextMenu,
19154 window: &mut Window,
19155 cx: &mut Context<Self>,
19156 ) {
19157 self.request_autoscroll(Autoscroll::newest(), cx);
19158 let position = self.selections.newest_display(cx).start;
19159 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19160 }
19161
19162 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19163 &self.inlay_hint_cache
19164 }
19165
19166 pub fn replay_insert_event(
19167 &mut self,
19168 text: &str,
19169 relative_utf16_range: Option<Range<isize>>,
19170 window: &mut Window,
19171 cx: &mut Context<Self>,
19172 ) {
19173 if !self.input_enabled {
19174 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19175 return;
19176 }
19177 if let Some(relative_utf16_range) = relative_utf16_range {
19178 let selections = self.selections.all::<OffsetUtf16>(cx);
19179 self.change_selections(None, window, cx, |s| {
19180 let new_ranges = selections.into_iter().map(|range| {
19181 let start = OffsetUtf16(
19182 range
19183 .head()
19184 .0
19185 .saturating_add_signed(relative_utf16_range.start),
19186 );
19187 let end = OffsetUtf16(
19188 range
19189 .head()
19190 .0
19191 .saturating_add_signed(relative_utf16_range.end),
19192 );
19193 start..end
19194 });
19195 s.select_ranges(new_ranges);
19196 });
19197 }
19198
19199 self.handle_input(text, window, cx);
19200 }
19201
19202 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19203 let Some(provider) = self.semantics_provider.as_ref() else {
19204 return false;
19205 };
19206
19207 let mut supports = false;
19208 self.buffer().update(cx, |this, cx| {
19209 this.for_each_buffer(|buffer| {
19210 supports |= provider.supports_inlay_hints(buffer, cx);
19211 });
19212 });
19213
19214 supports
19215 }
19216
19217 pub fn is_focused(&self, window: &Window) -> bool {
19218 self.focus_handle.is_focused(window)
19219 }
19220
19221 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19222 cx.emit(EditorEvent::Focused);
19223
19224 if let Some(descendant) = self
19225 .last_focused_descendant
19226 .take()
19227 .and_then(|descendant| descendant.upgrade())
19228 {
19229 window.focus(&descendant);
19230 } else {
19231 if let Some(blame) = self.blame.as_ref() {
19232 blame.update(cx, GitBlame::focus)
19233 }
19234
19235 self.blink_manager.update(cx, BlinkManager::enable);
19236 self.show_cursor_names(window, cx);
19237 self.buffer.update(cx, |buffer, cx| {
19238 buffer.finalize_last_transaction(cx);
19239 if self.leader_id.is_none() {
19240 buffer.set_active_selections(
19241 &self.selections.disjoint_anchors(),
19242 self.selections.line_mode,
19243 self.cursor_shape,
19244 cx,
19245 );
19246 }
19247 });
19248 }
19249 }
19250
19251 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19252 cx.emit(EditorEvent::FocusedIn)
19253 }
19254
19255 fn handle_focus_out(
19256 &mut self,
19257 event: FocusOutEvent,
19258 _window: &mut Window,
19259 cx: &mut Context<Self>,
19260 ) {
19261 if event.blurred != self.focus_handle {
19262 self.last_focused_descendant = Some(event.blurred);
19263 }
19264 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19265 }
19266
19267 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19268 self.blink_manager.update(cx, BlinkManager::disable);
19269 self.buffer
19270 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19271
19272 if let Some(blame) = self.blame.as_ref() {
19273 blame.update(cx, GitBlame::blur)
19274 }
19275 if !self.hover_state.focused(window, cx) {
19276 hide_hover(self, cx);
19277 }
19278 if !self
19279 .context_menu
19280 .borrow()
19281 .as_ref()
19282 .is_some_and(|context_menu| context_menu.focused(window, cx))
19283 {
19284 self.hide_context_menu(window, cx);
19285 }
19286 self.discard_inline_completion(false, cx);
19287 cx.emit(EditorEvent::Blurred);
19288 cx.notify();
19289 }
19290
19291 pub fn register_action<A: Action>(
19292 &mut self,
19293 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19294 ) -> Subscription {
19295 let id = self.next_editor_action_id.post_inc();
19296 let listener = Arc::new(listener);
19297 self.editor_actions.borrow_mut().insert(
19298 id,
19299 Box::new(move |window, _| {
19300 let listener = listener.clone();
19301 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19302 let action = action.downcast_ref().unwrap();
19303 if phase == DispatchPhase::Bubble {
19304 listener(action, window, cx)
19305 }
19306 })
19307 }),
19308 );
19309
19310 let editor_actions = self.editor_actions.clone();
19311 Subscription::new(move || {
19312 editor_actions.borrow_mut().remove(&id);
19313 })
19314 }
19315
19316 pub fn file_header_size(&self) -> u32 {
19317 FILE_HEADER_HEIGHT
19318 }
19319
19320 pub fn restore(
19321 &mut self,
19322 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19323 window: &mut Window,
19324 cx: &mut Context<Self>,
19325 ) {
19326 let workspace = self.workspace();
19327 let project = self.project.as_ref();
19328 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19329 let mut tasks = Vec::new();
19330 for (buffer_id, changes) in revert_changes {
19331 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19332 buffer.update(cx, |buffer, cx| {
19333 buffer.edit(
19334 changes
19335 .into_iter()
19336 .map(|(range, text)| (range, text.to_string())),
19337 None,
19338 cx,
19339 );
19340 });
19341
19342 if let Some(project) =
19343 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19344 {
19345 project.update(cx, |project, cx| {
19346 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19347 })
19348 }
19349 }
19350 }
19351 tasks
19352 });
19353 cx.spawn_in(window, async move |_, cx| {
19354 for (buffer, task) in save_tasks {
19355 let result = task.await;
19356 if result.is_err() {
19357 let Some(path) = buffer
19358 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19359 .ok()
19360 else {
19361 continue;
19362 };
19363 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19364 let Some(task) = cx
19365 .update_window_entity(&workspace, |workspace, window, cx| {
19366 workspace
19367 .open_path_preview(path, None, false, false, false, window, cx)
19368 })
19369 .ok()
19370 else {
19371 continue;
19372 };
19373 task.await.log_err();
19374 }
19375 }
19376 }
19377 })
19378 .detach();
19379 self.change_selections(None, window, cx, |selections| selections.refresh());
19380 }
19381
19382 pub fn to_pixel_point(
19383 &self,
19384 source: multi_buffer::Anchor,
19385 editor_snapshot: &EditorSnapshot,
19386 window: &mut Window,
19387 ) -> Option<gpui::Point<Pixels>> {
19388 let source_point = source.to_display_point(editor_snapshot);
19389 self.display_to_pixel_point(source_point, editor_snapshot, window)
19390 }
19391
19392 pub fn display_to_pixel_point(
19393 &self,
19394 source: DisplayPoint,
19395 editor_snapshot: &EditorSnapshot,
19396 window: &mut Window,
19397 ) -> Option<gpui::Point<Pixels>> {
19398 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19399 let text_layout_details = self.text_layout_details(window);
19400 let scroll_top = text_layout_details
19401 .scroll_anchor
19402 .scroll_position(editor_snapshot)
19403 .y;
19404
19405 if source.row().as_f32() < scroll_top.floor() {
19406 return None;
19407 }
19408 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19409 let source_y = line_height * (source.row().as_f32() - scroll_top);
19410 Some(gpui::Point::new(source_x, source_y))
19411 }
19412
19413 pub fn has_visible_completions_menu(&self) -> bool {
19414 !self.edit_prediction_preview_is_active()
19415 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19416 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19417 })
19418 }
19419
19420 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19421 if self.mode.is_minimap() {
19422 return;
19423 }
19424 self.addons
19425 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19426 }
19427
19428 pub fn unregister_addon<T: Addon>(&mut self) {
19429 self.addons.remove(&std::any::TypeId::of::<T>());
19430 }
19431
19432 pub fn addon<T: Addon>(&self) -> Option<&T> {
19433 let type_id = std::any::TypeId::of::<T>();
19434 self.addons
19435 .get(&type_id)
19436 .and_then(|item| item.to_any().downcast_ref::<T>())
19437 }
19438
19439 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19440 let type_id = std::any::TypeId::of::<T>();
19441 self.addons
19442 .get_mut(&type_id)
19443 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19444 }
19445
19446 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19447 let text_layout_details = self.text_layout_details(window);
19448 let style = &text_layout_details.editor_style;
19449 let font_id = window.text_system().resolve_font(&style.text.font());
19450 let font_size = style.text.font_size.to_pixels(window.rem_size());
19451 let line_height = style.text.line_height_in_pixels(window.rem_size());
19452 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19453
19454 gpui::Size::new(em_width, line_height)
19455 }
19456
19457 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
19458 self.load_diff_task.clone()
19459 }
19460
19461 fn read_metadata_from_db(
19462 &mut self,
19463 item_id: u64,
19464 workspace_id: WorkspaceId,
19465 window: &mut Window,
19466 cx: &mut Context<Editor>,
19467 ) {
19468 if self.is_singleton(cx)
19469 && !self.mode.is_minimap()
19470 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
19471 {
19472 let buffer_snapshot = OnceCell::new();
19473
19474 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
19475 if !folds.is_empty() {
19476 let snapshot =
19477 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19478 self.fold_ranges(
19479 folds
19480 .into_iter()
19481 .map(|(start, end)| {
19482 snapshot.clip_offset(start, Bias::Left)
19483 ..snapshot.clip_offset(end, Bias::Right)
19484 })
19485 .collect(),
19486 false,
19487 window,
19488 cx,
19489 );
19490 }
19491 }
19492
19493 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
19494 if !selections.is_empty() {
19495 let snapshot =
19496 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19497 self.change_selections(None, window, cx, |s| {
19498 s.select_ranges(selections.into_iter().map(|(start, end)| {
19499 snapshot.clip_offset(start, Bias::Left)
19500 ..snapshot.clip_offset(end, Bias::Right)
19501 }));
19502 });
19503 }
19504 };
19505 }
19506
19507 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
19508 }
19509}
19510
19511fn vim_enabled(cx: &App) -> bool {
19512 cx.global::<SettingsStore>()
19513 .raw_user_settings()
19514 .get("vim_mode")
19515 == Some(&serde_json::Value::Bool(true))
19516}
19517
19518// Consider user intent and default settings
19519fn choose_completion_range(
19520 completion: &Completion,
19521 intent: CompletionIntent,
19522 buffer: &Entity<Buffer>,
19523 cx: &mut Context<Editor>,
19524) -> Range<usize> {
19525 fn should_replace(
19526 completion: &Completion,
19527 insert_range: &Range<text::Anchor>,
19528 intent: CompletionIntent,
19529 completion_mode_setting: LspInsertMode,
19530 buffer: &Buffer,
19531 ) -> bool {
19532 // specific actions take precedence over settings
19533 match intent {
19534 CompletionIntent::CompleteWithInsert => return false,
19535 CompletionIntent::CompleteWithReplace => return true,
19536 CompletionIntent::Complete | CompletionIntent::Compose => {}
19537 }
19538
19539 match completion_mode_setting {
19540 LspInsertMode::Insert => false,
19541 LspInsertMode::Replace => true,
19542 LspInsertMode::ReplaceSubsequence => {
19543 let mut text_to_replace = buffer.chars_for_range(
19544 buffer.anchor_before(completion.replace_range.start)
19545 ..buffer.anchor_after(completion.replace_range.end),
19546 );
19547 let mut completion_text = completion.new_text.chars();
19548
19549 // is `text_to_replace` a subsequence of `completion_text`
19550 text_to_replace
19551 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
19552 }
19553 LspInsertMode::ReplaceSuffix => {
19554 let range_after_cursor = insert_range.end..completion.replace_range.end;
19555
19556 let text_after_cursor = buffer
19557 .text_for_range(
19558 buffer.anchor_before(range_after_cursor.start)
19559 ..buffer.anchor_after(range_after_cursor.end),
19560 )
19561 .collect::<String>();
19562 completion.new_text.ends_with(&text_after_cursor)
19563 }
19564 }
19565 }
19566
19567 let buffer = buffer.read(cx);
19568
19569 if let CompletionSource::Lsp {
19570 insert_range: Some(insert_range),
19571 ..
19572 } = &completion.source
19573 {
19574 let completion_mode_setting =
19575 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
19576 .completions
19577 .lsp_insert_mode;
19578
19579 if !should_replace(
19580 completion,
19581 &insert_range,
19582 intent,
19583 completion_mode_setting,
19584 buffer,
19585 ) {
19586 return insert_range.to_offset(buffer);
19587 }
19588 }
19589
19590 completion.replace_range.to_offset(buffer)
19591}
19592
19593fn insert_extra_newline_brackets(
19594 buffer: &MultiBufferSnapshot,
19595 range: Range<usize>,
19596 language: &language::LanguageScope,
19597) -> bool {
19598 let leading_whitespace_len = buffer
19599 .reversed_chars_at(range.start)
19600 .take_while(|c| c.is_whitespace() && *c != '\n')
19601 .map(|c| c.len_utf8())
19602 .sum::<usize>();
19603 let trailing_whitespace_len = buffer
19604 .chars_at(range.end)
19605 .take_while(|c| c.is_whitespace() && *c != '\n')
19606 .map(|c| c.len_utf8())
19607 .sum::<usize>();
19608 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
19609
19610 language.brackets().any(|(pair, enabled)| {
19611 let pair_start = pair.start.trim_end();
19612 let pair_end = pair.end.trim_start();
19613
19614 enabled
19615 && pair.newline
19616 && buffer.contains_str_at(range.end, pair_end)
19617 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
19618 })
19619}
19620
19621fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
19622 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
19623 [(buffer, range, _)] => (*buffer, range.clone()),
19624 _ => return false,
19625 };
19626 let pair = {
19627 let mut result: Option<BracketMatch> = None;
19628
19629 for pair in buffer
19630 .all_bracket_ranges(range.clone())
19631 .filter(move |pair| {
19632 pair.open_range.start <= range.start && pair.close_range.end >= range.end
19633 })
19634 {
19635 let len = pair.close_range.end - pair.open_range.start;
19636
19637 if let Some(existing) = &result {
19638 let existing_len = existing.close_range.end - existing.open_range.start;
19639 if len > existing_len {
19640 continue;
19641 }
19642 }
19643
19644 result = Some(pair);
19645 }
19646
19647 result
19648 };
19649 let Some(pair) = pair else {
19650 return false;
19651 };
19652 pair.newline_only
19653 && buffer
19654 .chars_for_range(pair.open_range.end..range.start)
19655 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
19656 .all(|c| c.is_whitespace() && c != '\n')
19657}
19658
19659fn update_uncommitted_diff_for_buffer(
19660 editor: Entity<Editor>,
19661 project: &Entity<Project>,
19662 buffers: impl IntoIterator<Item = Entity<Buffer>>,
19663 buffer: Entity<MultiBuffer>,
19664 cx: &mut App,
19665) -> Task<()> {
19666 let mut tasks = Vec::new();
19667 project.update(cx, |project, cx| {
19668 for buffer in buffers {
19669 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
19670 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
19671 }
19672 }
19673 });
19674 cx.spawn(async move |cx| {
19675 let diffs = future::join_all(tasks).await;
19676 if editor
19677 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19678 .unwrap_or(false)
19679 {
19680 return;
19681 }
19682
19683 buffer
19684 .update(cx, |buffer, cx| {
19685 for diff in diffs.into_iter().flatten() {
19686 buffer.add_diff(diff, cx);
19687 }
19688 })
19689 .ok();
19690 })
19691}
19692
19693fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
19694 let tab_size = tab_size.get() as usize;
19695 let mut width = offset;
19696
19697 for ch in text.chars() {
19698 width += if ch == '\t' {
19699 tab_size - (width % tab_size)
19700 } else {
19701 1
19702 };
19703 }
19704
19705 width - offset
19706}
19707
19708#[cfg(test)]
19709mod tests {
19710 use super::*;
19711
19712 #[test]
19713 fn test_string_size_with_expanded_tabs() {
19714 let nz = |val| NonZeroU32::new(val).unwrap();
19715 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
19716 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
19717 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
19718 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
19719 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
19720 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
19721 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
19722 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
19723 }
19724}
19725
19726/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
19727struct WordBreakingTokenizer<'a> {
19728 input: &'a str,
19729}
19730
19731impl<'a> WordBreakingTokenizer<'a> {
19732 fn new(input: &'a str) -> Self {
19733 Self { input }
19734 }
19735}
19736
19737fn is_char_ideographic(ch: char) -> bool {
19738 use unicode_script::Script::*;
19739 use unicode_script::UnicodeScript;
19740 matches!(ch.script(), Han | Tangut | Yi)
19741}
19742
19743fn is_grapheme_ideographic(text: &str) -> bool {
19744 text.chars().any(is_char_ideographic)
19745}
19746
19747fn is_grapheme_whitespace(text: &str) -> bool {
19748 text.chars().any(|x| x.is_whitespace())
19749}
19750
19751fn should_stay_with_preceding_ideograph(text: &str) -> bool {
19752 text.chars().next().map_or(false, |ch| {
19753 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
19754 })
19755}
19756
19757#[derive(PartialEq, Eq, Debug, Clone, Copy)]
19758enum WordBreakToken<'a> {
19759 Word { token: &'a str, grapheme_len: usize },
19760 InlineWhitespace { token: &'a str, grapheme_len: usize },
19761 Newline,
19762}
19763
19764impl<'a> Iterator for WordBreakingTokenizer<'a> {
19765 /// Yields a span, the count of graphemes in the token, and whether it was
19766 /// whitespace. Note that it also breaks at word boundaries.
19767 type Item = WordBreakToken<'a>;
19768
19769 fn next(&mut self) -> Option<Self::Item> {
19770 use unicode_segmentation::UnicodeSegmentation;
19771 if self.input.is_empty() {
19772 return None;
19773 }
19774
19775 let mut iter = self.input.graphemes(true).peekable();
19776 let mut offset = 0;
19777 let mut grapheme_len = 0;
19778 if let Some(first_grapheme) = iter.next() {
19779 let is_newline = first_grapheme == "\n";
19780 let is_whitespace = is_grapheme_whitespace(first_grapheme);
19781 offset += first_grapheme.len();
19782 grapheme_len += 1;
19783 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
19784 if let Some(grapheme) = iter.peek().copied() {
19785 if should_stay_with_preceding_ideograph(grapheme) {
19786 offset += grapheme.len();
19787 grapheme_len += 1;
19788 }
19789 }
19790 } else {
19791 let mut words = self.input[offset..].split_word_bound_indices().peekable();
19792 let mut next_word_bound = words.peek().copied();
19793 if next_word_bound.map_or(false, |(i, _)| i == 0) {
19794 next_word_bound = words.next();
19795 }
19796 while let Some(grapheme) = iter.peek().copied() {
19797 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19798 break;
19799 };
19800 if is_grapheme_whitespace(grapheme) != is_whitespace
19801 || (grapheme == "\n") != is_newline
19802 {
19803 break;
19804 };
19805 offset += grapheme.len();
19806 grapheme_len += 1;
19807 iter.next();
19808 }
19809 }
19810 let token = &self.input[..offset];
19811 self.input = &self.input[offset..];
19812 if token == "\n" {
19813 Some(WordBreakToken::Newline)
19814 } else if is_whitespace {
19815 Some(WordBreakToken::InlineWhitespace {
19816 token,
19817 grapheme_len,
19818 })
19819 } else {
19820 Some(WordBreakToken::Word {
19821 token,
19822 grapheme_len,
19823 })
19824 }
19825 } else {
19826 None
19827 }
19828 }
19829}
19830
19831#[test]
19832fn test_word_breaking_tokenizer() {
19833 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
19834 ("", &[]),
19835 (" ", &[whitespace(" ", 2)]),
19836 ("Ʒ", &[word("Ʒ", 1)]),
19837 ("Ǽ", &[word("Ǽ", 1)]),
19838 ("⋑", &[word("⋑", 1)]),
19839 ("⋑⋑", &[word("⋑⋑", 2)]),
19840 (
19841 "原理,进而",
19842 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
19843 ),
19844 (
19845 "hello world",
19846 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
19847 ),
19848 (
19849 "hello, world",
19850 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
19851 ),
19852 (
19853 " hello world",
19854 &[
19855 whitespace(" ", 2),
19856 word("hello", 5),
19857 whitespace(" ", 1),
19858 word("world", 5),
19859 ],
19860 ),
19861 (
19862 "这是什么 \n 钢笔",
19863 &[
19864 word("这", 1),
19865 word("是", 1),
19866 word("什", 1),
19867 word("么", 1),
19868 whitespace(" ", 1),
19869 newline(),
19870 whitespace(" ", 1),
19871 word("钢", 1),
19872 word("笔", 1),
19873 ],
19874 ),
19875 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
19876 ];
19877
19878 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19879 WordBreakToken::Word {
19880 token,
19881 grapheme_len,
19882 }
19883 }
19884
19885 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19886 WordBreakToken::InlineWhitespace {
19887 token,
19888 grapheme_len,
19889 }
19890 }
19891
19892 fn newline() -> WordBreakToken<'static> {
19893 WordBreakToken::Newline
19894 }
19895
19896 for (input, result) in tests {
19897 assert_eq!(
19898 WordBreakingTokenizer::new(input)
19899 .collect::<Vec<_>>()
19900 .as_slice(),
19901 *result,
19902 );
19903 }
19904}
19905
19906fn wrap_with_prefix(
19907 line_prefix: String,
19908 unwrapped_text: String,
19909 wrap_column: usize,
19910 tab_size: NonZeroU32,
19911 preserve_existing_whitespace: bool,
19912) -> String {
19913 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
19914 let mut wrapped_text = String::new();
19915 let mut current_line = line_prefix.clone();
19916
19917 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
19918 let mut current_line_len = line_prefix_len;
19919 let mut in_whitespace = false;
19920 for token in tokenizer {
19921 let have_preceding_whitespace = in_whitespace;
19922 match token {
19923 WordBreakToken::Word {
19924 token,
19925 grapheme_len,
19926 } => {
19927 in_whitespace = false;
19928 if current_line_len + grapheme_len > wrap_column
19929 && current_line_len != line_prefix_len
19930 {
19931 wrapped_text.push_str(current_line.trim_end());
19932 wrapped_text.push('\n');
19933 current_line.truncate(line_prefix.len());
19934 current_line_len = line_prefix_len;
19935 }
19936 current_line.push_str(token);
19937 current_line_len += grapheme_len;
19938 }
19939 WordBreakToken::InlineWhitespace {
19940 mut token,
19941 mut grapheme_len,
19942 } => {
19943 in_whitespace = true;
19944 if have_preceding_whitespace && !preserve_existing_whitespace {
19945 continue;
19946 }
19947 if !preserve_existing_whitespace {
19948 token = " ";
19949 grapheme_len = 1;
19950 }
19951 if current_line_len + grapheme_len > wrap_column {
19952 wrapped_text.push_str(current_line.trim_end());
19953 wrapped_text.push('\n');
19954 current_line.truncate(line_prefix.len());
19955 current_line_len = line_prefix_len;
19956 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
19957 current_line.push_str(token);
19958 current_line_len += grapheme_len;
19959 }
19960 }
19961 WordBreakToken::Newline => {
19962 in_whitespace = true;
19963 if preserve_existing_whitespace {
19964 wrapped_text.push_str(current_line.trim_end());
19965 wrapped_text.push('\n');
19966 current_line.truncate(line_prefix.len());
19967 current_line_len = line_prefix_len;
19968 } else if have_preceding_whitespace {
19969 continue;
19970 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
19971 {
19972 wrapped_text.push_str(current_line.trim_end());
19973 wrapped_text.push('\n');
19974 current_line.truncate(line_prefix.len());
19975 current_line_len = line_prefix_len;
19976 } else if current_line_len != line_prefix_len {
19977 current_line.push(' ');
19978 current_line_len += 1;
19979 }
19980 }
19981 }
19982 }
19983
19984 if !current_line.is_empty() {
19985 wrapped_text.push_str(¤t_line);
19986 }
19987 wrapped_text
19988}
19989
19990#[test]
19991fn test_wrap_with_prefix() {
19992 assert_eq!(
19993 wrap_with_prefix(
19994 "# ".to_string(),
19995 "abcdefg".to_string(),
19996 4,
19997 NonZeroU32::new(4).unwrap(),
19998 false,
19999 ),
20000 "# abcdefg"
20001 );
20002 assert_eq!(
20003 wrap_with_prefix(
20004 "".to_string(),
20005 "\thello world".to_string(),
20006 8,
20007 NonZeroU32::new(4).unwrap(),
20008 false,
20009 ),
20010 "hello\nworld"
20011 );
20012 assert_eq!(
20013 wrap_with_prefix(
20014 "// ".to_string(),
20015 "xx \nyy zz aa bb cc".to_string(),
20016 12,
20017 NonZeroU32::new(4).unwrap(),
20018 false,
20019 ),
20020 "// xx yy zz\n// aa bb cc"
20021 );
20022 assert_eq!(
20023 wrap_with_prefix(
20024 String::new(),
20025 "这是什么 \n 钢笔".to_string(),
20026 3,
20027 NonZeroU32::new(4).unwrap(),
20028 false,
20029 ),
20030 "这是什\n么 钢\n笔"
20031 );
20032}
20033
20034pub trait CollaborationHub {
20035 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20036 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20037 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20038}
20039
20040impl CollaborationHub for Entity<Project> {
20041 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20042 self.read(cx).collaborators()
20043 }
20044
20045 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
20046 self.read(cx).user_store().read(cx).participant_indices()
20047 }
20048
20049 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
20050 let this = self.read(cx);
20051 let user_ids = this.collaborators().values().map(|c| c.user_id);
20052 this.user_store().read(cx).participant_names(user_ids, cx)
20053 }
20054}
20055
20056pub trait SemanticsProvider {
20057 fn hover(
20058 &self,
20059 buffer: &Entity<Buffer>,
20060 position: text::Anchor,
20061 cx: &mut App,
20062 ) -> Option<Task<Vec<project::Hover>>>;
20063
20064 fn inline_values(
20065 &self,
20066 buffer_handle: Entity<Buffer>,
20067 range: Range<text::Anchor>,
20068 cx: &mut App,
20069 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20070
20071 fn inlay_hints(
20072 &self,
20073 buffer_handle: Entity<Buffer>,
20074 range: Range<text::Anchor>,
20075 cx: &mut App,
20076 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20077
20078 fn resolve_inlay_hint(
20079 &self,
20080 hint: InlayHint,
20081 buffer_handle: Entity<Buffer>,
20082 server_id: LanguageServerId,
20083 cx: &mut App,
20084 ) -> Option<Task<anyhow::Result<InlayHint>>>;
20085
20086 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
20087
20088 fn document_highlights(
20089 &self,
20090 buffer: &Entity<Buffer>,
20091 position: text::Anchor,
20092 cx: &mut App,
20093 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
20094
20095 fn definitions(
20096 &self,
20097 buffer: &Entity<Buffer>,
20098 position: text::Anchor,
20099 kind: GotoDefinitionKind,
20100 cx: &mut App,
20101 ) -> Option<Task<Result<Vec<LocationLink>>>>;
20102
20103 fn range_for_rename(
20104 &self,
20105 buffer: &Entity<Buffer>,
20106 position: text::Anchor,
20107 cx: &mut App,
20108 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
20109
20110 fn perform_rename(
20111 &self,
20112 buffer: &Entity<Buffer>,
20113 position: text::Anchor,
20114 new_name: String,
20115 cx: &mut App,
20116 ) -> Option<Task<Result<ProjectTransaction>>>;
20117}
20118
20119pub trait CompletionProvider {
20120 fn completions(
20121 &self,
20122 excerpt_id: ExcerptId,
20123 buffer: &Entity<Buffer>,
20124 buffer_position: text::Anchor,
20125 trigger: CompletionContext,
20126 window: &mut Window,
20127 cx: &mut Context<Editor>,
20128 ) -> Task<Result<Option<Vec<Completion>>>>;
20129
20130 fn resolve_completions(
20131 &self,
20132 buffer: Entity<Buffer>,
20133 completion_indices: Vec<usize>,
20134 completions: Rc<RefCell<Box<[Completion]>>>,
20135 cx: &mut Context<Editor>,
20136 ) -> Task<Result<bool>>;
20137
20138 fn apply_additional_edits_for_completion(
20139 &self,
20140 _buffer: Entity<Buffer>,
20141 _completions: Rc<RefCell<Box<[Completion]>>>,
20142 _completion_index: usize,
20143 _push_to_history: bool,
20144 _cx: &mut Context<Editor>,
20145 ) -> Task<Result<Option<language::Transaction>>> {
20146 Task::ready(Ok(None))
20147 }
20148
20149 fn is_completion_trigger(
20150 &self,
20151 buffer: &Entity<Buffer>,
20152 position: language::Anchor,
20153 text: &str,
20154 trigger_in_words: bool,
20155 cx: &mut Context<Editor>,
20156 ) -> bool;
20157
20158 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
20159
20160 fn sort_completions(&self) -> bool {
20161 true
20162 }
20163
20164 fn filter_completions(&self) -> bool {
20165 true
20166 }
20167}
20168
20169pub trait CodeActionProvider {
20170 fn id(&self) -> Arc<str>;
20171
20172 fn code_actions(
20173 &self,
20174 buffer: &Entity<Buffer>,
20175 range: Range<text::Anchor>,
20176 window: &mut Window,
20177 cx: &mut App,
20178 ) -> Task<Result<Vec<CodeAction>>>;
20179
20180 fn apply_code_action(
20181 &self,
20182 buffer_handle: Entity<Buffer>,
20183 action: CodeAction,
20184 excerpt_id: ExcerptId,
20185 push_to_history: bool,
20186 window: &mut Window,
20187 cx: &mut App,
20188 ) -> Task<Result<ProjectTransaction>>;
20189}
20190
20191impl CodeActionProvider for Entity<Project> {
20192 fn id(&self) -> Arc<str> {
20193 "project".into()
20194 }
20195
20196 fn code_actions(
20197 &self,
20198 buffer: &Entity<Buffer>,
20199 range: Range<text::Anchor>,
20200 _window: &mut Window,
20201 cx: &mut App,
20202 ) -> Task<Result<Vec<CodeAction>>> {
20203 self.update(cx, |project, cx| {
20204 let code_lens = project.code_lens(buffer, range.clone(), cx);
20205 let code_actions = project.code_actions(buffer, range, None, cx);
20206 cx.background_spawn(async move {
20207 let (code_lens, code_actions) = join(code_lens, code_actions).await;
20208 Ok(code_lens
20209 .context("code lens fetch")?
20210 .into_iter()
20211 .chain(code_actions.context("code action fetch")?)
20212 .collect())
20213 })
20214 })
20215 }
20216
20217 fn apply_code_action(
20218 &self,
20219 buffer_handle: Entity<Buffer>,
20220 action: CodeAction,
20221 _excerpt_id: ExcerptId,
20222 push_to_history: bool,
20223 _window: &mut Window,
20224 cx: &mut App,
20225 ) -> Task<Result<ProjectTransaction>> {
20226 self.update(cx, |project, cx| {
20227 project.apply_code_action(buffer_handle, action, push_to_history, cx)
20228 })
20229 }
20230}
20231
20232fn snippet_completions(
20233 project: &Project,
20234 buffer: &Entity<Buffer>,
20235 buffer_position: text::Anchor,
20236 cx: &mut App,
20237) -> Task<Result<Vec<Completion>>> {
20238 let languages = buffer.read(cx).languages_at(buffer_position);
20239 let snippet_store = project.snippets().read(cx);
20240
20241 let scopes: Vec<_> = languages
20242 .iter()
20243 .filter_map(|language| {
20244 let language_name = language.lsp_id();
20245 let snippets = snippet_store.snippets_for(Some(language_name), cx);
20246
20247 if snippets.is_empty() {
20248 None
20249 } else {
20250 Some((language.default_scope(), snippets))
20251 }
20252 })
20253 .collect();
20254
20255 if scopes.is_empty() {
20256 return Task::ready(Ok(vec![]));
20257 }
20258
20259 let snapshot = buffer.read(cx).text_snapshot();
20260 let chars: String = snapshot
20261 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
20262 .collect();
20263 let executor = cx.background_executor().clone();
20264
20265 cx.background_spawn(async move {
20266 let mut all_results: Vec<Completion> = Vec::new();
20267 for (scope, snippets) in scopes.into_iter() {
20268 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
20269 let mut last_word = chars
20270 .chars()
20271 .take_while(|c| classifier.is_word(*c))
20272 .collect::<String>();
20273 last_word = last_word.chars().rev().collect();
20274
20275 if last_word.is_empty() {
20276 return Ok(vec![]);
20277 }
20278
20279 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
20280 let to_lsp = |point: &text::Anchor| {
20281 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
20282 point_to_lsp(end)
20283 };
20284 let lsp_end = to_lsp(&buffer_position);
20285
20286 let candidates = snippets
20287 .iter()
20288 .enumerate()
20289 .flat_map(|(ix, snippet)| {
20290 snippet
20291 .prefix
20292 .iter()
20293 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
20294 })
20295 .collect::<Vec<StringMatchCandidate>>();
20296
20297 let mut matches = fuzzy::match_strings(
20298 &candidates,
20299 &last_word,
20300 last_word.chars().any(|c| c.is_uppercase()),
20301 100,
20302 &Default::default(),
20303 executor.clone(),
20304 )
20305 .await;
20306
20307 // Remove all candidates where the query's start does not match the start of any word in the candidate
20308 if let Some(query_start) = last_word.chars().next() {
20309 matches.retain(|string_match| {
20310 split_words(&string_match.string).any(|word| {
20311 // Check that the first codepoint of the word as lowercase matches the first
20312 // codepoint of the query as lowercase
20313 word.chars()
20314 .flat_map(|codepoint| codepoint.to_lowercase())
20315 .zip(query_start.to_lowercase())
20316 .all(|(word_cp, query_cp)| word_cp == query_cp)
20317 })
20318 });
20319 }
20320
20321 let matched_strings = matches
20322 .into_iter()
20323 .map(|m| m.string)
20324 .collect::<HashSet<_>>();
20325
20326 let mut result: Vec<Completion> = snippets
20327 .iter()
20328 .filter_map(|snippet| {
20329 let matching_prefix = snippet
20330 .prefix
20331 .iter()
20332 .find(|prefix| matched_strings.contains(*prefix))?;
20333 let start = as_offset - last_word.len();
20334 let start = snapshot.anchor_before(start);
20335 let range = start..buffer_position;
20336 let lsp_start = to_lsp(&start);
20337 let lsp_range = lsp::Range {
20338 start: lsp_start,
20339 end: lsp_end,
20340 };
20341 Some(Completion {
20342 replace_range: range,
20343 new_text: snippet.body.clone(),
20344 source: CompletionSource::Lsp {
20345 insert_range: None,
20346 server_id: LanguageServerId(usize::MAX),
20347 resolved: true,
20348 lsp_completion: Box::new(lsp::CompletionItem {
20349 label: snippet.prefix.first().unwrap().clone(),
20350 kind: Some(CompletionItemKind::SNIPPET),
20351 label_details: snippet.description.as_ref().map(|description| {
20352 lsp::CompletionItemLabelDetails {
20353 detail: Some(description.clone()),
20354 description: None,
20355 }
20356 }),
20357 insert_text_format: Some(InsertTextFormat::SNIPPET),
20358 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20359 lsp::InsertReplaceEdit {
20360 new_text: snippet.body.clone(),
20361 insert: lsp_range,
20362 replace: lsp_range,
20363 },
20364 )),
20365 filter_text: Some(snippet.body.clone()),
20366 sort_text: Some(char::MAX.to_string()),
20367 ..lsp::CompletionItem::default()
20368 }),
20369 lsp_defaults: None,
20370 },
20371 label: CodeLabel {
20372 text: matching_prefix.clone(),
20373 runs: Vec::new(),
20374 filter_range: 0..matching_prefix.len(),
20375 },
20376 icon_path: None,
20377 documentation: Some(
20378 CompletionDocumentation::SingleLineAndMultiLinePlainText {
20379 single_line: snippet.name.clone().into(),
20380 plain_text: snippet
20381 .description
20382 .clone()
20383 .map(|description| description.into()),
20384 },
20385 ),
20386 insert_text_mode: None,
20387 confirm: None,
20388 })
20389 })
20390 .collect();
20391
20392 all_results.append(&mut result);
20393 }
20394
20395 Ok(all_results)
20396 })
20397}
20398
20399impl CompletionProvider for Entity<Project> {
20400 fn completions(
20401 &self,
20402 _excerpt_id: ExcerptId,
20403 buffer: &Entity<Buffer>,
20404 buffer_position: text::Anchor,
20405 options: CompletionContext,
20406 _window: &mut Window,
20407 cx: &mut Context<Editor>,
20408 ) -> Task<Result<Option<Vec<Completion>>>> {
20409 self.update(cx, |project, cx| {
20410 let snippets = snippet_completions(project, buffer, buffer_position, cx);
20411 let project_completions = project.completions(buffer, buffer_position, options, cx);
20412 cx.background_spawn(async move {
20413 let snippets_completions = snippets.await?;
20414 match project_completions.await? {
20415 Some(mut completions) => {
20416 completions.extend(snippets_completions);
20417 Ok(Some(completions))
20418 }
20419 None => {
20420 if snippets_completions.is_empty() {
20421 Ok(None)
20422 } else {
20423 Ok(Some(snippets_completions))
20424 }
20425 }
20426 }
20427 })
20428 })
20429 }
20430
20431 fn resolve_completions(
20432 &self,
20433 buffer: Entity<Buffer>,
20434 completion_indices: Vec<usize>,
20435 completions: Rc<RefCell<Box<[Completion]>>>,
20436 cx: &mut Context<Editor>,
20437 ) -> Task<Result<bool>> {
20438 self.update(cx, |project, cx| {
20439 project.lsp_store().update(cx, |lsp_store, cx| {
20440 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
20441 })
20442 })
20443 }
20444
20445 fn apply_additional_edits_for_completion(
20446 &self,
20447 buffer: Entity<Buffer>,
20448 completions: Rc<RefCell<Box<[Completion]>>>,
20449 completion_index: usize,
20450 push_to_history: bool,
20451 cx: &mut Context<Editor>,
20452 ) -> Task<Result<Option<language::Transaction>>> {
20453 self.update(cx, |project, cx| {
20454 project.lsp_store().update(cx, |lsp_store, cx| {
20455 lsp_store.apply_additional_edits_for_completion(
20456 buffer,
20457 completions,
20458 completion_index,
20459 push_to_history,
20460 cx,
20461 )
20462 })
20463 })
20464 }
20465
20466 fn is_completion_trigger(
20467 &self,
20468 buffer: &Entity<Buffer>,
20469 position: language::Anchor,
20470 text: &str,
20471 trigger_in_words: bool,
20472 cx: &mut Context<Editor>,
20473 ) -> bool {
20474 let mut chars = text.chars();
20475 let char = if let Some(char) = chars.next() {
20476 char
20477 } else {
20478 return false;
20479 };
20480 if chars.next().is_some() {
20481 return false;
20482 }
20483
20484 let buffer = buffer.read(cx);
20485 let snapshot = buffer.snapshot();
20486 if !snapshot.settings_at(position, cx).show_completions_on_input {
20487 return false;
20488 }
20489 let classifier = snapshot.char_classifier_at(position).for_completion(true);
20490 if trigger_in_words && classifier.is_word(char) {
20491 return true;
20492 }
20493
20494 buffer.completion_triggers().contains(text)
20495 }
20496}
20497
20498impl SemanticsProvider for Entity<Project> {
20499 fn hover(
20500 &self,
20501 buffer: &Entity<Buffer>,
20502 position: text::Anchor,
20503 cx: &mut App,
20504 ) -> Option<Task<Vec<project::Hover>>> {
20505 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
20506 }
20507
20508 fn document_highlights(
20509 &self,
20510 buffer: &Entity<Buffer>,
20511 position: text::Anchor,
20512 cx: &mut App,
20513 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
20514 Some(self.update(cx, |project, cx| {
20515 project.document_highlights(buffer, position, cx)
20516 }))
20517 }
20518
20519 fn definitions(
20520 &self,
20521 buffer: &Entity<Buffer>,
20522 position: text::Anchor,
20523 kind: GotoDefinitionKind,
20524 cx: &mut App,
20525 ) -> Option<Task<Result<Vec<LocationLink>>>> {
20526 Some(self.update(cx, |project, cx| match kind {
20527 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
20528 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
20529 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
20530 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
20531 }))
20532 }
20533
20534 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
20535 // TODO: make this work for remote projects
20536 self.update(cx, |project, cx| {
20537 if project
20538 .active_debug_session(cx)
20539 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
20540 {
20541 return true;
20542 }
20543
20544 buffer.update(cx, |buffer, cx| {
20545 project.any_language_server_supports_inlay_hints(buffer, cx)
20546 })
20547 })
20548 }
20549
20550 fn inline_values(
20551 &self,
20552 buffer_handle: Entity<Buffer>,
20553
20554 range: Range<text::Anchor>,
20555 cx: &mut App,
20556 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20557 self.update(cx, |project, cx| {
20558 let (session, active_stack_frame) = project.active_debug_session(cx)?;
20559
20560 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
20561 })
20562 }
20563
20564 fn inlay_hints(
20565 &self,
20566 buffer_handle: Entity<Buffer>,
20567 range: Range<text::Anchor>,
20568 cx: &mut App,
20569 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20570 Some(self.update(cx, |project, cx| {
20571 project.inlay_hints(buffer_handle, range, cx)
20572 }))
20573 }
20574
20575 fn resolve_inlay_hint(
20576 &self,
20577 hint: InlayHint,
20578 buffer_handle: Entity<Buffer>,
20579 server_id: LanguageServerId,
20580 cx: &mut App,
20581 ) -> Option<Task<anyhow::Result<InlayHint>>> {
20582 Some(self.update(cx, |project, cx| {
20583 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
20584 }))
20585 }
20586
20587 fn range_for_rename(
20588 &self,
20589 buffer: &Entity<Buffer>,
20590 position: text::Anchor,
20591 cx: &mut App,
20592 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
20593 Some(self.update(cx, |project, cx| {
20594 let buffer = buffer.clone();
20595 let task = project.prepare_rename(buffer.clone(), position, cx);
20596 cx.spawn(async move |_, cx| {
20597 Ok(match task.await? {
20598 PrepareRenameResponse::Success(range) => Some(range),
20599 PrepareRenameResponse::InvalidPosition => None,
20600 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
20601 // Fallback on using TreeSitter info to determine identifier range
20602 buffer.read_with(cx, |buffer, _| {
20603 let snapshot = buffer.snapshot();
20604 let (range, kind) = snapshot.surrounding_word(position);
20605 if kind != Some(CharKind::Word) {
20606 return None;
20607 }
20608 Some(
20609 snapshot.anchor_before(range.start)
20610 ..snapshot.anchor_after(range.end),
20611 )
20612 })?
20613 }
20614 })
20615 })
20616 }))
20617 }
20618
20619 fn perform_rename(
20620 &self,
20621 buffer: &Entity<Buffer>,
20622 position: text::Anchor,
20623 new_name: String,
20624 cx: &mut App,
20625 ) -> Option<Task<Result<ProjectTransaction>>> {
20626 Some(self.update(cx, |project, cx| {
20627 project.perform_rename(buffer.clone(), position, new_name, cx)
20628 }))
20629 }
20630}
20631
20632fn inlay_hint_settings(
20633 location: Anchor,
20634 snapshot: &MultiBufferSnapshot,
20635 cx: &mut Context<Editor>,
20636) -> InlayHintSettings {
20637 let file = snapshot.file_at(location);
20638 let language = snapshot.language_at(location).map(|l| l.name());
20639 language_settings(language, file, cx).inlay_hints
20640}
20641
20642fn consume_contiguous_rows(
20643 contiguous_row_selections: &mut Vec<Selection<Point>>,
20644 selection: &Selection<Point>,
20645 display_map: &DisplaySnapshot,
20646 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
20647) -> (MultiBufferRow, MultiBufferRow) {
20648 contiguous_row_selections.push(selection.clone());
20649 let start_row = MultiBufferRow(selection.start.row);
20650 let mut end_row = ending_row(selection, display_map);
20651
20652 while let Some(next_selection) = selections.peek() {
20653 if next_selection.start.row <= end_row.0 {
20654 end_row = ending_row(next_selection, display_map);
20655 contiguous_row_selections.push(selections.next().unwrap().clone());
20656 } else {
20657 break;
20658 }
20659 }
20660 (start_row, end_row)
20661}
20662
20663fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
20664 if next_selection.end.column > 0 || next_selection.is_empty() {
20665 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
20666 } else {
20667 MultiBufferRow(next_selection.end.row)
20668 }
20669}
20670
20671impl EditorSnapshot {
20672 pub fn remote_selections_in_range<'a>(
20673 &'a self,
20674 range: &'a Range<Anchor>,
20675 collaboration_hub: &dyn CollaborationHub,
20676 cx: &'a App,
20677 ) -> impl 'a + Iterator<Item = RemoteSelection> {
20678 let participant_names = collaboration_hub.user_names(cx);
20679 let participant_indices = collaboration_hub.user_participant_indices(cx);
20680 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20681 let collaborators_by_replica_id = collaborators_by_peer_id
20682 .values()
20683 .map(|collaborator| (collaborator.replica_id, collaborator))
20684 .collect::<HashMap<_, _>>();
20685 self.buffer_snapshot
20686 .selections_in_range(range, false)
20687 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20688 if replica_id == AGENT_REPLICA_ID {
20689 Some(RemoteSelection {
20690 replica_id,
20691 selection,
20692 cursor_shape,
20693 line_mode,
20694 collaborator_id: CollaboratorId::Agent,
20695 user_name: Some("Agent".into()),
20696 color: cx.theme().players().agent(),
20697 })
20698 } else {
20699 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20700 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20701 let user_name = participant_names.get(&collaborator.user_id).cloned();
20702 Some(RemoteSelection {
20703 replica_id,
20704 selection,
20705 cursor_shape,
20706 line_mode,
20707 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20708 user_name,
20709 color: if let Some(index) = participant_index {
20710 cx.theme().players().color_for_participant(index.0)
20711 } else {
20712 cx.theme().players().absent()
20713 },
20714 })
20715 }
20716 })
20717 }
20718
20719 pub fn hunks_for_ranges(
20720 &self,
20721 ranges: impl IntoIterator<Item = Range<Point>>,
20722 ) -> Vec<MultiBufferDiffHunk> {
20723 let mut hunks = Vec::new();
20724 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20725 HashMap::default();
20726 for query_range in ranges {
20727 let query_rows =
20728 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20729 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20730 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20731 ) {
20732 // Include deleted hunks that are adjacent to the query range, because
20733 // otherwise they would be missed.
20734 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20735 if hunk.status().is_deleted() {
20736 intersects_range |= hunk.row_range.start == query_rows.end;
20737 intersects_range |= hunk.row_range.end == query_rows.start;
20738 }
20739 if intersects_range {
20740 if !processed_buffer_rows
20741 .entry(hunk.buffer_id)
20742 .or_default()
20743 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20744 {
20745 continue;
20746 }
20747 hunks.push(hunk);
20748 }
20749 }
20750 }
20751
20752 hunks
20753 }
20754
20755 fn display_diff_hunks_for_rows<'a>(
20756 &'a self,
20757 display_rows: Range<DisplayRow>,
20758 folded_buffers: &'a HashSet<BufferId>,
20759 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20760 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20761 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20762
20763 self.buffer_snapshot
20764 .diff_hunks_in_range(buffer_start..buffer_end)
20765 .filter_map(|hunk| {
20766 if folded_buffers.contains(&hunk.buffer_id) {
20767 return None;
20768 }
20769
20770 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20771 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20772
20773 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20774 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20775
20776 let display_hunk = if hunk_display_start.column() != 0 {
20777 DisplayDiffHunk::Folded {
20778 display_row: hunk_display_start.row(),
20779 }
20780 } else {
20781 let mut end_row = hunk_display_end.row();
20782 if hunk_display_end.column() > 0 {
20783 end_row.0 += 1;
20784 }
20785 let is_created_file = hunk.is_created_file();
20786 DisplayDiffHunk::Unfolded {
20787 status: hunk.status(),
20788 diff_base_byte_range: hunk.diff_base_byte_range,
20789 display_row_range: hunk_display_start.row()..end_row,
20790 multi_buffer_range: Anchor::range_in_buffer(
20791 hunk.excerpt_id,
20792 hunk.buffer_id,
20793 hunk.buffer_range,
20794 ),
20795 is_created_file,
20796 }
20797 };
20798
20799 Some(display_hunk)
20800 })
20801 }
20802
20803 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20804 self.display_snapshot.buffer_snapshot.language_at(position)
20805 }
20806
20807 pub fn is_focused(&self) -> bool {
20808 self.is_focused
20809 }
20810
20811 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20812 self.placeholder_text.as_ref()
20813 }
20814
20815 pub fn scroll_position(&self) -> gpui::Point<f32> {
20816 self.scroll_anchor.scroll_position(&self.display_snapshot)
20817 }
20818
20819 fn gutter_dimensions(
20820 &self,
20821 font_id: FontId,
20822 font_size: Pixels,
20823 max_line_number_width: Pixels,
20824 cx: &App,
20825 ) -> Option<GutterDimensions> {
20826 if !self.show_gutter {
20827 return None;
20828 }
20829
20830 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20831 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20832
20833 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20834 matches!(
20835 ProjectSettings::get_global(cx).git.git_gutter,
20836 Some(GitGutterSetting::TrackedFiles)
20837 )
20838 });
20839 let gutter_settings = EditorSettings::get_global(cx).gutter;
20840 let show_line_numbers = self
20841 .show_line_numbers
20842 .unwrap_or(gutter_settings.line_numbers);
20843 let line_gutter_width = if show_line_numbers {
20844 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20845 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20846 max_line_number_width.max(min_width_for_number_on_gutter)
20847 } else {
20848 0.0.into()
20849 };
20850
20851 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20852 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20853
20854 let git_blame_entries_width =
20855 self.git_blame_gutter_max_author_length
20856 .map(|max_author_length| {
20857 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20858 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20859
20860 /// The number of characters to dedicate to gaps and margins.
20861 const SPACING_WIDTH: usize = 4;
20862
20863 let max_char_count = max_author_length.min(renderer.max_author_length())
20864 + ::git::SHORT_SHA_LENGTH
20865 + MAX_RELATIVE_TIMESTAMP.len()
20866 + SPACING_WIDTH;
20867
20868 em_advance * max_char_count
20869 });
20870
20871 let is_singleton = self.buffer_snapshot.is_singleton();
20872
20873 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
20874 left_padding += if !is_singleton {
20875 em_width * 4.0
20876 } else if show_runnables || show_breakpoints {
20877 em_width * 3.0
20878 } else if show_git_gutter && show_line_numbers {
20879 em_width * 2.0
20880 } else if show_git_gutter || show_line_numbers {
20881 em_width
20882 } else {
20883 px(0.)
20884 };
20885
20886 let shows_folds = is_singleton && gutter_settings.folds;
20887
20888 let right_padding = if shows_folds && show_line_numbers {
20889 em_width * 4.0
20890 } else if shows_folds || (!is_singleton && show_line_numbers) {
20891 em_width * 3.0
20892 } else if show_line_numbers {
20893 em_width
20894 } else {
20895 px(0.)
20896 };
20897
20898 Some(GutterDimensions {
20899 left_padding,
20900 right_padding,
20901 width: line_gutter_width + left_padding + right_padding,
20902 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
20903 git_blame_entries_width,
20904 })
20905 }
20906
20907 pub fn render_crease_toggle(
20908 &self,
20909 buffer_row: MultiBufferRow,
20910 row_contains_cursor: bool,
20911 editor: Entity<Editor>,
20912 window: &mut Window,
20913 cx: &mut App,
20914 ) -> Option<AnyElement> {
20915 let folded = self.is_line_folded(buffer_row);
20916 let mut is_foldable = false;
20917
20918 if let Some(crease) = self
20919 .crease_snapshot
20920 .query_row(buffer_row, &self.buffer_snapshot)
20921 {
20922 is_foldable = true;
20923 match crease {
20924 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20925 if let Some(render_toggle) = render_toggle {
20926 let toggle_callback =
20927 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20928 if folded {
20929 editor.update(cx, |editor, cx| {
20930 editor.fold_at(buffer_row, window, cx)
20931 });
20932 } else {
20933 editor.update(cx, |editor, cx| {
20934 editor.unfold_at(buffer_row, window, cx)
20935 });
20936 }
20937 });
20938 return Some((render_toggle)(
20939 buffer_row,
20940 folded,
20941 toggle_callback,
20942 window,
20943 cx,
20944 ));
20945 }
20946 }
20947 }
20948 }
20949
20950 is_foldable |= self.starts_indent(buffer_row);
20951
20952 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20953 Some(
20954 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20955 .toggle_state(folded)
20956 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20957 if folded {
20958 this.unfold_at(buffer_row, window, cx);
20959 } else {
20960 this.fold_at(buffer_row, window, cx);
20961 }
20962 }))
20963 .into_any_element(),
20964 )
20965 } else {
20966 None
20967 }
20968 }
20969
20970 pub fn render_crease_trailer(
20971 &self,
20972 buffer_row: MultiBufferRow,
20973 window: &mut Window,
20974 cx: &mut App,
20975 ) -> Option<AnyElement> {
20976 let folded = self.is_line_folded(buffer_row);
20977 if let Crease::Inline { render_trailer, .. } = self
20978 .crease_snapshot
20979 .query_row(buffer_row, &self.buffer_snapshot)?
20980 {
20981 let render_trailer = render_trailer.as_ref()?;
20982 Some(render_trailer(buffer_row, folded, window, cx))
20983 } else {
20984 None
20985 }
20986 }
20987}
20988
20989impl Deref for EditorSnapshot {
20990 type Target = DisplaySnapshot;
20991
20992 fn deref(&self) -> &Self::Target {
20993 &self.display_snapshot
20994 }
20995}
20996
20997#[derive(Clone, Debug, PartialEq, Eq)]
20998pub enum EditorEvent {
20999 InputIgnored {
21000 text: Arc<str>,
21001 },
21002 InputHandled {
21003 utf16_range_to_replace: Option<Range<isize>>,
21004 text: Arc<str>,
21005 },
21006 ExcerptsAdded {
21007 buffer: Entity<Buffer>,
21008 predecessor: ExcerptId,
21009 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21010 },
21011 ExcerptsRemoved {
21012 ids: Vec<ExcerptId>,
21013 removed_buffer_ids: Vec<BufferId>,
21014 },
21015 BufferFoldToggled {
21016 ids: Vec<ExcerptId>,
21017 folded: bool,
21018 },
21019 ExcerptsEdited {
21020 ids: Vec<ExcerptId>,
21021 },
21022 ExcerptsExpanded {
21023 ids: Vec<ExcerptId>,
21024 },
21025 BufferEdited,
21026 Edited {
21027 transaction_id: clock::Lamport,
21028 },
21029 Reparsed(BufferId),
21030 Focused,
21031 FocusedIn,
21032 Blurred,
21033 DirtyChanged,
21034 Saved,
21035 TitleChanged,
21036 DiffBaseChanged,
21037 SelectionsChanged {
21038 local: bool,
21039 },
21040 ScrollPositionChanged {
21041 local: bool,
21042 autoscroll: bool,
21043 },
21044 Closed,
21045 TransactionUndone {
21046 transaction_id: clock::Lamport,
21047 },
21048 TransactionBegun {
21049 transaction_id: clock::Lamport,
21050 },
21051 Reloaded,
21052 CursorShapeChanged,
21053 PushedToNavHistory {
21054 anchor: Anchor,
21055 is_deactivate: bool,
21056 },
21057}
21058
21059impl EventEmitter<EditorEvent> for Editor {}
21060
21061impl Focusable for Editor {
21062 fn focus_handle(&self, _cx: &App) -> FocusHandle {
21063 self.focus_handle.clone()
21064 }
21065}
21066
21067impl Render for Editor {
21068 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21069 let settings = ThemeSettings::get_global(cx);
21070
21071 let mut text_style = match self.mode {
21072 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21073 color: cx.theme().colors().editor_foreground,
21074 font_family: settings.ui_font.family.clone(),
21075 font_features: settings.ui_font.features.clone(),
21076 font_fallbacks: settings.ui_font.fallbacks.clone(),
21077 font_size: rems(0.875).into(),
21078 font_weight: settings.ui_font.weight,
21079 line_height: relative(settings.buffer_line_height.value()),
21080 ..Default::default()
21081 },
21082 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
21083 color: cx.theme().colors().editor_foreground,
21084 font_family: settings.buffer_font.family.clone(),
21085 font_features: settings.buffer_font.features.clone(),
21086 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21087 font_size: settings.buffer_font_size(cx).into(),
21088 font_weight: settings.buffer_font.weight,
21089 line_height: relative(settings.buffer_line_height.value()),
21090 ..Default::default()
21091 },
21092 };
21093 if let Some(text_style_refinement) = &self.text_style_refinement {
21094 text_style.refine(text_style_refinement)
21095 }
21096
21097 let background = match self.mode {
21098 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
21099 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
21100 EditorMode::Full { .. } => cx.theme().colors().editor_background,
21101 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
21102 };
21103
21104 EditorElement::new(
21105 &cx.entity(),
21106 EditorStyle {
21107 background,
21108 local_player: cx.theme().players().local(),
21109 text: text_style,
21110 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
21111 syntax: cx.theme().syntax().clone(),
21112 status: cx.theme().status().clone(),
21113 inlay_hints_style: make_inlay_hints_style(cx),
21114 inline_completion_styles: make_suggestion_styles(cx),
21115 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
21116 show_underlines: !self.mode.is_minimap(),
21117 },
21118 )
21119 }
21120}
21121
21122impl EntityInputHandler for Editor {
21123 fn text_for_range(
21124 &mut self,
21125 range_utf16: Range<usize>,
21126 adjusted_range: &mut Option<Range<usize>>,
21127 _: &mut Window,
21128 cx: &mut Context<Self>,
21129 ) -> Option<String> {
21130 let snapshot = self.buffer.read(cx).read(cx);
21131 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
21132 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
21133 if (start.0..end.0) != range_utf16 {
21134 adjusted_range.replace(start.0..end.0);
21135 }
21136 Some(snapshot.text_for_range(start..end).collect())
21137 }
21138
21139 fn selected_text_range(
21140 &mut self,
21141 ignore_disabled_input: bool,
21142 _: &mut Window,
21143 cx: &mut Context<Self>,
21144 ) -> Option<UTF16Selection> {
21145 // Prevent the IME menu from appearing when holding down an alphabetic key
21146 // while input is disabled.
21147 if !ignore_disabled_input && !self.input_enabled {
21148 return None;
21149 }
21150
21151 let selection = self.selections.newest::<OffsetUtf16>(cx);
21152 let range = selection.range();
21153
21154 Some(UTF16Selection {
21155 range: range.start.0..range.end.0,
21156 reversed: selection.reversed,
21157 })
21158 }
21159
21160 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
21161 let snapshot = self.buffer.read(cx).read(cx);
21162 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
21163 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
21164 }
21165
21166 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21167 self.clear_highlights::<InputComposition>(cx);
21168 self.ime_transaction.take();
21169 }
21170
21171 fn replace_text_in_range(
21172 &mut self,
21173 range_utf16: Option<Range<usize>>,
21174 text: &str,
21175 window: &mut Window,
21176 cx: &mut Context<Self>,
21177 ) {
21178 if !self.input_enabled {
21179 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21180 return;
21181 }
21182
21183 self.transact(window, cx, |this, window, cx| {
21184 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
21185 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21186 Some(this.selection_replacement_ranges(range_utf16, cx))
21187 } else {
21188 this.marked_text_ranges(cx)
21189 };
21190
21191 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
21192 let newest_selection_id = this.selections.newest_anchor().id;
21193 this.selections
21194 .all::<OffsetUtf16>(cx)
21195 .iter()
21196 .zip(ranges_to_replace.iter())
21197 .find_map(|(selection, range)| {
21198 if selection.id == newest_selection_id {
21199 Some(
21200 (range.start.0 as isize - selection.head().0 as isize)
21201 ..(range.end.0 as isize - selection.head().0 as isize),
21202 )
21203 } else {
21204 None
21205 }
21206 })
21207 });
21208
21209 cx.emit(EditorEvent::InputHandled {
21210 utf16_range_to_replace: range_to_replace,
21211 text: text.into(),
21212 });
21213
21214 if let Some(new_selected_ranges) = new_selected_ranges {
21215 this.change_selections(None, window, cx, |selections| {
21216 selections.select_ranges(new_selected_ranges)
21217 });
21218 this.backspace(&Default::default(), window, cx);
21219 }
21220
21221 this.handle_input(text, window, cx);
21222 });
21223
21224 if let Some(transaction) = self.ime_transaction {
21225 self.buffer.update(cx, |buffer, cx| {
21226 buffer.group_until_transaction(transaction, cx);
21227 });
21228 }
21229
21230 self.unmark_text(window, cx);
21231 }
21232
21233 fn replace_and_mark_text_in_range(
21234 &mut self,
21235 range_utf16: Option<Range<usize>>,
21236 text: &str,
21237 new_selected_range_utf16: Option<Range<usize>>,
21238 window: &mut Window,
21239 cx: &mut Context<Self>,
21240 ) {
21241 if !self.input_enabled {
21242 return;
21243 }
21244
21245 let transaction = self.transact(window, cx, |this, window, cx| {
21246 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
21247 let snapshot = this.buffer.read(cx).read(cx);
21248 if let Some(relative_range_utf16) = range_utf16.as_ref() {
21249 for marked_range in &mut marked_ranges {
21250 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
21251 marked_range.start.0 += relative_range_utf16.start;
21252 marked_range.start =
21253 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
21254 marked_range.end =
21255 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
21256 }
21257 }
21258 Some(marked_ranges)
21259 } else if let Some(range_utf16) = range_utf16 {
21260 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21261 Some(this.selection_replacement_ranges(range_utf16, cx))
21262 } else {
21263 None
21264 };
21265
21266 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
21267 let newest_selection_id = this.selections.newest_anchor().id;
21268 this.selections
21269 .all::<OffsetUtf16>(cx)
21270 .iter()
21271 .zip(ranges_to_replace.iter())
21272 .find_map(|(selection, range)| {
21273 if selection.id == newest_selection_id {
21274 Some(
21275 (range.start.0 as isize - selection.head().0 as isize)
21276 ..(range.end.0 as isize - selection.head().0 as isize),
21277 )
21278 } else {
21279 None
21280 }
21281 })
21282 });
21283
21284 cx.emit(EditorEvent::InputHandled {
21285 utf16_range_to_replace: range_to_replace,
21286 text: text.into(),
21287 });
21288
21289 if let Some(ranges) = ranges_to_replace {
21290 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
21291 }
21292
21293 let marked_ranges = {
21294 let snapshot = this.buffer.read(cx).read(cx);
21295 this.selections
21296 .disjoint_anchors()
21297 .iter()
21298 .map(|selection| {
21299 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
21300 })
21301 .collect::<Vec<_>>()
21302 };
21303
21304 if text.is_empty() {
21305 this.unmark_text(window, cx);
21306 } else {
21307 this.highlight_text::<InputComposition>(
21308 marked_ranges.clone(),
21309 HighlightStyle {
21310 underline: Some(UnderlineStyle {
21311 thickness: px(1.),
21312 color: None,
21313 wavy: false,
21314 }),
21315 ..Default::default()
21316 },
21317 cx,
21318 );
21319 }
21320
21321 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
21322 let use_autoclose = this.use_autoclose;
21323 let use_auto_surround = this.use_auto_surround;
21324 this.set_use_autoclose(false);
21325 this.set_use_auto_surround(false);
21326 this.handle_input(text, window, cx);
21327 this.set_use_autoclose(use_autoclose);
21328 this.set_use_auto_surround(use_auto_surround);
21329
21330 if let Some(new_selected_range) = new_selected_range_utf16 {
21331 let snapshot = this.buffer.read(cx).read(cx);
21332 let new_selected_ranges = marked_ranges
21333 .into_iter()
21334 .map(|marked_range| {
21335 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
21336 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
21337 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
21338 snapshot.clip_offset_utf16(new_start, Bias::Left)
21339 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
21340 })
21341 .collect::<Vec<_>>();
21342
21343 drop(snapshot);
21344 this.change_selections(None, window, cx, |selections| {
21345 selections.select_ranges(new_selected_ranges)
21346 });
21347 }
21348 });
21349
21350 self.ime_transaction = self.ime_transaction.or(transaction);
21351 if let Some(transaction) = self.ime_transaction {
21352 self.buffer.update(cx, |buffer, cx| {
21353 buffer.group_until_transaction(transaction, cx);
21354 });
21355 }
21356
21357 if self.text_highlights::<InputComposition>(cx).is_none() {
21358 self.ime_transaction.take();
21359 }
21360 }
21361
21362 fn bounds_for_range(
21363 &mut self,
21364 range_utf16: Range<usize>,
21365 element_bounds: gpui::Bounds<Pixels>,
21366 window: &mut Window,
21367 cx: &mut Context<Self>,
21368 ) -> Option<gpui::Bounds<Pixels>> {
21369 let text_layout_details = self.text_layout_details(window);
21370 let gpui::Size {
21371 width: em_width,
21372 height: line_height,
21373 } = self.character_size(window);
21374
21375 let snapshot = self.snapshot(window, cx);
21376 let scroll_position = snapshot.scroll_position();
21377 let scroll_left = scroll_position.x * em_width;
21378
21379 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
21380 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
21381 + self.gutter_dimensions.width
21382 + self.gutter_dimensions.margin;
21383 let y = line_height * (start.row().as_f32() - scroll_position.y);
21384
21385 Some(Bounds {
21386 origin: element_bounds.origin + point(x, y),
21387 size: size(em_width, line_height),
21388 })
21389 }
21390
21391 fn character_index_for_point(
21392 &mut self,
21393 point: gpui::Point<Pixels>,
21394 _window: &mut Window,
21395 _cx: &mut Context<Self>,
21396 ) -> Option<usize> {
21397 let position_map = self.last_position_map.as_ref()?;
21398 if !position_map.text_hitbox.contains(&point) {
21399 return None;
21400 }
21401 let display_point = position_map.point_for_position(point).previous_valid;
21402 let anchor = position_map
21403 .snapshot
21404 .display_point_to_anchor(display_point, Bias::Left);
21405 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
21406 Some(utf16_offset.0)
21407 }
21408}
21409
21410trait SelectionExt {
21411 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
21412 fn spanned_rows(
21413 &self,
21414 include_end_if_at_line_start: bool,
21415 map: &DisplaySnapshot,
21416 ) -> Range<MultiBufferRow>;
21417}
21418
21419impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
21420 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
21421 let start = self
21422 .start
21423 .to_point(&map.buffer_snapshot)
21424 .to_display_point(map);
21425 let end = self
21426 .end
21427 .to_point(&map.buffer_snapshot)
21428 .to_display_point(map);
21429 if self.reversed {
21430 end..start
21431 } else {
21432 start..end
21433 }
21434 }
21435
21436 fn spanned_rows(
21437 &self,
21438 include_end_if_at_line_start: bool,
21439 map: &DisplaySnapshot,
21440 ) -> Range<MultiBufferRow> {
21441 let start = self.start.to_point(&map.buffer_snapshot);
21442 let mut end = self.end.to_point(&map.buffer_snapshot);
21443 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
21444 end.row -= 1;
21445 }
21446
21447 let buffer_start = map.prev_line_boundary(start).0;
21448 let buffer_end = map.next_line_boundary(end).0;
21449 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
21450 }
21451}
21452
21453impl<T: InvalidationRegion> InvalidationStack<T> {
21454 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
21455 where
21456 S: Clone + ToOffset,
21457 {
21458 while let Some(region) = self.last() {
21459 let all_selections_inside_invalidation_ranges =
21460 if selections.len() == region.ranges().len() {
21461 selections
21462 .iter()
21463 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
21464 .all(|(selection, invalidation_range)| {
21465 let head = selection.head().to_offset(buffer);
21466 invalidation_range.start <= head && invalidation_range.end >= head
21467 })
21468 } else {
21469 false
21470 };
21471
21472 if all_selections_inside_invalidation_ranges {
21473 break;
21474 } else {
21475 self.pop();
21476 }
21477 }
21478 }
21479}
21480
21481impl<T> Default for InvalidationStack<T> {
21482 fn default() -> Self {
21483 Self(Default::default())
21484 }
21485}
21486
21487impl<T> Deref for InvalidationStack<T> {
21488 type Target = Vec<T>;
21489
21490 fn deref(&self) -> &Self::Target {
21491 &self.0
21492 }
21493}
21494
21495impl<T> DerefMut for InvalidationStack<T> {
21496 fn deref_mut(&mut self) -> &mut Self::Target {
21497 &mut self.0
21498 }
21499}
21500
21501impl InvalidationRegion for SnippetState {
21502 fn ranges(&self) -> &[Range<Anchor>] {
21503 &self.ranges[self.active_index]
21504 }
21505}
21506
21507fn inline_completion_edit_text(
21508 current_snapshot: &BufferSnapshot,
21509 edits: &[(Range<Anchor>, String)],
21510 edit_preview: &EditPreview,
21511 include_deletions: bool,
21512 cx: &App,
21513) -> HighlightedText {
21514 let edits = edits
21515 .iter()
21516 .map(|(anchor, text)| {
21517 (
21518 anchor.start.text_anchor..anchor.end.text_anchor,
21519 text.clone(),
21520 )
21521 })
21522 .collect::<Vec<_>>();
21523
21524 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
21525}
21526
21527pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
21528 match severity {
21529 lsp::DiagnosticSeverity::ERROR => colors.error,
21530 lsp::DiagnosticSeverity::WARNING => colors.warning,
21531 lsp::DiagnosticSeverity::INFORMATION => colors.info,
21532 lsp::DiagnosticSeverity::HINT => colors.info,
21533 _ => colors.ignored,
21534 }
21535}
21536
21537pub fn styled_runs_for_code_label<'a>(
21538 label: &'a CodeLabel,
21539 syntax_theme: &'a theme::SyntaxTheme,
21540) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
21541 let fade_out = HighlightStyle {
21542 fade_out: Some(0.35),
21543 ..Default::default()
21544 };
21545
21546 let mut prev_end = label.filter_range.end;
21547 label
21548 .runs
21549 .iter()
21550 .enumerate()
21551 .flat_map(move |(ix, (range, highlight_id))| {
21552 let style = if let Some(style) = highlight_id.style(syntax_theme) {
21553 style
21554 } else {
21555 return Default::default();
21556 };
21557 let mut muted_style = style;
21558 muted_style.highlight(fade_out);
21559
21560 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
21561 if range.start >= label.filter_range.end {
21562 if range.start > prev_end {
21563 runs.push((prev_end..range.start, fade_out));
21564 }
21565 runs.push((range.clone(), muted_style));
21566 } else if range.end <= label.filter_range.end {
21567 runs.push((range.clone(), style));
21568 } else {
21569 runs.push((range.start..label.filter_range.end, style));
21570 runs.push((label.filter_range.end..range.end, muted_style));
21571 }
21572 prev_end = cmp::max(prev_end, range.end);
21573
21574 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
21575 runs.push((prev_end..label.text.len(), fade_out));
21576 }
21577
21578 runs
21579 })
21580}
21581
21582pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
21583 let mut prev_index = 0;
21584 let mut prev_codepoint: Option<char> = None;
21585 text.char_indices()
21586 .chain([(text.len(), '\0')])
21587 .filter_map(move |(index, codepoint)| {
21588 let prev_codepoint = prev_codepoint.replace(codepoint)?;
21589 let is_boundary = index == text.len()
21590 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
21591 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
21592 if is_boundary {
21593 let chunk = &text[prev_index..index];
21594 prev_index = index;
21595 Some(chunk)
21596 } else {
21597 None
21598 }
21599 })
21600}
21601
21602pub trait RangeToAnchorExt: Sized {
21603 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
21604
21605 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
21606 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
21607 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
21608 }
21609}
21610
21611impl<T: ToOffset> RangeToAnchorExt for Range<T> {
21612 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
21613 let start_offset = self.start.to_offset(snapshot);
21614 let end_offset = self.end.to_offset(snapshot);
21615 if start_offset == end_offset {
21616 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
21617 } else {
21618 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
21619 }
21620 }
21621}
21622
21623pub trait RowExt {
21624 fn as_f32(&self) -> f32;
21625
21626 fn next_row(&self) -> Self;
21627
21628 fn previous_row(&self) -> Self;
21629
21630 fn minus(&self, other: Self) -> u32;
21631}
21632
21633impl RowExt for DisplayRow {
21634 fn as_f32(&self) -> f32 {
21635 self.0 as f32
21636 }
21637
21638 fn next_row(&self) -> Self {
21639 Self(self.0 + 1)
21640 }
21641
21642 fn previous_row(&self) -> Self {
21643 Self(self.0.saturating_sub(1))
21644 }
21645
21646 fn minus(&self, other: Self) -> u32 {
21647 self.0 - other.0
21648 }
21649}
21650
21651impl RowExt for MultiBufferRow {
21652 fn as_f32(&self) -> f32 {
21653 self.0 as f32
21654 }
21655
21656 fn next_row(&self) -> Self {
21657 Self(self.0 + 1)
21658 }
21659
21660 fn previous_row(&self) -> Self {
21661 Self(self.0.saturating_sub(1))
21662 }
21663
21664 fn minus(&self, other: Self) -> u32 {
21665 self.0 - other.0
21666 }
21667}
21668
21669trait RowRangeExt {
21670 type Row;
21671
21672 fn len(&self) -> usize;
21673
21674 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
21675}
21676
21677impl RowRangeExt for Range<MultiBufferRow> {
21678 type Row = MultiBufferRow;
21679
21680 fn len(&self) -> usize {
21681 (self.end.0 - self.start.0) as usize
21682 }
21683
21684 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21685 (self.start.0..self.end.0).map(MultiBufferRow)
21686 }
21687}
21688
21689impl RowRangeExt for Range<DisplayRow> {
21690 type Row = DisplayRow;
21691
21692 fn len(&self) -> usize {
21693 (self.end.0 - self.start.0) as usize
21694 }
21695
21696 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21697 (self.start.0..self.end.0).map(DisplayRow)
21698 }
21699}
21700
21701/// If select range has more than one line, we
21702/// just point the cursor to range.start.
21703fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21704 if range.start.row == range.end.row {
21705 range
21706 } else {
21707 range.start..range.start
21708 }
21709}
21710pub struct KillRing(ClipboardItem);
21711impl Global for KillRing {}
21712
21713const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21714
21715enum BreakpointPromptEditAction {
21716 Log,
21717 Condition,
21718 HitCondition,
21719}
21720
21721struct BreakpointPromptEditor {
21722 pub(crate) prompt: Entity<Editor>,
21723 editor: WeakEntity<Editor>,
21724 breakpoint_anchor: Anchor,
21725 breakpoint: Breakpoint,
21726 edit_action: BreakpointPromptEditAction,
21727 block_ids: HashSet<CustomBlockId>,
21728 editor_margins: Arc<Mutex<EditorMargins>>,
21729 _subscriptions: Vec<Subscription>,
21730}
21731
21732impl BreakpointPromptEditor {
21733 const MAX_LINES: u8 = 4;
21734
21735 fn new(
21736 editor: WeakEntity<Editor>,
21737 breakpoint_anchor: Anchor,
21738 breakpoint: Breakpoint,
21739 edit_action: BreakpointPromptEditAction,
21740 window: &mut Window,
21741 cx: &mut Context<Self>,
21742 ) -> Self {
21743 let base_text = match edit_action {
21744 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21745 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21746 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21747 }
21748 .map(|msg| msg.to_string())
21749 .unwrap_or_default();
21750
21751 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21752 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21753
21754 let prompt = cx.new(|cx| {
21755 let mut prompt = Editor::new(
21756 EditorMode::AutoHeight {
21757 max_lines: Self::MAX_LINES as usize,
21758 },
21759 buffer,
21760 None,
21761 window,
21762 cx,
21763 );
21764 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21765 prompt.set_show_cursor_when_unfocused(false, cx);
21766 prompt.set_placeholder_text(
21767 match edit_action {
21768 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21769 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21770 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21771 },
21772 cx,
21773 );
21774
21775 prompt
21776 });
21777
21778 Self {
21779 prompt,
21780 editor,
21781 breakpoint_anchor,
21782 breakpoint,
21783 edit_action,
21784 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21785 block_ids: Default::default(),
21786 _subscriptions: vec![],
21787 }
21788 }
21789
21790 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21791 self.block_ids.extend(block_ids)
21792 }
21793
21794 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21795 if let Some(editor) = self.editor.upgrade() {
21796 let message = self
21797 .prompt
21798 .read(cx)
21799 .buffer
21800 .read(cx)
21801 .as_singleton()
21802 .expect("A multi buffer in breakpoint prompt isn't possible")
21803 .read(cx)
21804 .as_rope()
21805 .to_string();
21806
21807 editor.update(cx, |editor, cx| {
21808 editor.edit_breakpoint_at_anchor(
21809 self.breakpoint_anchor,
21810 self.breakpoint.clone(),
21811 match self.edit_action {
21812 BreakpointPromptEditAction::Log => {
21813 BreakpointEditAction::EditLogMessage(message.into())
21814 }
21815 BreakpointPromptEditAction::Condition => {
21816 BreakpointEditAction::EditCondition(message.into())
21817 }
21818 BreakpointPromptEditAction::HitCondition => {
21819 BreakpointEditAction::EditHitCondition(message.into())
21820 }
21821 },
21822 cx,
21823 );
21824
21825 editor.remove_blocks(self.block_ids.clone(), None, cx);
21826 cx.focus_self(window);
21827 });
21828 }
21829 }
21830
21831 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21832 self.editor
21833 .update(cx, |editor, cx| {
21834 editor.remove_blocks(self.block_ids.clone(), None, cx);
21835 window.focus(&editor.focus_handle);
21836 })
21837 .log_err();
21838 }
21839
21840 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21841 let settings = ThemeSettings::get_global(cx);
21842 let text_style = TextStyle {
21843 color: if self.prompt.read(cx).read_only(cx) {
21844 cx.theme().colors().text_disabled
21845 } else {
21846 cx.theme().colors().text
21847 },
21848 font_family: settings.buffer_font.family.clone(),
21849 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21850 font_size: settings.buffer_font_size(cx).into(),
21851 font_weight: settings.buffer_font.weight,
21852 line_height: relative(settings.buffer_line_height.value()),
21853 ..Default::default()
21854 };
21855 EditorElement::new(
21856 &self.prompt,
21857 EditorStyle {
21858 background: cx.theme().colors().editor_background,
21859 local_player: cx.theme().players().local(),
21860 text: text_style,
21861 ..Default::default()
21862 },
21863 )
21864 }
21865}
21866
21867impl Render for BreakpointPromptEditor {
21868 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21869 let editor_margins = *self.editor_margins.lock();
21870 let gutter_dimensions = editor_margins.gutter;
21871 h_flex()
21872 .key_context("Editor")
21873 .bg(cx.theme().colors().editor_background)
21874 .border_y_1()
21875 .border_color(cx.theme().status().info_border)
21876 .size_full()
21877 .py(window.line_height() / 2.5)
21878 .on_action(cx.listener(Self::confirm))
21879 .on_action(cx.listener(Self::cancel))
21880 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21881 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21882 }
21883}
21884
21885impl Focusable for BreakpointPromptEditor {
21886 fn focus_handle(&self, cx: &App) -> FocusHandle {
21887 self.prompt.focus_handle(cx)
21888 }
21889}
21890
21891fn all_edits_insertions_or_deletions(
21892 edits: &Vec<(Range<Anchor>, String)>,
21893 snapshot: &MultiBufferSnapshot,
21894) -> bool {
21895 let mut all_insertions = true;
21896 let mut all_deletions = true;
21897
21898 for (range, new_text) in edits.iter() {
21899 let range_is_empty = range.to_offset(&snapshot).is_empty();
21900 let text_is_empty = new_text.is_empty();
21901
21902 if range_is_empty != text_is_empty {
21903 if range_is_empty {
21904 all_deletions = false;
21905 } else {
21906 all_insertions = false;
21907 }
21908 } else {
21909 return false;
21910 }
21911
21912 if !all_insertions && !all_deletions {
21913 return false;
21914 }
21915 }
21916 all_insertions || all_deletions
21917}
21918
21919struct MissingEditPredictionKeybindingTooltip;
21920
21921impl Render for MissingEditPredictionKeybindingTooltip {
21922 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21923 ui::tooltip_container(window, cx, |container, _, cx| {
21924 container
21925 .flex_shrink_0()
21926 .max_w_80()
21927 .min_h(rems_from_px(124.))
21928 .justify_between()
21929 .child(
21930 v_flex()
21931 .flex_1()
21932 .text_ui_sm(cx)
21933 .child(Label::new("Conflict with Accept Keybinding"))
21934 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21935 )
21936 .child(
21937 h_flex()
21938 .pb_1()
21939 .gap_1()
21940 .items_end()
21941 .w_full()
21942 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21943 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21944 }))
21945 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21946 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21947 })),
21948 )
21949 })
21950 }
21951}
21952
21953#[derive(Debug, Clone, Copy, PartialEq)]
21954pub struct LineHighlight {
21955 pub background: Background,
21956 pub border: Option<gpui::Hsla>,
21957 pub include_gutter: bool,
21958 pub type_id: Option<TypeId>,
21959}
21960
21961fn render_diff_hunk_controls(
21962 row: u32,
21963 status: &DiffHunkStatus,
21964 hunk_range: Range<Anchor>,
21965 is_created_file: bool,
21966 line_height: Pixels,
21967 editor: &Entity<Editor>,
21968 _window: &mut Window,
21969 cx: &mut App,
21970) -> AnyElement {
21971 h_flex()
21972 .h(line_height)
21973 .mr_1()
21974 .gap_1()
21975 .px_0p5()
21976 .pb_1()
21977 .border_x_1()
21978 .border_b_1()
21979 .border_color(cx.theme().colors().border_variant)
21980 .rounded_b_lg()
21981 .bg(cx.theme().colors().editor_background)
21982 .gap_1()
21983 .block_mouse_except_scroll()
21984 .shadow_md()
21985 .child(if status.has_secondary_hunk() {
21986 Button::new(("stage", row as u64), "Stage")
21987 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21988 .tooltip({
21989 let focus_handle = editor.focus_handle(cx);
21990 move |window, cx| {
21991 Tooltip::for_action_in(
21992 "Stage Hunk",
21993 &::git::ToggleStaged,
21994 &focus_handle,
21995 window,
21996 cx,
21997 )
21998 }
21999 })
22000 .on_click({
22001 let editor = editor.clone();
22002 move |_event, _window, cx| {
22003 editor.update(cx, |editor, cx| {
22004 editor.stage_or_unstage_diff_hunks(
22005 true,
22006 vec![hunk_range.start..hunk_range.start],
22007 cx,
22008 );
22009 });
22010 }
22011 })
22012 } else {
22013 Button::new(("unstage", row as u64), "Unstage")
22014 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22015 .tooltip({
22016 let focus_handle = editor.focus_handle(cx);
22017 move |window, cx| {
22018 Tooltip::for_action_in(
22019 "Unstage Hunk",
22020 &::git::ToggleStaged,
22021 &focus_handle,
22022 window,
22023 cx,
22024 )
22025 }
22026 })
22027 .on_click({
22028 let editor = editor.clone();
22029 move |_event, _window, cx| {
22030 editor.update(cx, |editor, cx| {
22031 editor.stage_or_unstage_diff_hunks(
22032 false,
22033 vec![hunk_range.start..hunk_range.start],
22034 cx,
22035 );
22036 });
22037 }
22038 })
22039 })
22040 .child(
22041 Button::new(("restore", row as u64), "Restore")
22042 .tooltip({
22043 let focus_handle = editor.focus_handle(cx);
22044 move |window, cx| {
22045 Tooltip::for_action_in(
22046 "Restore Hunk",
22047 &::git::Restore,
22048 &focus_handle,
22049 window,
22050 cx,
22051 )
22052 }
22053 })
22054 .on_click({
22055 let editor = editor.clone();
22056 move |_event, window, cx| {
22057 editor.update(cx, |editor, cx| {
22058 let snapshot = editor.snapshot(window, cx);
22059 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
22060 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
22061 });
22062 }
22063 })
22064 .disabled(is_created_file),
22065 )
22066 .when(
22067 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
22068 |el| {
22069 el.child(
22070 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
22071 .shape(IconButtonShape::Square)
22072 .icon_size(IconSize::Small)
22073 // .disabled(!has_multiple_hunks)
22074 .tooltip({
22075 let focus_handle = editor.focus_handle(cx);
22076 move |window, cx| {
22077 Tooltip::for_action_in(
22078 "Next Hunk",
22079 &GoToHunk,
22080 &focus_handle,
22081 window,
22082 cx,
22083 )
22084 }
22085 })
22086 .on_click({
22087 let editor = editor.clone();
22088 move |_event, window, cx| {
22089 editor.update(cx, |editor, cx| {
22090 let snapshot = editor.snapshot(window, cx);
22091 let position =
22092 hunk_range.end.to_point(&snapshot.buffer_snapshot);
22093 editor.go_to_hunk_before_or_after_position(
22094 &snapshot,
22095 position,
22096 Direction::Next,
22097 window,
22098 cx,
22099 );
22100 editor.expand_selected_diff_hunks(cx);
22101 });
22102 }
22103 }),
22104 )
22105 .child(
22106 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
22107 .shape(IconButtonShape::Square)
22108 .icon_size(IconSize::Small)
22109 // .disabled(!has_multiple_hunks)
22110 .tooltip({
22111 let focus_handle = editor.focus_handle(cx);
22112 move |window, cx| {
22113 Tooltip::for_action_in(
22114 "Previous Hunk",
22115 &GoToPreviousHunk,
22116 &focus_handle,
22117 window,
22118 cx,
22119 )
22120 }
22121 })
22122 .on_click({
22123 let editor = editor.clone();
22124 move |_event, window, cx| {
22125 editor.update(cx, |editor, cx| {
22126 let snapshot = editor.snapshot(window, cx);
22127 let point =
22128 hunk_range.start.to_point(&snapshot.buffer_snapshot);
22129 editor.go_to_hunk_before_or_after_position(
22130 &snapshot,
22131 point,
22132 Direction::Prev,
22133 window,
22134 cx,
22135 );
22136 editor.expand_selected_diff_hunks(cx);
22137 });
22138 }
22139 }),
22140 )
22141 },
22142 )
22143 .into_any_element()
22144}