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 if let Some(debounce) = debounce {
15707 cx.background_executor().timer(debounce).await;
15708 }
15709 let Some(snapshot) = editor.upgrade().and_then(|editor| {
15710 editor
15711 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15712 .ok()
15713 }) else {
15714 return;
15715 };
15716
15717 let new_inline_diagnostics = cx
15718 .background_spawn(async move {
15719 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15720 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15721 let message = diagnostic_entry
15722 .diagnostic
15723 .message
15724 .split_once('\n')
15725 .map(|(line, _)| line)
15726 .map(SharedString::new)
15727 .unwrap_or_else(|| {
15728 SharedString::from(diagnostic_entry.diagnostic.message)
15729 });
15730 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15731 let (Ok(i) | Err(i)) = inline_diagnostics
15732 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15733 inline_diagnostics.insert(
15734 i,
15735 (
15736 start_anchor,
15737 InlineDiagnostic {
15738 message,
15739 group_id: diagnostic_entry.diagnostic.group_id,
15740 start: diagnostic_entry.range.start.to_point(&snapshot),
15741 is_primary: diagnostic_entry.diagnostic.is_primary,
15742 severity: diagnostic_entry.diagnostic.severity,
15743 },
15744 ),
15745 );
15746 }
15747 inline_diagnostics
15748 })
15749 .await;
15750
15751 editor
15752 .update(cx, |editor, cx| {
15753 editor.inline_diagnostics = new_inline_diagnostics;
15754 cx.notify();
15755 })
15756 .ok();
15757 });
15758 }
15759
15760 pub fn set_selections_from_remote(
15761 &mut self,
15762 selections: Vec<Selection<Anchor>>,
15763 pending_selection: Option<Selection<Anchor>>,
15764 window: &mut Window,
15765 cx: &mut Context<Self>,
15766 ) {
15767 let old_cursor_position = self.selections.newest_anchor().head();
15768 self.selections.change_with(cx, |s| {
15769 s.select_anchors(selections);
15770 if let Some(pending_selection) = pending_selection {
15771 s.set_pending(pending_selection, SelectMode::Character);
15772 } else {
15773 s.clear_pending();
15774 }
15775 });
15776 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15777 }
15778
15779 pub fn transact(
15780 &mut self,
15781 window: &mut Window,
15782 cx: &mut Context<Self>,
15783 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15784 ) -> Option<TransactionId> {
15785 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15786 this.start_transaction_at(Instant::now(), window, cx);
15787 update(this, window, cx);
15788 this.end_transaction_at(Instant::now(), cx)
15789 })
15790 }
15791
15792 pub fn start_transaction_at(
15793 &mut self,
15794 now: Instant,
15795 window: &mut Window,
15796 cx: &mut Context<Self>,
15797 ) {
15798 self.end_selection(window, cx);
15799 if let Some(tx_id) = self
15800 .buffer
15801 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15802 {
15803 self.selection_history
15804 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15805 cx.emit(EditorEvent::TransactionBegun {
15806 transaction_id: tx_id,
15807 })
15808 }
15809 }
15810
15811 pub fn end_transaction_at(
15812 &mut self,
15813 now: Instant,
15814 cx: &mut Context<Self>,
15815 ) -> Option<TransactionId> {
15816 if let Some(transaction_id) = self
15817 .buffer
15818 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15819 {
15820 if let Some((_, end_selections)) =
15821 self.selection_history.transaction_mut(transaction_id)
15822 {
15823 *end_selections = Some(self.selections.disjoint_anchors());
15824 } else {
15825 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15826 }
15827
15828 cx.emit(EditorEvent::Edited { transaction_id });
15829 Some(transaction_id)
15830 } else {
15831 None
15832 }
15833 }
15834
15835 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15836 if self.selection_mark_mode {
15837 self.change_selections(None, window, cx, |s| {
15838 s.move_with(|_, sel| {
15839 sel.collapse_to(sel.head(), SelectionGoal::None);
15840 });
15841 })
15842 }
15843 self.selection_mark_mode = true;
15844 cx.notify();
15845 }
15846
15847 pub fn swap_selection_ends(
15848 &mut self,
15849 _: &actions::SwapSelectionEnds,
15850 window: &mut Window,
15851 cx: &mut Context<Self>,
15852 ) {
15853 self.change_selections(None, window, cx, |s| {
15854 s.move_with(|_, sel| {
15855 if sel.start != sel.end {
15856 sel.reversed = !sel.reversed
15857 }
15858 });
15859 });
15860 self.request_autoscroll(Autoscroll::newest(), cx);
15861 cx.notify();
15862 }
15863
15864 pub fn toggle_fold(
15865 &mut self,
15866 _: &actions::ToggleFold,
15867 window: &mut Window,
15868 cx: &mut Context<Self>,
15869 ) {
15870 if self.is_singleton(cx) {
15871 let selection = self.selections.newest::<Point>(cx);
15872
15873 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15874 let range = if selection.is_empty() {
15875 let point = selection.head().to_display_point(&display_map);
15876 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15877 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15878 .to_point(&display_map);
15879 start..end
15880 } else {
15881 selection.range()
15882 };
15883 if display_map.folds_in_range(range).next().is_some() {
15884 self.unfold_lines(&Default::default(), window, cx)
15885 } else {
15886 self.fold(&Default::default(), window, cx)
15887 }
15888 } else {
15889 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15890 let buffer_ids: HashSet<_> = self
15891 .selections
15892 .disjoint_anchor_ranges()
15893 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15894 .collect();
15895
15896 let should_unfold = buffer_ids
15897 .iter()
15898 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15899
15900 for buffer_id in buffer_ids {
15901 if should_unfold {
15902 self.unfold_buffer(buffer_id, cx);
15903 } else {
15904 self.fold_buffer(buffer_id, cx);
15905 }
15906 }
15907 }
15908 }
15909
15910 pub fn toggle_fold_recursive(
15911 &mut self,
15912 _: &actions::ToggleFoldRecursive,
15913 window: &mut Window,
15914 cx: &mut Context<Self>,
15915 ) {
15916 let selection = self.selections.newest::<Point>(cx);
15917
15918 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15919 let range = if selection.is_empty() {
15920 let point = selection.head().to_display_point(&display_map);
15921 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15922 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15923 .to_point(&display_map);
15924 start..end
15925 } else {
15926 selection.range()
15927 };
15928 if display_map.folds_in_range(range).next().is_some() {
15929 self.unfold_recursive(&Default::default(), window, cx)
15930 } else {
15931 self.fold_recursive(&Default::default(), window, cx)
15932 }
15933 }
15934
15935 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15936 if self.is_singleton(cx) {
15937 let mut to_fold = Vec::new();
15938 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15939 let selections = self.selections.all_adjusted(cx);
15940
15941 for selection in selections {
15942 let range = selection.range().sorted();
15943 let buffer_start_row = range.start.row;
15944
15945 if range.start.row != range.end.row {
15946 let mut found = false;
15947 let mut row = range.start.row;
15948 while row <= range.end.row {
15949 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15950 {
15951 found = true;
15952 row = crease.range().end.row + 1;
15953 to_fold.push(crease);
15954 } else {
15955 row += 1
15956 }
15957 }
15958 if found {
15959 continue;
15960 }
15961 }
15962
15963 for row in (0..=range.start.row).rev() {
15964 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15965 if crease.range().end.row >= buffer_start_row {
15966 to_fold.push(crease);
15967 if row <= range.start.row {
15968 break;
15969 }
15970 }
15971 }
15972 }
15973 }
15974
15975 self.fold_creases(to_fold, true, window, cx);
15976 } else {
15977 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15978 let buffer_ids = self
15979 .selections
15980 .disjoint_anchor_ranges()
15981 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15982 .collect::<HashSet<_>>();
15983 for buffer_id in buffer_ids {
15984 self.fold_buffer(buffer_id, cx);
15985 }
15986 }
15987 }
15988
15989 fn fold_at_level(
15990 &mut self,
15991 fold_at: &FoldAtLevel,
15992 window: &mut Window,
15993 cx: &mut Context<Self>,
15994 ) {
15995 if !self.buffer.read(cx).is_singleton() {
15996 return;
15997 }
15998
15999 let fold_at_level = fold_at.0;
16000 let snapshot = self.buffer.read(cx).snapshot(cx);
16001 let mut to_fold = Vec::new();
16002 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16003
16004 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16005 while start_row < end_row {
16006 match self
16007 .snapshot(window, cx)
16008 .crease_for_buffer_row(MultiBufferRow(start_row))
16009 {
16010 Some(crease) => {
16011 let nested_start_row = crease.range().start.row + 1;
16012 let nested_end_row = crease.range().end.row;
16013
16014 if current_level < fold_at_level {
16015 stack.push((nested_start_row, nested_end_row, current_level + 1));
16016 } else if current_level == fold_at_level {
16017 to_fold.push(crease);
16018 }
16019
16020 start_row = nested_end_row + 1;
16021 }
16022 None => start_row += 1,
16023 }
16024 }
16025 }
16026
16027 self.fold_creases(to_fold, true, window, cx);
16028 }
16029
16030 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16031 if self.buffer.read(cx).is_singleton() {
16032 let mut fold_ranges = Vec::new();
16033 let snapshot = self.buffer.read(cx).snapshot(cx);
16034
16035 for row in 0..snapshot.max_row().0 {
16036 if let Some(foldable_range) = self
16037 .snapshot(window, cx)
16038 .crease_for_buffer_row(MultiBufferRow(row))
16039 {
16040 fold_ranges.push(foldable_range);
16041 }
16042 }
16043
16044 self.fold_creases(fold_ranges, true, window, cx);
16045 } else {
16046 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16047 editor
16048 .update_in(cx, |editor, _, cx| {
16049 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16050 editor.fold_buffer(buffer_id, cx);
16051 }
16052 })
16053 .ok();
16054 });
16055 }
16056 }
16057
16058 pub fn fold_function_bodies(
16059 &mut self,
16060 _: &actions::FoldFunctionBodies,
16061 window: &mut Window,
16062 cx: &mut Context<Self>,
16063 ) {
16064 let snapshot = self.buffer.read(cx).snapshot(cx);
16065
16066 let ranges = snapshot
16067 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16068 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16069 .collect::<Vec<_>>();
16070
16071 let creases = ranges
16072 .into_iter()
16073 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16074 .collect();
16075
16076 self.fold_creases(creases, true, window, cx);
16077 }
16078
16079 pub fn fold_recursive(
16080 &mut self,
16081 _: &actions::FoldRecursive,
16082 window: &mut Window,
16083 cx: &mut Context<Self>,
16084 ) {
16085 let mut to_fold = Vec::new();
16086 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16087 let selections = self.selections.all_adjusted(cx);
16088
16089 for selection in selections {
16090 let range = selection.range().sorted();
16091 let buffer_start_row = range.start.row;
16092
16093 if range.start.row != range.end.row {
16094 let mut found = false;
16095 for row in range.start.row..=range.end.row {
16096 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16097 found = true;
16098 to_fold.push(crease);
16099 }
16100 }
16101 if found {
16102 continue;
16103 }
16104 }
16105
16106 for row in (0..=range.start.row).rev() {
16107 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16108 if crease.range().end.row >= buffer_start_row {
16109 to_fold.push(crease);
16110 } else {
16111 break;
16112 }
16113 }
16114 }
16115 }
16116
16117 self.fold_creases(to_fold, true, window, cx);
16118 }
16119
16120 pub fn fold_at(
16121 &mut self,
16122 buffer_row: MultiBufferRow,
16123 window: &mut Window,
16124 cx: &mut Context<Self>,
16125 ) {
16126 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16127
16128 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16129 let autoscroll = self
16130 .selections
16131 .all::<Point>(cx)
16132 .iter()
16133 .any(|selection| crease.range().overlaps(&selection.range()));
16134
16135 self.fold_creases(vec![crease], autoscroll, window, cx);
16136 }
16137 }
16138
16139 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16140 if self.is_singleton(cx) {
16141 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16142 let buffer = &display_map.buffer_snapshot;
16143 let selections = self.selections.all::<Point>(cx);
16144 let ranges = selections
16145 .iter()
16146 .map(|s| {
16147 let range = s.display_range(&display_map).sorted();
16148 let mut start = range.start.to_point(&display_map);
16149 let mut end = range.end.to_point(&display_map);
16150 start.column = 0;
16151 end.column = buffer.line_len(MultiBufferRow(end.row));
16152 start..end
16153 })
16154 .collect::<Vec<_>>();
16155
16156 self.unfold_ranges(&ranges, true, true, cx);
16157 } else {
16158 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16159 let buffer_ids = self
16160 .selections
16161 .disjoint_anchor_ranges()
16162 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16163 .collect::<HashSet<_>>();
16164 for buffer_id in buffer_ids {
16165 self.unfold_buffer(buffer_id, cx);
16166 }
16167 }
16168 }
16169
16170 pub fn unfold_recursive(
16171 &mut self,
16172 _: &UnfoldRecursive,
16173 _window: &mut Window,
16174 cx: &mut Context<Self>,
16175 ) {
16176 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16177 let selections = self.selections.all::<Point>(cx);
16178 let ranges = selections
16179 .iter()
16180 .map(|s| {
16181 let mut range = s.display_range(&display_map).sorted();
16182 *range.start.column_mut() = 0;
16183 *range.end.column_mut() = display_map.line_len(range.end.row());
16184 let start = range.start.to_point(&display_map);
16185 let end = range.end.to_point(&display_map);
16186 start..end
16187 })
16188 .collect::<Vec<_>>();
16189
16190 self.unfold_ranges(&ranges, true, true, cx);
16191 }
16192
16193 pub fn unfold_at(
16194 &mut self,
16195 buffer_row: MultiBufferRow,
16196 _window: &mut Window,
16197 cx: &mut Context<Self>,
16198 ) {
16199 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16200
16201 let intersection_range = Point::new(buffer_row.0, 0)
16202 ..Point::new(
16203 buffer_row.0,
16204 display_map.buffer_snapshot.line_len(buffer_row),
16205 );
16206
16207 let autoscroll = self
16208 .selections
16209 .all::<Point>(cx)
16210 .iter()
16211 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16212
16213 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16214 }
16215
16216 pub fn unfold_all(
16217 &mut self,
16218 _: &actions::UnfoldAll,
16219 _window: &mut Window,
16220 cx: &mut Context<Self>,
16221 ) {
16222 if self.buffer.read(cx).is_singleton() {
16223 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16224 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16225 } else {
16226 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16227 editor
16228 .update(cx, |editor, cx| {
16229 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16230 editor.unfold_buffer(buffer_id, cx);
16231 }
16232 })
16233 .ok();
16234 });
16235 }
16236 }
16237
16238 pub fn fold_selected_ranges(
16239 &mut self,
16240 _: &FoldSelectedRanges,
16241 window: &mut Window,
16242 cx: &mut Context<Self>,
16243 ) {
16244 let selections = self.selections.all_adjusted(cx);
16245 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16246 let ranges = selections
16247 .into_iter()
16248 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16249 .collect::<Vec<_>>();
16250 self.fold_creases(ranges, true, window, cx);
16251 }
16252
16253 pub fn fold_ranges<T: ToOffset + Clone>(
16254 &mut self,
16255 ranges: Vec<Range<T>>,
16256 auto_scroll: bool,
16257 window: &mut Window,
16258 cx: &mut Context<Self>,
16259 ) {
16260 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16261 let ranges = ranges
16262 .into_iter()
16263 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16264 .collect::<Vec<_>>();
16265 self.fold_creases(ranges, auto_scroll, window, cx);
16266 }
16267
16268 pub fn fold_creases<T: ToOffset + Clone>(
16269 &mut self,
16270 creases: Vec<Crease<T>>,
16271 auto_scroll: bool,
16272 _window: &mut Window,
16273 cx: &mut Context<Self>,
16274 ) {
16275 if creases.is_empty() {
16276 return;
16277 }
16278
16279 let mut buffers_affected = HashSet::default();
16280 let multi_buffer = self.buffer().read(cx);
16281 for crease in &creases {
16282 if let Some((_, buffer, _)) =
16283 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16284 {
16285 buffers_affected.insert(buffer.read(cx).remote_id());
16286 };
16287 }
16288
16289 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16290
16291 if auto_scroll {
16292 self.request_autoscroll(Autoscroll::fit(), cx);
16293 }
16294
16295 cx.notify();
16296
16297 self.scrollbar_marker_state.dirty = true;
16298 self.folds_did_change(cx);
16299 }
16300
16301 /// Removes any folds whose ranges intersect any of the given ranges.
16302 pub fn unfold_ranges<T: ToOffset + Clone>(
16303 &mut self,
16304 ranges: &[Range<T>],
16305 inclusive: bool,
16306 auto_scroll: bool,
16307 cx: &mut Context<Self>,
16308 ) {
16309 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16310 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16311 });
16312 self.folds_did_change(cx);
16313 }
16314
16315 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16316 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16317 return;
16318 }
16319 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16320 self.display_map.update(cx, |display_map, cx| {
16321 display_map.fold_buffers([buffer_id], cx)
16322 });
16323 cx.emit(EditorEvent::BufferFoldToggled {
16324 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16325 folded: true,
16326 });
16327 cx.notify();
16328 }
16329
16330 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16331 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16332 return;
16333 }
16334 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16335 self.display_map.update(cx, |display_map, cx| {
16336 display_map.unfold_buffers([buffer_id], cx);
16337 });
16338 cx.emit(EditorEvent::BufferFoldToggled {
16339 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16340 folded: false,
16341 });
16342 cx.notify();
16343 }
16344
16345 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16346 self.display_map.read(cx).is_buffer_folded(buffer)
16347 }
16348
16349 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16350 self.display_map.read(cx).folded_buffers()
16351 }
16352
16353 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16354 self.display_map.update(cx, |display_map, cx| {
16355 display_map.disable_header_for_buffer(buffer_id, cx);
16356 });
16357 cx.notify();
16358 }
16359
16360 /// Removes any folds with the given ranges.
16361 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16362 &mut self,
16363 ranges: &[Range<T>],
16364 type_id: TypeId,
16365 auto_scroll: bool,
16366 cx: &mut Context<Self>,
16367 ) {
16368 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16369 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16370 });
16371 self.folds_did_change(cx);
16372 }
16373
16374 fn remove_folds_with<T: ToOffset + Clone>(
16375 &mut self,
16376 ranges: &[Range<T>],
16377 auto_scroll: bool,
16378 cx: &mut Context<Self>,
16379 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16380 ) {
16381 if ranges.is_empty() {
16382 return;
16383 }
16384
16385 let mut buffers_affected = HashSet::default();
16386 let multi_buffer = self.buffer().read(cx);
16387 for range in ranges {
16388 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16389 buffers_affected.insert(buffer.read(cx).remote_id());
16390 };
16391 }
16392
16393 self.display_map.update(cx, update);
16394
16395 if auto_scroll {
16396 self.request_autoscroll(Autoscroll::fit(), cx);
16397 }
16398
16399 cx.notify();
16400 self.scrollbar_marker_state.dirty = true;
16401 self.active_indent_guides_state.dirty = true;
16402 }
16403
16404 pub fn update_fold_widths(
16405 &mut self,
16406 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16407 cx: &mut Context<Self>,
16408 ) -> bool {
16409 self.display_map
16410 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16411 }
16412
16413 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16414 self.display_map.read(cx).fold_placeholder.clone()
16415 }
16416
16417 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16418 self.buffer.update(cx, |buffer, cx| {
16419 buffer.set_all_diff_hunks_expanded(cx);
16420 });
16421 }
16422
16423 pub fn expand_all_diff_hunks(
16424 &mut self,
16425 _: &ExpandAllDiffHunks,
16426 _window: &mut Window,
16427 cx: &mut Context<Self>,
16428 ) {
16429 self.buffer.update(cx, |buffer, cx| {
16430 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16431 });
16432 }
16433
16434 pub fn toggle_selected_diff_hunks(
16435 &mut self,
16436 _: &ToggleSelectedDiffHunks,
16437 _window: &mut Window,
16438 cx: &mut Context<Self>,
16439 ) {
16440 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16441 self.toggle_diff_hunks_in_ranges(ranges, cx);
16442 }
16443
16444 pub fn diff_hunks_in_ranges<'a>(
16445 &'a self,
16446 ranges: &'a [Range<Anchor>],
16447 buffer: &'a MultiBufferSnapshot,
16448 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16449 ranges.iter().flat_map(move |range| {
16450 let end_excerpt_id = range.end.excerpt_id;
16451 let range = range.to_point(buffer);
16452 let mut peek_end = range.end;
16453 if range.end.row < buffer.max_row().0 {
16454 peek_end = Point::new(range.end.row + 1, 0);
16455 }
16456 buffer
16457 .diff_hunks_in_range(range.start..peek_end)
16458 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16459 })
16460 }
16461
16462 pub fn has_stageable_diff_hunks_in_ranges(
16463 &self,
16464 ranges: &[Range<Anchor>],
16465 snapshot: &MultiBufferSnapshot,
16466 ) -> bool {
16467 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16468 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16469 }
16470
16471 pub fn toggle_staged_selected_diff_hunks(
16472 &mut self,
16473 _: &::git::ToggleStaged,
16474 _: &mut Window,
16475 cx: &mut Context<Self>,
16476 ) {
16477 let snapshot = self.buffer.read(cx).snapshot(cx);
16478 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16479 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16480 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16481 }
16482
16483 pub fn set_render_diff_hunk_controls(
16484 &mut self,
16485 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16486 cx: &mut Context<Self>,
16487 ) {
16488 self.render_diff_hunk_controls = render_diff_hunk_controls;
16489 cx.notify();
16490 }
16491
16492 pub fn stage_and_next(
16493 &mut self,
16494 _: &::git::StageAndNext,
16495 window: &mut Window,
16496 cx: &mut Context<Self>,
16497 ) {
16498 self.do_stage_or_unstage_and_next(true, window, cx);
16499 }
16500
16501 pub fn unstage_and_next(
16502 &mut self,
16503 _: &::git::UnstageAndNext,
16504 window: &mut Window,
16505 cx: &mut Context<Self>,
16506 ) {
16507 self.do_stage_or_unstage_and_next(false, window, cx);
16508 }
16509
16510 pub fn stage_or_unstage_diff_hunks(
16511 &mut self,
16512 stage: bool,
16513 ranges: Vec<Range<Anchor>>,
16514 cx: &mut Context<Self>,
16515 ) {
16516 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16517 cx.spawn(async move |this, cx| {
16518 task.await?;
16519 this.update(cx, |this, cx| {
16520 let snapshot = this.buffer.read(cx).snapshot(cx);
16521 let chunk_by = this
16522 .diff_hunks_in_ranges(&ranges, &snapshot)
16523 .chunk_by(|hunk| hunk.buffer_id);
16524 for (buffer_id, hunks) in &chunk_by {
16525 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16526 }
16527 })
16528 })
16529 .detach_and_log_err(cx);
16530 }
16531
16532 fn save_buffers_for_ranges_if_needed(
16533 &mut self,
16534 ranges: &[Range<Anchor>],
16535 cx: &mut Context<Editor>,
16536 ) -> Task<Result<()>> {
16537 let multibuffer = self.buffer.read(cx);
16538 let snapshot = multibuffer.read(cx);
16539 let buffer_ids: HashSet<_> = ranges
16540 .iter()
16541 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16542 .collect();
16543 drop(snapshot);
16544
16545 let mut buffers = HashSet::default();
16546 for buffer_id in buffer_ids {
16547 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16548 let buffer = buffer_entity.read(cx);
16549 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16550 {
16551 buffers.insert(buffer_entity);
16552 }
16553 }
16554 }
16555
16556 if let Some(project) = &self.project {
16557 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16558 } else {
16559 Task::ready(Ok(()))
16560 }
16561 }
16562
16563 fn do_stage_or_unstage_and_next(
16564 &mut self,
16565 stage: bool,
16566 window: &mut Window,
16567 cx: &mut Context<Self>,
16568 ) {
16569 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16570
16571 if ranges.iter().any(|range| range.start != range.end) {
16572 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16573 return;
16574 }
16575
16576 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16577 let snapshot = self.snapshot(window, cx);
16578 let position = self.selections.newest::<Point>(cx).head();
16579 let mut row = snapshot
16580 .buffer_snapshot
16581 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16582 .find(|hunk| hunk.row_range.start.0 > position.row)
16583 .map(|hunk| hunk.row_range.start);
16584
16585 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16586 // Outside of the project diff editor, wrap around to the beginning.
16587 if !all_diff_hunks_expanded {
16588 row = row.or_else(|| {
16589 snapshot
16590 .buffer_snapshot
16591 .diff_hunks_in_range(Point::zero()..position)
16592 .find(|hunk| hunk.row_range.end.0 < position.row)
16593 .map(|hunk| hunk.row_range.start)
16594 });
16595 }
16596
16597 if let Some(row) = row {
16598 let destination = Point::new(row.0, 0);
16599 let autoscroll = Autoscroll::center();
16600
16601 self.unfold_ranges(&[destination..destination], false, false, cx);
16602 self.change_selections(Some(autoscroll), window, cx, |s| {
16603 s.select_ranges([destination..destination]);
16604 });
16605 }
16606 }
16607
16608 fn do_stage_or_unstage(
16609 &self,
16610 stage: bool,
16611 buffer_id: BufferId,
16612 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16613 cx: &mut App,
16614 ) -> Option<()> {
16615 let project = self.project.as_ref()?;
16616 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16617 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16618 let buffer_snapshot = buffer.read(cx).snapshot();
16619 let file_exists = buffer_snapshot
16620 .file()
16621 .is_some_and(|file| file.disk_state().exists());
16622 diff.update(cx, |diff, cx| {
16623 diff.stage_or_unstage_hunks(
16624 stage,
16625 &hunks
16626 .map(|hunk| buffer_diff::DiffHunk {
16627 buffer_range: hunk.buffer_range,
16628 diff_base_byte_range: hunk.diff_base_byte_range,
16629 secondary_status: hunk.secondary_status,
16630 range: Point::zero()..Point::zero(), // unused
16631 })
16632 .collect::<Vec<_>>(),
16633 &buffer_snapshot,
16634 file_exists,
16635 cx,
16636 )
16637 });
16638 None
16639 }
16640
16641 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16642 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16643 self.buffer
16644 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16645 }
16646
16647 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16648 self.buffer.update(cx, |buffer, cx| {
16649 let ranges = vec![Anchor::min()..Anchor::max()];
16650 if !buffer.all_diff_hunks_expanded()
16651 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16652 {
16653 buffer.collapse_diff_hunks(ranges, cx);
16654 true
16655 } else {
16656 false
16657 }
16658 })
16659 }
16660
16661 fn toggle_diff_hunks_in_ranges(
16662 &mut self,
16663 ranges: Vec<Range<Anchor>>,
16664 cx: &mut Context<Editor>,
16665 ) {
16666 self.buffer.update(cx, |buffer, cx| {
16667 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16668 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16669 })
16670 }
16671
16672 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16673 self.buffer.update(cx, |buffer, cx| {
16674 let snapshot = buffer.snapshot(cx);
16675 let excerpt_id = range.end.excerpt_id;
16676 let point_range = range.to_point(&snapshot);
16677 let expand = !buffer.single_hunk_is_expanded(range, cx);
16678 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16679 })
16680 }
16681
16682 pub(crate) fn apply_all_diff_hunks(
16683 &mut self,
16684 _: &ApplyAllDiffHunks,
16685 window: &mut Window,
16686 cx: &mut Context<Self>,
16687 ) {
16688 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16689
16690 let buffers = self.buffer.read(cx).all_buffers();
16691 for branch_buffer in buffers {
16692 branch_buffer.update(cx, |branch_buffer, cx| {
16693 branch_buffer.merge_into_base(Vec::new(), cx);
16694 });
16695 }
16696
16697 if let Some(project) = self.project.clone() {
16698 self.save(true, project, window, cx).detach_and_log_err(cx);
16699 }
16700 }
16701
16702 pub(crate) fn apply_selected_diff_hunks(
16703 &mut self,
16704 _: &ApplyDiffHunk,
16705 window: &mut Window,
16706 cx: &mut Context<Self>,
16707 ) {
16708 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16709 let snapshot = self.snapshot(window, cx);
16710 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16711 let mut ranges_by_buffer = HashMap::default();
16712 self.transact(window, cx, |editor, _window, cx| {
16713 for hunk in hunks {
16714 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16715 ranges_by_buffer
16716 .entry(buffer.clone())
16717 .or_insert_with(Vec::new)
16718 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16719 }
16720 }
16721
16722 for (buffer, ranges) in ranges_by_buffer {
16723 buffer.update(cx, |buffer, cx| {
16724 buffer.merge_into_base(ranges, cx);
16725 });
16726 }
16727 });
16728
16729 if let Some(project) = self.project.clone() {
16730 self.save(true, project, window, cx).detach_and_log_err(cx);
16731 }
16732 }
16733
16734 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16735 if hovered != self.gutter_hovered {
16736 self.gutter_hovered = hovered;
16737 cx.notify();
16738 }
16739 }
16740
16741 pub fn insert_blocks(
16742 &mut self,
16743 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16744 autoscroll: Option<Autoscroll>,
16745 cx: &mut Context<Self>,
16746 ) -> Vec<CustomBlockId> {
16747 let blocks = self
16748 .display_map
16749 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16750 if let Some(autoscroll) = autoscroll {
16751 self.request_autoscroll(autoscroll, cx);
16752 }
16753 cx.notify();
16754 blocks
16755 }
16756
16757 pub fn resize_blocks(
16758 &mut self,
16759 heights: HashMap<CustomBlockId, u32>,
16760 autoscroll: Option<Autoscroll>,
16761 cx: &mut Context<Self>,
16762 ) {
16763 self.display_map
16764 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16765 if let Some(autoscroll) = autoscroll {
16766 self.request_autoscroll(autoscroll, cx);
16767 }
16768 cx.notify();
16769 }
16770
16771 pub fn replace_blocks(
16772 &mut self,
16773 renderers: HashMap<CustomBlockId, RenderBlock>,
16774 autoscroll: Option<Autoscroll>,
16775 cx: &mut Context<Self>,
16776 ) {
16777 self.display_map
16778 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16779 if let Some(autoscroll) = autoscroll {
16780 self.request_autoscroll(autoscroll, cx);
16781 }
16782 cx.notify();
16783 }
16784
16785 pub fn remove_blocks(
16786 &mut self,
16787 block_ids: HashSet<CustomBlockId>,
16788 autoscroll: Option<Autoscroll>,
16789 cx: &mut Context<Self>,
16790 ) {
16791 self.display_map.update(cx, |display_map, cx| {
16792 display_map.remove_blocks(block_ids, cx)
16793 });
16794 if let Some(autoscroll) = autoscroll {
16795 self.request_autoscroll(autoscroll, cx);
16796 }
16797 cx.notify();
16798 }
16799
16800 pub fn row_for_block(
16801 &self,
16802 block_id: CustomBlockId,
16803 cx: &mut Context<Self>,
16804 ) -> Option<DisplayRow> {
16805 self.display_map
16806 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16807 }
16808
16809 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16810 self.focused_block = Some(focused_block);
16811 }
16812
16813 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16814 self.focused_block.take()
16815 }
16816
16817 pub fn insert_creases(
16818 &mut self,
16819 creases: impl IntoIterator<Item = Crease<Anchor>>,
16820 cx: &mut Context<Self>,
16821 ) -> Vec<CreaseId> {
16822 self.display_map
16823 .update(cx, |map, cx| map.insert_creases(creases, cx))
16824 }
16825
16826 pub fn remove_creases(
16827 &mut self,
16828 ids: impl IntoIterator<Item = CreaseId>,
16829 cx: &mut Context<Self>,
16830 ) -> Vec<(CreaseId, Range<Anchor>)> {
16831 self.display_map
16832 .update(cx, |map, cx| map.remove_creases(ids, cx))
16833 }
16834
16835 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16836 self.display_map
16837 .update(cx, |map, cx| map.snapshot(cx))
16838 .longest_row()
16839 }
16840
16841 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16842 self.display_map
16843 .update(cx, |map, cx| map.snapshot(cx))
16844 .max_point()
16845 }
16846
16847 pub fn text(&self, cx: &App) -> String {
16848 self.buffer.read(cx).read(cx).text()
16849 }
16850
16851 pub fn is_empty(&self, cx: &App) -> bool {
16852 self.buffer.read(cx).read(cx).is_empty()
16853 }
16854
16855 pub fn text_option(&self, cx: &App) -> Option<String> {
16856 let text = self.text(cx);
16857 let text = text.trim();
16858
16859 if text.is_empty() {
16860 return None;
16861 }
16862
16863 Some(text.to_string())
16864 }
16865
16866 pub fn set_text(
16867 &mut self,
16868 text: impl Into<Arc<str>>,
16869 window: &mut Window,
16870 cx: &mut Context<Self>,
16871 ) {
16872 self.transact(window, cx, |this, _, cx| {
16873 this.buffer
16874 .read(cx)
16875 .as_singleton()
16876 .expect("you can only call set_text on editors for singleton buffers")
16877 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16878 });
16879 }
16880
16881 pub fn display_text(&self, cx: &mut App) -> String {
16882 self.display_map
16883 .update(cx, |map, cx| map.snapshot(cx))
16884 .text()
16885 }
16886
16887 fn create_minimap(
16888 &self,
16889 minimap_settings: MinimapSettings,
16890 window: &mut Window,
16891 cx: &mut Context<Self>,
16892 ) -> Option<Entity<Self>> {
16893 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
16894 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
16895 }
16896
16897 fn initialize_new_minimap(
16898 &self,
16899 minimap_settings: MinimapSettings,
16900 window: &mut Window,
16901 cx: &mut Context<Self>,
16902 ) -> Entity<Self> {
16903 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
16904
16905 let mut minimap = Editor::new_internal(
16906 EditorMode::Minimap {
16907 parent: cx.weak_entity(),
16908 },
16909 self.buffer.clone(),
16910 self.project.clone(),
16911 Some(self.display_map.clone()),
16912 window,
16913 cx,
16914 );
16915 minimap.scroll_manager.clone_state(&self.scroll_manager);
16916 minimap.set_text_style_refinement(TextStyleRefinement {
16917 font_size: Some(MINIMAP_FONT_SIZE),
16918 font_weight: Some(MINIMAP_FONT_WEIGHT),
16919 ..Default::default()
16920 });
16921 minimap.update_minimap_configuration(minimap_settings, cx);
16922 cx.new(|_| minimap)
16923 }
16924
16925 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
16926 let current_line_highlight = minimap_settings
16927 .current_line_highlight
16928 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
16929 self.set_current_line_highlight(Some(current_line_highlight));
16930 }
16931
16932 pub fn minimap(&self) -> Option<&Entity<Self>> {
16933 self.minimap
16934 .as_ref()
16935 .filter(|_| self.minimap_visibility.visible())
16936 }
16937
16938 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16939 let mut wrap_guides = smallvec![];
16940
16941 if self.show_wrap_guides == Some(false) {
16942 return wrap_guides;
16943 }
16944
16945 let settings = self.buffer.read(cx).language_settings(cx);
16946 if settings.show_wrap_guides {
16947 match self.soft_wrap_mode(cx) {
16948 SoftWrap::Column(soft_wrap) => {
16949 wrap_guides.push((soft_wrap as usize, true));
16950 }
16951 SoftWrap::Bounded(soft_wrap) => {
16952 wrap_guides.push((soft_wrap as usize, true));
16953 }
16954 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16955 }
16956 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16957 }
16958
16959 wrap_guides
16960 }
16961
16962 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16963 let settings = self.buffer.read(cx).language_settings(cx);
16964 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16965 match mode {
16966 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16967 SoftWrap::None
16968 }
16969 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16970 language_settings::SoftWrap::PreferredLineLength => {
16971 SoftWrap::Column(settings.preferred_line_length)
16972 }
16973 language_settings::SoftWrap::Bounded => {
16974 SoftWrap::Bounded(settings.preferred_line_length)
16975 }
16976 }
16977 }
16978
16979 pub fn set_soft_wrap_mode(
16980 &mut self,
16981 mode: language_settings::SoftWrap,
16982
16983 cx: &mut Context<Self>,
16984 ) {
16985 self.soft_wrap_mode_override = Some(mode);
16986 cx.notify();
16987 }
16988
16989 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16990 self.hard_wrap = hard_wrap;
16991 cx.notify();
16992 }
16993
16994 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16995 self.text_style_refinement = Some(style);
16996 }
16997
16998 /// called by the Element so we know what style we were most recently rendered with.
16999 pub(crate) fn set_style(
17000 &mut self,
17001 style: EditorStyle,
17002 window: &mut Window,
17003 cx: &mut Context<Self>,
17004 ) {
17005 // We intentionally do not inform the display map about the minimap style
17006 // so that wrapping is not recalculated and stays consistent for the editor
17007 // and its linked minimap.
17008 if !self.mode.is_minimap() {
17009 let rem_size = window.rem_size();
17010 self.display_map.update(cx, |map, cx| {
17011 map.set_font(
17012 style.text.font(),
17013 style.text.font_size.to_pixels(rem_size),
17014 cx,
17015 )
17016 });
17017 }
17018 self.style = Some(style);
17019 }
17020
17021 pub fn style(&self) -> Option<&EditorStyle> {
17022 self.style.as_ref()
17023 }
17024
17025 // Called by the element. This method is not designed to be called outside of the editor
17026 // element's layout code because it does not notify when rewrapping is computed synchronously.
17027 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17028 self.display_map
17029 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17030 }
17031
17032 pub fn set_soft_wrap(&mut self) {
17033 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17034 }
17035
17036 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17037 if self.soft_wrap_mode_override.is_some() {
17038 self.soft_wrap_mode_override.take();
17039 } else {
17040 let soft_wrap = match self.soft_wrap_mode(cx) {
17041 SoftWrap::GitDiff => return,
17042 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17043 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17044 language_settings::SoftWrap::None
17045 }
17046 };
17047 self.soft_wrap_mode_override = Some(soft_wrap);
17048 }
17049 cx.notify();
17050 }
17051
17052 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17053 let Some(workspace) = self.workspace() else {
17054 return;
17055 };
17056 let fs = workspace.read(cx).app_state().fs.clone();
17057 let current_show = TabBarSettings::get_global(cx).show;
17058 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17059 setting.show = Some(!current_show);
17060 });
17061 }
17062
17063 pub fn toggle_indent_guides(
17064 &mut self,
17065 _: &ToggleIndentGuides,
17066 _: &mut Window,
17067 cx: &mut Context<Self>,
17068 ) {
17069 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17070 self.buffer
17071 .read(cx)
17072 .language_settings(cx)
17073 .indent_guides
17074 .enabled
17075 });
17076 self.show_indent_guides = Some(!currently_enabled);
17077 cx.notify();
17078 }
17079
17080 fn should_show_indent_guides(&self) -> Option<bool> {
17081 self.show_indent_guides
17082 }
17083
17084 pub fn toggle_line_numbers(
17085 &mut self,
17086 _: &ToggleLineNumbers,
17087 _: &mut Window,
17088 cx: &mut Context<Self>,
17089 ) {
17090 let mut editor_settings = EditorSettings::get_global(cx).clone();
17091 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17092 EditorSettings::override_global(editor_settings, cx);
17093 }
17094
17095 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17096 if let Some(show_line_numbers) = self.show_line_numbers {
17097 return show_line_numbers;
17098 }
17099 EditorSettings::get_global(cx).gutter.line_numbers
17100 }
17101
17102 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17103 self.use_relative_line_numbers
17104 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17105 }
17106
17107 pub fn toggle_relative_line_numbers(
17108 &mut self,
17109 _: &ToggleRelativeLineNumbers,
17110 _: &mut Window,
17111 cx: &mut Context<Self>,
17112 ) {
17113 let is_relative = self.should_use_relative_line_numbers(cx);
17114 self.set_relative_line_number(Some(!is_relative), cx)
17115 }
17116
17117 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17118 self.use_relative_line_numbers = is_relative;
17119 cx.notify();
17120 }
17121
17122 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17123 self.show_gutter = show_gutter;
17124 cx.notify();
17125 }
17126
17127 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17128 self.show_scrollbars = ScrollbarAxes {
17129 horizontal: show,
17130 vertical: show,
17131 };
17132 cx.notify();
17133 }
17134
17135 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17136 self.show_scrollbars.vertical = show;
17137 cx.notify();
17138 }
17139
17140 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17141 self.show_scrollbars.horizontal = show;
17142 cx.notify();
17143 }
17144
17145 pub fn set_minimap_visibility(
17146 &mut self,
17147 minimap_visibility: MinimapVisibility,
17148 window: &mut Window,
17149 cx: &mut Context<Self>,
17150 ) {
17151 if self.minimap_visibility != minimap_visibility {
17152 if minimap_visibility.visible() && self.minimap.is_none() {
17153 let minimap_settings = EditorSettings::get_global(cx).minimap;
17154 self.minimap =
17155 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17156 }
17157 self.minimap_visibility = minimap_visibility;
17158 cx.notify();
17159 }
17160 }
17161
17162 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17163 self.set_show_scrollbars(false, cx);
17164 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17165 }
17166
17167 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17168 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17169 }
17170
17171 /// Normally the text in full mode and auto height editors is padded on the
17172 /// left side by roughly half a character width for improved hit testing.
17173 ///
17174 /// Use this method to disable this for cases where this is not wanted (e.g.
17175 /// if you want to align the editor text with some other text above or below)
17176 /// or if you want to add this padding to single-line editors.
17177 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17178 self.offset_content = offset_content;
17179 cx.notify();
17180 }
17181
17182 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17183 self.show_line_numbers = Some(show_line_numbers);
17184 cx.notify();
17185 }
17186
17187 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17188 self.disable_expand_excerpt_buttons = true;
17189 cx.notify();
17190 }
17191
17192 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17193 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17194 cx.notify();
17195 }
17196
17197 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17198 self.show_code_actions = Some(show_code_actions);
17199 cx.notify();
17200 }
17201
17202 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17203 self.show_runnables = Some(show_runnables);
17204 cx.notify();
17205 }
17206
17207 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17208 self.show_breakpoints = Some(show_breakpoints);
17209 cx.notify();
17210 }
17211
17212 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17213 if self.display_map.read(cx).masked != masked {
17214 self.display_map.update(cx, |map, _| map.masked = masked);
17215 }
17216 cx.notify()
17217 }
17218
17219 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17220 self.show_wrap_guides = Some(show_wrap_guides);
17221 cx.notify();
17222 }
17223
17224 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17225 self.show_indent_guides = Some(show_indent_guides);
17226 cx.notify();
17227 }
17228
17229 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17230 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17231 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17232 if let Some(dir) = file.abs_path(cx).parent() {
17233 return Some(dir.to_owned());
17234 }
17235 }
17236
17237 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17238 return Some(project_path.path.to_path_buf());
17239 }
17240 }
17241
17242 None
17243 }
17244
17245 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17246 self.active_excerpt(cx)?
17247 .1
17248 .read(cx)
17249 .file()
17250 .and_then(|f| f.as_local())
17251 }
17252
17253 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17254 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17255 let buffer = buffer.read(cx);
17256 if let Some(project_path) = buffer.project_path(cx) {
17257 let project = self.project.as_ref()?.read(cx);
17258 project.absolute_path(&project_path, cx)
17259 } else {
17260 buffer
17261 .file()
17262 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17263 }
17264 })
17265 }
17266
17267 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17268 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17269 let project_path = buffer.read(cx).project_path(cx)?;
17270 let project = self.project.as_ref()?.read(cx);
17271 let entry = project.entry_for_path(&project_path, cx)?;
17272 let path = entry.path.to_path_buf();
17273 Some(path)
17274 })
17275 }
17276
17277 pub fn reveal_in_finder(
17278 &mut self,
17279 _: &RevealInFileManager,
17280 _window: &mut Window,
17281 cx: &mut Context<Self>,
17282 ) {
17283 if let Some(target) = self.target_file(cx) {
17284 cx.reveal_path(&target.abs_path(cx));
17285 }
17286 }
17287
17288 pub fn copy_path(
17289 &mut self,
17290 _: &zed_actions::workspace::CopyPath,
17291 _window: &mut Window,
17292 cx: &mut Context<Self>,
17293 ) {
17294 if let Some(path) = self.target_file_abs_path(cx) {
17295 if let Some(path) = path.to_str() {
17296 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17297 }
17298 }
17299 }
17300
17301 pub fn copy_relative_path(
17302 &mut self,
17303 _: &zed_actions::workspace::CopyRelativePath,
17304 _window: &mut Window,
17305 cx: &mut Context<Self>,
17306 ) {
17307 if let Some(path) = self.target_file_path(cx) {
17308 if let Some(path) = path.to_str() {
17309 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17310 }
17311 }
17312 }
17313
17314 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17315 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17316 buffer.read(cx).project_path(cx)
17317 } else {
17318 None
17319 }
17320 }
17321
17322 // Returns true if the editor handled a go-to-line request
17323 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17324 maybe!({
17325 let breakpoint_store = self.breakpoint_store.as_ref()?;
17326
17327 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17328 else {
17329 self.clear_row_highlights::<ActiveDebugLine>();
17330 return None;
17331 };
17332
17333 let position = active_stack_frame.position;
17334 let buffer_id = position.buffer_id?;
17335 let snapshot = self
17336 .project
17337 .as_ref()?
17338 .read(cx)
17339 .buffer_for_id(buffer_id, cx)?
17340 .read(cx)
17341 .snapshot();
17342
17343 let mut handled = false;
17344 for (id, ExcerptRange { context, .. }) in
17345 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17346 {
17347 if context.start.cmp(&position, &snapshot).is_ge()
17348 || context.end.cmp(&position, &snapshot).is_lt()
17349 {
17350 continue;
17351 }
17352 let snapshot = self.buffer.read(cx).snapshot(cx);
17353 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17354
17355 handled = true;
17356 self.clear_row_highlights::<ActiveDebugLine>();
17357
17358 self.go_to_line::<ActiveDebugLine>(
17359 multibuffer_anchor,
17360 Some(cx.theme().colors().editor_debugger_active_line_background),
17361 window,
17362 cx,
17363 );
17364
17365 cx.notify();
17366 }
17367
17368 handled.then_some(())
17369 })
17370 .is_some()
17371 }
17372
17373 pub fn copy_file_name_without_extension(
17374 &mut self,
17375 _: &CopyFileNameWithoutExtension,
17376 _: &mut Window,
17377 cx: &mut Context<Self>,
17378 ) {
17379 if let Some(file) = self.target_file(cx) {
17380 if let Some(file_stem) = file.path().file_stem() {
17381 if let Some(name) = file_stem.to_str() {
17382 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17383 }
17384 }
17385 }
17386 }
17387
17388 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17389 if let Some(file) = self.target_file(cx) {
17390 if let Some(file_name) = file.path().file_name() {
17391 if let Some(name) = file_name.to_str() {
17392 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17393 }
17394 }
17395 }
17396 }
17397
17398 pub fn toggle_git_blame(
17399 &mut self,
17400 _: &::git::Blame,
17401 window: &mut Window,
17402 cx: &mut Context<Self>,
17403 ) {
17404 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17405
17406 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17407 self.start_git_blame(true, window, cx);
17408 }
17409
17410 cx.notify();
17411 }
17412
17413 pub fn toggle_git_blame_inline(
17414 &mut self,
17415 _: &ToggleGitBlameInline,
17416 window: &mut Window,
17417 cx: &mut Context<Self>,
17418 ) {
17419 self.toggle_git_blame_inline_internal(true, window, cx);
17420 cx.notify();
17421 }
17422
17423 pub fn open_git_blame_commit(
17424 &mut self,
17425 _: &OpenGitBlameCommit,
17426 window: &mut Window,
17427 cx: &mut Context<Self>,
17428 ) {
17429 self.open_git_blame_commit_internal(window, cx);
17430 }
17431
17432 fn open_git_blame_commit_internal(
17433 &mut self,
17434 window: &mut Window,
17435 cx: &mut Context<Self>,
17436 ) -> Option<()> {
17437 let blame = self.blame.as_ref()?;
17438 let snapshot = self.snapshot(window, cx);
17439 let cursor = self.selections.newest::<Point>(cx).head();
17440 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17441 let blame_entry = blame
17442 .update(cx, |blame, cx| {
17443 blame
17444 .blame_for_rows(
17445 &[RowInfo {
17446 buffer_id: Some(buffer.remote_id()),
17447 buffer_row: Some(point.row),
17448 ..Default::default()
17449 }],
17450 cx,
17451 )
17452 .next()
17453 })
17454 .flatten()?;
17455 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17456 let repo = blame.read(cx).repository(cx)?;
17457 let workspace = self.workspace()?.downgrade();
17458 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17459 None
17460 }
17461
17462 pub fn git_blame_inline_enabled(&self) -> bool {
17463 self.git_blame_inline_enabled
17464 }
17465
17466 pub fn toggle_selection_menu(
17467 &mut self,
17468 _: &ToggleSelectionMenu,
17469 _: &mut Window,
17470 cx: &mut Context<Self>,
17471 ) {
17472 self.show_selection_menu = self
17473 .show_selection_menu
17474 .map(|show_selections_menu| !show_selections_menu)
17475 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17476
17477 cx.notify();
17478 }
17479
17480 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17481 self.show_selection_menu
17482 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17483 }
17484
17485 fn start_git_blame(
17486 &mut self,
17487 user_triggered: bool,
17488 window: &mut Window,
17489 cx: &mut Context<Self>,
17490 ) {
17491 if let Some(project) = self.project.as_ref() {
17492 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17493 return;
17494 };
17495
17496 if buffer.read(cx).file().is_none() {
17497 return;
17498 }
17499
17500 let focused = self.focus_handle(cx).contains_focused(window, cx);
17501
17502 let project = project.clone();
17503 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17504 self.blame_subscription =
17505 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17506 self.blame = Some(blame);
17507 }
17508 }
17509
17510 fn toggle_git_blame_inline_internal(
17511 &mut self,
17512 user_triggered: bool,
17513 window: &mut Window,
17514 cx: &mut Context<Self>,
17515 ) {
17516 if self.git_blame_inline_enabled {
17517 self.git_blame_inline_enabled = false;
17518 self.show_git_blame_inline = false;
17519 self.show_git_blame_inline_delay_task.take();
17520 } else {
17521 self.git_blame_inline_enabled = true;
17522 self.start_git_blame_inline(user_triggered, window, cx);
17523 }
17524
17525 cx.notify();
17526 }
17527
17528 fn start_git_blame_inline(
17529 &mut self,
17530 user_triggered: bool,
17531 window: &mut Window,
17532 cx: &mut Context<Self>,
17533 ) {
17534 self.start_git_blame(user_triggered, window, cx);
17535
17536 if ProjectSettings::get_global(cx)
17537 .git
17538 .inline_blame_delay()
17539 .is_some()
17540 {
17541 self.start_inline_blame_timer(window, cx);
17542 } else {
17543 self.show_git_blame_inline = true
17544 }
17545 }
17546
17547 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17548 self.blame.as_ref()
17549 }
17550
17551 pub fn show_git_blame_gutter(&self) -> bool {
17552 self.show_git_blame_gutter
17553 }
17554
17555 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17556 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17557 }
17558
17559 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17560 self.show_git_blame_inline
17561 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17562 && !self.newest_selection_head_on_empty_line(cx)
17563 && self.has_blame_entries(cx)
17564 }
17565
17566 fn has_blame_entries(&self, cx: &App) -> bool {
17567 self.blame()
17568 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17569 }
17570
17571 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17572 let cursor_anchor = self.selections.newest_anchor().head();
17573
17574 let snapshot = self.buffer.read(cx).snapshot(cx);
17575 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17576
17577 snapshot.line_len(buffer_row) == 0
17578 }
17579
17580 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17581 let buffer_and_selection = maybe!({
17582 let selection = self.selections.newest::<Point>(cx);
17583 let selection_range = selection.range();
17584
17585 let multi_buffer = self.buffer().read(cx);
17586 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17587 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17588
17589 let (buffer, range, _) = if selection.reversed {
17590 buffer_ranges.first()
17591 } else {
17592 buffer_ranges.last()
17593 }?;
17594
17595 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17596 ..text::ToPoint::to_point(&range.end, &buffer).row;
17597 Some((
17598 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17599 selection,
17600 ))
17601 });
17602
17603 let Some((buffer, selection)) = buffer_and_selection else {
17604 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17605 };
17606
17607 let Some(project) = self.project.as_ref() else {
17608 return Task::ready(Err(anyhow!("editor does not have project")));
17609 };
17610
17611 project.update(cx, |project, cx| {
17612 project.get_permalink_to_line(&buffer, selection, cx)
17613 })
17614 }
17615
17616 pub fn copy_permalink_to_line(
17617 &mut self,
17618 _: &CopyPermalinkToLine,
17619 window: &mut Window,
17620 cx: &mut Context<Self>,
17621 ) {
17622 let permalink_task = self.get_permalink_to_line(cx);
17623 let workspace = self.workspace();
17624
17625 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17626 Ok(permalink) => {
17627 cx.update(|_, cx| {
17628 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17629 })
17630 .ok();
17631 }
17632 Err(err) => {
17633 let message = format!("Failed to copy permalink: {err}");
17634
17635 anyhow::Result::<()>::Err(err).log_err();
17636
17637 if let Some(workspace) = workspace {
17638 workspace
17639 .update_in(cx, |workspace, _, cx| {
17640 struct CopyPermalinkToLine;
17641
17642 workspace.show_toast(
17643 Toast::new(
17644 NotificationId::unique::<CopyPermalinkToLine>(),
17645 message,
17646 ),
17647 cx,
17648 )
17649 })
17650 .ok();
17651 }
17652 }
17653 })
17654 .detach();
17655 }
17656
17657 pub fn copy_file_location(
17658 &mut self,
17659 _: &CopyFileLocation,
17660 _: &mut Window,
17661 cx: &mut Context<Self>,
17662 ) {
17663 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17664 if let Some(file) = self.target_file(cx) {
17665 if let Some(path) = file.path().to_str() {
17666 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17667 }
17668 }
17669 }
17670
17671 pub fn open_permalink_to_line(
17672 &mut self,
17673 _: &OpenPermalinkToLine,
17674 window: &mut Window,
17675 cx: &mut Context<Self>,
17676 ) {
17677 let permalink_task = self.get_permalink_to_line(cx);
17678 let workspace = self.workspace();
17679
17680 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17681 Ok(permalink) => {
17682 cx.update(|_, cx| {
17683 cx.open_url(permalink.as_ref());
17684 })
17685 .ok();
17686 }
17687 Err(err) => {
17688 let message = format!("Failed to open permalink: {err}");
17689
17690 anyhow::Result::<()>::Err(err).log_err();
17691
17692 if let Some(workspace) = workspace {
17693 workspace
17694 .update(cx, |workspace, cx| {
17695 struct OpenPermalinkToLine;
17696
17697 workspace.show_toast(
17698 Toast::new(
17699 NotificationId::unique::<OpenPermalinkToLine>(),
17700 message,
17701 ),
17702 cx,
17703 )
17704 })
17705 .ok();
17706 }
17707 }
17708 })
17709 .detach();
17710 }
17711
17712 pub fn insert_uuid_v4(
17713 &mut self,
17714 _: &InsertUuidV4,
17715 window: &mut Window,
17716 cx: &mut Context<Self>,
17717 ) {
17718 self.insert_uuid(UuidVersion::V4, window, cx);
17719 }
17720
17721 pub fn insert_uuid_v7(
17722 &mut self,
17723 _: &InsertUuidV7,
17724 window: &mut Window,
17725 cx: &mut Context<Self>,
17726 ) {
17727 self.insert_uuid(UuidVersion::V7, window, cx);
17728 }
17729
17730 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17731 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17732 self.transact(window, cx, |this, window, cx| {
17733 let edits = this
17734 .selections
17735 .all::<Point>(cx)
17736 .into_iter()
17737 .map(|selection| {
17738 let uuid = match version {
17739 UuidVersion::V4 => uuid::Uuid::new_v4(),
17740 UuidVersion::V7 => uuid::Uuid::now_v7(),
17741 };
17742
17743 (selection.range(), uuid.to_string())
17744 });
17745 this.edit(edits, cx);
17746 this.refresh_inline_completion(true, false, window, cx);
17747 });
17748 }
17749
17750 pub fn open_selections_in_multibuffer(
17751 &mut self,
17752 _: &OpenSelectionsInMultibuffer,
17753 window: &mut Window,
17754 cx: &mut Context<Self>,
17755 ) {
17756 let multibuffer = self.buffer.read(cx);
17757
17758 let Some(buffer) = multibuffer.as_singleton() else {
17759 return;
17760 };
17761
17762 let Some(workspace) = self.workspace() else {
17763 return;
17764 };
17765
17766 let locations = self
17767 .selections
17768 .disjoint_anchors()
17769 .iter()
17770 .map(|selection| {
17771 let range = if selection.reversed {
17772 selection.end.text_anchor..selection.start.text_anchor
17773 } else {
17774 selection.start.text_anchor..selection.end.text_anchor
17775 };
17776 Location {
17777 buffer: buffer.clone(),
17778 range,
17779 }
17780 })
17781 .collect::<Vec<_>>();
17782
17783 let title = multibuffer.title(cx).to_string();
17784
17785 cx.spawn_in(window, async move |_, cx| {
17786 workspace.update_in(cx, |workspace, window, cx| {
17787 Self::open_locations_in_multibuffer(
17788 workspace,
17789 locations,
17790 format!("Selections for '{title}'"),
17791 false,
17792 MultibufferSelectionMode::All,
17793 window,
17794 cx,
17795 );
17796 })
17797 })
17798 .detach();
17799 }
17800
17801 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17802 /// last highlight added will be used.
17803 ///
17804 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17805 pub fn highlight_rows<T: 'static>(
17806 &mut self,
17807 range: Range<Anchor>,
17808 color: Hsla,
17809 options: RowHighlightOptions,
17810 cx: &mut Context<Self>,
17811 ) {
17812 let snapshot = self.buffer().read(cx).snapshot(cx);
17813 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17814 let ix = row_highlights.binary_search_by(|highlight| {
17815 Ordering::Equal
17816 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17817 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17818 });
17819
17820 if let Err(mut ix) = ix {
17821 let index = post_inc(&mut self.highlight_order);
17822
17823 // If this range intersects with the preceding highlight, then merge it with
17824 // the preceding highlight. Otherwise insert a new highlight.
17825 let mut merged = false;
17826 if ix > 0 {
17827 let prev_highlight = &mut row_highlights[ix - 1];
17828 if prev_highlight
17829 .range
17830 .end
17831 .cmp(&range.start, &snapshot)
17832 .is_ge()
17833 {
17834 ix -= 1;
17835 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17836 prev_highlight.range.end = range.end;
17837 }
17838 merged = true;
17839 prev_highlight.index = index;
17840 prev_highlight.color = color;
17841 prev_highlight.options = options;
17842 }
17843 }
17844
17845 if !merged {
17846 row_highlights.insert(
17847 ix,
17848 RowHighlight {
17849 range: range.clone(),
17850 index,
17851 color,
17852 options,
17853 type_id: TypeId::of::<T>(),
17854 },
17855 );
17856 }
17857
17858 // If any of the following highlights intersect with this one, merge them.
17859 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17860 let highlight = &row_highlights[ix];
17861 if next_highlight
17862 .range
17863 .start
17864 .cmp(&highlight.range.end, &snapshot)
17865 .is_le()
17866 {
17867 if next_highlight
17868 .range
17869 .end
17870 .cmp(&highlight.range.end, &snapshot)
17871 .is_gt()
17872 {
17873 row_highlights[ix].range.end = next_highlight.range.end;
17874 }
17875 row_highlights.remove(ix + 1);
17876 } else {
17877 break;
17878 }
17879 }
17880 }
17881 }
17882
17883 /// Remove any highlighted row ranges of the given type that intersect the
17884 /// given ranges.
17885 pub fn remove_highlighted_rows<T: 'static>(
17886 &mut self,
17887 ranges_to_remove: Vec<Range<Anchor>>,
17888 cx: &mut Context<Self>,
17889 ) {
17890 let snapshot = self.buffer().read(cx).snapshot(cx);
17891 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17892 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17893 row_highlights.retain(|highlight| {
17894 while let Some(range_to_remove) = ranges_to_remove.peek() {
17895 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17896 Ordering::Less | Ordering::Equal => {
17897 ranges_to_remove.next();
17898 }
17899 Ordering::Greater => {
17900 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17901 Ordering::Less | Ordering::Equal => {
17902 return false;
17903 }
17904 Ordering::Greater => break,
17905 }
17906 }
17907 }
17908 }
17909
17910 true
17911 })
17912 }
17913
17914 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17915 pub fn clear_row_highlights<T: 'static>(&mut self) {
17916 self.highlighted_rows.remove(&TypeId::of::<T>());
17917 }
17918
17919 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17920 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17921 self.highlighted_rows
17922 .get(&TypeId::of::<T>())
17923 .map_or(&[] as &[_], |vec| vec.as_slice())
17924 .iter()
17925 .map(|highlight| (highlight.range.clone(), highlight.color))
17926 }
17927
17928 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17929 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17930 /// Allows to ignore certain kinds of highlights.
17931 pub fn highlighted_display_rows(
17932 &self,
17933 window: &mut Window,
17934 cx: &mut App,
17935 ) -> BTreeMap<DisplayRow, LineHighlight> {
17936 let snapshot = self.snapshot(window, cx);
17937 let mut used_highlight_orders = HashMap::default();
17938 self.highlighted_rows
17939 .iter()
17940 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17941 .fold(
17942 BTreeMap::<DisplayRow, LineHighlight>::new(),
17943 |mut unique_rows, highlight| {
17944 let start = highlight.range.start.to_display_point(&snapshot);
17945 let end = highlight.range.end.to_display_point(&snapshot);
17946 let start_row = start.row().0;
17947 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17948 && end.column() == 0
17949 {
17950 end.row().0.saturating_sub(1)
17951 } else {
17952 end.row().0
17953 };
17954 for row in start_row..=end_row {
17955 let used_index =
17956 used_highlight_orders.entry(row).or_insert(highlight.index);
17957 if highlight.index >= *used_index {
17958 *used_index = highlight.index;
17959 unique_rows.insert(
17960 DisplayRow(row),
17961 LineHighlight {
17962 include_gutter: highlight.options.include_gutter,
17963 border: None,
17964 background: highlight.color.into(),
17965 type_id: Some(highlight.type_id),
17966 },
17967 );
17968 }
17969 }
17970 unique_rows
17971 },
17972 )
17973 }
17974
17975 pub fn highlighted_display_row_for_autoscroll(
17976 &self,
17977 snapshot: &DisplaySnapshot,
17978 ) -> Option<DisplayRow> {
17979 self.highlighted_rows
17980 .values()
17981 .flat_map(|highlighted_rows| highlighted_rows.iter())
17982 .filter_map(|highlight| {
17983 if highlight.options.autoscroll {
17984 Some(highlight.range.start.to_display_point(snapshot).row())
17985 } else {
17986 None
17987 }
17988 })
17989 .min()
17990 }
17991
17992 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17993 self.highlight_background::<SearchWithinRange>(
17994 ranges,
17995 |colors| colors.editor_document_highlight_read_background,
17996 cx,
17997 )
17998 }
17999
18000 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18001 self.breadcrumb_header = Some(new_header);
18002 }
18003
18004 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18005 self.clear_background_highlights::<SearchWithinRange>(cx);
18006 }
18007
18008 pub fn highlight_background<T: 'static>(
18009 &mut self,
18010 ranges: &[Range<Anchor>],
18011 color_fetcher: fn(&ThemeColors) -> Hsla,
18012 cx: &mut Context<Self>,
18013 ) {
18014 self.background_highlights
18015 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18016 self.scrollbar_marker_state.dirty = true;
18017 cx.notify();
18018 }
18019
18020 pub fn clear_background_highlights<T: 'static>(
18021 &mut self,
18022 cx: &mut Context<Self>,
18023 ) -> Option<BackgroundHighlight> {
18024 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
18025 if !text_highlights.1.is_empty() {
18026 self.scrollbar_marker_state.dirty = true;
18027 cx.notify();
18028 }
18029 Some(text_highlights)
18030 }
18031
18032 pub fn highlight_gutter<T: 'static>(
18033 &mut self,
18034 ranges: &[Range<Anchor>],
18035 color_fetcher: fn(&App) -> Hsla,
18036 cx: &mut Context<Self>,
18037 ) {
18038 self.gutter_highlights
18039 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18040 cx.notify();
18041 }
18042
18043 pub fn clear_gutter_highlights<T: 'static>(
18044 &mut self,
18045 cx: &mut Context<Self>,
18046 ) -> Option<GutterHighlight> {
18047 cx.notify();
18048 self.gutter_highlights.remove(&TypeId::of::<T>())
18049 }
18050
18051 #[cfg(feature = "test-support")]
18052 pub fn all_text_background_highlights(
18053 &self,
18054 window: &mut Window,
18055 cx: &mut Context<Self>,
18056 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18057 let snapshot = self.snapshot(window, cx);
18058 let buffer = &snapshot.buffer_snapshot;
18059 let start = buffer.anchor_before(0);
18060 let end = buffer.anchor_after(buffer.len());
18061 let theme = cx.theme().colors();
18062 self.background_highlights_in_range(start..end, &snapshot, theme)
18063 }
18064
18065 #[cfg(feature = "test-support")]
18066 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18067 let snapshot = self.buffer().read(cx).snapshot(cx);
18068
18069 let highlights = self
18070 .background_highlights
18071 .get(&TypeId::of::<items::BufferSearchHighlights>());
18072
18073 if let Some((_color, ranges)) = highlights {
18074 ranges
18075 .iter()
18076 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18077 .collect_vec()
18078 } else {
18079 vec![]
18080 }
18081 }
18082
18083 fn document_highlights_for_position<'a>(
18084 &'a self,
18085 position: Anchor,
18086 buffer: &'a MultiBufferSnapshot,
18087 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18088 let read_highlights = self
18089 .background_highlights
18090 .get(&TypeId::of::<DocumentHighlightRead>())
18091 .map(|h| &h.1);
18092 let write_highlights = self
18093 .background_highlights
18094 .get(&TypeId::of::<DocumentHighlightWrite>())
18095 .map(|h| &h.1);
18096 let left_position = position.bias_left(buffer);
18097 let right_position = position.bias_right(buffer);
18098 read_highlights
18099 .into_iter()
18100 .chain(write_highlights)
18101 .flat_map(move |ranges| {
18102 let start_ix = match ranges.binary_search_by(|probe| {
18103 let cmp = probe.end.cmp(&left_position, buffer);
18104 if cmp.is_ge() {
18105 Ordering::Greater
18106 } else {
18107 Ordering::Less
18108 }
18109 }) {
18110 Ok(i) | Err(i) => i,
18111 };
18112
18113 ranges[start_ix..]
18114 .iter()
18115 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
18116 })
18117 }
18118
18119 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18120 self.background_highlights
18121 .get(&TypeId::of::<T>())
18122 .map_or(false, |(_, highlights)| !highlights.is_empty())
18123 }
18124
18125 pub fn background_highlights_in_range(
18126 &self,
18127 search_range: Range<Anchor>,
18128 display_snapshot: &DisplaySnapshot,
18129 theme: &ThemeColors,
18130 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18131 let mut results = Vec::new();
18132 for (color_fetcher, ranges) in self.background_highlights.values() {
18133 let color = color_fetcher(theme);
18134 let start_ix = match ranges.binary_search_by(|probe| {
18135 let cmp = probe
18136 .end
18137 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18138 if cmp.is_gt() {
18139 Ordering::Greater
18140 } else {
18141 Ordering::Less
18142 }
18143 }) {
18144 Ok(i) | Err(i) => i,
18145 };
18146 for range in &ranges[start_ix..] {
18147 if range
18148 .start
18149 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18150 .is_ge()
18151 {
18152 break;
18153 }
18154
18155 let start = range.start.to_display_point(display_snapshot);
18156 let end = range.end.to_display_point(display_snapshot);
18157 results.push((start..end, color))
18158 }
18159 }
18160 results
18161 }
18162
18163 pub fn background_highlight_row_ranges<T: 'static>(
18164 &self,
18165 search_range: Range<Anchor>,
18166 display_snapshot: &DisplaySnapshot,
18167 count: usize,
18168 ) -> Vec<RangeInclusive<DisplayPoint>> {
18169 let mut results = Vec::new();
18170 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
18171 return vec![];
18172 };
18173
18174 let start_ix = match ranges.binary_search_by(|probe| {
18175 let cmp = probe
18176 .end
18177 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18178 if cmp.is_gt() {
18179 Ordering::Greater
18180 } else {
18181 Ordering::Less
18182 }
18183 }) {
18184 Ok(i) | Err(i) => i,
18185 };
18186 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18187 if let (Some(start_display), Some(end_display)) = (start, end) {
18188 results.push(
18189 start_display.to_display_point(display_snapshot)
18190 ..=end_display.to_display_point(display_snapshot),
18191 );
18192 }
18193 };
18194 let mut start_row: Option<Point> = None;
18195 let mut end_row: Option<Point> = None;
18196 if ranges.len() > count {
18197 return Vec::new();
18198 }
18199 for range in &ranges[start_ix..] {
18200 if range
18201 .start
18202 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18203 .is_ge()
18204 {
18205 break;
18206 }
18207 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18208 if let Some(current_row) = &end_row {
18209 if end.row == current_row.row {
18210 continue;
18211 }
18212 }
18213 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18214 if start_row.is_none() {
18215 assert_eq!(end_row, None);
18216 start_row = Some(start);
18217 end_row = Some(end);
18218 continue;
18219 }
18220 if let Some(current_end) = end_row.as_mut() {
18221 if start.row > current_end.row + 1 {
18222 push_region(start_row, end_row);
18223 start_row = Some(start);
18224 end_row = Some(end);
18225 } else {
18226 // Merge two hunks.
18227 *current_end = end;
18228 }
18229 } else {
18230 unreachable!();
18231 }
18232 }
18233 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18234 push_region(start_row, end_row);
18235 results
18236 }
18237
18238 pub fn gutter_highlights_in_range(
18239 &self,
18240 search_range: Range<Anchor>,
18241 display_snapshot: &DisplaySnapshot,
18242 cx: &App,
18243 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18244 let mut results = Vec::new();
18245 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18246 let color = color_fetcher(cx);
18247 let start_ix = match ranges.binary_search_by(|probe| {
18248 let cmp = probe
18249 .end
18250 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18251 if cmp.is_gt() {
18252 Ordering::Greater
18253 } else {
18254 Ordering::Less
18255 }
18256 }) {
18257 Ok(i) | Err(i) => i,
18258 };
18259 for range in &ranges[start_ix..] {
18260 if range
18261 .start
18262 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18263 .is_ge()
18264 {
18265 break;
18266 }
18267
18268 let start = range.start.to_display_point(display_snapshot);
18269 let end = range.end.to_display_point(display_snapshot);
18270 results.push((start..end, color))
18271 }
18272 }
18273 results
18274 }
18275
18276 /// Get the text ranges corresponding to the redaction query
18277 pub fn redacted_ranges(
18278 &self,
18279 search_range: Range<Anchor>,
18280 display_snapshot: &DisplaySnapshot,
18281 cx: &App,
18282 ) -> Vec<Range<DisplayPoint>> {
18283 display_snapshot
18284 .buffer_snapshot
18285 .redacted_ranges(search_range, |file| {
18286 if let Some(file) = file {
18287 file.is_private()
18288 && EditorSettings::get(
18289 Some(SettingsLocation {
18290 worktree_id: file.worktree_id(cx),
18291 path: file.path().as_ref(),
18292 }),
18293 cx,
18294 )
18295 .redact_private_values
18296 } else {
18297 false
18298 }
18299 })
18300 .map(|range| {
18301 range.start.to_display_point(display_snapshot)
18302 ..range.end.to_display_point(display_snapshot)
18303 })
18304 .collect()
18305 }
18306
18307 pub fn highlight_text<T: 'static>(
18308 &mut self,
18309 ranges: Vec<Range<Anchor>>,
18310 style: HighlightStyle,
18311 cx: &mut Context<Self>,
18312 ) {
18313 self.display_map.update(cx, |map, _| {
18314 map.highlight_text(TypeId::of::<T>(), ranges, style)
18315 });
18316 cx.notify();
18317 }
18318
18319 pub(crate) fn highlight_inlays<T: 'static>(
18320 &mut self,
18321 highlights: Vec<InlayHighlight>,
18322 style: HighlightStyle,
18323 cx: &mut Context<Self>,
18324 ) {
18325 self.display_map.update(cx, |map, _| {
18326 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18327 });
18328 cx.notify();
18329 }
18330
18331 pub fn text_highlights<'a, T: 'static>(
18332 &'a self,
18333 cx: &'a App,
18334 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18335 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18336 }
18337
18338 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18339 let cleared = self
18340 .display_map
18341 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18342 if cleared {
18343 cx.notify();
18344 }
18345 }
18346
18347 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18348 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18349 && self.focus_handle.is_focused(window)
18350 }
18351
18352 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18353 self.show_cursor_when_unfocused = is_enabled;
18354 cx.notify();
18355 }
18356
18357 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18358 cx.notify();
18359 }
18360
18361 fn on_debug_session_event(
18362 &mut self,
18363 _session: Entity<Session>,
18364 event: &SessionEvent,
18365 cx: &mut Context<Self>,
18366 ) {
18367 match event {
18368 SessionEvent::InvalidateInlineValue => {
18369 self.refresh_inline_values(cx);
18370 }
18371 _ => {}
18372 }
18373 }
18374
18375 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18376 let Some(project) = self.project.clone() else {
18377 return;
18378 };
18379
18380 if !self.inline_value_cache.enabled {
18381 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18382 self.splice_inlays(&inlays, Vec::new(), cx);
18383 return;
18384 }
18385
18386 let current_execution_position = self
18387 .highlighted_rows
18388 .get(&TypeId::of::<ActiveDebugLine>())
18389 .and_then(|lines| lines.last().map(|line| line.range.start));
18390
18391 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18392 let inline_values = editor
18393 .update(cx, |editor, cx| {
18394 let Some(current_execution_position) = current_execution_position else {
18395 return Some(Task::ready(Ok(Vec::new())));
18396 };
18397
18398 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18399 let snapshot = buffer.snapshot(cx);
18400
18401 let excerpt = snapshot.excerpt_containing(
18402 current_execution_position..current_execution_position,
18403 )?;
18404
18405 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18406 })?;
18407
18408 let range =
18409 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18410
18411 project.inline_values(buffer, range, cx)
18412 })
18413 .ok()
18414 .flatten()?
18415 .await
18416 .context("refreshing debugger inlays")
18417 .log_err()?;
18418
18419 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18420
18421 for (buffer_id, inline_value) in inline_values
18422 .into_iter()
18423 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18424 {
18425 buffer_inline_values
18426 .entry(buffer_id)
18427 .or_default()
18428 .push(inline_value);
18429 }
18430
18431 editor
18432 .update(cx, |editor, cx| {
18433 let snapshot = editor.buffer.read(cx).snapshot(cx);
18434 let mut new_inlays = Vec::default();
18435
18436 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18437 let buffer_id = buffer_snapshot.remote_id();
18438 buffer_inline_values
18439 .get(&buffer_id)
18440 .into_iter()
18441 .flatten()
18442 .for_each(|hint| {
18443 let inlay = Inlay::debugger_hint(
18444 post_inc(&mut editor.next_inlay_id),
18445 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18446 hint.text(),
18447 );
18448
18449 new_inlays.push(inlay);
18450 });
18451 }
18452
18453 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18454 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18455
18456 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18457 })
18458 .ok()?;
18459 Some(())
18460 });
18461 }
18462
18463 fn on_buffer_event(
18464 &mut self,
18465 multibuffer: &Entity<MultiBuffer>,
18466 event: &multi_buffer::Event,
18467 window: &mut Window,
18468 cx: &mut Context<Self>,
18469 ) {
18470 match event {
18471 multi_buffer::Event::Edited {
18472 singleton_buffer_edited,
18473 edited_buffer: buffer_edited,
18474 } => {
18475 self.scrollbar_marker_state.dirty = true;
18476 self.active_indent_guides_state.dirty = true;
18477 self.refresh_active_diagnostics(cx);
18478 self.refresh_code_actions(window, cx);
18479 self.refresh_selected_text_highlights(true, window, cx);
18480 refresh_matching_bracket_highlights(self, window, cx);
18481 if self.has_active_inline_completion() {
18482 self.update_visible_inline_completion(window, cx);
18483 }
18484 if let Some(buffer) = buffer_edited {
18485 let buffer_id = buffer.read(cx).remote_id();
18486 if !self.registered_buffers.contains_key(&buffer_id) {
18487 if let Some(project) = self.project.as_ref() {
18488 project.update(cx, |project, cx| {
18489 self.registered_buffers.insert(
18490 buffer_id,
18491 project.register_buffer_with_language_servers(&buffer, cx),
18492 );
18493 })
18494 }
18495 }
18496 }
18497 cx.emit(EditorEvent::BufferEdited);
18498 cx.emit(SearchEvent::MatchesInvalidated);
18499 if *singleton_buffer_edited {
18500 if let Some(project) = &self.project {
18501 #[allow(clippy::mutable_key_type)]
18502 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18503 multibuffer
18504 .all_buffers()
18505 .into_iter()
18506 .filter_map(|buffer| {
18507 buffer.update(cx, |buffer, cx| {
18508 let language = buffer.language()?;
18509 let should_discard = project.update(cx, |project, cx| {
18510 project.is_local()
18511 && !project.has_language_servers_for(buffer, cx)
18512 });
18513 should_discard.not().then_some(language.clone())
18514 })
18515 })
18516 .collect::<HashSet<_>>()
18517 });
18518 if !languages_affected.is_empty() {
18519 self.refresh_inlay_hints(
18520 InlayHintRefreshReason::BufferEdited(languages_affected),
18521 cx,
18522 );
18523 }
18524 }
18525 }
18526
18527 let Some(project) = &self.project else { return };
18528 let (telemetry, is_via_ssh) = {
18529 let project = project.read(cx);
18530 let telemetry = project.client().telemetry().clone();
18531 let is_via_ssh = project.is_via_ssh();
18532 (telemetry, is_via_ssh)
18533 };
18534 refresh_linked_ranges(self, window, cx);
18535 telemetry.log_edit_event("editor", is_via_ssh);
18536 }
18537 multi_buffer::Event::ExcerptsAdded {
18538 buffer,
18539 predecessor,
18540 excerpts,
18541 } => {
18542 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18543 let buffer_id = buffer.read(cx).remote_id();
18544 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18545 if let Some(project) = &self.project {
18546 update_uncommitted_diff_for_buffer(
18547 cx.entity(),
18548 project,
18549 [buffer.clone()],
18550 self.buffer.clone(),
18551 cx,
18552 )
18553 .detach();
18554 }
18555 }
18556 cx.emit(EditorEvent::ExcerptsAdded {
18557 buffer: buffer.clone(),
18558 predecessor: *predecessor,
18559 excerpts: excerpts.clone(),
18560 });
18561 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18562 }
18563 multi_buffer::Event::ExcerptsRemoved {
18564 ids,
18565 removed_buffer_ids,
18566 } => {
18567 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18568 let buffer = self.buffer.read(cx);
18569 self.registered_buffers
18570 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18571 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18572 cx.emit(EditorEvent::ExcerptsRemoved {
18573 ids: ids.clone(),
18574 removed_buffer_ids: removed_buffer_ids.clone(),
18575 })
18576 }
18577 multi_buffer::Event::ExcerptsEdited {
18578 excerpt_ids,
18579 buffer_ids,
18580 } => {
18581 self.display_map.update(cx, |map, cx| {
18582 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18583 });
18584 cx.emit(EditorEvent::ExcerptsEdited {
18585 ids: excerpt_ids.clone(),
18586 })
18587 }
18588 multi_buffer::Event::ExcerptsExpanded { ids } => {
18589 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18590 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18591 }
18592 multi_buffer::Event::Reparsed(buffer_id) => {
18593 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18594 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18595
18596 cx.emit(EditorEvent::Reparsed(*buffer_id));
18597 }
18598 multi_buffer::Event::DiffHunksToggled => {
18599 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18600 }
18601 multi_buffer::Event::LanguageChanged(buffer_id) => {
18602 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18603 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18604 cx.emit(EditorEvent::Reparsed(*buffer_id));
18605 cx.notify();
18606 }
18607 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18608 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18609 multi_buffer::Event::FileHandleChanged
18610 | multi_buffer::Event::Reloaded
18611 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18612 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18613 multi_buffer::Event::DiagnosticsUpdated => {
18614 self.refresh_active_diagnostics(cx);
18615 self.refresh_inline_diagnostics(true, window, cx);
18616 self.scrollbar_marker_state.dirty = true;
18617 cx.notify();
18618 }
18619 _ => {}
18620 };
18621 }
18622
18623 pub fn start_temporary_diff_override(&mut self) {
18624 self.load_diff_task.take();
18625 self.temporary_diff_override = true;
18626 }
18627
18628 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18629 self.temporary_diff_override = false;
18630 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18631 self.buffer.update(cx, |buffer, cx| {
18632 buffer.set_all_diff_hunks_collapsed(cx);
18633 });
18634
18635 if let Some(project) = self.project.clone() {
18636 self.load_diff_task = Some(
18637 update_uncommitted_diff_for_buffer(
18638 cx.entity(),
18639 &project,
18640 self.buffer.read(cx).all_buffers(),
18641 self.buffer.clone(),
18642 cx,
18643 )
18644 .shared(),
18645 );
18646 }
18647 }
18648
18649 fn on_display_map_changed(
18650 &mut self,
18651 _: Entity<DisplayMap>,
18652 _: &mut Window,
18653 cx: &mut Context<Self>,
18654 ) {
18655 cx.notify();
18656 }
18657
18658 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18659 let new_severity = if self.diagnostics_enabled() {
18660 EditorSettings::get_global(cx)
18661 .diagnostics_max_severity
18662 .unwrap_or(DiagnosticSeverity::Hint)
18663 } else {
18664 DiagnosticSeverity::Off
18665 };
18666 self.set_max_diagnostics_severity(new_severity, cx);
18667 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18668 self.update_edit_prediction_settings(cx);
18669 self.refresh_inline_completion(true, false, window, cx);
18670 self.refresh_inlay_hints(
18671 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18672 self.selections.newest_anchor().head(),
18673 &self.buffer.read(cx).snapshot(cx),
18674 cx,
18675 )),
18676 cx,
18677 );
18678
18679 let old_cursor_shape = self.cursor_shape;
18680
18681 {
18682 let editor_settings = EditorSettings::get_global(cx);
18683 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18684 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18685 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18686 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18687 }
18688
18689 if old_cursor_shape != self.cursor_shape {
18690 cx.emit(EditorEvent::CursorShapeChanged);
18691 }
18692
18693 let project_settings = ProjectSettings::get_global(cx);
18694 self.serialize_dirty_buffers =
18695 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18696
18697 if self.mode.is_full() {
18698 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18699 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18700 if self.show_inline_diagnostics != show_inline_diagnostics {
18701 self.show_inline_diagnostics = show_inline_diagnostics;
18702 self.refresh_inline_diagnostics(false, window, cx);
18703 }
18704
18705 if self.git_blame_inline_enabled != inline_blame_enabled {
18706 self.toggle_git_blame_inline_internal(false, window, cx);
18707 }
18708
18709 let minimap_settings = EditorSettings::get_global(cx).minimap;
18710 if self.minimap_visibility != MinimapVisibility::Disabled {
18711 if self.minimap_visibility.settings_visibility()
18712 != minimap_settings.minimap_enabled()
18713 {
18714 self.set_minimap_visibility(
18715 MinimapVisibility::for_mode(self.mode(), cx),
18716 window,
18717 cx,
18718 );
18719 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18720 minimap_entity.update(cx, |minimap_editor, cx| {
18721 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18722 })
18723 }
18724 }
18725 }
18726
18727 cx.notify();
18728 }
18729
18730 pub fn set_searchable(&mut self, searchable: bool) {
18731 self.searchable = searchable;
18732 }
18733
18734 pub fn searchable(&self) -> bool {
18735 self.searchable
18736 }
18737
18738 fn open_proposed_changes_editor(
18739 &mut self,
18740 _: &OpenProposedChangesEditor,
18741 window: &mut Window,
18742 cx: &mut Context<Self>,
18743 ) {
18744 let Some(workspace) = self.workspace() else {
18745 cx.propagate();
18746 return;
18747 };
18748
18749 let selections = self.selections.all::<usize>(cx);
18750 let multi_buffer = self.buffer.read(cx);
18751 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18752 let mut new_selections_by_buffer = HashMap::default();
18753 for selection in selections {
18754 for (buffer, range, _) in
18755 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18756 {
18757 let mut range = range.to_point(buffer);
18758 range.start.column = 0;
18759 range.end.column = buffer.line_len(range.end.row);
18760 new_selections_by_buffer
18761 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18762 .or_insert(Vec::new())
18763 .push(range)
18764 }
18765 }
18766
18767 let proposed_changes_buffers = new_selections_by_buffer
18768 .into_iter()
18769 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18770 .collect::<Vec<_>>();
18771 let proposed_changes_editor = cx.new(|cx| {
18772 ProposedChangesEditor::new(
18773 "Proposed changes",
18774 proposed_changes_buffers,
18775 self.project.clone(),
18776 window,
18777 cx,
18778 )
18779 });
18780
18781 window.defer(cx, move |window, cx| {
18782 workspace.update(cx, |workspace, cx| {
18783 workspace.active_pane().update(cx, |pane, cx| {
18784 pane.add_item(
18785 Box::new(proposed_changes_editor),
18786 true,
18787 true,
18788 None,
18789 window,
18790 cx,
18791 );
18792 });
18793 });
18794 });
18795 }
18796
18797 pub fn open_excerpts_in_split(
18798 &mut self,
18799 _: &OpenExcerptsSplit,
18800 window: &mut Window,
18801 cx: &mut Context<Self>,
18802 ) {
18803 self.open_excerpts_common(None, true, window, cx)
18804 }
18805
18806 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18807 self.open_excerpts_common(None, false, window, cx)
18808 }
18809
18810 fn open_excerpts_common(
18811 &mut self,
18812 jump_data: Option<JumpData>,
18813 split: bool,
18814 window: &mut Window,
18815 cx: &mut Context<Self>,
18816 ) {
18817 let Some(workspace) = self.workspace() else {
18818 cx.propagate();
18819 return;
18820 };
18821
18822 if self.buffer.read(cx).is_singleton() {
18823 cx.propagate();
18824 return;
18825 }
18826
18827 let mut new_selections_by_buffer = HashMap::default();
18828 match &jump_data {
18829 Some(JumpData::MultiBufferPoint {
18830 excerpt_id,
18831 position,
18832 anchor,
18833 line_offset_from_top,
18834 }) => {
18835 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18836 if let Some(buffer) = multi_buffer_snapshot
18837 .buffer_id_for_excerpt(*excerpt_id)
18838 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18839 {
18840 let buffer_snapshot = buffer.read(cx).snapshot();
18841 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18842 language::ToPoint::to_point(anchor, &buffer_snapshot)
18843 } else {
18844 buffer_snapshot.clip_point(*position, Bias::Left)
18845 };
18846 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18847 new_selections_by_buffer.insert(
18848 buffer,
18849 (
18850 vec![jump_to_offset..jump_to_offset],
18851 Some(*line_offset_from_top),
18852 ),
18853 );
18854 }
18855 }
18856 Some(JumpData::MultiBufferRow {
18857 row,
18858 line_offset_from_top,
18859 }) => {
18860 let point = MultiBufferPoint::new(row.0, 0);
18861 if let Some((buffer, buffer_point, _)) =
18862 self.buffer.read(cx).point_to_buffer_point(point, cx)
18863 {
18864 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18865 new_selections_by_buffer
18866 .entry(buffer)
18867 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18868 .0
18869 .push(buffer_offset..buffer_offset)
18870 }
18871 }
18872 None => {
18873 let selections = self.selections.all::<usize>(cx);
18874 let multi_buffer = self.buffer.read(cx);
18875 for selection in selections {
18876 for (snapshot, range, _, anchor) in multi_buffer
18877 .snapshot(cx)
18878 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18879 {
18880 if let Some(anchor) = anchor {
18881 // selection is in a deleted hunk
18882 let Some(buffer_id) = anchor.buffer_id else {
18883 continue;
18884 };
18885 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18886 continue;
18887 };
18888 let offset = text::ToOffset::to_offset(
18889 &anchor.text_anchor,
18890 &buffer_handle.read(cx).snapshot(),
18891 );
18892 let range = offset..offset;
18893 new_selections_by_buffer
18894 .entry(buffer_handle)
18895 .or_insert((Vec::new(), None))
18896 .0
18897 .push(range)
18898 } else {
18899 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18900 else {
18901 continue;
18902 };
18903 new_selections_by_buffer
18904 .entry(buffer_handle)
18905 .or_insert((Vec::new(), None))
18906 .0
18907 .push(range)
18908 }
18909 }
18910 }
18911 }
18912 }
18913
18914 new_selections_by_buffer
18915 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18916
18917 if new_selections_by_buffer.is_empty() {
18918 return;
18919 }
18920
18921 // We defer the pane interaction because we ourselves are a workspace item
18922 // and activating a new item causes the pane to call a method on us reentrantly,
18923 // which panics if we're on the stack.
18924 window.defer(cx, move |window, cx| {
18925 workspace.update(cx, |workspace, cx| {
18926 let pane = if split {
18927 workspace.adjacent_pane(window, cx)
18928 } else {
18929 workspace.active_pane().clone()
18930 };
18931
18932 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18933 let editor = buffer
18934 .read(cx)
18935 .file()
18936 .is_none()
18937 .then(|| {
18938 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18939 // so `workspace.open_project_item` will never find them, always opening a new editor.
18940 // Instead, we try to activate the existing editor in the pane first.
18941 let (editor, pane_item_index) =
18942 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18943 let editor = item.downcast::<Editor>()?;
18944 let singleton_buffer =
18945 editor.read(cx).buffer().read(cx).as_singleton()?;
18946 if singleton_buffer == buffer {
18947 Some((editor, i))
18948 } else {
18949 None
18950 }
18951 })?;
18952 pane.update(cx, |pane, cx| {
18953 pane.activate_item(pane_item_index, true, true, window, cx)
18954 });
18955 Some(editor)
18956 })
18957 .flatten()
18958 .unwrap_or_else(|| {
18959 workspace.open_project_item::<Self>(
18960 pane.clone(),
18961 buffer,
18962 true,
18963 true,
18964 window,
18965 cx,
18966 )
18967 });
18968
18969 editor.update(cx, |editor, cx| {
18970 let autoscroll = match scroll_offset {
18971 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18972 None => Autoscroll::newest(),
18973 };
18974 let nav_history = editor.nav_history.take();
18975 editor.change_selections(Some(autoscroll), window, cx, |s| {
18976 s.select_ranges(ranges);
18977 });
18978 editor.nav_history = nav_history;
18979 });
18980 }
18981 })
18982 });
18983 }
18984
18985 // For now, don't allow opening excerpts in buffers that aren't backed by
18986 // regular project files.
18987 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18988 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18989 }
18990
18991 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18992 let snapshot = self.buffer.read(cx).read(cx);
18993 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18994 Some(
18995 ranges
18996 .iter()
18997 .map(move |range| {
18998 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18999 })
19000 .collect(),
19001 )
19002 }
19003
19004 fn selection_replacement_ranges(
19005 &self,
19006 range: Range<OffsetUtf16>,
19007 cx: &mut App,
19008 ) -> Vec<Range<OffsetUtf16>> {
19009 let selections = self.selections.all::<OffsetUtf16>(cx);
19010 let newest_selection = selections
19011 .iter()
19012 .max_by_key(|selection| selection.id)
19013 .unwrap();
19014 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19015 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19016 let snapshot = self.buffer.read(cx).read(cx);
19017 selections
19018 .into_iter()
19019 .map(|mut selection| {
19020 selection.start.0 =
19021 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19022 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19023 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19024 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19025 })
19026 .collect()
19027 }
19028
19029 fn report_editor_event(
19030 &self,
19031 event_type: &'static str,
19032 file_extension: Option<String>,
19033 cx: &App,
19034 ) {
19035 if cfg!(any(test, feature = "test-support")) {
19036 return;
19037 }
19038
19039 let Some(project) = &self.project else { return };
19040
19041 // If None, we are in a file without an extension
19042 let file = self
19043 .buffer
19044 .read(cx)
19045 .as_singleton()
19046 .and_then(|b| b.read(cx).file());
19047 let file_extension = file_extension.or(file
19048 .as_ref()
19049 .and_then(|file| Path::new(file.file_name(cx)).extension())
19050 .and_then(|e| e.to_str())
19051 .map(|a| a.to_string()));
19052
19053 let vim_mode = vim_enabled(cx);
19054
19055 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19056 let copilot_enabled = edit_predictions_provider
19057 == language::language_settings::EditPredictionProvider::Copilot;
19058 let copilot_enabled_for_language = self
19059 .buffer
19060 .read(cx)
19061 .language_settings(cx)
19062 .show_edit_predictions;
19063
19064 let project = project.read(cx);
19065 telemetry::event!(
19066 event_type,
19067 file_extension,
19068 vim_mode,
19069 copilot_enabled,
19070 copilot_enabled_for_language,
19071 edit_predictions_provider,
19072 is_via_ssh = project.is_via_ssh(),
19073 );
19074 }
19075
19076 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19077 /// with each line being an array of {text, highlight} objects.
19078 fn copy_highlight_json(
19079 &mut self,
19080 _: &CopyHighlightJson,
19081 window: &mut Window,
19082 cx: &mut Context<Self>,
19083 ) {
19084 #[derive(Serialize)]
19085 struct Chunk<'a> {
19086 text: String,
19087 highlight: Option<&'a str>,
19088 }
19089
19090 let snapshot = self.buffer.read(cx).snapshot(cx);
19091 let range = self
19092 .selected_text_range(false, window, cx)
19093 .and_then(|selection| {
19094 if selection.range.is_empty() {
19095 None
19096 } else {
19097 Some(selection.range)
19098 }
19099 })
19100 .unwrap_or_else(|| 0..snapshot.len());
19101
19102 let chunks = snapshot.chunks(range, true);
19103 let mut lines = Vec::new();
19104 let mut line: VecDeque<Chunk> = VecDeque::new();
19105
19106 let Some(style) = self.style.as_ref() else {
19107 return;
19108 };
19109
19110 for chunk in chunks {
19111 let highlight = chunk
19112 .syntax_highlight_id
19113 .and_then(|id| id.name(&style.syntax));
19114 let mut chunk_lines = chunk.text.split('\n').peekable();
19115 while let Some(text) = chunk_lines.next() {
19116 let mut merged_with_last_token = false;
19117 if let Some(last_token) = line.back_mut() {
19118 if last_token.highlight == highlight {
19119 last_token.text.push_str(text);
19120 merged_with_last_token = true;
19121 }
19122 }
19123
19124 if !merged_with_last_token {
19125 line.push_back(Chunk {
19126 text: text.into(),
19127 highlight,
19128 });
19129 }
19130
19131 if chunk_lines.peek().is_some() {
19132 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19133 line.pop_front();
19134 }
19135 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19136 line.pop_back();
19137 }
19138
19139 lines.push(mem::take(&mut line));
19140 }
19141 }
19142 }
19143
19144 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19145 return;
19146 };
19147 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19148 }
19149
19150 pub fn open_context_menu(
19151 &mut self,
19152 _: &OpenContextMenu,
19153 window: &mut Window,
19154 cx: &mut Context<Self>,
19155 ) {
19156 self.request_autoscroll(Autoscroll::newest(), cx);
19157 let position = self.selections.newest_display(cx).start;
19158 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19159 }
19160
19161 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19162 &self.inlay_hint_cache
19163 }
19164
19165 pub fn replay_insert_event(
19166 &mut self,
19167 text: &str,
19168 relative_utf16_range: Option<Range<isize>>,
19169 window: &mut Window,
19170 cx: &mut Context<Self>,
19171 ) {
19172 if !self.input_enabled {
19173 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19174 return;
19175 }
19176 if let Some(relative_utf16_range) = relative_utf16_range {
19177 let selections = self.selections.all::<OffsetUtf16>(cx);
19178 self.change_selections(None, window, cx, |s| {
19179 let new_ranges = selections.into_iter().map(|range| {
19180 let start = OffsetUtf16(
19181 range
19182 .head()
19183 .0
19184 .saturating_add_signed(relative_utf16_range.start),
19185 );
19186 let end = OffsetUtf16(
19187 range
19188 .head()
19189 .0
19190 .saturating_add_signed(relative_utf16_range.end),
19191 );
19192 start..end
19193 });
19194 s.select_ranges(new_ranges);
19195 });
19196 }
19197
19198 self.handle_input(text, window, cx);
19199 }
19200
19201 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19202 let Some(provider) = self.semantics_provider.as_ref() else {
19203 return false;
19204 };
19205
19206 let mut supports = false;
19207 self.buffer().update(cx, |this, cx| {
19208 this.for_each_buffer(|buffer| {
19209 supports |= provider.supports_inlay_hints(buffer, cx);
19210 });
19211 });
19212
19213 supports
19214 }
19215
19216 pub fn is_focused(&self, window: &Window) -> bool {
19217 self.focus_handle.is_focused(window)
19218 }
19219
19220 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19221 cx.emit(EditorEvent::Focused);
19222
19223 if let Some(descendant) = self
19224 .last_focused_descendant
19225 .take()
19226 .and_then(|descendant| descendant.upgrade())
19227 {
19228 window.focus(&descendant);
19229 } else {
19230 if let Some(blame) = self.blame.as_ref() {
19231 blame.update(cx, GitBlame::focus)
19232 }
19233
19234 self.blink_manager.update(cx, BlinkManager::enable);
19235 self.show_cursor_names(window, cx);
19236 self.buffer.update(cx, |buffer, cx| {
19237 buffer.finalize_last_transaction(cx);
19238 if self.leader_id.is_none() {
19239 buffer.set_active_selections(
19240 &self.selections.disjoint_anchors(),
19241 self.selections.line_mode,
19242 self.cursor_shape,
19243 cx,
19244 );
19245 }
19246 });
19247 }
19248 }
19249
19250 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19251 cx.emit(EditorEvent::FocusedIn)
19252 }
19253
19254 fn handle_focus_out(
19255 &mut self,
19256 event: FocusOutEvent,
19257 _window: &mut Window,
19258 cx: &mut Context<Self>,
19259 ) {
19260 if event.blurred != self.focus_handle {
19261 self.last_focused_descendant = Some(event.blurred);
19262 }
19263 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19264 }
19265
19266 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19267 self.blink_manager.update(cx, BlinkManager::disable);
19268 self.buffer
19269 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19270
19271 if let Some(blame) = self.blame.as_ref() {
19272 blame.update(cx, GitBlame::blur)
19273 }
19274 if !self.hover_state.focused(window, cx) {
19275 hide_hover(self, cx);
19276 }
19277 if !self
19278 .context_menu
19279 .borrow()
19280 .as_ref()
19281 .is_some_and(|context_menu| context_menu.focused(window, cx))
19282 {
19283 self.hide_context_menu(window, cx);
19284 }
19285 self.discard_inline_completion(false, cx);
19286 cx.emit(EditorEvent::Blurred);
19287 cx.notify();
19288 }
19289
19290 pub fn register_action<A: Action>(
19291 &mut self,
19292 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19293 ) -> Subscription {
19294 let id = self.next_editor_action_id.post_inc();
19295 let listener = Arc::new(listener);
19296 self.editor_actions.borrow_mut().insert(
19297 id,
19298 Box::new(move |window, _| {
19299 let listener = listener.clone();
19300 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19301 let action = action.downcast_ref().unwrap();
19302 if phase == DispatchPhase::Bubble {
19303 listener(action, window, cx)
19304 }
19305 })
19306 }),
19307 );
19308
19309 let editor_actions = self.editor_actions.clone();
19310 Subscription::new(move || {
19311 editor_actions.borrow_mut().remove(&id);
19312 })
19313 }
19314
19315 pub fn file_header_size(&self) -> u32 {
19316 FILE_HEADER_HEIGHT
19317 }
19318
19319 pub fn restore(
19320 &mut self,
19321 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19322 window: &mut Window,
19323 cx: &mut Context<Self>,
19324 ) {
19325 let workspace = self.workspace();
19326 let project = self.project.as_ref();
19327 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19328 let mut tasks = Vec::new();
19329 for (buffer_id, changes) in revert_changes {
19330 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19331 buffer.update(cx, |buffer, cx| {
19332 buffer.edit(
19333 changes
19334 .into_iter()
19335 .map(|(range, text)| (range, text.to_string())),
19336 None,
19337 cx,
19338 );
19339 });
19340
19341 if let Some(project) =
19342 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19343 {
19344 project.update(cx, |project, cx| {
19345 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19346 })
19347 }
19348 }
19349 }
19350 tasks
19351 });
19352 cx.spawn_in(window, async move |_, cx| {
19353 for (buffer, task) in save_tasks {
19354 let result = task.await;
19355 if result.is_err() {
19356 let Some(path) = buffer
19357 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19358 .ok()
19359 else {
19360 continue;
19361 };
19362 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19363 let Some(task) = cx
19364 .update_window_entity(&workspace, |workspace, window, cx| {
19365 workspace
19366 .open_path_preview(path, None, false, false, false, window, cx)
19367 })
19368 .ok()
19369 else {
19370 continue;
19371 };
19372 task.await.log_err();
19373 }
19374 }
19375 }
19376 })
19377 .detach();
19378 self.change_selections(None, window, cx, |selections| selections.refresh());
19379 }
19380
19381 pub fn to_pixel_point(
19382 &self,
19383 source: multi_buffer::Anchor,
19384 editor_snapshot: &EditorSnapshot,
19385 window: &mut Window,
19386 ) -> Option<gpui::Point<Pixels>> {
19387 let source_point = source.to_display_point(editor_snapshot);
19388 self.display_to_pixel_point(source_point, editor_snapshot, window)
19389 }
19390
19391 pub fn display_to_pixel_point(
19392 &self,
19393 source: DisplayPoint,
19394 editor_snapshot: &EditorSnapshot,
19395 window: &mut Window,
19396 ) -> Option<gpui::Point<Pixels>> {
19397 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19398 let text_layout_details = self.text_layout_details(window);
19399 let scroll_top = text_layout_details
19400 .scroll_anchor
19401 .scroll_position(editor_snapshot)
19402 .y;
19403
19404 if source.row().as_f32() < scroll_top.floor() {
19405 return None;
19406 }
19407 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19408 let source_y = line_height * (source.row().as_f32() - scroll_top);
19409 Some(gpui::Point::new(source_x, source_y))
19410 }
19411
19412 pub fn has_visible_completions_menu(&self) -> bool {
19413 !self.edit_prediction_preview_is_active()
19414 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19415 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19416 })
19417 }
19418
19419 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19420 if self.mode.is_minimap() {
19421 return;
19422 }
19423 self.addons
19424 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19425 }
19426
19427 pub fn unregister_addon<T: Addon>(&mut self) {
19428 self.addons.remove(&std::any::TypeId::of::<T>());
19429 }
19430
19431 pub fn addon<T: Addon>(&self) -> Option<&T> {
19432 let type_id = std::any::TypeId::of::<T>();
19433 self.addons
19434 .get(&type_id)
19435 .and_then(|item| item.to_any().downcast_ref::<T>())
19436 }
19437
19438 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19439 let type_id = std::any::TypeId::of::<T>();
19440 self.addons
19441 .get_mut(&type_id)
19442 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19443 }
19444
19445 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19446 let text_layout_details = self.text_layout_details(window);
19447 let style = &text_layout_details.editor_style;
19448 let font_id = window.text_system().resolve_font(&style.text.font());
19449 let font_size = style.text.font_size.to_pixels(window.rem_size());
19450 let line_height = style.text.line_height_in_pixels(window.rem_size());
19451 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19452
19453 gpui::Size::new(em_width, line_height)
19454 }
19455
19456 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
19457 self.load_diff_task.clone()
19458 }
19459
19460 fn read_metadata_from_db(
19461 &mut self,
19462 item_id: u64,
19463 workspace_id: WorkspaceId,
19464 window: &mut Window,
19465 cx: &mut Context<Editor>,
19466 ) {
19467 if self.is_singleton(cx)
19468 && !self.mode.is_minimap()
19469 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
19470 {
19471 let buffer_snapshot = OnceCell::new();
19472
19473 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
19474 if !folds.is_empty() {
19475 let snapshot =
19476 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19477 self.fold_ranges(
19478 folds
19479 .into_iter()
19480 .map(|(start, end)| {
19481 snapshot.clip_offset(start, Bias::Left)
19482 ..snapshot.clip_offset(end, Bias::Right)
19483 })
19484 .collect(),
19485 false,
19486 window,
19487 cx,
19488 );
19489 }
19490 }
19491
19492 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
19493 if !selections.is_empty() {
19494 let snapshot =
19495 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19496 self.change_selections(None, window, cx, |s| {
19497 s.select_ranges(selections.into_iter().map(|(start, end)| {
19498 snapshot.clip_offset(start, Bias::Left)
19499 ..snapshot.clip_offset(end, Bias::Right)
19500 }));
19501 });
19502 }
19503 };
19504 }
19505
19506 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
19507 }
19508}
19509
19510fn vim_enabled(cx: &App) -> bool {
19511 cx.global::<SettingsStore>()
19512 .raw_user_settings()
19513 .get("vim_mode")
19514 == Some(&serde_json::Value::Bool(true))
19515}
19516
19517// Consider user intent and default settings
19518fn choose_completion_range(
19519 completion: &Completion,
19520 intent: CompletionIntent,
19521 buffer: &Entity<Buffer>,
19522 cx: &mut Context<Editor>,
19523) -> Range<usize> {
19524 fn should_replace(
19525 completion: &Completion,
19526 insert_range: &Range<text::Anchor>,
19527 intent: CompletionIntent,
19528 completion_mode_setting: LspInsertMode,
19529 buffer: &Buffer,
19530 ) -> bool {
19531 // specific actions take precedence over settings
19532 match intent {
19533 CompletionIntent::CompleteWithInsert => return false,
19534 CompletionIntent::CompleteWithReplace => return true,
19535 CompletionIntent::Complete | CompletionIntent::Compose => {}
19536 }
19537
19538 match completion_mode_setting {
19539 LspInsertMode::Insert => false,
19540 LspInsertMode::Replace => true,
19541 LspInsertMode::ReplaceSubsequence => {
19542 let mut text_to_replace = buffer.chars_for_range(
19543 buffer.anchor_before(completion.replace_range.start)
19544 ..buffer.anchor_after(completion.replace_range.end),
19545 );
19546 let mut completion_text = completion.new_text.chars();
19547
19548 // is `text_to_replace` a subsequence of `completion_text`
19549 text_to_replace
19550 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
19551 }
19552 LspInsertMode::ReplaceSuffix => {
19553 let range_after_cursor = insert_range.end..completion.replace_range.end;
19554
19555 let text_after_cursor = buffer
19556 .text_for_range(
19557 buffer.anchor_before(range_after_cursor.start)
19558 ..buffer.anchor_after(range_after_cursor.end),
19559 )
19560 .collect::<String>();
19561 completion.new_text.ends_with(&text_after_cursor)
19562 }
19563 }
19564 }
19565
19566 let buffer = buffer.read(cx);
19567
19568 if let CompletionSource::Lsp {
19569 insert_range: Some(insert_range),
19570 ..
19571 } = &completion.source
19572 {
19573 let completion_mode_setting =
19574 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
19575 .completions
19576 .lsp_insert_mode;
19577
19578 if !should_replace(
19579 completion,
19580 &insert_range,
19581 intent,
19582 completion_mode_setting,
19583 buffer,
19584 ) {
19585 return insert_range.to_offset(buffer);
19586 }
19587 }
19588
19589 completion.replace_range.to_offset(buffer)
19590}
19591
19592fn insert_extra_newline_brackets(
19593 buffer: &MultiBufferSnapshot,
19594 range: Range<usize>,
19595 language: &language::LanguageScope,
19596) -> bool {
19597 let leading_whitespace_len = buffer
19598 .reversed_chars_at(range.start)
19599 .take_while(|c| c.is_whitespace() && *c != '\n')
19600 .map(|c| c.len_utf8())
19601 .sum::<usize>();
19602 let trailing_whitespace_len = buffer
19603 .chars_at(range.end)
19604 .take_while(|c| c.is_whitespace() && *c != '\n')
19605 .map(|c| c.len_utf8())
19606 .sum::<usize>();
19607 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
19608
19609 language.brackets().any(|(pair, enabled)| {
19610 let pair_start = pair.start.trim_end();
19611 let pair_end = pair.end.trim_start();
19612
19613 enabled
19614 && pair.newline
19615 && buffer.contains_str_at(range.end, pair_end)
19616 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
19617 })
19618}
19619
19620fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
19621 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
19622 [(buffer, range, _)] => (*buffer, range.clone()),
19623 _ => return false,
19624 };
19625 let pair = {
19626 let mut result: Option<BracketMatch> = None;
19627
19628 for pair in buffer
19629 .all_bracket_ranges(range.clone())
19630 .filter(move |pair| {
19631 pair.open_range.start <= range.start && pair.close_range.end >= range.end
19632 })
19633 {
19634 let len = pair.close_range.end - pair.open_range.start;
19635
19636 if let Some(existing) = &result {
19637 let existing_len = existing.close_range.end - existing.open_range.start;
19638 if len > existing_len {
19639 continue;
19640 }
19641 }
19642
19643 result = Some(pair);
19644 }
19645
19646 result
19647 };
19648 let Some(pair) = pair else {
19649 return false;
19650 };
19651 pair.newline_only
19652 && buffer
19653 .chars_for_range(pair.open_range.end..range.start)
19654 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
19655 .all(|c| c.is_whitespace() && c != '\n')
19656}
19657
19658fn update_uncommitted_diff_for_buffer(
19659 editor: Entity<Editor>,
19660 project: &Entity<Project>,
19661 buffers: impl IntoIterator<Item = Entity<Buffer>>,
19662 buffer: Entity<MultiBuffer>,
19663 cx: &mut App,
19664) -> Task<()> {
19665 let mut tasks = Vec::new();
19666 project.update(cx, |project, cx| {
19667 for buffer in buffers {
19668 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
19669 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
19670 }
19671 }
19672 });
19673 cx.spawn(async move |cx| {
19674 let diffs = future::join_all(tasks).await;
19675 if editor
19676 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19677 .unwrap_or(false)
19678 {
19679 return;
19680 }
19681
19682 buffer
19683 .update(cx, |buffer, cx| {
19684 for diff in diffs.into_iter().flatten() {
19685 buffer.add_diff(diff, cx);
19686 }
19687 })
19688 .ok();
19689 })
19690}
19691
19692fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
19693 let tab_size = tab_size.get() as usize;
19694 let mut width = offset;
19695
19696 for ch in text.chars() {
19697 width += if ch == '\t' {
19698 tab_size - (width % tab_size)
19699 } else {
19700 1
19701 };
19702 }
19703
19704 width - offset
19705}
19706
19707#[cfg(test)]
19708mod tests {
19709 use super::*;
19710
19711 #[test]
19712 fn test_string_size_with_expanded_tabs() {
19713 let nz = |val| NonZeroU32::new(val).unwrap();
19714 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
19715 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
19716 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
19717 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
19718 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
19719 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
19720 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
19721 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
19722 }
19723}
19724
19725/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
19726struct WordBreakingTokenizer<'a> {
19727 input: &'a str,
19728}
19729
19730impl<'a> WordBreakingTokenizer<'a> {
19731 fn new(input: &'a str) -> Self {
19732 Self { input }
19733 }
19734}
19735
19736fn is_char_ideographic(ch: char) -> bool {
19737 use unicode_script::Script::*;
19738 use unicode_script::UnicodeScript;
19739 matches!(ch.script(), Han | Tangut | Yi)
19740}
19741
19742fn is_grapheme_ideographic(text: &str) -> bool {
19743 text.chars().any(is_char_ideographic)
19744}
19745
19746fn is_grapheme_whitespace(text: &str) -> bool {
19747 text.chars().any(|x| x.is_whitespace())
19748}
19749
19750fn should_stay_with_preceding_ideograph(text: &str) -> bool {
19751 text.chars().next().map_or(false, |ch| {
19752 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
19753 })
19754}
19755
19756#[derive(PartialEq, Eq, Debug, Clone, Copy)]
19757enum WordBreakToken<'a> {
19758 Word { token: &'a str, grapheme_len: usize },
19759 InlineWhitespace { token: &'a str, grapheme_len: usize },
19760 Newline,
19761}
19762
19763impl<'a> Iterator for WordBreakingTokenizer<'a> {
19764 /// Yields a span, the count of graphemes in the token, and whether it was
19765 /// whitespace. Note that it also breaks at word boundaries.
19766 type Item = WordBreakToken<'a>;
19767
19768 fn next(&mut self) -> Option<Self::Item> {
19769 use unicode_segmentation::UnicodeSegmentation;
19770 if self.input.is_empty() {
19771 return None;
19772 }
19773
19774 let mut iter = self.input.graphemes(true).peekable();
19775 let mut offset = 0;
19776 let mut grapheme_len = 0;
19777 if let Some(first_grapheme) = iter.next() {
19778 let is_newline = first_grapheme == "\n";
19779 let is_whitespace = is_grapheme_whitespace(first_grapheme);
19780 offset += first_grapheme.len();
19781 grapheme_len += 1;
19782 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
19783 if let Some(grapheme) = iter.peek().copied() {
19784 if should_stay_with_preceding_ideograph(grapheme) {
19785 offset += grapheme.len();
19786 grapheme_len += 1;
19787 }
19788 }
19789 } else {
19790 let mut words = self.input[offset..].split_word_bound_indices().peekable();
19791 let mut next_word_bound = words.peek().copied();
19792 if next_word_bound.map_or(false, |(i, _)| i == 0) {
19793 next_word_bound = words.next();
19794 }
19795 while let Some(grapheme) = iter.peek().copied() {
19796 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19797 break;
19798 };
19799 if is_grapheme_whitespace(grapheme) != is_whitespace
19800 || (grapheme == "\n") != is_newline
19801 {
19802 break;
19803 };
19804 offset += grapheme.len();
19805 grapheme_len += 1;
19806 iter.next();
19807 }
19808 }
19809 let token = &self.input[..offset];
19810 self.input = &self.input[offset..];
19811 if token == "\n" {
19812 Some(WordBreakToken::Newline)
19813 } else if is_whitespace {
19814 Some(WordBreakToken::InlineWhitespace {
19815 token,
19816 grapheme_len,
19817 })
19818 } else {
19819 Some(WordBreakToken::Word {
19820 token,
19821 grapheme_len,
19822 })
19823 }
19824 } else {
19825 None
19826 }
19827 }
19828}
19829
19830#[test]
19831fn test_word_breaking_tokenizer() {
19832 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
19833 ("", &[]),
19834 (" ", &[whitespace(" ", 2)]),
19835 ("Ʒ", &[word("Ʒ", 1)]),
19836 ("Ǽ", &[word("Ǽ", 1)]),
19837 ("⋑", &[word("⋑", 1)]),
19838 ("⋑⋑", &[word("⋑⋑", 2)]),
19839 (
19840 "原理,进而",
19841 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
19842 ),
19843 (
19844 "hello world",
19845 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
19846 ),
19847 (
19848 "hello, world",
19849 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
19850 ),
19851 (
19852 " hello world",
19853 &[
19854 whitespace(" ", 2),
19855 word("hello", 5),
19856 whitespace(" ", 1),
19857 word("world", 5),
19858 ],
19859 ),
19860 (
19861 "这是什么 \n 钢笔",
19862 &[
19863 word("这", 1),
19864 word("是", 1),
19865 word("什", 1),
19866 word("么", 1),
19867 whitespace(" ", 1),
19868 newline(),
19869 whitespace(" ", 1),
19870 word("钢", 1),
19871 word("笔", 1),
19872 ],
19873 ),
19874 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
19875 ];
19876
19877 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19878 WordBreakToken::Word {
19879 token,
19880 grapheme_len,
19881 }
19882 }
19883
19884 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19885 WordBreakToken::InlineWhitespace {
19886 token,
19887 grapheme_len,
19888 }
19889 }
19890
19891 fn newline() -> WordBreakToken<'static> {
19892 WordBreakToken::Newline
19893 }
19894
19895 for (input, result) in tests {
19896 assert_eq!(
19897 WordBreakingTokenizer::new(input)
19898 .collect::<Vec<_>>()
19899 .as_slice(),
19900 *result,
19901 );
19902 }
19903}
19904
19905fn wrap_with_prefix(
19906 line_prefix: String,
19907 unwrapped_text: String,
19908 wrap_column: usize,
19909 tab_size: NonZeroU32,
19910 preserve_existing_whitespace: bool,
19911) -> String {
19912 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
19913 let mut wrapped_text = String::new();
19914 let mut current_line = line_prefix.clone();
19915
19916 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
19917 let mut current_line_len = line_prefix_len;
19918 let mut in_whitespace = false;
19919 for token in tokenizer {
19920 let have_preceding_whitespace = in_whitespace;
19921 match token {
19922 WordBreakToken::Word {
19923 token,
19924 grapheme_len,
19925 } => {
19926 in_whitespace = false;
19927 if current_line_len + grapheme_len > wrap_column
19928 && current_line_len != line_prefix_len
19929 {
19930 wrapped_text.push_str(current_line.trim_end());
19931 wrapped_text.push('\n');
19932 current_line.truncate(line_prefix.len());
19933 current_line_len = line_prefix_len;
19934 }
19935 current_line.push_str(token);
19936 current_line_len += grapheme_len;
19937 }
19938 WordBreakToken::InlineWhitespace {
19939 mut token,
19940 mut grapheme_len,
19941 } => {
19942 in_whitespace = true;
19943 if have_preceding_whitespace && !preserve_existing_whitespace {
19944 continue;
19945 }
19946 if !preserve_existing_whitespace {
19947 token = " ";
19948 grapheme_len = 1;
19949 }
19950 if current_line_len + grapheme_len > wrap_column {
19951 wrapped_text.push_str(current_line.trim_end());
19952 wrapped_text.push('\n');
19953 current_line.truncate(line_prefix.len());
19954 current_line_len = line_prefix_len;
19955 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
19956 current_line.push_str(token);
19957 current_line_len += grapheme_len;
19958 }
19959 }
19960 WordBreakToken::Newline => {
19961 in_whitespace = true;
19962 if preserve_existing_whitespace {
19963 wrapped_text.push_str(current_line.trim_end());
19964 wrapped_text.push('\n');
19965 current_line.truncate(line_prefix.len());
19966 current_line_len = line_prefix_len;
19967 } else if have_preceding_whitespace {
19968 continue;
19969 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
19970 {
19971 wrapped_text.push_str(current_line.trim_end());
19972 wrapped_text.push('\n');
19973 current_line.truncate(line_prefix.len());
19974 current_line_len = line_prefix_len;
19975 } else if current_line_len != line_prefix_len {
19976 current_line.push(' ');
19977 current_line_len += 1;
19978 }
19979 }
19980 }
19981 }
19982
19983 if !current_line.is_empty() {
19984 wrapped_text.push_str(¤t_line);
19985 }
19986 wrapped_text
19987}
19988
19989#[test]
19990fn test_wrap_with_prefix() {
19991 assert_eq!(
19992 wrap_with_prefix(
19993 "# ".to_string(),
19994 "abcdefg".to_string(),
19995 4,
19996 NonZeroU32::new(4).unwrap(),
19997 false,
19998 ),
19999 "# abcdefg"
20000 );
20001 assert_eq!(
20002 wrap_with_prefix(
20003 "".to_string(),
20004 "\thello world".to_string(),
20005 8,
20006 NonZeroU32::new(4).unwrap(),
20007 false,
20008 ),
20009 "hello\nworld"
20010 );
20011 assert_eq!(
20012 wrap_with_prefix(
20013 "// ".to_string(),
20014 "xx \nyy zz aa bb cc".to_string(),
20015 12,
20016 NonZeroU32::new(4).unwrap(),
20017 false,
20018 ),
20019 "// xx yy zz\n// aa bb cc"
20020 );
20021 assert_eq!(
20022 wrap_with_prefix(
20023 String::new(),
20024 "这是什么 \n 钢笔".to_string(),
20025 3,
20026 NonZeroU32::new(4).unwrap(),
20027 false,
20028 ),
20029 "这是什\n么 钢\n笔"
20030 );
20031}
20032
20033pub trait CollaborationHub {
20034 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20035 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20036 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20037}
20038
20039impl CollaborationHub for Entity<Project> {
20040 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20041 self.read(cx).collaborators()
20042 }
20043
20044 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
20045 self.read(cx).user_store().read(cx).participant_indices()
20046 }
20047
20048 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
20049 let this = self.read(cx);
20050 let user_ids = this.collaborators().values().map(|c| c.user_id);
20051 this.user_store().read(cx).participant_names(user_ids, cx)
20052 }
20053}
20054
20055pub trait SemanticsProvider {
20056 fn hover(
20057 &self,
20058 buffer: &Entity<Buffer>,
20059 position: text::Anchor,
20060 cx: &mut App,
20061 ) -> Option<Task<Vec<project::Hover>>>;
20062
20063 fn inline_values(
20064 &self,
20065 buffer_handle: Entity<Buffer>,
20066 range: Range<text::Anchor>,
20067 cx: &mut App,
20068 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20069
20070 fn inlay_hints(
20071 &self,
20072 buffer_handle: Entity<Buffer>,
20073 range: Range<text::Anchor>,
20074 cx: &mut App,
20075 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20076
20077 fn resolve_inlay_hint(
20078 &self,
20079 hint: InlayHint,
20080 buffer_handle: Entity<Buffer>,
20081 server_id: LanguageServerId,
20082 cx: &mut App,
20083 ) -> Option<Task<anyhow::Result<InlayHint>>>;
20084
20085 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
20086
20087 fn document_highlights(
20088 &self,
20089 buffer: &Entity<Buffer>,
20090 position: text::Anchor,
20091 cx: &mut App,
20092 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
20093
20094 fn definitions(
20095 &self,
20096 buffer: &Entity<Buffer>,
20097 position: text::Anchor,
20098 kind: GotoDefinitionKind,
20099 cx: &mut App,
20100 ) -> Option<Task<Result<Vec<LocationLink>>>>;
20101
20102 fn range_for_rename(
20103 &self,
20104 buffer: &Entity<Buffer>,
20105 position: text::Anchor,
20106 cx: &mut App,
20107 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
20108
20109 fn perform_rename(
20110 &self,
20111 buffer: &Entity<Buffer>,
20112 position: text::Anchor,
20113 new_name: String,
20114 cx: &mut App,
20115 ) -> Option<Task<Result<ProjectTransaction>>>;
20116}
20117
20118pub trait CompletionProvider {
20119 fn completions(
20120 &self,
20121 excerpt_id: ExcerptId,
20122 buffer: &Entity<Buffer>,
20123 buffer_position: text::Anchor,
20124 trigger: CompletionContext,
20125 window: &mut Window,
20126 cx: &mut Context<Editor>,
20127 ) -> Task<Result<Option<Vec<Completion>>>>;
20128
20129 fn resolve_completions(
20130 &self,
20131 buffer: Entity<Buffer>,
20132 completion_indices: Vec<usize>,
20133 completions: Rc<RefCell<Box<[Completion]>>>,
20134 cx: &mut Context<Editor>,
20135 ) -> Task<Result<bool>>;
20136
20137 fn apply_additional_edits_for_completion(
20138 &self,
20139 _buffer: Entity<Buffer>,
20140 _completions: Rc<RefCell<Box<[Completion]>>>,
20141 _completion_index: usize,
20142 _push_to_history: bool,
20143 _cx: &mut Context<Editor>,
20144 ) -> Task<Result<Option<language::Transaction>>> {
20145 Task::ready(Ok(None))
20146 }
20147
20148 fn is_completion_trigger(
20149 &self,
20150 buffer: &Entity<Buffer>,
20151 position: language::Anchor,
20152 text: &str,
20153 trigger_in_words: bool,
20154 cx: &mut Context<Editor>,
20155 ) -> bool;
20156
20157 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
20158
20159 fn sort_completions(&self) -> bool {
20160 true
20161 }
20162
20163 fn filter_completions(&self) -> bool {
20164 true
20165 }
20166}
20167
20168pub trait CodeActionProvider {
20169 fn id(&self) -> Arc<str>;
20170
20171 fn code_actions(
20172 &self,
20173 buffer: &Entity<Buffer>,
20174 range: Range<text::Anchor>,
20175 window: &mut Window,
20176 cx: &mut App,
20177 ) -> Task<Result<Vec<CodeAction>>>;
20178
20179 fn apply_code_action(
20180 &self,
20181 buffer_handle: Entity<Buffer>,
20182 action: CodeAction,
20183 excerpt_id: ExcerptId,
20184 push_to_history: bool,
20185 window: &mut Window,
20186 cx: &mut App,
20187 ) -> Task<Result<ProjectTransaction>>;
20188}
20189
20190impl CodeActionProvider for Entity<Project> {
20191 fn id(&self) -> Arc<str> {
20192 "project".into()
20193 }
20194
20195 fn code_actions(
20196 &self,
20197 buffer: &Entity<Buffer>,
20198 range: Range<text::Anchor>,
20199 _window: &mut Window,
20200 cx: &mut App,
20201 ) -> Task<Result<Vec<CodeAction>>> {
20202 self.update(cx, |project, cx| {
20203 let code_lens = project.code_lens(buffer, range.clone(), cx);
20204 let code_actions = project.code_actions(buffer, range, None, cx);
20205 cx.background_spawn(async move {
20206 let (code_lens, code_actions) = join(code_lens, code_actions).await;
20207 Ok(code_lens
20208 .context("code lens fetch")?
20209 .into_iter()
20210 .chain(code_actions.context("code action fetch")?)
20211 .collect())
20212 })
20213 })
20214 }
20215
20216 fn apply_code_action(
20217 &self,
20218 buffer_handle: Entity<Buffer>,
20219 action: CodeAction,
20220 _excerpt_id: ExcerptId,
20221 push_to_history: bool,
20222 _window: &mut Window,
20223 cx: &mut App,
20224 ) -> Task<Result<ProjectTransaction>> {
20225 self.update(cx, |project, cx| {
20226 project.apply_code_action(buffer_handle, action, push_to_history, cx)
20227 })
20228 }
20229}
20230
20231fn snippet_completions(
20232 project: &Project,
20233 buffer: &Entity<Buffer>,
20234 buffer_position: text::Anchor,
20235 cx: &mut App,
20236) -> Task<Result<Vec<Completion>>> {
20237 let languages = buffer.read(cx).languages_at(buffer_position);
20238 let snippet_store = project.snippets().read(cx);
20239
20240 let scopes: Vec<_> = languages
20241 .iter()
20242 .filter_map(|language| {
20243 let language_name = language.lsp_id();
20244 let snippets = snippet_store.snippets_for(Some(language_name), cx);
20245
20246 if snippets.is_empty() {
20247 None
20248 } else {
20249 Some((language.default_scope(), snippets))
20250 }
20251 })
20252 .collect();
20253
20254 if scopes.is_empty() {
20255 return Task::ready(Ok(vec![]));
20256 }
20257
20258 let snapshot = buffer.read(cx).text_snapshot();
20259 let chars: String = snapshot
20260 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
20261 .collect();
20262 let executor = cx.background_executor().clone();
20263
20264 cx.background_spawn(async move {
20265 let mut all_results: Vec<Completion> = Vec::new();
20266 for (scope, snippets) in scopes.into_iter() {
20267 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
20268 let mut last_word = chars
20269 .chars()
20270 .take_while(|c| classifier.is_word(*c))
20271 .collect::<String>();
20272 last_word = last_word.chars().rev().collect();
20273
20274 if last_word.is_empty() {
20275 return Ok(vec![]);
20276 }
20277
20278 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
20279 let to_lsp = |point: &text::Anchor| {
20280 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
20281 point_to_lsp(end)
20282 };
20283 let lsp_end = to_lsp(&buffer_position);
20284
20285 let candidates = snippets
20286 .iter()
20287 .enumerate()
20288 .flat_map(|(ix, snippet)| {
20289 snippet
20290 .prefix
20291 .iter()
20292 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
20293 })
20294 .collect::<Vec<StringMatchCandidate>>();
20295
20296 let mut matches = fuzzy::match_strings(
20297 &candidates,
20298 &last_word,
20299 last_word.chars().any(|c| c.is_uppercase()),
20300 100,
20301 &Default::default(),
20302 executor.clone(),
20303 )
20304 .await;
20305
20306 // Remove all candidates where the query's start does not match the start of any word in the candidate
20307 if let Some(query_start) = last_word.chars().next() {
20308 matches.retain(|string_match| {
20309 split_words(&string_match.string).any(|word| {
20310 // Check that the first codepoint of the word as lowercase matches the first
20311 // codepoint of the query as lowercase
20312 word.chars()
20313 .flat_map(|codepoint| codepoint.to_lowercase())
20314 .zip(query_start.to_lowercase())
20315 .all(|(word_cp, query_cp)| word_cp == query_cp)
20316 })
20317 });
20318 }
20319
20320 let matched_strings = matches
20321 .into_iter()
20322 .map(|m| m.string)
20323 .collect::<HashSet<_>>();
20324
20325 let mut result: Vec<Completion> = snippets
20326 .iter()
20327 .filter_map(|snippet| {
20328 let matching_prefix = snippet
20329 .prefix
20330 .iter()
20331 .find(|prefix| matched_strings.contains(*prefix))?;
20332 let start = as_offset - last_word.len();
20333 let start = snapshot.anchor_before(start);
20334 let range = start..buffer_position;
20335 let lsp_start = to_lsp(&start);
20336 let lsp_range = lsp::Range {
20337 start: lsp_start,
20338 end: lsp_end,
20339 };
20340 Some(Completion {
20341 replace_range: range,
20342 new_text: snippet.body.clone(),
20343 source: CompletionSource::Lsp {
20344 insert_range: None,
20345 server_id: LanguageServerId(usize::MAX),
20346 resolved: true,
20347 lsp_completion: Box::new(lsp::CompletionItem {
20348 label: snippet.prefix.first().unwrap().clone(),
20349 kind: Some(CompletionItemKind::SNIPPET),
20350 label_details: snippet.description.as_ref().map(|description| {
20351 lsp::CompletionItemLabelDetails {
20352 detail: Some(description.clone()),
20353 description: None,
20354 }
20355 }),
20356 insert_text_format: Some(InsertTextFormat::SNIPPET),
20357 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20358 lsp::InsertReplaceEdit {
20359 new_text: snippet.body.clone(),
20360 insert: lsp_range,
20361 replace: lsp_range,
20362 },
20363 )),
20364 filter_text: Some(snippet.body.clone()),
20365 sort_text: Some(char::MAX.to_string()),
20366 ..lsp::CompletionItem::default()
20367 }),
20368 lsp_defaults: None,
20369 },
20370 label: CodeLabel {
20371 text: matching_prefix.clone(),
20372 runs: Vec::new(),
20373 filter_range: 0..matching_prefix.len(),
20374 },
20375 icon_path: None,
20376 documentation: Some(
20377 CompletionDocumentation::SingleLineAndMultiLinePlainText {
20378 single_line: snippet.name.clone().into(),
20379 plain_text: snippet
20380 .description
20381 .clone()
20382 .map(|description| description.into()),
20383 },
20384 ),
20385 insert_text_mode: None,
20386 confirm: None,
20387 })
20388 })
20389 .collect();
20390
20391 all_results.append(&mut result);
20392 }
20393
20394 Ok(all_results)
20395 })
20396}
20397
20398impl CompletionProvider for Entity<Project> {
20399 fn completions(
20400 &self,
20401 _excerpt_id: ExcerptId,
20402 buffer: &Entity<Buffer>,
20403 buffer_position: text::Anchor,
20404 options: CompletionContext,
20405 _window: &mut Window,
20406 cx: &mut Context<Editor>,
20407 ) -> Task<Result<Option<Vec<Completion>>>> {
20408 self.update(cx, |project, cx| {
20409 let snippets = snippet_completions(project, buffer, buffer_position, cx);
20410 let project_completions = project.completions(buffer, buffer_position, options, cx);
20411 cx.background_spawn(async move {
20412 let snippets_completions = snippets.await?;
20413 match project_completions.await? {
20414 Some(mut completions) => {
20415 completions.extend(snippets_completions);
20416 Ok(Some(completions))
20417 }
20418 None => {
20419 if snippets_completions.is_empty() {
20420 Ok(None)
20421 } else {
20422 Ok(Some(snippets_completions))
20423 }
20424 }
20425 }
20426 })
20427 })
20428 }
20429
20430 fn resolve_completions(
20431 &self,
20432 buffer: Entity<Buffer>,
20433 completion_indices: Vec<usize>,
20434 completions: Rc<RefCell<Box<[Completion]>>>,
20435 cx: &mut Context<Editor>,
20436 ) -> Task<Result<bool>> {
20437 self.update(cx, |project, cx| {
20438 project.lsp_store().update(cx, |lsp_store, cx| {
20439 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
20440 })
20441 })
20442 }
20443
20444 fn apply_additional_edits_for_completion(
20445 &self,
20446 buffer: Entity<Buffer>,
20447 completions: Rc<RefCell<Box<[Completion]>>>,
20448 completion_index: usize,
20449 push_to_history: bool,
20450 cx: &mut Context<Editor>,
20451 ) -> Task<Result<Option<language::Transaction>>> {
20452 self.update(cx, |project, cx| {
20453 project.lsp_store().update(cx, |lsp_store, cx| {
20454 lsp_store.apply_additional_edits_for_completion(
20455 buffer,
20456 completions,
20457 completion_index,
20458 push_to_history,
20459 cx,
20460 )
20461 })
20462 })
20463 }
20464
20465 fn is_completion_trigger(
20466 &self,
20467 buffer: &Entity<Buffer>,
20468 position: language::Anchor,
20469 text: &str,
20470 trigger_in_words: bool,
20471 cx: &mut Context<Editor>,
20472 ) -> bool {
20473 let mut chars = text.chars();
20474 let char = if let Some(char) = chars.next() {
20475 char
20476 } else {
20477 return false;
20478 };
20479 if chars.next().is_some() {
20480 return false;
20481 }
20482
20483 let buffer = buffer.read(cx);
20484 let snapshot = buffer.snapshot();
20485 if !snapshot.settings_at(position, cx).show_completions_on_input {
20486 return false;
20487 }
20488 let classifier = snapshot.char_classifier_at(position).for_completion(true);
20489 if trigger_in_words && classifier.is_word(char) {
20490 return true;
20491 }
20492
20493 buffer.completion_triggers().contains(text)
20494 }
20495}
20496
20497impl SemanticsProvider for Entity<Project> {
20498 fn hover(
20499 &self,
20500 buffer: &Entity<Buffer>,
20501 position: text::Anchor,
20502 cx: &mut App,
20503 ) -> Option<Task<Vec<project::Hover>>> {
20504 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
20505 }
20506
20507 fn document_highlights(
20508 &self,
20509 buffer: &Entity<Buffer>,
20510 position: text::Anchor,
20511 cx: &mut App,
20512 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
20513 Some(self.update(cx, |project, cx| {
20514 project.document_highlights(buffer, position, cx)
20515 }))
20516 }
20517
20518 fn definitions(
20519 &self,
20520 buffer: &Entity<Buffer>,
20521 position: text::Anchor,
20522 kind: GotoDefinitionKind,
20523 cx: &mut App,
20524 ) -> Option<Task<Result<Vec<LocationLink>>>> {
20525 Some(self.update(cx, |project, cx| match kind {
20526 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
20527 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
20528 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
20529 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
20530 }))
20531 }
20532
20533 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
20534 // TODO: make this work for remote projects
20535 self.update(cx, |project, cx| {
20536 if project
20537 .active_debug_session(cx)
20538 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
20539 {
20540 return true;
20541 }
20542
20543 buffer.update(cx, |buffer, cx| {
20544 project.any_language_server_supports_inlay_hints(buffer, cx)
20545 })
20546 })
20547 }
20548
20549 fn inline_values(
20550 &self,
20551 buffer_handle: Entity<Buffer>,
20552
20553 range: Range<text::Anchor>,
20554 cx: &mut App,
20555 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20556 self.update(cx, |project, cx| {
20557 let (session, active_stack_frame) = project.active_debug_session(cx)?;
20558
20559 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
20560 })
20561 }
20562
20563 fn inlay_hints(
20564 &self,
20565 buffer_handle: Entity<Buffer>,
20566 range: Range<text::Anchor>,
20567 cx: &mut App,
20568 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20569 Some(self.update(cx, |project, cx| {
20570 project.inlay_hints(buffer_handle, range, cx)
20571 }))
20572 }
20573
20574 fn resolve_inlay_hint(
20575 &self,
20576 hint: InlayHint,
20577 buffer_handle: Entity<Buffer>,
20578 server_id: LanguageServerId,
20579 cx: &mut App,
20580 ) -> Option<Task<anyhow::Result<InlayHint>>> {
20581 Some(self.update(cx, |project, cx| {
20582 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
20583 }))
20584 }
20585
20586 fn range_for_rename(
20587 &self,
20588 buffer: &Entity<Buffer>,
20589 position: text::Anchor,
20590 cx: &mut App,
20591 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
20592 Some(self.update(cx, |project, cx| {
20593 let buffer = buffer.clone();
20594 let task = project.prepare_rename(buffer.clone(), position, cx);
20595 cx.spawn(async move |_, cx| {
20596 Ok(match task.await? {
20597 PrepareRenameResponse::Success(range) => Some(range),
20598 PrepareRenameResponse::InvalidPosition => None,
20599 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
20600 // Fallback on using TreeSitter info to determine identifier range
20601 buffer.read_with(cx, |buffer, _| {
20602 let snapshot = buffer.snapshot();
20603 let (range, kind) = snapshot.surrounding_word(position);
20604 if kind != Some(CharKind::Word) {
20605 return None;
20606 }
20607 Some(
20608 snapshot.anchor_before(range.start)
20609 ..snapshot.anchor_after(range.end),
20610 )
20611 })?
20612 }
20613 })
20614 })
20615 }))
20616 }
20617
20618 fn perform_rename(
20619 &self,
20620 buffer: &Entity<Buffer>,
20621 position: text::Anchor,
20622 new_name: String,
20623 cx: &mut App,
20624 ) -> Option<Task<Result<ProjectTransaction>>> {
20625 Some(self.update(cx, |project, cx| {
20626 project.perform_rename(buffer.clone(), position, new_name, cx)
20627 }))
20628 }
20629}
20630
20631fn inlay_hint_settings(
20632 location: Anchor,
20633 snapshot: &MultiBufferSnapshot,
20634 cx: &mut Context<Editor>,
20635) -> InlayHintSettings {
20636 let file = snapshot.file_at(location);
20637 let language = snapshot.language_at(location).map(|l| l.name());
20638 language_settings(language, file, cx).inlay_hints
20639}
20640
20641fn consume_contiguous_rows(
20642 contiguous_row_selections: &mut Vec<Selection<Point>>,
20643 selection: &Selection<Point>,
20644 display_map: &DisplaySnapshot,
20645 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
20646) -> (MultiBufferRow, MultiBufferRow) {
20647 contiguous_row_selections.push(selection.clone());
20648 let start_row = MultiBufferRow(selection.start.row);
20649 let mut end_row = ending_row(selection, display_map);
20650
20651 while let Some(next_selection) = selections.peek() {
20652 if next_selection.start.row <= end_row.0 {
20653 end_row = ending_row(next_selection, display_map);
20654 contiguous_row_selections.push(selections.next().unwrap().clone());
20655 } else {
20656 break;
20657 }
20658 }
20659 (start_row, end_row)
20660}
20661
20662fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
20663 if next_selection.end.column > 0 || next_selection.is_empty() {
20664 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
20665 } else {
20666 MultiBufferRow(next_selection.end.row)
20667 }
20668}
20669
20670impl EditorSnapshot {
20671 pub fn remote_selections_in_range<'a>(
20672 &'a self,
20673 range: &'a Range<Anchor>,
20674 collaboration_hub: &dyn CollaborationHub,
20675 cx: &'a App,
20676 ) -> impl 'a + Iterator<Item = RemoteSelection> {
20677 let participant_names = collaboration_hub.user_names(cx);
20678 let participant_indices = collaboration_hub.user_participant_indices(cx);
20679 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20680 let collaborators_by_replica_id = collaborators_by_peer_id
20681 .values()
20682 .map(|collaborator| (collaborator.replica_id, collaborator))
20683 .collect::<HashMap<_, _>>();
20684 self.buffer_snapshot
20685 .selections_in_range(range, false)
20686 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20687 if replica_id == AGENT_REPLICA_ID {
20688 Some(RemoteSelection {
20689 replica_id,
20690 selection,
20691 cursor_shape,
20692 line_mode,
20693 collaborator_id: CollaboratorId::Agent,
20694 user_name: Some("Agent".into()),
20695 color: cx.theme().players().agent(),
20696 })
20697 } else {
20698 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20699 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20700 let user_name = participant_names.get(&collaborator.user_id).cloned();
20701 Some(RemoteSelection {
20702 replica_id,
20703 selection,
20704 cursor_shape,
20705 line_mode,
20706 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20707 user_name,
20708 color: if let Some(index) = participant_index {
20709 cx.theme().players().color_for_participant(index.0)
20710 } else {
20711 cx.theme().players().absent()
20712 },
20713 })
20714 }
20715 })
20716 }
20717
20718 pub fn hunks_for_ranges(
20719 &self,
20720 ranges: impl IntoIterator<Item = Range<Point>>,
20721 ) -> Vec<MultiBufferDiffHunk> {
20722 let mut hunks = Vec::new();
20723 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20724 HashMap::default();
20725 for query_range in ranges {
20726 let query_rows =
20727 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20728 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20729 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20730 ) {
20731 // Include deleted hunks that are adjacent to the query range, because
20732 // otherwise they would be missed.
20733 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20734 if hunk.status().is_deleted() {
20735 intersects_range |= hunk.row_range.start == query_rows.end;
20736 intersects_range |= hunk.row_range.end == query_rows.start;
20737 }
20738 if intersects_range {
20739 if !processed_buffer_rows
20740 .entry(hunk.buffer_id)
20741 .or_default()
20742 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20743 {
20744 continue;
20745 }
20746 hunks.push(hunk);
20747 }
20748 }
20749 }
20750
20751 hunks
20752 }
20753
20754 fn display_diff_hunks_for_rows<'a>(
20755 &'a self,
20756 display_rows: Range<DisplayRow>,
20757 folded_buffers: &'a HashSet<BufferId>,
20758 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20759 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20760 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20761
20762 self.buffer_snapshot
20763 .diff_hunks_in_range(buffer_start..buffer_end)
20764 .filter_map(|hunk| {
20765 if folded_buffers.contains(&hunk.buffer_id) {
20766 return None;
20767 }
20768
20769 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20770 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20771
20772 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20773 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20774
20775 let display_hunk = if hunk_display_start.column() != 0 {
20776 DisplayDiffHunk::Folded {
20777 display_row: hunk_display_start.row(),
20778 }
20779 } else {
20780 let mut end_row = hunk_display_end.row();
20781 if hunk_display_end.column() > 0 {
20782 end_row.0 += 1;
20783 }
20784 let is_created_file = hunk.is_created_file();
20785 DisplayDiffHunk::Unfolded {
20786 status: hunk.status(),
20787 diff_base_byte_range: hunk.diff_base_byte_range,
20788 display_row_range: hunk_display_start.row()..end_row,
20789 multi_buffer_range: Anchor::range_in_buffer(
20790 hunk.excerpt_id,
20791 hunk.buffer_id,
20792 hunk.buffer_range,
20793 ),
20794 is_created_file,
20795 }
20796 };
20797
20798 Some(display_hunk)
20799 })
20800 }
20801
20802 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20803 self.display_snapshot.buffer_snapshot.language_at(position)
20804 }
20805
20806 pub fn is_focused(&self) -> bool {
20807 self.is_focused
20808 }
20809
20810 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20811 self.placeholder_text.as_ref()
20812 }
20813
20814 pub fn scroll_position(&self) -> gpui::Point<f32> {
20815 self.scroll_anchor.scroll_position(&self.display_snapshot)
20816 }
20817
20818 fn gutter_dimensions(
20819 &self,
20820 font_id: FontId,
20821 font_size: Pixels,
20822 max_line_number_width: Pixels,
20823 cx: &App,
20824 ) -> Option<GutterDimensions> {
20825 if !self.show_gutter {
20826 return None;
20827 }
20828
20829 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20830 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20831
20832 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20833 matches!(
20834 ProjectSettings::get_global(cx).git.git_gutter,
20835 Some(GitGutterSetting::TrackedFiles)
20836 )
20837 });
20838 let gutter_settings = EditorSettings::get_global(cx).gutter;
20839 let show_line_numbers = self
20840 .show_line_numbers
20841 .unwrap_or(gutter_settings.line_numbers);
20842 let line_gutter_width = if show_line_numbers {
20843 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20844 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20845 max_line_number_width.max(min_width_for_number_on_gutter)
20846 } else {
20847 0.0.into()
20848 };
20849
20850 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20851 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20852
20853 let git_blame_entries_width =
20854 self.git_blame_gutter_max_author_length
20855 .map(|max_author_length| {
20856 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20857 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20858
20859 /// The number of characters to dedicate to gaps and margins.
20860 const SPACING_WIDTH: usize = 4;
20861
20862 let max_char_count = max_author_length.min(renderer.max_author_length())
20863 + ::git::SHORT_SHA_LENGTH
20864 + MAX_RELATIVE_TIMESTAMP.len()
20865 + SPACING_WIDTH;
20866
20867 em_advance * max_char_count
20868 });
20869
20870 let is_singleton = self.buffer_snapshot.is_singleton();
20871
20872 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
20873 left_padding += if !is_singleton {
20874 em_width * 4.0
20875 } else if show_runnables || show_breakpoints {
20876 em_width * 3.0
20877 } else if show_git_gutter && show_line_numbers {
20878 em_width * 2.0
20879 } else if show_git_gutter || show_line_numbers {
20880 em_width
20881 } else {
20882 px(0.)
20883 };
20884
20885 let shows_folds = is_singleton && gutter_settings.folds;
20886
20887 let right_padding = if shows_folds && show_line_numbers {
20888 em_width * 4.0
20889 } else if shows_folds || (!is_singleton && show_line_numbers) {
20890 em_width * 3.0
20891 } else if show_line_numbers {
20892 em_width
20893 } else {
20894 px(0.)
20895 };
20896
20897 Some(GutterDimensions {
20898 left_padding,
20899 right_padding,
20900 width: line_gutter_width + left_padding + right_padding,
20901 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
20902 git_blame_entries_width,
20903 })
20904 }
20905
20906 pub fn render_crease_toggle(
20907 &self,
20908 buffer_row: MultiBufferRow,
20909 row_contains_cursor: bool,
20910 editor: Entity<Editor>,
20911 window: &mut Window,
20912 cx: &mut App,
20913 ) -> Option<AnyElement> {
20914 let folded = self.is_line_folded(buffer_row);
20915 let mut is_foldable = false;
20916
20917 if let Some(crease) = self
20918 .crease_snapshot
20919 .query_row(buffer_row, &self.buffer_snapshot)
20920 {
20921 is_foldable = true;
20922 match crease {
20923 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20924 if let Some(render_toggle) = render_toggle {
20925 let toggle_callback =
20926 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20927 if folded {
20928 editor.update(cx, |editor, cx| {
20929 editor.fold_at(buffer_row, window, cx)
20930 });
20931 } else {
20932 editor.update(cx, |editor, cx| {
20933 editor.unfold_at(buffer_row, window, cx)
20934 });
20935 }
20936 });
20937 return Some((render_toggle)(
20938 buffer_row,
20939 folded,
20940 toggle_callback,
20941 window,
20942 cx,
20943 ));
20944 }
20945 }
20946 }
20947 }
20948
20949 is_foldable |= self.starts_indent(buffer_row);
20950
20951 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20952 Some(
20953 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20954 .toggle_state(folded)
20955 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20956 if folded {
20957 this.unfold_at(buffer_row, window, cx);
20958 } else {
20959 this.fold_at(buffer_row, window, cx);
20960 }
20961 }))
20962 .into_any_element(),
20963 )
20964 } else {
20965 None
20966 }
20967 }
20968
20969 pub fn render_crease_trailer(
20970 &self,
20971 buffer_row: MultiBufferRow,
20972 window: &mut Window,
20973 cx: &mut App,
20974 ) -> Option<AnyElement> {
20975 let folded = self.is_line_folded(buffer_row);
20976 if let Crease::Inline { render_trailer, .. } = self
20977 .crease_snapshot
20978 .query_row(buffer_row, &self.buffer_snapshot)?
20979 {
20980 let render_trailer = render_trailer.as_ref()?;
20981 Some(render_trailer(buffer_row, folded, window, cx))
20982 } else {
20983 None
20984 }
20985 }
20986}
20987
20988impl Deref for EditorSnapshot {
20989 type Target = DisplaySnapshot;
20990
20991 fn deref(&self) -> &Self::Target {
20992 &self.display_snapshot
20993 }
20994}
20995
20996#[derive(Clone, Debug, PartialEq, Eq)]
20997pub enum EditorEvent {
20998 InputIgnored {
20999 text: Arc<str>,
21000 },
21001 InputHandled {
21002 utf16_range_to_replace: Option<Range<isize>>,
21003 text: Arc<str>,
21004 },
21005 ExcerptsAdded {
21006 buffer: Entity<Buffer>,
21007 predecessor: ExcerptId,
21008 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21009 },
21010 ExcerptsRemoved {
21011 ids: Vec<ExcerptId>,
21012 removed_buffer_ids: Vec<BufferId>,
21013 },
21014 BufferFoldToggled {
21015 ids: Vec<ExcerptId>,
21016 folded: bool,
21017 },
21018 ExcerptsEdited {
21019 ids: Vec<ExcerptId>,
21020 },
21021 ExcerptsExpanded {
21022 ids: Vec<ExcerptId>,
21023 },
21024 BufferEdited,
21025 Edited {
21026 transaction_id: clock::Lamport,
21027 },
21028 Reparsed(BufferId),
21029 Focused,
21030 FocusedIn,
21031 Blurred,
21032 DirtyChanged,
21033 Saved,
21034 TitleChanged,
21035 DiffBaseChanged,
21036 SelectionsChanged {
21037 local: bool,
21038 },
21039 ScrollPositionChanged {
21040 local: bool,
21041 autoscroll: bool,
21042 },
21043 Closed,
21044 TransactionUndone {
21045 transaction_id: clock::Lamport,
21046 },
21047 TransactionBegun {
21048 transaction_id: clock::Lamport,
21049 },
21050 Reloaded,
21051 CursorShapeChanged,
21052 PushedToNavHistory {
21053 anchor: Anchor,
21054 is_deactivate: bool,
21055 },
21056}
21057
21058impl EventEmitter<EditorEvent> for Editor {}
21059
21060impl Focusable for Editor {
21061 fn focus_handle(&self, _cx: &App) -> FocusHandle {
21062 self.focus_handle.clone()
21063 }
21064}
21065
21066impl Render for Editor {
21067 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21068 let settings = ThemeSettings::get_global(cx);
21069
21070 let mut text_style = match self.mode {
21071 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21072 color: cx.theme().colors().editor_foreground,
21073 font_family: settings.ui_font.family.clone(),
21074 font_features: settings.ui_font.features.clone(),
21075 font_fallbacks: settings.ui_font.fallbacks.clone(),
21076 font_size: rems(0.875).into(),
21077 font_weight: settings.ui_font.weight,
21078 line_height: relative(settings.buffer_line_height.value()),
21079 ..Default::default()
21080 },
21081 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
21082 color: cx.theme().colors().editor_foreground,
21083 font_family: settings.buffer_font.family.clone(),
21084 font_features: settings.buffer_font.features.clone(),
21085 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21086 font_size: settings.buffer_font_size(cx).into(),
21087 font_weight: settings.buffer_font.weight,
21088 line_height: relative(settings.buffer_line_height.value()),
21089 ..Default::default()
21090 },
21091 };
21092 if let Some(text_style_refinement) = &self.text_style_refinement {
21093 text_style.refine(text_style_refinement)
21094 }
21095
21096 let background = match self.mode {
21097 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
21098 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
21099 EditorMode::Full { .. } => cx.theme().colors().editor_background,
21100 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
21101 };
21102
21103 EditorElement::new(
21104 &cx.entity(),
21105 EditorStyle {
21106 background,
21107 local_player: cx.theme().players().local(),
21108 text: text_style,
21109 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
21110 syntax: cx.theme().syntax().clone(),
21111 status: cx.theme().status().clone(),
21112 inlay_hints_style: make_inlay_hints_style(cx),
21113 inline_completion_styles: make_suggestion_styles(cx),
21114 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
21115 show_underlines: !self.mode.is_minimap(),
21116 },
21117 )
21118 }
21119}
21120
21121impl EntityInputHandler for Editor {
21122 fn text_for_range(
21123 &mut self,
21124 range_utf16: Range<usize>,
21125 adjusted_range: &mut Option<Range<usize>>,
21126 _: &mut Window,
21127 cx: &mut Context<Self>,
21128 ) -> Option<String> {
21129 let snapshot = self.buffer.read(cx).read(cx);
21130 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
21131 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
21132 if (start.0..end.0) != range_utf16 {
21133 adjusted_range.replace(start.0..end.0);
21134 }
21135 Some(snapshot.text_for_range(start..end).collect())
21136 }
21137
21138 fn selected_text_range(
21139 &mut self,
21140 ignore_disabled_input: bool,
21141 _: &mut Window,
21142 cx: &mut Context<Self>,
21143 ) -> Option<UTF16Selection> {
21144 // Prevent the IME menu from appearing when holding down an alphabetic key
21145 // while input is disabled.
21146 if !ignore_disabled_input && !self.input_enabled {
21147 return None;
21148 }
21149
21150 let selection = self.selections.newest::<OffsetUtf16>(cx);
21151 let range = selection.range();
21152
21153 Some(UTF16Selection {
21154 range: range.start.0..range.end.0,
21155 reversed: selection.reversed,
21156 })
21157 }
21158
21159 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
21160 let snapshot = self.buffer.read(cx).read(cx);
21161 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
21162 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
21163 }
21164
21165 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21166 self.clear_highlights::<InputComposition>(cx);
21167 self.ime_transaction.take();
21168 }
21169
21170 fn replace_text_in_range(
21171 &mut self,
21172 range_utf16: Option<Range<usize>>,
21173 text: &str,
21174 window: &mut Window,
21175 cx: &mut Context<Self>,
21176 ) {
21177 if !self.input_enabled {
21178 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21179 return;
21180 }
21181
21182 self.transact(window, cx, |this, window, cx| {
21183 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
21184 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21185 Some(this.selection_replacement_ranges(range_utf16, cx))
21186 } else {
21187 this.marked_text_ranges(cx)
21188 };
21189
21190 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
21191 let newest_selection_id = this.selections.newest_anchor().id;
21192 this.selections
21193 .all::<OffsetUtf16>(cx)
21194 .iter()
21195 .zip(ranges_to_replace.iter())
21196 .find_map(|(selection, range)| {
21197 if selection.id == newest_selection_id {
21198 Some(
21199 (range.start.0 as isize - selection.head().0 as isize)
21200 ..(range.end.0 as isize - selection.head().0 as isize),
21201 )
21202 } else {
21203 None
21204 }
21205 })
21206 });
21207
21208 cx.emit(EditorEvent::InputHandled {
21209 utf16_range_to_replace: range_to_replace,
21210 text: text.into(),
21211 });
21212
21213 if let Some(new_selected_ranges) = new_selected_ranges {
21214 this.change_selections(None, window, cx, |selections| {
21215 selections.select_ranges(new_selected_ranges)
21216 });
21217 this.backspace(&Default::default(), window, cx);
21218 }
21219
21220 this.handle_input(text, window, cx);
21221 });
21222
21223 if let Some(transaction) = self.ime_transaction {
21224 self.buffer.update(cx, |buffer, cx| {
21225 buffer.group_until_transaction(transaction, cx);
21226 });
21227 }
21228
21229 self.unmark_text(window, cx);
21230 }
21231
21232 fn replace_and_mark_text_in_range(
21233 &mut self,
21234 range_utf16: Option<Range<usize>>,
21235 text: &str,
21236 new_selected_range_utf16: Option<Range<usize>>,
21237 window: &mut Window,
21238 cx: &mut Context<Self>,
21239 ) {
21240 if !self.input_enabled {
21241 return;
21242 }
21243
21244 let transaction = self.transact(window, cx, |this, window, cx| {
21245 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
21246 let snapshot = this.buffer.read(cx).read(cx);
21247 if let Some(relative_range_utf16) = range_utf16.as_ref() {
21248 for marked_range in &mut marked_ranges {
21249 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
21250 marked_range.start.0 += relative_range_utf16.start;
21251 marked_range.start =
21252 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
21253 marked_range.end =
21254 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
21255 }
21256 }
21257 Some(marked_ranges)
21258 } else if let Some(range_utf16) = range_utf16 {
21259 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21260 Some(this.selection_replacement_ranges(range_utf16, cx))
21261 } else {
21262 None
21263 };
21264
21265 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
21266 let newest_selection_id = this.selections.newest_anchor().id;
21267 this.selections
21268 .all::<OffsetUtf16>(cx)
21269 .iter()
21270 .zip(ranges_to_replace.iter())
21271 .find_map(|(selection, range)| {
21272 if selection.id == newest_selection_id {
21273 Some(
21274 (range.start.0 as isize - selection.head().0 as isize)
21275 ..(range.end.0 as isize - selection.head().0 as isize),
21276 )
21277 } else {
21278 None
21279 }
21280 })
21281 });
21282
21283 cx.emit(EditorEvent::InputHandled {
21284 utf16_range_to_replace: range_to_replace,
21285 text: text.into(),
21286 });
21287
21288 if let Some(ranges) = ranges_to_replace {
21289 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
21290 }
21291
21292 let marked_ranges = {
21293 let snapshot = this.buffer.read(cx).read(cx);
21294 this.selections
21295 .disjoint_anchors()
21296 .iter()
21297 .map(|selection| {
21298 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
21299 })
21300 .collect::<Vec<_>>()
21301 };
21302
21303 if text.is_empty() {
21304 this.unmark_text(window, cx);
21305 } else {
21306 this.highlight_text::<InputComposition>(
21307 marked_ranges.clone(),
21308 HighlightStyle {
21309 underline: Some(UnderlineStyle {
21310 thickness: px(1.),
21311 color: None,
21312 wavy: false,
21313 }),
21314 ..Default::default()
21315 },
21316 cx,
21317 );
21318 }
21319
21320 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
21321 let use_autoclose = this.use_autoclose;
21322 let use_auto_surround = this.use_auto_surround;
21323 this.set_use_autoclose(false);
21324 this.set_use_auto_surround(false);
21325 this.handle_input(text, window, cx);
21326 this.set_use_autoclose(use_autoclose);
21327 this.set_use_auto_surround(use_auto_surround);
21328
21329 if let Some(new_selected_range) = new_selected_range_utf16 {
21330 let snapshot = this.buffer.read(cx).read(cx);
21331 let new_selected_ranges = marked_ranges
21332 .into_iter()
21333 .map(|marked_range| {
21334 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
21335 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
21336 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
21337 snapshot.clip_offset_utf16(new_start, Bias::Left)
21338 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
21339 })
21340 .collect::<Vec<_>>();
21341
21342 drop(snapshot);
21343 this.change_selections(None, window, cx, |selections| {
21344 selections.select_ranges(new_selected_ranges)
21345 });
21346 }
21347 });
21348
21349 self.ime_transaction = self.ime_transaction.or(transaction);
21350 if let Some(transaction) = self.ime_transaction {
21351 self.buffer.update(cx, |buffer, cx| {
21352 buffer.group_until_transaction(transaction, cx);
21353 });
21354 }
21355
21356 if self.text_highlights::<InputComposition>(cx).is_none() {
21357 self.ime_transaction.take();
21358 }
21359 }
21360
21361 fn bounds_for_range(
21362 &mut self,
21363 range_utf16: Range<usize>,
21364 element_bounds: gpui::Bounds<Pixels>,
21365 window: &mut Window,
21366 cx: &mut Context<Self>,
21367 ) -> Option<gpui::Bounds<Pixels>> {
21368 let text_layout_details = self.text_layout_details(window);
21369 let gpui::Size {
21370 width: em_width,
21371 height: line_height,
21372 } = self.character_size(window);
21373
21374 let snapshot = self.snapshot(window, cx);
21375 let scroll_position = snapshot.scroll_position();
21376 let scroll_left = scroll_position.x * em_width;
21377
21378 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
21379 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
21380 + self.gutter_dimensions.width
21381 + self.gutter_dimensions.margin;
21382 let y = line_height * (start.row().as_f32() - scroll_position.y);
21383
21384 Some(Bounds {
21385 origin: element_bounds.origin + point(x, y),
21386 size: size(em_width, line_height),
21387 })
21388 }
21389
21390 fn character_index_for_point(
21391 &mut self,
21392 point: gpui::Point<Pixels>,
21393 _window: &mut Window,
21394 _cx: &mut Context<Self>,
21395 ) -> Option<usize> {
21396 let position_map = self.last_position_map.as_ref()?;
21397 if !position_map.text_hitbox.contains(&point) {
21398 return None;
21399 }
21400 let display_point = position_map.point_for_position(point).previous_valid;
21401 let anchor = position_map
21402 .snapshot
21403 .display_point_to_anchor(display_point, Bias::Left);
21404 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
21405 Some(utf16_offset.0)
21406 }
21407}
21408
21409trait SelectionExt {
21410 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
21411 fn spanned_rows(
21412 &self,
21413 include_end_if_at_line_start: bool,
21414 map: &DisplaySnapshot,
21415 ) -> Range<MultiBufferRow>;
21416}
21417
21418impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
21419 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
21420 let start = self
21421 .start
21422 .to_point(&map.buffer_snapshot)
21423 .to_display_point(map);
21424 let end = self
21425 .end
21426 .to_point(&map.buffer_snapshot)
21427 .to_display_point(map);
21428 if self.reversed {
21429 end..start
21430 } else {
21431 start..end
21432 }
21433 }
21434
21435 fn spanned_rows(
21436 &self,
21437 include_end_if_at_line_start: bool,
21438 map: &DisplaySnapshot,
21439 ) -> Range<MultiBufferRow> {
21440 let start = self.start.to_point(&map.buffer_snapshot);
21441 let mut end = self.end.to_point(&map.buffer_snapshot);
21442 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
21443 end.row -= 1;
21444 }
21445
21446 let buffer_start = map.prev_line_boundary(start).0;
21447 let buffer_end = map.next_line_boundary(end).0;
21448 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
21449 }
21450}
21451
21452impl<T: InvalidationRegion> InvalidationStack<T> {
21453 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
21454 where
21455 S: Clone + ToOffset,
21456 {
21457 while let Some(region) = self.last() {
21458 let all_selections_inside_invalidation_ranges =
21459 if selections.len() == region.ranges().len() {
21460 selections
21461 .iter()
21462 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
21463 .all(|(selection, invalidation_range)| {
21464 let head = selection.head().to_offset(buffer);
21465 invalidation_range.start <= head && invalidation_range.end >= head
21466 })
21467 } else {
21468 false
21469 };
21470
21471 if all_selections_inside_invalidation_ranges {
21472 break;
21473 } else {
21474 self.pop();
21475 }
21476 }
21477 }
21478}
21479
21480impl<T> Default for InvalidationStack<T> {
21481 fn default() -> Self {
21482 Self(Default::default())
21483 }
21484}
21485
21486impl<T> Deref for InvalidationStack<T> {
21487 type Target = Vec<T>;
21488
21489 fn deref(&self) -> &Self::Target {
21490 &self.0
21491 }
21492}
21493
21494impl<T> DerefMut for InvalidationStack<T> {
21495 fn deref_mut(&mut self) -> &mut Self::Target {
21496 &mut self.0
21497 }
21498}
21499
21500impl InvalidationRegion for SnippetState {
21501 fn ranges(&self) -> &[Range<Anchor>] {
21502 &self.ranges[self.active_index]
21503 }
21504}
21505
21506fn inline_completion_edit_text(
21507 current_snapshot: &BufferSnapshot,
21508 edits: &[(Range<Anchor>, String)],
21509 edit_preview: &EditPreview,
21510 include_deletions: bool,
21511 cx: &App,
21512) -> HighlightedText {
21513 let edits = edits
21514 .iter()
21515 .map(|(anchor, text)| {
21516 (
21517 anchor.start.text_anchor..anchor.end.text_anchor,
21518 text.clone(),
21519 )
21520 })
21521 .collect::<Vec<_>>();
21522
21523 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
21524}
21525
21526pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
21527 match severity {
21528 lsp::DiagnosticSeverity::ERROR => colors.error,
21529 lsp::DiagnosticSeverity::WARNING => colors.warning,
21530 lsp::DiagnosticSeverity::INFORMATION => colors.info,
21531 lsp::DiagnosticSeverity::HINT => colors.info,
21532 _ => colors.ignored,
21533 }
21534}
21535
21536pub fn styled_runs_for_code_label<'a>(
21537 label: &'a CodeLabel,
21538 syntax_theme: &'a theme::SyntaxTheme,
21539) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
21540 let fade_out = HighlightStyle {
21541 fade_out: Some(0.35),
21542 ..Default::default()
21543 };
21544
21545 let mut prev_end = label.filter_range.end;
21546 label
21547 .runs
21548 .iter()
21549 .enumerate()
21550 .flat_map(move |(ix, (range, highlight_id))| {
21551 let style = if let Some(style) = highlight_id.style(syntax_theme) {
21552 style
21553 } else {
21554 return Default::default();
21555 };
21556 let mut muted_style = style;
21557 muted_style.highlight(fade_out);
21558
21559 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
21560 if range.start >= label.filter_range.end {
21561 if range.start > prev_end {
21562 runs.push((prev_end..range.start, fade_out));
21563 }
21564 runs.push((range.clone(), muted_style));
21565 } else if range.end <= label.filter_range.end {
21566 runs.push((range.clone(), style));
21567 } else {
21568 runs.push((range.start..label.filter_range.end, style));
21569 runs.push((label.filter_range.end..range.end, muted_style));
21570 }
21571 prev_end = cmp::max(prev_end, range.end);
21572
21573 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
21574 runs.push((prev_end..label.text.len(), fade_out));
21575 }
21576
21577 runs
21578 })
21579}
21580
21581pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
21582 let mut prev_index = 0;
21583 let mut prev_codepoint: Option<char> = None;
21584 text.char_indices()
21585 .chain([(text.len(), '\0')])
21586 .filter_map(move |(index, codepoint)| {
21587 let prev_codepoint = prev_codepoint.replace(codepoint)?;
21588 let is_boundary = index == text.len()
21589 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
21590 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
21591 if is_boundary {
21592 let chunk = &text[prev_index..index];
21593 prev_index = index;
21594 Some(chunk)
21595 } else {
21596 None
21597 }
21598 })
21599}
21600
21601pub trait RangeToAnchorExt: Sized {
21602 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
21603
21604 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
21605 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
21606 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
21607 }
21608}
21609
21610impl<T: ToOffset> RangeToAnchorExt for Range<T> {
21611 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
21612 let start_offset = self.start.to_offset(snapshot);
21613 let end_offset = self.end.to_offset(snapshot);
21614 if start_offset == end_offset {
21615 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
21616 } else {
21617 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
21618 }
21619 }
21620}
21621
21622pub trait RowExt {
21623 fn as_f32(&self) -> f32;
21624
21625 fn next_row(&self) -> Self;
21626
21627 fn previous_row(&self) -> Self;
21628
21629 fn minus(&self, other: Self) -> u32;
21630}
21631
21632impl RowExt for DisplayRow {
21633 fn as_f32(&self) -> f32 {
21634 self.0 as f32
21635 }
21636
21637 fn next_row(&self) -> Self {
21638 Self(self.0 + 1)
21639 }
21640
21641 fn previous_row(&self) -> Self {
21642 Self(self.0.saturating_sub(1))
21643 }
21644
21645 fn minus(&self, other: Self) -> u32 {
21646 self.0 - other.0
21647 }
21648}
21649
21650impl RowExt for MultiBufferRow {
21651 fn as_f32(&self) -> f32 {
21652 self.0 as f32
21653 }
21654
21655 fn next_row(&self) -> Self {
21656 Self(self.0 + 1)
21657 }
21658
21659 fn previous_row(&self) -> Self {
21660 Self(self.0.saturating_sub(1))
21661 }
21662
21663 fn minus(&self, other: Self) -> u32 {
21664 self.0 - other.0
21665 }
21666}
21667
21668trait RowRangeExt {
21669 type Row;
21670
21671 fn len(&self) -> usize;
21672
21673 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
21674}
21675
21676impl RowRangeExt for Range<MultiBufferRow> {
21677 type Row = MultiBufferRow;
21678
21679 fn len(&self) -> usize {
21680 (self.end.0 - self.start.0) as usize
21681 }
21682
21683 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21684 (self.start.0..self.end.0).map(MultiBufferRow)
21685 }
21686}
21687
21688impl RowRangeExt for Range<DisplayRow> {
21689 type Row = DisplayRow;
21690
21691 fn len(&self) -> usize {
21692 (self.end.0 - self.start.0) as usize
21693 }
21694
21695 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21696 (self.start.0..self.end.0).map(DisplayRow)
21697 }
21698}
21699
21700/// If select range has more than one line, we
21701/// just point the cursor to range.start.
21702fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21703 if range.start.row == range.end.row {
21704 range
21705 } else {
21706 range.start..range.start
21707 }
21708}
21709pub struct KillRing(ClipboardItem);
21710impl Global for KillRing {}
21711
21712const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21713
21714enum BreakpointPromptEditAction {
21715 Log,
21716 Condition,
21717 HitCondition,
21718}
21719
21720struct BreakpointPromptEditor {
21721 pub(crate) prompt: Entity<Editor>,
21722 editor: WeakEntity<Editor>,
21723 breakpoint_anchor: Anchor,
21724 breakpoint: Breakpoint,
21725 edit_action: BreakpointPromptEditAction,
21726 block_ids: HashSet<CustomBlockId>,
21727 editor_margins: Arc<Mutex<EditorMargins>>,
21728 _subscriptions: Vec<Subscription>,
21729}
21730
21731impl BreakpointPromptEditor {
21732 const MAX_LINES: u8 = 4;
21733
21734 fn new(
21735 editor: WeakEntity<Editor>,
21736 breakpoint_anchor: Anchor,
21737 breakpoint: Breakpoint,
21738 edit_action: BreakpointPromptEditAction,
21739 window: &mut Window,
21740 cx: &mut Context<Self>,
21741 ) -> Self {
21742 let base_text = match edit_action {
21743 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21744 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21745 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21746 }
21747 .map(|msg| msg.to_string())
21748 .unwrap_or_default();
21749
21750 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21751 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21752
21753 let prompt = cx.new(|cx| {
21754 let mut prompt = Editor::new(
21755 EditorMode::AutoHeight {
21756 max_lines: Self::MAX_LINES as usize,
21757 },
21758 buffer,
21759 None,
21760 window,
21761 cx,
21762 );
21763 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21764 prompt.set_show_cursor_when_unfocused(false, cx);
21765 prompt.set_placeholder_text(
21766 match edit_action {
21767 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21768 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21769 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21770 },
21771 cx,
21772 );
21773
21774 prompt
21775 });
21776
21777 Self {
21778 prompt,
21779 editor,
21780 breakpoint_anchor,
21781 breakpoint,
21782 edit_action,
21783 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21784 block_ids: Default::default(),
21785 _subscriptions: vec![],
21786 }
21787 }
21788
21789 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21790 self.block_ids.extend(block_ids)
21791 }
21792
21793 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21794 if let Some(editor) = self.editor.upgrade() {
21795 let message = self
21796 .prompt
21797 .read(cx)
21798 .buffer
21799 .read(cx)
21800 .as_singleton()
21801 .expect("A multi buffer in breakpoint prompt isn't possible")
21802 .read(cx)
21803 .as_rope()
21804 .to_string();
21805
21806 editor.update(cx, |editor, cx| {
21807 editor.edit_breakpoint_at_anchor(
21808 self.breakpoint_anchor,
21809 self.breakpoint.clone(),
21810 match self.edit_action {
21811 BreakpointPromptEditAction::Log => {
21812 BreakpointEditAction::EditLogMessage(message.into())
21813 }
21814 BreakpointPromptEditAction::Condition => {
21815 BreakpointEditAction::EditCondition(message.into())
21816 }
21817 BreakpointPromptEditAction::HitCondition => {
21818 BreakpointEditAction::EditHitCondition(message.into())
21819 }
21820 },
21821 cx,
21822 );
21823
21824 editor.remove_blocks(self.block_ids.clone(), None, cx);
21825 cx.focus_self(window);
21826 });
21827 }
21828 }
21829
21830 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21831 self.editor
21832 .update(cx, |editor, cx| {
21833 editor.remove_blocks(self.block_ids.clone(), None, cx);
21834 window.focus(&editor.focus_handle);
21835 })
21836 .log_err();
21837 }
21838
21839 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21840 let settings = ThemeSettings::get_global(cx);
21841 let text_style = TextStyle {
21842 color: if self.prompt.read(cx).read_only(cx) {
21843 cx.theme().colors().text_disabled
21844 } else {
21845 cx.theme().colors().text
21846 },
21847 font_family: settings.buffer_font.family.clone(),
21848 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21849 font_size: settings.buffer_font_size(cx).into(),
21850 font_weight: settings.buffer_font.weight,
21851 line_height: relative(settings.buffer_line_height.value()),
21852 ..Default::default()
21853 };
21854 EditorElement::new(
21855 &self.prompt,
21856 EditorStyle {
21857 background: cx.theme().colors().editor_background,
21858 local_player: cx.theme().players().local(),
21859 text: text_style,
21860 ..Default::default()
21861 },
21862 )
21863 }
21864}
21865
21866impl Render for BreakpointPromptEditor {
21867 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21868 let editor_margins = *self.editor_margins.lock();
21869 let gutter_dimensions = editor_margins.gutter;
21870 h_flex()
21871 .key_context("Editor")
21872 .bg(cx.theme().colors().editor_background)
21873 .border_y_1()
21874 .border_color(cx.theme().status().info_border)
21875 .size_full()
21876 .py(window.line_height() / 2.5)
21877 .on_action(cx.listener(Self::confirm))
21878 .on_action(cx.listener(Self::cancel))
21879 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21880 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21881 }
21882}
21883
21884impl Focusable for BreakpointPromptEditor {
21885 fn focus_handle(&self, cx: &App) -> FocusHandle {
21886 self.prompt.focus_handle(cx)
21887 }
21888}
21889
21890fn all_edits_insertions_or_deletions(
21891 edits: &Vec<(Range<Anchor>, String)>,
21892 snapshot: &MultiBufferSnapshot,
21893) -> bool {
21894 let mut all_insertions = true;
21895 let mut all_deletions = true;
21896
21897 for (range, new_text) in edits.iter() {
21898 let range_is_empty = range.to_offset(&snapshot).is_empty();
21899 let text_is_empty = new_text.is_empty();
21900
21901 if range_is_empty != text_is_empty {
21902 if range_is_empty {
21903 all_deletions = false;
21904 } else {
21905 all_insertions = false;
21906 }
21907 } else {
21908 return false;
21909 }
21910
21911 if !all_insertions && !all_deletions {
21912 return false;
21913 }
21914 }
21915 all_insertions || all_deletions
21916}
21917
21918struct MissingEditPredictionKeybindingTooltip;
21919
21920impl Render for MissingEditPredictionKeybindingTooltip {
21921 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21922 ui::tooltip_container(window, cx, |container, _, cx| {
21923 container
21924 .flex_shrink_0()
21925 .max_w_80()
21926 .min_h(rems_from_px(124.))
21927 .justify_between()
21928 .child(
21929 v_flex()
21930 .flex_1()
21931 .text_ui_sm(cx)
21932 .child(Label::new("Conflict with Accept Keybinding"))
21933 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21934 )
21935 .child(
21936 h_flex()
21937 .pb_1()
21938 .gap_1()
21939 .items_end()
21940 .w_full()
21941 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21942 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21943 }))
21944 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21945 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21946 })),
21947 )
21948 })
21949 }
21950}
21951
21952#[derive(Debug, Clone, Copy, PartialEq)]
21953pub struct LineHighlight {
21954 pub background: Background,
21955 pub border: Option<gpui::Hsla>,
21956 pub include_gutter: bool,
21957 pub type_id: Option<TypeId>,
21958}
21959
21960fn render_diff_hunk_controls(
21961 row: u32,
21962 status: &DiffHunkStatus,
21963 hunk_range: Range<Anchor>,
21964 is_created_file: bool,
21965 line_height: Pixels,
21966 editor: &Entity<Editor>,
21967 _window: &mut Window,
21968 cx: &mut App,
21969) -> AnyElement {
21970 h_flex()
21971 .h(line_height)
21972 .mr_1()
21973 .gap_1()
21974 .px_0p5()
21975 .pb_1()
21976 .border_x_1()
21977 .border_b_1()
21978 .border_color(cx.theme().colors().border_variant)
21979 .rounded_b_lg()
21980 .bg(cx.theme().colors().editor_background)
21981 .gap_1()
21982 .block_mouse_except_scroll()
21983 .shadow_md()
21984 .child(if status.has_secondary_hunk() {
21985 Button::new(("stage", row as u64), "Stage")
21986 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21987 .tooltip({
21988 let focus_handle = editor.focus_handle(cx);
21989 move |window, cx| {
21990 Tooltip::for_action_in(
21991 "Stage Hunk",
21992 &::git::ToggleStaged,
21993 &focus_handle,
21994 window,
21995 cx,
21996 )
21997 }
21998 })
21999 .on_click({
22000 let editor = editor.clone();
22001 move |_event, _window, cx| {
22002 editor.update(cx, |editor, cx| {
22003 editor.stage_or_unstage_diff_hunks(
22004 true,
22005 vec![hunk_range.start..hunk_range.start],
22006 cx,
22007 );
22008 });
22009 }
22010 })
22011 } else {
22012 Button::new(("unstage", row as u64), "Unstage")
22013 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22014 .tooltip({
22015 let focus_handle = editor.focus_handle(cx);
22016 move |window, cx| {
22017 Tooltip::for_action_in(
22018 "Unstage Hunk",
22019 &::git::ToggleStaged,
22020 &focus_handle,
22021 window,
22022 cx,
22023 )
22024 }
22025 })
22026 .on_click({
22027 let editor = editor.clone();
22028 move |_event, _window, cx| {
22029 editor.update(cx, |editor, cx| {
22030 editor.stage_or_unstage_diff_hunks(
22031 false,
22032 vec![hunk_range.start..hunk_range.start],
22033 cx,
22034 );
22035 });
22036 }
22037 })
22038 })
22039 .child(
22040 Button::new(("restore", row as u64), "Restore")
22041 .tooltip({
22042 let focus_handle = editor.focus_handle(cx);
22043 move |window, cx| {
22044 Tooltip::for_action_in(
22045 "Restore Hunk",
22046 &::git::Restore,
22047 &focus_handle,
22048 window,
22049 cx,
22050 )
22051 }
22052 })
22053 .on_click({
22054 let editor = editor.clone();
22055 move |_event, window, cx| {
22056 editor.update(cx, |editor, cx| {
22057 let snapshot = editor.snapshot(window, cx);
22058 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
22059 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
22060 });
22061 }
22062 })
22063 .disabled(is_created_file),
22064 )
22065 .when(
22066 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
22067 |el| {
22068 el.child(
22069 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
22070 .shape(IconButtonShape::Square)
22071 .icon_size(IconSize::Small)
22072 // .disabled(!has_multiple_hunks)
22073 .tooltip({
22074 let focus_handle = editor.focus_handle(cx);
22075 move |window, cx| {
22076 Tooltip::for_action_in(
22077 "Next Hunk",
22078 &GoToHunk,
22079 &focus_handle,
22080 window,
22081 cx,
22082 )
22083 }
22084 })
22085 .on_click({
22086 let editor = editor.clone();
22087 move |_event, window, cx| {
22088 editor.update(cx, |editor, cx| {
22089 let snapshot = editor.snapshot(window, cx);
22090 let position =
22091 hunk_range.end.to_point(&snapshot.buffer_snapshot);
22092 editor.go_to_hunk_before_or_after_position(
22093 &snapshot,
22094 position,
22095 Direction::Next,
22096 window,
22097 cx,
22098 );
22099 editor.expand_selected_diff_hunks(cx);
22100 });
22101 }
22102 }),
22103 )
22104 .child(
22105 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
22106 .shape(IconButtonShape::Square)
22107 .icon_size(IconSize::Small)
22108 // .disabled(!has_multiple_hunks)
22109 .tooltip({
22110 let focus_handle = editor.focus_handle(cx);
22111 move |window, cx| {
22112 Tooltip::for_action_in(
22113 "Previous Hunk",
22114 &GoToPreviousHunk,
22115 &focus_handle,
22116 window,
22117 cx,
22118 )
22119 }
22120 })
22121 .on_click({
22122 let editor = editor.clone();
22123 move |_event, window, cx| {
22124 editor.update(cx, |editor, cx| {
22125 let snapshot = editor.snapshot(window, cx);
22126 let point =
22127 hunk_range.start.to_point(&snapshot.buffer_snapshot);
22128 editor.go_to_hunk_before_or_after_position(
22129 &snapshot,
22130 point,
22131 Direction::Prev,
22132 window,
22133 cx,
22134 );
22135 editor.expand_selected_diff_hunks(cx);
22136 });
22137 }
22138 }),
22139 )
22140 },
22141 )
22142 .into_any_element()
22143}