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, CompletionResponse, 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 columnar_display_point: Option<DisplayPoint>,
936 add_selections_state: Option<AddSelectionsState>,
937 select_next_state: Option<SelectNextState>,
938 select_prev_state: Option<SelectNextState>,
939 selection_history: SelectionHistory,
940 defer_selection_effects: bool,
941 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
942 autoclose_regions: Vec<AutocloseRegion>,
943 snippet_stack: InvalidationStack<SnippetState>,
944 select_syntax_node_history: SelectSyntaxNodeHistory,
945 ime_transaction: Option<TransactionId>,
946 pub diagnostics_max_severity: DiagnosticSeverity,
947 active_diagnostics: ActiveDiagnostic,
948 show_inline_diagnostics: bool,
949 inline_diagnostics_update: Task<()>,
950 inline_diagnostics_enabled: bool,
951 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
952 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
953 hard_wrap: Option<usize>,
954
955 // TODO: make this a access method
956 pub project: Option<Entity<Project>>,
957 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
958 completion_provider: Option<Rc<dyn CompletionProvider>>,
959 collaboration_hub: Option<Box<dyn CollaborationHub>>,
960 blink_manager: Entity<BlinkManager>,
961 show_cursor_names: bool,
962 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
963 pub show_local_selections: bool,
964 mode: EditorMode,
965 show_breadcrumbs: bool,
966 show_gutter: bool,
967 show_scrollbars: ScrollbarAxes,
968 minimap_visibility: MinimapVisibility,
969 offset_content: bool,
970 disable_expand_excerpt_buttons: bool,
971 show_line_numbers: Option<bool>,
972 use_relative_line_numbers: Option<bool>,
973 show_git_diff_gutter: Option<bool>,
974 show_code_actions: Option<bool>,
975 show_runnables: Option<bool>,
976 show_breakpoints: Option<bool>,
977 show_wrap_guides: Option<bool>,
978 show_indent_guides: Option<bool>,
979 placeholder_text: Option<Arc<str>>,
980 highlight_order: usize,
981 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
982 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
983 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
984 scrollbar_marker_state: ScrollbarMarkerState,
985 active_indent_guides_state: ActiveIndentGuidesState,
986 nav_history: Option<ItemNavHistory>,
987 context_menu: RefCell<Option<CodeContextMenu>>,
988 context_menu_options: Option<ContextMenuOptions>,
989 mouse_context_menu: Option<MouseContextMenu>,
990 completion_tasks: Vec<(CompletionId, Task<()>)>,
991 inline_blame_popover: Option<InlineBlamePopover>,
992 signature_help_state: SignatureHelpState,
993 auto_signature_help: Option<bool>,
994 find_all_references_task_sources: Vec<Anchor>,
995 next_completion_id: CompletionId,
996 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
997 code_actions_task: Option<Task<Result<()>>>,
998 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
999 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1000 document_highlights_task: Option<Task<()>>,
1001 linked_editing_range_task: Option<Task<Option<()>>>,
1002 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1003 pending_rename: Option<RenameState>,
1004 searchable: bool,
1005 cursor_shape: CursorShape,
1006 current_line_highlight: Option<CurrentLineHighlight>,
1007 collapse_matches: bool,
1008 autoindent_mode: Option<AutoindentMode>,
1009 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1010 input_enabled: bool,
1011 use_modal_editing: bool,
1012 read_only: bool,
1013 leader_id: Option<CollaboratorId>,
1014 remote_id: Option<ViewId>,
1015 pub hover_state: HoverState,
1016 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1017 gutter_hovered: bool,
1018 hovered_link_state: Option<HoveredLinkState>,
1019 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1020 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1021 active_inline_completion: Option<InlineCompletionState>,
1022 /// Used to prevent flickering as the user types while the menu is open
1023 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1024 edit_prediction_settings: EditPredictionSettings,
1025 inline_completions_hidden_for_vim_mode: bool,
1026 show_inline_completions_override: Option<bool>,
1027 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1028 edit_prediction_preview: EditPredictionPreview,
1029 edit_prediction_indent_conflict: bool,
1030 edit_prediction_requires_modifier_in_indent_conflict: bool,
1031 inlay_hint_cache: InlayHintCache,
1032 next_inlay_id: usize,
1033 _subscriptions: Vec<Subscription>,
1034 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1035 gutter_dimensions: GutterDimensions,
1036 style: Option<EditorStyle>,
1037 text_style_refinement: Option<TextStyleRefinement>,
1038 next_editor_action_id: EditorActionId,
1039 editor_actions:
1040 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
1041 use_autoclose: bool,
1042 use_auto_surround: bool,
1043 auto_replace_emoji_shortcode: bool,
1044 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1045 show_git_blame_gutter: bool,
1046 show_git_blame_inline: bool,
1047 show_git_blame_inline_delay_task: Option<Task<()>>,
1048 git_blame_inline_enabled: bool,
1049 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1050 serialize_dirty_buffers: bool,
1051 show_selection_menu: Option<bool>,
1052 blame: Option<Entity<GitBlame>>,
1053 blame_subscription: Option<Subscription>,
1054 custom_context_menu: Option<
1055 Box<
1056 dyn 'static
1057 + Fn(
1058 &mut Self,
1059 DisplayPoint,
1060 &mut Window,
1061 &mut Context<Self>,
1062 ) -> Option<Entity<ui::ContextMenu>>,
1063 >,
1064 >,
1065 last_bounds: Option<Bounds<Pixels>>,
1066 last_position_map: Option<Rc<PositionMap>>,
1067 expect_bounds_change: Option<Bounds<Pixels>>,
1068 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1069 tasks_update_task: Option<Task<()>>,
1070 breakpoint_store: Option<Entity<BreakpointStore>>,
1071 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1072 in_project_search: bool,
1073 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1074 breadcrumb_header: Option<String>,
1075 focused_block: Option<FocusedBlock>,
1076 next_scroll_position: NextScrollCursorCenterTopBottom,
1077 addons: HashMap<TypeId, Box<dyn Addon>>,
1078 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1079 load_diff_task: Option<Shared<Task<()>>>,
1080 /// Whether we are temporarily displaying a diff other than git's
1081 temporary_diff_override: bool,
1082 selection_mark_mode: bool,
1083 toggle_fold_multiple_buffers: Task<()>,
1084 _scroll_cursor_center_top_bottom_task: Task<()>,
1085 serialize_selections: Task<()>,
1086 serialize_folds: Task<()>,
1087 mouse_cursor_hidden: bool,
1088 minimap: Option<Entity<Self>>,
1089 hide_mouse_mode: HideMouseMode,
1090 pub change_list: ChangeList,
1091 inline_value_cache: InlineValueCache,
1092}
1093
1094#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1095enum NextScrollCursorCenterTopBottom {
1096 #[default]
1097 Center,
1098 Top,
1099 Bottom,
1100}
1101
1102impl NextScrollCursorCenterTopBottom {
1103 fn next(&self) -> Self {
1104 match self {
1105 Self::Center => Self::Top,
1106 Self::Top => Self::Bottom,
1107 Self::Bottom => Self::Center,
1108 }
1109 }
1110}
1111
1112#[derive(Clone)]
1113pub struct EditorSnapshot {
1114 pub mode: EditorMode,
1115 show_gutter: bool,
1116 show_line_numbers: Option<bool>,
1117 show_git_diff_gutter: Option<bool>,
1118 show_code_actions: Option<bool>,
1119 show_runnables: Option<bool>,
1120 show_breakpoints: Option<bool>,
1121 git_blame_gutter_max_author_length: Option<usize>,
1122 pub display_snapshot: DisplaySnapshot,
1123 pub placeholder_text: Option<Arc<str>>,
1124 is_focused: bool,
1125 scroll_anchor: ScrollAnchor,
1126 ongoing_scroll: OngoingScroll,
1127 current_line_highlight: CurrentLineHighlight,
1128 gutter_hovered: bool,
1129}
1130
1131#[derive(Default, Debug, Clone, Copy)]
1132pub struct GutterDimensions {
1133 pub left_padding: Pixels,
1134 pub right_padding: Pixels,
1135 pub width: Pixels,
1136 pub margin: Pixels,
1137 pub git_blame_entries_width: Option<Pixels>,
1138}
1139
1140impl GutterDimensions {
1141 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1142 Self {
1143 margin: Self::default_gutter_margin(font_id, font_size, cx),
1144 ..Default::default()
1145 }
1146 }
1147
1148 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1149 -cx.text_system().descent(font_id, font_size)
1150 }
1151 /// The full width of the space taken up by the gutter.
1152 pub fn full_width(&self) -> Pixels {
1153 self.margin + self.width
1154 }
1155
1156 /// The width of the space reserved for the fold indicators,
1157 /// use alongside 'justify_end' and `gutter_width` to
1158 /// right align content with the line numbers
1159 pub fn fold_area_width(&self) -> Pixels {
1160 self.margin + self.right_padding
1161 }
1162}
1163
1164#[derive(Debug)]
1165pub struct RemoteSelection {
1166 pub replica_id: ReplicaId,
1167 pub selection: Selection<Anchor>,
1168 pub cursor_shape: CursorShape,
1169 pub collaborator_id: CollaboratorId,
1170 pub line_mode: bool,
1171 pub user_name: Option<SharedString>,
1172 pub color: PlayerColor,
1173}
1174
1175#[derive(Clone, Debug)]
1176struct SelectionHistoryEntry {
1177 selections: Arc<[Selection<Anchor>]>,
1178 select_next_state: Option<SelectNextState>,
1179 select_prev_state: Option<SelectNextState>,
1180 add_selections_state: Option<AddSelectionsState>,
1181}
1182
1183enum SelectionHistoryMode {
1184 Normal,
1185 Undoing,
1186 Redoing,
1187}
1188
1189#[derive(Clone, PartialEq, Eq, Hash)]
1190struct HoveredCursor {
1191 replica_id: u16,
1192 selection_id: usize,
1193}
1194
1195impl Default for SelectionHistoryMode {
1196 fn default() -> Self {
1197 Self::Normal
1198 }
1199}
1200
1201struct DeferredSelectionEffectsState {
1202 changed: bool,
1203 should_update_completions: bool,
1204 autoscroll: Option<Autoscroll>,
1205 old_cursor_position: Anchor,
1206 history_entry: SelectionHistoryEntry,
1207}
1208
1209#[derive(Default)]
1210struct SelectionHistory {
1211 #[allow(clippy::type_complexity)]
1212 selections_by_transaction:
1213 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1214 mode: SelectionHistoryMode,
1215 undo_stack: VecDeque<SelectionHistoryEntry>,
1216 redo_stack: VecDeque<SelectionHistoryEntry>,
1217}
1218
1219impl SelectionHistory {
1220 fn insert_transaction(
1221 &mut self,
1222 transaction_id: TransactionId,
1223 selections: Arc<[Selection<Anchor>]>,
1224 ) {
1225 self.selections_by_transaction
1226 .insert(transaction_id, (selections, None));
1227 }
1228
1229 #[allow(clippy::type_complexity)]
1230 fn transaction(
1231 &self,
1232 transaction_id: TransactionId,
1233 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1234 self.selections_by_transaction.get(&transaction_id)
1235 }
1236
1237 #[allow(clippy::type_complexity)]
1238 fn transaction_mut(
1239 &mut self,
1240 transaction_id: TransactionId,
1241 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1242 self.selections_by_transaction.get_mut(&transaction_id)
1243 }
1244
1245 fn push(&mut self, entry: SelectionHistoryEntry) {
1246 if !entry.selections.is_empty() {
1247 match self.mode {
1248 SelectionHistoryMode::Normal => {
1249 self.push_undo(entry);
1250 self.redo_stack.clear();
1251 }
1252 SelectionHistoryMode::Undoing => self.push_redo(entry),
1253 SelectionHistoryMode::Redoing => self.push_undo(entry),
1254 }
1255 }
1256 }
1257
1258 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1259 if self
1260 .undo_stack
1261 .back()
1262 .map_or(true, |e| e.selections != entry.selections)
1263 {
1264 self.undo_stack.push_back(entry);
1265 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1266 self.undo_stack.pop_front();
1267 }
1268 }
1269 }
1270
1271 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1272 if self
1273 .redo_stack
1274 .back()
1275 .map_or(true, |e| e.selections != entry.selections)
1276 {
1277 self.redo_stack.push_back(entry);
1278 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1279 self.redo_stack.pop_front();
1280 }
1281 }
1282 }
1283}
1284
1285#[derive(Clone, Copy)]
1286pub struct RowHighlightOptions {
1287 pub autoscroll: bool,
1288 pub include_gutter: bool,
1289}
1290
1291impl Default for RowHighlightOptions {
1292 fn default() -> Self {
1293 Self {
1294 autoscroll: Default::default(),
1295 include_gutter: true,
1296 }
1297 }
1298}
1299
1300struct RowHighlight {
1301 index: usize,
1302 range: Range<Anchor>,
1303 color: Hsla,
1304 options: RowHighlightOptions,
1305 type_id: TypeId,
1306}
1307
1308#[derive(Clone, Debug)]
1309struct AddSelectionsState {
1310 above: bool,
1311 stack: Vec<usize>,
1312}
1313
1314#[derive(Clone)]
1315struct SelectNextState {
1316 query: AhoCorasick,
1317 wordwise: bool,
1318 done: bool,
1319}
1320
1321impl std::fmt::Debug for SelectNextState {
1322 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1323 f.debug_struct(std::any::type_name::<Self>())
1324 .field("wordwise", &self.wordwise)
1325 .field("done", &self.done)
1326 .finish()
1327 }
1328}
1329
1330#[derive(Debug)]
1331struct AutocloseRegion {
1332 selection_id: usize,
1333 range: Range<Anchor>,
1334 pair: BracketPair,
1335}
1336
1337#[derive(Debug)]
1338struct SnippetState {
1339 ranges: Vec<Vec<Range<Anchor>>>,
1340 active_index: usize,
1341 choices: Vec<Option<Vec<String>>>,
1342}
1343
1344#[doc(hidden)]
1345pub struct RenameState {
1346 pub range: Range<Anchor>,
1347 pub old_name: Arc<str>,
1348 pub editor: Entity<Editor>,
1349 block_id: CustomBlockId,
1350}
1351
1352struct InvalidationStack<T>(Vec<T>);
1353
1354struct RegisteredInlineCompletionProvider {
1355 provider: Arc<dyn InlineCompletionProviderHandle>,
1356 _subscription: Subscription,
1357}
1358
1359#[derive(Debug, PartialEq, Eq)]
1360pub struct ActiveDiagnosticGroup {
1361 pub active_range: Range<Anchor>,
1362 pub active_message: String,
1363 pub group_id: usize,
1364 pub blocks: HashSet<CustomBlockId>,
1365}
1366
1367#[derive(Debug, PartialEq, Eq)]
1368
1369pub(crate) enum ActiveDiagnostic {
1370 None,
1371 All,
1372 Group(ActiveDiagnosticGroup),
1373}
1374
1375#[derive(Serialize, Deserialize, Clone, Debug)]
1376pub struct ClipboardSelection {
1377 /// The number of bytes in this selection.
1378 pub len: usize,
1379 /// Whether this was a full-line selection.
1380 pub is_entire_line: bool,
1381 /// The indentation of the first line when this content was originally copied.
1382 pub first_line_indent: u32,
1383}
1384
1385// selections, scroll behavior, was newest selection reversed
1386type SelectSyntaxNodeHistoryState = (
1387 Box<[Selection<usize>]>,
1388 SelectSyntaxNodeScrollBehavior,
1389 bool,
1390);
1391
1392#[derive(Default)]
1393struct SelectSyntaxNodeHistory {
1394 stack: Vec<SelectSyntaxNodeHistoryState>,
1395 // disable temporarily to allow changing selections without losing the stack
1396 pub disable_clearing: bool,
1397}
1398
1399impl SelectSyntaxNodeHistory {
1400 pub fn try_clear(&mut self) {
1401 if !self.disable_clearing {
1402 self.stack.clear();
1403 }
1404 }
1405
1406 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1407 self.stack.push(selection);
1408 }
1409
1410 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1411 self.stack.pop()
1412 }
1413}
1414
1415enum SelectSyntaxNodeScrollBehavior {
1416 CursorTop,
1417 FitSelection,
1418 CursorBottom,
1419}
1420
1421#[derive(Debug)]
1422pub(crate) struct NavigationData {
1423 cursor_anchor: Anchor,
1424 cursor_position: Point,
1425 scroll_anchor: ScrollAnchor,
1426 scroll_top_row: u32,
1427}
1428
1429#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1430pub enum GotoDefinitionKind {
1431 Symbol,
1432 Declaration,
1433 Type,
1434 Implementation,
1435}
1436
1437#[derive(Debug, Clone)]
1438enum InlayHintRefreshReason {
1439 ModifiersChanged(bool),
1440 Toggle(bool),
1441 SettingsChange(InlayHintSettings),
1442 NewLinesShown,
1443 BufferEdited(HashSet<Arc<Language>>),
1444 RefreshRequested,
1445 ExcerptsRemoved(Vec<ExcerptId>),
1446}
1447
1448impl InlayHintRefreshReason {
1449 fn description(&self) -> &'static str {
1450 match self {
1451 Self::ModifiersChanged(_) => "modifiers changed",
1452 Self::Toggle(_) => "toggle",
1453 Self::SettingsChange(_) => "settings change",
1454 Self::NewLinesShown => "new lines shown",
1455 Self::BufferEdited(_) => "buffer edited",
1456 Self::RefreshRequested => "refresh requested",
1457 Self::ExcerptsRemoved(_) => "excerpts removed",
1458 }
1459 }
1460}
1461
1462pub enum FormatTarget {
1463 Buffers,
1464 Ranges(Vec<Range<MultiBufferPoint>>),
1465}
1466
1467pub(crate) struct FocusedBlock {
1468 id: BlockId,
1469 focus_handle: WeakFocusHandle,
1470}
1471
1472#[derive(Clone)]
1473enum JumpData {
1474 MultiBufferRow {
1475 row: MultiBufferRow,
1476 line_offset_from_top: u32,
1477 },
1478 MultiBufferPoint {
1479 excerpt_id: ExcerptId,
1480 position: Point,
1481 anchor: text::Anchor,
1482 line_offset_from_top: u32,
1483 },
1484}
1485
1486pub enum MultibufferSelectionMode {
1487 First,
1488 All,
1489}
1490
1491#[derive(Clone, Copy, Debug, Default)]
1492pub struct RewrapOptions {
1493 pub override_language_settings: bool,
1494 pub preserve_existing_whitespace: bool,
1495}
1496
1497impl Editor {
1498 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1499 let buffer = cx.new(|cx| Buffer::local("", cx));
1500 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1501 Self::new(
1502 EditorMode::SingleLine { auto_width: false },
1503 buffer,
1504 None,
1505 window,
1506 cx,
1507 )
1508 }
1509
1510 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1511 let buffer = cx.new(|cx| Buffer::local("", cx));
1512 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1513 Self::new(EditorMode::full(), buffer, None, window, cx)
1514 }
1515
1516 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1517 let buffer = cx.new(|cx| Buffer::local("", cx));
1518 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1519 Self::new(
1520 EditorMode::SingleLine { auto_width: true },
1521 buffer,
1522 None,
1523 window,
1524 cx,
1525 )
1526 }
1527
1528 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1529 let buffer = cx.new(|cx| Buffer::local("", cx));
1530 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1531 Self::new(
1532 EditorMode::AutoHeight { max_lines },
1533 buffer,
1534 None,
1535 window,
1536 cx,
1537 )
1538 }
1539
1540 pub fn for_buffer(
1541 buffer: Entity<Buffer>,
1542 project: Option<Entity<Project>>,
1543 window: &mut Window,
1544 cx: &mut Context<Self>,
1545 ) -> Self {
1546 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1547 Self::new(EditorMode::full(), buffer, project, window, cx)
1548 }
1549
1550 pub fn for_multibuffer(
1551 buffer: Entity<MultiBuffer>,
1552 project: Option<Entity<Project>>,
1553 window: &mut Window,
1554 cx: &mut Context<Self>,
1555 ) -> Self {
1556 Self::new(EditorMode::full(), buffer, project, window, cx)
1557 }
1558
1559 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1560 let mut clone = Self::new(
1561 self.mode.clone(),
1562 self.buffer.clone(),
1563 self.project.clone(),
1564 window,
1565 cx,
1566 );
1567 self.display_map.update(cx, |display_map, cx| {
1568 let snapshot = display_map.snapshot(cx);
1569 clone.display_map.update(cx, |display_map, cx| {
1570 display_map.set_state(&snapshot, cx);
1571 });
1572 });
1573 clone.folds_did_change(cx);
1574 clone.selections.clone_state(&self.selections);
1575 clone.scroll_manager.clone_state(&self.scroll_manager);
1576 clone.searchable = self.searchable;
1577 clone.read_only = self.read_only;
1578 clone
1579 }
1580
1581 pub fn new(
1582 mode: EditorMode,
1583 buffer: Entity<MultiBuffer>,
1584 project: Option<Entity<Project>>,
1585 window: &mut Window,
1586 cx: &mut Context<Self>,
1587 ) -> Self {
1588 Editor::new_internal(mode, buffer, project, None, window, cx)
1589 }
1590
1591 fn new_internal(
1592 mode: EditorMode,
1593 buffer: Entity<MultiBuffer>,
1594 project: Option<Entity<Project>>,
1595 display_map: Option<Entity<DisplayMap>>,
1596 window: &mut Window,
1597 cx: &mut Context<Self>,
1598 ) -> Self {
1599 debug_assert!(
1600 display_map.is_none() || mode.is_minimap(),
1601 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1602 );
1603
1604 let full_mode = mode.is_full();
1605 let diagnostics_max_severity = if full_mode {
1606 EditorSettings::get_global(cx)
1607 .diagnostics_max_severity
1608 .unwrap_or(DiagnosticSeverity::Hint)
1609 } else {
1610 DiagnosticSeverity::Off
1611 };
1612 let style = window.text_style();
1613 let font_size = style.font_size.to_pixels(window.rem_size());
1614 let editor = cx.entity().downgrade();
1615 let fold_placeholder = FoldPlaceholder {
1616 constrain_width: true,
1617 render: Arc::new(move |fold_id, fold_range, cx| {
1618 let editor = editor.clone();
1619 div()
1620 .id(fold_id)
1621 .bg(cx.theme().colors().ghost_element_background)
1622 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1623 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1624 .rounded_xs()
1625 .size_full()
1626 .cursor_pointer()
1627 .child("⋯")
1628 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1629 .on_click(move |_, _window, cx| {
1630 editor
1631 .update(cx, |editor, cx| {
1632 editor.unfold_ranges(
1633 &[fold_range.start..fold_range.end],
1634 true,
1635 false,
1636 cx,
1637 );
1638 cx.stop_propagation();
1639 })
1640 .ok();
1641 })
1642 .into_any()
1643 }),
1644 merge_adjacent: true,
1645 ..FoldPlaceholder::default()
1646 };
1647 let display_map = display_map.unwrap_or_else(|| {
1648 cx.new(|cx| {
1649 DisplayMap::new(
1650 buffer.clone(),
1651 style.font(),
1652 font_size,
1653 None,
1654 FILE_HEADER_HEIGHT,
1655 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1656 fold_placeholder,
1657 diagnostics_max_severity,
1658 cx,
1659 )
1660 })
1661 });
1662
1663 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1664
1665 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1666
1667 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1668 .then(|| language_settings::SoftWrap::None);
1669
1670 let mut project_subscriptions = Vec::new();
1671 if mode.is_full() {
1672 if let Some(project) = project.as_ref() {
1673 project_subscriptions.push(cx.subscribe_in(
1674 project,
1675 window,
1676 |editor, _, event, window, cx| match event {
1677 project::Event::RefreshCodeLens => {
1678 // we always query lens with actions, without storing them, always refreshing them
1679 }
1680 project::Event::RefreshInlayHints => {
1681 editor
1682 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1683 }
1684 project::Event::LanguageServerAdded(..)
1685 | project::Event::LanguageServerRemoved(..) => {
1686 if editor.tasks_update_task.is_none() {
1687 editor.tasks_update_task =
1688 Some(editor.refresh_runnables(window, cx));
1689 }
1690 }
1691 project::Event::SnippetEdit(id, snippet_edits) => {
1692 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1693 let focus_handle = editor.focus_handle(cx);
1694 if focus_handle.is_focused(window) {
1695 let snapshot = buffer.read(cx).snapshot();
1696 for (range, snippet) in snippet_edits {
1697 let editor_range =
1698 language::range_from_lsp(*range).to_offset(&snapshot);
1699 editor
1700 .insert_snippet(
1701 &[editor_range],
1702 snippet.clone(),
1703 window,
1704 cx,
1705 )
1706 .ok();
1707 }
1708 }
1709 }
1710 }
1711 _ => {}
1712 },
1713 ));
1714 if let Some(task_inventory) = project
1715 .read(cx)
1716 .task_store()
1717 .read(cx)
1718 .task_inventory()
1719 .cloned()
1720 {
1721 project_subscriptions.push(cx.observe_in(
1722 &task_inventory,
1723 window,
1724 |editor, _, window, cx| {
1725 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1726 },
1727 ));
1728 };
1729
1730 project_subscriptions.push(cx.subscribe_in(
1731 &project.read(cx).breakpoint_store(),
1732 window,
1733 |editor, _, event, window, cx| match event {
1734 BreakpointStoreEvent::ClearDebugLines => {
1735 editor.clear_row_highlights::<ActiveDebugLine>();
1736 editor.refresh_inline_values(cx);
1737 }
1738 BreakpointStoreEvent::SetDebugLine => {
1739 if editor.go_to_active_debug_line(window, cx) {
1740 cx.stop_propagation();
1741 }
1742
1743 editor.refresh_inline_values(cx);
1744 }
1745 _ => {}
1746 },
1747 ));
1748 }
1749 }
1750
1751 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1752
1753 let inlay_hint_settings =
1754 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1755 let focus_handle = cx.focus_handle();
1756 cx.on_focus(&focus_handle, window, Self::handle_focus)
1757 .detach();
1758 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1759 .detach();
1760 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1761 .detach();
1762 cx.on_blur(&focus_handle, window, Self::handle_blur)
1763 .detach();
1764
1765 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1766 Some(false)
1767 } else {
1768 None
1769 };
1770
1771 let breakpoint_store = match (&mode, project.as_ref()) {
1772 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1773 _ => None,
1774 };
1775
1776 let mut code_action_providers = Vec::new();
1777 let mut load_uncommitted_diff = None;
1778 if let Some(project) = project.clone() {
1779 load_uncommitted_diff = Some(
1780 update_uncommitted_diff_for_buffer(
1781 cx.entity(),
1782 &project,
1783 buffer.read(cx).all_buffers(),
1784 buffer.clone(),
1785 cx,
1786 )
1787 .shared(),
1788 );
1789 code_action_providers.push(Rc::new(project) as Rc<_>);
1790 }
1791
1792 let mut this = Self {
1793 focus_handle,
1794 show_cursor_when_unfocused: false,
1795 last_focused_descendant: None,
1796 buffer: buffer.clone(),
1797 display_map: display_map.clone(),
1798 selections,
1799 scroll_manager: ScrollManager::new(cx),
1800 columnar_selection_tail: None,
1801 columnar_display_point: None,
1802 add_selections_state: None,
1803 select_next_state: None,
1804 select_prev_state: None,
1805 selection_history: SelectionHistory::default(),
1806 defer_selection_effects: false,
1807 deferred_selection_effects_state: None,
1808 autoclose_regions: Vec::new(),
1809 snippet_stack: InvalidationStack::default(),
1810 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1811 ime_transaction: None,
1812 active_diagnostics: ActiveDiagnostic::None,
1813 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1814 inline_diagnostics_update: Task::ready(()),
1815 inline_diagnostics: Vec::new(),
1816 soft_wrap_mode_override,
1817 diagnostics_max_severity,
1818 hard_wrap: None,
1819 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1820 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1821 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1822 project,
1823 blink_manager: blink_manager.clone(),
1824 show_local_selections: true,
1825 show_scrollbars: ScrollbarAxes {
1826 horizontal: full_mode,
1827 vertical: full_mode,
1828 },
1829 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1830 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1831 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1832 show_gutter: mode.is_full(),
1833 show_line_numbers: None,
1834 use_relative_line_numbers: None,
1835 disable_expand_excerpt_buttons: false,
1836 show_git_diff_gutter: None,
1837 show_code_actions: None,
1838 show_runnables: None,
1839 show_breakpoints: None,
1840 show_wrap_guides: None,
1841 show_indent_guides,
1842 placeholder_text: None,
1843 highlight_order: 0,
1844 highlighted_rows: HashMap::default(),
1845 background_highlights: TreeMap::default(),
1846 gutter_highlights: TreeMap::default(),
1847 scrollbar_marker_state: ScrollbarMarkerState::default(),
1848 active_indent_guides_state: ActiveIndentGuidesState::default(),
1849 nav_history: None,
1850 context_menu: RefCell::new(None),
1851 context_menu_options: None,
1852 mouse_context_menu: None,
1853 completion_tasks: Vec::new(),
1854 inline_blame_popover: None,
1855 signature_help_state: SignatureHelpState::default(),
1856 auto_signature_help: None,
1857 find_all_references_task_sources: Vec::new(),
1858 next_completion_id: 0,
1859 next_inlay_id: 0,
1860 code_action_providers,
1861 available_code_actions: None,
1862 code_actions_task: None,
1863 quick_selection_highlight_task: None,
1864 debounced_selection_highlight_task: None,
1865 document_highlights_task: None,
1866 linked_editing_range_task: None,
1867 pending_rename: None,
1868 searchable: true,
1869 cursor_shape: EditorSettings::get_global(cx)
1870 .cursor_shape
1871 .unwrap_or_default(),
1872 current_line_highlight: None,
1873 autoindent_mode: Some(AutoindentMode::EachLine),
1874 collapse_matches: false,
1875 workspace: None,
1876 input_enabled: true,
1877 use_modal_editing: mode.is_full(),
1878 read_only: mode.is_minimap(),
1879 use_autoclose: true,
1880 use_auto_surround: true,
1881 auto_replace_emoji_shortcode: false,
1882 jsx_tag_auto_close_enabled_in_any_buffer: false,
1883 leader_id: None,
1884 remote_id: None,
1885 hover_state: HoverState::default(),
1886 pending_mouse_down: None,
1887 hovered_link_state: None,
1888 edit_prediction_provider: None,
1889 active_inline_completion: None,
1890 stale_inline_completion_in_menu: None,
1891 edit_prediction_preview: EditPredictionPreview::Inactive {
1892 released_too_fast: false,
1893 },
1894 inline_diagnostics_enabled: mode.is_full(),
1895 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1896 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1897
1898 gutter_hovered: false,
1899 pixel_position_of_newest_cursor: None,
1900 last_bounds: None,
1901 last_position_map: None,
1902 expect_bounds_change: None,
1903 gutter_dimensions: GutterDimensions::default(),
1904 style: None,
1905 show_cursor_names: false,
1906 hovered_cursors: HashMap::default(),
1907 next_editor_action_id: EditorActionId::default(),
1908 editor_actions: Rc::default(),
1909 inline_completions_hidden_for_vim_mode: false,
1910 show_inline_completions_override: None,
1911 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1912 edit_prediction_settings: EditPredictionSettings::Disabled,
1913 edit_prediction_indent_conflict: false,
1914 edit_prediction_requires_modifier_in_indent_conflict: true,
1915 custom_context_menu: None,
1916 show_git_blame_gutter: false,
1917 show_git_blame_inline: false,
1918 show_selection_menu: None,
1919 show_git_blame_inline_delay_task: None,
1920 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1921 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1922 serialize_dirty_buffers: !mode.is_minimap()
1923 && ProjectSettings::get_global(cx)
1924 .session
1925 .restore_unsaved_buffers,
1926 blame: None,
1927 blame_subscription: None,
1928 tasks: BTreeMap::default(),
1929
1930 breakpoint_store,
1931 gutter_breakpoint_indicator: (None, None),
1932 _subscriptions: vec![
1933 cx.observe(&buffer, Self::on_buffer_changed),
1934 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1935 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1936 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1937 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1938 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1939 cx.observe_window_activation(window, |editor, window, cx| {
1940 let active = window.is_window_active();
1941 editor.blink_manager.update(cx, |blink_manager, cx| {
1942 if active {
1943 blink_manager.enable(cx);
1944 } else {
1945 blink_manager.disable(cx);
1946 }
1947 });
1948 if active {
1949 editor.show_mouse_cursor();
1950 }
1951 }),
1952 ],
1953 tasks_update_task: None,
1954 linked_edit_ranges: Default::default(),
1955 in_project_search: false,
1956 previous_search_ranges: None,
1957 breadcrumb_header: None,
1958 focused_block: None,
1959 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1960 addons: HashMap::default(),
1961 registered_buffers: HashMap::default(),
1962 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1963 selection_mark_mode: false,
1964 toggle_fold_multiple_buffers: Task::ready(()),
1965 serialize_selections: Task::ready(()),
1966 serialize_folds: Task::ready(()),
1967 text_style_refinement: None,
1968 load_diff_task: load_uncommitted_diff,
1969 temporary_diff_override: false,
1970 mouse_cursor_hidden: false,
1971 minimap: None,
1972 hide_mouse_mode: EditorSettings::get_global(cx)
1973 .hide_mouse
1974 .unwrap_or_default(),
1975 change_list: ChangeList::new(),
1976 mode,
1977 };
1978 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1979 this._subscriptions
1980 .push(cx.observe(breakpoints, |_, _, cx| {
1981 cx.notify();
1982 }));
1983 }
1984 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1985 this._subscriptions.extend(project_subscriptions);
1986
1987 this._subscriptions.push(cx.subscribe_in(
1988 &cx.entity(),
1989 window,
1990 |editor, _, e: &EditorEvent, window, cx| match e {
1991 EditorEvent::ScrollPositionChanged { local, .. } => {
1992 if *local {
1993 let new_anchor = editor.scroll_manager.anchor();
1994 let snapshot = editor.snapshot(window, cx);
1995 editor.update_restoration_data(cx, move |data| {
1996 data.scroll_position = (
1997 new_anchor.top_row(&snapshot.buffer_snapshot),
1998 new_anchor.offset,
1999 );
2000 });
2001 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2002 editor.inline_blame_popover.take();
2003 }
2004 }
2005 EditorEvent::Edited { .. } => {
2006 if !vim_enabled(cx) {
2007 let (map, selections) = editor.selections.all_adjusted_display(cx);
2008 let pop_state = editor
2009 .change_list
2010 .last()
2011 .map(|previous| {
2012 previous.len() == selections.len()
2013 && previous.iter().enumerate().all(|(ix, p)| {
2014 p.to_display_point(&map).row()
2015 == selections[ix].head().row()
2016 })
2017 })
2018 .unwrap_or(false);
2019 let new_positions = selections
2020 .into_iter()
2021 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2022 .collect();
2023 editor
2024 .change_list
2025 .push_to_change_list(pop_state, new_positions);
2026 }
2027 }
2028 _ => (),
2029 },
2030 ));
2031
2032 if let Some(dap_store) = this
2033 .project
2034 .as_ref()
2035 .map(|project| project.read(cx).dap_store())
2036 {
2037 let weak_editor = cx.weak_entity();
2038
2039 this._subscriptions
2040 .push(
2041 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2042 let session_entity = cx.entity();
2043 weak_editor
2044 .update(cx, |editor, cx| {
2045 editor._subscriptions.push(
2046 cx.subscribe(&session_entity, Self::on_debug_session_event),
2047 );
2048 })
2049 .ok();
2050 }),
2051 );
2052
2053 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2054 this._subscriptions
2055 .push(cx.subscribe(&session, Self::on_debug_session_event));
2056 }
2057 }
2058
2059 this.end_selection(window, cx);
2060 this.scroll_manager.show_scrollbars(window, cx);
2061 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
2062
2063 if full_mode {
2064 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2065 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2066
2067 if this.git_blame_inline_enabled {
2068 this.start_git_blame_inline(false, window, cx);
2069 }
2070
2071 this.go_to_active_debug_line(window, cx);
2072
2073 if let Some(buffer) = buffer.read(cx).as_singleton() {
2074 if let Some(project) = this.project.as_ref() {
2075 let handle = project.update(cx, |project, cx| {
2076 project.register_buffer_with_language_servers(&buffer, cx)
2077 });
2078 this.registered_buffers
2079 .insert(buffer.read(cx).remote_id(), handle);
2080 }
2081 }
2082
2083 this.minimap = this.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2084 }
2085
2086 this.report_editor_event("Editor Opened", None, cx);
2087 this
2088 }
2089
2090 pub fn deploy_mouse_context_menu(
2091 &mut self,
2092 position: gpui::Point<Pixels>,
2093 context_menu: Entity<ContextMenu>,
2094 window: &mut Window,
2095 cx: &mut Context<Self>,
2096 ) {
2097 self.mouse_context_menu = Some(MouseContextMenu::new(
2098 self,
2099 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2100 context_menu,
2101 window,
2102 cx,
2103 ));
2104 }
2105
2106 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2107 self.mouse_context_menu
2108 .as_ref()
2109 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2110 }
2111
2112 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2113 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2114 }
2115
2116 fn key_context_internal(
2117 &self,
2118 has_active_edit_prediction: bool,
2119 window: &Window,
2120 cx: &App,
2121 ) -> KeyContext {
2122 let mut key_context = KeyContext::new_with_defaults();
2123 key_context.add("Editor");
2124 let mode = match self.mode {
2125 EditorMode::SingleLine { .. } => "single_line",
2126 EditorMode::AutoHeight { .. } => "auto_height",
2127 EditorMode::Minimap { .. } => "minimap",
2128 EditorMode::Full { .. } => "full",
2129 };
2130
2131 if EditorSettings::jupyter_enabled(cx) {
2132 key_context.add("jupyter");
2133 }
2134
2135 key_context.set("mode", mode);
2136 if self.pending_rename.is_some() {
2137 key_context.add("renaming");
2138 }
2139
2140 match self.context_menu.borrow().as_ref() {
2141 Some(CodeContextMenu::Completions(_)) => {
2142 key_context.add("menu");
2143 key_context.add("showing_completions");
2144 }
2145 Some(CodeContextMenu::CodeActions(_)) => {
2146 key_context.add("menu");
2147 key_context.add("showing_code_actions")
2148 }
2149 None => {}
2150 }
2151
2152 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2153 if !self.focus_handle(cx).contains_focused(window, cx)
2154 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2155 {
2156 for addon in self.addons.values() {
2157 addon.extend_key_context(&mut key_context, cx)
2158 }
2159 }
2160
2161 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2162 if let Some(extension) = singleton_buffer
2163 .read(cx)
2164 .file()
2165 .and_then(|file| file.path().extension()?.to_str())
2166 {
2167 key_context.set("extension", extension.to_string());
2168 }
2169 } else {
2170 key_context.add("multibuffer");
2171 }
2172
2173 if has_active_edit_prediction {
2174 if self.edit_prediction_in_conflict() {
2175 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2176 } else {
2177 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2178 key_context.add("copilot_suggestion");
2179 }
2180 }
2181
2182 if self.selection_mark_mode {
2183 key_context.add("selection_mode");
2184 }
2185
2186 key_context
2187 }
2188
2189 fn show_mouse_cursor(&mut self) {
2190 self.mouse_cursor_hidden = false;
2191 }
2192
2193 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2194 self.mouse_cursor_hidden = match origin {
2195 HideMouseCursorOrigin::TypingAction => {
2196 matches!(
2197 self.hide_mouse_mode,
2198 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2199 )
2200 }
2201 HideMouseCursorOrigin::MovementAction => {
2202 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2203 }
2204 };
2205 }
2206
2207 pub fn edit_prediction_in_conflict(&self) -> bool {
2208 if !self.show_edit_predictions_in_menu() {
2209 return false;
2210 }
2211
2212 let showing_completions = self
2213 .context_menu
2214 .borrow()
2215 .as_ref()
2216 .map_or(false, |context| {
2217 matches!(context, CodeContextMenu::Completions(_))
2218 });
2219
2220 showing_completions
2221 || self.edit_prediction_requires_modifier()
2222 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2223 // bindings to insert tab characters.
2224 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2225 }
2226
2227 pub fn accept_edit_prediction_keybind(
2228 &self,
2229 window: &Window,
2230 cx: &App,
2231 ) -> AcceptEditPredictionBinding {
2232 let key_context = self.key_context_internal(true, window, cx);
2233 let in_conflict = self.edit_prediction_in_conflict();
2234
2235 AcceptEditPredictionBinding(
2236 window
2237 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2238 .into_iter()
2239 .filter(|binding| {
2240 !in_conflict
2241 || binding
2242 .keystrokes()
2243 .first()
2244 .map_or(false, |keystroke| keystroke.modifiers.modified())
2245 })
2246 .rev()
2247 .min_by_key(|binding| {
2248 binding
2249 .keystrokes()
2250 .first()
2251 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
2252 }),
2253 )
2254 }
2255
2256 pub fn new_file(
2257 workspace: &mut Workspace,
2258 _: &workspace::NewFile,
2259 window: &mut Window,
2260 cx: &mut Context<Workspace>,
2261 ) {
2262 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2263 "Failed to create buffer",
2264 window,
2265 cx,
2266 |e, _, _| match e.error_code() {
2267 ErrorCode::RemoteUpgradeRequired => Some(format!(
2268 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2269 e.error_tag("required").unwrap_or("the latest version")
2270 )),
2271 _ => None,
2272 },
2273 );
2274 }
2275
2276 pub fn new_in_workspace(
2277 workspace: &mut Workspace,
2278 window: &mut Window,
2279 cx: &mut Context<Workspace>,
2280 ) -> Task<Result<Entity<Editor>>> {
2281 let project = workspace.project().clone();
2282 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2283
2284 cx.spawn_in(window, async move |workspace, cx| {
2285 let buffer = create.await?;
2286 workspace.update_in(cx, |workspace, window, cx| {
2287 let editor =
2288 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2289 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2290 editor
2291 })
2292 })
2293 }
2294
2295 fn new_file_vertical(
2296 workspace: &mut Workspace,
2297 _: &workspace::NewFileSplitVertical,
2298 window: &mut Window,
2299 cx: &mut Context<Workspace>,
2300 ) {
2301 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2302 }
2303
2304 fn new_file_horizontal(
2305 workspace: &mut Workspace,
2306 _: &workspace::NewFileSplitHorizontal,
2307 window: &mut Window,
2308 cx: &mut Context<Workspace>,
2309 ) {
2310 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2311 }
2312
2313 fn new_file_in_direction(
2314 workspace: &mut Workspace,
2315 direction: SplitDirection,
2316 window: &mut Window,
2317 cx: &mut Context<Workspace>,
2318 ) {
2319 let project = workspace.project().clone();
2320 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2321
2322 cx.spawn_in(window, async move |workspace, cx| {
2323 let buffer = create.await?;
2324 workspace.update_in(cx, move |workspace, window, cx| {
2325 workspace.split_item(
2326 direction,
2327 Box::new(
2328 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2329 ),
2330 window,
2331 cx,
2332 )
2333 })?;
2334 anyhow::Ok(())
2335 })
2336 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2337 match e.error_code() {
2338 ErrorCode::RemoteUpgradeRequired => Some(format!(
2339 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2340 e.error_tag("required").unwrap_or("the latest version")
2341 )),
2342 _ => None,
2343 }
2344 });
2345 }
2346
2347 pub fn leader_id(&self) -> Option<CollaboratorId> {
2348 self.leader_id
2349 }
2350
2351 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2352 &self.buffer
2353 }
2354
2355 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2356 self.workspace.as_ref()?.0.upgrade()
2357 }
2358
2359 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2360 self.buffer().read(cx).title(cx)
2361 }
2362
2363 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2364 let git_blame_gutter_max_author_length = self
2365 .render_git_blame_gutter(cx)
2366 .then(|| {
2367 if let Some(blame) = self.blame.as_ref() {
2368 let max_author_length =
2369 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2370 Some(max_author_length)
2371 } else {
2372 None
2373 }
2374 })
2375 .flatten();
2376
2377 EditorSnapshot {
2378 mode: self.mode.clone(),
2379 show_gutter: self.show_gutter,
2380 show_line_numbers: self.show_line_numbers,
2381 show_git_diff_gutter: self.show_git_diff_gutter,
2382 show_code_actions: self.show_code_actions,
2383 show_runnables: self.show_runnables,
2384 show_breakpoints: self.show_breakpoints,
2385 git_blame_gutter_max_author_length,
2386 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2387 scroll_anchor: self.scroll_manager.anchor(),
2388 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2389 placeholder_text: self.placeholder_text.clone(),
2390 is_focused: self.focus_handle.is_focused(window),
2391 current_line_highlight: self
2392 .current_line_highlight
2393 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2394 gutter_hovered: self.gutter_hovered,
2395 }
2396 }
2397
2398 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2399 self.buffer.read(cx).language_at(point, cx)
2400 }
2401
2402 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2403 self.buffer.read(cx).read(cx).file_at(point).cloned()
2404 }
2405
2406 pub fn active_excerpt(
2407 &self,
2408 cx: &App,
2409 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2410 self.buffer
2411 .read(cx)
2412 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2413 }
2414
2415 pub fn mode(&self) -> &EditorMode {
2416 &self.mode
2417 }
2418
2419 pub fn set_mode(&mut self, mode: EditorMode) {
2420 self.mode = mode;
2421 }
2422
2423 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2424 self.collaboration_hub.as_deref()
2425 }
2426
2427 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2428 self.collaboration_hub = Some(hub);
2429 }
2430
2431 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2432 self.in_project_search = in_project_search;
2433 }
2434
2435 pub fn set_custom_context_menu(
2436 &mut self,
2437 f: impl 'static
2438 + Fn(
2439 &mut Self,
2440 DisplayPoint,
2441 &mut Window,
2442 &mut Context<Self>,
2443 ) -> Option<Entity<ui::ContextMenu>>,
2444 ) {
2445 self.custom_context_menu = Some(Box::new(f))
2446 }
2447
2448 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2449 self.completion_provider = provider;
2450 }
2451
2452 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2453 self.semantics_provider.clone()
2454 }
2455
2456 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2457 self.semantics_provider = provider;
2458 }
2459
2460 pub fn set_edit_prediction_provider<T>(
2461 &mut self,
2462 provider: Option<Entity<T>>,
2463 window: &mut Window,
2464 cx: &mut Context<Self>,
2465 ) where
2466 T: EditPredictionProvider,
2467 {
2468 self.edit_prediction_provider =
2469 provider.map(|provider| RegisteredInlineCompletionProvider {
2470 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2471 if this.focus_handle.is_focused(window) {
2472 this.update_visible_inline_completion(window, cx);
2473 }
2474 }),
2475 provider: Arc::new(provider),
2476 });
2477 self.update_edit_prediction_settings(cx);
2478 self.refresh_inline_completion(false, false, window, cx);
2479 }
2480
2481 pub fn placeholder_text(&self) -> Option<&str> {
2482 self.placeholder_text.as_deref()
2483 }
2484
2485 pub fn set_placeholder_text(
2486 &mut self,
2487 placeholder_text: impl Into<Arc<str>>,
2488 cx: &mut Context<Self>,
2489 ) {
2490 let placeholder_text = Some(placeholder_text.into());
2491 if self.placeholder_text != placeholder_text {
2492 self.placeholder_text = placeholder_text;
2493 cx.notify();
2494 }
2495 }
2496
2497 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2498 self.cursor_shape = cursor_shape;
2499
2500 // Disrupt blink for immediate user feedback that the cursor shape has changed
2501 self.blink_manager.update(cx, BlinkManager::show_cursor);
2502
2503 cx.notify();
2504 }
2505
2506 pub fn set_current_line_highlight(
2507 &mut self,
2508 current_line_highlight: Option<CurrentLineHighlight>,
2509 ) {
2510 self.current_line_highlight = current_line_highlight;
2511 }
2512
2513 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2514 self.collapse_matches = collapse_matches;
2515 }
2516
2517 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2518 let buffers = self.buffer.read(cx).all_buffers();
2519 let Some(project) = self.project.as_ref() else {
2520 return;
2521 };
2522 project.update(cx, |project, cx| {
2523 for buffer in buffers {
2524 self.registered_buffers
2525 .entry(buffer.read(cx).remote_id())
2526 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2527 }
2528 })
2529 }
2530
2531 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2532 if self.collapse_matches {
2533 return range.start..range.start;
2534 }
2535 range.clone()
2536 }
2537
2538 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2539 if self.display_map.read(cx).clip_at_line_ends != clip {
2540 self.display_map
2541 .update(cx, |map, _| map.clip_at_line_ends = clip);
2542 }
2543 }
2544
2545 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2546 self.input_enabled = input_enabled;
2547 }
2548
2549 pub fn set_inline_completions_hidden_for_vim_mode(
2550 &mut self,
2551 hidden: bool,
2552 window: &mut Window,
2553 cx: &mut Context<Self>,
2554 ) {
2555 if hidden != self.inline_completions_hidden_for_vim_mode {
2556 self.inline_completions_hidden_for_vim_mode = hidden;
2557 if hidden {
2558 self.update_visible_inline_completion(window, cx);
2559 } else {
2560 self.refresh_inline_completion(true, false, window, cx);
2561 }
2562 }
2563 }
2564
2565 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2566 self.menu_inline_completions_policy = value;
2567 }
2568
2569 pub fn set_autoindent(&mut self, autoindent: bool) {
2570 if autoindent {
2571 self.autoindent_mode = Some(AutoindentMode::EachLine);
2572 } else {
2573 self.autoindent_mode = None;
2574 }
2575 }
2576
2577 pub fn read_only(&self, cx: &App) -> bool {
2578 self.read_only || self.buffer.read(cx).read_only()
2579 }
2580
2581 pub fn set_read_only(&mut self, read_only: bool) {
2582 self.read_only = read_only;
2583 }
2584
2585 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2586 self.use_autoclose = autoclose;
2587 }
2588
2589 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2590 self.use_auto_surround = auto_surround;
2591 }
2592
2593 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2594 self.auto_replace_emoji_shortcode = auto_replace;
2595 }
2596
2597 pub fn toggle_edit_predictions(
2598 &mut self,
2599 _: &ToggleEditPrediction,
2600 window: &mut Window,
2601 cx: &mut Context<Self>,
2602 ) {
2603 if self.show_inline_completions_override.is_some() {
2604 self.set_show_edit_predictions(None, window, cx);
2605 } else {
2606 let show_edit_predictions = !self.edit_predictions_enabled();
2607 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2608 }
2609 }
2610
2611 pub fn set_show_edit_predictions(
2612 &mut self,
2613 show_edit_predictions: Option<bool>,
2614 window: &mut Window,
2615 cx: &mut Context<Self>,
2616 ) {
2617 self.show_inline_completions_override = show_edit_predictions;
2618 self.update_edit_prediction_settings(cx);
2619
2620 if let Some(false) = show_edit_predictions {
2621 self.discard_inline_completion(false, cx);
2622 } else {
2623 self.refresh_inline_completion(false, true, window, cx);
2624 }
2625 }
2626
2627 fn inline_completions_disabled_in_scope(
2628 &self,
2629 buffer: &Entity<Buffer>,
2630 buffer_position: language::Anchor,
2631 cx: &App,
2632 ) -> bool {
2633 let snapshot = buffer.read(cx).snapshot();
2634 let settings = snapshot.settings_at(buffer_position, cx);
2635
2636 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2637 return false;
2638 };
2639
2640 scope.override_name().map_or(false, |scope_name| {
2641 settings
2642 .edit_predictions_disabled_in
2643 .iter()
2644 .any(|s| s == scope_name)
2645 })
2646 }
2647
2648 pub fn set_use_modal_editing(&mut self, to: bool) {
2649 self.use_modal_editing = to;
2650 }
2651
2652 pub fn use_modal_editing(&self) -> bool {
2653 self.use_modal_editing
2654 }
2655
2656 fn selections_did_change(
2657 &mut self,
2658 local: bool,
2659 old_cursor_position: &Anchor,
2660 should_update_completions: bool,
2661 window: &mut Window,
2662 cx: &mut Context<Self>,
2663 ) {
2664 window.invalidate_character_coordinates();
2665
2666 // Copy selections to primary selection buffer
2667 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2668 if local {
2669 let selections = self.selections.all::<usize>(cx);
2670 let buffer_handle = self.buffer.read(cx).read(cx);
2671
2672 let mut text = String::new();
2673 for (index, selection) in selections.iter().enumerate() {
2674 let text_for_selection = buffer_handle
2675 .text_for_range(selection.start..selection.end)
2676 .collect::<String>();
2677
2678 text.push_str(&text_for_selection);
2679 if index != selections.len() - 1 {
2680 text.push('\n');
2681 }
2682 }
2683
2684 if !text.is_empty() {
2685 cx.write_to_primary(ClipboardItem::new_string(text));
2686 }
2687 }
2688
2689 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2690 self.buffer.update(cx, |buffer, cx| {
2691 buffer.set_active_selections(
2692 &self.selections.disjoint_anchors(),
2693 self.selections.line_mode,
2694 self.cursor_shape,
2695 cx,
2696 )
2697 });
2698 }
2699 let display_map = self
2700 .display_map
2701 .update(cx, |display_map, cx| display_map.snapshot(cx));
2702 let buffer = &display_map.buffer_snapshot;
2703 self.add_selections_state = None;
2704 self.select_next_state = None;
2705 self.select_prev_state = None;
2706 self.select_syntax_node_history.try_clear();
2707 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2708 self.snippet_stack
2709 .invalidate(&self.selections.disjoint_anchors(), buffer);
2710 self.take_rename(false, window, cx);
2711
2712 let new_cursor_position = self.selections.newest_anchor().head();
2713
2714 self.push_to_nav_history(
2715 *old_cursor_position,
2716 Some(new_cursor_position.to_point(buffer)),
2717 false,
2718 cx,
2719 );
2720
2721 if local {
2722 let new_cursor_position = self.selections.newest_anchor().head();
2723
2724 if let Some(buffer_id) = new_cursor_position.buffer_id {
2725 if !self.registered_buffers.contains_key(&buffer_id) {
2726 if let Some(project) = self.project.as_ref() {
2727 project.update(cx, |project, cx| {
2728 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2729 return;
2730 };
2731 self.registered_buffers.insert(
2732 buffer_id,
2733 project.register_buffer_with_language_servers(&buffer, cx),
2734 );
2735 })
2736 }
2737 }
2738 }
2739
2740 let mut context_menu = self.context_menu.borrow_mut();
2741 let completion_menu = match context_menu.as_ref() {
2742 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2743 Some(CodeContextMenu::CodeActions(_)) => {
2744 *context_menu = None;
2745 None
2746 }
2747 None => None,
2748 };
2749 let completion_position = completion_menu.map(|menu| menu.initial_position);
2750 drop(context_menu);
2751
2752 if should_update_completions {
2753 if let Some(completion_position) = completion_position {
2754 let new_cursor_offset = new_cursor_position.to_offset(buffer);
2755 let position_matches =
2756 new_cursor_offset == completion_position.to_offset(buffer);
2757 let continue_showing = if position_matches {
2758 let (word_range, kind) = buffer.surrounding_word(new_cursor_offset, true);
2759 if let Some(CharKind::Word) = kind {
2760 word_range.start < new_cursor_offset
2761 } else {
2762 false
2763 }
2764 } else {
2765 false
2766 };
2767
2768 if continue_showing {
2769 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2770 } else {
2771 self.hide_context_menu(window, cx);
2772 }
2773 }
2774 }
2775
2776 hide_hover(self, cx);
2777
2778 if old_cursor_position.to_display_point(&display_map).row()
2779 != new_cursor_position.to_display_point(&display_map).row()
2780 {
2781 self.available_code_actions.take();
2782 }
2783 self.refresh_code_actions(window, cx);
2784 self.refresh_document_highlights(cx);
2785 self.refresh_selected_text_highlights(false, window, cx);
2786 refresh_matching_bracket_highlights(self, window, cx);
2787 self.update_visible_inline_completion(window, cx);
2788 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2789 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2790 self.inline_blame_popover.take();
2791 if self.git_blame_inline_enabled {
2792 self.start_inline_blame_timer(window, cx);
2793 }
2794 }
2795
2796 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2797 cx.emit(EditorEvent::SelectionsChanged { local });
2798
2799 let selections = &self.selections.disjoint;
2800 if selections.len() == 1 {
2801 cx.emit(SearchEvent::ActiveMatchChanged)
2802 }
2803 if local {
2804 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2805 let inmemory_selections = selections
2806 .iter()
2807 .map(|s| {
2808 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2809 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2810 })
2811 .collect();
2812 self.update_restoration_data(cx, |data| {
2813 data.selections = inmemory_selections;
2814 });
2815
2816 if WorkspaceSettings::get(None, cx).restore_on_startup
2817 != RestoreOnStartupBehavior::None
2818 {
2819 if let Some(workspace_id) =
2820 self.workspace.as_ref().and_then(|workspace| workspace.1)
2821 {
2822 let snapshot = self.buffer().read(cx).snapshot(cx);
2823 let selections = selections.clone();
2824 let background_executor = cx.background_executor().clone();
2825 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2826 self.serialize_selections = cx.background_spawn(async move {
2827 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2828 let db_selections = selections
2829 .iter()
2830 .map(|selection| {
2831 (
2832 selection.start.to_offset(&snapshot),
2833 selection.end.to_offset(&snapshot),
2834 )
2835 })
2836 .collect();
2837
2838 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2839 .await
2840 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2841 .log_err();
2842 });
2843 }
2844 }
2845 }
2846 }
2847
2848 cx.notify();
2849 }
2850
2851 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2852 use text::ToOffset as _;
2853 use text::ToPoint as _;
2854
2855 if self.mode.is_minimap()
2856 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2857 {
2858 return;
2859 }
2860
2861 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2862 return;
2863 };
2864
2865 let snapshot = singleton.read(cx).snapshot();
2866 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2867 let display_snapshot = display_map.snapshot(cx);
2868
2869 display_snapshot
2870 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2871 .map(|fold| {
2872 fold.range.start.text_anchor.to_point(&snapshot)
2873 ..fold.range.end.text_anchor.to_point(&snapshot)
2874 })
2875 .collect()
2876 });
2877 self.update_restoration_data(cx, |data| {
2878 data.folds = inmemory_folds;
2879 });
2880
2881 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2882 return;
2883 };
2884 let background_executor = cx.background_executor().clone();
2885 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2886 let db_folds = self.display_map.update(cx, |display_map, cx| {
2887 display_map
2888 .snapshot(cx)
2889 .folds_in_range(0..snapshot.len())
2890 .map(|fold| {
2891 (
2892 fold.range.start.text_anchor.to_offset(&snapshot),
2893 fold.range.end.text_anchor.to_offset(&snapshot),
2894 )
2895 })
2896 .collect()
2897 });
2898 self.serialize_folds = cx.background_spawn(async move {
2899 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2900 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2901 .await
2902 .with_context(|| {
2903 format!(
2904 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2905 )
2906 })
2907 .log_err();
2908 });
2909 }
2910
2911 pub fn sync_selections(
2912 &mut self,
2913 other: Entity<Editor>,
2914 cx: &mut Context<Self>,
2915 ) -> gpui::Subscription {
2916 let other_selections = other.read(cx).selections.disjoint.to_vec();
2917 self.selections.change_with(cx, |selections| {
2918 selections.select_anchors(other_selections);
2919 });
2920
2921 let other_subscription =
2922 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2923 EditorEvent::SelectionsChanged { local: true } => {
2924 let other_selections = other.read(cx).selections.disjoint.to_vec();
2925 if other_selections.is_empty() {
2926 return;
2927 }
2928 this.selections.change_with(cx, |selections| {
2929 selections.select_anchors(other_selections);
2930 });
2931 }
2932 _ => {}
2933 });
2934
2935 let this_subscription =
2936 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2937 EditorEvent::SelectionsChanged { local: true } => {
2938 let these_selections = this.selections.disjoint.to_vec();
2939 if these_selections.is_empty() {
2940 return;
2941 }
2942 other.update(cx, |other_editor, cx| {
2943 other_editor.selections.change_with(cx, |selections| {
2944 selections.select_anchors(these_selections);
2945 })
2946 });
2947 }
2948 _ => {}
2949 });
2950
2951 Subscription::join(other_subscription, this_subscription)
2952 }
2953
2954 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
2955 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
2956 /// effects of selection change occur at the end of the transaction.
2957 pub fn change_selections<R>(
2958 &mut self,
2959 autoscroll: Option<Autoscroll>,
2960 window: &mut Window,
2961 cx: &mut Context<Self>,
2962 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2963 ) -> R {
2964 self.change_selections_inner(true, autoscroll, window, cx, change)
2965 }
2966
2967 pub(crate) fn change_selections_without_updating_completions<R>(
2968 &mut self,
2969 autoscroll: Option<Autoscroll>,
2970 window: &mut Window,
2971 cx: &mut Context<Self>,
2972 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2973 ) -> R {
2974 self.change_selections_inner(false, autoscroll, window, cx, change)
2975 }
2976
2977 fn change_selections_inner<R>(
2978 &mut self,
2979 should_update_completions: bool,
2980 autoscroll: Option<Autoscroll>,
2981 window: &mut Window,
2982 cx: &mut Context<Self>,
2983 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2984 ) -> R {
2985 if let Some(state) = &mut self.deferred_selection_effects_state {
2986 state.autoscroll = autoscroll.or(state.autoscroll);
2987 state.should_update_completions = should_update_completions;
2988 let (changed, result) = self.selections.change_with(cx, change);
2989 state.changed |= changed;
2990 return result;
2991 }
2992 let mut state = DeferredSelectionEffectsState {
2993 changed: false,
2994 should_update_completions,
2995 autoscroll,
2996 old_cursor_position: self.selections.newest_anchor().head(),
2997 history_entry: SelectionHistoryEntry {
2998 selections: self.selections.disjoint_anchors(),
2999 select_next_state: self.select_next_state.clone(),
3000 select_prev_state: self.select_prev_state.clone(),
3001 add_selections_state: self.add_selections_state.clone(),
3002 },
3003 };
3004 let (changed, result) = self.selections.change_with(cx, change);
3005 state.changed = state.changed || changed;
3006 if self.defer_selection_effects {
3007 self.deferred_selection_effects_state = Some(state);
3008 } else {
3009 self.apply_selection_effects(state, window, cx);
3010 }
3011 result
3012 }
3013
3014 /// Defers the effects of selection change, so that the effects of multiple calls to
3015 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3016 /// to selection history and the state of popovers based on selection position aren't
3017 /// erroneously updated.
3018 pub fn with_selection_effects_deferred<R>(
3019 &mut self,
3020 window: &mut Window,
3021 cx: &mut Context<Self>,
3022 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3023 ) -> R {
3024 let already_deferred = self.defer_selection_effects;
3025 self.defer_selection_effects = true;
3026 let result = update(self, window, cx);
3027 if !already_deferred {
3028 self.defer_selection_effects = false;
3029 if let Some(state) = self.deferred_selection_effects_state.take() {
3030 self.apply_selection_effects(state, window, cx);
3031 }
3032 }
3033 result
3034 }
3035
3036 fn apply_selection_effects(
3037 &mut self,
3038 state: DeferredSelectionEffectsState,
3039 window: &mut Window,
3040 cx: &mut Context<Self>,
3041 ) {
3042 if state.changed {
3043 self.selection_history.push(state.history_entry);
3044
3045 if let Some(autoscroll) = state.autoscroll {
3046 self.request_autoscroll(autoscroll, cx);
3047 }
3048
3049 let old_cursor_position = &state.old_cursor_position;
3050
3051 self.selections_did_change(
3052 true,
3053 &old_cursor_position,
3054 state.should_update_completions,
3055 window,
3056 cx,
3057 );
3058
3059 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3060 self.show_signature_help(&ShowSignatureHelp, window, cx);
3061 }
3062 }
3063 }
3064
3065 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3066 where
3067 I: IntoIterator<Item = (Range<S>, T)>,
3068 S: ToOffset,
3069 T: Into<Arc<str>>,
3070 {
3071 if self.read_only(cx) {
3072 return;
3073 }
3074
3075 self.buffer
3076 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3077 }
3078
3079 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3080 where
3081 I: IntoIterator<Item = (Range<S>, T)>,
3082 S: ToOffset,
3083 T: Into<Arc<str>>,
3084 {
3085 if self.read_only(cx) {
3086 return;
3087 }
3088
3089 self.buffer.update(cx, |buffer, cx| {
3090 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3091 });
3092 }
3093
3094 pub fn edit_with_block_indent<I, S, T>(
3095 &mut self,
3096 edits: I,
3097 original_indent_columns: Vec<Option<u32>>,
3098 cx: &mut Context<Self>,
3099 ) where
3100 I: IntoIterator<Item = (Range<S>, T)>,
3101 S: ToOffset,
3102 T: Into<Arc<str>>,
3103 {
3104 if self.read_only(cx) {
3105 return;
3106 }
3107
3108 self.buffer.update(cx, |buffer, cx| {
3109 buffer.edit(
3110 edits,
3111 Some(AutoindentMode::Block {
3112 original_indent_columns,
3113 }),
3114 cx,
3115 )
3116 });
3117 }
3118
3119 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3120 self.hide_context_menu(window, cx);
3121
3122 match phase {
3123 SelectPhase::Begin {
3124 position,
3125 add,
3126 click_count,
3127 } => self.begin_selection(position, add, click_count, window, cx),
3128 SelectPhase::BeginColumnar {
3129 position,
3130 goal_column,
3131 reset,
3132 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3133 SelectPhase::Extend {
3134 position,
3135 click_count,
3136 } => self.extend_selection(position, click_count, window, cx),
3137 SelectPhase::Update {
3138 position,
3139 goal_column,
3140 scroll_delta,
3141 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3142 SelectPhase::End => self.end_selection(window, cx),
3143 }
3144 }
3145
3146 fn extend_selection(
3147 &mut self,
3148 position: DisplayPoint,
3149 click_count: usize,
3150 window: &mut Window,
3151 cx: &mut Context<Self>,
3152 ) {
3153 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3154 let tail = self.selections.newest::<usize>(cx).tail();
3155 self.begin_selection(position, false, click_count, window, cx);
3156
3157 let position = position.to_offset(&display_map, Bias::Left);
3158 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3159
3160 let mut pending_selection = self
3161 .selections
3162 .pending_anchor()
3163 .expect("extend_selection not called with pending selection");
3164 if position >= tail {
3165 pending_selection.start = tail_anchor;
3166 } else {
3167 pending_selection.end = tail_anchor;
3168 pending_selection.reversed = true;
3169 }
3170
3171 let mut pending_mode = self.selections.pending_mode().unwrap();
3172 match &mut pending_mode {
3173 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3174 _ => {}
3175 }
3176
3177 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3178
3179 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3180 s.set_pending(pending_selection, pending_mode)
3181 });
3182 }
3183
3184 fn begin_selection(
3185 &mut self,
3186 position: DisplayPoint,
3187 add: bool,
3188 click_count: usize,
3189 window: &mut Window,
3190 cx: &mut Context<Self>,
3191 ) {
3192 if !self.focus_handle.is_focused(window) {
3193 self.last_focused_descendant = None;
3194 window.focus(&self.focus_handle);
3195 }
3196
3197 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3198 let buffer = &display_map.buffer_snapshot;
3199 let position = display_map.clip_point(position, Bias::Left);
3200
3201 let start;
3202 let end;
3203 let mode;
3204 let mut auto_scroll;
3205 match click_count {
3206 1 => {
3207 start = buffer.anchor_before(position.to_point(&display_map));
3208 end = start;
3209 mode = SelectMode::Character;
3210 auto_scroll = true;
3211 }
3212 2 => {
3213 let range = movement::surrounding_word(&display_map, position);
3214 start = buffer.anchor_before(range.start.to_point(&display_map));
3215 end = buffer.anchor_before(range.end.to_point(&display_map));
3216 mode = SelectMode::Word(start..end);
3217 auto_scroll = true;
3218 }
3219 3 => {
3220 let position = display_map
3221 .clip_point(position, Bias::Left)
3222 .to_point(&display_map);
3223 let line_start = display_map.prev_line_boundary(position).0;
3224 let next_line_start = buffer.clip_point(
3225 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3226 Bias::Left,
3227 );
3228 start = buffer.anchor_before(line_start);
3229 end = buffer.anchor_before(next_line_start);
3230 mode = SelectMode::Line(start..end);
3231 auto_scroll = true;
3232 }
3233 _ => {
3234 start = buffer.anchor_before(0);
3235 end = buffer.anchor_before(buffer.len());
3236 mode = SelectMode::All;
3237 auto_scroll = false;
3238 }
3239 }
3240 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3241
3242 let point_to_delete: Option<usize> = {
3243 let selected_points: Vec<Selection<Point>> =
3244 self.selections.disjoint_in_range(start..end, cx);
3245
3246 if !add || click_count > 1 {
3247 None
3248 } else if !selected_points.is_empty() {
3249 Some(selected_points[0].id)
3250 } else {
3251 let clicked_point_already_selected =
3252 self.selections.disjoint.iter().find(|selection| {
3253 selection.start.to_point(buffer) == start.to_point(buffer)
3254 || selection.end.to_point(buffer) == end.to_point(buffer)
3255 });
3256
3257 clicked_point_already_selected.map(|selection| selection.id)
3258 }
3259 };
3260
3261 let selections_count = self.selections.count();
3262
3263 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3264 if let Some(point_to_delete) = point_to_delete {
3265 s.delete(point_to_delete);
3266
3267 if selections_count == 1 {
3268 s.set_pending_anchor_range(start..end, mode);
3269 }
3270 } else {
3271 if !add {
3272 s.clear_disjoint();
3273 }
3274
3275 s.set_pending_anchor_range(start..end, mode);
3276 }
3277 });
3278 }
3279
3280 fn begin_columnar_selection(
3281 &mut self,
3282 position: DisplayPoint,
3283 goal_column: u32,
3284 reset: bool,
3285 window: &mut Window,
3286 cx: &mut Context<Self>,
3287 ) {
3288 if !self.focus_handle.is_focused(window) {
3289 self.last_focused_descendant = None;
3290 window.focus(&self.focus_handle);
3291 }
3292
3293 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3294
3295 if reset {
3296 let pointer_position = display_map
3297 .buffer_snapshot
3298 .anchor_before(position.to_point(&display_map));
3299
3300 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3301 s.clear_disjoint();
3302 s.set_pending_anchor_range(
3303 pointer_position..pointer_position,
3304 SelectMode::Character,
3305 );
3306 });
3307 if position.column() != goal_column {
3308 self.columnar_display_point = Some(DisplayPoint::new(position.row(), goal_column));
3309 } else {
3310 self.columnar_display_point = None;
3311 }
3312 }
3313
3314 let tail = self.selections.newest::<Point>(cx).tail();
3315 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3316
3317 if !reset {
3318 self.columnar_display_point = None;
3319 self.select_columns(
3320 tail.to_display_point(&display_map),
3321 position,
3322 goal_column,
3323 &display_map,
3324 window,
3325 cx,
3326 );
3327 }
3328 }
3329
3330 fn update_selection(
3331 &mut self,
3332 position: DisplayPoint,
3333 goal_column: u32,
3334 scroll_delta: gpui::Point<f32>,
3335 window: &mut Window,
3336 cx: &mut Context<Self>,
3337 ) {
3338 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3339
3340 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3341 let tail = self
3342 .columnar_display_point
3343 .unwrap_or_else(|| tail.to_display_point(&display_map));
3344 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3345 } else if let Some(mut pending) = self.selections.pending_anchor() {
3346 let buffer = self.buffer.read(cx).snapshot(cx);
3347 let head;
3348 let tail;
3349 let mode = self.selections.pending_mode().unwrap();
3350 match &mode {
3351 SelectMode::Character => {
3352 head = position.to_point(&display_map);
3353 tail = pending.tail().to_point(&buffer);
3354 }
3355 SelectMode::Word(original_range) => {
3356 let original_display_range = original_range.start.to_display_point(&display_map)
3357 ..original_range.end.to_display_point(&display_map);
3358 let original_buffer_range = original_display_range.start.to_point(&display_map)
3359 ..original_display_range.end.to_point(&display_map);
3360 if movement::is_inside_word(&display_map, position)
3361 || original_display_range.contains(&position)
3362 {
3363 let word_range = movement::surrounding_word(&display_map, position);
3364 if word_range.start < original_display_range.start {
3365 head = word_range.start.to_point(&display_map);
3366 } else {
3367 head = word_range.end.to_point(&display_map);
3368 }
3369 } else {
3370 head = position.to_point(&display_map);
3371 }
3372
3373 if head <= original_buffer_range.start {
3374 tail = original_buffer_range.end;
3375 } else {
3376 tail = original_buffer_range.start;
3377 }
3378 }
3379 SelectMode::Line(original_range) => {
3380 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3381
3382 let position = display_map
3383 .clip_point(position, Bias::Left)
3384 .to_point(&display_map);
3385 let line_start = display_map.prev_line_boundary(position).0;
3386 let next_line_start = buffer.clip_point(
3387 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3388 Bias::Left,
3389 );
3390
3391 if line_start < original_range.start {
3392 head = line_start
3393 } else {
3394 head = next_line_start
3395 }
3396
3397 if head <= original_range.start {
3398 tail = original_range.end;
3399 } else {
3400 tail = original_range.start;
3401 }
3402 }
3403 SelectMode::All => {
3404 return;
3405 }
3406 };
3407
3408 if head < tail {
3409 pending.start = buffer.anchor_before(head);
3410 pending.end = buffer.anchor_before(tail);
3411 pending.reversed = true;
3412 } else {
3413 pending.start = buffer.anchor_before(tail);
3414 pending.end = buffer.anchor_before(head);
3415 pending.reversed = false;
3416 }
3417
3418 self.change_selections(None, window, cx, |s| {
3419 s.set_pending(pending, mode);
3420 });
3421 } else {
3422 log::error!("update_selection dispatched with no pending selection");
3423 return;
3424 }
3425
3426 self.apply_scroll_delta(scroll_delta, window, cx);
3427 cx.notify();
3428 }
3429
3430 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3431 self.columnar_selection_tail.take();
3432 if self.selections.pending_anchor().is_some() {
3433 let selections = self.selections.all::<usize>(cx);
3434 self.change_selections(None, window, cx, |s| {
3435 s.select(selections);
3436 s.clear_pending();
3437 });
3438 }
3439 }
3440
3441 fn select_columns(
3442 &mut self,
3443 tail: DisplayPoint,
3444 head: DisplayPoint,
3445 goal_column: u32,
3446 display_map: &DisplaySnapshot,
3447 window: &mut Window,
3448 cx: &mut Context<Self>,
3449 ) {
3450 let start_row = cmp::min(tail.row(), head.row());
3451 let end_row = cmp::max(tail.row(), head.row());
3452 let start_column = cmp::min(tail.column(), goal_column);
3453 let end_column = cmp::max(tail.column(), goal_column);
3454 let reversed = start_column < tail.column();
3455
3456 let selection_ranges = (start_row.0..=end_row.0)
3457 .map(DisplayRow)
3458 .filter_map(|row| {
3459 if !display_map.is_block_line(row) {
3460 let start = display_map
3461 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3462 .to_point(display_map);
3463 let end = display_map
3464 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3465 .to_point(display_map);
3466 if reversed {
3467 Some(end..start)
3468 } else {
3469 Some(start..end)
3470 }
3471 } else {
3472 None
3473 }
3474 })
3475 .collect::<Vec<_>>();
3476
3477 let mut non_empty_ranges = selection_ranges
3478 .iter()
3479 .filter(|selection_range| selection_range.start != selection_range.end)
3480 .peekable();
3481
3482 let ranges = if non_empty_ranges.peek().is_some() {
3483 non_empty_ranges.cloned().collect()
3484 } else {
3485 selection_ranges
3486 };
3487
3488 self.change_selections(None, window, cx, |s| {
3489 s.select_ranges(ranges);
3490 });
3491 cx.notify();
3492 }
3493
3494 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3495 self.selections
3496 .all_adjusted(cx)
3497 .iter()
3498 .any(|selection| !selection.is_empty())
3499 }
3500
3501 pub fn has_pending_nonempty_selection(&self) -> bool {
3502 let pending_nonempty_selection = match self.selections.pending_anchor() {
3503 Some(Selection { start, end, .. }) => start != end,
3504 None => false,
3505 };
3506
3507 pending_nonempty_selection
3508 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3509 }
3510
3511 pub fn has_pending_selection(&self) -> bool {
3512 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3513 }
3514
3515 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3516 self.selection_mark_mode = false;
3517
3518 if self.clear_expanded_diff_hunks(cx) {
3519 cx.notify();
3520 return;
3521 }
3522 if self.dismiss_menus_and_popups(true, window, cx) {
3523 return;
3524 }
3525
3526 if self.mode.is_full()
3527 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3528 {
3529 return;
3530 }
3531
3532 cx.propagate();
3533 }
3534
3535 pub fn dismiss_menus_and_popups(
3536 &mut self,
3537 is_user_requested: bool,
3538 window: &mut Window,
3539 cx: &mut Context<Self>,
3540 ) -> bool {
3541 if self.take_rename(false, window, cx).is_some() {
3542 return true;
3543 }
3544
3545 if hide_hover(self, cx) {
3546 return true;
3547 }
3548
3549 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3550 return true;
3551 }
3552
3553 if self.hide_context_menu(window, cx).is_some() {
3554 return true;
3555 }
3556
3557 if self.mouse_context_menu.take().is_some() {
3558 return true;
3559 }
3560
3561 if is_user_requested && self.discard_inline_completion(true, cx) {
3562 return true;
3563 }
3564
3565 if self.snippet_stack.pop().is_some() {
3566 return true;
3567 }
3568
3569 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3570 self.dismiss_diagnostics(cx);
3571 return true;
3572 }
3573
3574 false
3575 }
3576
3577 fn linked_editing_ranges_for(
3578 &self,
3579 selection: Range<text::Anchor>,
3580 cx: &App,
3581 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3582 if self.linked_edit_ranges.is_empty() {
3583 return None;
3584 }
3585 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3586 selection.end.buffer_id.and_then(|end_buffer_id| {
3587 if selection.start.buffer_id != Some(end_buffer_id) {
3588 return None;
3589 }
3590 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3591 let snapshot = buffer.read(cx).snapshot();
3592 self.linked_edit_ranges
3593 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3594 .map(|ranges| (ranges, snapshot, buffer))
3595 })?;
3596 use text::ToOffset as TO;
3597 // find offset from the start of current range to current cursor position
3598 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3599
3600 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3601 let start_difference = start_offset - start_byte_offset;
3602 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3603 let end_difference = end_offset - start_byte_offset;
3604 // Current range has associated linked ranges.
3605 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3606 for range in linked_ranges.iter() {
3607 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3608 let end_offset = start_offset + end_difference;
3609 let start_offset = start_offset + start_difference;
3610 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3611 continue;
3612 }
3613 if self.selections.disjoint_anchor_ranges().any(|s| {
3614 if s.start.buffer_id != selection.start.buffer_id
3615 || s.end.buffer_id != selection.end.buffer_id
3616 {
3617 return false;
3618 }
3619 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3620 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3621 }) {
3622 continue;
3623 }
3624 let start = buffer_snapshot.anchor_after(start_offset);
3625 let end = buffer_snapshot.anchor_after(end_offset);
3626 linked_edits
3627 .entry(buffer.clone())
3628 .or_default()
3629 .push(start..end);
3630 }
3631 Some(linked_edits)
3632 }
3633
3634 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3635 let text: Arc<str> = text.into();
3636
3637 if self.read_only(cx) {
3638 return;
3639 }
3640
3641 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3642
3643 let selections = self.selections.all_adjusted(cx);
3644 let mut bracket_inserted = false;
3645 let mut edits = Vec::new();
3646 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3647 let mut new_selections = Vec::with_capacity(selections.len());
3648 let mut new_autoclose_regions = Vec::new();
3649 let snapshot = self.buffer.read(cx).read(cx);
3650 let mut clear_linked_edit_ranges = false;
3651
3652 for (selection, autoclose_region) in
3653 self.selections_with_autoclose_regions(selections, &snapshot)
3654 {
3655 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3656 // Determine if the inserted text matches the opening or closing
3657 // bracket of any of this language's bracket pairs.
3658 let mut bracket_pair = None;
3659 let mut is_bracket_pair_start = false;
3660 let mut is_bracket_pair_end = false;
3661 if !text.is_empty() {
3662 let mut bracket_pair_matching_end = None;
3663 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3664 // and they are removing the character that triggered IME popup.
3665 for (pair, enabled) in scope.brackets() {
3666 if !pair.close && !pair.surround {
3667 continue;
3668 }
3669
3670 if enabled && pair.start.ends_with(text.as_ref()) {
3671 let prefix_len = pair.start.len() - text.len();
3672 let preceding_text_matches_prefix = prefix_len == 0
3673 || (selection.start.column >= (prefix_len as u32)
3674 && snapshot.contains_str_at(
3675 Point::new(
3676 selection.start.row,
3677 selection.start.column - (prefix_len as u32),
3678 ),
3679 &pair.start[..prefix_len],
3680 ));
3681 if preceding_text_matches_prefix {
3682 bracket_pair = Some(pair.clone());
3683 is_bracket_pair_start = true;
3684 break;
3685 }
3686 }
3687 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3688 {
3689 // take first bracket pair matching end, but don't break in case a later bracket
3690 // pair matches start
3691 bracket_pair_matching_end = Some(pair.clone());
3692 }
3693 }
3694 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3695 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3696 is_bracket_pair_end = true;
3697 }
3698 }
3699
3700 if let Some(bracket_pair) = bracket_pair {
3701 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3702 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3703 let auto_surround =
3704 self.use_auto_surround && snapshot_settings.use_auto_surround;
3705 if selection.is_empty() {
3706 if is_bracket_pair_start {
3707 // If the inserted text is a suffix of an opening bracket and the
3708 // selection is preceded by the rest of the opening bracket, then
3709 // insert the closing bracket.
3710 let following_text_allows_autoclose = snapshot
3711 .chars_at(selection.start)
3712 .next()
3713 .map_or(true, |c| scope.should_autoclose_before(c));
3714
3715 let preceding_text_allows_autoclose = selection.start.column == 0
3716 || snapshot.reversed_chars_at(selection.start).next().map_or(
3717 true,
3718 |c| {
3719 bracket_pair.start != bracket_pair.end
3720 || !snapshot
3721 .char_classifier_at(selection.start)
3722 .is_word(c)
3723 },
3724 );
3725
3726 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3727 && bracket_pair.start.len() == 1
3728 {
3729 let target = bracket_pair.start.chars().next().unwrap();
3730 let current_line_count = snapshot
3731 .reversed_chars_at(selection.start)
3732 .take_while(|&c| c != '\n')
3733 .filter(|&c| c == target)
3734 .count();
3735 current_line_count % 2 == 1
3736 } else {
3737 false
3738 };
3739
3740 if autoclose
3741 && bracket_pair.close
3742 && following_text_allows_autoclose
3743 && preceding_text_allows_autoclose
3744 && !is_closing_quote
3745 {
3746 let anchor = snapshot.anchor_before(selection.end);
3747 new_selections.push((selection.map(|_| anchor), text.len()));
3748 new_autoclose_regions.push((
3749 anchor,
3750 text.len(),
3751 selection.id,
3752 bracket_pair.clone(),
3753 ));
3754 edits.push((
3755 selection.range(),
3756 format!("{}{}", text, bracket_pair.end).into(),
3757 ));
3758 bracket_inserted = true;
3759 continue;
3760 }
3761 }
3762
3763 if let Some(region) = autoclose_region {
3764 // If the selection is followed by an auto-inserted closing bracket,
3765 // then don't insert that closing bracket again; just move the selection
3766 // past the closing bracket.
3767 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3768 && text.as_ref() == region.pair.end.as_str();
3769 if should_skip {
3770 let anchor = snapshot.anchor_after(selection.end);
3771 new_selections
3772 .push((selection.map(|_| anchor), region.pair.end.len()));
3773 continue;
3774 }
3775 }
3776
3777 let always_treat_brackets_as_autoclosed = snapshot
3778 .language_settings_at(selection.start, cx)
3779 .always_treat_brackets_as_autoclosed;
3780 if always_treat_brackets_as_autoclosed
3781 && is_bracket_pair_end
3782 && snapshot.contains_str_at(selection.end, text.as_ref())
3783 {
3784 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3785 // and the inserted text is a closing bracket and the selection is followed
3786 // by the closing bracket then move the selection past the closing bracket.
3787 let anchor = snapshot.anchor_after(selection.end);
3788 new_selections.push((selection.map(|_| anchor), text.len()));
3789 continue;
3790 }
3791 }
3792 // If an opening bracket is 1 character long and is typed while
3793 // text is selected, then surround that text with the bracket pair.
3794 else if auto_surround
3795 && bracket_pair.surround
3796 && is_bracket_pair_start
3797 && bracket_pair.start.chars().count() == 1
3798 {
3799 edits.push((selection.start..selection.start, text.clone()));
3800 edits.push((
3801 selection.end..selection.end,
3802 bracket_pair.end.as_str().into(),
3803 ));
3804 bracket_inserted = true;
3805 new_selections.push((
3806 Selection {
3807 id: selection.id,
3808 start: snapshot.anchor_after(selection.start),
3809 end: snapshot.anchor_before(selection.end),
3810 reversed: selection.reversed,
3811 goal: selection.goal,
3812 },
3813 0,
3814 ));
3815 continue;
3816 }
3817 }
3818 }
3819
3820 if self.auto_replace_emoji_shortcode
3821 && selection.is_empty()
3822 && text.as_ref().ends_with(':')
3823 {
3824 if let Some(possible_emoji_short_code) =
3825 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3826 {
3827 if !possible_emoji_short_code.is_empty() {
3828 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3829 let emoji_shortcode_start = Point::new(
3830 selection.start.row,
3831 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3832 );
3833
3834 // Remove shortcode from buffer
3835 edits.push((
3836 emoji_shortcode_start..selection.start,
3837 "".to_string().into(),
3838 ));
3839 new_selections.push((
3840 Selection {
3841 id: selection.id,
3842 start: snapshot.anchor_after(emoji_shortcode_start),
3843 end: snapshot.anchor_before(selection.start),
3844 reversed: selection.reversed,
3845 goal: selection.goal,
3846 },
3847 0,
3848 ));
3849
3850 // Insert emoji
3851 let selection_start_anchor = snapshot.anchor_after(selection.start);
3852 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3853 edits.push((selection.start..selection.end, emoji.to_string().into()));
3854
3855 continue;
3856 }
3857 }
3858 }
3859 }
3860
3861 // If not handling any auto-close operation, then just replace the selected
3862 // text with the given input and move the selection to the end of the
3863 // newly inserted text.
3864 let anchor = snapshot.anchor_after(selection.end);
3865 if !self.linked_edit_ranges.is_empty() {
3866 let start_anchor = snapshot.anchor_before(selection.start);
3867
3868 let is_word_char = text.chars().next().map_or(true, |char| {
3869 let classifier = snapshot
3870 .char_classifier_at(start_anchor.to_offset(&snapshot))
3871 .ignore_punctuation(true);
3872 classifier.is_word(char)
3873 });
3874
3875 if is_word_char {
3876 if let Some(ranges) = self
3877 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3878 {
3879 for (buffer, edits) in ranges {
3880 linked_edits
3881 .entry(buffer.clone())
3882 .or_default()
3883 .extend(edits.into_iter().map(|range| (range, text.clone())));
3884 }
3885 }
3886 } else {
3887 clear_linked_edit_ranges = true;
3888 }
3889 }
3890
3891 new_selections.push((selection.map(|_| anchor), 0));
3892 edits.push((selection.start..selection.end, text.clone()));
3893 }
3894
3895 drop(snapshot);
3896
3897 self.transact(window, cx, |this, window, cx| {
3898 if clear_linked_edit_ranges {
3899 this.linked_edit_ranges.clear();
3900 }
3901 let initial_buffer_versions =
3902 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3903
3904 this.buffer.update(cx, |buffer, cx| {
3905 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3906 });
3907 for (buffer, edits) in linked_edits {
3908 buffer.update(cx, |buffer, cx| {
3909 let snapshot = buffer.snapshot();
3910 let edits = edits
3911 .into_iter()
3912 .map(|(range, text)| {
3913 use text::ToPoint as TP;
3914 let end_point = TP::to_point(&range.end, &snapshot);
3915 let start_point = TP::to_point(&range.start, &snapshot);
3916 (start_point..end_point, text)
3917 })
3918 .sorted_by_key(|(range, _)| range.start);
3919 buffer.edit(edits, None, cx);
3920 })
3921 }
3922 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3923 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3924 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3925 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3926 .zip(new_selection_deltas)
3927 .map(|(selection, delta)| Selection {
3928 id: selection.id,
3929 start: selection.start + delta,
3930 end: selection.end + delta,
3931 reversed: selection.reversed,
3932 goal: SelectionGoal::None,
3933 })
3934 .collect::<Vec<_>>();
3935
3936 let mut i = 0;
3937 for (position, delta, selection_id, pair) in new_autoclose_regions {
3938 let position = position.to_offset(&map.buffer_snapshot) + delta;
3939 let start = map.buffer_snapshot.anchor_before(position);
3940 let end = map.buffer_snapshot.anchor_after(position);
3941 while let Some(existing_state) = this.autoclose_regions.get(i) {
3942 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3943 Ordering::Less => i += 1,
3944 Ordering::Greater => break,
3945 Ordering::Equal => {
3946 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3947 Ordering::Less => i += 1,
3948 Ordering::Equal => break,
3949 Ordering::Greater => break,
3950 }
3951 }
3952 }
3953 }
3954 this.autoclose_regions.insert(
3955 i,
3956 AutocloseRegion {
3957 selection_id,
3958 range: start..end,
3959 pair,
3960 },
3961 );
3962 }
3963
3964 let had_active_inline_completion = this.has_active_inline_completion();
3965 this.change_selections_without_updating_completions(
3966 Some(Autoscroll::fit()),
3967 window,
3968 cx,
3969 |s| s.select(new_selections),
3970 );
3971
3972 if !bracket_inserted {
3973 if let Some(on_type_format_task) =
3974 this.trigger_on_type_formatting(text.to_string(), window, cx)
3975 {
3976 on_type_format_task.detach_and_log_err(cx);
3977 }
3978 }
3979
3980 let editor_settings = EditorSettings::get_global(cx);
3981 if bracket_inserted
3982 && (editor_settings.auto_signature_help
3983 || editor_settings.show_signature_help_after_edits)
3984 {
3985 this.show_signature_help(&ShowSignatureHelp, window, cx);
3986 }
3987
3988 let trigger_in_words =
3989 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3990 if this.hard_wrap.is_some() {
3991 let latest: Range<Point> = this.selections.newest(cx).range();
3992 if latest.is_empty()
3993 && this
3994 .buffer()
3995 .read(cx)
3996 .snapshot(cx)
3997 .line_len(MultiBufferRow(latest.start.row))
3998 == latest.start.column
3999 {
4000 this.rewrap_impl(
4001 RewrapOptions {
4002 override_language_settings: true,
4003 preserve_existing_whitespace: true,
4004 },
4005 cx,
4006 )
4007 }
4008 }
4009 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4010 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4011 this.refresh_inline_completion(true, false, window, cx);
4012 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4013 });
4014 }
4015
4016 fn find_possible_emoji_shortcode_at_position(
4017 snapshot: &MultiBufferSnapshot,
4018 position: Point,
4019 ) -> Option<String> {
4020 let mut chars = Vec::new();
4021 let mut found_colon = false;
4022 for char in snapshot.reversed_chars_at(position).take(100) {
4023 // Found a possible emoji shortcode in the middle of the buffer
4024 if found_colon {
4025 if char.is_whitespace() {
4026 chars.reverse();
4027 return Some(chars.iter().collect());
4028 }
4029 // If the previous character is not a whitespace, we are in the middle of a word
4030 // and we only want to complete the shortcode if the word is made up of other emojis
4031 let mut containing_word = String::new();
4032 for ch in snapshot
4033 .reversed_chars_at(position)
4034 .skip(chars.len() + 1)
4035 .take(100)
4036 {
4037 if ch.is_whitespace() {
4038 break;
4039 }
4040 containing_word.push(ch);
4041 }
4042 let containing_word = containing_word.chars().rev().collect::<String>();
4043 if util::word_consists_of_emojis(containing_word.as_str()) {
4044 chars.reverse();
4045 return Some(chars.iter().collect());
4046 }
4047 }
4048
4049 if char.is_whitespace() || !char.is_ascii() {
4050 return None;
4051 }
4052 if char == ':' {
4053 found_colon = true;
4054 } else {
4055 chars.push(char);
4056 }
4057 }
4058 // Found a possible emoji shortcode at the beginning of the buffer
4059 chars.reverse();
4060 Some(chars.iter().collect())
4061 }
4062
4063 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4064 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4065 self.transact(window, cx, |this, window, cx| {
4066 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4067 let selections = this.selections.all::<usize>(cx);
4068 let multi_buffer = this.buffer.read(cx);
4069 let buffer = multi_buffer.snapshot(cx);
4070 selections
4071 .iter()
4072 .map(|selection| {
4073 let start_point = selection.start.to_point(&buffer);
4074 let mut existing_indent =
4075 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4076 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4077 let start = selection.start;
4078 let end = selection.end;
4079 let selection_is_empty = start == end;
4080 let language_scope = buffer.language_scope_at(start);
4081 let (
4082 comment_delimiter,
4083 doc_delimiter,
4084 insert_extra_newline,
4085 indent_on_newline,
4086 indent_on_extra_newline,
4087 ) = if let Some(language) = &language_scope {
4088 let mut insert_extra_newline =
4089 insert_extra_newline_brackets(&buffer, start..end, language)
4090 || insert_extra_newline_tree_sitter(&buffer, start..end);
4091
4092 // Comment extension on newline is allowed only for cursor selections
4093 let comment_delimiter = maybe!({
4094 if !selection_is_empty {
4095 return None;
4096 }
4097
4098 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4099 return None;
4100 }
4101
4102 let delimiters = language.line_comment_prefixes();
4103 let max_len_of_delimiter =
4104 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4105 let (snapshot, range) =
4106 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4107
4108 let num_of_whitespaces = snapshot
4109 .chars_for_range(range.clone())
4110 .take_while(|c| c.is_whitespace())
4111 .count();
4112 let comment_candidate = snapshot
4113 .chars_for_range(range)
4114 .skip(num_of_whitespaces)
4115 .take(max_len_of_delimiter)
4116 .collect::<String>();
4117 let (delimiter, trimmed_len) = delimiters
4118 .iter()
4119 .filter_map(|delimiter| {
4120 let prefix = delimiter.trim_end();
4121 if comment_candidate.starts_with(prefix) {
4122 Some((delimiter, prefix.len()))
4123 } else {
4124 None
4125 }
4126 })
4127 .max_by_key(|(_, len)| *len)?;
4128
4129 let cursor_is_placed_after_comment_marker =
4130 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4131 if cursor_is_placed_after_comment_marker {
4132 Some(delimiter.clone())
4133 } else {
4134 None
4135 }
4136 });
4137
4138 let mut indent_on_newline = IndentSize::spaces(0);
4139 let mut indent_on_extra_newline = IndentSize::spaces(0);
4140
4141 let doc_delimiter = maybe!({
4142 if !selection_is_empty {
4143 return None;
4144 }
4145
4146 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4147 return None;
4148 }
4149
4150 let DocumentationConfig {
4151 start: start_tag,
4152 end: end_tag,
4153 prefix: delimiter,
4154 tab_size: len,
4155 } = language.documentation()?;
4156
4157 let is_within_block_comment = buffer
4158 .language_scope_at(start_point)
4159 .is_some_and(|scope| scope.override_name() == Some("comment"));
4160 if !is_within_block_comment {
4161 return None;
4162 }
4163
4164 let (snapshot, range) =
4165 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4166
4167 let num_of_whitespaces = snapshot
4168 .chars_for_range(range.clone())
4169 .take_while(|c| c.is_whitespace())
4170 .count();
4171
4172 // 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.
4173 let column = start_point.column;
4174 let cursor_is_after_start_tag = {
4175 let start_tag_len = start_tag.len();
4176 let start_tag_line = snapshot
4177 .chars_for_range(range.clone())
4178 .skip(num_of_whitespaces)
4179 .take(start_tag_len)
4180 .collect::<String>();
4181 if start_tag_line.starts_with(start_tag.as_ref()) {
4182 num_of_whitespaces + start_tag_len <= column as usize
4183 } else {
4184 false
4185 }
4186 };
4187
4188 let cursor_is_after_delimiter = {
4189 let delimiter_trim = delimiter.trim_end();
4190 let delimiter_line = snapshot
4191 .chars_for_range(range.clone())
4192 .skip(num_of_whitespaces)
4193 .take(delimiter_trim.len())
4194 .collect::<String>();
4195 if delimiter_line.starts_with(delimiter_trim) {
4196 num_of_whitespaces + delimiter_trim.len() <= column as usize
4197 } else {
4198 false
4199 }
4200 };
4201
4202 let cursor_is_before_end_tag_if_exists = {
4203 let mut char_position = 0u32;
4204 let mut end_tag_offset = None;
4205
4206 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4207 if let Some(byte_pos) = chunk.find(&**end_tag) {
4208 let chars_before_match =
4209 chunk[..byte_pos].chars().count() as u32;
4210 end_tag_offset =
4211 Some(char_position + chars_before_match);
4212 break 'outer;
4213 }
4214 char_position += chunk.chars().count() as u32;
4215 }
4216
4217 if let Some(end_tag_offset) = end_tag_offset {
4218 let cursor_is_before_end_tag = column <= end_tag_offset;
4219 if cursor_is_after_start_tag {
4220 if cursor_is_before_end_tag {
4221 insert_extra_newline = true;
4222 }
4223 let cursor_is_at_start_of_end_tag =
4224 column == end_tag_offset;
4225 if cursor_is_at_start_of_end_tag {
4226 indent_on_extra_newline.len = (*len).into();
4227 }
4228 }
4229 cursor_is_before_end_tag
4230 } else {
4231 true
4232 }
4233 };
4234
4235 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4236 && cursor_is_before_end_tag_if_exists
4237 {
4238 if cursor_is_after_start_tag {
4239 indent_on_newline.len = (*len).into();
4240 }
4241 Some(delimiter.clone())
4242 } else {
4243 None
4244 }
4245 });
4246
4247 (
4248 comment_delimiter,
4249 doc_delimiter,
4250 insert_extra_newline,
4251 indent_on_newline,
4252 indent_on_extra_newline,
4253 )
4254 } else {
4255 (
4256 None,
4257 None,
4258 false,
4259 IndentSize::default(),
4260 IndentSize::default(),
4261 )
4262 };
4263
4264 let prevent_auto_indent = doc_delimiter.is_some();
4265 let delimiter = comment_delimiter.or(doc_delimiter);
4266
4267 let capacity_for_delimiter =
4268 delimiter.as_deref().map(str::len).unwrap_or_default();
4269 let mut new_text = String::with_capacity(
4270 1 + capacity_for_delimiter
4271 + existing_indent.len as usize
4272 + indent_on_newline.len as usize
4273 + indent_on_extra_newline.len as usize,
4274 );
4275 new_text.push('\n');
4276 new_text.extend(existing_indent.chars());
4277 new_text.extend(indent_on_newline.chars());
4278
4279 if let Some(delimiter) = &delimiter {
4280 new_text.push_str(delimiter);
4281 }
4282
4283 if insert_extra_newline {
4284 new_text.push('\n');
4285 new_text.extend(existing_indent.chars());
4286 new_text.extend(indent_on_extra_newline.chars());
4287 }
4288
4289 let anchor = buffer.anchor_after(end);
4290 let new_selection = selection.map(|_| anchor);
4291 (
4292 ((start..end, new_text), prevent_auto_indent),
4293 (insert_extra_newline, new_selection),
4294 )
4295 })
4296 .unzip()
4297 };
4298
4299 let mut auto_indent_edits = Vec::new();
4300 let mut edits = Vec::new();
4301 for (edit, prevent_auto_indent) in edits_with_flags {
4302 if prevent_auto_indent {
4303 edits.push(edit);
4304 } else {
4305 auto_indent_edits.push(edit);
4306 }
4307 }
4308 if !edits.is_empty() {
4309 this.edit(edits, cx);
4310 }
4311 if !auto_indent_edits.is_empty() {
4312 this.edit_with_autoindent(auto_indent_edits, cx);
4313 }
4314
4315 let buffer = this.buffer.read(cx).snapshot(cx);
4316 let new_selections = selection_info
4317 .into_iter()
4318 .map(|(extra_newline_inserted, new_selection)| {
4319 let mut cursor = new_selection.end.to_point(&buffer);
4320 if extra_newline_inserted {
4321 cursor.row -= 1;
4322 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4323 }
4324 new_selection.map(|_| cursor)
4325 })
4326 .collect();
4327
4328 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4329 s.select(new_selections)
4330 });
4331 this.refresh_inline_completion(true, false, window, cx);
4332 });
4333 }
4334
4335 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4336 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4337
4338 let buffer = self.buffer.read(cx);
4339 let snapshot = buffer.snapshot(cx);
4340
4341 let mut edits = Vec::new();
4342 let mut rows = Vec::new();
4343
4344 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4345 let cursor = selection.head();
4346 let row = cursor.row;
4347
4348 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4349
4350 let newline = "\n".to_string();
4351 edits.push((start_of_line..start_of_line, newline));
4352
4353 rows.push(row + rows_inserted as u32);
4354 }
4355
4356 self.transact(window, cx, |editor, window, cx| {
4357 editor.edit(edits, cx);
4358
4359 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4360 let mut index = 0;
4361 s.move_cursors_with(|map, _, _| {
4362 let row = rows[index];
4363 index += 1;
4364
4365 let point = Point::new(row, 0);
4366 let boundary = map.next_line_boundary(point).1;
4367 let clipped = map.clip_point(boundary, Bias::Left);
4368
4369 (clipped, SelectionGoal::None)
4370 });
4371 });
4372
4373 let mut indent_edits = Vec::new();
4374 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4375 for row in rows {
4376 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4377 for (row, indent) in indents {
4378 if indent.len == 0 {
4379 continue;
4380 }
4381
4382 let text = match indent.kind {
4383 IndentKind::Space => " ".repeat(indent.len as usize),
4384 IndentKind::Tab => "\t".repeat(indent.len as usize),
4385 };
4386 let point = Point::new(row.0, 0);
4387 indent_edits.push((point..point, text));
4388 }
4389 }
4390 editor.edit(indent_edits, cx);
4391 });
4392 }
4393
4394 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4395 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4396
4397 let buffer = self.buffer.read(cx);
4398 let snapshot = buffer.snapshot(cx);
4399
4400 let mut edits = Vec::new();
4401 let mut rows = Vec::new();
4402 let mut rows_inserted = 0;
4403
4404 for selection in self.selections.all_adjusted(cx) {
4405 let cursor = selection.head();
4406 let row = cursor.row;
4407
4408 let point = Point::new(row + 1, 0);
4409 let start_of_line = snapshot.clip_point(point, Bias::Left);
4410
4411 let newline = "\n".to_string();
4412 edits.push((start_of_line..start_of_line, newline));
4413
4414 rows_inserted += 1;
4415 rows.push(row + rows_inserted);
4416 }
4417
4418 self.transact(window, cx, |editor, window, cx| {
4419 editor.edit(edits, cx);
4420
4421 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4422 let mut index = 0;
4423 s.move_cursors_with(|map, _, _| {
4424 let row = rows[index];
4425 index += 1;
4426
4427 let point = Point::new(row, 0);
4428 let boundary = map.next_line_boundary(point).1;
4429 let clipped = map.clip_point(boundary, Bias::Left);
4430
4431 (clipped, SelectionGoal::None)
4432 });
4433 });
4434
4435 let mut indent_edits = Vec::new();
4436 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4437 for row in rows {
4438 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4439 for (row, indent) in indents {
4440 if indent.len == 0 {
4441 continue;
4442 }
4443
4444 let text = match indent.kind {
4445 IndentKind::Space => " ".repeat(indent.len as usize),
4446 IndentKind::Tab => "\t".repeat(indent.len as usize),
4447 };
4448 let point = Point::new(row.0, 0);
4449 indent_edits.push((point..point, text));
4450 }
4451 }
4452 editor.edit(indent_edits, cx);
4453 });
4454 }
4455
4456 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4457 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4458 original_indent_columns: Vec::new(),
4459 });
4460 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4461 }
4462
4463 fn insert_with_autoindent_mode(
4464 &mut self,
4465 text: &str,
4466 autoindent_mode: Option<AutoindentMode>,
4467 window: &mut Window,
4468 cx: &mut Context<Self>,
4469 ) {
4470 if self.read_only(cx) {
4471 return;
4472 }
4473
4474 let text: Arc<str> = text.into();
4475 self.transact(window, cx, |this, window, cx| {
4476 let old_selections = this.selections.all_adjusted(cx);
4477 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4478 let anchors = {
4479 let snapshot = buffer.read(cx);
4480 old_selections
4481 .iter()
4482 .map(|s| {
4483 let anchor = snapshot.anchor_after(s.head());
4484 s.map(|_| anchor)
4485 })
4486 .collect::<Vec<_>>()
4487 };
4488 buffer.edit(
4489 old_selections
4490 .iter()
4491 .map(|s| (s.start..s.end, text.clone())),
4492 autoindent_mode,
4493 cx,
4494 );
4495 anchors
4496 });
4497
4498 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4499 s.select_anchors(selection_anchors);
4500 });
4501
4502 cx.notify();
4503 });
4504 }
4505
4506 fn trigger_completion_on_input(
4507 &mut self,
4508 text: &str,
4509 trigger_in_words: bool,
4510 window: &mut Window,
4511 cx: &mut Context<Self>,
4512 ) {
4513 let ignore_completion_provider = self
4514 .context_menu
4515 .borrow()
4516 .as_ref()
4517 .map(|menu| match menu {
4518 CodeContextMenu::Completions(completions_menu) => {
4519 completions_menu.ignore_completion_provider
4520 }
4521 CodeContextMenu::CodeActions(_) => false,
4522 })
4523 .unwrap_or(false);
4524
4525 if ignore_completion_provider {
4526 self.show_word_completions(&ShowWordCompletions, window, cx);
4527 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
4528 self.show_completions(
4529 &ShowCompletions {
4530 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4531 },
4532 window,
4533 cx,
4534 );
4535 } else {
4536 self.hide_context_menu(window, cx);
4537 }
4538 }
4539
4540 fn is_completion_trigger(
4541 &self,
4542 text: &str,
4543 trigger_in_words: bool,
4544 cx: &mut Context<Self>,
4545 ) -> bool {
4546 let position = self.selections.newest_anchor().head();
4547 let multibuffer = self.buffer.read(cx);
4548 let Some(buffer) = position
4549 .buffer_id
4550 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4551 else {
4552 return false;
4553 };
4554
4555 if let Some(completion_provider) = &self.completion_provider {
4556 completion_provider.is_completion_trigger(
4557 &buffer,
4558 position.text_anchor,
4559 text,
4560 trigger_in_words,
4561 cx,
4562 )
4563 } else {
4564 false
4565 }
4566 }
4567
4568 /// If any empty selections is touching the start of its innermost containing autoclose
4569 /// region, expand it to select the brackets.
4570 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4571 let selections = self.selections.all::<usize>(cx);
4572 let buffer = self.buffer.read(cx).read(cx);
4573 let new_selections = self
4574 .selections_with_autoclose_regions(selections, &buffer)
4575 .map(|(mut selection, region)| {
4576 if !selection.is_empty() {
4577 return selection;
4578 }
4579
4580 if let Some(region) = region {
4581 let mut range = region.range.to_offset(&buffer);
4582 if selection.start == range.start && range.start >= region.pair.start.len() {
4583 range.start -= region.pair.start.len();
4584 if buffer.contains_str_at(range.start, ®ion.pair.start)
4585 && buffer.contains_str_at(range.end, ®ion.pair.end)
4586 {
4587 range.end += region.pair.end.len();
4588 selection.start = range.start;
4589 selection.end = range.end;
4590
4591 return selection;
4592 }
4593 }
4594 }
4595
4596 let always_treat_brackets_as_autoclosed = buffer
4597 .language_settings_at(selection.start, cx)
4598 .always_treat_brackets_as_autoclosed;
4599
4600 if !always_treat_brackets_as_autoclosed {
4601 return selection;
4602 }
4603
4604 if let Some(scope) = buffer.language_scope_at(selection.start) {
4605 for (pair, enabled) in scope.brackets() {
4606 if !enabled || !pair.close {
4607 continue;
4608 }
4609
4610 if buffer.contains_str_at(selection.start, &pair.end) {
4611 let pair_start_len = pair.start.len();
4612 if buffer.contains_str_at(
4613 selection.start.saturating_sub(pair_start_len),
4614 &pair.start,
4615 ) {
4616 selection.start -= pair_start_len;
4617 selection.end += pair.end.len();
4618
4619 return selection;
4620 }
4621 }
4622 }
4623 }
4624
4625 selection
4626 })
4627 .collect();
4628
4629 drop(buffer);
4630 self.change_selections(None, window, cx, |selections| {
4631 selections.select(new_selections)
4632 });
4633 }
4634
4635 /// Iterate the given selections, and for each one, find the smallest surrounding
4636 /// autoclose region. This uses the ordering of the selections and the autoclose
4637 /// regions to avoid repeated comparisons.
4638 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4639 &'a self,
4640 selections: impl IntoIterator<Item = Selection<D>>,
4641 buffer: &'a MultiBufferSnapshot,
4642 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4643 let mut i = 0;
4644 let mut regions = self.autoclose_regions.as_slice();
4645 selections.into_iter().map(move |selection| {
4646 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4647
4648 let mut enclosing = None;
4649 while let Some(pair_state) = regions.get(i) {
4650 if pair_state.range.end.to_offset(buffer) < range.start {
4651 regions = ®ions[i + 1..];
4652 i = 0;
4653 } else if pair_state.range.start.to_offset(buffer) > range.end {
4654 break;
4655 } else {
4656 if pair_state.selection_id == selection.id {
4657 enclosing = Some(pair_state);
4658 }
4659 i += 1;
4660 }
4661 }
4662
4663 (selection, enclosing)
4664 })
4665 }
4666
4667 /// Remove any autoclose regions that no longer contain their selection.
4668 fn invalidate_autoclose_regions(
4669 &mut self,
4670 mut selections: &[Selection<Anchor>],
4671 buffer: &MultiBufferSnapshot,
4672 ) {
4673 self.autoclose_regions.retain(|state| {
4674 let mut i = 0;
4675 while let Some(selection) = selections.get(i) {
4676 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4677 selections = &selections[1..];
4678 continue;
4679 }
4680 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4681 break;
4682 }
4683 if selection.id == state.selection_id {
4684 return true;
4685 } else {
4686 i += 1;
4687 }
4688 }
4689 false
4690 });
4691 }
4692
4693 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4694 let offset = position.to_offset(buffer);
4695 let (word_range, kind) = buffer.surrounding_word(offset, true);
4696 if offset > word_range.start && kind == Some(CharKind::Word) {
4697 Some(
4698 buffer
4699 .text_for_range(word_range.start..offset)
4700 .collect::<String>(),
4701 )
4702 } else {
4703 None
4704 }
4705 }
4706
4707 pub fn toggle_inline_values(
4708 &mut self,
4709 _: &ToggleInlineValues,
4710 _: &mut Window,
4711 cx: &mut Context<Self>,
4712 ) {
4713 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4714
4715 self.refresh_inline_values(cx);
4716 }
4717
4718 pub fn toggle_inlay_hints(
4719 &mut self,
4720 _: &ToggleInlayHints,
4721 _: &mut Window,
4722 cx: &mut Context<Self>,
4723 ) {
4724 self.refresh_inlay_hints(
4725 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4726 cx,
4727 );
4728 }
4729
4730 pub fn inlay_hints_enabled(&self) -> bool {
4731 self.inlay_hint_cache.enabled
4732 }
4733
4734 pub fn inline_values_enabled(&self) -> bool {
4735 self.inline_value_cache.enabled
4736 }
4737
4738 #[cfg(any(test, feature = "test-support"))]
4739 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4740 self.display_map
4741 .read(cx)
4742 .current_inlays()
4743 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4744 .cloned()
4745 .collect()
4746 }
4747
4748 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4749 if self.semantics_provider.is_none() || !self.mode.is_full() {
4750 return;
4751 }
4752
4753 let reason_description = reason.description();
4754 let ignore_debounce = matches!(
4755 reason,
4756 InlayHintRefreshReason::SettingsChange(_)
4757 | InlayHintRefreshReason::Toggle(_)
4758 | InlayHintRefreshReason::ExcerptsRemoved(_)
4759 | InlayHintRefreshReason::ModifiersChanged(_)
4760 );
4761 let (invalidate_cache, required_languages) = match reason {
4762 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4763 match self.inlay_hint_cache.modifiers_override(enabled) {
4764 Some(enabled) => {
4765 if enabled {
4766 (InvalidationStrategy::RefreshRequested, None)
4767 } else {
4768 self.splice_inlays(
4769 &self
4770 .visible_inlay_hints(cx)
4771 .iter()
4772 .map(|inlay| inlay.id)
4773 .collect::<Vec<InlayId>>(),
4774 Vec::new(),
4775 cx,
4776 );
4777 return;
4778 }
4779 }
4780 None => return,
4781 }
4782 }
4783 InlayHintRefreshReason::Toggle(enabled) => {
4784 if self.inlay_hint_cache.toggle(enabled) {
4785 if enabled {
4786 (InvalidationStrategy::RefreshRequested, None)
4787 } else {
4788 self.splice_inlays(
4789 &self
4790 .visible_inlay_hints(cx)
4791 .iter()
4792 .map(|inlay| inlay.id)
4793 .collect::<Vec<InlayId>>(),
4794 Vec::new(),
4795 cx,
4796 );
4797 return;
4798 }
4799 } else {
4800 return;
4801 }
4802 }
4803 InlayHintRefreshReason::SettingsChange(new_settings) => {
4804 match self.inlay_hint_cache.update_settings(
4805 &self.buffer,
4806 new_settings,
4807 self.visible_inlay_hints(cx),
4808 cx,
4809 ) {
4810 ControlFlow::Break(Some(InlaySplice {
4811 to_remove,
4812 to_insert,
4813 })) => {
4814 self.splice_inlays(&to_remove, to_insert, cx);
4815 return;
4816 }
4817 ControlFlow::Break(None) => return,
4818 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4819 }
4820 }
4821 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4822 if let Some(InlaySplice {
4823 to_remove,
4824 to_insert,
4825 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4826 {
4827 self.splice_inlays(&to_remove, to_insert, cx);
4828 }
4829 self.display_map.update(cx, |display_map, _| {
4830 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4831 });
4832 return;
4833 }
4834 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4835 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4836 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4837 }
4838 InlayHintRefreshReason::RefreshRequested => {
4839 (InvalidationStrategy::RefreshRequested, None)
4840 }
4841 };
4842
4843 if let Some(InlaySplice {
4844 to_remove,
4845 to_insert,
4846 }) = self.inlay_hint_cache.spawn_hint_refresh(
4847 reason_description,
4848 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4849 invalidate_cache,
4850 ignore_debounce,
4851 cx,
4852 ) {
4853 self.splice_inlays(&to_remove, to_insert, cx);
4854 }
4855 }
4856
4857 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4858 self.display_map
4859 .read(cx)
4860 .current_inlays()
4861 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4862 .cloned()
4863 .collect()
4864 }
4865
4866 pub fn excerpts_for_inlay_hints_query(
4867 &self,
4868 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4869 cx: &mut Context<Editor>,
4870 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4871 let Some(project) = self.project.as_ref() else {
4872 return HashMap::default();
4873 };
4874 let project = project.read(cx);
4875 let multi_buffer = self.buffer().read(cx);
4876 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4877 let multi_buffer_visible_start = self
4878 .scroll_manager
4879 .anchor()
4880 .anchor
4881 .to_point(&multi_buffer_snapshot);
4882 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4883 multi_buffer_visible_start
4884 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4885 Bias::Left,
4886 );
4887 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4888 multi_buffer_snapshot
4889 .range_to_buffer_ranges(multi_buffer_visible_range)
4890 .into_iter()
4891 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4892 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4893 let buffer_file = project::File::from_dyn(buffer.file())?;
4894 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4895 let worktree_entry = buffer_worktree
4896 .read(cx)
4897 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4898 if worktree_entry.is_ignored {
4899 return None;
4900 }
4901
4902 let language = buffer.language()?;
4903 if let Some(restrict_to_languages) = restrict_to_languages {
4904 if !restrict_to_languages.contains(language) {
4905 return None;
4906 }
4907 }
4908 Some((
4909 excerpt_id,
4910 (
4911 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4912 buffer.version().clone(),
4913 excerpt_visible_range,
4914 ),
4915 ))
4916 })
4917 .collect()
4918 }
4919
4920 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4921 TextLayoutDetails {
4922 text_system: window.text_system().clone(),
4923 editor_style: self.style.clone().unwrap(),
4924 rem_size: window.rem_size(),
4925 scroll_anchor: self.scroll_manager.anchor(),
4926 visible_rows: self.visible_line_count(),
4927 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4928 }
4929 }
4930
4931 pub fn splice_inlays(
4932 &self,
4933 to_remove: &[InlayId],
4934 to_insert: Vec<Inlay>,
4935 cx: &mut Context<Self>,
4936 ) {
4937 self.display_map.update(cx, |display_map, cx| {
4938 display_map.splice_inlays(to_remove, to_insert, cx)
4939 });
4940 cx.notify();
4941 }
4942
4943 fn trigger_on_type_formatting(
4944 &self,
4945 input: String,
4946 window: &mut Window,
4947 cx: &mut Context<Self>,
4948 ) -> Option<Task<Result<()>>> {
4949 if input.len() != 1 {
4950 return None;
4951 }
4952
4953 let project = self.project.as_ref()?;
4954 let position = self.selections.newest_anchor().head();
4955 let (buffer, buffer_position) = self
4956 .buffer
4957 .read(cx)
4958 .text_anchor_for_position(position, cx)?;
4959
4960 let settings = language_settings::language_settings(
4961 buffer
4962 .read(cx)
4963 .language_at(buffer_position)
4964 .map(|l| l.name()),
4965 buffer.read(cx).file(),
4966 cx,
4967 );
4968 if !settings.use_on_type_format {
4969 return None;
4970 }
4971
4972 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4973 // hence we do LSP request & edit on host side only — add formats to host's history.
4974 let push_to_lsp_host_history = true;
4975 // If this is not the host, append its history with new edits.
4976 let push_to_client_history = project.read(cx).is_via_collab();
4977
4978 let on_type_formatting = project.update(cx, |project, cx| {
4979 project.on_type_format(
4980 buffer.clone(),
4981 buffer_position,
4982 input,
4983 push_to_lsp_host_history,
4984 cx,
4985 )
4986 });
4987 Some(cx.spawn_in(window, async move |editor, cx| {
4988 if let Some(transaction) = on_type_formatting.await? {
4989 if push_to_client_history {
4990 buffer
4991 .update(cx, |buffer, _| {
4992 buffer.push_transaction(transaction, Instant::now());
4993 buffer.finalize_last_transaction();
4994 })
4995 .ok();
4996 }
4997 editor.update(cx, |editor, cx| {
4998 editor.refresh_document_highlights(cx);
4999 })?;
5000 }
5001 Ok(())
5002 }))
5003 }
5004
5005 pub fn show_word_completions(
5006 &mut self,
5007 _: &ShowWordCompletions,
5008 window: &mut Window,
5009 cx: &mut Context<Self>,
5010 ) {
5011 self.open_or_update_completions_menu(true, None, window, cx);
5012 }
5013
5014 pub fn show_completions(
5015 &mut self,
5016 options: &ShowCompletions,
5017 window: &mut Window,
5018 cx: &mut Context<Self>,
5019 ) {
5020 self.open_or_update_completions_menu(false, options.trigger.as_deref(), window, cx);
5021 }
5022
5023 fn open_or_update_completions_menu(
5024 &mut self,
5025 ignore_completion_provider: bool,
5026 trigger: Option<&str>,
5027 window: &mut Window,
5028 cx: &mut Context<Self>,
5029 ) {
5030 if self.pending_rename.is_some() {
5031 return;
5032 }
5033
5034 let position = self.selections.newest_anchor().head();
5035 if position.diff_base_anchor.is_some() {
5036 return;
5037 }
5038 let (buffer, buffer_position) =
5039 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5040 output
5041 } else {
5042 return;
5043 };
5044 let buffer_snapshot = buffer.read(cx).snapshot();
5045
5046 let query: Option<Arc<String>> =
5047 Self::completion_query(&self.buffer.read(cx).read(cx), position)
5048 .map(|query| query.into());
5049
5050 let provider = if ignore_completion_provider {
5051 None
5052 } else {
5053 self.completion_provider.clone()
5054 };
5055
5056 let sort_completions = provider
5057 .as_ref()
5058 .map_or(false, |provider| provider.sort_completions());
5059
5060 let filter_completions = provider
5061 .as_ref()
5062 .map_or(true, |provider| provider.filter_completions());
5063
5064 // When `is_incomplete` is false, can filter completions instead of re-querying when the
5065 // current query is a suffix of the initial query.
5066 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5067 if !menu.is_incomplete && filter_completions {
5068 // If the new query is a suffix of the old query (typing more characters) and
5069 // the previous result was complete, the existing completions can be filtered.
5070 //
5071 // Note that this is always true for snippet completions.
5072 let query_matches = match (&menu.initial_query, &query) {
5073 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5074 (None, _) => true,
5075 _ => false,
5076 };
5077 if query_matches {
5078 let position_matches = if menu.initial_position == position {
5079 true
5080 } else {
5081 let snapshot = self.buffer.read(cx).read(cx);
5082 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5083 };
5084 if position_matches {
5085 menu.filter(query.clone(), provider.clone(), window, cx);
5086 return;
5087 }
5088 }
5089 }
5090 };
5091
5092 let trigger_kind = match trigger {
5093 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5094 CompletionTriggerKind::TRIGGER_CHARACTER
5095 }
5096 _ => CompletionTriggerKind::INVOKED,
5097 };
5098 let completion_context = CompletionContext {
5099 trigger_character: trigger.and_then(|trigger| {
5100 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5101 Some(String::from(trigger))
5102 } else {
5103 None
5104 }
5105 }),
5106 trigger_kind,
5107 };
5108
5109 let (replace_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
5110 let (replace_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
5111 let word_to_exclude = buffer_snapshot
5112 .text_for_range(replace_range.clone())
5113 .collect::<String>();
5114 (
5115 buffer_snapshot.anchor_before(replace_range.start)
5116 ..buffer_snapshot.anchor_after(replace_range.end),
5117 Some(word_to_exclude),
5118 )
5119 } else {
5120 (buffer_position..buffer_position, None)
5121 };
5122
5123 let language = buffer_snapshot
5124 .language_at(buffer_position)
5125 .map(|language| language.name());
5126
5127 let completion_settings =
5128 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5129
5130 let show_completion_documentation = buffer_snapshot
5131 .settings_at(buffer_position, cx)
5132 .show_completion_documentation;
5133
5134 // The document can be large, so stay in reasonable bounds when searching for words,
5135 // otherwise completion pop-up might be slow to appear.
5136 const WORD_LOOKUP_ROWS: u32 = 5_000;
5137 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5138 let min_word_search = buffer_snapshot.clip_point(
5139 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5140 Bias::Left,
5141 );
5142 let max_word_search = buffer_snapshot.clip_point(
5143 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5144 Bias::Right,
5145 );
5146 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5147 ..buffer_snapshot.point_to_offset(max_word_search);
5148
5149 let skip_digits = query
5150 .as_ref()
5151 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5152
5153 let (mut words, provider_responses) = match &provider {
5154 Some(provider) => {
5155 let provider_responses = provider.completions(
5156 position.excerpt_id,
5157 &buffer,
5158 buffer_position,
5159 completion_context,
5160 window,
5161 cx,
5162 );
5163
5164 let words = match completion_settings.words {
5165 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5166 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5167 .background_spawn(async move {
5168 buffer_snapshot.words_in_range(WordsQuery {
5169 fuzzy_contents: None,
5170 range: word_search_range,
5171 skip_digits,
5172 })
5173 }),
5174 };
5175
5176 (words, provider_responses)
5177 }
5178 None => (
5179 cx.background_spawn(async move {
5180 buffer_snapshot.words_in_range(WordsQuery {
5181 fuzzy_contents: None,
5182 range: word_search_range,
5183 skip_digits,
5184 })
5185 }),
5186 Task::ready(Ok(Vec::new())),
5187 ),
5188 };
5189
5190 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5191
5192 let id = post_inc(&mut self.next_completion_id);
5193 let task = cx.spawn_in(window, async move |editor, cx| {
5194 let Ok(()) = editor.update(cx, |this, _| {
5195 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5196 }) else {
5197 return;
5198 };
5199
5200 // TODO: Ideally completions from different sources would be selectively re-queried, so
5201 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5202 let mut completions = Vec::new();
5203 let mut is_incomplete = false;
5204 if let Some(provider_responses) = provider_responses.await.log_err() {
5205 if !provider_responses.is_empty() {
5206 for response in provider_responses {
5207 completions.extend(response.completions);
5208 is_incomplete = is_incomplete || response.is_incomplete;
5209 }
5210 if completion_settings.words == WordsCompletionMode::Fallback {
5211 words = Task::ready(BTreeMap::default());
5212 }
5213 }
5214 }
5215
5216 let mut words = words.await;
5217 if let Some(word_to_exclude) = &word_to_exclude {
5218 words.remove(word_to_exclude);
5219 }
5220 for lsp_completion in &completions {
5221 words.remove(&lsp_completion.new_text);
5222 }
5223 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5224 replace_range: replace_range.clone(),
5225 new_text: word.clone(),
5226 label: CodeLabel::plain(word, None),
5227 icon_path: None,
5228 documentation: None,
5229 source: CompletionSource::BufferWord {
5230 word_range,
5231 resolved: false,
5232 },
5233 insert_text_mode: Some(InsertTextMode::AS_IS),
5234 confirm: None,
5235 }));
5236
5237 let menu = if completions.is_empty() {
5238 None
5239 } else {
5240 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5241 let languages = editor
5242 .workspace
5243 .as_ref()
5244 .and_then(|(workspace, _)| workspace.upgrade())
5245 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5246 let menu = CompletionsMenu::new(
5247 id,
5248 sort_completions,
5249 show_completion_documentation,
5250 ignore_completion_provider,
5251 position,
5252 query.clone(),
5253 is_incomplete,
5254 buffer.clone(),
5255 completions.into(),
5256 snippet_sort_order,
5257 languages,
5258 language,
5259 cx,
5260 );
5261
5262 let query = if filter_completions { query } else { None };
5263 let matches_task = if let Some(query) = query {
5264 menu.do_async_filtering(query, cx)
5265 } else {
5266 Task::ready(menu.unfiltered_matches())
5267 };
5268 (menu, matches_task)
5269 }) else {
5270 return;
5271 };
5272
5273 let matches = matches_task.await;
5274
5275 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5276 // Newer menu already set, so exit.
5277 match editor.context_menu.borrow().as_ref() {
5278 Some(CodeContextMenu::Completions(prev_menu)) => {
5279 if prev_menu.id > id {
5280 return;
5281 }
5282 }
5283 _ => {}
5284 };
5285
5286 // Only valid to take prev_menu because it the new menu is immediately set
5287 // below, or the menu is hidden.
5288 match editor.context_menu.borrow_mut().take() {
5289 Some(CodeContextMenu::Completions(prev_menu)) => {
5290 let position_matches =
5291 if prev_menu.initial_position == menu.initial_position {
5292 true
5293 } else {
5294 let snapshot = editor.buffer.read(cx).read(cx);
5295 prev_menu.initial_position.to_offset(&snapshot)
5296 == menu.initial_position.to_offset(&snapshot)
5297 };
5298 if position_matches {
5299 // Preserve markdown cache before `set_filter_results` because it will
5300 // try to populate the documentation cache.
5301 menu.preserve_markdown_cache(prev_menu);
5302 }
5303 }
5304 _ => {}
5305 };
5306
5307 menu.set_filter_results(matches, provider, window, cx);
5308 }) else {
5309 return;
5310 };
5311
5312 menu.visible().then_some(menu)
5313 };
5314
5315 editor
5316 .update_in(cx, |editor, window, cx| {
5317 if editor.focus_handle.is_focused(window) {
5318 if let Some(menu) = menu {
5319 *editor.context_menu.borrow_mut() =
5320 Some(CodeContextMenu::Completions(menu));
5321
5322 crate::hover_popover::hide_hover(editor, cx);
5323 if editor.show_edit_predictions_in_menu() {
5324 editor.update_visible_inline_completion(window, cx);
5325 } else {
5326 editor.discard_inline_completion(false, cx);
5327 }
5328
5329 cx.notify();
5330 return;
5331 }
5332 }
5333
5334 if editor.completion_tasks.len() <= 1 {
5335 // If there are no more completion tasks and the last menu was empty, we should hide it.
5336 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5337 // If it was already hidden and we don't show inline completions in the menu, we should
5338 // also show the inline-completion when available.
5339 if was_hidden && editor.show_edit_predictions_in_menu() {
5340 editor.update_visible_inline_completion(window, cx);
5341 }
5342 }
5343 })
5344 .ok();
5345 });
5346
5347 self.completion_tasks.push((id, task));
5348 }
5349
5350 #[cfg(feature = "test-support")]
5351 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5352 let menu = self.context_menu.borrow();
5353 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5354 let completions = menu.completions.borrow();
5355 Some(completions.to_vec())
5356 } else {
5357 None
5358 }
5359 }
5360
5361 pub fn with_completions_menu_matching_id<R>(
5362 &self,
5363 id: CompletionId,
5364 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5365 ) -> R {
5366 let mut context_menu = self.context_menu.borrow_mut();
5367 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5368 return f(None);
5369 };
5370 if completions_menu.id != id {
5371 return f(None);
5372 }
5373 f(Some(completions_menu))
5374 }
5375
5376 pub fn confirm_completion(
5377 &mut self,
5378 action: &ConfirmCompletion,
5379 window: &mut Window,
5380 cx: &mut Context<Self>,
5381 ) -> Option<Task<Result<()>>> {
5382 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5383 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5384 }
5385
5386 pub fn confirm_completion_insert(
5387 &mut self,
5388 _: &ConfirmCompletionInsert,
5389 window: &mut Window,
5390 cx: &mut Context<Self>,
5391 ) -> Option<Task<Result<()>>> {
5392 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5393 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5394 }
5395
5396 pub fn confirm_completion_replace(
5397 &mut self,
5398 _: &ConfirmCompletionReplace,
5399 window: &mut Window,
5400 cx: &mut Context<Self>,
5401 ) -> Option<Task<Result<()>>> {
5402 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5403 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5404 }
5405
5406 pub fn compose_completion(
5407 &mut self,
5408 action: &ComposeCompletion,
5409 window: &mut Window,
5410 cx: &mut Context<Self>,
5411 ) -> Option<Task<Result<()>>> {
5412 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5413 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5414 }
5415
5416 fn do_completion(
5417 &mut self,
5418 item_ix: Option<usize>,
5419 intent: CompletionIntent,
5420 window: &mut Window,
5421 cx: &mut Context<Editor>,
5422 ) -> Option<Task<Result<()>>> {
5423 use language::ToOffset as _;
5424
5425 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5426 else {
5427 return None;
5428 };
5429
5430 let candidate_id = {
5431 let entries = completions_menu.entries.borrow();
5432 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5433 if self.show_edit_predictions_in_menu() {
5434 self.discard_inline_completion(true, cx);
5435 }
5436 mat.candidate_id
5437 };
5438
5439 let completion = completions_menu
5440 .completions
5441 .borrow()
5442 .get(candidate_id)?
5443 .clone();
5444 cx.stop_propagation();
5445
5446 let buffer_handle = completions_menu.buffer.clone();
5447
5448 let CompletionEdit {
5449 new_text,
5450 snippet,
5451 replace_range,
5452 } = process_completion_for_edit(
5453 &completion,
5454 intent,
5455 &buffer_handle,
5456 &completions_menu.initial_position.text_anchor,
5457 cx,
5458 );
5459
5460 let buffer = buffer_handle.read(cx);
5461 let snapshot = self.buffer.read(cx).snapshot(cx);
5462 let newest_anchor = self.selections.newest_anchor();
5463 let replace_range_multibuffer = {
5464 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5465 let multibuffer_anchor = snapshot
5466 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5467 .unwrap()
5468 ..snapshot
5469 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5470 .unwrap();
5471 multibuffer_anchor.start.to_offset(&snapshot)
5472 ..multibuffer_anchor.end.to_offset(&snapshot)
5473 };
5474 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5475 return None;
5476 }
5477
5478 let old_text = buffer
5479 .text_for_range(replace_range.clone())
5480 .collect::<String>();
5481 let lookbehind = newest_anchor
5482 .start
5483 .text_anchor
5484 .to_offset(buffer)
5485 .saturating_sub(replace_range.start);
5486 let lookahead = replace_range
5487 .end
5488 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5489 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5490 let suffix = &old_text[lookbehind.min(old_text.len())..];
5491
5492 let selections = self.selections.all::<usize>(cx);
5493 let mut ranges = Vec::new();
5494 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5495
5496 for selection in &selections {
5497 let range = if selection.id == newest_anchor.id {
5498 replace_range_multibuffer.clone()
5499 } else {
5500 let mut range = selection.range();
5501
5502 // if prefix is present, don't duplicate it
5503 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5504 range.start = range.start.saturating_sub(lookbehind);
5505
5506 // if suffix is also present, mimic the newest cursor and replace it
5507 if selection.id != newest_anchor.id
5508 && snapshot.contains_str_at(range.end, suffix)
5509 {
5510 range.end += lookahead;
5511 }
5512 }
5513 range
5514 };
5515
5516 ranges.push(range.clone());
5517
5518 if !self.linked_edit_ranges.is_empty() {
5519 let start_anchor = snapshot.anchor_before(range.start);
5520 let end_anchor = snapshot.anchor_after(range.end);
5521 if let Some(ranges) = self
5522 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5523 {
5524 for (buffer, edits) in ranges {
5525 linked_edits
5526 .entry(buffer.clone())
5527 .or_default()
5528 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5529 }
5530 }
5531 }
5532 }
5533
5534 cx.emit(EditorEvent::InputHandled {
5535 utf16_range_to_replace: None,
5536 text: new_text.clone().into(),
5537 });
5538
5539 self.transact(window, cx, |this, window, cx| {
5540 if let Some(mut snippet) = snippet {
5541 snippet.text = new_text.to_string();
5542 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5543 } else {
5544 this.buffer.update(cx, |buffer, cx| {
5545 let auto_indent = match completion.insert_text_mode {
5546 Some(InsertTextMode::AS_IS) => None,
5547 _ => this.autoindent_mode.clone(),
5548 };
5549 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5550 buffer.edit(edits, auto_indent, cx);
5551 });
5552 }
5553 for (buffer, edits) in linked_edits {
5554 buffer.update(cx, |buffer, cx| {
5555 let snapshot = buffer.snapshot();
5556 let edits = edits
5557 .into_iter()
5558 .map(|(range, text)| {
5559 use text::ToPoint as TP;
5560 let end_point = TP::to_point(&range.end, &snapshot);
5561 let start_point = TP::to_point(&range.start, &snapshot);
5562 (start_point..end_point, text)
5563 })
5564 .sorted_by_key(|(range, _)| range.start);
5565 buffer.edit(edits, None, cx);
5566 })
5567 }
5568
5569 this.refresh_inline_completion(true, false, window, cx);
5570 });
5571
5572 let show_new_completions_on_confirm = completion
5573 .confirm
5574 .as_ref()
5575 .map_or(false, |confirm| confirm(intent, window, cx));
5576 if show_new_completions_on_confirm {
5577 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5578 }
5579
5580 let provider = self.completion_provider.as_ref()?;
5581 drop(completion);
5582 let apply_edits = provider.apply_additional_edits_for_completion(
5583 buffer_handle,
5584 completions_menu.completions.clone(),
5585 candidate_id,
5586 true,
5587 cx,
5588 );
5589
5590 let editor_settings = EditorSettings::get_global(cx);
5591 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5592 // After the code completion is finished, users often want to know what signatures are needed.
5593 // so we should automatically call signature_help
5594 self.show_signature_help(&ShowSignatureHelp, window, cx);
5595 }
5596
5597 Some(cx.foreground_executor().spawn(async move {
5598 apply_edits.await?;
5599 Ok(())
5600 }))
5601 }
5602
5603 pub fn toggle_code_actions(
5604 &mut self,
5605 action: &ToggleCodeActions,
5606 window: &mut Window,
5607 cx: &mut Context<Self>,
5608 ) {
5609 let quick_launch = action.quick_launch;
5610 let mut context_menu = self.context_menu.borrow_mut();
5611 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5612 if code_actions.deployed_from == action.deployed_from {
5613 // Toggle if we're selecting the same one
5614 *context_menu = None;
5615 cx.notify();
5616 return;
5617 } else {
5618 // Otherwise, clear it and start a new one
5619 *context_menu = None;
5620 cx.notify();
5621 }
5622 }
5623 drop(context_menu);
5624 let snapshot = self.snapshot(window, cx);
5625 let deployed_from = action.deployed_from.clone();
5626 let mut task = self.code_actions_task.take();
5627 let action = action.clone();
5628 cx.spawn_in(window, async move |editor, cx| {
5629 while let Some(prev_task) = task {
5630 prev_task.await.log_err();
5631 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5632 }
5633
5634 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5635 if editor.focus_handle.is_focused(window) {
5636 let multibuffer_point = match &action.deployed_from {
5637 Some(CodeActionSource::Indicator(row)) => {
5638 DisplayPoint::new(*row, 0).to_point(&snapshot)
5639 }
5640 _ => editor.selections.newest::<Point>(cx).head(),
5641 };
5642 let (buffer, buffer_row) = snapshot
5643 .buffer_snapshot
5644 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5645 .and_then(|(buffer_snapshot, range)| {
5646 editor
5647 .buffer
5648 .read(cx)
5649 .buffer(buffer_snapshot.remote_id())
5650 .map(|buffer| (buffer, range.start.row))
5651 })?;
5652 let (_, code_actions) = editor
5653 .available_code_actions
5654 .clone()
5655 .and_then(|(location, code_actions)| {
5656 let snapshot = location.buffer.read(cx).snapshot();
5657 let point_range = location.range.to_point(&snapshot);
5658 let point_range = point_range.start.row..=point_range.end.row;
5659 if point_range.contains(&buffer_row) {
5660 Some((location, code_actions))
5661 } else {
5662 None
5663 }
5664 })
5665 .unzip();
5666 let buffer_id = buffer.read(cx).remote_id();
5667 let tasks = editor
5668 .tasks
5669 .get(&(buffer_id, buffer_row))
5670 .map(|t| Arc::new(t.to_owned()));
5671 if tasks.is_none() && code_actions.is_none() {
5672 return None;
5673 }
5674
5675 editor.completion_tasks.clear();
5676 editor.discard_inline_completion(false, cx);
5677 let task_context =
5678 tasks
5679 .as_ref()
5680 .zip(editor.project.clone())
5681 .map(|(tasks, project)| {
5682 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5683 });
5684
5685 Some(cx.spawn_in(window, async move |editor, cx| {
5686 let task_context = match task_context {
5687 Some(task_context) => task_context.await,
5688 None => None,
5689 };
5690 let resolved_tasks =
5691 tasks
5692 .zip(task_context.clone())
5693 .map(|(tasks, task_context)| ResolvedTasks {
5694 templates: tasks.resolve(&task_context).collect(),
5695 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5696 multibuffer_point.row,
5697 tasks.column,
5698 )),
5699 });
5700 let debug_scenarios = editor.update(cx, |editor, cx| {
5701 if cx.has_flag::<DebuggerFeatureFlag>() {
5702 maybe!({
5703 let project = editor.project.as_ref()?;
5704 let dap_store = project.read(cx).dap_store();
5705 let mut scenarios = vec![];
5706 let resolved_tasks = resolved_tasks.as_ref()?;
5707 let buffer = buffer.read(cx);
5708 let language = buffer.language()?;
5709 let file = buffer.file();
5710 let debug_adapter =
5711 language_settings(language.name().into(), file, cx)
5712 .debuggers
5713 .first()
5714 .map(SharedString::from)
5715 .or_else(|| {
5716 language
5717 .config()
5718 .debuggers
5719 .first()
5720 .map(SharedString::from)
5721 })?;
5722
5723 dap_store.update(cx, |dap_store, cx| {
5724 for (_, task) in &resolved_tasks.templates {
5725 if let Some(scenario) = dap_store
5726 .debug_scenario_for_build_task(
5727 task.original_task().clone(),
5728 debug_adapter.clone().into(),
5729 task.display_label().to_owned().into(),
5730 cx,
5731 )
5732 {
5733 scenarios.push(scenario);
5734 }
5735 }
5736 });
5737 Some(scenarios)
5738 })
5739 .unwrap_or_default()
5740 } else {
5741 vec![]
5742 }
5743 })?;
5744 let spawn_straight_away = quick_launch
5745 && resolved_tasks
5746 .as_ref()
5747 .map_or(false, |tasks| tasks.templates.len() == 1)
5748 && code_actions
5749 .as_ref()
5750 .map_or(true, |actions| actions.is_empty())
5751 && debug_scenarios.is_empty();
5752 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5753 crate::hover_popover::hide_hover(editor, cx);
5754 *editor.context_menu.borrow_mut() =
5755 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5756 buffer,
5757 actions: CodeActionContents::new(
5758 resolved_tasks,
5759 code_actions,
5760 debug_scenarios,
5761 task_context.unwrap_or_default(),
5762 ),
5763 selected_item: Default::default(),
5764 scroll_handle: UniformListScrollHandle::default(),
5765 deployed_from,
5766 }));
5767 if spawn_straight_away {
5768 if let Some(task) = editor.confirm_code_action(
5769 &ConfirmCodeAction { item_ix: Some(0) },
5770 window,
5771 cx,
5772 ) {
5773 cx.notify();
5774 return task;
5775 }
5776 }
5777 cx.notify();
5778 Task::ready(Ok(()))
5779 }) {
5780 task.await
5781 } else {
5782 Ok(())
5783 }
5784 }))
5785 } else {
5786 Some(Task::ready(Ok(())))
5787 }
5788 })?;
5789 if let Some(task) = spawned_test_task {
5790 task.await?;
5791 }
5792
5793 anyhow::Ok(())
5794 })
5795 .detach_and_log_err(cx);
5796 }
5797
5798 pub fn confirm_code_action(
5799 &mut self,
5800 action: &ConfirmCodeAction,
5801 window: &mut Window,
5802 cx: &mut Context<Self>,
5803 ) -> Option<Task<Result<()>>> {
5804 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5805
5806 let actions_menu =
5807 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5808 menu
5809 } else {
5810 return None;
5811 };
5812
5813 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5814 let action = actions_menu.actions.get(action_ix)?;
5815 let title = action.label();
5816 let buffer = actions_menu.buffer;
5817 let workspace = self.workspace()?;
5818
5819 match action {
5820 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5821 workspace.update(cx, |workspace, cx| {
5822 workspace.schedule_resolved_task(
5823 task_source_kind,
5824 resolved_task,
5825 false,
5826 window,
5827 cx,
5828 );
5829
5830 Some(Task::ready(Ok(())))
5831 })
5832 }
5833 CodeActionsItem::CodeAction {
5834 excerpt_id,
5835 action,
5836 provider,
5837 } => {
5838 let apply_code_action =
5839 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5840 let workspace = workspace.downgrade();
5841 Some(cx.spawn_in(window, async move |editor, cx| {
5842 let project_transaction = apply_code_action.await?;
5843 Self::open_project_transaction(
5844 &editor,
5845 workspace,
5846 project_transaction,
5847 title,
5848 cx,
5849 )
5850 .await
5851 }))
5852 }
5853 CodeActionsItem::DebugScenario(scenario) => {
5854 let context = actions_menu.actions.context.clone();
5855
5856 workspace.update(cx, |workspace, cx| {
5857 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
5858 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5859 });
5860 Some(Task::ready(Ok(())))
5861 }
5862 }
5863 }
5864
5865 pub async fn open_project_transaction(
5866 this: &WeakEntity<Editor>,
5867 workspace: WeakEntity<Workspace>,
5868 transaction: ProjectTransaction,
5869 title: String,
5870 cx: &mut AsyncWindowContext,
5871 ) -> Result<()> {
5872 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5873 cx.update(|_, cx| {
5874 entries.sort_unstable_by_key(|(buffer, _)| {
5875 buffer.read(cx).file().map(|f| f.path().clone())
5876 });
5877 })?;
5878
5879 // If the project transaction's edits are all contained within this editor, then
5880 // avoid opening a new editor to display them.
5881
5882 if let Some((buffer, transaction)) = entries.first() {
5883 if entries.len() == 1 {
5884 let excerpt = this.update(cx, |editor, cx| {
5885 editor
5886 .buffer()
5887 .read(cx)
5888 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5889 })?;
5890 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5891 if excerpted_buffer == *buffer {
5892 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5893 let excerpt_range = excerpt_range.to_offset(buffer);
5894 buffer
5895 .edited_ranges_for_transaction::<usize>(transaction)
5896 .all(|range| {
5897 excerpt_range.start <= range.start
5898 && excerpt_range.end >= range.end
5899 })
5900 })?;
5901
5902 if all_edits_within_excerpt {
5903 return Ok(());
5904 }
5905 }
5906 }
5907 }
5908 } else {
5909 return Ok(());
5910 }
5911
5912 let mut ranges_to_highlight = Vec::new();
5913 let excerpt_buffer = cx.new(|cx| {
5914 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5915 for (buffer_handle, transaction) in &entries {
5916 let edited_ranges = buffer_handle
5917 .read(cx)
5918 .edited_ranges_for_transaction::<Point>(transaction)
5919 .collect::<Vec<_>>();
5920 let (ranges, _) = multibuffer.set_excerpts_for_path(
5921 PathKey::for_buffer(buffer_handle, cx),
5922 buffer_handle.clone(),
5923 edited_ranges,
5924 DEFAULT_MULTIBUFFER_CONTEXT,
5925 cx,
5926 );
5927
5928 ranges_to_highlight.extend(ranges);
5929 }
5930 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5931 multibuffer
5932 })?;
5933
5934 workspace.update_in(cx, |workspace, window, cx| {
5935 let project = workspace.project().clone();
5936 let editor =
5937 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5938 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5939 editor.update(cx, |editor, cx| {
5940 editor.highlight_background::<Self>(
5941 &ranges_to_highlight,
5942 |theme| theme.editor_highlighted_line_background,
5943 cx,
5944 );
5945 });
5946 })?;
5947
5948 Ok(())
5949 }
5950
5951 pub fn clear_code_action_providers(&mut self) {
5952 self.code_action_providers.clear();
5953 self.available_code_actions.take();
5954 }
5955
5956 pub fn add_code_action_provider(
5957 &mut self,
5958 provider: Rc<dyn CodeActionProvider>,
5959 window: &mut Window,
5960 cx: &mut Context<Self>,
5961 ) {
5962 if self
5963 .code_action_providers
5964 .iter()
5965 .any(|existing_provider| existing_provider.id() == provider.id())
5966 {
5967 return;
5968 }
5969
5970 self.code_action_providers.push(provider);
5971 self.refresh_code_actions(window, cx);
5972 }
5973
5974 pub fn remove_code_action_provider(
5975 &mut self,
5976 id: Arc<str>,
5977 window: &mut Window,
5978 cx: &mut Context<Self>,
5979 ) {
5980 self.code_action_providers
5981 .retain(|provider| provider.id() != id);
5982 self.refresh_code_actions(window, cx);
5983 }
5984
5985 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
5986 !self.code_action_providers.is_empty()
5987 && EditorSettings::get_global(cx).toolbar.code_actions
5988 }
5989
5990 pub fn has_available_code_actions(&self) -> bool {
5991 self.available_code_actions
5992 .as_ref()
5993 .is_some_and(|(_, actions)| !actions.is_empty())
5994 }
5995
5996 fn render_inline_code_actions(
5997 &self,
5998 icon_size: ui::IconSize,
5999 display_row: DisplayRow,
6000 is_active: bool,
6001 cx: &mut Context<Self>,
6002 ) -> AnyElement {
6003 let show_tooltip = !self.context_menu_visible();
6004 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6005 .icon_size(icon_size)
6006 .shape(ui::IconButtonShape::Square)
6007 .style(ButtonStyle::Transparent)
6008 .icon_color(ui::Color::Hidden)
6009 .toggle_state(is_active)
6010 .when(show_tooltip, |this| {
6011 this.tooltip({
6012 let focus_handle = self.focus_handle.clone();
6013 move |window, cx| {
6014 Tooltip::for_action_in(
6015 "Toggle Code Actions",
6016 &ToggleCodeActions {
6017 deployed_from: None,
6018 quick_launch: false,
6019 },
6020 &focus_handle,
6021 window,
6022 cx,
6023 )
6024 }
6025 })
6026 })
6027 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6028 window.focus(&editor.focus_handle(cx));
6029 editor.toggle_code_actions(
6030 &crate::actions::ToggleCodeActions {
6031 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6032 display_row,
6033 )),
6034 quick_launch: false,
6035 },
6036 window,
6037 cx,
6038 );
6039 }))
6040 .into_any_element()
6041 }
6042
6043 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6044 &self.context_menu
6045 }
6046
6047 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6048 let newest_selection = self.selections.newest_anchor().clone();
6049 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6050 let buffer = self.buffer.read(cx);
6051 if newest_selection.head().diff_base_anchor.is_some() {
6052 return None;
6053 }
6054 let (start_buffer, start) =
6055 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6056 let (end_buffer, end) =
6057 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6058 if start_buffer != end_buffer {
6059 return None;
6060 }
6061
6062 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6063 cx.background_executor()
6064 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6065 .await;
6066
6067 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6068 let providers = this.code_action_providers.clone();
6069 let tasks = this
6070 .code_action_providers
6071 .iter()
6072 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6073 .collect::<Vec<_>>();
6074 (providers, tasks)
6075 })?;
6076
6077 let mut actions = Vec::new();
6078 for (provider, provider_actions) in
6079 providers.into_iter().zip(future::join_all(tasks).await)
6080 {
6081 if let Some(provider_actions) = provider_actions.log_err() {
6082 actions.extend(provider_actions.into_iter().map(|action| {
6083 AvailableCodeAction {
6084 excerpt_id: newest_selection.start.excerpt_id,
6085 action,
6086 provider: provider.clone(),
6087 }
6088 }));
6089 }
6090 }
6091
6092 this.update(cx, |this, cx| {
6093 this.available_code_actions = if actions.is_empty() {
6094 None
6095 } else {
6096 Some((
6097 Location {
6098 buffer: start_buffer,
6099 range: start..end,
6100 },
6101 actions.into(),
6102 ))
6103 };
6104 cx.notify();
6105 })
6106 }));
6107 None
6108 }
6109
6110 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6111 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6112 self.show_git_blame_inline = false;
6113
6114 self.show_git_blame_inline_delay_task =
6115 Some(cx.spawn_in(window, async move |this, cx| {
6116 cx.background_executor().timer(delay).await;
6117
6118 this.update(cx, |this, cx| {
6119 this.show_git_blame_inline = true;
6120 cx.notify();
6121 })
6122 .log_err();
6123 }));
6124 }
6125 }
6126
6127 fn show_blame_popover(
6128 &mut self,
6129 blame_entry: &BlameEntry,
6130 position: gpui::Point<Pixels>,
6131 cx: &mut Context<Self>,
6132 ) {
6133 if let Some(state) = &mut self.inline_blame_popover {
6134 state.hide_task.take();
6135 cx.notify();
6136 } else {
6137 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6138 let show_task = cx.spawn(async move |editor, cx| {
6139 cx.background_executor()
6140 .timer(std::time::Duration::from_millis(delay))
6141 .await;
6142 editor
6143 .update(cx, |editor, cx| {
6144 if let Some(state) = &mut editor.inline_blame_popover {
6145 state.show_task = None;
6146 cx.notify();
6147 }
6148 })
6149 .ok();
6150 });
6151 let Some(blame) = self.blame.as_ref() else {
6152 return;
6153 };
6154 let blame = blame.read(cx);
6155 let details = blame.details_for_entry(&blame_entry);
6156 let markdown = cx.new(|cx| {
6157 Markdown::new(
6158 details
6159 .as_ref()
6160 .map(|message| message.message.clone())
6161 .unwrap_or_default(),
6162 None,
6163 None,
6164 cx,
6165 )
6166 });
6167 self.inline_blame_popover = Some(InlineBlamePopover {
6168 position,
6169 show_task: Some(show_task),
6170 hide_task: None,
6171 popover_bounds: None,
6172 popover_state: InlineBlamePopoverState {
6173 scroll_handle: ScrollHandle::new(),
6174 commit_message: details,
6175 markdown,
6176 },
6177 });
6178 }
6179 }
6180
6181 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6182 if let Some(state) = &mut self.inline_blame_popover {
6183 if state.show_task.is_some() {
6184 self.inline_blame_popover.take();
6185 cx.notify();
6186 } else {
6187 let hide_task = cx.spawn(async move |editor, cx| {
6188 cx.background_executor()
6189 .timer(std::time::Duration::from_millis(100))
6190 .await;
6191 editor
6192 .update(cx, |editor, cx| {
6193 editor.inline_blame_popover.take();
6194 cx.notify();
6195 })
6196 .ok();
6197 });
6198 state.hide_task = Some(hide_task);
6199 }
6200 }
6201 }
6202
6203 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6204 if self.pending_rename.is_some() {
6205 return None;
6206 }
6207
6208 let provider = self.semantics_provider.clone()?;
6209 let buffer = self.buffer.read(cx);
6210 let newest_selection = self.selections.newest_anchor().clone();
6211 let cursor_position = newest_selection.head();
6212 let (cursor_buffer, cursor_buffer_position) =
6213 buffer.text_anchor_for_position(cursor_position, cx)?;
6214 let (tail_buffer, tail_buffer_position) =
6215 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6216 if cursor_buffer != tail_buffer {
6217 return None;
6218 }
6219
6220 let snapshot = cursor_buffer.read(cx).snapshot();
6221 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6222 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6223 if start_word_range != end_word_range {
6224 self.document_highlights_task.take();
6225 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6226 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6227 return None;
6228 }
6229
6230 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6231 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6232 cx.background_executor()
6233 .timer(Duration::from_millis(debounce))
6234 .await;
6235
6236 let highlights = if let Some(highlights) = cx
6237 .update(|cx| {
6238 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6239 })
6240 .ok()
6241 .flatten()
6242 {
6243 highlights.await.log_err()
6244 } else {
6245 None
6246 };
6247
6248 if let Some(highlights) = highlights {
6249 this.update(cx, |this, cx| {
6250 if this.pending_rename.is_some() {
6251 return;
6252 }
6253
6254 let buffer_id = cursor_position.buffer_id;
6255 let buffer = this.buffer.read(cx);
6256 if !buffer
6257 .text_anchor_for_position(cursor_position, cx)
6258 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6259 {
6260 return;
6261 }
6262
6263 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6264 let mut write_ranges = Vec::new();
6265 let mut read_ranges = Vec::new();
6266 for highlight in highlights {
6267 for (excerpt_id, excerpt_range) in
6268 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6269 {
6270 let start = highlight
6271 .range
6272 .start
6273 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6274 let end = highlight
6275 .range
6276 .end
6277 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6278 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6279 continue;
6280 }
6281
6282 let range = Anchor {
6283 buffer_id,
6284 excerpt_id,
6285 text_anchor: start,
6286 diff_base_anchor: None,
6287 }..Anchor {
6288 buffer_id,
6289 excerpt_id,
6290 text_anchor: end,
6291 diff_base_anchor: None,
6292 };
6293 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6294 write_ranges.push(range);
6295 } else {
6296 read_ranges.push(range);
6297 }
6298 }
6299 }
6300
6301 this.highlight_background::<DocumentHighlightRead>(
6302 &read_ranges,
6303 |theme| theme.editor_document_highlight_read_background,
6304 cx,
6305 );
6306 this.highlight_background::<DocumentHighlightWrite>(
6307 &write_ranges,
6308 |theme| theme.editor_document_highlight_write_background,
6309 cx,
6310 );
6311 cx.notify();
6312 })
6313 .log_err();
6314 }
6315 }));
6316 None
6317 }
6318
6319 fn prepare_highlight_query_from_selection(
6320 &mut self,
6321 cx: &mut Context<Editor>,
6322 ) -> Option<(String, Range<Anchor>)> {
6323 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6324 return None;
6325 }
6326 if !EditorSettings::get_global(cx).selection_highlight {
6327 return None;
6328 }
6329 if self.selections.count() != 1 || self.selections.line_mode {
6330 return None;
6331 }
6332 let selection = self.selections.newest::<Point>(cx);
6333 if selection.is_empty() || selection.start.row != selection.end.row {
6334 return None;
6335 }
6336 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6337 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6338 let query = multi_buffer_snapshot
6339 .text_for_range(selection_anchor_range.clone())
6340 .collect::<String>();
6341 if query.trim().is_empty() {
6342 return None;
6343 }
6344 Some((query, selection_anchor_range))
6345 }
6346
6347 fn update_selection_occurrence_highlights(
6348 &mut self,
6349 query_text: String,
6350 query_range: Range<Anchor>,
6351 multi_buffer_range_to_query: Range<Point>,
6352 use_debounce: bool,
6353 window: &mut Window,
6354 cx: &mut Context<Editor>,
6355 ) -> Task<()> {
6356 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6357 cx.spawn_in(window, async move |editor, cx| {
6358 if use_debounce {
6359 cx.background_executor()
6360 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6361 .await;
6362 }
6363 let match_task = cx.background_spawn(async move {
6364 let buffer_ranges = multi_buffer_snapshot
6365 .range_to_buffer_ranges(multi_buffer_range_to_query)
6366 .into_iter()
6367 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6368 let mut match_ranges = Vec::new();
6369 let Ok(regex) = project::search::SearchQuery::text(
6370 query_text.clone(),
6371 false,
6372 false,
6373 false,
6374 Default::default(),
6375 Default::default(),
6376 false,
6377 None,
6378 ) else {
6379 return Vec::default();
6380 };
6381 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6382 match_ranges.extend(
6383 regex
6384 .search(&buffer_snapshot, Some(search_range.clone()))
6385 .await
6386 .into_iter()
6387 .filter_map(|match_range| {
6388 let match_start = buffer_snapshot
6389 .anchor_after(search_range.start + match_range.start);
6390 let match_end = buffer_snapshot
6391 .anchor_before(search_range.start + match_range.end);
6392 let match_anchor_range = Anchor::range_in_buffer(
6393 excerpt_id,
6394 buffer_snapshot.remote_id(),
6395 match_start..match_end,
6396 );
6397 (match_anchor_range != query_range).then_some(match_anchor_range)
6398 }),
6399 );
6400 }
6401 match_ranges
6402 });
6403 let match_ranges = match_task.await;
6404 editor
6405 .update_in(cx, |editor, _, cx| {
6406 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6407 if !match_ranges.is_empty() {
6408 editor.highlight_background::<SelectedTextHighlight>(
6409 &match_ranges,
6410 |theme| theme.editor_document_highlight_bracket_background,
6411 cx,
6412 )
6413 }
6414 })
6415 .log_err();
6416 })
6417 }
6418
6419 fn refresh_selected_text_highlights(
6420 &mut self,
6421 on_buffer_edit: bool,
6422 window: &mut Window,
6423 cx: &mut Context<Editor>,
6424 ) {
6425 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6426 else {
6427 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6428 self.quick_selection_highlight_task.take();
6429 self.debounced_selection_highlight_task.take();
6430 return;
6431 };
6432 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6433 if on_buffer_edit
6434 || self
6435 .quick_selection_highlight_task
6436 .as_ref()
6437 .map_or(true, |(prev_anchor_range, _)| {
6438 prev_anchor_range != &query_range
6439 })
6440 {
6441 let multi_buffer_visible_start = self
6442 .scroll_manager
6443 .anchor()
6444 .anchor
6445 .to_point(&multi_buffer_snapshot);
6446 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6447 multi_buffer_visible_start
6448 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6449 Bias::Left,
6450 );
6451 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6452 self.quick_selection_highlight_task = Some((
6453 query_range.clone(),
6454 self.update_selection_occurrence_highlights(
6455 query_text.clone(),
6456 query_range.clone(),
6457 multi_buffer_visible_range,
6458 false,
6459 window,
6460 cx,
6461 ),
6462 ));
6463 }
6464 if on_buffer_edit
6465 || self
6466 .debounced_selection_highlight_task
6467 .as_ref()
6468 .map_or(true, |(prev_anchor_range, _)| {
6469 prev_anchor_range != &query_range
6470 })
6471 {
6472 let multi_buffer_start = multi_buffer_snapshot
6473 .anchor_before(0)
6474 .to_point(&multi_buffer_snapshot);
6475 let multi_buffer_end = multi_buffer_snapshot
6476 .anchor_after(multi_buffer_snapshot.len())
6477 .to_point(&multi_buffer_snapshot);
6478 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6479 self.debounced_selection_highlight_task = Some((
6480 query_range.clone(),
6481 self.update_selection_occurrence_highlights(
6482 query_text,
6483 query_range,
6484 multi_buffer_full_range,
6485 true,
6486 window,
6487 cx,
6488 ),
6489 ));
6490 }
6491 }
6492
6493 pub fn refresh_inline_completion(
6494 &mut self,
6495 debounce: bool,
6496 user_requested: bool,
6497 window: &mut Window,
6498 cx: &mut Context<Self>,
6499 ) -> Option<()> {
6500 let provider = self.edit_prediction_provider()?;
6501 let cursor = self.selections.newest_anchor().head();
6502 let (buffer, cursor_buffer_position) =
6503 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6504
6505 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6506 self.discard_inline_completion(false, cx);
6507 return None;
6508 }
6509
6510 if !user_requested
6511 && (!self.should_show_edit_predictions()
6512 || !self.is_focused(window)
6513 || buffer.read(cx).is_empty())
6514 {
6515 self.discard_inline_completion(false, cx);
6516 return None;
6517 }
6518
6519 self.update_visible_inline_completion(window, cx);
6520 provider.refresh(
6521 self.project.clone(),
6522 buffer,
6523 cursor_buffer_position,
6524 debounce,
6525 cx,
6526 );
6527 Some(())
6528 }
6529
6530 fn show_edit_predictions_in_menu(&self) -> bool {
6531 match self.edit_prediction_settings {
6532 EditPredictionSettings::Disabled => false,
6533 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6534 }
6535 }
6536
6537 pub fn edit_predictions_enabled(&self) -> bool {
6538 match self.edit_prediction_settings {
6539 EditPredictionSettings::Disabled => false,
6540 EditPredictionSettings::Enabled { .. } => true,
6541 }
6542 }
6543
6544 fn edit_prediction_requires_modifier(&self) -> bool {
6545 match self.edit_prediction_settings {
6546 EditPredictionSettings::Disabled => false,
6547 EditPredictionSettings::Enabled {
6548 preview_requires_modifier,
6549 ..
6550 } => preview_requires_modifier,
6551 }
6552 }
6553
6554 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6555 if self.edit_prediction_provider.is_none() {
6556 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6557 } else {
6558 let selection = self.selections.newest_anchor();
6559 let cursor = selection.head();
6560
6561 if let Some((buffer, cursor_buffer_position)) =
6562 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6563 {
6564 self.edit_prediction_settings =
6565 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6566 }
6567 }
6568 }
6569
6570 fn edit_prediction_settings_at_position(
6571 &self,
6572 buffer: &Entity<Buffer>,
6573 buffer_position: language::Anchor,
6574 cx: &App,
6575 ) -> EditPredictionSettings {
6576 if !self.mode.is_full()
6577 || !self.show_inline_completions_override.unwrap_or(true)
6578 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6579 {
6580 return EditPredictionSettings::Disabled;
6581 }
6582
6583 let buffer = buffer.read(cx);
6584
6585 let file = buffer.file();
6586
6587 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6588 return EditPredictionSettings::Disabled;
6589 };
6590
6591 let by_provider = matches!(
6592 self.menu_inline_completions_policy,
6593 MenuInlineCompletionsPolicy::ByProvider
6594 );
6595
6596 let show_in_menu = by_provider
6597 && self
6598 .edit_prediction_provider
6599 .as_ref()
6600 .map_or(false, |provider| {
6601 provider.provider.show_completions_in_menu()
6602 });
6603
6604 let preview_requires_modifier =
6605 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6606
6607 EditPredictionSettings::Enabled {
6608 show_in_menu,
6609 preview_requires_modifier,
6610 }
6611 }
6612
6613 fn should_show_edit_predictions(&self) -> bool {
6614 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6615 }
6616
6617 pub fn edit_prediction_preview_is_active(&self) -> bool {
6618 matches!(
6619 self.edit_prediction_preview,
6620 EditPredictionPreview::Active { .. }
6621 )
6622 }
6623
6624 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6625 let cursor = self.selections.newest_anchor().head();
6626 if let Some((buffer, cursor_position)) =
6627 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6628 {
6629 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6630 } else {
6631 false
6632 }
6633 }
6634
6635 pub fn supports_minimap(&self, cx: &App) -> bool {
6636 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6637 }
6638
6639 fn edit_predictions_enabled_in_buffer(
6640 &self,
6641 buffer: &Entity<Buffer>,
6642 buffer_position: language::Anchor,
6643 cx: &App,
6644 ) -> bool {
6645 maybe!({
6646 if self.read_only(cx) {
6647 return Some(false);
6648 }
6649 let provider = self.edit_prediction_provider()?;
6650 if !provider.is_enabled(&buffer, buffer_position, cx) {
6651 return Some(false);
6652 }
6653 let buffer = buffer.read(cx);
6654 let Some(file) = buffer.file() else {
6655 return Some(true);
6656 };
6657 let settings = all_language_settings(Some(file), cx);
6658 Some(settings.edit_predictions_enabled_for_file(file, cx))
6659 })
6660 .unwrap_or(false)
6661 }
6662
6663 fn cycle_inline_completion(
6664 &mut self,
6665 direction: Direction,
6666 window: &mut Window,
6667 cx: &mut Context<Self>,
6668 ) -> Option<()> {
6669 let provider = self.edit_prediction_provider()?;
6670 let cursor = self.selections.newest_anchor().head();
6671 let (buffer, cursor_buffer_position) =
6672 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6673 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6674 return None;
6675 }
6676
6677 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6678 self.update_visible_inline_completion(window, cx);
6679
6680 Some(())
6681 }
6682
6683 pub fn show_inline_completion(
6684 &mut self,
6685 _: &ShowEditPrediction,
6686 window: &mut Window,
6687 cx: &mut Context<Self>,
6688 ) {
6689 if !self.has_active_inline_completion() {
6690 self.refresh_inline_completion(false, true, window, cx);
6691 return;
6692 }
6693
6694 self.update_visible_inline_completion(window, cx);
6695 }
6696
6697 pub fn display_cursor_names(
6698 &mut self,
6699 _: &DisplayCursorNames,
6700 window: &mut Window,
6701 cx: &mut Context<Self>,
6702 ) {
6703 self.show_cursor_names(window, cx);
6704 }
6705
6706 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6707 self.show_cursor_names = true;
6708 cx.notify();
6709 cx.spawn_in(window, async move |this, cx| {
6710 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6711 this.update(cx, |this, cx| {
6712 this.show_cursor_names = false;
6713 cx.notify()
6714 })
6715 .ok()
6716 })
6717 .detach();
6718 }
6719
6720 pub fn next_edit_prediction(
6721 &mut self,
6722 _: &NextEditPrediction,
6723 window: &mut Window,
6724 cx: &mut Context<Self>,
6725 ) {
6726 if self.has_active_inline_completion() {
6727 self.cycle_inline_completion(Direction::Next, window, cx);
6728 } else {
6729 let is_copilot_disabled = self
6730 .refresh_inline_completion(false, true, window, cx)
6731 .is_none();
6732 if is_copilot_disabled {
6733 cx.propagate();
6734 }
6735 }
6736 }
6737
6738 pub fn previous_edit_prediction(
6739 &mut self,
6740 _: &PreviousEditPrediction,
6741 window: &mut Window,
6742 cx: &mut Context<Self>,
6743 ) {
6744 if self.has_active_inline_completion() {
6745 self.cycle_inline_completion(Direction::Prev, window, cx);
6746 } else {
6747 let is_copilot_disabled = self
6748 .refresh_inline_completion(false, true, window, cx)
6749 .is_none();
6750 if is_copilot_disabled {
6751 cx.propagate();
6752 }
6753 }
6754 }
6755
6756 pub fn accept_edit_prediction(
6757 &mut self,
6758 _: &AcceptEditPrediction,
6759 window: &mut Window,
6760 cx: &mut Context<Self>,
6761 ) {
6762 if self.show_edit_predictions_in_menu() {
6763 self.hide_context_menu(window, cx);
6764 }
6765
6766 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6767 return;
6768 };
6769
6770 self.report_inline_completion_event(
6771 active_inline_completion.completion_id.clone(),
6772 true,
6773 cx,
6774 );
6775
6776 match &active_inline_completion.completion {
6777 InlineCompletion::Move { target, .. } => {
6778 let target = *target;
6779
6780 if let Some(position_map) = &self.last_position_map {
6781 if position_map
6782 .visible_row_range
6783 .contains(&target.to_display_point(&position_map.snapshot).row())
6784 || !self.edit_prediction_requires_modifier()
6785 {
6786 self.unfold_ranges(&[target..target], true, false, cx);
6787 // Note that this is also done in vim's handler of the Tab action.
6788 self.change_selections(
6789 Some(Autoscroll::newest()),
6790 window,
6791 cx,
6792 |selections| {
6793 selections.select_anchor_ranges([target..target]);
6794 },
6795 );
6796 self.clear_row_highlights::<EditPredictionPreview>();
6797
6798 self.edit_prediction_preview
6799 .set_previous_scroll_position(None);
6800 } else {
6801 self.edit_prediction_preview
6802 .set_previous_scroll_position(Some(
6803 position_map.snapshot.scroll_anchor,
6804 ));
6805
6806 self.highlight_rows::<EditPredictionPreview>(
6807 target..target,
6808 cx.theme().colors().editor_highlighted_line_background,
6809 RowHighlightOptions {
6810 autoscroll: true,
6811 ..Default::default()
6812 },
6813 cx,
6814 );
6815 self.request_autoscroll(Autoscroll::fit(), cx);
6816 }
6817 }
6818 }
6819 InlineCompletion::Edit { edits, .. } => {
6820 if let Some(provider) = self.edit_prediction_provider() {
6821 provider.accept(cx);
6822 }
6823
6824 // Store the transaction ID and selections before applying the edit
6825 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
6826
6827 let snapshot = self.buffer.read(cx).snapshot(cx);
6828 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6829
6830 self.buffer.update(cx, |buffer, cx| {
6831 buffer.edit(edits.iter().cloned(), None, cx)
6832 });
6833
6834 self.change_selections(None, window, cx, |s| {
6835 s.select_anchor_ranges([last_edit_end..last_edit_end]);
6836 });
6837
6838 let selections = self.selections.disjoint_anchors();
6839 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
6840 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
6841 if has_new_transaction {
6842 self.selection_history
6843 .insert_transaction(transaction_id_now, selections);
6844 }
6845 }
6846
6847 self.update_visible_inline_completion(window, cx);
6848 if self.active_inline_completion.is_none() {
6849 self.refresh_inline_completion(true, true, window, cx);
6850 }
6851
6852 cx.notify();
6853 }
6854 }
6855
6856 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6857 }
6858
6859 pub fn accept_partial_inline_completion(
6860 &mut self,
6861 _: &AcceptPartialEditPrediction,
6862 window: &mut Window,
6863 cx: &mut Context<Self>,
6864 ) {
6865 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6866 return;
6867 };
6868 if self.selections.count() != 1 {
6869 return;
6870 }
6871
6872 self.report_inline_completion_event(
6873 active_inline_completion.completion_id.clone(),
6874 true,
6875 cx,
6876 );
6877
6878 match &active_inline_completion.completion {
6879 InlineCompletion::Move { target, .. } => {
6880 let target = *target;
6881 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6882 selections.select_anchor_ranges([target..target]);
6883 });
6884 }
6885 InlineCompletion::Edit { edits, .. } => {
6886 // Find an insertion that starts at the cursor position.
6887 let snapshot = self.buffer.read(cx).snapshot(cx);
6888 let cursor_offset = self.selections.newest::<usize>(cx).head();
6889 let insertion = edits.iter().find_map(|(range, text)| {
6890 let range = range.to_offset(&snapshot);
6891 if range.is_empty() && range.start == cursor_offset {
6892 Some(text)
6893 } else {
6894 None
6895 }
6896 });
6897
6898 if let Some(text) = insertion {
6899 let mut partial_completion = text
6900 .chars()
6901 .by_ref()
6902 .take_while(|c| c.is_alphabetic())
6903 .collect::<String>();
6904 if partial_completion.is_empty() {
6905 partial_completion = text
6906 .chars()
6907 .by_ref()
6908 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6909 .collect::<String>();
6910 }
6911
6912 cx.emit(EditorEvent::InputHandled {
6913 utf16_range_to_replace: None,
6914 text: partial_completion.clone().into(),
6915 });
6916
6917 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6918
6919 self.refresh_inline_completion(true, true, window, cx);
6920 cx.notify();
6921 } else {
6922 self.accept_edit_prediction(&Default::default(), window, cx);
6923 }
6924 }
6925 }
6926 }
6927
6928 fn discard_inline_completion(
6929 &mut self,
6930 should_report_inline_completion_event: bool,
6931 cx: &mut Context<Self>,
6932 ) -> bool {
6933 if should_report_inline_completion_event {
6934 let completion_id = self
6935 .active_inline_completion
6936 .as_ref()
6937 .and_then(|active_completion| active_completion.completion_id.clone());
6938
6939 self.report_inline_completion_event(completion_id, false, cx);
6940 }
6941
6942 if let Some(provider) = self.edit_prediction_provider() {
6943 provider.discard(cx);
6944 }
6945
6946 self.take_active_inline_completion(cx)
6947 }
6948
6949 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6950 let Some(provider) = self.edit_prediction_provider() else {
6951 return;
6952 };
6953
6954 let Some((_, buffer, _)) = self
6955 .buffer
6956 .read(cx)
6957 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6958 else {
6959 return;
6960 };
6961
6962 let extension = buffer
6963 .read(cx)
6964 .file()
6965 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6966
6967 let event_type = match accepted {
6968 true => "Edit Prediction Accepted",
6969 false => "Edit Prediction Discarded",
6970 };
6971 telemetry::event!(
6972 event_type,
6973 provider = provider.name(),
6974 prediction_id = id,
6975 suggestion_accepted = accepted,
6976 file_extension = extension,
6977 );
6978 }
6979
6980 pub fn has_active_inline_completion(&self) -> bool {
6981 self.active_inline_completion.is_some()
6982 }
6983
6984 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6985 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6986 return false;
6987 };
6988
6989 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6990 self.clear_highlights::<InlineCompletionHighlight>(cx);
6991 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6992 true
6993 }
6994
6995 /// Returns true when we're displaying the edit prediction popover below the cursor
6996 /// like we are not previewing and the LSP autocomplete menu is visible
6997 /// or we are in `when_holding_modifier` mode.
6998 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6999 if self.edit_prediction_preview_is_active()
7000 || !self.show_edit_predictions_in_menu()
7001 || !self.edit_predictions_enabled()
7002 {
7003 return false;
7004 }
7005
7006 if self.has_visible_completions_menu() {
7007 return true;
7008 }
7009
7010 has_completion && self.edit_prediction_requires_modifier()
7011 }
7012
7013 fn handle_modifiers_changed(
7014 &mut self,
7015 modifiers: Modifiers,
7016 position_map: &PositionMap,
7017 window: &mut Window,
7018 cx: &mut Context<Self>,
7019 ) {
7020 if self.show_edit_predictions_in_menu() {
7021 self.update_edit_prediction_preview(&modifiers, window, cx);
7022 }
7023
7024 self.update_selection_mode(&modifiers, position_map, window, cx);
7025
7026 let mouse_position = window.mouse_position();
7027 if !position_map.text_hitbox.is_hovered(window) {
7028 return;
7029 }
7030
7031 self.update_hovered_link(
7032 position_map.point_for_position(mouse_position),
7033 &position_map.snapshot,
7034 modifiers,
7035 window,
7036 cx,
7037 )
7038 }
7039
7040 fn update_selection_mode(
7041 &mut self,
7042 modifiers: &Modifiers,
7043 position_map: &PositionMap,
7044 window: &mut Window,
7045 cx: &mut Context<Self>,
7046 ) {
7047 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
7048 return;
7049 }
7050
7051 let mouse_position = window.mouse_position();
7052 let point_for_position = position_map.point_for_position(mouse_position);
7053 let position = point_for_position.previous_valid;
7054
7055 self.select(
7056 SelectPhase::BeginColumnar {
7057 position,
7058 reset: false,
7059 goal_column: point_for_position.exact_unclipped.column(),
7060 },
7061 window,
7062 cx,
7063 );
7064 }
7065
7066 fn update_edit_prediction_preview(
7067 &mut self,
7068 modifiers: &Modifiers,
7069 window: &mut Window,
7070 cx: &mut Context<Self>,
7071 ) {
7072 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
7073 let Some(accept_keystroke) = accept_keybind.keystroke() else {
7074 return;
7075 };
7076
7077 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
7078 if matches!(
7079 self.edit_prediction_preview,
7080 EditPredictionPreview::Inactive { .. }
7081 ) {
7082 self.edit_prediction_preview = EditPredictionPreview::Active {
7083 previous_scroll_position: None,
7084 since: Instant::now(),
7085 };
7086
7087 self.update_visible_inline_completion(window, cx);
7088 cx.notify();
7089 }
7090 } else if let EditPredictionPreview::Active {
7091 previous_scroll_position,
7092 since,
7093 } = self.edit_prediction_preview
7094 {
7095 if let (Some(previous_scroll_position), Some(position_map)) =
7096 (previous_scroll_position, self.last_position_map.as_ref())
7097 {
7098 self.set_scroll_position(
7099 previous_scroll_position
7100 .scroll_position(&position_map.snapshot.display_snapshot),
7101 window,
7102 cx,
7103 );
7104 }
7105
7106 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7107 released_too_fast: since.elapsed() < Duration::from_millis(200),
7108 };
7109 self.clear_row_highlights::<EditPredictionPreview>();
7110 self.update_visible_inline_completion(window, cx);
7111 cx.notify();
7112 }
7113 }
7114
7115 fn update_visible_inline_completion(
7116 &mut self,
7117 _window: &mut Window,
7118 cx: &mut Context<Self>,
7119 ) -> Option<()> {
7120 let selection = self.selections.newest_anchor();
7121 let cursor = selection.head();
7122 let multibuffer = self.buffer.read(cx).snapshot(cx);
7123 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7124 let excerpt_id = cursor.excerpt_id;
7125
7126 let show_in_menu = self.show_edit_predictions_in_menu();
7127 let completions_menu_has_precedence = !show_in_menu
7128 && (self.context_menu.borrow().is_some()
7129 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7130
7131 if completions_menu_has_precedence
7132 || !offset_selection.is_empty()
7133 || self
7134 .active_inline_completion
7135 .as_ref()
7136 .map_or(false, |completion| {
7137 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7138 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7139 !invalidation_range.contains(&offset_selection.head())
7140 })
7141 {
7142 self.discard_inline_completion(false, cx);
7143 return None;
7144 }
7145
7146 self.take_active_inline_completion(cx);
7147 let Some(provider) = self.edit_prediction_provider() else {
7148 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7149 return None;
7150 };
7151
7152 let (buffer, cursor_buffer_position) =
7153 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7154
7155 self.edit_prediction_settings =
7156 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7157
7158 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7159
7160 if self.edit_prediction_indent_conflict {
7161 let cursor_point = cursor.to_point(&multibuffer);
7162
7163 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7164
7165 if let Some((_, indent)) = indents.iter().next() {
7166 if indent.len == cursor_point.column {
7167 self.edit_prediction_indent_conflict = false;
7168 }
7169 }
7170 }
7171
7172 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7173 let edits = inline_completion
7174 .edits
7175 .into_iter()
7176 .flat_map(|(range, new_text)| {
7177 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7178 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7179 Some((start..end, new_text))
7180 })
7181 .collect::<Vec<_>>();
7182 if edits.is_empty() {
7183 return None;
7184 }
7185
7186 let first_edit_start = edits.first().unwrap().0.start;
7187 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7188 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7189
7190 let last_edit_end = edits.last().unwrap().0.end;
7191 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7192 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7193
7194 let cursor_row = cursor.to_point(&multibuffer).row;
7195
7196 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7197
7198 let mut inlay_ids = Vec::new();
7199 let invalidation_row_range;
7200 let move_invalidation_row_range = if cursor_row < edit_start_row {
7201 Some(cursor_row..edit_end_row)
7202 } else if cursor_row > edit_end_row {
7203 Some(edit_start_row..cursor_row)
7204 } else {
7205 None
7206 };
7207 let is_move =
7208 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7209 let completion = if is_move {
7210 invalidation_row_range =
7211 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7212 let target = first_edit_start;
7213 InlineCompletion::Move { target, snapshot }
7214 } else {
7215 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7216 && !self.inline_completions_hidden_for_vim_mode;
7217
7218 if show_completions_in_buffer {
7219 if edits
7220 .iter()
7221 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7222 {
7223 let mut inlays = Vec::new();
7224 for (range, new_text) in &edits {
7225 let inlay = Inlay::inline_completion(
7226 post_inc(&mut self.next_inlay_id),
7227 range.start,
7228 new_text.as_str(),
7229 );
7230 inlay_ids.push(inlay.id);
7231 inlays.push(inlay);
7232 }
7233
7234 self.splice_inlays(&[], inlays, cx);
7235 } else {
7236 let background_color = cx.theme().status().deleted_background;
7237 self.highlight_text::<InlineCompletionHighlight>(
7238 edits.iter().map(|(range, _)| range.clone()).collect(),
7239 HighlightStyle {
7240 background_color: Some(background_color),
7241 ..Default::default()
7242 },
7243 cx,
7244 );
7245 }
7246 }
7247
7248 invalidation_row_range = edit_start_row..edit_end_row;
7249
7250 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7251 if provider.show_tab_accept_marker() {
7252 EditDisplayMode::TabAccept
7253 } else {
7254 EditDisplayMode::Inline
7255 }
7256 } else {
7257 EditDisplayMode::DiffPopover
7258 };
7259
7260 InlineCompletion::Edit {
7261 edits,
7262 edit_preview: inline_completion.edit_preview,
7263 display_mode,
7264 snapshot,
7265 }
7266 };
7267
7268 let invalidation_range = multibuffer
7269 .anchor_before(Point::new(invalidation_row_range.start, 0))
7270 ..multibuffer.anchor_after(Point::new(
7271 invalidation_row_range.end,
7272 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7273 ));
7274
7275 self.stale_inline_completion_in_menu = None;
7276 self.active_inline_completion = Some(InlineCompletionState {
7277 inlay_ids,
7278 completion,
7279 completion_id: inline_completion.id,
7280 invalidation_range,
7281 });
7282
7283 cx.notify();
7284
7285 Some(())
7286 }
7287
7288 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7289 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7290 }
7291
7292 fn clear_tasks(&mut self) {
7293 self.tasks.clear()
7294 }
7295
7296 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7297 if self.tasks.insert(key, value).is_some() {
7298 // This case should hopefully be rare, but just in case...
7299 log::error!(
7300 "multiple different run targets found on a single line, only the last target will be rendered"
7301 )
7302 }
7303 }
7304
7305 /// Get all display points of breakpoints that will be rendered within editor
7306 ///
7307 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7308 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7309 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7310 fn active_breakpoints(
7311 &self,
7312 range: Range<DisplayRow>,
7313 window: &mut Window,
7314 cx: &mut Context<Self>,
7315 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7316 let mut breakpoint_display_points = HashMap::default();
7317
7318 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7319 return breakpoint_display_points;
7320 };
7321
7322 let snapshot = self.snapshot(window, cx);
7323
7324 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7325 let Some(project) = self.project.as_ref() else {
7326 return breakpoint_display_points;
7327 };
7328
7329 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7330 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7331
7332 for (buffer_snapshot, range, excerpt_id) in
7333 multi_buffer_snapshot.range_to_buffer_ranges(range)
7334 {
7335 let Some(buffer) = project
7336 .read(cx)
7337 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7338 else {
7339 continue;
7340 };
7341 let breakpoints = breakpoint_store.read(cx).breakpoints(
7342 &buffer,
7343 Some(
7344 buffer_snapshot.anchor_before(range.start)
7345 ..buffer_snapshot.anchor_after(range.end),
7346 ),
7347 buffer_snapshot,
7348 cx,
7349 );
7350 for (breakpoint, state) in breakpoints {
7351 let multi_buffer_anchor =
7352 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7353 let position = multi_buffer_anchor
7354 .to_point(&multi_buffer_snapshot)
7355 .to_display_point(&snapshot);
7356
7357 breakpoint_display_points.insert(
7358 position.row(),
7359 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7360 );
7361 }
7362 }
7363
7364 breakpoint_display_points
7365 }
7366
7367 fn breakpoint_context_menu(
7368 &self,
7369 anchor: Anchor,
7370 window: &mut Window,
7371 cx: &mut Context<Self>,
7372 ) -> Entity<ui::ContextMenu> {
7373 let weak_editor = cx.weak_entity();
7374 let focus_handle = self.focus_handle(cx);
7375
7376 let row = self
7377 .buffer
7378 .read(cx)
7379 .snapshot(cx)
7380 .summary_for_anchor::<Point>(&anchor)
7381 .row;
7382
7383 let breakpoint = self
7384 .breakpoint_at_row(row, window, cx)
7385 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7386
7387 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7388 "Edit Log Breakpoint"
7389 } else {
7390 "Set Log Breakpoint"
7391 };
7392
7393 let condition_breakpoint_msg = if breakpoint
7394 .as_ref()
7395 .is_some_and(|bp| bp.1.condition.is_some())
7396 {
7397 "Edit Condition Breakpoint"
7398 } else {
7399 "Set Condition Breakpoint"
7400 };
7401
7402 let hit_condition_breakpoint_msg = if breakpoint
7403 .as_ref()
7404 .is_some_and(|bp| bp.1.hit_condition.is_some())
7405 {
7406 "Edit Hit Condition Breakpoint"
7407 } else {
7408 "Set Hit Condition Breakpoint"
7409 };
7410
7411 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7412 "Unset Breakpoint"
7413 } else {
7414 "Set Breakpoint"
7415 };
7416
7417 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
7418 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
7419
7420 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7421 BreakpointState::Enabled => Some("Disable"),
7422 BreakpointState::Disabled => Some("Enable"),
7423 });
7424
7425 let (anchor, breakpoint) =
7426 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7427
7428 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7429 menu.on_blur_subscription(Subscription::new(|| {}))
7430 .context(focus_handle)
7431 .when(run_to_cursor, |this| {
7432 let weak_editor = weak_editor.clone();
7433 this.entry("Run to cursor", None, move |window, cx| {
7434 weak_editor
7435 .update(cx, |editor, cx| {
7436 editor.change_selections(None, window, cx, |s| {
7437 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7438 });
7439 })
7440 .ok();
7441
7442 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
7443 })
7444 .separator()
7445 })
7446 .when_some(toggle_state_msg, |this, msg| {
7447 this.entry(msg, None, {
7448 let weak_editor = weak_editor.clone();
7449 let breakpoint = breakpoint.clone();
7450 move |_window, cx| {
7451 weak_editor
7452 .update(cx, |this, cx| {
7453 this.edit_breakpoint_at_anchor(
7454 anchor,
7455 breakpoint.as_ref().clone(),
7456 BreakpointEditAction::InvertState,
7457 cx,
7458 );
7459 })
7460 .log_err();
7461 }
7462 })
7463 })
7464 .entry(set_breakpoint_msg, None, {
7465 let weak_editor = weak_editor.clone();
7466 let breakpoint = breakpoint.clone();
7467 move |_window, cx| {
7468 weak_editor
7469 .update(cx, |this, cx| {
7470 this.edit_breakpoint_at_anchor(
7471 anchor,
7472 breakpoint.as_ref().clone(),
7473 BreakpointEditAction::Toggle,
7474 cx,
7475 );
7476 })
7477 .log_err();
7478 }
7479 })
7480 .entry(log_breakpoint_msg, None, {
7481 let breakpoint = breakpoint.clone();
7482 let weak_editor = weak_editor.clone();
7483 move |window, cx| {
7484 weak_editor
7485 .update(cx, |this, cx| {
7486 this.add_edit_breakpoint_block(
7487 anchor,
7488 breakpoint.as_ref(),
7489 BreakpointPromptEditAction::Log,
7490 window,
7491 cx,
7492 );
7493 })
7494 .log_err();
7495 }
7496 })
7497 .entry(condition_breakpoint_msg, None, {
7498 let breakpoint = breakpoint.clone();
7499 let weak_editor = weak_editor.clone();
7500 move |window, cx| {
7501 weak_editor
7502 .update(cx, |this, cx| {
7503 this.add_edit_breakpoint_block(
7504 anchor,
7505 breakpoint.as_ref(),
7506 BreakpointPromptEditAction::Condition,
7507 window,
7508 cx,
7509 );
7510 })
7511 .log_err();
7512 }
7513 })
7514 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7515 weak_editor
7516 .update(cx, |this, cx| {
7517 this.add_edit_breakpoint_block(
7518 anchor,
7519 breakpoint.as_ref(),
7520 BreakpointPromptEditAction::HitCondition,
7521 window,
7522 cx,
7523 );
7524 })
7525 .log_err();
7526 })
7527 })
7528 }
7529
7530 fn render_breakpoint(
7531 &self,
7532 position: Anchor,
7533 row: DisplayRow,
7534 breakpoint: &Breakpoint,
7535 state: Option<BreakpointSessionState>,
7536 cx: &mut Context<Self>,
7537 ) -> IconButton {
7538 let is_rejected = state.is_some_and(|s| !s.verified);
7539 // Is it a breakpoint that shows up when hovering over gutter?
7540 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7541 (false, false),
7542 |PhantomBreakpointIndicator {
7543 is_active,
7544 display_row,
7545 collides_with_existing_breakpoint,
7546 }| {
7547 (
7548 is_active && display_row == row,
7549 collides_with_existing_breakpoint,
7550 )
7551 },
7552 );
7553
7554 let (color, icon) = {
7555 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7556 (false, false) => ui::IconName::DebugBreakpoint,
7557 (true, false) => ui::IconName::DebugLogBreakpoint,
7558 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7559 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7560 };
7561
7562 let color = if is_phantom {
7563 Color::Hint
7564 } else if is_rejected {
7565 Color::Disabled
7566 } else {
7567 Color::Debugger
7568 };
7569
7570 (color, icon)
7571 };
7572
7573 let breakpoint = Arc::from(breakpoint.clone());
7574
7575 let alt_as_text = gpui::Keystroke {
7576 modifiers: Modifiers::secondary_key(),
7577 ..Default::default()
7578 };
7579 let primary_action_text = if breakpoint.is_disabled() {
7580 "Enable breakpoint"
7581 } else if is_phantom && !collides_with_existing {
7582 "Set breakpoint"
7583 } else {
7584 "Unset breakpoint"
7585 };
7586 let focus_handle = self.focus_handle.clone();
7587
7588 let meta = if is_rejected {
7589 SharedString::from("No executable code is associated with this line.")
7590 } else if collides_with_existing && !breakpoint.is_disabled() {
7591 SharedString::from(format!(
7592 "{alt_as_text}-click to disable,\nright-click for more options."
7593 ))
7594 } else {
7595 SharedString::from("Right-click for more options.")
7596 };
7597 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7598 .icon_size(IconSize::XSmall)
7599 .size(ui::ButtonSize::None)
7600 .when(is_rejected, |this| {
7601 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7602 })
7603 .icon_color(color)
7604 .style(ButtonStyle::Transparent)
7605 .on_click(cx.listener({
7606 let breakpoint = breakpoint.clone();
7607
7608 move |editor, event: &ClickEvent, window, cx| {
7609 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7610 BreakpointEditAction::InvertState
7611 } else {
7612 BreakpointEditAction::Toggle
7613 };
7614
7615 window.focus(&editor.focus_handle(cx));
7616 editor.edit_breakpoint_at_anchor(
7617 position,
7618 breakpoint.as_ref().clone(),
7619 edit_action,
7620 cx,
7621 );
7622 }
7623 }))
7624 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7625 editor.set_breakpoint_context_menu(
7626 row,
7627 Some(position),
7628 event.down.position,
7629 window,
7630 cx,
7631 );
7632 }))
7633 .tooltip(move |window, cx| {
7634 Tooltip::with_meta_in(
7635 primary_action_text,
7636 Some(&ToggleBreakpoint),
7637 meta.clone(),
7638 &focus_handle,
7639 window,
7640 cx,
7641 )
7642 })
7643 }
7644
7645 fn build_tasks_context(
7646 project: &Entity<Project>,
7647 buffer: &Entity<Buffer>,
7648 buffer_row: u32,
7649 tasks: &Arc<RunnableTasks>,
7650 cx: &mut Context<Self>,
7651 ) -> Task<Option<task::TaskContext>> {
7652 let position = Point::new(buffer_row, tasks.column);
7653 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7654 let location = Location {
7655 buffer: buffer.clone(),
7656 range: range_start..range_start,
7657 };
7658 // Fill in the environmental variables from the tree-sitter captures
7659 let mut captured_task_variables = TaskVariables::default();
7660 for (capture_name, value) in tasks.extra_variables.clone() {
7661 captured_task_variables.insert(
7662 task::VariableName::Custom(capture_name.into()),
7663 value.clone(),
7664 );
7665 }
7666 project.update(cx, |project, cx| {
7667 project.task_store().update(cx, |task_store, cx| {
7668 task_store.task_context_for_location(captured_task_variables, location, cx)
7669 })
7670 })
7671 }
7672
7673 pub fn spawn_nearest_task(
7674 &mut self,
7675 action: &SpawnNearestTask,
7676 window: &mut Window,
7677 cx: &mut Context<Self>,
7678 ) {
7679 let Some((workspace, _)) = self.workspace.clone() else {
7680 return;
7681 };
7682 let Some(project) = self.project.clone() else {
7683 return;
7684 };
7685
7686 // Try to find a closest, enclosing node using tree-sitter that has a
7687 // task
7688 let Some((buffer, buffer_row, tasks)) = self
7689 .find_enclosing_node_task(cx)
7690 // Or find the task that's closest in row-distance.
7691 .or_else(|| self.find_closest_task(cx))
7692 else {
7693 return;
7694 };
7695
7696 let reveal_strategy = action.reveal;
7697 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7698 cx.spawn_in(window, async move |_, cx| {
7699 let context = task_context.await?;
7700 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7701
7702 let resolved = &mut resolved_task.resolved;
7703 resolved.reveal = reveal_strategy;
7704
7705 workspace
7706 .update_in(cx, |workspace, window, cx| {
7707 workspace.schedule_resolved_task(
7708 task_source_kind,
7709 resolved_task,
7710 false,
7711 window,
7712 cx,
7713 );
7714 })
7715 .ok()
7716 })
7717 .detach();
7718 }
7719
7720 fn find_closest_task(
7721 &mut self,
7722 cx: &mut Context<Self>,
7723 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7724 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7725
7726 let ((buffer_id, row), tasks) = self
7727 .tasks
7728 .iter()
7729 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7730
7731 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7732 let tasks = Arc::new(tasks.to_owned());
7733 Some((buffer, *row, tasks))
7734 }
7735
7736 fn find_enclosing_node_task(
7737 &mut self,
7738 cx: &mut Context<Self>,
7739 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7740 let snapshot = self.buffer.read(cx).snapshot(cx);
7741 let offset = self.selections.newest::<usize>(cx).head();
7742 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7743 let buffer_id = excerpt.buffer().remote_id();
7744
7745 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7746 let mut cursor = layer.node().walk();
7747
7748 while cursor.goto_first_child_for_byte(offset).is_some() {
7749 if cursor.node().end_byte() == offset {
7750 cursor.goto_next_sibling();
7751 }
7752 }
7753
7754 // Ascend to the smallest ancestor that contains the range and has a task.
7755 loop {
7756 let node = cursor.node();
7757 let node_range = node.byte_range();
7758 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7759
7760 // Check if this node contains our offset
7761 if node_range.start <= offset && node_range.end >= offset {
7762 // If it contains offset, check for task
7763 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7764 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7765 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7766 }
7767 }
7768
7769 if !cursor.goto_parent() {
7770 break;
7771 }
7772 }
7773 None
7774 }
7775
7776 fn render_run_indicator(
7777 &self,
7778 _style: &EditorStyle,
7779 is_active: bool,
7780 row: DisplayRow,
7781 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7782 cx: &mut Context<Self>,
7783 ) -> IconButton {
7784 let color = Color::Muted;
7785 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7786
7787 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7788 .shape(ui::IconButtonShape::Square)
7789 .icon_size(IconSize::XSmall)
7790 .icon_color(color)
7791 .toggle_state(is_active)
7792 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7793 let quick_launch = e.down.button == MouseButton::Left;
7794 window.focus(&editor.focus_handle(cx));
7795 editor.toggle_code_actions(
7796 &ToggleCodeActions {
7797 deployed_from: Some(CodeActionSource::Indicator(row)),
7798 quick_launch,
7799 },
7800 window,
7801 cx,
7802 );
7803 }))
7804 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7805 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7806 }))
7807 }
7808
7809 pub fn context_menu_visible(&self) -> bool {
7810 !self.edit_prediction_preview_is_active()
7811 && self
7812 .context_menu
7813 .borrow()
7814 .as_ref()
7815 .map_or(false, |menu| menu.visible())
7816 }
7817
7818 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7819 self.context_menu
7820 .borrow()
7821 .as_ref()
7822 .map(|menu| menu.origin())
7823 }
7824
7825 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7826 self.context_menu_options = Some(options);
7827 }
7828
7829 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7830 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7831
7832 fn render_edit_prediction_popover(
7833 &mut self,
7834 text_bounds: &Bounds<Pixels>,
7835 content_origin: gpui::Point<Pixels>,
7836 right_margin: Pixels,
7837 editor_snapshot: &EditorSnapshot,
7838 visible_row_range: Range<DisplayRow>,
7839 scroll_top: f32,
7840 scroll_bottom: f32,
7841 line_layouts: &[LineWithInvisibles],
7842 line_height: Pixels,
7843 scroll_pixel_position: gpui::Point<Pixels>,
7844 newest_selection_head: Option<DisplayPoint>,
7845 editor_width: Pixels,
7846 style: &EditorStyle,
7847 window: &mut Window,
7848 cx: &mut App,
7849 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7850 if self.mode().is_minimap() {
7851 return None;
7852 }
7853 let active_inline_completion = self.active_inline_completion.as_ref()?;
7854
7855 if self.edit_prediction_visible_in_cursor_popover(true) {
7856 return None;
7857 }
7858
7859 match &active_inline_completion.completion {
7860 InlineCompletion::Move { target, .. } => {
7861 let target_display_point = target.to_display_point(editor_snapshot);
7862
7863 if self.edit_prediction_requires_modifier() {
7864 if !self.edit_prediction_preview_is_active() {
7865 return None;
7866 }
7867
7868 self.render_edit_prediction_modifier_jump_popover(
7869 text_bounds,
7870 content_origin,
7871 visible_row_range,
7872 line_layouts,
7873 line_height,
7874 scroll_pixel_position,
7875 newest_selection_head,
7876 target_display_point,
7877 window,
7878 cx,
7879 )
7880 } else {
7881 self.render_edit_prediction_eager_jump_popover(
7882 text_bounds,
7883 content_origin,
7884 editor_snapshot,
7885 visible_row_range,
7886 scroll_top,
7887 scroll_bottom,
7888 line_height,
7889 scroll_pixel_position,
7890 target_display_point,
7891 editor_width,
7892 window,
7893 cx,
7894 )
7895 }
7896 }
7897 InlineCompletion::Edit {
7898 display_mode: EditDisplayMode::Inline,
7899 ..
7900 } => None,
7901 InlineCompletion::Edit {
7902 display_mode: EditDisplayMode::TabAccept,
7903 edits,
7904 ..
7905 } => {
7906 let range = &edits.first()?.0;
7907 let target_display_point = range.end.to_display_point(editor_snapshot);
7908
7909 self.render_edit_prediction_end_of_line_popover(
7910 "Accept",
7911 editor_snapshot,
7912 visible_row_range,
7913 target_display_point,
7914 line_height,
7915 scroll_pixel_position,
7916 content_origin,
7917 editor_width,
7918 window,
7919 cx,
7920 )
7921 }
7922 InlineCompletion::Edit {
7923 edits,
7924 edit_preview,
7925 display_mode: EditDisplayMode::DiffPopover,
7926 snapshot,
7927 } => self.render_edit_prediction_diff_popover(
7928 text_bounds,
7929 content_origin,
7930 right_margin,
7931 editor_snapshot,
7932 visible_row_range,
7933 line_layouts,
7934 line_height,
7935 scroll_pixel_position,
7936 newest_selection_head,
7937 editor_width,
7938 style,
7939 edits,
7940 edit_preview,
7941 snapshot,
7942 window,
7943 cx,
7944 ),
7945 }
7946 }
7947
7948 fn render_edit_prediction_modifier_jump_popover(
7949 &mut self,
7950 text_bounds: &Bounds<Pixels>,
7951 content_origin: gpui::Point<Pixels>,
7952 visible_row_range: Range<DisplayRow>,
7953 line_layouts: &[LineWithInvisibles],
7954 line_height: Pixels,
7955 scroll_pixel_position: gpui::Point<Pixels>,
7956 newest_selection_head: Option<DisplayPoint>,
7957 target_display_point: DisplayPoint,
7958 window: &mut Window,
7959 cx: &mut App,
7960 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7961 let scrolled_content_origin =
7962 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7963
7964 const SCROLL_PADDING_Y: Pixels = px(12.);
7965
7966 if target_display_point.row() < visible_row_range.start {
7967 return self.render_edit_prediction_scroll_popover(
7968 |_| SCROLL_PADDING_Y,
7969 IconName::ArrowUp,
7970 visible_row_range,
7971 line_layouts,
7972 newest_selection_head,
7973 scrolled_content_origin,
7974 window,
7975 cx,
7976 );
7977 } else if target_display_point.row() >= visible_row_range.end {
7978 return self.render_edit_prediction_scroll_popover(
7979 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7980 IconName::ArrowDown,
7981 visible_row_range,
7982 line_layouts,
7983 newest_selection_head,
7984 scrolled_content_origin,
7985 window,
7986 cx,
7987 );
7988 }
7989
7990 const POLE_WIDTH: Pixels = px(2.);
7991
7992 let line_layout =
7993 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7994 let target_column = target_display_point.column() as usize;
7995
7996 let target_x = line_layout.x_for_index(target_column);
7997 let target_y =
7998 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7999
8000 let flag_on_right = target_x < text_bounds.size.width / 2.;
8001
8002 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8003 border_color.l += 0.001;
8004
8005 let mut element = v_flex()
8006 .items_end()
8007 .when(flag_on_right, |el| el.items_start())
8008 .child(if flag_on_right {
8009 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8010 .rounded_bl(px(0.))
8011 .rounded_tl(px(0.))
8012 .border_l_2()
8013 .border_color(border_color)
8014 } else {
8015 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8016 .rounded_br(px(0.))
8017 .rounded_tr(px(0.))
8018 .border_r_2()
8019 .border_color(border_color)
8020 })
8021 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8022 .into_any();
8023
8024 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8025
8026 let mut origin = scrolled_content_origin + point(target_x, target_y)
8027 - point(
8028 if flag_on_right {
8029 POLE_WIDTH
8030 } else {
8031 size.width - POLE_WIDTH
8032 },
8033 size.height - line_height,
8034 );
8035
8036 origin.x = origin.x.max(content_origin.x);
8037
8038 element.prepaint_at(origin, window, cx);
8039
8040 Some((element, origin))
8041 }
8042
8043 fn render_edit_prediction_scroll_popover(
8044 &mut self,
8045 to_y: impl Fn(Size<Pixels>) -> Pixels,
8046 scroll_icon: IconName,
8047 visible_row_range: Range<DisplayRow>,
8048 line_layouts: &[LineWithInvisibles],
8049 newest_selection_head: Option<DisplayPoint>,
8050 scrolled_content_origin: gpui::Point<Pixels>,
8051 window: &mut Window,
8052 cx: &mut App,
8053 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8054 let mut element = self
8055 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8056 .into_any();
8057
8058 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8059
8060 let cursor = newest_selection_head?;
8061 let cursor_row_layout =
8062 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8063 let cursor_column = cursor.column() as usize;
8064
8065 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8066
8067 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8068
8069 element.prepaint_at(origin, window, cx);
8070 Some((element, origin))
8071 }
8072
8073 fn render_edit_prediction_eager_jump_popover(
8074 &mut self,
8075 text_bounds: &Bounds<Pixels>,
8076 content_origin: gpui::Point<Pixels>,
8077 editor_snapshot: &EditorSnapshot,
8078 visible_row_range: Range<DisplayRow>,
8079 scroll_top: f32,
8080 scroll_bottom: f32,
8081 line_height: Pixels,
8082 scroll_pixel_position: gpui::Point<Pixels>,
8083 target_display_point: DisplayPoint,
8084 editor_width: Pixels,
8085 window: &mut Window,
8086 cx: &mut App,
8087 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8088 if target_display_point.row().as_f32() < scroll_top {
8089 let mut element = self
8090 .render_edit_prediction_line_popover(
8091 "Jump to Edit",
8092 Some(IconName::ArrowUp),
8093 window,
8094 cx,
8095 )?
8096 .into_any();
8097
8098 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8099 let offset = point(
8100 (text_bounds.size.width - size.width) / 2.,
8101 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8102 );
8103
8104 let origin = text_bounds.origin + offset;
8105 element.prepaint_at(origin, window, cx);
8106 Some((element, origin))
8107 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8108 let mut element = self
8109 .render_edit_prediction_line_popover(
8110 "Jump to Edit",
8111 Some(IconName::ArrowDown),
8112 window,
8113 cx,
8114 )?
8115 .into_any();
8116
8117 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8118 let offset = point(
8119 (text_bounds.size.width - size.width) / 2.,
8120 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8121 );
8122
8123 let origin = text_bounds.origin + offset;
8124 element.prepaint_at(origin, window, cx);
8125 Some((element, origin))
8126 } else {
8127 self.render_edit_prediction_end_of_line_popover(
8128 "Jump to Edit",
8129 editor_snapshot,
8130 visible_row_range,
8131 target_display_point,
8132 line_height,
8133 scroll_pixel_position,
8134 content_origin,
8135 editor_width,
8136 window,
8137 cx,
8138 )
8139 }
8140 }
8141
8142 fn render_edit_prediction_end_of_line_popover(
8143 self: &mut Editor,
8144 label: &'static str,
8145 editor_snapshot: &EditorSnapshot,
8146 visible_row_range: Range<DisplayRow>,
8147 target_display_point: DisplayPoint,
8148 line_height: Pixels,
8149 scroll_pixel_position: gpui::Point<Pixels>,
8150 content_origin: gpui::Point<Pixels>,
8151 editor_width: Pixels,
8152 window: &mut Window,
8153 cx: &mut App,
8154 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8155 let target_line_end = DisplayPoint::new(
8156 target_display_point.row(),
8157 editor_snapshot.line_len(target_display_point.row()),
8158 );
8159
8160 let mut element = self
8161 .render_edit_prediction_line_popover(label, None, window, cx)?
8162 .into_any();
8163
8164 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8165
8166 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8167
8168 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8169 let mut origin = start_point
8170 + line_origin
8171 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8172 origin.x = origin.x.max(content_origin.x);
8173
8174 let max_x = content_origin.x + editor_width - size.width;
8175
8176 if origin.x > max_x {
8177 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8178
8179 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8180 origin.y += offset;
8181 IconName::ArrowUp
8182 } else {
8183 origin.y -= offset;
8184 IconName::ArrowDown
8185 };
8186
8187 element = self
8188 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8189 .into_any();
8190
8191 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8192
8193 origin.x = content_origin.x + editor_width - size.width - px(2.);
8194 }
8195
8196 element.prepaint_at(origin, window, cx);
8197 Some((element, origin))
8198 }
8199
8200 fn render_edit_prediction_diff_popover(
8201 self: &Editor,
8202 text_bounds: &Bounds<Pixels>,
8203 content_origin: gpui::Point<Pixels>,
8204 right_margin: Pixels,
8205 editor_snapshot: &EditorSnapshot,
8206 visible_row_range: Range<DisplayRow>,
8207 line_layouts: &[LineWithInvisibles],
8208 line_height: Pixels,
8209 scroll_pixel_position: gpui::Point<Pixels>,
8210 newest_selection_head: Option<DisplayPoint>,
8211 editor_width: Pixels,
8212 style: &EditorStyle,
8213 edits: &Vec<(Range<Anchor>, String)>,
8214 edit_preview: &Option<language::EditPreview>,
8215 snapshot: &language::BufferSnapshot,
8216 window: &mut Window,
8217 cx: &mut App,
8218 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8219 let edit_start = edits
8220 .first()
8221 .unwrap()
8222 .0
8223 .start
8224 .to_display_point(editor_snapshot);
8225 let edit_end = edits
8226 .last()
8227 .unwrap()
8228 .0
8229 .end
8230 .to_display_point(editor_snapshot);
8231
8232 let is_visible = visible_row_range.contains(&edit_start.row())
8233 || visible_row_range.contains(&edit_end.row());
8234 if !is_visible {
8235 return None;
8236 }
8237
8238 let highlighted_edits =
8239 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8240
8241 let styled_text = highlighted_edits.to_styled_text(&style.text);
8242 let line_count = highlighted_edits.text.lines().count();
8243
8244 const BORDER_WIDTH: Pixels = px(1.);
8245
8246 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8247 let has_keybind = keybind.is_some();
8248
8249 let mut element = h_flex()
8250 .items_start()
8251 .child(
8252 h_flex()
8253 .bg(cx.theme().colors().editor_background)
8254 .border(BORDER_WIDTH)
8255 .shadow_sm()
8256 .border_color(cx.theme().colors().border)
8257 .rounded_l_lg()
8258 .when(line_count > 1, |el| el.rounded_br_lg())
8259 .pr_1()
8260 .child(styled_text),
8261 )
8262 .child(
8263 h_flex()
8264 .h(line_height + BORDER_WIDTH * 2.)
8265 .px_1p5()
8266 .gap_1()
8267 // Workaround: For some reason, there's a gap if we don't do this
8268 .ml(-BORDER_WIDTH)
8269 .shadow(vec![gpui::BoxShadow {
8270 color: gpui::black().opacity(0.05),
8271 offset: point(px(1.), px(1.)),
8272 blur_radius: px(2.),
8273 spread_radius: px(0.),
8274 }])
8275 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8276 .border(BORDER_WIDTH)
8277 .border_color(cx.theme().colors().border)
8278 .rounded_r_lg()
8279 .id("edit_prediction_diff_popover_keybind")
8280 .when(!has_keybind, |el| {
8281 let status_colors = cx.theme().status();
8282
8283 el.bg(status_colors.error_background)
8284 .border_color(status_colors.error.opacity(0.6))
8285 .child(Icon::new(IconName::Info).color(Color::Error))
8286 .cursor_default()
8287 .hoverable_tooltip(move |_window, cx| {
8288 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8289 })
8290 })
8291 .children(keybind),
8292 )
8293 .into_any();
8294
8295 let longest_row =
8296 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8297 let longest_line_width = if visible_row_range.contains(&longest_row) {
8298 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8299 } else {
8300 layout_line(
8301 longest_row,
8302 editor_snapshot,
8303 style,
8304 editor_width,
8305 |_| false,
8306 window,
8307 cx,
8308 )
8309 .width
8310 };
8311
8312 let viewport_bounds =
8313 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8314 right: -right_margin,
8315 ..Default::default()
8316 });
8317
8318 let x_after_longest =
8319 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8320 - scroll_pixel_position.x;
8321
8322 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8323
8324 // Fully visible if it can be displayed within the window (allow overlapping other
8325 // panes). However, this is only allowed if the popover starts within text_bounds.
8326 let can_position_to_the_right = x_after_longest < text_bounds.right()
8327 && x_after_longest + element_bounds.width < viewport_bounds.right();
8328
8329 let mut origin = if can_position_to_the_right {
8330 point(
8331 x_after_longest,
8332 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8333 - scroll_pixel_position.y,
8334 )
8335 } else {
8336 let cursor_row = newest_selection_head.map(|head| head.row());
8337 let above_edit = edit_start
8338 .row()
8339 .0
8340 .checked_sub(line_count as u32)
8341 .map(DisplayRow);
8342 let below_edit = Some(edit_end.row() + 1);
8343 let above_cursor =
8344 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8345 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8346
8347 // Place the edit popover adjacent to the edit if there is a location
8348 // available that is onscreen and does not obscure the cursor. Otherwise,
8349 // place it adjacent to the cursor.
8350 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8351 .into_iter()
8352 .flatten()
8353 .find(|&start_row| {
8354 let end_row = start_row + line_count as u32;
8355 visible_row_range.contains(&start_row)
8356 && visible_row_range.contains(&end_row)
8357 && cursor_row.map_or(true, |cursor_row| {
8358 !((start_row..end_row).contains(&cursor_row))
8359 })
8360 })?;
8361
8362 content_origin
8363 + point(
8364 -scroll_pixel_position.x,
8365 row_target.as_f32() * line_height - scroll_pixel_position.y,
8366 )
8367 };
8368
8369 origin.x -= BORDER_WIDTH;
8370
8371 window.defer_draw(element, origin, 1);
8372
8373 // Do not return an element, since it will already be drawn due to defer_draw.
8374 None
8375 }
8376
8377 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8378 px(30.)
8379 }
8380
8381 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8382 if self.read_only(cx) {
8383 cx.theme().players().read_only()
8384 } else {
8385 self.style.as_ref().unwrap().local_player
8386 }
8387 }
8388
8389 fn render_edit_prediction_accept_keybind(
8390 &self,
8391 window: &mut Window,
8392 cx: &App,
8393 ) -> Option<AnyElement> {
8394 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
8395 let accept_keystroke = accept_binding.keystroke()?;
8396
8397 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8398
8399 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8400 Color::Accent
8401 } else {
8402 Color::Muted
8403 };
8404
8405 h_flex()
8406 .px_0p5()
8407 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8408 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8409 .text_size(TextSize::XSmall.rems(cx))
8410 .child(h_flex().children(ui::render_modifiers(
8411 &accept_keystroke.modifiers,
8412 PlatformStyle::platform(),
8413 Some(modifiers_color),
8414 Some(IconSize::XSmall.rems().into()),
8415 true,
8416 )))
8417 .when(is_platform_style_mac, |parent| {
8418 parent.child(accept_keystroke.key.clone())
8419 })
8420 .when(!is_platform_style_mac, |parent| {
8421 parent.child(
8422 Key::new(
8423 util::capitalize(&accept_keystroke.key),
8424 Some(Color::Default),
8425 )
8426 .size(Some(IconSize::XSmall.rems().into())),
8427 )
8428 })
8429 .into_any()
8430 .into()
8431 }
8432
8433 fn render_edit_prediction_line_popover(
8434 &self,
8435 label: impl Into<SharedString>,
8436 icon: Option<IconName>,
8437 window: &mut Window,
8438 cx: &App,
8439 ) -> Option<Stateful<Div>> {
8440 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8441
8442 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8443 let has_keybind = keybind.is_some();
8444
8445 let result = h_flex()
8446 .id("ep-line-popover")
8447 .py_0p5()
8448 .pl_1()
8449 .pr(padding_right)
8450 .gap_1()
8451 .rounded_md()
8452 .border_1()
8453 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8454 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8455 .shadow_sm()
8456 .when(!has_keybind, |el| {
8457 let status_colors = cx.theme().status();
8458
8459 el.bg(status_colors.error_background)
8460 .border_color(status_colors.error.opacity(0.6))
8461 .pl_2()
8462 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8463 .cursor_default()
8464 .hoverable_tooltip(move |_window, cx| {
8465 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8466 })
8467 })
8468 .children(keybind)
8469 .child(
8470 Label::new(label)
8471 .size(LabelSize::Small)
8472 .when(!has_keybind, |el| {
8473 el.color(cx.theme().status().error.into()).strikethrough()
8474 }),
8475 )
8476 .when(!has_keybind, |el| {
8477 el.child(
8478 h_flex().ml_1().child(
8479 Icon::new(IconName::Info)
8480 .size(IconSize::Small)
8481 .color(cx.theme().status().error.into()),
8482 ),
8483 )
8484 })
8485 .when_some(icon, |element, icon| {
8486 element.child(
8487 div()
8488 .mt(px(1.5))
8489 .child(Icon::new(icon).size(IconSize::Small)),
8490 )
8491 });
8492
8493 Some(result)
8494 }
8495
8496 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8497 let accent_color = cx.theme().colors().text_accent;
8498 let editor_bg_color = cx.theme().colors().editor_background;
8499 editor_bg_color.blend(accent_color.opacity(0.1))
8500 }
8501
8502 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8503 let accent_color = cx.theme().colors().text_accent;
8504 let editor_bg_color = cx.theme().colors().editor_background;
8505 editor_bg_color.blend(accent_color.opacity(0.6))
8506 }
8507
8508 fn render_edit_prediction_cursor_popover(
8509 &self,
8510 min_width: Pixels,
8511 max_width: Pixels,
8512 cursor_point: Point,
8513 style: &EditorStyle,
8514 accept_keystroke: Option<&gpui::Keystroke>,
8515 _window: &Window,
8516 cx: &mut Context<Editor>,
8517 ) -> Option<AnyElement> {
8518 let provider = self.edit_prediction_provider.as_ref()?;
8519
8520 if provider.provider.needs_terms_acceptance(cx) {
8521 return Some(
8522 h_flex()
8523 .min_w(min_width)
8524 .flex_1()
8525 .px_2()
8526 .py_1()
8527 .gap_3()
8528 .elevation_2(cx)
8529 .hover(|style| style.bg(cx.theme().colors().element_hover))
8530 .id("accept-terms")
8531 .cursor_pointer()
8532 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8533 .on_click(cx.listener(|this, _event, window, cx| {
8534 cx.stop_propagation();
8535 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8536 window.dispatch_action(
8537 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8538 cx,
8539 );
8540 }))
8541 .child(
8542 h_flex()
8543 .flex_1()
8544 .gap_2()
8545 .child(Icon::new(IconName::ZedPredict))
8546 .child(Label::new("Accept Terms of Service"))
8547 .child(div().w_full())
8548 .child(
8549 Icon::new(IconName::ArrowUpRight)
8550 .color(Color::Muted)
8551 .size(IconSize::Small),
8552 )
8553 .into_any_element(),
8554 )
8555 .into_any(),
8556 );
8557 }
8558
8559 let is_refreshing = provider.provider.is_refreshing(cx);
8560
8561 fn pending_completion_container() -> Div {
8562 h_flex()
8563 .h_full()
8564 .flex_1()
8565 .gap_2()
8566 .child(Icon::new(IconName::ZedPredict))
8567 }
8568
8569 let completion = match &self.active_inline_completion {
8570 Some(prediction) => {
8571 if !self.has_visible_completions_menu() {
8572 const RADIUS: Pixels = px(6.);
8573 const BORDER_WIDTH: Pixels = px(1.);
8574
8575 return Some(
8576 h_flex()
8577 .elevation_2(cx)
8578 .border(BORDER_WIDTH)
8579 .border_color(cx.theme().colors().border)
8580 .when(accept_keystroke.is_none(), |el| {
8581 el.border_color(cx.theme().status().error)
8582 })
8583 .rounded(RADIUS)
8584 .rounded_tl(px(0.))
8585 .overflow_hidden()
8586 .child(div().px_1p5().child(match &prediction.completion {
8587 InlineCompletion::Move { target, snapshot } => {
8588 use text::ToPoint as _;
8589 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8590 {
8591 Icon::new(IconName::ZedPredictDown)
8592 } else {
8593 Icon::new(IconName::ZedPredictUp)
8594 }
8595 }
8596 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8597 }))
8598 .child(
8599 h_flex()
8600 .gap_1()
8601 .py_1()
8602 .px_2()
8603 .rounded_r(RADIUS - BORDER_WIDTH)
8604 .border_l_1()
8605 .border_color(cx.theme().colors().border)
8606 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8607 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8608 el.child(
8609 Label::new("Hold")
8610 .size(LabelSize::Small)
8611 .when(accept_keystroke.is_none(), |el| {
8612 el.strikethrough()
8613 })
8614 .line_height_style(LineHeightStyle::UiLabel),
8615 )
8616 })
8617 .id("edit_prediction_cursor_popover_keybind")
8618 .when(accept_keystroke.is_none(), |el| {
8619 let status_colors = cx.theme().status();
8620
8621 el.bg(status_colors.error_background)
8622 .border_color(status_colors.error.opacity(0.6))
8623 .child(Icon::new(IconName::Info).color(Color::Error))
8624 .cursor_default()
8625 .hoverable_tooltip(move |_window, cx| {
8626 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8627 .into()
8628 })
8629 })
8630 .when_some(
8631 accept_keystroke.as_ref(),
8632 |el, accept_keystroke| {
8633 el.child(h_flex().children(ui::render_modifiers(
8634 &accept_keystroke.modifiers,
8635 PlatformStyle::platform(),
8636 Some(Color::Default),
8637 Some(IconSize::XSmall.rems().into()),
8638 false,
8639 )))
8640 },
8641 ),
8642 )
8643 .into_any(),
8644 );
8645 }
8646
8647 self.render_edit_prediction_cursor_popover_preview(
8648 prediction,
8649 cursor_point,
8650 style,
8651 cx,
8652 )?
8653 }
8654
8655 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8656 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8657 stale_completion,
8658 cursor_point,
8659 style,
8660 cx,
8661 )?,
8662
8663 None => {
8664 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8665 }
8666 },
8667
8668 None => pending_completion_container().child(Label::new("No Prediction")),
8669 };
8670
8671 let completion = if is_refreshing {
8672 completion
8673 .with_animation(
8674 "loading-completion",
8675 Animation::new(Duration::from_secs(2))
8676 .repeat()
8677 .with_easing(pulsating_between(0.4, 0.8)),
8678 |label, delta| label.opacity(delta),
8679 )
8680 .into_any_element()
8681 } else {
8682 completion.into_any_element()
8683 };
8684
8685 let has_completion = self.active_inline_completion.is_some();
8686
8687 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8688 Some(
8689 h_flex()
8690 .min_w(min_width)
8691 .max_w(max_width)
8692 .flex_1()
8693 .elevation_2(cx)
8694 .border_color(cx.theme().colors().border)
8695 .child(
8696 div()
8697 .flex_1()
8698 .py_1()
8699 .px_2()
8700 .overflow_hidden()
8701 .child(completion),
8702 )
8703 .when_some(accept_keystroke, |el, accept_keystroke| {
8704 if !accept_keystroke.modifiers.modified() {
8705 return el;
8706 }
8707
8708 el.child(
8709 h_flex()
8710 .h_full()
8711 .border_l_1()
8712 .rounded_r_lg()
8713 .border_color(cx.theme().colors().border)
8714 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8715 .gap_1()
8716 .py_1()
8717 .px_2()
8718 .child(
8719 h_flex()
8720 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8721 .when(is_platform_style_mac, |parent| parent.gap_1())
8722 .child(h_flex().children(ui::render_modifiers(
8723 &accept_keystroke.modifiers,
8724 PlatformStyle::platform(),
8725 Some(if !has_completion {
8726 Color::Muted
8727 } else {
8728 Color::Default
8729 }),
8730 None,
8731 false,
8732 ))),
8733 )
8734 .child(Label::new("Preview").into_any_element())
8735 .opacity(if has_completion { 1.0 } else { 0.4 }),
8736 )
8737 })
8738 .into_any(),
8739 )
8740 }
8741
8742 fn render_edit_prediction_cursor_popover_preview(
8743 &self,
8744 completion: &InlineCompletionState,
8745 cursor_point: Point,
8746 style: &EditorStyle,
8747 cx: &mut Context<Editor>,
8748 ) -> Option<Div> {
8749 use text::ToPoint as _;
8750
8751 fn render_relative_row_jump(
8752 prefix: impl Into<String>,
8753 current_row: u32,
8754 target_row: u32,
8755 ) -> Div {
8756 let (row_diff, arrow) = if target_row < current_row {
8757 (current_row - target_row, IconName::ArrowUp)
8758 } else {
8759 (target_row - current_row, IconName::ArrowDown)
8760 };
8761
8762 h_flex()
8763 .child(
8764 Label::new(format!("{}{}", prefix.into(), row_diff))
8765 .color(Color::Muted)
8766 .size(LabelSize::Small),
8767 )
8768 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8769 }
8770
8771 match &completion.completion {
8772 InlineCompletion::Move {
8773 target, snapshot, ..
8774 } => Some(
8775 h_flex()
8776 .px_2()
8777 .gap_2()
8778 .flex_1()
8779 .child(
8780 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8781 Icon::new(IconName::ZedPredictDown)
8782 } else {
8783 Icon::new(IconName::ZedPredictUp)
8784 },
8785 )
8786 .child(Label::new("Jump to Edit")),
8787 ),
8788
8789 InlineCompletion::Edit {
8790 edits,
8791 edit_preview,
8792 snapshot,
8793 display_mode: _,
8794 } => {
8795 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8796
8797 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8798 &snapshot,
8799 &edits,
8800 edit_preview.as_ref()?,
8801 true,
8802 cx,
8803 )
8804 .first_line_preview();
8805
8806 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8807 .with_default_highlights(&style.text, highlighted_edits.highlights);
8808
8809 let preview = h_flex()
8810 .gap_1()
8811 .min_w_16()
8812 .child(styled_text)
8813 .when(has_more_lines, |parent| parent.child("…"));
8814
8815 let left = if first_edit_row != cursor_point.row {
8816 render_relative_row_jump("", cursor_point.row, first_edit_row)
8817 .into_any_element()
8818 } else {
8819 Icon::new(IconName::ZedPredict).into_any_element()
8820 };
8821
8822 Some(
8823 h_flex()
8824 .h_full()
8825 .flex_1()
8826 .gap_2()
8827 .pr_1()
8828 .overflow_x_hidden()
8829 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8830 .child(left)
8831 .child(preview),
8832 )
8833 }
8834 }
8835 }
8836
8837 pub fn render_context_menu(
8838 &self,
8839 style: &EditorStyle,
8840 max_height_in_lines: u32,
8841 window: &mut Window,
8842 cx: &mut Context<Editor>,
8843 ) -> Option<AnyElement> {
8844 let menu = self.context_menu.borrow();
8845 let menu = menu.as_ref()?;
8846 if !menu.visible() {
8847 return None;
8848 };
8849 Some(menu.render(style, max_height_in_lines, window, cx))
8850 }
8851
8852 fn render_context_menu_aside(
8853 &mut self,
8854 max_size: Size<Pixels>,
8855 window: &mut Window,
8856 cx: &mut Context<Editor>,
8857 ) -> Option<AnyElement> {
8858 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8859 if menu.visible() {
8860 menu.render_aside(max_size, window, cx)
8861 } else {
8862 None
8863 }
8864 })
8865 }
8866
8867 fn hide_context_menu(
8868 &mut self,
8869 window: &mut Window,
8870 cx: &mut Context<Self>,
8871 ) -> Option<CodeContextMenu> {
8872 cx.notify();
8873 self.completion_tasks.clear();
8874 let context_menu = self.context_menu.borrow_mut().take();
8875 self.stale_inline_completion_in_menu.take();
8876 self.update_visible_inline_completion(window, cx);
8877 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
8878 if let Some(completion_provider) = &self.completion_provider {
8879 completion_provider.selection_changed(None, window, cx);
8880 }
8881 }
8882 context_menu
8883 }
8884
8885 fn show_snippet_choices(
8886 &mut self,
8887 choices: &Vec<String>,
8888 selection: Range<Anchor>,
8889 cx: &mut Context<Self>,
8890 ) {
8891 if selection.start.buffer_id.is_none() {
8892 return;
8893 }
8894 let buffer_id = selection.start.buffer_id.unwrap();
8895 let buffer = self.buffer().read(cx).buffer(buffer_id);
8896 let id = post_inc(&mut self.next_completion_id);
8897 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8898
8899 if let Some(buffer) = buffer {
8900 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8901 CompletionsMenu::new_snippet_choices(
8902 id,
8903 true,
8904 choices,
8905 selection,
8906 buffer,
8907 snippet_sort_order,
8908 ),
8909 ));
8910 }
8911 }
8912
8913 pub fn insert_snippet(
8914 &mut self,
8915 insertion_ranges: &[Range<usize>],
8916 snippet: Snippet,
8917 window: &mut Window,
8918 cx: &mut Context<Self>,
8919 ) -> Result<()> {
8920 struct Tabstop<T> {
8921 is_end_tabstop: bool,
8922 ranges: Vec<Range<T>>,
8923 choices: Option<Vec<String>>,
8924 }
8925
8926 let tabstops = self.buffer.update(cx, |buffer, cx| {
8927 let snippet_text: Arc<str> = snippet.text.clone().into();
8928 let edits = insertion_ranges
8929 .iter()
8930 .cloned()
8931 .map(|range| (range, snippet_text.clone()));
8932 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8933
8934 let snapshot = &*buffer.read(cx);
8935 let snippet = &snippet;
8936 snippet
8937 .tabstops
8938 .iter()
8939 .map(|tabstop| {
8940 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8941 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8942 });
8943 let mut tabstop_ranges = tabstop
8944 .ranges
8945 .iter()
8946 .flat_map(|tabstop_range| {
8947 let mut delta = 0_isize;
8948 insertion_ranges.iter().map(move |insertion_range| {
8949 let insertion_start = insertion_range.start as isize + delta;
8950 delta +=
8951 snippet.text.len() as isize - insertion_range.len() as isize;
8952
8953 let start = ((insertion_start + tabstop_range.start) as usize)
8954 .min(snapshot.len());
8955 let end = ((insertion_start + tabstop_range.end) as usize)
8956 .min(snapshot.len());
8957 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8958 })
8959 })
8960 .collect::<Vec<_>>();
8961 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8962
8963 Tabstop {
8964 is_end_tabstop,
8965 ranges: tabstop_ranges,
8966 choices: tabstop.choices.clone(),
8967 }
8968 })
8969 .collect::<Vec<_>>()
8970 });
8971 if let Some(tabstop) = tabstops.first() {
8972 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8973 s.select_ranges(tabstop.ranges.iter().cloned());
8974 });
8975
8976 if let Some(choices) = &tabstop.choices {
8977 if let Some(selection) = tabstop.ranges.first() {
8978 self.show_snippet_choices(choices, selection.clone(), cx)
8979 }
8980 }
8981
8982 // If we're already at the last tabstop and it's at the end of the snippet,
8983 // we're done, we don't need to keep the state around.
8984 if !tabstop.is_end_tabstop {
8985 let choices = tabstops
8986 .iter()
8987 .map(|tabstop| tabstop.choices.clone())
8988 .collect();
8989
8990 let ranges = tabstops
8991 .into_iter()
8992 .map(|tabstop| tabstop.ranges)
8993 .collect::<Vec<_>>();
8994
8995 self.snippet_stack.push(SnippetState {
8996 active_index: 0,
8997 ranges,
8998 choices,
8999 });
9000 }
9001
9002 // Check whether the just-entered snippet ends with an auto-closable bracket.
9003 if self.autoclose_regions.is_empty() {
9004 let snapshot = self.buffer.read(cx).snapshot(cx);
9005 for selection in &mut self.selections.all::<Point>(cx) {
9006 let selection_head = selection.head();
9007 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9008 continue;
9009 };
9010
9011 let mut bracket_pair = None;
9012 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9013 let prev_chars = snapshot
9014 .reversed_chars_at(selection_head)
9015 .collect::<String>();
9016 for (pair, enabled) in scope.brackets() {
9017 if enabled
9018 && pair.close
9019 && prev_chars.starts_with(pair.start.as_str())
9020 && next_chars.starts_with(pair.end.as_str())
9021 {
9022 bracket_pair = Some(pair.clone());
9023 break;
9024 }
9025 }
9026 if let Some(pair) = bracket_pair {
9027 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9028 let autoclose_enabled =
9029 self.use_autoclose && snapshot_settings.use_autoclose;
9030 if autoclose_enabled {
9031 let start = snapshot.anchor_after(selection_head);
9032 let end = snapshot.anchor_after(selection_head);
9033 self.autoclose_regions.push(AutocloseRegion {
9034 selection_id: selection.id,
9035 range: start..end,
9036 pair,
9037 });
9038 }
9039 }
9040 }
9041 }
9042 }
9043 Ok(())
9044 }
9045
9046 pub fn move_to_next_snippet_tabstop(
9047 &mut self,
9048 window: &mut Window,
9049 cx: &mut Context<Self>,
9050 ) -> bool {
9051 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9052 }
9053
9054 pub fn move_to_prev_snippet_tabstop(
9055 &mut self,
9056 window: &mut Window,
9057 cx: &mut Context<Self>,
9058 ) -> bool {
9059 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9060 }
9061
9062 pub fn move_to_snippet_tabstop(
9063 &mut self,
9064 bias: Bias,
9065 window: &mut Window,
9066 cx: &mut Context<Self>,
9067 ) -> bool {
9068 if let Some(mut snippet) = self.snippet_stack.pop() {
9069 match bias {
9070 Bias::Left => {
9071 if snippet.active_index > 0 {
9072 snippet.active_index -= 1;
9073 } else {
9074 self.snippet_stack.push(snippet);
9075 return false;
9076 }
9077 }
9078 Bias::Right => {
9079 if snippet.active_index + 1 < snippet.ranges.len() {
9080 snippet.active_index += 1;
9081 } else {
9082 self.snippet_stack.push(snippet);
9083 return false;
9084 }
9085 }
9086 }
9087 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9088 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9089 s.select_anchor_ranges(current_ranges.iter().cloned())
9090 });
9091
9092 if let Some(choices) = &snippet.choices[snippet.active_index] {
9093 if let Some(selection) = current_ranges.first() {
9094 self.show_snippet_choices(&choices, selection.clone(), cx);
9095 }
9096 }
9097
9098 // If snippet state is not at the last tabstop, push it back on the stack
9099 if snippet.active_index + 1 < snippet.ranges.len() {
9100 self.snippet_stack.push(snippet);
9101 }
9102 return true;
9103 }
9104 }
9105
9106 false
9107 }
9108
9109 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9110 self.transact(window, cx, |this, window, cx| {
9111 this.select_all(&SelectAll, window, cx);
9112 this.insert("", window, cx);
9113 });
9114 }
9115
9116 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9117 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9118 self.transact(window, cx, |this, window, cx| {
9119 this.select_autoclose_pair(window, cx);
9120 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9121 if !this.linked_edit_ranges.is_empty() {
9122 let selections = this.selections.all::<MultiBufferPoint>(cx);
9123 let snapshot = this.buffer.read(cx).snapshot(cx);
9124
9125 for selection in selections.iter() {
9126 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9127 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9128 if selection_start.buffer_id != selection_end.buffer_id {
9129 continue;
9130 }
9131 if let Some(ranges) =
9132 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9133 {
9134 for (buffer, entries) in ranges {
9135 linked_ranges.entry(buffer).or_default().extend(entries);
9136 }
9137 }
9138 }
9139 }
9140
9141 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9142 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9143 for selection in &mut selections {
9144 if selection.is_empty() {
9145 let old_head = selection.head();
9146 let mut new_head =
9147 movement::left(&display_map, old_head.to_display_point(&display_map))
9148 .to_point(&display_map);
9149 if let Some((buffer, line_buffer_range)) = display_map
9150 .buffer_snapshot
9151 .buffer_line_for_row(MultiBufferRow(old_head.row))
9152 {
9153 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9154 let indent_len = match indent_size.kind {
9155 IndentKind::Space => {
9156 buffer.settings_at(line_buffer_range.start, cx).tab_size
9157 }
9158 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9159 };
9160 if old_head.column <= indent_size.len && old_head.column > 0 {
9161 let indent_len = indent_len.get();
9162 new_head = cmp::min(
9163 new_head,
9164 MultiBufferPoint::new(
9165 old_head.row,
9166 ((old_head.column - 1) / indent_len) * indent_len,
9167 ),
9168 );
9169 }
9170 }
9171
9172 selection.set_head(new_head, SelectionGoal::None);
9173 }
9174 }
9175
9176 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9177 s.select(selections)
9178 });
9179 this.insert("", window, cx);
9180 let empty_str: Arc<str> = Arc::from("");
9181 for (buffer, edits) in linked_ranges {
9182 let snapshot = buffer.read(cx).snapshot();
9183 use text::ToPoint as TP;
9184
9185 let edits = edits
9186 .into_iter()
9187 .map(|range| {
9188 let end_point = TP::to_point(&range.end, &snapshot);
9189 let mut start_point = TP::to_point(&range.start, &snapshot);
9190
9191 if end_point == start_point {
9192 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9193 .saturating_sub(1);
9194 start_point =
9195 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9196 };
9197
9198 (start_point..end_point, empty_str.clone())
9199 })
9200 .sorted_by_key(|(range, _)| range.start)
9201 .collect::<Vec<_>>();
9202 buffer.update(cx, |this, cx| {
9203 this.edit(edits, None, cx);
9204 })
9205 }
9206 this.refresh_inline_completion(true, false, window, cx);
9207 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9208 });
9209 }
9210
9211 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9212 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9213 self.transact(window, cx, |this, window, cx| {
9214 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9215 s.move_with(|map, selection| {
9216 if selection.is_empty() {
9217 let cursor = movement::right(map, selection.head());
9218 selection.end = cursor;
9219 selection.reversed = true;
9220 selection.goal = SelectionGoal::None;
9221 }
9222 })
9223 });
9224 this.insert("", window, cx);
9225 this.refresh_inline_completion(true, false, window, cx);
9226 });
9227 }
9228
9229 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9230 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9231 if self.move_to_prev_snippet_tabstop(window, cx) {
9232 return;
9233 }
9234 self.outdent(&Outdent, window, cx);
9235 }
9236
9237 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9238 if self.move_to_next_snippet_tabstop(window, cx) {
9239 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9240 return;
9241 }
9242 if self.read_only(cx) {
9243 return;
9244 }
9245 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9246 let mut selections = self.selections.all_adjusted(cx);
9247 let buffer = self.buffer.read(cx);
9248 let snapshot = buffer.snapshot(cx);
9249 let rows_iter = selections.iter().map(|s| s.head().row);
9250 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9251
9252 let has_some_cursor_in_whitespace = selections
9253 .iter()
9254 .filter(|selection| selection.is_empty())
9255 .any(|selection| {
9256 let cursor = selection.head();
9257 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9258 cursor.column < current_indent.len
9259 });
9260
9261 let mut edits = Vec::new();
9262 let mut prev_edited_row = 0;
9263 let mut row_delta = 0;
9264 for selection in &mut selections {
9265 if selection.start.row != prev_edited_row {
9266 row_delta = 0;
9267 }
9268 prev_edited_row = selection.end.row;
9269
9270 // If the selection is non-empty, then increase the indentation of the selected lines.
9271 if !selection.is_empty() {
9272 row_delta =
9273 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9274 continue;
9275 }
9276
9277 let cursor = selection.head();
9278 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9279 if let Some(suggested_indent) =
9280 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9281 {
9282 // Don't do anything if already at suggested indent
9283 // and there is any other cursor which is not
9284 if has_some_cursor_in_whitespace
9285 && cursor.column == current_indent.len
9286 && current_indent.len == suggested_indent.len
9287 {
9288 continue;
9289 }
9290
9291 // Adjust line and move cursor to suggested indent
9292 // if cursor is not at suggested indent
9293 if cursor.column < suggested_indent.len
9294 && cursor.column <= current_indent.len
9295 && current_indent.len <= suggested_indent.len
9296 {
9297 selection.start = Point::new(cursor.row, suggested_indent.len);
9298 selection.end = selection.start;
9299 if row_delta == 0 {
9300 edits.extend(Buffer::edit_for_indent_size_adjustment(
9301 cursor.row,
9302 current_indent,
9303 suggested_indent,
9304 ));
9305 row_delta = suggested_indent.len - current_indent.len;
9306 }
9307 continue;
9308 }
9309
9310 // If current indent is more than suggested indent
9311 // only move cursor to current indent and skip indent
9312 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9313 selection.start = Point::new(cursor.row, current_indent.len);
9314 selection.end = selection.start;
9315 continue;
9316 }
9317 }
9318
9319 // Otherwise, insert a hard or soft tab.
9320 let settings = buffer.language_settings_at(cursor, cx);
9321 let tab_size = if settings.hard_tabs {
9322 IndentSize::tab()
9323 } else {
9324 let tab_size = settings.tab_size.get();
9325 let indent_remainder = snapshot
9326 .text_for_range(Point::new(cursor.row, 0)..cursor)
9327 .flat_map(str::chars)
9328 .fold(row_delta % tab_size, |counter: u32, c| {
9329 if c == '\t' {
9330 0
9331 } else {
9332 (counter + 1) % tab_size
9333 }
9334 });
9335
9336 let chars_to_next_tab_stop = tab_size - indent_remainder;
9337 IndentSize::spaces(chars_to_next_tab_stop)
9338 };
9339 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9340 selection.end = selection.start;
9341 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9342 row_delta += tab_size.len;
9343 }
9344
9345 self.transact(window, cx, |this, window, cx| {
9346 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9347 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9348 s.select(selections)
9349 });
9350 this.refresh_inline_completion(true, false, window, cx);
9351 });
9352 }
9353
9354 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9355 if self.read_only(cx) {
9356 return;
9357 }
9358 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9359 let mut selections = self.selections.all::<Point>(cx);
9360 let mut prev_edited_row = 0;
9361 let mut row_delta = 0;
9362 let mut edits = Vec::new();
9363 let buffer = self.buffer.read(cx);
9364 let snapshot = buffer.snapshot(cx);
9365 for selection in &mut selections {
9366 if selection.start.row != prev_edited_row {
9367 row_delta = 0;
9368 }
9369 prev_edited_row = selection.end.row;
9370
9371 row_delta =
9372 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9373 }
9374
9375 self.transact(window, cx, |this, window, cx| {
9376 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9377 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9378 s.select(selections)
9379 });
9380 });
9381 }
9382
9383 fn indent_selection(
9384 buffer: &MultiBuffer,
9385 snapshot: &MultiBufferSnapshot,
9386 selection: &mut Selection<Point>,
9387 edits: &mut Vec<(Range<Point>, String)>,
9388 delta_for_start_row: u32,
9389 cx: &App,
9390 ) -> u32 {
9391 let settings = buffer.language_settings_at(selection.start, cx);
9392 let tab_size = settings.tab_size.get();
9393 let indent_kind = if settings.hard_tabs {
9394 IndentKind::Tab
9395 } else {
9396 IndentKind::Space
9397 };
9398 let mut start_row = selection.start.row;
9399 let mut end_row = selection.end.row + 1;
9400
9401 // If a selection ends at the beginning of a line, don't indent
9402 // that last line.
9403 if selection.end.column == 0 && selection.end.row > selection.start.row {
9404 end_row -= 1;
9405 }
9406
9407 // Avoid re-indenting a row that has already been indented by a
9408 // previous selection, but still update this selection's column
9409 // to reflect that indentation.
9410 if delta_for_start_row > 0 {
9411 start_row += 1;
9412 selection.start.column += delta_for_start_row;
9413 if selection.end.row == selection.start.row {
9414 selection.end.column += delta_for_start_row;
9415 }
9416 }
9417
9418 let mut delta_for_end_row = 0;
9419 let has_multiple_rows = start_row + 1 != end_row;
9420 for row in start_row..end_row {
9421 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9422 let indent_delta = match (current_indent.kind, indent_kind) {
9423 (IndentKind::Space, IndentKind::Space) => {
9424 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9425 IndentSize::spaces(columns_to_next_tab_stop)
9426 }
9427 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9428 (_, IndentKind::Tab) => IndentSize::tab(),
9429 };
9430
9431 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9432 0
9433 } else {
9434 selection.start.column
9435 };
9436 let row_start = Point::new(row, start);
9437 edits.push((
9438 row_start..row_start,
9439 indent_delta.chars().collect::<String>(),
9440 ));
9441
9442 // Update this selection's endpoints to reflect the indentation.
9443 if row == selection.start.row {
9444 selection.start.column += indent_delta.len;
9445 }
9446 if row == selection.end.row {
9447 selection.end.column += indent_delta.len;
9448 delta_for_end_row = indent_delta.len;
9449 }
9450 }
9451
9452 if selection.start.row == selection.end.row {
9453 delta_for_start_row + delta_for_end_row
9454 } else {
9455 delta_for_end_row
9456 }
9457 }
9458
9459 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9460 if self.read_only(cx) {
9461 return;
9462 }
9463 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9464 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9465 let selections = self.selections.all::<Point>(cx);
9466 let mut deletion_ranges = Vec::new();
9467 let mut last_outdent = None;
9468 {
9469 let buffer = self.buffer.read(cx);
9470 let snapshot = buffer.snapshot(cx);
9471 for selection in &selections {
9472 let settings = buffer.language_settings_at(selection.start, cx);
9473 let tab_size = settings.tab_size.get();
9474 let mut rows = selection.spanned_rows(false, &display_map);
9475
9476 // Avoid re-outdenting a row that has already been outdented by a
9477 // previous selection.
9478 if let Some(last_row) = last_outdent {
9479 if last_row == rows.start {
9480 rows.start = rows.start.next_row();
9481 }
9482 }
9483 let has_multiple_rows = rows.len() > 1;
9484 for row in rows.iter_rows() {
9485 let indent_size = snapshot.indent_size_for_line(row);
9486 if indent_size.len > 0 {
9487 let deletion_len = match indent_size.kind {
9488 IndentKind::Space => {
9489 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9490 if columns_to_prev_tab_stop == 0 {
9491 tab_size
9492 } else {
9493 columns_to_prev_tab_stop
9494 }
9495 }
9496 IndentKind::Tab => 1,
9497 };
9498 let start = if has_multiple_rows
9499 || deletion_len > selection.start.column
9500 || indent_size.len < selection.start.column
9501 {
9502 0
9503 } else {
9504 selection.start.column - deletion_len
9505 };
9506 deletion_ranges.push(
9507 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9508 );
9509 last_outdent = Some(row);
9510 }
9511 }
9512 }
9513 }
9514
9515 self.transact(window, cx, |this, window, cx| {
9516 this.buffer.update(cx, |buffer, cx| {
9517 let empty_str: Arc<str> = Arc::default();
9518 buffer.edit(
9519 deletion_ranges
9520 .into_iter()
9521 .map(|range| (range, empty_str.clone())),
9522 None,
9523 cx,
9524 );
9525 });
9526 let selections = this.selections.all::<usize>(cx);
9527 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9528 s.select(selections)
9529 });
9530 });
9531 }
9532
9533 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9534 if self.read_only(cx) {
9535 return;
9536 }
9537 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9538 let selections = self
9539 .selections
9540 .all::<usize>(cx)
9541 .into_iter()
9542 .map(|s| s.range());
9543
9544 self.transact(window, cx, |this, window, cx| {
9545 this.buffer.update(cx, |buffer, cx| {
9546 buffer.autoindent_ranges(selections, cx);
9547 });
9548 let selections = this.selections.all::<usize>(cx);
9549 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9550 s.select(selections)
9551 });
9552 });
9553 }
9554
9555 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9556 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9557 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9558 let selections = self.selections.all::<Point>(cx);
9559
9560 let mut new_cursors = Vec::new();
9561 let mut edit_ranges = Vec::new();
9562 let mut selections = selections.iter().peekable();
9563 while let Some(selection) = selections.next() {
9564 let mut rows = selection.spanned_rows(false, &display_map);
9565 let goal_display_column = selection.head().to_display_point(&display_map).column();
9566
9567 // Accumulate contiguous regions of rows that we want to delete.
9568 while let Some(next_selection) = selections.peek() {
9569 let next_rows = next_selection.spanned_rows(false, &display_map);
9570 if next_rows.start <= rows.end {
9571 rows.end = next_rows.end;
9572 selections.next().unwrap();
9573 } else {
9574 break;
9575 }
9576 }
9577
9578 let buffer = &display_map.buffer_snapshot;
9579 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9580 let edit_end;
9581 let cursor_buffer_row;
9582 if buffer.max_point().row >= rows.end.0 {
9583 // If there's a line after the range, delete the \n from the end of the row range
9584 // and position the cursor on the next line.
9585 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9586 cursor_buffer_row = rows.end;
9587 } else {
9588 // If there isn't a line after the range, delete the \n from the line before the
9589 // start of the row range and position the cursor there.
9590 edit_start = edit_start.saturating_sub(1);
9591 edit_end = buffer.len();
9592 cursor_buffer_row = rows.start.previous_row();
9593 }
9594
9595 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9596 *cursor.column_mut() =
9597 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9598
9599 new_cursors.push((
9600 selection.id,
9601 buffer.anchor_after(cursor.to_point(&display_map)),
9602 ));
9603 edit_ranges.push(edit_start..edit_end);
9604 }
9605
9606 self.transact(window, cx, |this, window, cx| {
9607 let buffer = this.buffer.update(cx, |buffer, cx| {
9608 let empty_str: Arc<str> = Arc::default();
9609 buffer.edit(
9610 edit_ranges
9611 .into_iter()
9612 .map(|range| (range, empty_str.clone())),
9613 None,
9614 cx,
9615 );
9616 buffer.snapshot(cx)
9617 });
9618 let new_selections = new_cursors
9619 .into_iter()
9620 .map(|(id, cursor)| {
9621 let cursor = cursor.to_point(&buffer);
9622 Selection {
9623 id,
9624 start: cursor,
9625 end: cursor,
9626 reversed: false,
9627 goal: SelectionGoal::None,
9628 }
9629 })
9630 .collect();
9631
9632 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9633 s.select(new_selections);
9634 });
9635 });
9636 }
9637
9638 pub fn join_lines_impl(
9639 &mut self,
9640 insert_whitespace: bool,
9641 window: &mut Window,
9642 cx: &mut Context<Self>,
9643 ) {
9644 if self.read_only(cx) {
9645 return;
9646 }
9647 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9648 for selection in self.selections.all::<Point>(cx) {
9649 let start = MultiBufferRow(selection.start.row);
9650 // Treat single line selections as if they include the next line. Otherwise this action
9651 // would do nothing for single line selections individual cursors.
9652 let end = if selection.start.row == selection.end.row {
9653 MultiBufferRow(selection.start.row + 1)
9654 } else {
9655 MultiBufferRow(selection.end.row)
9656 };
9657
9658 if let Some(last_row_range) = row_ranges.last_mut() {
9659 if start <= last_row_range.end {
9660 last_row_range.end = end;
9661 continue;
9662 }
9663 }
9664 row_ranges.push(start..end);
9665 }
9666
9667 let snapshot = self.buffer.read(cx).snapshot(cx);
9668 let mut cursor_positions = Vec::new();
9669 for row_range in &row_ranges {
9670 let anchor = snapshot.anchor_before(Point::new(
9671 row_range.end.previous_row().0,
9672 snapshot.line_len(row_range.end.previous_row()),
9673 ));
9674 cursor_positions.push(anchor..anchor);
9675 }
9676
9677 self.transact(window, cx, |this, window, cx| {
9678 for row_range in row_ranges.into_iter().rev() {
9679 for row in row_range.iter_rows().rev() {
9680 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9681 let next_line_row = row.next_row();
9682 let indent = snapshot.indent_size_for_line(next_line_row);
9683 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9684
9685 let replace =
9686 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9687 " "
9688 } else {
9689 ""
9690 };
9691
9692 this.buffer.update(cx, |buffer, cx| {
9693 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9694 });
9695 }
9696 }
9697
9698 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9699 s.select_anchor_ranges(cursor_positions)
9700 });
9701 });
9702 }
9703
9704 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9705 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9706 self.join_lines_impl(true, window, cx);
9707 }
9708
9709 pub fn sort_lines_case_sensitive(
9710 &mut self,
9711 _: &SortLinesCaseSensitive,
9712 window: &mut Window,
9713 cx: &mut Context<Self>,
9714 ) {
9715 self.manipulate_lines(window, cx, |lines| lines.sort())
9716 }
9717
9718 pub fn sort_lines_case_insensitive(
9719 &mut self,
9720 _: &SortLinesCaseInsensitive,
9721 window: &mut Window,
9722 cx: &mut Context<Self>,
9723 ) {
9724 self.manipulate_lines(window, cx, |lines| {
9725 lines.sort_by_key(|line| line.to_lowercase())
9726 })
9727 }
9728
9729 pub fn unique_lines_case_insensitive(
9730 &mut self,
9731 _: &UniqueLinesCaseInsensitive,
9732 window: &mut Window,
9733 cx: &mut Context<Self>,
9734 ) {
9735 self.manipulate_lines(window, cx, |lines| {
9736 let mut seen = HashSet::default();
9737 lines.retain(|line| seen.insert(line.to_lowercase()));
9738 })
9739 }
9740
9741 pub fn unique_lines_case_sensitive(
9742 &mut self,
9743 _: &UniqueLinesCaseSensitive,
9744 window: &mut Window,
9745 cx: &mut Context<Self>,
9746 ) {
9747 self.manipulate_lines(window, cx, |lines| {
9748 let mut seen = HashSet::default();
9749 lines.retain(|line| seen.insert(*line));
9750 })
9751 }
9752
9753 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9754 let Some(project) = self.project.clone() else {
9755 return;
9756 };
9757 self.reload(project, window, cx)
9758 .detach_and_notify_err(window, cx);
9759 }
9760
9761 pub fn restore_file(
9762 &mut self,
9763 _: &::git::RestoreFile,
9764 window: &mut Window,
9765 cx: &mut Context<Self>,
9766 ) {
9767 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9768 let mut buffer_ids = HashSet::default();
9769 let snapshot = self.buffer().read(cx).snapshot(cx);
9770 for selection in self.selections.all::<usize>(cx) {
9771 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9772 }
9773
9774 let buffer = self.buffer().read(cx);
9775 let ranges = buffer_ids
9776 .into_iter()
9777 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9778 .collect::<Vec<_>>();
9779
9780 self.restore_hunks_in_ranges(ranges, window, cx);
9781 }
9782
9783 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9784 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9785 let selections = self
9786 .selections
9787 .all(cx)
9788 .into_iter()
9789 .map(|s| s.range())
9790 .collect();
9791 self.restore_hunks_in_ranges(selections, window, cx);
9792 }
9793
9794 pub fn restore_hunks_in_ranges(
9795 &mut self,
9796 ranges: Vec<Range<Point>>,
9797 window: &mut Window,
9798 cx: &mut Context<Editor>,
9799 ) {
9800 let mut revert_changes = HashMap::default();
9801 let chunk_by = self
9802 .snapshot(window, cx)
9803 .hunks_for_ranges(ranges)
9804 .into_iter()
9805 .chunk_by(|hunk| hunk.buffer_id);
9806 for (buffer_id, hunks) in &chunk_by {
9807 let hunks = hunks.collect::<Vec<_>>();
9808 for hunk in &hunks {
9809 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9810 }
9811 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9812 }
9813 drop(chunk_by);
9814 if !revert_changes.is_empty() {
9815 self.transact(window, cx, |editor, window, cx| {
9816 editor.restore(revert_changes, window, cx);
9817 });
9818 }
9819 }
9820
9821 pub fn open_active_item_in_terminal(
9822 &mut self,
9823 _: &OpenInTerminal,
9824 window: &mut Window,
9825 cx: &mut Context<Self>,
9826 ) {
9827 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9828 let project_path = buffer.read(cx).project_path(cx)?;
9829 let project = self.project.as_ref()?.read(cx);
9830 let entry = project.entry_for_path(&project_path, cx)?;
9831 let parent = match &entry.canonical_path {
9832 Some(canonical_path) => canonical_path.to_path_buf(),
9833 None => project.absolute_path(&project_path, cx)?,
9834 }
9835 .parent()?
9836 .to_path_buf();
9837 Some(parent)
9838 }) {
9839 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9840 }
9841 }
9842
9843 fn set_breakpoint_context_menu(
9844 &mut self,
9845 display_row: DisplayRow,
9846 position: Option<Anchor>,
9847 clicked_point: gpui::Point<Pixels>,
9848 window: &mut Window,
9849 cx: &mut Context<Self>,
9850 ) {
9851 if !cx.has_flag::<DebuggerFeatureFlag>() {
9852 return;
9853 }
9854 let source = self
9855 .buffer
9856 .read(cx)
9857 .snapshot(cx)
9858 .anchor_before(Point::new(display_row.0, 0u32));
9859
9860 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9861
9862 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9863 self,
9864 source,
9865 clicked_point,
9866 context_menu,
9867 window,
9868 cx,
9869 );
9870 }
9871
9872 fn add_edit_breakpoint_block(
9873 &mut self,
9874 anchor: Anchor,
9875 breakpoint: &Breakpoint,
9876 edit_action: BreakpointPromptEditAction,
9877 window: &mut Window,
9878 cx: &mut Context<Self>,
9879 ) {
9880 let weak_editor = cx.weak_entity();
9881 let bp_prompt = cx.new(|cx| {
9882 BreakpointPromptEditor::new(
9883 weak_editor,
9884 anchor,
9885 breakpoint.clone(),
9886 edit_action,
9887 window,
9888 cx,
9889 )
9890 });
9891
9892 let height = bp_prompt.update(cx, |this, cx| {
9893 this.prompt
9894 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9895 });
9896 let cloned_prompt = bp_prompt.clone();
9897 let blocks = vec![BlockProperties {
9898 style: BlockStyle::Sticky,
9899 placement: BlockPlacement::Above(anchor),
9900 height: Some(height),
9901 render: Arc::new(move |cx| {
9902 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9903 cloned_prompt.clone().into_any_element()
9904 }),
9905 priority: 0,
9906 render_in_minimap: true,
9907 }];
9908
9909 let focus_handle = bp_prompt.focus_handle(cx);
9910 window.focus(&focus_handle);
9911
9912 let block_ids = self.insert_blocks(blocks, None, cx);
9913 bp_prompt.update(cx, |prompt, _| {
9914 prompt.add_block_ids(block_ids);
9915 });
9916 }
9917
9918 pub(crate) fn breakpoint_at_row(
9919 &self,
9920 row: u32,
9921 window: &mut Window,
9922 cx: &mut Context<Self>,
9923 ) -> Option<(Anchor, Breakpoint)> {
9924 let snapshot = self.snapshot(window, cx);
9925 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9926
9927 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9928 }
9929
9930 pub(crate) fn breakpoint_at_anchor(
9931 &self,
9932 breakpoint_position: Anchor,
9933 snapshot: &EditorSnapshot,
9934 cx: &mut Context<Self>,
9935 ) -> Option<(Anchor, Breakpoint)> {
9936 let project = self.project.clone()?;
9937
9938 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9939 snapshot
9940 .buffer_snapshot
9941 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9942 })?;
9943
9944 let enclosing_excerpt = breakpoint_position.excerpt_id;
9945 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
9946 let buffer_snapshot = buffer.read(cx).snapshot();
9947
9948 let row = buffer_snapshot
9949 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9950 .row;
9951
9952 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9953 let anchor_end = snapshot
9954 .buffer_snapshot
9955 .anchor_after(Point::new(row, line_len));
9956
9957 let bp = self
9958 .breakpoint_store
9959 .as_ref()?
9960 .read_with(cx, |breakpoint_store, cx| {
9961 breakpoint_store
9962 .breakpoints(
9963 &buffer,
9964 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9965 &buffer_snapshot,
9966 cx,
9967 )
9968 .next()
9969 .and_then(|(bp, _)| {
9970 let breakpoint_row = buffer_snapshot
9971 .summary_for_anchor::<text::PointUtf16>(&bp.position)
9972 .row;
9973
9974 if breakpoint_row == row {
9975 snapshot
9976 .buffer_snapshot
9977 .anchor_in_excerpt(enclosing_excerpt, bp.position)
9978 .map(|position| (position, bp.bp.clone()))
9979 } else {
9980 None
9981 }
9982 })
9983 });
9984 bp
9985 }
9986
9987 pub fn edit_log_breakpoint(
9988 &mut self,
9989 _: &EditLogBreakpoint,
9990 window: &mut Window,
9991 cx: &mut Context<Self>,
9992 ) {
9993 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9994 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9995 message: None,
9996 state: BreakpointState::Enabled,
9997 condition: None,
9998 hit_condition: None,
9999 });
10000
10001 self.add_edit_breakpoint_block(
10002 anchor,
10003 &breakpoint,
10004 BreakpointPromptEditAction::Log,
10005 window,
10006 cx,
10007 );
10008 }
10009 }
10010
10011 fn breakpoints_at_cursors(
10012 &self,
10013 window: &mut Window,
10014 cx: &mut Context<Self>,
10015 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10016 let snapshot = self.snapshot(window, cx);
10017 let cursors = self
10018 .selections
10019 .disjoint_anchors()
10020 .into_iter()
10021 .map(|selection| {
10022 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10023
10024 let breakpoint_position = self
10025 .breakpoint_at_row(cursor_position.row, window, cx)
10026 .map(|bp| bp.0)
10027 .unwrap_or_else(|| {
10028 snapshot
10029 .display_snapshot
10030 .buffer_snapshot
10031 .anchor_after(Point::new(cursor_position.row, 0))
10032 });
10033
10034 let breakpoint = self
10035 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10036 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10037
10038 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10039 })
10040 // 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.
10041 .collect::<HashMap<Anchor, _>>();
10042
10043 cursors.into_iter().collect()
10044 }
10045
10046 pub fn enable_breakpoint(
10047 &mut self,
10048 _: &crate::actions::EnableBreakpoint,
10049 window: &mut Window,
10050 cx: &mut Context<Self>,
10051 ) {
10052 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10053 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10054 continue;
10055 };
10056 self.edit_breakpoint_at_anchor(
10057 anchor,
10058 breakpoint,
10059 BreakpointEditAction::InvertState,
10060 cx,
10061 );
10062 }
10063 }
10064
10065 pub fn disable_breakpoint(
10066 &mut self,
10067 _: &crate::actions::DisableBreakpoint,
10068 window: &mut Window,
10069 cx: &mut Context<Self>,
10070 ) {
10071 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10072 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10073 continue;
10074 };
10075 self.edit_breakpoint_at_anchor(
10076 anchor,
10077 breakpoint,
10078 BreakpointEditAction::InvertState,
10079 cx,
10080 );
10081 }
10082 }
10083
10084 pub fn toggle_breakpoint(
10085 &mut self,
10086 _: &crate::actions::ToggleBreakpoint,
10087 window: &mut Window,
10088 cx: &mut Context<Self>,
10089 ) {
10090 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10091 if let Some(breakpoint) = breakpoint {
10092 self.edit_breakpoint_at_anchor(
10093 anchor,
10094 breakpoint,
10095 BreakpointEditAction::Toggle,
10096 cx,
10097 );
10098 } else {
10099 self.edit_breakpoint_at_anchor(
10100 anchor,
10101 Breakpoint::new_standard(),
10102 BreakpointEditAction::Toggle,
10103 cx,
10104 );
10105 }
10106 }
10107 }
10108
10109 pub fn edit_breakpoint_at_anchor(
10110 &mut self,
10111 breakpoint_position: Anchor,
10112 breakpoint: Breakpoint,
10113 edit_action: BreakpointEditAction,
10114 cx: &mut Context<Self>,
10115 ) {
10116 let Some(breakpoint_store) = &self.breakpoint_store else {
10117 return;
10118 };
10119
10120 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10121 if breakpoint_position == Anchor::min() {
10122 self.buffer()
10123 .read(cx)
10124 .excerpt_buffer_ids()
10125 .into_iter()
10126 .next()
10127 } else {
10128 None
10129 }
10130 }) else {
10131 return;
10132 };
10133
10134 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10135 return;
10136 };
10137
10138 breakpoint_store.update(cx, |breakpoint_store, cx| {
10139 breakpoint_store.toggle_breakpoint(
10140 buffer,
10141 BreakpointWithPosition {
10142 position: breakpoint_position.text_anchor,
10143 bp: breakpoint,
10144 },
10145 edit_action,
10146 cx,
10147 );
10148 });
10149
10150 cx.notify();
10151 }
10152
10153 #[cfg(any(test, feature = "test-support"))]
10154 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10155 self.breakpoint_store.clone()
10156 }
10157
10158 pub fn prepare_restore_change(
10159 &self,
10160 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10161 hunk: &MultiBufferDiffHunk,
10162 cx: &mut App,
10163 ) -> Option<()> {
10164 if hunk.is_created_file() {
10165 return None;
10166 }
10167 let buffer = self.buffer.read(cx);
10168 let diff = buffer.diff_for(hunk.buffer_id)?;
10169 let buffer = buffer.buffer(hunk.buffer_id)?;
10170 let buffer = buffer.read(cx);
10171 let original_text = diff
10172 .read(cx)
10173 .base_text()
10174 .as_rope()
10175 .slice(hunk.diff_base_byte_range.clone());
10176 let buffer_snapshot = buffer.snapshot();
10177 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10178 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10179 probe
10180 .0
10181 .start
10182 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10183 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10184 }) {
10185 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10186 Some(())
10187 } else {
10188 None
10189 }
10190 }
10191
10192 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10193 self.manipulate_lines(window, cx, |lines| lines.reverse())
10194 }
10195
10196 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10197 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10198 }
10199
10200 fn manipulate_lines<Fn>(
10201 &mut self,
10202 window: &mut Window,
10203 cx: &mut Context<Self>,
10204 mut callback: Fn,
10205 ) where
10206 Fn: FnMut(&mut Vec<&str>),
10207 {
10208 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10209
10210 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10211 let buffer = self.buffer.read(cx).snapshot(cx);
10212
10213 let mut edits = Vec::new();
10214
10215 let selections = self.selections.all::<Point>(cx);
10216 let mut selections = selections.iter().peekable();
10217 let mut contiguous_row_selections = Vec::new();
10218 let mut new_selections = Vec::new();
10219 let mut added_lines = 0;
10220 let mut removed_lines = 0;
10221
10222 while let Some(selection) = selections.next() {
10223 let (start_row, end_row) = consume_contiguous_rows(
10224 &mut contiguous_row_selections,
10225 selection,
10226 &display_map,
10227 &mut selections,
10228 );
10229
10230 let start_point = Point::new(start_row.0, 0);
10231 let end_point = Point::new(
10232 end_row.previous_row().0,
10233 buffer.line_len(end_row.previous_row()),
10234 );
10235 let text = buffer
10236 .text_for_range(start_point..end_point)
10237 .collect::<String>();
10238
10239 let mut lines = text.split('\n').collect_vec();
10240
10241 let lines_before = lines.len();
10242 callback(&mut lines);
10243 let lines_after = lines.len();
10244
10245 edits.push((start_point..end_point, lines.join("\n")));
10246
10247 // Selections must change based on added and removed line count
10248 let start_row =
10249 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10250 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10251 new_selections.push(Selection {
10252 id: selection.id,
10253 start: start_row,
10254 end: end_row,
10255 goal: SelectionGoal::None,
10256 reversed: selection.reversed,
10257 });
10258
10259 if lines_after > lines_before {
10260 added_lines += lines_after - lines_before;
10261 } else if lines_before > lines_after {
10262 removed_lines += lines_before - lines_after;
10263 }
10264 }
10265
10266 self.transact(window, cx, |this, window, cx| {
10267 let buffer = this.buffer.update(cx, |buffer, cx| {
10268 buffer.edit(edits, None, cx);
10269 buffer.snapshot(cx)
10270 });
10271
10272 // Recalculate offsets on newly edited buffer
10273 let new_selections = new_selections
10274 .iter()
10275 .map(|s| {
10276 let start_point = Point::new(s.start.0, 0);
10277 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10278 Selection {
10279 id: s.id,
10280 start: buffer.point_to_offset(start_point),
10281 end: buffer.point_to_offset(end_point),
10282 goal: s.goal,
10283 reversed: s.reversed,
10284 }
10285 })
10286 .collect();
10287
10288 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10289 s.select(new_selections);
10290 });
10291
10292 this.request_autoscroll(Autoscroll::fit(), cx);
10293 });
10294 }
10295
10296 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10297 self.manipulate_text(window, cx, |text| {
10298 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10299 if has_upper_case_characters {
10300 text.to_lowercase()
10301 } else {
10302 text.to_uppercase()
10303 }
10304 })
10305 }
10306
10307 pub fn convert_to_upper_case(
10308 &mut self,
10309 _: &ConvertToUpperCase,
10310 window: &mut Window,
10311 cx: &mut Context<Self>,
10312 ) {
10313 self.manipulate_text(window, cx, |text| text.to_uppercase())
10314 }
10315
10316 pub fn convert_to_lower_case(
10317 &mut self,
10318 _: &ConvertToLowerCase,
10319 window: &mut Window,
10320 cx: &mut Context<Self>,
10321 ) {
10322 self.manipulate_text(window, cx, |text| text.to_lowercase())
10323 }
10324
10325 pub fn convert_to_title_case(
10326 &mut self,
10327 _: &ConvertToTitleCase,
10328 window: &mut Window,
10329 cx: &mut Context<Self>,
10330 ) {
10331 self.manipulate_text(window, cx, |text| {
10332 text.split('\n')
10333 .map(|line| line.to_case(Case::Title))
10334 .join("\n")
10335 })
10336 }
10337
10338 pub fn convert_to_snake_case(
10339 &mut self,
10340 _: &ConvertToSnakeCase,
10341 window: &mut Window,
10342 cx: &mut Context<Self>,
10343 ) {
10344 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10345 }
10346
10347 pub fn convert_to_kebab_case(
10348 &mut self,
10349 _: &ConvertToKebabCase,
10350 window: &mut Window,
10351 cx: &mut Context<Self>,
10352 ) {
10353 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10354 }
10355
10356 pub fn convert_to_upper_camel_case(
10357 &mut self,
10358 _: &ConvertToUpperCamelCase,
10359 window: &mut Window,
10360 cx: &mut Context<Self>,
10361 ) {
10362 self.manipulate_text(window, cx, |text| {
10363 text.split('\n')
10364 .map(|line| line.to_case(Case::UpperCamel))
10365 .join("\n")
10366 })
10367 }
10368
10369 pub fn convert_to_lower_camel_case(
10370 &mut self,
10371 _: &ConvertToLowerCamelCase,
10372 window: &mut Window,
10373 cx: &mut Context<Self>,
10374 ) {
10375 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10376 }
10377
10378 pub fn convert_to_opposite_case(
10379 &mut self,
10380 _: &ConvertToOppositeCase,
10381 window: &mut Window,
10382 cx: &mut Context<Self>,
10383 ) {
10384 self.manipulate_text(window, cx, |text| {
10385 text.chars()
10386 .fold(String::with_capacity(text.len()), |mut t, c| {
10387 if c.is_uppercase() {
10388 t.extend(c.to_lowercase());
10389 } else {
10390 t.extend(c.to_uppercase());
10391 }
10392 t
10393 })
10394 })
10395 }
10396
10397 pub fn convert_to_rot13(
10398 &mut self,
10399 _: &ConvertToRot13,
10400 window: &mut Window,
10401 cx: &mut Context<Self>,
10402 ) {
10403 self.manipulate_text(window, cx, |text| {
10404 text.chars()
10405 .map(|c| match c {
10406 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10407 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10408 _ => c,
10409 })
10410 .collect()
10411 })
10412 }
10413
10414 pub fn convert_to_rot47(
10415 &mut self,
10416 _: &ConvertToRot47,
10417 window: &mut Window,
10418 cx: &mut Context<Self>,
10419 ) {
10420 self.manipulate_text(window, cx, |text| {
10421 text.chars()
10422 .map(|c| {
10423 let code_point = c as u32;
10424 if code_point >= 33 && code_point <= 126 {
10425 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10426 }
10427 c
10428 })
10429 .collect()
10430 })
10431 }
10432
10433 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10434 where
10435 Fn: FnMut(&str) -> String,
10436 {
10437 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10438 let buffer = self.buffer.read(cx).snapshot(cx);
10439
10440 let mut new_selections = Vec::new();
10441 let mut edits = Vec::new();
10442 let mut selection_adjustment = 0i32;
10443
10444 for selection in self.selections.all::<usize>(cx) {
10445 let selection_is_empty = selection.is_empty();
10446
10447 let (start, end) = if selection_is_empty {
10448 let word_range = movement::surrounding_word(
10449 &display_map,
10450 selection.start.to_display_point(&display_map),
10451 );
10452 let start = word_range.start.to_offset(&display_map, Bias::Left);
10453 let end = word_range.end.to_offset(&display_map, Bias::Left);
10454 (start, end)
10455 } else {
10456 (selection.start, selection.end)
10457 };
10458
10459 let text = buffer.text_for_range(start..end).collect::<String>();
10460 let old_length = text.len() as i32;
10461 let text = callback(&text);
10462
10463 new_selections.push(Selection {
10464 start: (start as i32 - selection_adjustment) as usize,
10465 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10466 goal: SelectionGoal::None,
10467 ..selection
10468 });
10469
10470 selection_adjustment += old_length - text.len() as i32;
10471
10472 edits.push((start..end, text));
10473 }
10474
10475 self.transact(window, cx, |this, window, cx| {
10476 this.buffer.update(cx, |buffer, cx| {
10477 buffer.edit(edits, None, cx);
10478 });
10479
10480 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10481 s.select(new_selections);
10482 });
10483
10484 this.request_autoscroll(Autoscroll::fit(), cx);
10485 });
10486 }
10487
10488 pub fn duplicate(
10489 &mut self,
10490 upwards: bool,
10491 whole_lines: bool,
10492 window: &mut Window,
10493 cx: &mut Context<Self>,
10494 ) {
10495 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10496
10497 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10498 let buffer = &display_map.buffer_snapshot;
10499 let selections = self.selections.all::<Point>(cx);
10500
10501 let mut edits = Vec::new();
10502 let mut selections_iter = selections.iter().peekable();
10503 while let Some(selection) = selections_iter.next() {
10504 let mut rows = selection.spanned_rows(false, &display_map);
10505 // duplicate line-wise
10506 if whole_lines || selection.start == selection.end {
10507 // Avoid duplicating the same lines twice.
10508 while let Some(next_selection) = selections_iter.peek() {
10509 let next_rows = next_selection.spanned_rows(false, &display_map);
10510 if next_rows.start < rows.end {
10511 rows.end = next_rows.end;
10512 selections_iter.next().unwrap();
10513 } else {
10514 break;
10515 }
10516 }
10517
10518 // Copy the text from the selected row region and splice it either at the start
10519 // or end of the region.
10520 let start = Point::new(rows.start.0, 0);
10521 let end = Point::new(
10522 rows.end.previous_row().0,
10523 buffer.line_len(rows.end.previous_row()),
10524 );
10525 let text = buffer
10526 .text_for_range(start..end)
10527 .chain(Some("\n"))
10528 .collect::<String>();
10529 let insert_location = if upwards {
10530 Point::new(rows.end.0, 0)
10531 } else {
10532 start
10533 };
10534 edits.push((insert_location..insert_location, text));
10535 } else {
10536 // duplicate character-wise
10537 let start = selection.start;
10538 let end = selection.end;
10539 let text = buffer.text_for_range(start..end).collect::<String>();
10540 edits.push((selection.end..selection.end, text));
10541 }
10542 }
10543
10544 self.transact(window, cx, |this, _, cx| {
10545 this.buffer.update(cx, |buffer, cx| {
10546 buffer.edit(edits, None, cx);
10547 });
10548
10549 this.request_autoscroll(Autoscroll::fit(), cx);
10550 });
10551 }
10552
10553 pub fn duplicate_line_up(
10554 &mut self,
10555 _: &DuplicateLineUp,
10556 window: &mut Window,
10557 cx: &mut Context<Self>,
10558 ) {
10559 self.duplicate(true, true, window, cx);
10560 }
10561
10562 pub fn duplicate_line_down(
10563 &mut self,
10564 _: &DuplicateLineDown,
10565 window: &mut Window,
10566 cx: &mut Context<Self>,
10567 ) {
10568 self.duplicate(false, true, window, cx);
10569 }
10570
10571 pub fn duplicate_selection(
10572 &mut self,
10573 _: &DuplicateSelection,
10574 window: &mut Window,
10575 cx: &mut Context<Self>,
10576 ) {
10577 self.duplicate(false, false, window, cx);
10578 }
10579
10580 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10581 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10582
10583 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10584 let buffer = self.buffer.read(cx).snapshot(cx);
10585
10586 let mut edits = Vec::new();
10587 let mut unfold_ranges = Vec::new();
10588 let mut refold_creases = Vec::new();
10589
10590 let selections = self.selections.all::<Point>(cx);
10591 let mut selections = selections.iter().peekable();
10592 let mut contiguous_row_selections = Vec::new();
10593 let mut new_selections = Vec::new();
10594
10595 while let Some(selection) = selections.next() {
10596 // Find all the selections that span a contiguous row range
10597 let (start_row, end_row) = consume_contiguous_rows(
10598 &mut contiguous_row_selections,
10599 selection,
10600 &display_map,
10601 &mut selections,
10602 );
10603
10604 // Move the text spanned by the row range to be before the line preceding the row range
10605 if start_row.0 > 0 {
10606 let range_to_move = Point::new(
10607 start_row.previous_row().0,
10608 buffer.line_len(start_row.previous_row()),
10609 )
10610 ..Point::new(
10611 end_row.previous_row().0,
10612 buffer.line_len(end_row.previous_row()),
10613 );
10614 let insertion_point = display_map
10615 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10616 .0;
10617
10618 // Don't move lines across excerpts
10619 if buffer
10620 .excerpt_containing(insertion_point..range_to_move.end)
10621 .is_some()
10622 {
10623 let text = buffer
10624 .text_for_range(range_to_move.clone())
10625 .flat_map(|s| s.chars())
10626 .skip(1)
10627 .chain(['\n'])
10628 .collect::<String>();
10629
10630 edits.push((
10631 buffer.anchor_after(range_to_move.start)
10632 ..buffer.anchor_before(range_to_move.end),
10633 String::new(),
10634 ));
10635 let insertion_anchor = buffer.anchor_after(insertion_point);
10636 edits.push((insertion_anchor..insertion_anchor, text));
10637
10638 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10639
10640 // Move selections up
10641 new_selections.extend(contiguous_row_selections.drain(..).map(
10642 |mut selection| {
10643 selection.start.row -= row_delta;
10644 selection.end.row -= row_delta;
10645 selection
10646 },
10647 ));
10648
10649 // Move folds up
10650 unfold_ranges.push(range_to_move.clone());
10651 for fold in display_map.folds_in_range(
10652 buffer.anchor_before(range_to_move.start)
10653 ..buffer.anchor_after(range_to_move.end),
10654 ) {
10655 let mut start = fold.range.start.to_point(&buffer);
10656 let mut end = fold.range.end.to_point(&buffer);
10657 start.row -= row_delta;
10658 end.row -= row_delta;
10659 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10660 }
10661 }
10662 }
10663
10664 // If we didn't move line(s), preserve the existing selections
10665 new_selections.append(&mut contiguous_row_selections);
10666 }
10667
10668 self.transact(window, cx, |this, window, cx| {
10669 this.unfold_ranges(&unfold_ranges, true, true, cx);
10670 this.buffer.update(cx, |buffer, cx| {
10671 for (range, text) in edits {
10672 buffer.edit([(range, text)], None, cx);
10673 }
10674 });
10675 this.fold_creases(refold_creases, true, window, cx);
10676 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10677 s.select(new_selections);
10678 })
10679 });
10680 }
10681
10682 pub fn move_line_down(
10683 &mut self,
10684 _: &MoveLineDown,
10685 window: &mut Window,
10686 cx: &mut Context<Self>,
10687 ) {
10688 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10689
10690 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10691 let buffer = self.buffer.read(cx).snapshot(cx);
10692
10693 let mut edits = Vec::new();
10694 let mut unfold_ranges = Vec::new();
10695 let mut refold_creases = Vec::new();
10696
10697 let selections = self.selections.all::<Point>(cx);
10698 let mut selections = selections.iter().peekable();
10699 let mut contiguous_row_selections = Vec::new();
10700 let mut new_selections = Vec::new();
10701
10702 while let Some(selection) = selections.next() {
10703 // Find all the selections that span a contiguous row range
10704 let (start_row, end_row) = consume_contiguous_rows(
10705 &mut contiguous_row_selections,
10706 selection,
10707 &display_map,
10708 &mut selections,
10709 );
10710
10711 // Move the text spanned by the row range to be after the last line of the row range
10712 if end_row.0 <= buffer.max_point().row {
10713 let range_to_move =
10714 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10715 let insertion_point = display_map
10716 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10717 .0;
10718
10719 // Don't move lines across excerpt boundaries
10720 if buffer
10721 .excerpt_containing(range_to_move.start..insertion_point)
10722 .is_some()
10723 {
10724 let mut text = String::from("\n");
10725 text.extend(buffer.text_for_range(range_to_move.clone()));
10726 text.pop(); // Drop trailing newline
10727 edits.push((
10728 buffer.anchor_after(range_to_move.start)
10729 ..buffer.anchor_before(range_to_move.end),
10730 String::new(),
10731 ));
10732 let insertion_anchor = buffer.anchor_after(insertion_point);
10733 edits.push((insertion_anchor..insertion_anchor, text));
10734
10735 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10736
10737 // Move selections down
10738 new_selections.extend(contiguous_row_selections.drain(..).map(
10739 |mut selection| {
10740 selection.start.row += row_delta;
10741 selection.end.row += row_delta;
10742 selection
10743 },
10744 ));
10745
10746 // Move folds down
10747 unfold_ranges.push(range_to_move.clone());
10748 for fold in display_map.folds_in_range(
10749 buffer.anchor_before(range_to_move.start)
10750 ..buffer.anchor_after(range_to_move.end),
10751 ) {
10752 let mut start = fold.range.start.to_point(&buffer);
10753 let mut end = fold.range.end.to_point(&buffer);
10754 start.row += row_delta;
10755 end.row += row_delta;
10756 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10757 }
10758 }
10759 }
10760
10761 // If we didn't move line(s), preserve the existing selections
10762 new_selections.append(&mut contiguous_row_selections);
10763 }
10764
10765 self.transact(window, cx, |this, window, cx| {
10766 this.unfold_ranges(&unfold_ranges, true, true, cx);
10767 this.buffer.update(cx, |buffer, cx| {
10768 for (range, text) in edits {
10769 buffer.edit([(range, text)], None, cx);
10770 }
10771 });
10772 this.fold_creases(refold_creases, true, window, cx);
10773 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10774 s.select(new_selections)
10775 });
10776 });
10777 }
10778
10779 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10780 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10781 let text_layout_details = &self.text_layout_details(window);
10782 self.transact(window, cx, |this, window, cx| {
10783 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10784 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10785 s.move_with(|display_map, selection| {
10786 if !selection.is_empty() {
10787 return;
10788 }
10789
10790 let mut head = selection.head();
10791 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10792 if head.column() == display_map.line_len(head.row()) {
10793 transpose_offset = display_map
10794 .buffer_snapshot
10795 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10796 }
10797
10798 if transpose_offset == 0 {
10799 return;
10800 }
10801
10802 *head.column_mut() += 1;
10803 head = display_map.clip_point(head, Bias::Right);
10804 let goal = SelectionGoal::HorizontalPosition(
10805 display_map
10806 .x_for_display_point(head, text_layout_details)
10807 .into(),
10808 );
10809 selection.collapse_to(head, goal);
10810
10811 let transpose_start = display_map
10812 .buffer_snapshot
10813 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10814 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10815 let transpose_end = display_map
10816 .buffer_snapshot
10817 .clip_offset(transpose_offset + 1, Bias::Right);
10818 if let Some(ch) =
10819 display_map.buffer_snapshot.chars_at(transpose_start).next()
10820 {
10821 edits.push((transpose_start..transpose_offset, String::new()));
10822 edits.push((transpose_end..transpose_end, ch.to_string()));
10823 }
10824 }
10825 });
10826 edits
10827 });
10828 this.buffer
10829 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10830 let selections = this.selections.all::<usize>(cx);
10831 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10832 s.select(selections);
10833 });
10834 });
10835 }
10836
10837 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10838 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10839 self.rewrap_impl(RewrapOptions::default(), cx)
10840 }
10841
10842 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10843 let buffer = self.buffer.read(cx).snapshot(cx);
10844 let selections = self.selections.all::<Point>(cx);
10845 let mut selections = selections.iter().peekable();
10846
10847 let mut edits = Vec::new();
10848 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10849
10850 while let Some(selection) = selections.next() {
10851 let mut start_row = selection.start.row;
10852 let mut end_row = selection.end.row;
10853
10854 // Skip selections that overlap with a range that has already been rewrapped.
10855 let selection_range = start_row..end_row;
10856 if rewrapped_row_ranges
10857 .iter()
10858 .any(|range| range.overlaps(&selection_range))
10859 {
10860 continue;
10861 }
10862
10863 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10864
10865 // Since not all lines in the selection may be at the same indent
10866 // level, choose the indent size that is the most common between all
10867 // of the lines.
10868 //
10869 // If there is a tie, we use the deepest indent.
10870 let (indent_size, indent_end) = {
10871 let mut indent_size_occurrences = HashMap::default();
10872 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10873
10874 for row in start_row..=end_row {
10875 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10876 rows_by_indent_size.entry(indent).or_default().push(row);
10877 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10878 }
10879
10880 let indent_size = indent_size_occurrences
10881 .into_iter()
10882 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10883 .map(|(indent, _)| indent)
10884 .unwrap_or_default();
10885 let row = rows_by_indent_size[&indent_size][0];
10886 let indent_end = Point::new(row, indent_size.len);
10887
10888 (indent_size, indent_end)
10889 };
10890
10891 let mut line_prefix = indent_size.chars().collect::<String>();
10892
10893 let mut inside_comment = false;
10894 if let Some(comment_prefix) =
10895 buffer
10896 .language_scope_at(selection.head())
10897 .and_then(|language| {
10898 language
10899 .line_comment_prefixes()
10900 .iter()
10901 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10902 .cloned()
10903 })
10904 {
10905 line_prefix.push_str(&comment_prefix);
10906 inside_comment = true;
10907 }
10908
10909 let language_settings = buffer.language_settings_at(selection.head(), cx);
10910 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10911 RewrapBehavior::InComments => inside_comment,
10912 RewrapBehavior::InSelections => !selection.is_empty(),
10913 RewrapBehavior::Anywhere => true,
10914 };
10915
10916 let should_rewrap = options.override_language_settings
10917 || allow_rewrap_based_on_language
10918 || self.hard_wrap.is_some();
10919 if !should_rewrap {
10920 continue;
10921 }
10922
10923 if selection.is_empty() {
10924 'expand_upwards: while start_row > 0 {
10925 let prev_row = start_row - 1;
10926 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10927 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10928 {
10929 start_row = prev_row;
10930 } else {
10931 break 'expand_upwards;
10932 }
10933 }
10934
10935 'expand_downwards: while end_row < buffer.max_point().row {
10936 let next_row = end_row + 1;
10937 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10938 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10939 {
10940 end_row = next_row;
10941 } else {
10942 break 'expand_downwards;
10943 }
10944 }
10945 }
10946
10947 let start = Point::new(start_row, 0);
10948 let start_offset = start.to_offset(&buffer);
10949 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10950 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10951 let Some(lines_without_prefixes) = selection_text
10952 .lines()
10953 .map(|line| {
10954 line.strip_prefix(&line_prefix)
10955 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10956 .with_context(|| {
10957 format!("line did not start with prefix {line_prefix:?}: {line:?}")
10958 })
10959 })
10960 .collect::<Result<Vec<_>, _>>()
10961 .log_err()
10962 else {
10963 continue;
10964 };
10965
10966 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10967 buffer
10968 .language_settings_at(Point::new(start_row, 0), cx)
10969 .preferred_line_length as usize
10970 });
10971 let wrapped_text = wrap_with_prefix(
10972 line_prefix,
10973 lines_without_prefixes.join("\n"),
10974 wrap_column,
10975 tab_size,
10976 options.preserve_existing_whitespace,
10977 );
10978
10979 // TODO: should always use char-based diff while still supporting cursor behavior that
10980 // matches vim.
10981 let mut diff_options = DiffOptions::default();
10982 if options.override_language_settings {
10983 diff_options.max_word_diff_len = 0;
10984 diff_options.max_word_diff_line_count = 0;
10985 } else {
10986 diff_options.max_word_diff_len = usize::MAX;
10987 diff_options.max_word_diff_line_count = usize::MAX;
10988 }
10989
10990 for (old_range, new_text) in
10991 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10992 {
10993 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10994 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10995 edits.push((edit_start..edit_end, new_text));
10996 }
10997
10998 rewrapped_row_ranges.push(start_row..=end_row);
10999 }
11000
11001 self.buffer
11002 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11003 }
11004
11005 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11006 let mut text = String::new();
11007 let buffer = self.buffer.read(cx).snapshot(cx);
11008 let mut selections = self.selections.all::<Point>(cx);
11009 let mut clipboard_selections = Vec::with_capacity(selections.len());
11010 {
11011 let max_point = buffer.max_point();
11012 let mut is_first = true;
11013 for selection in &mut selections {
11014 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11015 if is_entire_line {
11016 selection.start = Point::new(selection.start.row, 0);
11017 if !selection.is_empty() && selection.end.column == 0 {
11018 selection.end = cmp::min(max_point, selection.end);
11019 } else {
11020 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11021 }
11022 selection.goal = SelectionGoal::None;
11023 }
11024 if is_first {
11025 is_first = false;
11026 } else {
11027 text += "\n";
11028 }
11029 let mut len = 0;
11030 for chunk in buffer.text_for_range(selection.start..selection.end) {
11031 text.push_str(chunk);
11032 len += chunk.len();
11033 }
11034 clipboard_selections.push(ClipboardSelection {
11035 len,
11036 is_entire_line,
11037 first_line_indent: buffer
11038 .indent_size_for_line(MultiBufferRow(selection.start.row))
11039 .len,
11040 });
11041 }
11042 }
11043
11044 self.transact(window, cx, |this, window, cx| {
11045 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11046 s.select(selections);
11047 });
11048 this.insert("", window, cx);
11049 });
11050 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11051 }
11052
11053 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11054 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11055 let item = self.cut_common(window, cx);
11056 cx.write_to_clipboard(item);
11057 }
11058
11059 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11060 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11061 self.change_selections(None, window, cx, |s| {
11062 s.move_with(|snapshot, sel| {
11063 if sel.is_empty() {
11064 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11065 }
11066 });
11067 });
11068 let item = self.cut_common(window, cx);
11069 cx.set_global(KillRing(item))
11070 }
11071
11072 pub fn kill_ring_yank(
11073 &mut self,
11074 _: &KillRingYank,
11075 window: &mut Window,
11076 cx: &mut Context<Self>,
11077 ) {
11078 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11079 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11080 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11081 (kill_ring.text().to_string(), kill_ring.metadata_json())
11082 } else {
11083 return;
11084 }
11085 } else {
11086 return;
11087 };
11088 self.do_paste(&text, metadata, false, window, cx);
11089 }
11090
11091 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11092 self.do_copy(true, cx);
11093 }
11094
11095 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11096 self.do_copy(false, cx);
11097 }
11098
11099 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11100 let selections = self.selections.all::<Point>(cx);
11101 let buffer = self.buffer.read(cx).read(cx);
11102 let mut text = String::new();
11103
11104 let mut clipboard_selections = Vec::with_capacity(selections.len());
11105 {
11106 let max_point = buffer.max_point();
11107 let mut is_first = true;
11108 for selection in &selections {
11109 let mut start = selection.start;
11110 let mut end = selection.end;
11111 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11112 if is_entire_line {
11113 start = Point::new(start.row, 0);
11114 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11115 }
11116
11117 let mut trimmed_selections = Vec::new();
11118 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11119 let row = MultiBufferRow(start.row);
11120 let first_indent = buffer.indent_size_for_line(row);
11121 if first_indent.len == 0 || start.column > first_indent.len {
11122 trimmed_selections.push(start..end);
11123 } else {
11124 trimmed_selections.push(
11125 Point::new(row.0, first_indent.len)
11126 ..Point::new(row.0, buffer.line_len(row)),
11127 );
11128 for row in start.row + 1..=end.row {
11129 let mut line_len = buffer.line_len(MultiBufferRow(row));
11130 if row == end.row {
11131 line_len = end.column;
11132 }
11133 if line_len == 0 {
11134 trimmed_selections
11135 .push(Point::new(row, 0)..Point::new(row, line_len));
11136 continue;
11137 }
11138 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11139 if row_indent_size.len >= first_indent.len {
11140 trimmed_selections.push(
11141 Point::new(row, first_indent.len)..Point::new(row, line_len),
11142 );
11143 } else {
11144 trimmed_selections.clear();
11145 trimmed_selections.push(start..end);
11146 break;
11147 }
11148 }
11149 }
11150 } else {
11151 trimmed_selections.push(start..end);
11152 }
11153
11154 for trimmed_range in trimmed_selections {
11155 if is_first {
11156 is_first = false;
11157 } else {
11158 text += "\n";
11159 }
11160 let mut len = 0;
11161 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11162 text.push_str(chunk);
11163 len += chunk.len();
11164 }
11165 clipboard_selections.push(ClipboardSelection {
11166 len,
11167 is_entire_line,
11168 first_line_indent: buffer
11169 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11170 .len,
11171 });
11172 }
11173 }
11174 }
11175
11176 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11177 text,
11178 clipboard_selections,
11179 ));
11180 }
11181
11182 pub fn do_paste(
11183 &mut self,
11184 text: &String,
11185 clipboard_selections: Option<Vec<ClipboardSelection>>,
11186 handle_entire_lines: bool,
11187 window: &mut Window,
11188 cx: &mut Context<Self>,
11189 ) {
11190 if self.read_only(cx) {
11191 return;
11192 }
11193
11194 let clipboard_text = Cow::Borrowed(text);
11195
11196 self.transact(window, cx, |this, window, cx| {
11197 if let Some(mut clipboard_selections) = clipboard_selections {
11198 let old_selections = this.selections.all::<usize>(cx);
11199 let all_selections_were_entire_line =
11200 clipboard_selections.iter().all(|s| s.is_entire_line);
11201 let first_selection_indent_column =
11202 clipboard_selections.first().map(|s| s.first_line_indent);
11203 if clipboard_selections.len() != old_selections.len() {
11204 clipboard_selections.drain(..);
11205 }
11206 let cursor_offset = this.selections.last::<usize>(cx).head();
11207 let mut auto_indent_on_paste = true;
11208
11209 this.buffer.update(cx, |buffer, cx| {
11210 let snapshot = buffer.read(cx);
11211 auto_indent_on_paste = snapshot
11212 .language_settings_at(cursor_offset, cx)
11213 .auto_indent_on_paste;
11214
11215 let mut start_offset = 0;
11216 let mut edits = Vec::new();
11217 let mut original_indent_columns = Vec::new();
11218 for (ix, selection) in old_selections.iter().enumerate() {
11219 let to_insert;
11220 let entire_line;
11221 let original_indent_column;
11222 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11223 let end_offset = start_offset + clipboard_selection.len;
11224 to_insert = &clipboard_text[start_offset..end_offset];
11225 entire_line = clipboard_selection.is_entire_line;
11226 start_offset = end_offset + 1;
11227 original_indent_column = Some(clipboard_selection.first_line_indent);
11228 } else {
11229 to_insert = clipboard_text.as_str();
11230 entire_line = all_selections_were_entire_line;
11231 original_indent_column = first_selection_indent_column
11232 }
11233
11234 // If the corresponding selection was empty when this slice of the
11235 // clipboard text was written, then the entire line containing the
11236 // selection was copied. If this selection is also currently empty,
11237 // then paste the line before the current line of the buffer.
11238 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11239 let column = selection.start.to_point(&snapshot).column as usize;
11240 let line_start = selection.start - column;
11241 line_start..line_start
11242 } else {
11243 selection.range()
11244 };
11245
11246 edits.push((range, to_insert));
11247 original_indent_columns.push(original_indent_column);
11248 }
11249 drop(snapshot);
11250
11251 buffer.edit(
11252 edits,
11253 if auto_indent_on_paste {
11254 Some(AutoindentMode::Block {
11255 original_indent_columns,
11256 })
11257 } else {
11258 None
11259 },
11260 cx,
11261 );
11262 });
11263
11264 let selections = this.selections.all::<usize>(cx);
11265 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11266 s.select(selections)
11267 });
11268 } else {
11269 this.insert(&clipboard_text, window, cx);
11270 }
11271 });
11272 }
11273
11274 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11275 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11276 if let Some(item) = cx.read_from_clipboard() {
11277 let entries = item.entries();
11278
11279 match entries.first() {
11280 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11281 // of all the pasted entries.
11282 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11283 .do_paste(
11284 clipboard_string.text(),
11285 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11286 true,
11287 window,
11288 cx,
11289 ),
11290 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11291 }
11292 }
11293 }
11294
11295 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11296 if self.read_only(cx) {
11297 return;
11298 }
11299
11300 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11301
11302 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11303 if let Some((selections, _)) =
11304 self.selection_history.transaction(transaction_id).cloned()
11305 {
11306 self.change_selections(None, window, cx, |s| {
11307 s.select_anchors(selections.to_vec());
11308 });
11309 } else {
11310 log::error!(
11311 "No entry in selection_history found for undo. \
11312 This may correspond to a bug where undo does not update the selection. \
11313 If this is occurring, please add details to \
11314 https://github.com/zed-industries/zed/issues/22692"
11315 );
11316 }
11317 self.request_autoscroll(Autoscroll::fit(), cx);
11318 self.unmark_text(window, cx);
11319 self.refresh_inline_completion(true, false, window, cx);
11320 cx.emit(EditorEvent::Edited { transaction_id });
11321 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11322 }
11323 }
11324
11325 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11326 if self.read_only(cx) {
11327 return;
11328 }
11329
11330 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11331
11332 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11333 if let Some((_, Some(selections))) =
11334 self.selection_history.transaction(transaction_id).cloned()
11335 {
11336 self.change_selections(None, window, cx, |s| {
11337 s.select_anchors(selections.to_vec());
11338 });
11339 } else {
11340 log::error!(
11341 "No entry in selection_history found for redo. \
11342 This may correspond to a bug where undo does not update the selection. \
11343 If this is occurring, please add details to \
11344 https://github.com/zed-industries/zed/issues/22692"
11345 );
11346 }
11347 self.request_autoscroll(Autoscroll::fit(), cx);
11348 self.unmark_text(window, cx);
11349 self.refresh_inline_completion(true, false, window, cx);
11350 cx.emit(EditorEvent::Edited { transaction_id });
11351 }
11352 }
11353
11354 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11355 self.buffer
11356 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11357 }
11358
11359 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11360 self.buffer
11361 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11362 }
11363
11364 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11365 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11366 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11367 s.move_with(|map, selection| {
11368 let cursor = if selection.is_empty() {
11369 movement::left(map, selection.start)
11370 } else {
11371 selection.start
11372 };
11373 selection.collapse_to(cursor, SelectionGoal::None);
11374 });
11375 })
11376 }
11377
11378 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11379 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11380 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11381 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11382 })
11383 }
11384
11385 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11386 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11387 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11388 s.move_with(|map, selection| {
11389 let cursor = if selection.is_empty() {
11390 movement::right(map, selection.end)
11391 } else {
11392 selection.end
11393 };
11394 selection.collapse_to(cursor, SelectionGoal::None)
11395 });
11396 })
11397 }
11398
11399 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11400 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11401 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11402 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11403 })
11404 }
11405
11406 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11407 if self.take_rename(true, window, cx).is_some() {
11408 return;
11409 }
11410
11411 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11412 cx.propagate();
11413 return;
11414 }
11415
11416 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11417
11418 let text_layout_details = &self.text_layout_details(window);
11419 let selection_count = self.selections.count();
11420 let first_selection = self.selections.first_anchor();
11421
11422 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11423 s.move_with(|map, selection| {
11424 if !selection.is_empty() {
11425 selection.goal = SelectionGoal::None;
11426 }
11427 let (cursor, goal) = movement::up(
11428 map,
11429 selection.start,
11430 selection.goal,
11431 false,
11432 text_layout_details,
11433 );
11434 selection.collapse_to(cursor, goal);
11435 });
11436 });
11437
11438 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11439 {
11440 cx.propagate();
11441 }
11442 }
11443
11444 pub fn move_up_by_lines(
11445 &mut self,
11446 action: &MoveUpByLines,
11447 window: &mut Window,
11448 cx: &mut Context<Self>,
11449 ) {
11450 if self.take_rename(true, window, cx).is_some() {
11451 return;
11452 }
11453
11454 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11455 cx.propagate();
11456 return;
11457 }
11458
11459 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11460
11461 let text_layout_details = &self.text_layout_details(window);
11462
11463 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11464 s.move_with(|map, selection| {
11465 if !selection.is_empty() {
11466 selection.goal = SelectionGoal::None;
11467 }
11468 let (cursor, goal) = movement::up_by_rows(
11469 map,
11470 selection.start,
11471 action.lines,
11472 selection.goal,
11473 false,
11474 text_layout_details,
11475 );
11476 selection.collapse_to(cursor, goal);
11477 });
11478 })
11479 }
11480
11481 pub fn move_down_by_lines(
11482 &mut self,
11483 action: &MoveDownByLines,
11484 window: &mut Window,
11485 cx: &mut Context<Self>,
11486 ) {
11487 if self.take_rename(true, window, cx).is_some() {
11488 return;
11489 }
11490
11491 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11492 cx.propagate();
11493 return;
11494 }
11495
11496 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11497
11498 let text_layout_details = &self.text_layout_details(window);
11499
11500 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11501 s.move_with(|map, selection| {
11502 if !selection.is_empty() {
11503 selection.goal = SelectionGoal::None;
11504 }
11505 let (cursor, goal) = movement::down_by_rows(
11506 map,
11507 selection.start,
11508 action.lines,
11509 selection.goal,
11510 false,
11511 text_layout_details,
11512 );
11513 selection.collapse_to(cursor, goal);
11514 });
11515 })
11516 }
11517
11518 pub fn select_down_by_lines(
11519 &mut self,
11520 action: &SelectDownByLines,
11521 window: &mut Window,
11522 cx: &mut Context<Self>,
11523 ) {
11524 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11525 let text_layout_details = &self.text_layout_details(window);
11526 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11527 s.move_heads_with(|map, head, goal| {
11528 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11529 })
11530 })
11531 }
11532
11533 pub fn select_up_by_lines(
11534 &mut self,
11535 action: &SelectUpByLines,
11536 window: &mut Window,
11537 cx: &mut Context<Self>,
11538 ) {
11539 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11540 let text_layout_details = &self.text_layout_details(window);
11541 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11542 s.move_heads_with(|map, head, goal| {
11543 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11544 })
11545 })
11546 }
11547
11548 pub fn select_page_up(
11549 &mut self,
11550 _: &SelectPageUp,
11551 window: &mut Window,
11552 cx: &mut Context<Self>,
11553 ) {
11554 let Some(row_count) = self.visible_row_count() else {
11555 return;
11556 };
11557
11558 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11559
11560 let text_layout_details = &self.text_layout_details(window);
11561
11562 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11563 s.move_heads_with(|map, head, goal| {
11564 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11565 })
11566 })
11567 }
11568
11569 pub fn move_page_up(
11570 &mut self,
11571 action: &MovePageUp,
11572 window: &mut Window,
11573 cx: &mut Context<Self>,
11574 ) {
11575 if self.take_rename(true, window, cx).is_some() {
11576 return;
11577 }
11578
11579 if self
11580 .context_menu
11581 .borrow_mut()
11582 .as_mut()
11583 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11584 .unwrap_or(false)
11585 {
11586 return;
11587 }
11588
11589 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11590 cx.propagate();
11591 return;
11592 }
11593
11594 let Some(row_count) = self.visible_row_count() else {
11595 return;
11596 };
11597
11598 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11599
11600 let autoscroll = if action.center_cursor {
11601 Autoscroll::center()
11602 } else {
11603 Autoscroll::fit()
11604 };
11605
11606 let text_layout_details = &self.text_layout_details(window);
11607
11608 self.change_selections(Some(autoscroll), window, cx, |s| {
11609 s.move_with(|map, selection| {
11610 if !selection.is_empty() {
11611 selection.goal = SelectionGoal::None;
11612 }
11613 let (cursor, goal) = movement::up_by_rows(
11614 map,
11615 selection.end,
11616 row_count,
11617 selection.goal,
11618 false,
11619 text_layout_details,
11620 );
11621 selection.collapse_to(cursor, goal);
11622 });
11623 });
11624 }
11625
11626 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11627 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11628 let text_layout_details = &self.text_layout_details(window);
11629 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11630 s.move_heads_with(|map, head, goal| {
11631 movement::up(map, head, goal, false, text_layout_details)
11632 })
11633 })
11634 }
11635
11636 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11637 self.take_rename(true, window, cx);
11638
11639 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11640 cx.propagate();
11641 return;
11642 }
11643
11644 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11645
11646 let text_layout_details = &self.text_layout_details(window);
11647 let selection_count = self.selections.count();
11648 let first_selection = self.selections.first_anchor();
11649
11650 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11651 s.move_with(|map, selection| {
11652 if !selection.is_empty() {
11653 selection.goal = SelectionGoal::None;
11654 }
11655 let (cursor, goal) = movement::down(
11656 map,
11657 selection.end,
11658 selection.goal,
11659 false,
11660 text_layout_details,
11661 );
11662 selection.collapse_to(cursor, goal);
11663 });
11664 });
11665
11666 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11667 {
11668 cx.propagate();
11669 }
11670 }
11671
11672 pub fn select_page_down(
11673 &mut self,
11674 _: &SelectPageDown,
11675 window: &mut Window,
11676 cx: &mut Context<Self>,
11677 ) {
11678 let Some(row_count) = self.visible_row_count() else {
11679 return;
11680 };
11681
11682 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11683
11684 let text_layout_details = &self.text_layout_details(window);
11685
11686 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11687 s.move_heads_with(|map, head, goal| {
11688 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11689 })
11690 })
11691 }
11692
11693 pub fn move_page_down(
11694 &mut self,
11695 action: &MovePageDown,
11696 window: &mut Window,
11697 cx: &mut Context<Self>,
11698 ) {
11699 if self.take_rename(true, window, cx).is_some() {
11700 return;
11701 }
11702
11703 if self
11704 .context_menu
11705 .borrow_mut()
11706 .as_mut()
11707 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
11708 .unwrap_or(false)
11709 {
11710 return;
11711 }
11712
11713 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11714 cx.propagate();
11715 return;
11716 }
11717
11718 let Some(row_count) = self.visible_row_count() else {
11719 return;
11720 };
11721
11722 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11723
11724 let autoscroll = if action.center_cursor {
11725 Autoscroll::center()
11726 } else {
11727 Autoscroll::fit()
11728 };
11729
11730 let text_layout_details = &self.text_layout_details(window);
11731 self.change_selections(Some(autoscroll), window, cx, |s| {
11732 s.move_with(|map, selection| {
11733 if !selection.is_empty() {
11734 selection.goal = SelectionGoal::None;
11735 }
11736 let (cursor, goal) = movement::down_by_rows(
11737 map,
11738 selection.end,
11739 row_count,
11740 selection.goal,
11741 false,
11742 text_layout_details,
11743 );
11744 selection.collapse_to(cursor, goal);
11745 });
11746 });
11747 }
11748
11749 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11750 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11751 let text_layout_details = &self.text_layout_details(window);
11752 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11753 s.move_heads_with(|map, head, goal| {
11754 movement::down(map, head, goal, false, text_layout_details)
11755 })
11756 });
11757 }
11758
11759 pub fn context_menu_first(
11760 &mut self,
11761 _: &ContextMenuFirst,
11762 window: &mut Window,
11763 cx: &mut Context<Self>,
11764 ) {
11765 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11766 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
11767 }
11768 }
11769
11770 pub fn context_menu_prev(
11771 &mut self,
11772 _: &ContextMenuPrevious,
11773 window: &mut Window,
11774 cx: &mut Context<Self>,
11775 ) {
11776 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11777 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
11778 }
11779 }
11780
11781 pub fn context_menu_next(
11782 &mut self,
11783 _: &ContextMenuNext,
11784 window: &mut Window,
11785 cx: &mut Context<Self>,
11786 ) {
11787 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11788 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
11789 }
11790 }
11791
11792 pub fn context_menu_last(
11793 &mut self,
11794 _: &ContextMenuLast,
11795 window: &mut Window,
11796 cx: &mut Context<Self>,
11797 ) {
11798 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11799 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
11800 }
11801 }
11802
11803 pub fn move_to_previous_word_start(
11804 &mut self,
11805 _: &MoveToPreviousWordStart,
11806 window: &mut Window,
11807 cx: &mut Context<Self>,
11808 ) {
11809 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11810 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11811 s.move_cursors_with(|map, head, _| {
11812 (
11813 movement::previous_word_start(map, head),
11814 SelectionGoal::None,
11815 )
11816 });
11817 })
11818 }
11819
11820 pub fn move_to_previous_subword_start(
11821 &mut self,
11822 _: &MoveToPreviousSubwordStart,
11823 window: &mut Window,
11824 cx: &mut Context<Self>,
11825 ) {
11826 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11827 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11828 s.move_cursors_with(|map, head, _| {
11829 (
11830 movement::previous_subword_start(map, head),
11831 SelectionGoal::None,
11832 )
11833 });
11834 })
11835 }
11836
11837 pub fn select_to_previous_word_start(
11838 &mut self,
11839 _: &SelectToPreviousWordStart,
11840 window: &mut Window,
11841 cx: &mut Context<Self>,
11842 ) {
11843 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11844 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11845 s.move_heads_with(|map, head, _| {
11846 (
11847 movement::previous_word_start(map, head),
11848 SelectionGoal::None,
11849 )
11850 });
11851 })
11852 }
11853
11854 pub fn select_to_previous_subword_start(
11855 &mut self,
11856 _: &SelectToPreviousSubwordStart,
11857 window: &mut Window,
11858 cx: &mut Context<Self>,
11859 ) {
11860 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11861 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11862 s.move_heads_with(|map, head, _| {
11863 (
11864 movement::previous_subword_start(map, head),
11865 SelectionGoal::None,
11866 )
11867 });
11868 })
11869 }
11870
11871 pub fn delete_to_previous_word_start(
11872 &mut self,
11873 action: &DeleteToPreviousWordStart,
11874 window: &mut Window,
11875 cx: &mut Context<Self>,
11876 ) {
11877 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11878 self.transact(window, cx, |this, window, cx| {
11879 this.select_autoclose_pair(window, cx);
11880 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11881 s.move_with(|map, selection| {
11882 if selection.is_empty() {
11883 let cursor = if action.ignore_newlines {
11884 movement::previous_word_start(map, selection.head())
11885 } else {
11886 movement::previous_word_start_or_newline(map, selection.head())
11887 };
11888 selection.set_head(cursor, SelectionGoal::None);
11889 }
11890 });
11891 });
11892 this.insert("", window, cx);
11893 });
11894 }
11895
11896 pub fn delete_to_previous_subword_start(
11897 &mut self,
11898 _: &DeleteToPreviousSubwordStart,
11899 window: &mut Window,
11900 cx: &mut Context<Self>,
11901 ) {
11902 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11903 self.transact(window, cx, |this, window, cx| {
11904 this.select_autoclose_pair(window, cx);
11905 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11906 s.move_with(|map, selection| {
11907 if selection.is_empty() {
11908 let cursor = movement::previous_subword_start(map, selection.head());
11909 selection.set_head(cursor, SelectionGoal::None);
11910 }
11911 });
11912 });
11913 this.insert("", window, cx);
11914 });
11915 }
11916
11917 pub fn move_to_next_word_end(
11918 &mut self,
11919 _: &MoveToNextWordEnd,
11920 window: &mut Window,
11921 cx: &mut Context<Self>,
11922 ) {
11923 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11924 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11925 s.move_cursors_with(|map, head, _| {
11926 (movement::next_word_end(map, head), SelectionGoal::None)
11927 });
11928 })
11929 }
11930
11931 pub fn move_to_next_subword_end(
11932 &mut self,
11933 _: &MoveToNextSubwordEnd,
11934 window: &mut Window,
11935 cx: &mut Context<Self>,
11936 ) {
11937 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11938 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11939 s.move_cursors_with(|map, head, _| {
11940 (movement::next_subword_end(map, head), SelectionGoal::None)
11941 });
11942 })
11943 }
11944
11945 pub fn select_to_next_word_end(
11946 &mut self,
11947 _: &SelectToNextWordEnd,
11948 window: &mut Window,
11949 cx: &mut Context<Self>,
11950 ) {
11951 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11952 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11953 s.move_heads_with(|map, head, _| {
11954 (movement::next_word_end(map, head), SelectionGoal::None)
11955 });
11956 })
11957 }
11958
11959 pub fn select_to_next_subword_end(
11960 &mut self,
11961 _: &SelectToNextSubwordEnd,
11962 window: &mut Window,
11963 cx: &mut Context<Self>,
11964 ) {
11965 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11966 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11967 s.move_heads_with(|map, head, _| {
11968 (movement::next_subword_end(map, head), SelectionGoal::None)
11969 });
11970 })
11971 }
11972
11973 pub fn delete_to_next_word_end(
11974 &mut self,
11975 action: &DeleteToNextWordEnd,
11976 window: &mut Window,
11977 cx: &mut Context<Self>,
11978 ) {
11979 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11980 self.transact(window, cx, |this, window, cx| {
11981 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11982 s.move_with(|map, selection| {
11983 if selection.is_empty() {
11984 let cursor = if action.ignore_newlines {
11985 movement::next_word_end(map, selection.head())
11986 } else {
11987 movement::next_word_end_or_newline(map, selection.head())
11988 };
11989 selection.set_head(cursor, SelectionGoal::None);
11990 }
11991 });
11992 });
11993 this.insert("", window, cx);
11994 });
11995 }
11996
11997 pub fn delete_to_next_subword_end(
11998 &mut self,
11999 _: &DeleteToNextSubwordEnd,
12000 window: &mut Window,
12001 cx: &mut Context<Self>,
12002 ) {
12003 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12004 self.transact(window, cx, |this, window, cx| {
12005 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12006 s.move_with(|map, selection| {
12007 if selection.is_empty() {
12008 let cursor = movement::next_subword_end(map, selection.head());
12009 selection.set_head(cursor, SelectionGoal::None);
12010 }
12011 });
12012 });
12013 this.insert("", window, cx);
12014 });
12015 }
12016
12017 pub fn move_to_beginning_of_line(
12018 &mut self,
12019 action: &MoveToBeginningOfLine,
12020 window: &mut Window,
12021 cx: &mut Context<Self>,
12022 ) {
12023 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12024 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12025 s.move_cursors_with(|map, head, _| {
12026 (
12027 movement::indented_line_beginning(
12028 map,
12029 head,
12030 action.stop_at_soft_wraps,
12031 action.stop_at_indent,
12032 ),
12033 SelectionGoal::None,
12034 )
12035 });
12036 })
12037 }
12038
12039 pub fn select_to_beginning_of_line(
12040 &mut self,
12041 action: &SelectToBeginningOfLine,
12042 window: &mut Window,
12043 cx: &mut Context<Self>,
12044 ) {
12045 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12046 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12047 s.move_heads_with(|map, head, _| {
12048 (
12049 movement::indented_line_beginning(
12050 map,
12051 head,
12052 action.stop_at_soft_wraps,
12053 action.stop_at_indent,
12054 ),
12055 SelectionGoal::None,
12056 )
12057 });
12058 });
12059 }
12060
12061 pub fn delete_to_beginning_of_line(
12062 &mut self,
12063 action: &DeleteToBeginningOfLine,
12064 window: &mut Window,
12065 cx: &mut Context<Self>,
12066 ) {
12067 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12068 self.transact(window, cx, |this, window, cx| {
12069 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12070 s.move_with(|_, selection| {
12071 selection.reversed = true;
12072 });
12073 });
12074
12075 this.select_to_beginning_of_line(
12076 &SelectToBeginningOfLine {
12077 stop_at_soft_wraps: false,
12078 stop_at_indent: action.stop_at_indent,
12079 },
12080 window,
12081 cx,
12082 );
12083 this.backspace(&Backspace, window, cx);
12084 });
12085 }
12086
12087 pub fn move_to_end_of_line(
12088 &mut self,
12089 action: &MoveToEndOfLine,
12090 window: &mut Window,
12091 cx: &mut Context<Self>,
12092 ) {
12093 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12094 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12095 s.move_cursors_with(|map, head, _| {
12096 (
12097 movement::line_end(map, head, action.stop_at_soft_wraps),
12098 SelectionGoal::None,
12099 )
12100 });
12101 })
12102 }
12103
12104 pub fn select_to_end_of_line(
12105 &mut self,
12106 action: &SelectToEndOfLine,
12107 window: &mut Window,
12108 cx: &mut Context<Self>,
12109 ) {
12110 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12111 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12112 s.move_heads_with(|map, head, _| {
12113 (
12114 movement::line_end(map, head, action.stop_at_soft_wraps),
12115 SelectionGoal::None,
12116 )
12117 });
12118 })
12119 }
12120
12121 pub fn delete_to_end_of_line(
12122 &mut self,
12123 _: &DeleteToEndOfLine,
12124 window: &mut Window,
12125 cx: &mut Context<Self>,
12126 ) {
12127 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12128 self.transact(window, cx, |this, window, cx| {
12129 this.select_to_end_of_line(
12130 &SelectToEndOfLine {
12131 stop_at_soft_wraps: false,
12132 },
12133 window,
12134 cx,
12135 );
12136 this.delete(&Delete, window, cx);
12137 });
12138 }
12139
12140 pub fn cut_to_end_of_line(
12141 &mut self,
12142 _: &CutToEndOfLine,
12143 window: &mut Window,
12144 cx: &mut Context<Self>,
12145 ) {
12146 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12147 self.transact(window, cx, |this, window, cx| {
12148 this.select_to_end_of_line(
12149 &SelectToEndOfLine {
12150 stop_at_soft_wraps: false,
12151 },
12152 window,
12153 cx,
12154 );
12155 this.cut(&Cut, window, cx);
12156 });
12157 }
12158
12159 pub fn move_to_start_of_paragraph(
12160 &mut self,
12161 _: &MoveToStartOfParagraph,
12162 window: &mut Window,
12163 cx: &mut Context<Self>,
12164 ) {
12165 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12166 cx.propagate();
12167 return;
12168 }
12169 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12170 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12171 s.move_with(|map, selection| {
12172 selection.collapse_to(
12173 movement::start_of_paragraph(map, selection.head(), 1),
12174 SelectionGoal::None,
12175 )
12176 });
12177 })
12178 }
12179
12180 pub fn move_to_end_of_paragraph(
12181 &mut self,
12182 _: &MoveToEndOfParagraph,
12183 window: &mut Window,
12184 cx: &mut Context<Self>,
12185 ) {
12186 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12187 cx.propagate();
12188 return;
12189 }
12190 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12191 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12192 s.move_with(|map, selection| {
12193 selection.collapse_to(
12194 movement::end_of_paragraph(map, selection.head(), 1),
12195 SelectionGoal::None,
12196 )
12197 });
12198 })
12199 }
12200
12201 pub fn select_to_start_of_paragraph(
12202 &mut self,
12203 _: &SelectToStartOfParagraph,
12204 window: &mut Window,
12205 cx: &mut Context<Self>,
12206 ) {
12207 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12208 cx.propagate();
12209 return;
12210 }
12211 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12212 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12213 s.move_heads_with(|map, head, _| {
12214 (
12215 movement::start_of_paragraph(map, head, 1),
12216 SelectionGoal::None,
12217 )
12218 });
12219 })
12220 }
12221
12222 pub fn select_to_end_of_paragraph(
12223 &mut self,
12224 _: &SelectToEndOfParagraph,
12225 window: &mut Window,
12226 cx: &mut Context<Self>,
12227 ) {
12228 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12229 cx.propagate();
12230 return;
12231 }
12232 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12233 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12234 s.move_heads_with(|map, head, _| {
12235 (
12236 movement::end_of_paragraph(map, head, 1),
12237 SelectionGoal::None,
12238 )
12239 });
12240 })
12241 }
12242
12243 pub fn move_to_start_of_excerpt(
12244 &mut self,
12245 _: &MoveToStartOfExcerpt,
12246 window: &mut Window,
12247 cx: &mut Context<Self>,
12248 ) {
12249 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12250 cx.propagate();
12251 return;
12252 }
12253 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12254 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12255 s.move_with(|map, selection| {
12256 selection.collapse_to(
12257 movement::start_of_excerpt(
12258 map,
12259 selection.head(),
12260 workspace::searchable::Direction::Prev,
12261 ),
12262 SelectionGoal::None,
12263 )
12264 });
12265 })
12266 }
12267
12268 pub fn move_to_start_of_next_excerpt(
12269 &mut self,
12270 _: &MoveToStartOfNextExcerpt,
12271 window: &mut Window,
12272 cx: &mut Context<Self>,
12273 ) {
12274 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12275 cx.propagate();
12276 return;
12277 }
12278
12279 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12280 s.move_with(|map, selection| {
12281 selection.collapse_to(
12282 movement::start_of_excerpt(
12283 map,
12284 selection.head(),
12285 workspace::searchable::Direction::Next,
12286 ),
12287 SelectionGoal::None,
12288 )
12289 });
12290 })
12291 }
12292
12293 pub fn move_to_end_of_excerpt(
12294 &mut self,
12295 _: &MoveToEndOfExcerpt,
12296 window: &mut Window,
12297 cx: &mut Context<Self>,
12298 ) {
12299 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12300 cx.propagate();
12301 return;
12302 }
12303 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12304 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12305 s.move_with(|map, selection| {
12306 selection.collapse_to(
12307 movement::end_of_excerpt(
12308 map,
12309 selection.head(),
12310 workspace::searchable::Direction::Next,
12311 ),
12312 SelectionGoal::None,
12313 )
12314 });
12315 })
12316 }
12317
12318 pub fn move_to_end_of_previous_excerpt(
12319 &mut self,
12320 _: &MoveToEndOfPreviousExcerpt,
12321 window: &mut Window,
12322 cx: &mut Context<Self>,
12323 ) {
12324 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12325 cx.propagate();
12326 return;
12327 }
12328 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12329 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12330 s.move_with(|map, selection| {
12331 selection.collapse_to(
12332 movement::end_of_excerpt(
12333 map,
12334 selection.head(),
12335 workspace::searchable::Direction::Prev,
12336 ),
12337 SelectionGoal::None,
12338 )
12339 });
12340 })
12341 }
12342
12343 pub fn select_to_start_of_excerpt(
12344 &mut self,
12345 _: &SelectToStartOfExcerpt,
12346 window: &mut Window,
12347 cx: &mut Context<Self>,
12348 ) {
12349 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12350 cx.propagate();
12351 return;
12352 }
12353 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12354 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12355 s.move_heads_with(|map, head, _| {
12356 (
12357 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12358 SelectionGoal::None,
12359 )
12360 });
12361 })
12362 }
12363
12364 pub fn select_to_start_of_next_excerpt(
12365 &mut self,
12366 _: &SelectToStartOfNextExcerpt,
12367 window: &mut Window,
12368 cx: &mut Context<Self>,
12369 ) {
12370 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12371 cx.propagate();
12372 return;
12373 }
12374 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12375 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12376 s.move_heads_with(|map, head, _| {
12377 (
12378 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12379 SelectionGoal::None,
12380 )
12381 });
12382 })
12383 }
12384
12385 pub fn select_to_end_of_excerpt(
12386 &mut self,
12387 _: &SelectToEndOfExcerpt,
12388 window: &mut Window,
12389 cx: &mut Context<Self>,
12390 ) {
12391 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12392 cx.propagate();
12393 return;
12394 }
12395 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12396 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12397 s.move_heads_with(|map, head, _| {
12398 (
12399 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12400 SelectionGoal::None,
12401 )
12402 });
12403 })
12404 }
12405
12406 pub fn select_to_end_of_previous_excerpt(
12407 &mut self,
12408 _: &SelectToEndOfPreviousExcerpt,
12409 window: &mut Window,
12410 cx: &mut Context<Self>,
12411 ) {
12412 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12413 cx.propagate();
12414 return;
12415 }
12416 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12417 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12418 s.move_heads_with(|map, head, _| {
12419 (
12420 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12421 SelectionGoal::None,
12422 )
12423 });
12424 })
12425 }
12426
12427 pub fn move_to_beginning(
12428 &mut self,
12429 _: &MoveToBeginning,
12430 window: &mut Window,
12431 cx: &mut Context<Self>,
12432 ) {
12433 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12434 cx.propagate();
12435 return;
12436 }
12437 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12438 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12439 s.select_ranges(vec![0..0]);
12440 });
12441 }
12442
12443 pub fn select_to_beginning(
12444 &mut self,
12445 _: &SelectToBeginning,
12446 window: &mut Window,
12447 cx: &mut Context<Self>,
12448 ) {
12449 let mut selection = self.selections.last::<Point>(cx);
12450 selection.set_head(Point::zero(), SelectionGoal::None);
12451 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12452 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12453 s.select(vec![selection]);
12454 });
12455 }
12456
12457 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12458 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12459 cx.propagate();
12460 return;
12461 }
12462 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12463 let cursor = self.buffer.read(cx).read(cx).len();
12464 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12465 s.select_ranges(vec![cursor..cursor])
12466 });
12467 }
12468
12469 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12470 self.nav_history = nav_history;
12471 }
12472
12473 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12474 self.nav_history.as_ref()
12475 }
12476
12477 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12478 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12479 }
12480
12481 fn push_to_nav_history(
12482 &mut self,
12483 cursor_anchor: Anchor,
12484 new_position: Option<Point>,
12485 is_deactivate: bool,
12486 cx: &mut Context<Self>,
12487 ) {
12488 if let Some(nav_history) = self.nav_history.as_mut() {
12489 let buffer = self.buffer.read(cx).read(cx);
12490 let cursor_position = cursor_anchor.to_point(&buffer);
12491 let scroll_state = self.scroll_manager.anchor();
12492 let scroll_top_row = scroll_state.top_row(&buffer);
12493 drop(buffer);
12494
12495 if let Some(new_position) = new_position {
12496 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12497 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12498 return;
12499 }
12500 }
12501
12502 nav_history.push(
12503 Some(NavigationData {
12504 cursor_anchor,
12505 cursor_position,
12506 scroll_anchor: scroll_state,
12507 scroll_top_row,
12508 }),
12509 cx,
12510 );
12511 cx.emit(EditorEvent::PushedToNavHistory {
12512 anchor: cursor_anchor,
12513 is_deactivate,
12514 })
12515 }
12516 }
12517
12518 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12519 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12520 let buffer = self.buffer.read(cx).snapshot(cx);
12521 let mut selection = self.selections.first::<usize>(cx);
12522 selection.set_head(buffer.len(), SelectionGoal::None);
12523 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12524 s.select(vec![selection]);
12525 });
12526 }
12527
12528 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12529 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12530 let end = self.buffer.read(cx).read(cx).len();
12531 self.change_selections(None, window, cx, |s| {
12532 s.select_ranges(vec![0..end]);
12533 });
12534 }
12535
12536 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12537 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12538 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12539 let mut selections = self.selections.all::<Point>(cx);
12540 let max_point = display_map.buffer_snapshot.max_point();
12541 for selection in &mut selections {
12542 let rows = selection.spanned_rows(true, &display_map);
12543 selection.start = Point::new(rows.start.0, 0);
12544 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12545 selection.reversed = false;
12546 }
12547 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12548 s.select(selections);
12549 });
12550 }
12551
12552 pub fn split_selection_into_lines(
12553 &mut self,
12554 _: &SplitSelectionIntoLines,
12555 window: &mut Window,
12556 cx: &mut Context<Self>,
12557 ) {
12558 let selections = self
12559 .selections
12560 .all::<Point>(cx)
12561 .into_iter()
12562 .map(|selection| selection.start..selection.end)
12563 .collect::<Vec<_>>();
12564 self.unfold_ranges(&selections, true, true, cx);
12565
12566 let mut new_selection_ranges = Vec::new();
12567 {
12568 let buffer = self.buffer.read(cx).read(cx);
12569 for selection in selections {
12570 for row in selection.start.row..selection.end.row {
12571 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12572 new_selection_ranges.push(cursor..cursor);
12573 }
12574
12575 let is_multiline_selection = selection.start.row != selection.end.row;
12576 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12577 // so this action feels more ergonomic when paired with other selection operations
12578 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12579 if !should_skip_last {
12580 new_selection_ranges.push(selection.end..selection.end);
12581 }
12582 }
12583 }
12584 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12585 s.select_ranges(new_selection_ranges);
12586 });
12587 }
12588
12589 pub fn add_selection_above(
12590 &mut self,
12591 _: &AddSelectionAbove,
12592 window: &mut Window,
12593 cx: &mut Context<Self>,
12594 ) {
12595 self.add_selection(true, window, cx);
12596 }
12597
12598 pub fn add_selection_below(
12599 &mut self,
12600 _: &AddSelectionBelow,
12601 window: &mut Window,
12602 cx: &mut Context<Self>,
12603 ) {
12604 self.add_selection(false, window, cx);
12605 }
12606
12607 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12608 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12609
12610 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12611 let mut selections = self.selections.all::<Point>(cx);
12612 let text_layout_details = self.text_layout_details(window);
12613 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12614 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12615 let range = oldest_selection.display_range(&display_map).sorted();
12616
12617 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12618 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12619 let positions = start_x.min(end_x)..start_x.max(end_x);
12620
12621 selections.clear();
12622 let mut stack = Vec::new();
12623 for row in range.start.row().0..=range.end.row().0 {
12624 if let Some(selection) = self.selections.build_columnar_selection(
12625 &display_map,
12626 DisplayRow(row),
12627 &positions,
12628 oldest_selection.reversed,
12629 &text_layout_details,
12630 ) {
12631 stack.push(selection.id);
12632 selections.push(selection);
12633 }
12634 }
12635
12636 if above {
12637 stack.reverse();
12638 }
12639
12640 AddSelectionsState { above, stack }
12641 });
12642
12643 let last_added_selection = *state.stack.last().unwrap();
12644 let mut new_selections = Vec::new();
12645 if above == state.above {
12646 let end_row = if above {
12647 DisplayRow(0)
12648 } else {
12649 display_map.max_point().row()
12650 };
12651
12652 'outer: for selection in selections {
12653 if selection.id == last_added_selection {
12654 let range = selection.display_range(&display_map).sorted();
12655 debug_assert_eq!(range.start.row(), range.end.row());
12656 let mut row = range.start.row();
12657 let positions =
12658 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12659 px(start)..px(end)
12660 } else {
12661 let start_x =
12662 display_map.x_for_display_point(range.start, &text_layout_details);
12663 let end_x =
12664 display_map.x_for_display_point(range.end, &text_layout_details);
12665 start_x.min(end_x)..start_x.max(end_x)
12666 };
12667
12668 while row != end_row {
12669 if above {
12670 row.0 -= 1;
12671 } else {
12672 row.0 += 1;
12673 }
12674
12675 if let Some(new_selection) = self.selections.build_columnar_selection(
12676 &display_map,
12677 row,
12678 &positions,
12679 selection.reversed,
12680 &text_layout_details,
12681 ) {
12682 state.stack.push(new_selection.id);
12683 if above {
12684 new_selections.push(new_selection);
12685 new_selections.push(selection);
12686 } else {
12687 new_selections.push(selection);
12688 new_selections.push(new_selection);
12689 }
12690
12691 continue 'outer;
12692 }
12693 }
12694 }
12695
12696 new_selections.push(selection);
12697 }
12698 } else {
12699 new_selections = selections;
12700 new_selections.retain(|s| s.id != last_added_selection);
12701 state.stack.pop();
12702 }
12703
12704 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12705 s.select(new_selections);
12706 });
12707 if state.stack.len() > 1 {
12708 self.add_selections_state = Some(state);
12709 }
12710 }
12711
12712 fn select_match_ranges(
12713 &mut self,
12714 range: Range<usize>,
12715 reversed: bool,
12716 replace_newest: bool,
12717 auto_scroll: Option<Autoscroll>,
12718 window: &mut Window,
12719 cx: &mut Context<Editor>,
12720 ) {
12721 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12722 self.change_selections(auto_scroll, window, cx, |s| {
12723 if replace_newest {
12724 s.delete(s.newest_anchor().id);
12725 }
12726 if reversed {
12727 s.insert_range(range.end..range.start);
12728 } else {
12729 s.insert_range(range);
12730 }
12731 });
12732 }
12733
12734 pub fn select_next_match_internal(
12735 &mut self,
12736 display_map: &DisplaySnapshot,
12737 replace_newest: bool,
12738 autoscroll: Option<Autoscroll>,
12739 window: &mut Window,
12740 cx: &mut Context<Self>,
12741 ) -> Result<()> {
12742 let buffer = &display_map.buffer_snapshot;
12743 let mut selections = self.selections.all::<usize>(cx);
12744 if let Some(mut select_next_state) = self.select_next_state.take() {
12745 let query = &select_next_state.query;
12746 if !select_next_state.done {
12747 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12748 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12749 let mut next_selected_range = None;
12750
12751 let bytes_after_last_selection =
12752 buffer.bytes_in_range(last_selection.end..buffer.len());
12753 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12754 let query_matches = query
12755 .stream_find_iter(bytes_after_last_selection)
12756 .map(|result| (last_selection.end, result))
12757 .chain(
12758 query
12759 .stream_find_iter(bytes_before_first_selection)
12760 .map(|result| (0, result)),
12761 );
12762
12763 for (start_offset, query_match) in query_matches {
12764 let query_match = query_match.unwrap(); // can only fail due to I/O
12765 let offset_range =
12766 start_offset + query_match.start()..start_offset + query_match.end();
12767 let display_range = offset_range.start.to_display_point(display_map)
12768 ..offset_range.end.to_display_point(display_map);
12769
12770 if !select_next_state.wordwise
12771 || (!movement::is_inside_word(display_map, display_range.start)
12772 && !movement::is_inside_word(display_map, display_range.end))
12773 {
12774 // TODO: This is n^2, because we might check all the selections
12775 if !selections
12776 .iter()
12777 .any(|selection| selection.range().overlaps(&offset_range))
12778 {
12779 next_selected_range = Some(offset_range);
12780 break;
12781 }
12782 }
12783 }
12784
12785 if let Some(next_selected_range) = next_selected_range {
12786 self.select_match_ranges(
12787 next_selected_range,
12788 last_selection.reversed,
12789 replace_newest,
12790 autoscroll,
12791 window,
12792 cx,
12793 );
12794 } else {
12795 select_next_state.done = true;
12796 }
12797 }
12798
12799 self.select_next_state = Some(select_next_state);
12800 } else {
12801 let mut only_carets = true;
12802 let mut same_text_selected = true;
12803 let mut selected_text = None;
12804
12805 let mut selections_iter = selections.iter().peekable();
12806 while let Some(selection) = selections_iter.next() {
12807 if selection.start != selection.end {
12808 only_carets = false;
12809 }
12810
12811 if same_text_selected {
12812 if selected_text.is_none() {
12813 selected_text =
12814 Some(buffer.text_for_range(selection.range()).collect::<String>());
12815 }
12816
12817 if let Some(next_selection) = selections_iter.peek() {
12818 if next_selection.range().len() == selection.range().len() {
12819 let next_selected_text = buffer
12820 .text_for_range(next_selection.range())
12821 .collect::<String>();
12822 if Some(next_selected_text) != selected_text {
12823 same_text_selected = false;
12824 selected_text = None;
12825 }
12826 } else {
12827 same_text_selected = false;
12828 selected_text = None;
12829 }
12830 }
12831 }
12832 }
12833
12834 if only_carets {
12835 for selection in &mut selections {
12836 let word_range = movement::surrounding_word(
12837 display_map,
12838 selection.start.to_display_point(display_map),
12839 );
12840 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12841 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12842 selection.goal = SelectionGoal::None;
12843 selection.reversed = false;
12844 self.select_match_ranges(
12845 selection.start..selection.end,
12846 selection.reversed,
12847 replace_newest,
12848 autoscroll,
12849 window,
12850 cx,
12851 );
12852 }
12853
12854 if selections.len() == 1 {
12855 let selection = selections
12856 .last()
12857 .expect("ensured that there's only one selection");
12858 let query = buffer
12859 .text_for_range(selection.start..selection.end)
12860 .collect::<String>();
12861 let is_empty = query.is_empty();
12862 let select_state = SelectNextState {
12863 query: AhoCorasick::new(&[query])?,
12864 wordwise: true,
12865 done: is_empty,
12866 };
12867 self.select_next_state = Some(select_state);
12868 } else {
12869 self.select_next_state = None;
12870 }
12871 } else if let Some(selected_text) = selected_text {
12872 self.select_next_state = Some(SelectNextState {
12873 query: AhoCorasick::new(&[selected_text])?,
12874 wordwise: false,
12875 done: false,
12876 });
12877 self.select_next_match_internal(
12878 display_map,
12879 replace_newest,
12880 autoscroll,
12881 window,
12882 cx,
12883 )?;
12884 }
12885 }
12886 Ok(())
12887 }
12888
12889 pub fn select_all_matches(
12890 &mut self,
12891 _action: &SelectAllMatches,
12892 window: &mut Window,
12893 cx: &mut Context<Self>,
12894 ) -> Result<()> {
12895 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12896
12897 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12898
12899 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12900 let Some(select_next_state) = self.select_next_state.as_mut() else {
12901 return Ok(());
12902 };
12903 if select_next_state.done {
12904 return Ok(());
12905 }
12906
12907 let mut new_selections = Vec::new();
12908
12909 let reversed = self.selections.oldest::<usize>(cx).reversed;
12910 let buffer = &display_map.buffer_snapshot;
12911 let query_matches = select_next_state
12912 .query
12913 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12914
12915 for query_match in query_matches.into_iter() {
12916 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12917 let offset_range = if reversed {
12918 query_match.end()..query_match.start()
12919 } else {
12920 query_match.start()..query_match.end()
12921 };
12922 let display_range = offset_range.start.to_display_point(&display_map)
12923 ..offset_range.end.to_display_point(&display_map);
12924
12925 if !select_next_state.wordwise
12926 || (!movement::is_inside_word(&display_map, display_range.start)
12927 && !movement::is_inside_word(&display_map, display_range.end))
12928 {
12929 new_selections.push(offset_range.start..offset_range.end);
12930 }
12931 }
12932
12933 select_next_state.done = true;
12934 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12935 self.change_selections(None, window, cx, |selections| {
12936 selections.select_ranges(new_selections)
12937 });
12938
12939 Ok(())
12940 }
12941
12942 pub fn select_next(
12943 &mut self,
12944 action: &SelectNext,
12945 window: &mut Window,
12946 cx: &mut Context<Self>,
12947 ) -> Result<()> {
12948 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12949 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12950 self.select_next_match_internal(
12951 &display_map,
12952 action.replace_newest,
12953 Some(Autoscroll::newest()),
12954 window,
12955 cx,
12956 )?;
12957 Ok(())
12958 }
12959
12960 pub fn select_previous(
12961 &mut self,
12962 action: &SelectPrevious,
12963 window: &mut Window,
12964 cx: &mut Context<Self>,
12965 ) -> Result<()> {
12966 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12967 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12968 let buffer = &display_map.buffer_snapshot;
12969 let mut selections = self.selections.all::<usize>(cx);
12970 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12971 let query = &select_prev_state.query;
12972 if !select_prev_state.done {
12973 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12974 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12975 let mut next_selected_range = None;
12976 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12977 let bytes_before_last_selection =
12978 buffer.reversed_bytes_in_range(0..last_selection.start);
12979 let bytes_after_first_selection =
12980 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12981 let query_matches = query
12982 .stream_find_iter(bytes_before_last_selection)
12983 .map(|result| (last_selection.start, result))
12984 .chain(
12985 query
12986 .stream_find_iter(bytes_after_first_selection)
12987 .map(|result| (buffer.len(), result)),
12988 );
12989 for (end_offset, query_match) in query_matches {
12990 let query_match = query_match.unwrap(); // can only fail due to I/O
12991 let offset_range =
12992 end_offset - query_match.end()..end_offset - query_match.start();
12993 let display_range = offset_range.start.to_display_point(&display_map)
12994 ..offset_range.end.to_display_point(&display_map);
12995
12996 if !select_prev_state.wordwise
12997 || (!movement::is_inside_word(&display_map, display_range.start)
12998 && !movement::is_inside_word(&display_map, display_range.end))
12999 {
13000 next_selected_range = Some(offset_range);
13001 break;
13002 }
13003 }
13004
13005 if let Some(next_selected_range) = next_selected_range {
13006 self.select_match_ranges(
13007 next_selected_range,
13008 last_selection.reversed,
13009 action.replace_newest,
13010 Some(Autoscroll::newest()),
13011 window,
13012 cx,
13013 );
13014 } else {
13015 select_prev_state.done = true;
13016 }
13017 }
13018
13019 self.select_prev_state = Some(select_prev_state);
13020 } else {
13021 let mut only_carets = true;
13022 let mut same_text_selected = true;
13023 let mut selected_text = None;
13024
13025 let mut selections_iter = selections.iter().peekable();
13026 while let Some(selection) = selections_iter.next() {
13027 if selection.start != selection.end {
13028 only_carets = false;
13029 }
13030
13031 if same_text_selected {
13032 if selected_text.is_none() {
13033 selected_text =
13034 Some(buffer.text_for_range(selection.range()).collect::<String>());
13035 }
13036
13037 if let Some(next_selection) = selections_iter.peek() {
13038 if next_selection.range().len() == selection.range().len() {
13039 let next_selected_text = buffer
13040 .text_for_range(next_selection.range())
13041 .collect::<String>();
13042 if Some(next_selected_text) != selected_text {
13043 same_text_selected = false;
13044 selected_text = None;
13045 }
13046 } else {
13047 same_text_selected = false;
13048 selected_text = None;
13049 }
13050 }
13051 }
13052 }
13053
13054 if only_carets {
13055 for selection in &mut selections {
13056 let word_range = movement::surrounding_word(
13057 &display_map,
13058 selection.start.to_display_point(&display_map),
13059 );
13060 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
13061 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
13062 selection.goal = SelectionGoal::None;
13063 selection.reversed = false;
13064 self.select_match_ranges(
13065 selection.start..selection.end,
13066 selection.reversed,
13067 action.replace_newest,
13068 Some(Autoscroll::newest()),
13069 window,
13070 cx,
13071 );
13072 }
13073 if selections.len() == 1 {
13074 let selection = selections
13075 .last()
13076 .expect("ensured that there's only one selection");
13077 let query = buffer
13078 .text_for_range(selection.start..selection.end)
13079 .collect::<String>();
13080 let is_empty = query.is_empty();
13081 let select_state = SelectNextState {
13082 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13083 wordwise: true,
13084 done: is_empty,
13085 };
13086 self.select_prev_state = Some(select_state);
13087 } else {
13088 self.select_prev_state = None;
13089 }
13090 } else if let Some(selected_text) = selected_text {
13091 self.select_prev_state = Some(SelectNextState {
13092 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13093 wordwise: false,
13094 done: false,
13095 });
13096 self.select_previous(action, window, cx)?;
13097 }
13098 }
13099 Ok(())
13100 }
13101
13102 pub fn find_next_match(
13103 &mut self,
13104 _: &FindNextMatch,
13105 window: &mut Window,
13106 cx: &mut Context<Self>,
13107 ) -> Result<()> {
13108 let selections = self.selections.disjoint_anchors();
13109 match selections.first() {
13110 Some(first) if selections.len() >= 2 => {
13111 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13112 s.select_ranges([first.range()]);
13113 });
13114 }
13115 _ => self.select_next(
13116 &SelectNext {
13117 replace_newest: true,
13118 },
13119 window,
13120 cx,
13121 )?,
13122 }
13123 Ok(())
13124 }
13125
13126 pub fn find_previous_match(
13127 &mut self,
13128 _: &FindPreviousMatch,
13129 window: &mut Window,
13130 cx: &mut Context<Self>,
13131 ) -> Result<()> {
13132 let selections = self.selections.disjoint_anchors();
13133 match selections.last() {
13134 Some(last) if selections.len() >= 2 => {
13135 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13136 s.select_ranges([last.range()]);
13137 });
13138 }
13139 _ => self.select_previous(
13140 &SelectPrevious {
13141 replace_newest: true,
13142 },
13143 window,
13144 cx,
13145 )?,
13146 }
13147 Ok(())
13148 }
13149
13150 pub fn toggle_comments(
13151 &mut self,
13152 action: &ToggleComments,
13153 window: &mut Window,
13154 cx: &mut Context<Self>,
13155 ) {
13156 if self.read_only(cx) {
13157 return;
13158 }
13159 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13160 let text_layout_details = &self.text_layout_details(window);
13161 self.transact(window, cx, |this, window, cx| {
13162 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13163 let mut edits = Vec::new();
13164 let mut selection_edit_ranges = Vec::new();
13165 let mut last_toggled_row = None;
13166 let snapshot = this.buffer.read(cx).read(cx);
13167 let empty_str: Arc<str> = Arc::default();
13168 let mut suffixes_inserted = Vec::new();
13169 let ignore_indent = action.ignore_indent;
13170
13171 fn comment_prefix_range(
13172 snapshot: &MultiBufferSnapshot,
13173 row: MultiBufferRow,
13174 comment_prefix: &str,
13175 comment_prefix_whitespace: &str,
13176 ignore_indent: bool,
13177 ) -> Range<Point> {
13178 let indent_size = if ignore_indent {
13179 0
13180 } else {
13181 snapshot.indent_size_for_line(row).len
13182 };
13183
13184 let start = Point::new(row.0, indent_size);
13185
13186 let mut line_bytes = snapshot
13187 .bytes_in_range(start..snapshot.max_point())
13188 .flatten()
13189 .copied();
13190
13191 // If this line currently begins with the line comment prefix, then record
13192 // the range containing the prefix.
13193 if line_bytes
13194 .by_ref()
13195 .take(comment_prefix.len())
13196 .eq(comment_prefix.bytes())
13197 {
13198 // Include any whitespace that matches the comment prefix.
13199 let matching_whitespace_len = line_bytes
13200 .zip(comment_prefix_whitespace.bytes())
13201 .take_while(|(a, b)| a == b)
13202 .count() as u32;
13203 let end = Point::new(
13204 start.row,
13205 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13206 );
13207 start..end
13208 } else {
13209 start..start
13210 }
13211 }
13212
13213 fn comment_suffix_range(
13214 snapshot: &MultiBufferSnapshot,
13215 row: MultiBufferRow,
13216 comment_suffix: &str,
13217 comment_suffix_has_leading_space: bool,
13218 ) -> Range<Point> {
13219 let end = Point::new(row.0, snapshot.line_len(row));
13220 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13221
13222 let mut line_end_bytes = snapshot
13223 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13224 .flatten()
13225 .copied();
13226
13227 let leading_space_len = if suffix_start_column > 0
13228 && line_end_bytes.next() == Some(b' ')
13229 && comment_suffix_has_leading_space
13230 {
13231 1
13232 } else {
13233 0
13234 };
13235
13236 // If this line currently begins with the line comment prefix, then record
13237 // the range containing the prefix.
13238 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13239 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13240 start..end
13241 } else {
13242 end..end
13243 }
13244 }
13245
13246 // TODO: Handle selections that cross excerpts
13247 for selection in &mut selections {
13248 let start_column = snapshot
13249 .indent_size_for_line(MultiBufferRow(selection.start.row))
13250 .len;
13251 let language = if let Some(language) =
13252 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13253 {
13254 language
13255 } else {
13256 continue;
13257 };
13258
13259 selection_edit_ranges.clear();
13260
13261 // If multiple selections contain a given row, avoid processing that
13262 // row more than once.
13263 let mut start_row = MultiBufferRow(selection.start.row);
13264 if last_toggled_row == Some(start_row) {
13265 start_row = start_row.next_row();
13266 }
13267 let end_row =
13268 if selection.end.row > selection.start.row && selection.end.column == 0 {
13269 MultiBufferRow(selection.end.row - 1)
13270 } else {
13271 MultiBufferRow(selection.end.row)
13272 };
13273 last_toggled_row = Some(end_row);
13274
13275 if start_row > end_row {
13276 continue;
13277 }
13278
13279 // If the language has line comments, toggle those.
13280 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13281
13282 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13283 if ignore_indent {
13284 full_comment_prefixes = full_comment_prefixes
13285 .into_iter()
13286 .map(|s| Arc::from(s.trim_end()))
13287 .collect();
13288 }
13289
13290 if !full_comment_prefixes.is_empty() {
13291 let first_prefix = full_comment_prefixes
13292 .first()
13293 .expect("prefixes is non-empty");
13294 let prefix_trimmed_lengths = full_comment_prefixes
13295 .iter()
13296 .map(|p| p.trim_end_matches(' ').len())
13297 .collect::<SmallVec<[usize; 4]>>();
13298
13299 let mut all_selection_lines_are_comments = true;
13300
13301 for row in start_row.0..=end_row.0 {
13302 let row = MultiBufferRow(row);
13303 if start_row < end_row && snapshot.is_line_blank(row) {
13304 continue;
13305 }
13306
13307 let prefix_range = full_comment_prefixes
13308 .iter()
13309 .zip(prefix_trimmed_lengths.iter().copied())
13310 .map(|(prefix, trimmed_prefix_len)| {
13311 comment_prefix_range(
13312 snapshot.deref(),
13313 row,
13314 &prefix[..trimmed_prefix_len],
13315 &prefix[trimmed_prefix_len..],
13316 ignore_indent,
13317 )
13318 })
13319 .max_by_key(|range| range.end.column - range.start.column)
13320 .expect("prefixes is non-empty");
13321
13322 if prefix_range.is_empty() {
13323 all_selection_lines_are_comments = false;
13324 }
13325
13326 selection_edit_ranges.push(prefix_range);
13327 }
13328
13329 if all_selection_lines_are_comments {
13330 edits.extend(
13331 selection_edit_ranges
13332 .iter()
13333 .cloned()
13334 .map(|range| (range, empty_str.clone())),
13335 );
13336 } else {
13337 let min_column = selection_edit_ranges
13338 .iter()
13339 .map(|range| range.start.column)
13340 .min()
13341 .unwrap_or(0);
13342 edits.extend(selection_edit_ranges.iter().map(|range| {
13343 let position = Point::new(range.start.row, min_column);
13344 (position..position, first_prefix.clone())
13345 }));
13346 }
13347 } else if let Some((full_comment_prefix, comment_suffix)) =
13348 language.block_comment_delimiters()
13349 {
13350 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13351 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13352 let prefix_range = comment_prefix_range(
13353 snapshot.deref(),
13354 start_row,
13355 comment_prefix,
13356 comment_prefix_whitespace,
13357 ignore_indent,
13358 );
13359 let suffix_range = comment_suffix_range(
13360 snapshot.deref(),
13361 end_row,
13362 comment_suffix.trim_start_matches(' '),
13363 comment_suffix.starts_with(' '),
13364 );
13365
13366 if prefix_range.is_empty() || suffix_range.is_empty() {
13367 edits.push((
13368 prefix_range.start..prefix_range.start,
13369 full_comment_prefix.clone(),
13370 ));
13371 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13372 suffixes_inserted.push((end_row, comment_suffix.len()));
13373 } else {
13374 edits.push((prefix_range, empty_str.clone()));
13375 edits.push((suffix_range, empty_str.clone()));
13376 }
13377 } else {
13378 continue;
13379 }
13380 }
13381
13382 drop(snapshot);
13383 this.buffer.update(cx, |buffer, cx| {
13384 buffer.edit(edits, None, cx);
13385 });
13386
13387 // Adjust selections so that they end before any comment suffixes that
13388 // were inserted.
13389 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13390 let mut selections = this.selections.all::<Point>(cx);
13391 let snapshot = this.buffer.read(cx).read(cx);
13392 for selection in &mut selections {
13393 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13394 match row.cmp(&MultiBufferRow(selection.end.row)) {
13395 Ordering::Less => {
13396 suffixes_inserted.next();
13397 continue;
13398 }
13399 Ordering::Greater => break,
13400 Ordering::Equal => {
13401 if selection.end.column == snapshot.line_len(row) {
13402 if selection.is_empty() {
13403 selection.start.column -= suffix_len as u32;
13404 }
13405 selection.end.column -= suffix_len as u32;
13406 }
13407 break;
13408 }
13409 }
13410 }
13411 }
13412
13413 drop(snapshot);
13414 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13415 s.select(selections)
13416 });
13417
13418 let selections = this.selections.all::<Point>(cx);
13419 let selections_on_single_row = selections.windows(2).all(|selections| {
13420 selections[0].start.row == selections[1].start.row
13421 && selections[0].end.row == selections[1].end.row
13422 && selections[0].start.row == selections[0].end.row
13423 });
13424 let selections_selecting = selections
13425 .iter()
13426 .any(|selection| selection.start != selection.end);
13427 let advance_downwards = action.advance_downwards
13428 && selections_on_single_row
13429 && !selections_selecting
13430 && !matches!(this.mode, EditorMode::SingleLine { .. });
13431
13432 if advance_downwards {
13433 let snapshot = this.buffer.read(cx).snapshot(cx);
13434
13435 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13436 s.move_cursors_with(|display_snapshot, display_point, _| {
13437 let mut point = display_point.to_point(display_snapshot);
13438 point.row += 1;
13439 point = snapshot.clip_point(point, Bias::Left);
13440 let display_point = point.to_display_point(display_snapshot);
13441 let goal = SelectionGoal::HorizontalPosition(
13442 display_snapshot
13443 .x_for_display_point(display_point, text_layout_details)
13444 .into(),
13445 );
13446 (display_point, goal)
13447 })
13448 });
13449 }
13450 });
13451 }
13452
13453 pub fn select_enclosing_symbol(
13454 &mut self,
13455 _: &SelectEnclosingSymbol,
13456 window: &mut Window,
13457 cx: &mut Context<Self>,
13458 ) {
13459 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13460
13461 let buffer = self.buffer.read(cx).snapshot(cx);
13462 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13463
13464 fn update_selection(
13465 selection: &Selection<usize>,
13466 buffer_snap: &MultiBufferSnapshot,
13467 ) -> Option<Selection<usize>> {
13468 let cursor = selection.head();
13469 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13470 for symbol in symbols.iter().rev() {
13471 let start = symbol.range.start.to_offset(buffer_snap);
13472 let end = symbol.range.end.to_offset(buffer_snap);
13473 let new_range = start..end;
13474 if start < selection.start || end > selection.end {
13475 return Some(Selection {
13476 id: selection.id,
13477 start: new_range.start,
13478 end: new_range.end,
13479 goal: SelectionGoal::None,
13480 reversed: selection.reversed,
13481 });
13482 }
13483 }
13484 None
13485 }
13486
13487 let mut selected_larger_symbol = false;
13488 let new_selections = old_selections
13489 .iter()
13490 .map(|selection| match update_selection(selection, &buffer) {
13491 Some(new_selection) => {
13492 if new_selection.range() != selection.range() {
13493 selected_larger_symbol = true;
13494 }
13495 new_selection
13496 }
13497 None => selection.clone(),
13498 })
13499 .collect::<Vec<_>>();
13500
13501 if selected_larger_symbol {
13502 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13503 s.select(new_selections);
13504 });
13505 }
13506 }
13507
13508 pub fn select_larger_syntax_node(
13509 &mut self,
13510 _: &SelectLargerSyntaxNode,
13511 window: &mut Window,
13512 cx: &mut Context<Self>,
13513 ) {
13514 let Some(visible_row_count) = self.visible_row_count() else {
13515 return;
13516 };
13517 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13518 if old_selections.is_empty() {
13519 return;
13520 }
13521
13522 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13523
13524 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13525 let buffer = self.buffer.read(cx).snapshot(cx);
13526
13527 let mut selected_larger_node = false;
13528 let mut new_selections = old_selections
13529 .iter()
13530 .map(|selection| {
13531 let old_range = selection.start..selection.end;
13532
13533 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13534 // manually select word at selection
13535 if ["string_content", "inline"].contains(&node.kind()) {
13536 let word_range = {
13537 let display_point = buffer
13538 .offset_to_point(old_range.start)
13539 .to_display_point(&display_map);
13540 let Range { start, end } =
13541 movement::surrounding_word(&display_map, display_point);
13542 start.to_point(&display_map).to_offset(&buffer)
13543 ..end.to_point(&display_map).to_offset(&buffer)
13544 };
13545 // ignore if word is already selected
13546 if !word_range.is_empty() && old_range != word_range {
13547 let last_word_range = {
13548 let display_point = buffer
13549 .offset_to_point(old_range.end)
13550 .to_display_point(&display_map);
13551 let Range { start, end } =
13552 movement::surrounding_word(&display_map, display_point);
13553 start.to_point(&display_map).to_offset(&buffer)
13554 ..end.to_point(&display_map).to_offset(&buffer)
13555 };
13556 // only select word if start and end point belongs to same word
13557 if word_range == last_word_range {
13558 selected_larger_node = true;
13559 return Selection {
13560 id: selection.id,
13561 start: word_range.start,
13562 end: word_range.end,
13563 goal: SelectionGoal::None,
13564 reversed: selection.reversed,
13565 };
13566 }
13567 }
13568 }
13569 }
13570
13571 let mut new_range = old_range.clone();
13572 while let Some((_node, containing_range)) =
13573 buffer.syntax_ancestor(new_range.clone())
13574 {
13575 new_range = match containing_range {
13576 MultiOrSingleBufferOffsetRange::Single(_) => break,
13577 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13578 };
13579 if !display_map.intersects_fold(new_range.start)
13580 && !display_map.intersects_fold(new_range.end)
13581 {
13582 break;
13583 }
13584 }
13585
13586 selected_larger_node |= new_range != old_range;
13587 Selection {
13588 id: selection.id,
13589 start: new_range.start,
13590 end: new_range.end,
13591 goal: SelectionGoal::None,
13592 reversed: selection.reversed,
13593 }
13594 })
13595 .collect::<Vec<_>>();
13596
13597 if !selected_larger_node {
13598 return; // don't put this call in the history
13599 }
13600
13601 // scroll based on transformation done to the last selection created by the user
13602 let (last_old, last_new) = old_selections
13603 .last()
13604 .zip(new_selections.last().cloned())
13605 .expect("old_selections isn't empty");
13606
13607 // revert selection
13608 let is_selection_reversed = {
13609 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13610 new_selections.last_mut().expect("checked above").reversed =
13611 should_newest_selection_be_reversed;
13612 should_newest_selection_be_reversed
13613 };
13614
13615 if selected_larger_node {
13616 self.select_syntax_node_history.disable_clearing = true;
13617 self.change_selections(None, window, cx, |s| {
13618 s.select(new_selections.clone());
13619 });
13620 self.select_syntax_node_history.disable_clearing = false;
13621 }
13622
13623 let start_row = last_new.start.to_display_point(&display_map).row().0;
13624 let end_row = last_new.end.to_display_point(&display_map).row().0;
13625 let selection_height = end_row - start_row + 1;
13626 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13627
13628 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13629 let scroll_behavior = if fits_on_the_screen {
13630 self.request_autoscroll(Autoscroll::fit(), cx);
13631 SelectSyntaxNodeScrollBehavior::FitSelection
13632 } else if is_selection_reversed {
13633 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13634 SelectSyntaxNodeScrollBehavior::CursorTop
13635 } else {
13636 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13637 SelectSyntaxNodeScrollBehavior::CursorBottom
13638 };
13639
13640 self.select_syntax_node_history.push((
13641 old_selections,
13642 scroll_behavior,
13643 is_selection_reversed,
13644 ));
13645 }
13646
13647 pub fn select_smaller_syntax_node(
13648 &mut self,
13649 _: &SelectSmallerSyntaxNode,
13650 window: &mut Window,
13651 cx: &mut Context<Self>,
13652 ) {
13653 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13654
13655 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13656 self.select_syntax_node_history.pop()
13657 {
13658 if let Some(selection) = selections.last_mut() {
13659 selection.reversed = is_selection_reversed;
13660 }
13661
13662 self.select_syntax_node_history.disable_clearing = true;
13663 self.change_selections(None, window, cx, |s| {
13664 s.select(selections.to_vec());
13665 });
13666 self.select_syntax_node_history.disable_clearing = false;
13667
13668 match scroll_behavior {
13669 SelectSyntaxNodeScrollBehavior::CursorTop => {
13670 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13671 }
13672 SelectSyntaxNodeScrollBehavior::FitSelection => {
13673 self.request_autoscroll(Autoscroll::fit(), cx);
13674 }
13675 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13676 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13677 }
13678 }
13679 }
13680 }
13681
13682 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13683 if !EditorSettings::get_global(cx).gutter.runnables {
13684 self.clear_tasks();
13685 return Task::ready(());
13686 }
13687 let project = self.project.as_ref().map(Entity::downgrade);
13688 let task_sources = self.lsp_task_sources(cx);
13689 let multi_buffer = self.buffer.downgrade();
13690 cx.spawn_in(window, async move |editor, cx| {
13691 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13692 let Some(project) = project.and_then(|p| p.upgrade()) else {
13693 return;
13694 };
13695 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13696 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13697 }) else {
13698 return;
13699 };
13700
13701 let hide_runnables = project
13702 .update(cx, |project, cx| {
13703 // Do not display any test indicators in non-dev server remote projects.
13704 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13705 })
13706 .unwrap_or(true);
13707 if hide_runnables {
13708 return;
13709 }
13710 let new_rows =
13711 cx.background_spawn({
13712 let snapshot = display_snapshot.clone();
13713 async move {
13714 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13715 }
13716 })
13717 .await;
13718 let Ok(lsp_tasks) =
13719 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13720 else {
13721 return;
13722 };
13723 let lsp_tasks = lsp_tasks.await;
13724
13725 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13726 lsp_tasks
13727 .into_iter()
13728 .flat_map(|(kind, tasks)| {
13729 tasks.into_iter().filter_map(move |(location, task)| {
13730 Some((kind.clone(), location?, task))
13731 })
13732 })
13733 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13734 let buffer = location.target.buffer;
13735 let buffer_snapshot = buffer.read(cx).snapshot();
13736 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13737 |(excerpt_id, snapshot, _)| {
13738 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13739 display_snapshot
13740 .buffer_snapshot
13741 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13742 } else {
13743 None
13744 }
13745 },
13746 );
13747 if let Some(offset) = offset {
13748 let task_buffer_range =
13749 location.target.range.to_point(&buffer_snapshot);
13750 let context_buffer_range =
13751 task_buffer_range.to_offset(&buffer_snapshot);
13752 let context_range = BufferOffset(context_buffer_range.start)
13753 ..BufferOffset(context_buffer_range.end);
13754
13755 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13756 .or_insert_with(|| RunnableTasks {
13757 templates: Vec::new(),
13758 offset,
13759 column: task_buffer_range.start.column,
13760 extra_variables: HashMap::default(),
13761 context_range,
13762 })
13763 .templates
13764 .push((kind, task.original_task().clone()));
13765 }
13766
13767 acc
13768 })
13769 }) else {
13770 return;
13771 };
13772
13773 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
13774 buffer.language_settings(cx).tasks.prefer_lsp
13775 }) else {
13776 return;
13777 };
13778
13779 let rows = Self::runnable_rows(
13780 project,
13781 display_snapshot,
13782 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
13783 new_rows,
13784 cx.clone(),
13785 );
13786 editor
13787 .update(cx, |editor, _| {
13788 editor.clear_tasks();
13789 for (key, mut value) in rows {
13790 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13791 value.templates.extend(lsp_tasks.templates);
13792 }
13793
13794 editor.insert_tasks(key, value);
13795 }
13796 for (key, value) in lsp_tasks_by_rows {
13797 editor.insert_tasks(key, value);
13798 }
13799 })
13800 .ok();
13801 })
13802 }
13803 fn fetch_runnable_ranges(
13804 snapshot: &DisplaySnapshot,
13805 range: Range<Anchor>,
13806 ) -> Vec<language::RunnableRange> {
13807 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13808 }
13809
13810 fn runnable_rows(
13811 project: Entity<Project>,
13812 snapshot: DisplaySnapshot,
13813 prefer_lsp: bool,
13814 runnable_ranges: Vec<RunnableRange>,
13815 mut cx: AsyncWindowContext,
13816 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13817 runnable_ranges
13818 .into_iter()
13819 .filter_map(|mut runnable| {
13820 let mut tasks = cx
13821 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13822 .ok()?;
13823 if prefer_lsp {
13824 tasks.retain(|(task_kind, _)| {
13825 !matches!(task_kind, TaskSourceKind::Language { .. })
13826 });
13827 }
13828 if tasks.is_empty() {
13829 return None;
13830 }
13831
13832 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13833
13834 let row = snapshot
13835 .buffer_snapshot
13836 .buffer_line_for_row(MultiBufferRow(point.row))?
13837 .1
13838 .start
13839 .row;
13840
13841 let context_range =
13842 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13843 Some((
13844 (runnable.buffer_id, row),
13845 RunnableTasks {
13846 templates: tasks,
13847 offset: snapshot
13848 .buffer_snapshot
13849 .anchor_before(runnable.run_range.start),
13850 context_range,
13851 column: point.column,
13852 extra_variables: runnable.extra_captures,
13853 },
13854 ))
13855 })
13856 .collect()
13857 }
13858
13859 fn templates_with_tags(
13860 project: &Entity<Project>,
13861 runnable: &mut Runnable,
13862 cx: &mut App,
13863 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13864 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13865 let (worktree_id, file) = project
13866 .buffer_for_id(runnable.buffer, cx)
13867 .and_then(|buffer| buffer.read(cx).file())
13868 .map(|file| (file.worktree_id(cx), file.clone()))
13869 .unzip();
13870
13871 (
13872 project.task_store().read(cx).task_inventory().cloned(),
13873 worktree_id,
13874 file,
13875 )
13876 });
13877
13878 let mut templates_with_tags = mem::take(&mut runnable.tags)
13879 .into_iter()
13880 .flat_map(|RunnableTag(tag)| {
13881 inventory
13882 .as_ref()
13883 .into_iter()
13884 .flat_map(|inventory| {
13885 inventory.read(cx).list_tasks(
13886 file.clone(),
13887 Some(runnable.language.clone()),
13888 worktree_id,
13889 cx,
13890 )
13891 })
13892 .filter(move |(_, template)| {
13893 template.tags.iter().any(|source_tag| source_tag == &tag)
13894 })
13895 })
13896 .sorted_by_key(|(kind, _)| kind.to_owned())
13897 .collect::<Vec<_>>();
13898 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13899 // Strongest source wins; if we have worktree tag binding, prefer that to
13900 // global and language bindings;
13901 // if we have a global binding, prefer that to language binding.
13902 let first_mismatch = templates_with_tags
13903 .iter()
13904 .position(|(tag_source, _)| tag_source != leading_tag_source);
13905 if let Some(index) = first_mismatch {
13906 templates_with_tags.truncate(index);
13907 }
13908 }
13909
13910 templates_with_tags
13911 }
13912
13913 pub fn move_to_enclosing_bracket(
13914 &mut self,
13915 _: &MoveToEnclosingBracket,
13916 window: &mut Window,
13917 cx: &mut Context<Self>,
13918 ) {
13919 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13920 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13921 s.move_offsets_with(|snapshot, selection| {
13922 let Some(enclosing_bracket_ranges) =
13923 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13924 else {
13925 return;
13926 };
13927
13928 let mut best_length = usize::MAX;
13929 let mut best_inside = false;
13930 let mut best_in_bracket_range = false;
13931 let mut best_destination = None;
13932 for (open, close) in enclosing_bracket_ranges {
13933 let close = close.to_inclusive();
13934 let length = close.end() - open.start;
13935 let inside = selection.start >= open.end && selection.end <= *close.start();
13936 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13937 || close.contains(&selection.head());
13938
13939 // If best is next to a bracket and current isn't, skip
13940 if !in_bracket_range && best_in_bracket_range {
13941 continue;
13942 }
13943
13944 // Prefer smaller lengths unless best is inside and current isn't
13945 if length > best_length && (best_inside || !inside) {
13946 continue;
13947 }
13948
13949 best_length = length;
13950 best_inside = inside;
13951 best_in_bracket_range = in_bracket_range;
13952 best_destination = Some(
13953 if close.contains(&selection.start) && close.contains(&selection.end) {
13954 if inside { open.end } else { open.start }
13955 } else if inside {
13956 *close.start()
13957 } else {
13958 *close.end()
13959 },
13960 );
13961 }
13962
13963 if let Some(destination) = best_destination {
13964 selection.collapse_to(destination, SelectionGoal::None);
13965 }
13966 })
13967 });
13968 }
13969
13970 pub fn undo_selection(
13971 &mut self,
13972 _: &UndoSelection,
13973 window: &mut Window,
13974 cx: &mut Context<Self>,
13975 ) {
13976 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13977 self.end_selection(window, cx);
13978 self.selection_history.mode = SelectionHistoryMode::Undoing;
13979 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13980 self.change_selections(None, window, cx, |s| {
13981 s.select_anchors(entry.selections.to_vec())
13982 });
13983 self.select_next_state = entry.select_next_state;
13984 self.select_prev_state = entry.select_prev_state;
13985 self.add_selections_state = entry.add_selections_state;
13986 self.request_autoscroll(Autoscroll::newest(), cx);
13987 }
13988 self.selection_history.mode = SelectionHistoryMode::Normal;
13989 }
13990
13991 pub fn redo_selection(
13992 &mut self,
13993 _: &RedoSelection,
13994 window: &mut Window,
13995 cx: &mut Context<Self>,
13996 ) {
13997 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13998 self.end_selection(window, cx);
13999 self.selection_history.mode = SelectionHistoryMode::Redoing;
14000 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14001 self.change_selections(None, window, cx, |s| {
14002 s.select_anchors(entry.selections.to_vec())
14003 });
14004 self.select_next_state = entry.select_next_state;
14005 self.select_prev_state = entry.select_prev_state;
14006 self.add_selections_state = entry.add_selections_state;
14007 self.request_autoscroll(Autoscroll::newest(), cx);
14008 }
14009 self.selection_history.mode = SelectionHistoryMode::Normal;
14010 }
14011
14012 pub fn expand_excerpts(
14013 &mut self,
14014 action: &ExpandExcerpts,
14015 _: &mut Window,
14016 cx: &mut Context<Self>,
14017 ) {
14018 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14019 }
14020
14021 pub fn expand_excerpts_down(
14022 &mut self,
14023 action: &ExpandExcerptsDown,
14024 _: &mut Window,
14025 cx: &mut Context<Self>,
14026 ) {
14027 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14028 }
14029
14030 pub fn expand_excerpts_up(
14031 &mut self,
14032 action: &ExpandExcerptsUp,
14033 _: &mut Window,
14034 cx: &mut Context<Self>,
14035 ) {
14036 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14037 }
14038
14039 pub fn expand_excerpts_for_direction(
14040 &mut self,
14041 lines: u32,
14042 direction: ExpandExcerptDirection,
14043
14044 cx: &mut Context<Self>,
14045 ) {
14046 let selections = self.selections.disjoint_anchors();
14047
14048 let lines = if lines == 0 {
14049 EditorSettings::get_global(cx).expand_excerpt_lines
14050 } else {
14051 lines
14052 };
14053
14054 self.buffer.update(cx, |buffer, cx| {
14055 let snapshot = buffer.snapshot(cx);
14056 let mut excerpt_ids = selections
14057 .iter()
14058 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14059 .collect::<Vec<_>>();
14060 excerpt_ids.sort();
14061 excerpt_ids.dedup();
14062 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14063 })
14064 }
14065
14066 pub fn expand_excerpt(
14067 &mut self,
14068 excerpt: ExcerptId,
14069 direction: ExpandExcerptDirection,
14070 window: &mut Window,
14071 cx: &mut Context<Self>,
14072 ) {
14073 let current_scroll_position = self.scroll_position(cx);
14074 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14075 let mut should_scroll_up = false;
14076
14077 if direction == ExpandExcerptDirection::Down {
14078 let multi_buffer = self.buffer.read(cx);
14079 let snapshot = multi_buffer.snapshot(cx);
14080 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14081 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14082 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14083 let buffer_snapshot = buffer.read(cx).snapshot();
14084 let excerpt_end_row =
14085 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14086 let last_row = buffer_snapshot.max_point().row;
14087 let lines_below = last_row.saturating_sub(excerpt_end_row);
14088 should_scroll_up = lines_below >= lines_to_expand;
14089 }
14090 }
14091 }
14092 }
14093
14094 self.buffer.update(cx, |buffer, cx| {
14095 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14096 });
14097
14098 if should_scroll_up {
14099 let new_scroll_position =
14100 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14101 self.set_scroll_position(new_scroll_position, window, cx);
14102 }
14103 }
14104
14105 pub fn go_to_singleton_buffer_point(
14106 &mut self,
14107 point: Point,
14108 window: &mut Window,
14109 cx: &mut Context<Self>,
14110 ) {
14111 self.go_to_singleton_buffer_range(point..point, window, cx);
14112 }
14113
14114 pub fn go_to_singleton_buffer_range(
14115 &mut self,
14116 range: Range<Point>,
14117 window: &mut Window,
14118 cx: &mut Context<Self>,
14119 ) {
14120 let multibuffer = self.buffer().read(cx);
14121 let Some(buffer) = multibuffer.as_singleton() else {
14122 return;
14123 };
14124 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14125 return;
14126 };
14127 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14128 return;
14129 };
14130 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14131 s.select_anchor_ranges([start..end])
14132 });
14133 }
14134
14135 pub fn go_to_diagnostic(
14136 &mut self,
14137 _: &GoToDiagnostic,
14138 window: &mut Window,
14139 cx: &mut Context<Self>,
14140 ) {
14141 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14142 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14143 }
14144
14145 pub fn go_to_prev_diagnostic(
14146 &mut self,
14147 _: &GoToPreviousDiagnostic,
14148 window: &mut Window,
14149 cx: &mut Context<Self>,
14150 ) {
14151 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14152 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14153 }
14154
14155 pub fn go_to_diagnostic_impl(
14156 &mut self,
14157 direction: Direction,
14158 window: &mut Window,
14159 cx: &mut Context<Self>,
14160 ) {
14161 let buffer = self.buffer.read(cx).snapshot(cx);
14162 let selection = self.selections.newest::<usize>(cx);
14163
14164 let mut active_group_id = None;
14165 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14166 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14167 active_group_id = Some(active_group.group_id);
14168 }
14169 }
14170
14171 fn filtered(
14172 snapshot: EditorSnapshot,
14173 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14174 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14175 diagnostics
14176 .filter(|entry| entry.range.start != entry.range.end)
14177 .filter(|entry| !entry.diagnostic.is_unnecessary)
14178 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14179 }
14180
14181 let snapshot = self.snapshot(window, cx);
14182 let before = filtered(
14183 snapshot.clone(),
14184 buffer
14185 .diagnostics_in_range(0..selection.start)
14186 .filter(|entry| entry.range.start <= selection.start),
14187 );
14188 let after = filtered(
14189 snapshot,
14190 buffer
14191 .diagnostics_in_range(selection.start..buffer.len())
14192 .filter(|entry| entry.range.start >= selection.start),
14193 );
14194
14195 let mut found: Option<DiagnosticEntry<usize>> = None;
14196 if direction == Direction::Prev {
14197 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14198 {
14199 for diagnostic in prev_diagnostics.into_iter().rev() {
14200 if diagnostic.range.start != selection.start
14201 || active_group_id
14202 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14203 {
14204 found = Some(diagnostic);
14205 break 'outer;
14206 }
14207 }
14208 }
14209 } else {
14210 for diagnostic in after.chain(before) {
14211 if diagnostic.range.start != selection.start
14212 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14213 {
14214 found = Some(diagnostic);
14215 break;
14216 }
14217 }
14218 }
14219 let Some(next_diagnostic) = found else {
14220 return;
14221 };
14222
14223 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14224 return;
14225 };
14226 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14227 s.select_ranges(vec![
14228 next_diagnostic.range.start..next_diagnostic.range.start,
14229 ])
14230 });
14231 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14232 self.refresh_inline_completion(false, true, window, cx);
14233 }
14234
14235 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14236 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14237 let snapshot = self.snapshot(window, cx);
14238 let selection = self.selections.newest::<Point>(cx);
14239 self.go_to_hunk_before_or_after_position(
14240 &snapshot,
14241 selection.head(),
14242 Direction::Next,
14243 window,
14244 cx,
14245 );
14246 }
14247
14248 pub fn go_to_hunk_before_or_after_position(
14249 &mut self,
14250 snapshot: &EditorSnapshot,
14251 position: Point,
14252 direction: Direction,
14253 window: &mut Window,
14254 cx: &mut Context<Editor>,
14255 ) {
14256 let row = if direction == Direction::Next {
14257 self.hunk_after_position(snapshot, position)
14258 .map(|hunk| hunk.row_range.start)
14259 } else {
14260 self.hunk_before_position(snapshot, position)
14261 };
14262
14263 if let Some(row) = row {
14264 let destination = Point::new(row.0, 0);
14265 let autoscroll = Autoscroll::center();
14266
14267 self.unfold_ranges(&[destination..destination], false, false, cx);
14268 self.change_selections(Some(autoscroll), window, cx, |s| {
14269 s.select_ranges([destination..destination]);
14270 });
14271 }
14272 }
14273
14274 fn hunk_after_position(
14275 &mut self,
14276 snapshot: &EditorSnapshot,
14277 position: Point,
14278 ) -> Option<MultiBufferDiffHunk> {
14279 snapshot
14280 .buffer_snapshot
14281 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14282 .find(|hunk| hunk.row_range.start.0 > position.row)
14283 .or_else(|| {
14284 snapshot
14285 .buffer_snapshot
14286 .diff_hunks_in_range(Point::zero()..position)
14287 .find(|hunk| hunk.row_range.end.0 < position.row)
14288 })
14289 }
14290
14291 fn go_to_prev_hunk(
14292 &mut self,
14293 _: &GoToPreviousHunk,
14294 window: &mut Window,
14295 cx: &mut Context<Self>,
14296 ) {
14297 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14298 let snapshot = self.snapshot(window, cx);
14299 let selection = self.selections.newest::<Point>(cx);
14300 self.go_to_hunk_before_or_after_position(
14301 &snapshot,
14302 selection.head(),
14303 Direction::Prev,
14304 window,
14305 cx,
14306 );
14307 }
14308
14309 fn hunk_before_position(
14310 &mut self,
14311 snapshot: &EditorSnapshot,
14312 position: Point,
14313 ) -> Option<MultiBufferRow> {
14314 snapshot
14315 .buffer_snapshot
14316 .diff_hunk_before(position)
14317 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14318 }
14319
14320 fn go_to_next_change(
14321 &mut self,
14322 _: &GoToNextChange,
14323 window: &mut Window,
14324 cx: &mut Context<Self>,
14325 ) {
14326 if let Some(selections) = self
14327 .change_list
14328 .next_change(1, Direction::Next)
14329 .map(|s| s.to_vec())
14330 {
14331 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14332 let map = s.display_map();
14333 s.select_display_ranges(selections.iter().map(|a| {
14334 let point = a.to_display_point(&map);
14335 point..point
14336 }))
14337 })
14338 }
14339 }
14340
14341 fn go_to_previous_change(
14342 &mut self,
14343 _: &GoToPreviousChange,
14344 window: &mut Window,
14345 cx: &mut Context<Self>,
14346 ) {
14347 if let Some(selections) = self
14348 .change_list
14349 .next_change(1, Direction::Prev)
14350 .map(|s| s.to_vec())
14351 {
14352 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14353 let map = s.display_map();
14354 s.select_display_ranges(selections.iter().map(|a| {
14355 let point = a.to_display_point(&map);
14356 point..point
14357 }))
14358 })
14359 }
14360 }
14361
14362 fn go_to_line<T: 'static>(
14363 &mut self,
14364 position: Anchor,
14365 highlight_color: Option<Hsla>,
14366 window: &mut Window,
14367 cx: &mut Context<Self>,
14368 ) {
14369 let snapshot = self.snapshot(window, cx).display_snapshot;
14370 let position = position.to_point(&snapshot.buffer_snapshot);
14371 let start = snapshot
14372 .buffer_snapshot
14373 .clip_point(Point::new(position.row, 0), Bias::Left);
14374 let end = start + Point::new(1, 0);
14375 let start = snapshot.buffer_snapshot.anchor_before(start);
14376 let end = snapshot.buffer_snapshot.anchor_before(end);
14377
14378 self.highlight_rows::<T>(
14379 start..end,
14380 highlight_color
14381 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14382 Default::default(),
14383 cx,
14384 );
14385
14386 if self.buffer.read(cx).is_singleton() {
14387 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14388 }
14389 }
14390
14391 pub fn go_to_definition(
14392 &mut self,
14393 _: &GoToDefinition,
14394 window: &mut Window,
14395 cx: &mut Context<Self>,
14396 ) -> Task<Result<Navigated>> {
14397 let definition =
14398 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14399 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14400 cx.spawn_in(window, async move |editor, cx| {
14401 if definition.await? == Navigated::Yes {
14402 return Ok(Navigated::Yes);
14403 }
14404 match fallback_strategy {
14405 GoToDefinitionFallback::None => Ok(Navigated::No),
14406 GoToDefinitionFallback::FindAllReferences => {
14407 match editor.update_in(cx, |editor, window, cx| {
14408 editor.find_all_references(&FindAllReferences, window, cx)
14409 })? {
14410 Some(references) => references.await,
14411 None => Ok(Navigated::No),
14412 }
14413 }
14414 }
14415 })
14416 }
14417
14418 pub fn go_to_declaration(
14419 &mut self,
14420 _: &GoToDeclaration,
14421 window: &mut Window,
14422 cx: &mut Context<Self>,
14423 ) -> Task<Result<Navigated>> {
14424 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14425 }
14426
14427 pub fn go_to_declaration_split(
14428 &mut self,
14429 _: &GoToDeclaration,
14430 window: &mut Window,
14431 cx: &mut Context<Self>,
14432 ) -> Task<Result<Navigated>> {
14433 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14434 }
14435
14436 pub fn go_to_implementation(
14437 &mut self,
14438 _: &GoToImplementation,
14439 window: &mut Window,
14440 cx: &mut Context<Self>,
14441 ) -> Task<Result<Navigated>> {
14442 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14443 }
14444
14445 pub fn go_to_implementation_split(
14446 &mut self,
14447 _: &GoToImplementationSplit,
14448 window: &mut Window,
14449 cx: &mut Context<Self>,
14450 ) -> Task<Result<Navigated>> {
14451 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14452 }
14453
14454 pub fn go_to_type_definition(
14455 &mut self,
14456 _: &GoToTypeDefinition,
14457 window: &mut Window,
14458 cx: &mut Context<Self>,
14459 ) -> Task<Result<Navigated>> {
14460 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14461 }
14462
14463 pub fn go_to_definition_split(
14464 &mut self,
14465 _: &GoToDefinitionSplit,
14466 window: &mut Window,
14467 cx: &mut Context<Self>,
14468 ) -> Task<Result<Navigated>> {
14469 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14470 }
14471
14472 pub fn go_to_type_definition_split(
14473 &mut self,
14474 _: &GoToTypeDefinitionSplit,
14475 window: &mut Window,
14476 cx: &mut Context<Self>,
14477 ) -> Task<Result<Navigated>> {
14478 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14479 }
14480
14481 fn go_to_definition_of_kind(
14482 &mut self,
14483 kind: GotoDefinitionKind,
14484 split: bool,
14485 window: &mut Window,
14486 cx: &mut Context<Self>,
14487 ) -> Task<Result<Navigated>> {
14488 let Some(provider) = self.semantics_provider.clone() else {
14489 return Task::ready(Ok(Navigated::No));
14490 };
14491 let head = self.selections.newest::<usize>(cx).head();
14492 let buffer = self.buffer.read(cx);
14493 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14494 text_anchor
14495 } else {
14496 return Task::ready(Ok(Navigated::No));
14497 };
14498
14499 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14500 return Task::ready(Ok(Navigated::No));
14501 };
14502
14503 cx.spawn_in(window, async move |editor, cx| {
14504 let definitions = definitions.await?;
14505 let navigated = editor
14506 .update_in(cx, |editor, window, cx| {
14507 editor.navigate_to_hover_links(
14508 Some(kind),
14509 definitions
14510 .into_iter()
14511 .filter(|location| {
14512 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14513 })
14514 .map(HoverLink::Text)
14515 .collect::<Vec<_>>(),
14516 split,
14517 window,
14518 cx,
14519 )
14520 })?
14521 .await?;
14522 anyhow::Ok(navigated)
14523 })
14524 }
14525
14526 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14527 let selection = self.selections.newest_anchor();
14528 let head = selection.head();
14529 let tail = selection.tail();
14530
14531 let Some((buffer, start_position)) =
14532 self.buffer.read(cx).text_anchor_for_position(head, cx)
14533 else {
14534 return;
14535 };
14536
14537 let end_position = if head != tail {
14538 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14539 return;
14540 };
14541 Some(pos)
14542 } else {
14543 None
14544 };
14545
14546 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14547 let url = if let Some(end_pos) = end_position {
14548 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14549 } else {
14550 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14551 };
14552
14553 if let Some(url) = url {
14554 editor.update(cx, |_, cx| {
14555 cx.open_url(&url);
14556 })
14557 } else {
14558 Ok(())
14559 }
14560 });
14561
14562 url_finder.detach();
14563 }
14564
14565 pub fn open_selected_filename(
14566 &mut self,
14567 _: &OpenSelectedFilename,
14568 window: &mut Window,
14569 cx: &mut Context<Self>,
14570 ) {
14571 let Some(workspace) = self.workspace() else {
14572 return;
14573 };
14574
14575 let position = self.selections.newest_anchor().head();
14576
14577 let Some((buffer, buffer_position)) =
14578 self.buffer.read(cx).text_anchor_for_position(position, cx)
14579 else {
14580 return;
14581 };
14582
14583 let project = self.project.clone();
14584
14585 cx.spawn_in(window, async move |_, cx| {
14586 let result = find_file(&buffer, project, buffer_position, cx).await;
14587
14588 if let Some((_, path)) = result {
14589 workspace
14590 .update_in(cx, |workspace, window, cx| {
14591 workspace.open_resolved_path(path, window, cx)
14592 })?
14593 .await?;
14594 }
14595 anyhow::Ok(())
14596 })
14597 .detach();
14598 }
14599
14600 pub(crate) fn navigate_to_hover_links(
14601 &mut self,
14602 kind: Option<GotoDefinitionKind>,
14603 mut definitions: Vec<HoverLink>,
14604 split: bool,
14605 window: &mut Window,
14606 cx: &mut Context<Editor>,
14607 ) -> Task<Result<Navigated>> {
14608 // If there is one definition, just open it directly
14609 if definitions.len() == 1 {
14610 let definition = definitions.pop().unwrap();
14611
14612 enum TargetTaskResult {
14613 Location(Option<Location>),
14614 AlreadyNavigated,
14615 }
14616
14617 let target_task = match definition {
14618 HoverLink::Text(link) => {
14619 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14620 }
14621 HoverLink::InlayHint(lsp_location, server_id) => {
14622 let computation =
14623 self.compute_target_location(lsp_location, server_id, window, cx);
14624 cx.background_spawn(async move {
14625 let location = computation.await?;
14626 Ok(TargetTaskResult::Location(location))
14627 })
14628 }
14629 HoverLink::Url(url) => {
14630 cx.open_url(&url);
14631 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14632 }
14633 HoverLink::File(path) => {
14634 if let Some(workspace) = self.workspace() {
14635 cx.spawn_in(window, async move |_, cx| {
14636 workspace
14637 .update_in(cx, |workspace, window, cx| {
14638 workspace.open_resolved_path(path, window, cx)
14639 })?
14640 .await
14641 .map(|_| TargetTaskResult::AlreadyNavigated)
14642 })
14643 } else {
14644 Task::ready(Ok(TargetTaskResult::Location(None)))
14645 }
14646 }
14647 };
14648 cx.spawn_in(window, async move |editor, cx| {
14649 let target = match target_task.await.context("target resolution task")? {
14650 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14651 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14652 TargetTaskResult::Location(Some(target)) => target,
14653 };
14654
14655 editor.update_in(cx, |editor, window, cx| {
14656 let Some(workspace) = editor.workspace() else {
14657 return Navigated::No;
14658 };
14659 let pane = workspace.read(cx).active_pane().clone();
14660
14661 let range = target.range.to_point(target.buffer.read(cx));
14662 let range = editor.range_for_match(&range);
14663 let range = collapse_multiline_range(range);
14664
14665 if !split
14666 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14667 {
14668 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14669 } else {
14670 window.defer(cx, move |window, cx| {
14671 let target_editor: Entity<Self> =
14672 workspace.update(cx, |workspace, cx| {
14673 let pane = if split {
14674 workspace.adjacent_pane(window, cx)
14675 } else {
14676 workspace.active_pane().clone()
14677 };
14678
14679 workspace.open_project_item(
14680 pane,
14681 target.buffer.clone(),
14682 true,
14683 true,
14684 window,
14685 cx,
14686 )
14687 });
14688 target_editor.update(cx, |target_editor, cx| {
14689 // When selecting a definition in a different buffer, disable the nav history
14690 // to avoid creating a history entry at the previous cursor location.
14691 pane.update(cx, |pane, _| pane.disable_history());
14692 target_editor.go_to_singleton_buffer_range(range, window, cx);
14693 pane.update(cx, |pane, _| pane.enable_history());
14694 });
14695 });
14696 }
14697 Navigated::Yes
14698 })
14699 })
14700 } else if !definitions.is_empty() {
14701 cx.spawn_in(window, async move |editor, cx| {
14702 let (title, location_tasks, workspace) = editor
14703 .update_in(cx, |editor, window, cx| {
14704 let tab_kind = match kind {
14705 Some(GotoDefinitionKind::Implementation) => "Implementations",
14706 _ => "Definitions",
14707 };
14708 let title = definitions
14709 .iter()
14710 .find_map(|definition| match definition {
14711 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14712 let buffer = origin.buffer.read(cx);
14713 format!(
14714 "{} for {}",
14715 tab_kind,
14716 buffer
14717 .text_for_range(origin.range.clone())
14718 .collect::<String>()
14719 )
14720 }),
14721 HoverLink::InlayHint(_, _) => None,
14722 HoverLink::Url(_) => None,
14723 HoverLink::File(_) => None,
14724 })
14725 .unwrap_or(tab_kind.to_string());
14726 let location_tasks = definitions
14727 .into_iter()
14728 .map(|definition| match definition {
14729 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14730 HoverLink::InlayHint(lsp_location, server_id) => editor
14731 .compute_target_location(lsp_location, server_id, window, cx),
14732 HoverLink::Url(_) => Task::ready(Ok(None)),
14733 HoverLink::File(_) => Task::ready(Ok(None)),
14734 })
14735 .collect::<Vec<_>>();
14736 (title, location_tasks, editor.workspace().clone())
14737 })
14738 .context("location tasks preparation")?;
14739
14740 let locations = future::join_all(location_tasks)
14741 .await
14742 .into_iter()
14743 .filter_map(|location| location.transpose())
14744 .collect::<Result<_>>()
14745 .context("location tasks")?;
14746
14747 let Some(workspace) = workspace else {
14748 return Ok(Navigated::No);
14749 };
14750 let opened = workspace
14751 .update_in(cx, |workspace, window, cx| {
14752 Self::open_locations_in_multibuffer(
14753 workspace,
14754 locations,
14755 title,
14756 split,
14757 MultibufferSelectionMode::First,
14758 window,
14759 cx,
14760 )
14761 })
14762 .ok();
14763
14764 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14765 })
14766 } else {
14767 Task::ready(Ok(Navigated::No))
14768 }
14769 }
14770
14771 fn compute_target_location(
14772 &self,
14773 lsp_location: lsp::Location,
14774 server_id: LanguageServerId,
14775 window: &mut Window,
14776 cx: &mut Context<Self>,
14777 ) -> Task<anyhow::Result<Option<Location>>> {
14778 let Some(project) = self.project.clone() else {
14779 return Task::ready(Ok(None));
14780 };
14781
14782 cx.spawn_in(window, async move |editor, cx| {
14783 let location_task = editor.update(cx, |_, cx| {
14784 project.update(cx, |project, cx| {
14785 let language_server_name = project
14786 .language_server_statuses(cx)
14787 .find(|(id, _)| server_id == *id)
14788 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14789 language_server_name.map(|language_server_name| {
14790 project.open_local_buffer_via_lsp(
14791 lsp_location.uri.clone(),
14792 server_id,
14793 language_server_name,
14794 cx,
14795 )
14796 })
14797 })
14798 })?;
14799 let location = match location_task {
14800 Some(task) => Some({
14801 let target_buffer_handle = task.await.context("open local buffer")?;
14802 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
14803 let target_start = target_buffer
14804 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14805 let target_end = target_buffer
14806 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14807 target_buffer.anchor_after(target_start)
14808 ..target_buffer.anchor_before(target_end)
14809 })?;
14810 Location {
14811 buffer: target_buffer_handle,
14812 range,
14813 }
14814 }),
14815 None => None,
14816 };
14817 Ok(location)
14818 })
14819 }
14820
14821 pub fn find_all_references(
14822 &mut self,
14823 _: &FindAllReferences,
14824 window: &mut Window,
14825 cx: &mut Context<Self>,
14826 ) -> Option<Task<Result<Navigated>>> {
14827 let selection = self.selections.newest::<usize>(cx);
14828 let multi_buffer = self.buffer.read(cx);
14829 let head = selection.head();
14830
14831 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14832 let head_anchor = multi_buffer_snapshot.anchor_at(
14833 head,
14834 if head < selection.tail() {
14835 Bias::Right
14836 } else {
14837 Bias::Left
14838 },
14839 );
14840
14841 match self
14842 .find_all_references_task_sources
14843 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14844 {
14845 Ok(_) => {
14846 log::info!(
14847 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14848 );
14849 return None;
14850 }
14851 Err(i) => {
14852 self.find_all_references_task_sources.insert(i, head_anchor);
14853 }
14854 }
14855
14856 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14857 let workspace = self.workspace()?;
14858 let project = workspace.read(cx).project().clone();
14859 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14860 Some(cx.spawn_in(window, async move |editor, cx| {
14861 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14862 if let Ok(i) = editor
14863 .find_all_references_task_sources
14864 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14865 {
14866 editor.find_all_references_task_sources.remove(i);
14867 }
14868 });
14869
14870 let locations = references.await?;
14871 if locations.is_empty() {
14872 return anyhow::Ok(Navigated::No);
14873 }
14874
14875 workspace.update_in(cx, |workspace, window, cx| {
14876 let title = locations
14877 .first()
14878 .as_ref()
14879 .map(|location| {
14880 let buffer = location.buffer.read(cx);
14881 format!(
14882 "References to `{}`",
14883 buffer
14884 .text_for_range(location.range.clone())
14885 .collect::<String>()
14886 )
14887 })
14888 .unwrap();
14889 Self::open_locations_in_multibuffer(
14890 workspace,
14891 locations,
14892 title,
14893 false,
14894 MultibufferSelectionMode::First,
14895 window,
14896 cx,
14897 );
14898 Navigated::Yes
14899 })
14900 }))
14901 }
14902
14903 /// Opens a multibuffer with the given project locations in it
14904 pub fn open_locations_in_multibuffer(
14905 workspace: &mut Workspace,
14906 mut locations: Vec<Location>,
14907 title: String,
14908 split: bool,
14909 multibuffer_selection_mode: MultibufferSelectionMode,
14910 window: &mut Window,
14911 cx: &mut Context<Workspace>,
14912 ) {
14913 // If there are multiple definitions, open them in a multibuffer
14914 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14915 let mut locations = locations.into_iter().peekable();
14916 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14917 let capability = workspace.project().read(cx).capability();
14918
14919 let excerpt_buffer = cx.new(|cx| {
14920 let mut multibuffer = MultiBuffer::new(capability);
14921 while let Some(location) = locations.next() {
14922 let buffer = location.buffer.read(cx);
14923 let mut ranges_for_buffer = Vec::new();
14924 let range = location.range.to_point(buffer);
14925 ranges_for_buffer.push(range.clone());
14926
14927 while let Some(next_location) = locations.peek() {
14928 if next_location.buffer == location.buffer {
14929 ranges_for_buffer.push(next_location.range.to_point(buffer));
14930 locations.next();
14931 } else {
14932 break;
14933 }
14934 }
14935
14936 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14937 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14938 PathKey::for_buffer(&location.buffer, cx),
14939 location.buffer.clone(),
14940 ranges_for_buffer,
14941 DEFAULT_MULTIBUFFER_CONTEXT,
14942 cx,
14943 );
14944 ranges.extend(new_ranges)
14945 }
14946
14947 multibuffer.with_title(title)
14948 });
14949
14950 let editor = cx.new(|cx| {
14951 Editor::for_multibuffer(
14952 excerpt_buffer,
14953 Some(workspace.project().clone()),
14954 window,
14955 cx,
14956 )
14957 });
14958 editor.update(cx, |editor, cx| {
14959 match multibuffer_selection_mode {
14960 MultibufferSelectionMode::First => {
14961 if let Some(first_range) = ranges.first() {
14962 editor.change_selections(None, window, cx, |selections| {
14963 selections.clear_disjoint();
14964 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14965 });
14966 }
14967 editor.highlight_background::<Self>(
14968 &ranges,
14969 |theme| theme.editor_highlighted_line_background,
14970 cx,
14971 );
14972 }
14973 MultibufferSelectionMode::All => {
14974 editor.change_selections(None, window, cx, |selections| {
14975 selections.clear_disjoint();
14976 selections.select_anchor_ranges(ranges);
14977 });
14978 }
14979 }
14980 editor.register_buffers_with_language_servers(cx);
14981 });
14982
14983 let item = Box::new(editor);
14984 let item_id = item.item_id();
14985
14986 if split {
14987 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14988 } else {
14989 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14990 let (preview_item_id, preview_item_idx) =
14991 workspace.active_pane().read_with(cx, |pane, _| {
14992 (pane.preview_item_id(), pane.preview_item_idx())
14993 });
14994
14995 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14996
14997 if let Some(preview_item_id) = preview_item_id {
14998 workspace.active_pane().update(cx, |pane, cx| {
14999 pane.remove_item(preview_item_id, false, false, window, cx);
15000 });
15001 }
15002 } else {
15003 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15004 }
15005 }
15006 workspace.active_pane().update(cx, |pane, cx| {
15007 pane.set_preview_item_id(Some(item_id), cx);
15008 });
15009 }
15010
15011 pub fn rename(
15012 &mut self,
15013 _: &Rename,
15014 window: &mut Window,
15015 cx: &mut Context<Self>,
15016 ) -> Option<Task<Result<()>>> {
15017 use language::ToOffset as _;
15018
15019 let provider = self.semantics_provider.clone()?;
15020 let selection = self.selections.newest_anchor().clone();
15021 let (cursor_buffer, cursor_buffer_position) = self
15022 .buffer
15023 .read(cx)
15024 .text_anchor_for_position(selection.head(), cx)?;
15025 let (tail_buffer, cursor_buffer_position_end) = self
15026 .buffer
15027 .read(cx)
15028 .text_anchor_for_position(selection.tail(), cx)?;
15029 if tail_buffer != cursor_buffer {
15030 return None;
15031 }
15032
15033 let snapshot = cursor_buffer.read(cx).snapshot();
15034 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15035 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15036 let prepare_rename = provider
15037 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15038 .unwrap_or_else(|| Task::ready(Ok(None)));
15039 drop(snapshot);
15040
15041 Some(cx.spawn_in(window, async move |this, cx| {
15042 let rename_range = if let Some(range) = prepare_rename.await? {
15043 Some(range)
15044 } else {
15045 this.update(cx, |this, cx| {
15046 let buffer = this.buffer.read(cx).snapshot(cx);
15047 let mut buffer_highlights = this
15048 .document_highlights_for_position(selection.head(), &buffer)
15049 .filter(|highlight| {
15050 highlight.start.excerpt_id == selection.head().excerpt_id
15051 && highlight.end.excerpt_id == selection.head().excerpt_id
15052 });
15053 buffer_highlights
15054 .next()
15055 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15056 })?
15057 };
15058 if let Some(rename_range) = rename_range {
15059 this.update_in(cx, |this, window, cx| {
15060 let snapshot = cursor_buffer.read(cx).snapshot();
15061 let rename_buffer_range = rename_range.to_offset(&snapshot);
15062 let cursor_offset_in_rename_range =
15063 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15064 let cursor_offset_in_rename_range_end =
15065 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15066
15067 this.take_rename(false, window, cx);
15068 let buffer = this.buffer.read(cx).read(cx);
15069 let cursor_offset = selection.head().to_offset(&buffer);
15070 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15071 let rename_end = rename_start + rename_buffer_range.len();
15072 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15073 let mut old_highlight_id = None;
15074 let old_name: Arc<str> = buffer
15075 .chunks(rename_start..rename_end, true)
15076 .map(|chunk| {
15077 if old_highlight_id.is_none() {
15078 old_highlight_id = chunk.syntax_highlight_id;
15079 }
15080 chunk.text
15081 })
15082 .collect::<String>()
15083 .into();
15084
15085 drop(buffer);
15086
15087 // Position the selection in the rename editor so that it matches the current selection.
15088 this.show_local_selections = false;
15089 let rename_editor = cx.new(|cx| {
15090 let mut editor = Editor::single_line(window, cx);
15091 editor.buffer.update(cx, |buffer, cx| {
15092 buffer.edit([(0..0, old_name.clone())], None, cx)
15093 });
15094 let rename_selection_range = match cursor_offset_in_rename_range
15095 .cmp(&cursor_offset_in_rename_range_end)
15096 {
15097 Ordering::Equal => {
15098 editor.select_all(&SelectAll, window, cx);
15099 return editor;
15100 }
15101 Ordering::Less => {
15102 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15103 }
15104 Ordering::Greater => {
15105 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15106 }
15107 };
15108 if rename_selection_range.end > old_name.len() {
15109 editor.select_all(&SelectAll, window, cx);
15110 } else {
15111 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15112 s.select_ranges([rename_selection_range]);
15113 });
15114 }
15115 editor
15116 });
15117 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15118 if e == &EditorEvent::Focused {
15119 cx.emit(EditorEvent::FocusedIn)
15120 }
15121 })
15122 .detach();
15123
15124 let write_highlights =
15125 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15126 let read_highlights =
15127 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15128 let ranges = write_highlights
15129 .iter()
15130 .flat_map(|(_, ranges)| ranges.iter())
15131 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15132 .cloned()
15133 .collect();
15134
15135 this.highlight_text::<Rename>(
15136 ranges,
15137 HighlightStyle {
15138 fade_out: Some(0.6),
15139 ..Default::default()
15140 },
15141 cx,
15142 );
15143 let rename_focus_handle = rename_editor.focus_handle(cx);
15144 window.focus(&rename_focus_handle);
15145 let block_id = this.insert_blocks(
15146 [BlockProperties {
15147 style: BlockStyle::Flex,
15148 placement: BlockPlacement::Below(range.start),
15149 height: Some(1),
15150 render: Arc::new({
15151 let rename_editor = rename_editor.clone();
15152 move |cx: &mut BlockContext| {
15153 let mut text_style = cx.editor_style.text.clone();
15154 if let Some(highlight_style) = old_highlight_id
15155 .and_then(|h| h.style(&cx.editor_style.syntax))
15156 {
15157 text_style = text_style.highlight(highlight_style);
15158 }
15159 div()
15160 .block_mouse_except_scroll()
15161 .pl(cx.anchor_x)
15162 .child(EditorElement::new(
15163 &rename_editor,
15164 EditorStyle {
15165 background: cx.theme().system().transparent,
15166 local_player: cx.editor_style.local_player,
15167 text: text_style,
15168 scrollbar_width: cx.editor_style.scrollbar_width,
15169 syntax: cx.editor_style.syntax.clone(),
15170 status: cx.editor_style.status.clone(),
15171 inlay_hints_style: HighlightStyle {
15172 font_weight: Some(FontWeight::BOLD),
15173 ..make_inlay_hints_style(cx.app)
15174 },
15175 inline_completion_styles: make_suggestion_styles(
15176 cx.app,
15177 ),
15178 ..EditorStyle::default()
15179 },
15180 ))
15181 .into_any_element()
15182 }
15183 }),
15184 priority: 0,
15185 render_in_minimap: true,
15186 }],
15187 Some(Autoscroll::fit()),
15188 cx,
15189 )[0];
15190 this.pending_rename = Some(RenameState {
15191 range,
15192 old_name,
15193 editor: rename_editor,
15194 block_id,
15195 });
15196 })?;
15197 }
15198
15199 Ok(())
15200 }))
15201 }
15202
15203 pub fn confirm_rename(
15204 &mut self,
15205 _: &ConfirmRename,
15206 window: &mut Window,
15207 cx: &mut Context<Self>,
15208 ) -> Option<Task<Result<()>>> {
15209 let rename = self.take_rename(false, window, cx)?;
15210 let workspace = self.workspace()?.downgrade();
15211 let (buffer, start) = self
15212 .buffer
15213 .read(cx)
15214 .text_anchor_for_position(rename.range.start, cx)?;
15215 let (end_buffer, _) = self
15216 .buffer
15217 .read(cx)
15218 .text_anchor_for_position(rename.range.end, cx)?;
15219 if buffer != end_buffer {
15220 return None;
15221 }
15222
15223 let old_name = rename.old_name;
15224 let new_name = rename.editor.read(cx).text(cx);
15225
15226 let rename = self.semantics_provider.as_ref()?.perform_rename(
15227 &buffer,
15228 start,
15229 new_name.clone(),
15230 cx,
15231 )?;
15232
15233 Some(cx.spawn_in(window, async move |editor, cx| {
15234 let project_transaction = rename.await?;
15235 Self::open_project_transaction(
15236 &editor,
15237 workspace,
15238 project_transaction,
15239 format!("Rename: {} → {}", old_name, new_name),
15240 cx,
15241 )
15242 .await?;
15243
15244 editor.update(cx, |editor, cx| {
15245 editor.refresh_document_highlights(cx);
15246 })?;
15247 Ok(())
15248 }))
15249 }
15250
15251 fn take_rename(
15252 &mut self,
15253 moving_cursor: bool,
15254 window: &mut Window,
15255 cx: &mut Context<Self>,
15256 ) -> Option<RenameState> {
15257 let rename = self.pending_rename.take()?;
15258 if rename.editor.focus_handle(cx).is_focused(window) {
15259 window.focus(&self.focus_handle);
15260 }
15261
15262 self.remove_blocks(
15263 [rename.block_id].into_iter().collect(),
15264 Some(Autoscroll::fit()),
15265 cx,
15266 );
15267 self.clear_highlights::<Rename>(cx);
15268 self.show_local_selections = true;
15269
15270 if moving_cursor {
15271 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15272 editor.selections.newest::<usize>(cx).head()
15273 });
15274
15275 // Update the selection to match the position of the selection inside
15276 // the rename editor.
15277 let snapshot = self.buffer.read(cx).read(cx);
15278 let rename_range = rename.range.to_offset(&snapshot);
15279 let cursor_in_editor = snapshot
15280 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15281 .min(rename_range.end);
15282 drop(snapshot);
15283
15284 self.change_selections(None, window, cx, |s| {
15285 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15286 });
15287 } else {
15288 self.refresh_document_highlights(cx);
15289 }
15290
15291 Some(rename)
15292 }
15293
15294 pub fn pending_rename(&self) -> Option<&RenameState> {
15295 self.pending_rename.as_ref()
15296 }
15297
15298 fn format(
15299 &mut self,
15300 _: &Format,
15301 window: &mut Window,
15302 cx: &mut Context<Self>,
15303 ) -> Option<Task<Result<()>>> {
15304 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15305
15306 let project = match &self.project {
15307 Some(project) => project.clone(),
15308 None => return None,
15309 };
15310
15311 Some(self.perform_format(
15312 project,
15313 FormatTrigger::Manual,
15314 FormatTarget::Buffers,
15315 window,
15316 cx,
15317 ))
15318 }
15319
15320 fn format_selections(
15321 &mut self,
15322 _: &FormatSelections,
15323 window: &mut Window,
15324 cx: &mut Context<Self>,
15325 ) -> Option<Task<Result<()>>> {
15326 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15327
15328 let project = match &self.project {
15329 Some(project) => project.clone(),
15330 None => return None,
15331 };
15332
15333 let ranges = self
15334 .selections
15335 .all_adjusted(cx)
15336 .into_iter()
15337 .map(|selection| selection.range())
15338 .collect_vec();
15339
15340 Some(self.perform_format(
15341 project,
15342 FormatTrigger::Manual,
15343 FormatTarget::Ranges(ranges),
15344 window,
15345 cx,
15346 ))
15347 }
15348
15349 fn perform_format(
15350 &mut self,
15351 project: Entity<Project>,
15352 trigger: FormatTrigger,
15353 target: FormatTarget,
15354 window: &mut Window,
15355 cx: &mut Context<Self>,
15356 ) -> Task<Result<()>> {
15357 let buffer = self.buffer.clone();
15358 let (buffers, target) = match target {
15359 FormatTarget::Buffers => {
15360 let mut buffers = buffer.read(cx).all_buffers();
15361 if trigger == FormatTrigger::Save {
15362 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15363 }
15364 (buffers, LspFormatTarget::Buffers)
15365 }
15366 FormatTarget::Ranges(selection_ranges) => {
15367 let multi_buffer = buffer.read(cx);
15368 let snapshot = multi_buffer.read(cx);
15369 let mut buffers = HashSet::default();
15370 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15371 BTreeMap::new();
15372 for selection_range in selection_ranges {
15373 for (buffer, buffer_range, _) in
15374 snapshot.range_to_buffer_ranges(selection_range)
15375 {
15376 let buffer_id = buffer.remote_id();
15377 let start = buffer.anchor_before(buffer_range.start);
15378 let end = buffer.anchor_after(buffer_range.end);
15379 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15380 buffer_id_to_ranges
15381 .entry(buffer_id)
15382 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15383 .or_insert_with(|| vec![start..end]);
15384 }
15385 }
15386 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15387 }
15388 };
15389
15390 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15391 let selections_prev = transaction_id_prev
15392 .and_then(|transaction_id_prev| {
15393 // default to selections as they were after the last edit, if we have them,
15394 // instead of how they are now.
15395 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15396 // will take you back to where you made the last edit, instead of staying where you scrolled
15397 self.selection_history
15398 .transaction(transaction_id_prev)
15399 .map(|t| t.0.clone())
15400 })
15401 .unwrap_or_else(|| {
15402 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15403 self.selections.disjoint_anchors()
15404 });
15405
15406 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15407 let format = project.update(cx, |project, cx| {
15408 project.format(buffers, target, true, trigger, cx)
15409 });
15410
15411 cx.spawn_in(window, async move |editor, cx| {
15412 let transaction = futures::select_biased! {
15413 transaction = format.log_err().fuse() => transaction,
15414 () = timeout => {
15415 log::warn!("timed out waiting for formatting");
15416 None
15417 }
15418 };
15419
15420 buffer
15421 .update(cx, |buffer, cx| {
15422 if let Some(transaction) = transaction {
15423 if !buffer.is_singleton() {
15424 buffer.push_transaction(&transaction.0, cx);
15425 }
15426 }
15427 cx.notify();
15428 })
15429 .ok();
15430
15431 if let Some(transaction_id_now) =
15432 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15433 {
15434 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15435 if has_new_transaction {
15436 _ = editor.update(cx, |editor, _| {
15437 editor
15438 .selection_history
15439 .insert_transaction(transaction_id_now, selections_prev);
15440 });
15441 }
15442 }
15443
15444 Ok(())
15445 })
15446 }
15447
15448 fn organize_imports(
15449 &mut self,
15450 _: &OrganizeImports,
15451 window: &mut Window,
15452 cx: &mut Context<Self>,
15453 ) -> Option<Task<Result<()>>> {
15454 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15455 let project = match &self.project {
15456 Some(project) => project.clone(),
15457 None => return None,
15458 };
15459 Some(self.perform_code_action_kind(
15460 project,
15461 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15462 window,
15463 cx,
15464 ))
15465 }
15466
15467 fn perform_code_action_kind(
15468 &mut self,
15469 project: Entity<Project>,
15470 kind: CodeActionKind,
15471 window: &mut Window,
15472 cx: &mut Context<Self>,
15473 ) -> Task<Result<()>> {
15474 let buffer = self.buffer.clone();
15475 let buffers = buffer.read(cx).all_buffers();
15476 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15477 let apply_action = project.update(cx, |project, cx| {
15478 project.apply_code_action_kind(buffers, kind, true, cx)
15479 });
15480 cx.spawn_in(window, async move |_, cx| {
15481 let transaction = futures::select_biased! {
15482 () = timeout => {
15483 log::warn!("timed out waiting for executing code action");
15484 None
15485 }
15486 transaction = apply_action.log_err().fuse() => transaction,
15487 };
15488 buffer
15489 .update(cx, |buffer, cx| {
15490 // check if we need this
15491 if let Some(transaction) = transaction {
15492 if !buffer.is_singleton() {
15493 buffer.push_transaction(&transaction.0, cx);
15494 }
15495 }
15496 cx.notify();
15497 })
15498 .ok();
15499 Ok(())
15500 })
15501 }
15502
15503 fn restart_language_server(
15504 &mut self,
15505 _: &RestartLanguageServer,
15506 _: &mut Window,
15507 cx: &mut Context<Self>,
15508 ) {
15509 if let Some(project) = self.project.clone() {
15510 self.buffer.update(cx, |multi_buffer, cx| {
15511 project.update(cx, |project, cx| {
15512 project.restart_language_servers_for_buffers(
15513 multi_buffer.all_buffers().into_iter().collect(),
15514 cx,
15515 );
15516 });
15517 })
15518 }
15519 }
15520
15521 fn stop_language_server(
15522 &mut self,
15523 _: &StopLanguageServer,
15524 _: &mut Window,
15525 cx: &mut Context<Self>,
15526 ) {
15527 if let Some(project) = self.project.clone() {
15528 self.buffer.update(cx, |multi_buffer, cx| {
15529 project.update(cx, |project, cx| {
15530 project.stop_language_servers_for_buffers(
15531 multi_buffer.all_buffers().into_iter().collect(),
15532 cx,
15533 );
15534 cx.emit(project::Event::RefreshInlayHints);
15535 });
15536 });
15537 }
15538 }
15539
15540 fn cancel_language_server_work(
15541 workspace: &mut Workspace,
15542 _: &actions::CancelLanguageServerWork,
15543 _: &mut Window,
15544 cx: &mut Context<Workspace>,
15545 ) {
15546 let project = workspace.project();
15547 let buffers = workspace
15548 .active_item(cx)
15549 .and_then(|item| item.act_as::<Editor>(cx))
15550 .map_or(HashSet::default(), |editor| {
15551 editor.read(cx).buffer.read(cx).all_buffers()
15552 });
15553 project.update(cx, |project, cx| {
15554 project.cancel_language_server_work_for_buffers(buffers, cx);
15555 });
15556 }
15557
15558 fn show_character_palette(
15559 &mut self,
15560 _: &ShowCharacterPalette,
15561 window: &mut Window,
15562 _: &mut Context<Self>,
15563 ) {
15564 window.show_character_palette();
15565 }
15566
15567 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15568 if self.mode.is_minimap() {
15569 return;
15570 }
15571
15572 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15573 let buffer = self.buffer.read(cx).snapshot(cx);
15574 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15575 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15576 let is_valid = buffer
15577 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15578 .any(|entry| {
15579 entry.diagnostic.is_primary
15580 && !entry.range.is_empty()
15581 && entry.range.start == primary_range_start
15582 && entry.diagnostic.message == active_diagnostics.active_message
15583 });
15584
15585 if !is_valid {
15586 self.dismiss_diagnostics(cx);
15587 }
15588 }
15589 }
15590
15591 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15592 match &self.active_diagnostics {
15593 ActiveDiagnostic::Group(group) => Some(group),
15594 _ => None,
15595 }
15596 }
15597
15598 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15599 self.dismiss_diagnostics(cx);
15600 self.active_diagnostics = ActiveDiagnostic::All;
15601 }
15602
15603 fn activate_diagnostics(
15604 &mut self,
15605 buffer_id: BufferId,
15606 diagnostic: DiagnosticEntry<usize>,
15607 window: &mut Window,
15608 cx: &mut Context<Self>,
15609 ) {
15610 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15611 return;
15612 }
15613 self.dismiss_diagnostics(cx);
15614 let snapshot = self.snapshot(window, cx);
15615 let buffer = self.buffer.read(cx).snapshot(cx);
15616 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15617 return;
15618 };
15619
15620 let diagnostic_group = buffer
15621 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15622 .collect::<Vec<_>>();
15623
15624 let blocks =
15625 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15626
15627 let blocks = self.display_map.update(cx, |display_map, cx| {
15628 display_map.insert_blocks(blocks, cx).into_iter().collect()
15629 });
15630 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15631 active_range: buffer.anchor_before(diagnostic.range.start)
15632 ..buffer.anchor_after(diagnostic.range.end),
15633 active_message: diagnostic.diagnostic.message.clone(),
15634 group_id: diagnostic.diagnostic.group_id,
15635 blocks,
15636 });
15637 cx.notify();
15638 }
15639
15640 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15641 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15642 return;
15643 };
15644
15645 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15646 if let ActiveDiagnostic::Group(group) = prev {
15647 self.display_map.update(cx, |display_map, cx| {
15648 display_map.remove_blocks(group.blocks, cx);
15649 });
15650 cx.notify();
15651 }
15652 }
15653
15654 /// Disable inline diagnostics rendering for this editor.
15655 pub fn disable_inline_diagnostics(&mut self) {
15656 self.inline_diagnostics_enabled = false;
15657 self.inline_diagnostics_update = Task::ready(());
15658 self.inline_diagnostics.clear();
15659 }
15660
15661 pub fn diagnostics_enabled(&self) -> bool {
15662 self.mode.is_full()
15663 }
15664
15665 pub fn inline_diagnostics_enabled(&self) -> bool {
15666 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15667 }
15668
15669 pub fn show_inline_diagnostics(&self) -> bool {
15670 self.show_inline_diagnostics
15671 }
15672
15673 pub fn toggle_inline_diagnostics(
15674 &mut self,
15675 _: &ToggleInlineDiagnostics,
15676 window: &mut Window,
15677 cx: &mut Context<Editor>,
15678 ) {
15679 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15680 self.refresh_inline_diagnostics(false, window, cx);
15681 }
15682
15683 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15684 self.diagnostics_max_severity = severity;
15685 self.display_map.update(cx, |display_map, _| {
15686 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15687 });
15688 }
15689
15690 pub fn toggle_diagnostics(
15691 &mut self,
15692 _: &ToggleDiagnostics,
15693 window: &mut Window,
15694 cx: &mut Context<Editor>,
15695 ) {
15696 if !self.diagnostics_enabled() {
15697 return;
15698 }
15699
15700 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15701 EditorSettings::get_global(cx)
15702 .diagnostics_max_severity
15703 .filter(|severity| severity != &DiagnosticSeverity::Off)
15704 .unwrap_or(DiagnosticSeverity::Hint)
15705 } else {
15706 DiagnosticSeverity::Off
15707 };
15708 self.set_max_diagnostics_severity(new_severity, cx);
15709 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15710 self.active_diagnostics = ActiveDiagnostic::None;
15711 self.inline_diagnostics_update = Task::ready(());
15712 self.inline_diagnostics.clear();
15713 } else {
15714 self.refresh_inline_diagnostics(false, window, cx);
15715 }
15716
15717 cx.notify();
15718 }
15719
15720 pub fn toggle_minimap(
15721 &mut self,
15722 _: &ToggleMinimap,
15723 window: &mut Window,
15724 cx: &mut Context<Editor>,
15725 ) {
15726 if self.supports_minimap(cx) {
15727 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15728 }
15729 }
15730
15731 fn refresh_inline_diagnostics(
15732 &mut self,
15733 debounce: bool,
15734 window: &mut Window,
15735 cx: &mut Context<Self>,
15736 ) {
15737 let max_severity = ProjectSettings::get_global(cx)
15738 .diagnostics
15739 .inline
15740 .max_severity
15741 .unwrap_or(self.diagnostics_max_severity);
15742
15743 if !self.inline_diagnostics_enabled()
15744 || !self.show_inline_diagnostics
15745 || max_severity == DiagnosticSeverity::Off
15746 {
15747 self.inline_diagnostics_update = Task::ready(());
15748 self.inline_diagnostics.clear();
15749 return;
15750 }
15751
15752 let debounce_ms = ProjectSettings::get_global(cx)
15753 .diagnostics
15754 .inline
15755 .update_debounce_ms;
15756 let debounce = if debounce && debounce_ms > 0 {
15757 Some(Duration::from_millis(debounce_ms))
15758 } else {
15759 None
15760 };
15761 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15762 if let Some(debounce) = debounce {
15763 cx.background_executor().timer(debounce).await;
15764 }
15765 let Some(snapshot) = editor.upgrade().and_then(|editor| {
15766 editor
15767 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15768 .ok()
15769 }) else {
15770 return;
15771 };
15772
15773 let new_inline_diagnostics = cx
15774 .background_spawn(async move {
15775 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15776 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15777 let message = diagnostic_entry
15778 .diagnostic
15779 .message
15780 .split_once('\n')
15781 .map(|(line, _)| line)
15782 .map(SharedString::new)
15783 .unwrap_or_else(|| {
15784 SharedString::from(diagnostic_entry.diagnostic.message)
15785 });
15786 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15787 let (Ok(i) | Err(i)) = inline_diagnostics
15788 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15789 inline_diagnostics.insert(
15790 i,
15791 (
15792 start_anchor,
15793 InlineDiagnostic {
15794 message,
15795 group_id: diagnostic_entry.diagnostic.group_id,
15796 start: diagnostic_entry.range.start.to_point(&snapshot),
15797 is_primary: diagnostic_entry.diagnostic.is_primary,
15798 severity: diagnostic_entry.diagnostic.severity,
15799 },
15800 ),
15801 );
15802 }
15803 inline_diagnostics
15804 })
15805 .await;
15806
15807 editor
15808 .update(cx, |editor, cx| {
15809 editor.inline_diagnostics = new_inline_diagnostics;
15810 cx.notify();
15811 })
15812 .ok();
15813 });
15814 }
15815
15816 pub fn set_selections_from_remote(
15817 &mut self,
15818 selections: Vec<Selection<Anchor>>,
15819 pending_selection: Option<Selection<Anchor>>,
15820 window: &mut Window,
15821 cx: &mut Context<Self>,
15822 ) {
15823 let old_cursor_position = self.selections.newest_anchor().head();
15824 self.selections.change_with(cx, |s| {
15825 s.select_anchors(selections);
15826 if let Some(pending_selection) = pending_selection {
15827 s.set_pending(pending_selection, SelectMode::Character);
15828 } else {
15829 s.clear_pending();
15830 }
15831 });
15832 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15833 }
15834
15835 pub fn transact(
15836 &mut self,
15837 window: &mut Window,
15838 cx: &mut Context<Self>,
15839 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15840 ) -> Option<TransactionId> {
15841 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15842 this.start_transaction_at(Instant::now(), window, cx);
15843 update(this, window, cx);
15844 this.end_transaction_at(Instant::now(), cx)
15845 })
15846 }
15847
15848 pub fn start_transaction_at(
15849 &mut self,
15850 now: Instant,
15851 window: &mut Window,
15852 cx: &mut Context<Self>,
15853 ) {
15854 self.end_selection(window, cx);
15855 if let Some(tx_id) = self
15856 .buffer
15857 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15858 {
15859 self.selection_history
15860 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15861 cx.emit(EditorEvent::TransactionBegun {
15862 transaction_id: tx_id,
15863 })
15864 }
15865 }
15866
15867 pub fn end_transaction_at(
15868 &mut self,
15869 now: Instant,
15870 cx: &mut Context<Self>,
15871 ) -> Option<TransactionId> {
15872 if let Some(transaction_id) = self
15873 .buffer
15874 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15875 {
15876 if let Some((_, end_selections)) =
15877 self.selection_history.transaction_mut(transaction_id)
15878 {
15879 *end_selections = Some(self.selections.disjoint_anchors());
15880 } else {
15881 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15882 }
15883
15884 cx.emit(EditorEvent::Edited { transaction_id });
15885 Some(transaction_id)
15886 } else {
15887 None
15888 }
15889 }
15890
15891 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15892 if self.selection_mark_mode {
15893 self.change_selections(None, window, cx, |s| {
15894 s.move_with(|_, sel| {
15895 sel.collapse_to(sel.head(), SelectionGoal::None);
15896 });
15897 })
15898 }
15899 self.selection_mark_mode = true;
15900 cx.notify();
15901 }
15902
15903 pub fn swap_selection_ends(
15904 &mut self,
15905 _: &actions::SwapSelectionEnds,
15906 window: &mut Window,
15907 cx: &mut Context<Self>,
15908 ) {
15909 self.change_selections(None, window, cx, |s| {
15910 s.move_with(|_, sel| {
15911 if sel.start != sel.end {
15912 sel.reversed = !sel.reversed
15913 }
15914 });
15915 });
15916 self.request_autoscroll(Autoscroll::newest(), cx);
15917 cx.notify();
15918 }
15919
15920 pub fn toggle_fold(
15921 &mut self,
15922 _: &actions::ToggleFold,
15923 window: &mut Window,
15924 cx: &mut Context<Self>,
15925 ) {
15926 if self.is_singleton(cx) {
15927 let selection = self.selections.newest::<Point>(cx);
15928
15929 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15930 let range = if selection.is_empty() {
15931 let point = selection.head().to_display_point(&display_map);
15932 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15933 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15934 .to_point(&display_map);
15935 start..end
15936 } else {
15937 selection.range()
15938 };
15939 if display_map.folds_in_range(range).next().is_some() {
15940 self.unfold_lines(&Default::default(), window, cx)
15941 } else {
15942 self.fold(&Default::default(), window, cx)
15943 }
15944 } else {
15945 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15946 let buffer_ids: HashSet<_> = self
15947 .selections
15948 .disjoint_anchor_ranges()
15949 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15950 .collect();
15951
15952 let should_unfold = buffer_ids
15953 .iter()
15954 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15955
15956 for buffer_id in buffer_ids {
15957 if should_unfold {
15958 self.unfold_buffer(buffer_id, cx);
15959 } else {
15960 self.fold_buffer(buffer_id, cx);
15961 }
15962 }
15963 }
15964 }
15965
15966 pub fn toggle_fold_recursive(
15967 &mut self,
15968 _: &actions::ToggleFoldRecursive,
15969 window: &mut Window,
15970 cx: &mut Context<Self>,
15971 ) {
15972 let selection = self.selections.newest::<Point>(cx);
15973
15974 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15975 let range = if selection.is_empty() {
15976 let point = selection.head().to_display_point(&display_map);
15977 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15978 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15979 .to_point(&display_map);
15980 start..end
15981 } else {
15982 selection.range()
15983 };
15984 if display_map.folds_in_range(range).next().is_some() {
15985 self.unfold_recursive(&Default::default(), window, cx)
15986 } else {
15987 self.fold_recursive(&Default::default(), window, cx)
15988 }
15989 }
15990
15991 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15992 if self.is_singleton(cx) {
15993 let mut to_fold = Vec::new();
15994 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15995 let selections = self.selections.all_adjusted(cx);
15996
15997 for selection in selections {
15998 let range = selection.range().sorted();
15999 let buffer_start_row = range.start.row;
16000
16001 if range.start.row != range.end.row {
16002 let mut found = false;
16003 let mut row = range.start.row;
16004 while row <= range.end.row {
16005 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16006 {
16007 found = true;
16008 row = crease.range().end.row + 1;
16009 to_fold.push(crease);
16010 } else {
16011 row += 1
16012 }
16013 }
16014 if found {
16015 continue;
16016 }
16017 }
16018
16019 for row in (0..=range.start.row).rev() {
16020 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16021 if crease.range().end.row >= buffer_start_row {
16022 to_fold.push(crease);
16023 if row <= range.start.row {
16024 break;
16025 }
16026 }
16027 }
16028 }
16029 }
16030
16031 self.fold_creases(to_fold, true, window, cx);
16032 } else {
16033 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16034 let buffer_ids = self
16035 .selections
16036 .disjoint_anchor_ranges()
16037 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16038 .collect::<HashSet<_>>();
16039 for buffer_id in buffer_ids {
16040 self.fold_buffer(buffer_id, cx);
16041 }
16042 }
16043 }
16044
16045 fn fold_at_level(
16046 &mut self,
16047 fold_at: &FoldAtLevel,
16048 window: &mut Window,
16049 cx: &mut Context<Self>,
16050 ) {
16051 if !self.buffer.read(cx).is_singleton() {
16052 return;
16053 }
16054
16055 let fold_at_level = fold_at.0;
16056 let snapshot = self.buffer.read(cx).snapshot(cx);
16057 let mut to_fold = Vec::new();
16058 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16059
16060 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16061 while start_row < end_row {
16062 match self
16063 .snapshot(window, cx)
16064 .crease_for_buffer_row(MultiBufferRow(start_row))
16065 {
16066 Some(crease) => {
16067 let nested_start_row = crease.range().start.row + 1;
16068 let nested_end_row = crease.range().end.row;
16069
16070 if current_level < fold_at_level {
16071 stack.push((nested_start_row, nested_end_row, current_level + 1));
16072 } else if current_level == fold_at_level {
16073 to_fold.push(crease);
16074 }
16075
16076 start_row = nested_end_row + 1;
16077 }
16078 None => start_row += 1,
16079 }
16080 }
16081 }
16082
16083 self.fold_creases(to_fold, true, window, cx);
16084 }
16085
16086 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16087 if self.buffer.read(cx).is_singleton() {
16088 let mut fold_ranges = Vec::new();
16089 let snapshot = self.buffer.read(cx).snapshot(cx);
16090
16091 for row in 0..snapshot.max_row().0 {
16092 if let Some(foldable_range) = self
16093 .snapshot(window, cx)
16094 .crease_for_buffer_row(MultiBufferRow(row))
16095 {
16096 fold_ranges.push(foldable_range);
16097 }
16098 }
16099
16100 self.fold_creases(fold_ranges, true, window, cx);
16101 } else {
16102 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16103 editor
16104 .update_in(cx, |editor, _, cx| {
16105 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16106 editor.fold_buffer(buffer_id, cx);
16107 }
16108 })
16109 .ok();
16110 });
16111 }
16112 }
16113
16114 pub fn fold_function_bodies(
16115 &mut self,
16116 _: &actions::FoldFunctionBodies,
16117 window: &mut Window,
16118 cx: &mut Context<Self>,
16119 ) {
16120 let snapshot = self.buffer.read(cx).snapshot(cx);
16121
16122 let ranges = snapshot
16123 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16124 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16125 .collect::<Vec<_>>();
16126
16127 let creases = ranges
16128 .into_iter()
16129 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16130 .collect();
16131
16132 self.fold_creases(creases, true, window, cx);
16133 }
16134
16135 pub fn fold_recursive(
16136 &mut self,
16137 _: &actions::FoldRecursive,
16138 window: &mut Window,
16139 cx: &mut Context<Self>,
16140 ) {
16141 let mut to_fold = Vec::new();
16142 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16143 let selections = self.selections.all_adjusted(cx);
16144
16145 for selection in selections {
16146 let range = selection.range().sorted();
16147 let buffer_start_row = range.start.row;
16148
16149 if range.start.row != range.end.row {
16150 let mut found = false;
16151 for row in range.start.row..=range.end.row {
16152 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16153 found = true;
16154 to_fold.push(crease);
16155 }
16156 }
16157 if found {
16158 continue;
16159 }
16160 }
16161
16162 for row in (0..=range.start.row).rev() {
16163 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16164 if crease.range().end.row >= buffer_start_row {
16165 to_fold.push(crease);
16166 } else {
16167 break;
16168 }
16169 }
16170 }
16171 }
16172
16173 self.fold_creases(to_fold, true, window, cx);
16174 }
16175
16176 pub fn fold_at(
16177 &mut self,
16178 buffer_row: MultiBufferRow,
16179 window: &mut Window,
16180 cx: &mut Context<Self>,
16181 ) {
16182 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16183
16184 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16185 let autoscroll = self
16186 .selections
16187 .all::<Point>(cx)
16188 .iter()
16189 .any(|selection| crease.range().overlaps(&selection.range()));
16190
16191 self.fold_creases(vec![crease], autoscroll, window, cx);
16192 }
16193 }
16194
16195 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16196 if self.is_singleton(cx) {
16197 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16198 let buffer = &display_map.buffer_snapshot;
16199 let selections = self.selections.all::<Point>(cx);
16200 let ranges = selections
16201 .iter()
16202 .map(|s| {
16203 let range = s.display_range(&display_map).sorted();
16204 let mut start = range.start.to_point(&display_map);
16205 let mut end = range.end.to_point(&display_map);
16206 start.column = 0;
16207 end.column = buffer.line_len(MultiBufferRow(end.row));
16208 start..end
16209 })
16210 .collect::<Vec<_>>();
16211
16212 self.unfold_ranges(&ranges, true, true, cx);
16213 } else {
16214 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16215 let buffer_ids = self
16216 .selections
16217 .disjoint_anchor_ranges()
16218 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16219 .collect::<HashSet<_>>();
16220 for buffer_id in buffer_ids {
16221 self.unfold_buffer(buffer_id, cx);
16222 }
16223 }
16224 }
16225
16226 pub fn unfold_recursive(
16227 &mut self,
16228 _: &UnfoldRecursive,
16229 _window: &mut Window,
16230 cx: &mut Context<Self>,
16231 ) {
16232 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16233 let selections = self.selections.all::<Point>(cx);
16234 let ranges = selections
16235 .iter()
16236 .map(|s| {
16237 let mut range = s.display_range(&display_map).sorted();
16238 *range.start.column_mut() = 0;
16239 *range.end.column_mut() = display_map.line_len(range.end.row());
16240 let start = range.start.to_point(&display_map);
16241 let end = range.end.to_point(&display_map);
16242 start..end
16243 })
16244 .collect::<Vec<_>>();
16245
16246 self.unfold_ranges(&ranges, true, true, cx);
16247 }
16248
16249 pub fn unfold_at(
16250 &mut self,
16251 buffer_row: MultiBufferRow,
16252 _window: &mut Window,
16253 cx: &mut Context<Self>,
16254 ) {
16255 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16256
16257 let intersection_range = Point::new(buffer_row.0, 0)
16258 ..Point::new(
16259 buffer_row.0,
16260 display_map.buffer_snapshot.line_len(buffer_row),
16261 );
16262
16263 let autoscroll = self
16264 .selections
16265 .all::<Point>(cx)
16266 .iter()
16267 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16268
16269 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16270 }
16271
16272 pub fn unfold_all(
16273 &mut self,
16274 _: &actions::UnfoldAll,
16275 _window: &mut Window,
16276 cx: &mut Context<Self>,
16277 ) {
16278 if self.buffer.read(cx).is_singleton() {
16279 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16280 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16281 } else {
16282 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16283 editor
16284 .update(cx, |editor, cx| {
16285 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16286 editor.unfold_buffer(buffer_id, cx);
16287 }
16288 })
16289 .ok();
16290 });
16291 }
16292 }
16293
16294 pub fn fold_selected_ranges(
16295 &mut self,
16296 _: &FoldSelectedRanges,
16297 window: &mut Window,
16298 cx: &mut Context<Self>,
16299 ) {
16300 let selections = self.selections.all_adjusted(cx);
16301 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16302 let ranges = selections
16303 .into_iter()
16304 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16305 .collect::<Vec<_>>();
16306 self.fold_creases(ranges, true, window, cx);
16307 }
16308
16309 pub fn fold_ranges<T: ToOffset + Clone>(
16310 &mut self,
16311 ranges: Vec<Range<T>>,
16312 auto_scroll: bool,
16313 window: &mut Window,
16314 cx: &mut Context<Self>,
16315 ) {
16316 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16317 let ranges = ranges
16318 .into_iter()
16319 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16320 .collect::<Vec<_>>();
16321 self.fold_creases(ranges, auto_scroll, window, cx);
16322 }
16323
16324 pub fn fold_creases<T: ToOffset + Clone>(
16325 &mut self,
16326 creases: Vec<Crease<T>>,
16327 auto_scroll: bool,
16328 _window: &mut Window,
16329 cx: &mut Context<Self>,
16330 ) {
16331 if creases.is_empty() {
16332 return;
16333 }
16334
16335 let mut buffers_affected = HashSet::default();
16336 let multi_buffer = self.buffer().read(cx);
16337 for crease in &creases {
16338 if let Some((_, buffer, _)) =
16339 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16340 {
16341 buffers_affected.insert(buffer.read(cx).remote_id());
16342 };
16343 }
16344
16345 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16346
16347 if auto_scroll {
16348 self.request_autoscroll(Autoscroll::fit(), cx);
16349 }
16350
16351 cx.notify();
16352
16353 self.scrollbar_marker_state.dirty = true;
16354 self.folds_did_change(cx);
16355 }
16356
16357 /// Removes any folds whose ranges intersect any of the given ranges.
16358 pub fn unfold_ranges<T: ToOffset + Clone>(
16359 &mut self,
16360 ranges: &[Range<T>],
16361 inclusive: bool,
16362 auto_scroll: bool,
16363 cx: &mut Context<Self>,
16364 ) {
16365 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16366 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16367 });
16368 self.folds_did_change(cx);
16369 }
16370
16371 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16372 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16373 return;
16374 }
16375 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16376 self.display_map.update(cx, |display_map, cx| {
16377 display_map.fold_buffers([buffer_id], cx)
16378 });
16379 cx.emit(EditorEvent::BufferFoldToggled {
16380 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16381 folded: true,
16382 });
16383 cx.notify();
16384 }
16385
16386 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16387 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16388 return;
16389 }
16390 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16391 self.display_map.update(cx, |display_map, cx| {
16392 display_map.unfold_buffers([buffer_id], cx);
16393 });
16394 cx.emit(EditorEvent::BufferFoldToggled {
16395 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16396 folded: false,
16397 });
16398 cx.notify();
16399 }
16400
16401 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16402 self.display_map.read(cx).is_buffer_folded(buffer)
16403 }
16404
16405 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16406 self.display_map.read(cx).folded_buffers()
16407 }
16408
16409 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16410 self.display_map.update(cx, |display_map, cx| {
16411 display_map.disable_header_for_buffer(buffer_id, cx);
16412 });
16413 cx.notify();
16414 }
16415
16416 /// Removes any folds with the given ranges.
16417 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16418 &mut self,
16419 ranges: &[Range<T>],
16420 type_id: TypeId,
16421 auto_scroll: bool,
16422 cx: &mut Context<Self>,
16423 ) {
16424 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16425 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16426 });
16427 self.folds_did_change(cx);
16428 }
16429
16430 fn remove_folds_with<T: ToOffset + Clone>(
16431 &mut self,
16432 ranges: &[Range<T>],
16433 auto_scroll: bool,
16434 cx: &mut Context<Self>,
16435 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16436 ) {
16437 if ranges.is_empty() {
16438 return;
16439 }
16440
16441 let mut buffers_affected = HashSet::default();
16442 let multi_buffer = self.buffer().read(cx);
16443 for range in ranges {
16444 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16445 buffers_affected.insert(buffer.read(cx).remote_id());
16446 };
16447 }
16448
16449 self.display_map.update(cx, update);
16450
16451 if auto_scroll {
16452 self.request_autoscroll(Autoscroll::fit(), cx);
16453 }
16454
16455 cx.notify();
16456 self.scrollbar_marker_state.dirty = true;
16457 self.active_indent_guides_state.dirty = true;
16458 }
16459
16460 pub fn update_fold_widths(
16461 &mut self,
16462 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16463 cx: &mut Context<Self>,
16464 ) -> bool {
16465 self.display_map
16466 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16467 }
16468
16469 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16470 self.display_map.read(cx).fold_placeholder.clone()
16471 }
16472
16473 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16474 self.buffer.update(cx, |buffer, cx| {
16475 buffer.set_all_diff_hunks_expanded(cx);
16476 });
16477 }
16478
16479 pub fn expand_all_diff_hunks(
16480 &mut self,
16481 _: &ExpandAllDiffHunks,
16482 _window: &mut Window,
16483 cx: &mut Context<Self>,
16484 ) {
16485 self.buffer.update(cx, |buffer, cx| {
16486 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16487 });
16488 }
16489
16490 pub fn toggle_selected_diff_hunks(
16491 &mut self,
16492 _: &ToggleSelectedDiffHunks,
16493 _window: &mut Window,
16494 cx: &mut Context<Self>,
16495 ) {
16496 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16497 self.toggle_diff_hunks_in_ranges(ranges, cx);
16498 }
16499
16500 pub fn diff_hunks_in_ranges<'a>(
16501 &'a self,
16502 ranges: &'a [Range<Anchor>],
16503 buffer: &'a MultiBufferSnapshot,
16504 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16505 ranges.iter().flat_map(move |range| {
16506 let end_excerpt_id = range.end.excerpt_id;
16507 let range = range.to_point(buffer);
16508 let mut peek_end = range.end;
16509 if range.end.row < buffer.max_row().0 {
16510 peek_end = Point::new(range.end.row + 1, 0);
16511 }
16512 buffer
16513 .diff_hunks_in_range(range.start..peek_end)
16514 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16515 })
16516 }
16517
16518 pub fn has_stageable_diff_hunks_in_ranges(
16519 &self,
16520 ranges: &[Range<Anchor>],
16521 snapshot: &MultiBufferSnapshot,
16522 ) -> bool {
16523 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16524 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16525 }
16526
16527 pub fn toggle_staged_selected_diff_hunks(
16528 &mut self,
16529 _: &::git::ToggleStaged,
16530 _: &mut Window,
16531 cx: &mut Context<Self>,
16532 ) {
16533 let snapshot = self.buffer.read(cx).snapshot(cx);
16534 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16535 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16536 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16537 }
16538
16539 pub fn set_render_diff_hunk_controls(
16540 &mut self,
16541 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16542 cx: &mut Context<Self>,
16543 ) {
16544 self.render_diff_hunk_controls = render_diff_hunk_controls;
16545 cx.notify();
16546 }
16547
16548 pub fn stage_and_next(
16549 &mut self,
16550 _: &::git::StageAndNext,
16551 window: &mut Window,
16552 cx: &mut Context<Self>,
16553 ) {
16554 self.do_stage_or_unstage_and_next(true, window, cx);
16555 }
16556
16557 pub fn unstage_and_next(
16558 &mut self,
16559 _: &::git::UnstageAndNext,
16560 window: &mut Window,
16561 cx: &mut Context<Self>,
16562 ) {
16563 self.do_stage_or_unstage_and_next(false, window, cx);
16564 }
16565
16566 pub fn stage_or_unstage_diff_hunks(
16567 &mut self,
16568 stage: bool,
16569 ranges: Vec<Range<Anchor>>,
16570 cx: &mut Context<Self>,
16571 ) {
16572 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16573 cx.spawn(async move |this, cx| {
16574 task.await?;
16575 this.update(cx, |this, cx| {
16576 let snapshot = this.buffer.read(cx).snapshot(cx);
16577 let chunk_by = this
16578 .diff_hunks_in_ranges(&ranges, &snapshot)
16579 .chunk_by(|hunk| hunk.buffer_id);
16580 for (buffer_id, hunks) in &chunk_by {
16581 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16582 }
16583 })
16584 })
16585 .detach_and_log_err(cx);
16586 }
16587
16588 fn save_buffers_for_ranges_if_needed(
16589 &mut self,
16590 ranges: &[Range<Anchor>],
16591 cx: &mut Context<Editor>,
16592 ) -> Task<Result<()>> {
16593 let multibuffer = self.buffer.read(cx);
16594 let snapshot = multibuffer.read(cx);
16595 let buffer_ids: HashSet<_> = ranges
16596 .iter()
16597 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16598 .collect();
16599 drop(snapshot);
16600
16601 let mut buffers = HashSet::default();
16602 for buffer_id in buffer_ids {
16603 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16604 let buffer = buffer_entity.read(cx);
16605 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16606 {
16607 buffers.insert(buffer_entity);
16608 }
16609 }
16610 }
16611
16612 if let Some(project) = &self.project {
16613 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16614 } else {
16615 Task::ready(Ok(()))
16616 }
16617 }
16618
16619 fn do_stage_or_unstage_and_next(
16620 &mut self,
16621 stage: bool,
16622 window: &mut Window,
16623 cx: &mut Context<Self>,
16624 ) {
16625 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16626
16627 if ranges.iter().any(|range| range.start != range.end) {
16628 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16629 return;
16630 }
16631
16632 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16633 let snapshot = self.snapshot(window, cx);
16634 let position = self.selections.newest::<Point>(cx).head();
16635 let mut row = snapshot
16636 .buffer_snapshot
16637 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16638 .find(|hunk| hunk.row_range.start.0 > position.row)
16639 .map(|hunk| hunk.row_range.start);
16640
16641 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16642 // Outside of the project diff editor, wrap around to the beginning.
16643 if !all_diff_hunks_expanded {
16644 row = row.or_else(|| {
16645 snapshot
16646 .buffer_snapshot
16647 .diff_hunks_in_range(Point::zero()..position)
16648 .find(|hunk| hunk.row_range.end.0 < position.row)
16649 .map(|hunk| hunk.row_range.start)
16650 });
16651 }
16652
16653 if let Some(row) = row {
16654 let destination = Point::new(row.0, 0);
16655 let autoscroll = Autoscroll::center();
16656
16657 self.unfold_ranges(&[destination..destination], false, false, cx);
16658 self.change_selections(Some(autoscroll), window, cx, |s| {
16659 s.select_ranges([destination..destination]);
16660 });
16661 }
16662 }
16663
16664 fn do_stage_or_unstage(
16665 &self,
16666 stage: bool,
16667 buffer_id: BufferId,
16668 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16669 cx: &mut App,
16670 ) -> Option<()> {
16671 let project = self.project.as_ref()?;
16672 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16673 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16674 let buffer_snapshot = buffer.read(cx).snapshot();
16675 let file_exists = buffer_snapshot
16676 .file()
16677 .is_some_and(|file| file.disk_state().exists());
16678 diff.update(cx, |diff, cx| {
16679 diff.stage_or_unstage_hunks(
16680 stage,
16681 &hunks
16682 .map(|hunk| buffer_diff::DiffHunk {
16683 buffer_range: hunk.buffer_range,
16684 diff_base_byte_range: hunk.diff_base_byte_range,
16685 secondary_status: hunk.secondary_status,
16686 range: Point::zero()..Point::zero(), // unused
16687 })
16688 .collect::<Vec<_>>(),
16689 &buffer_snapshot,
16690 file_exists,
16691 cx,
16692 )
16693 });
16694 None
16695 }
16696
16697 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16698 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16699 self.buffer
16700 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16701 }
16702
16703 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16704 self.buffer.update(cx, |buffer, cx| {
16705 let ranges = vec![Anchor::min()..Anchor::max()];
16706 if !buffer.all_diff_hunks_expanded()
16707 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16708 {
16709 buffer.collapse_diff_hunks(ranges, cx);
16710 true
16711 } else {
16712 false
16713 }
16714 })
16715 }
16716
16717 fn toggle_diff_hunks_in_ranges(
16718 &mut self,
16719 ranges: Vec<Range<Anchor>>,
16720 cx: &mut Context<Editor>,
16721 ) {
16722 self.buffer.update(cx, |buffer, cx| {
16723 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16724 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16725 })
16726 }
16727
16728 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16729 self.buffer.update(cx, |buffer, cx| {
16730 let snapshot = buffer.snapshot(cx);
16731 let excerpt_id = range.end.excerpt_id;
16732 let point_range = range.to_point(&snapshot);
16733 let expand = !buffer.single_hunk_is_expanded(range, cx);
16734 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16735 })
16736 }
16737
16738 pub(crate) fn apply_all_diff_hunks(
16739 &mut self,
16740 _: &ApplyAllDiffHunks,
16741 window: &mut Window,
16742 cx: &mut Context<Self>,
16743 ) {
16744 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16745
16746 let buffers = self.buffer.read(cx).all_buffers();
16747 for branch_buffer in buffers {
16748 branch_buffer.update(cx, |branch_buffer, cx| {
16749 branch_buffer.merge_into_base(Vec::new(), cx);
16750 });
16751 }
16752
16753 if let Some(project) = self.project.clone() {
16754 self.save(true, project, window, cx).detach_and_log_err(cx);
16755 }
16756 }
16757
16758 pub(crate) fn apply_selected_diff_hunks(
16759 &mut self,
16760 _: &ApplyDiffHunk,
16761 window: &mut Window,
16762 cx: &mut Context<Self>,
16763 ) {
16764 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16765 let snapshot = self.snapshot(window, cx);
16766 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16767 let mut ranges_by_buffer = HashMap::default();
16768 self.transact(window, cx, |editor, _window, cx| {
16769 for hunk in hunks {
16770 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16771 ranges_by_buffer
16772 .entry(buffer.clone())
16773 .or_insert_with(Vec::new)
16774 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16775 }
16776 }
16777
16778 for (buffer, ranges) in ranges_by_buffer {
16779 buffer.update(cx, |buffer, cx| {
16780 buffer.merge_into_base(ranges, cx);
16781 });
16782 }
16783 });
16784
16785 if let Some(project) = self.project.clone() {
16786 self.save(true, project, window, cx).detach_and_log_err(cx);
16787 }
16788 }
16789
16790 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16791 if hovered != self.gutter_hovered {
16792 self.gutter_hovered = hovered;
16793 cx.notify();
16794 }
16795 }
16796
16797 pub fn insert_blocks(
16798 &mut self,
16799 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16800 autoscroll: Option<Autoscroll>,
16801 cx: &mut Context<Self>,
16802 ) -> Vec<CustomBlockId> {
16803 let blocks = self
16804 .display_map
16805 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16806 if let Some(autoscroll) = autoscroll {
16807 self.request_autoscroll(autoscroll, cx);
16808 }
16809 cx.notify();
16810 blocks
16811 }
16812
16813 pub fn resize_blocks(
16814 &mut self,
16815 heights: HashMap<CustomBlockId, u32>,
16816 autoscroll: Option<Autoscroll>,
16817 cx: &mut Context<Self>,
16818 ) {
16819 self.display_map
16820 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16821 if let Some(autoscroll) = autoscroll {
16822 self.request_autoscroll(autoscroll, cx);
16823 }
16824 cx.notify();
16825 }
16826
16827 pub fn replace_blocks(
16828 &mut self,
16829 renderers: HashMap<CustomBlockId, RenderBlock>,
16830 autoscroll: Option<Autoscroll>,
16831 cx: &mut Context<Self>,
16832 ) {
16833 self.display_map
16834 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16835 if let Some(autoscroll) = autoscroll {
16836 self.request_autoscroll(autoscroll, cx);
16837 }
16838 cx.notify();
16839 }
16840
16841 pub fn remove_blocks(
16842 &mut self,
16843 block_ids: HashSet<CustomBlockId>,
16844 autoscroll: Option<Autoscroll>,
16845 cx: &mut Context<Self>,
16846 ) {
16847 self.display_map.update(cx, |display_map, cx| {
16848 display_map.remove_blocks(block_ids, cx)
16849 });
16850 if let Some(autoscroll) = autoscroll {
16851 self.request_autoscroll(autoscroll, cx);
16852 }
16853 cx.notify();
16854 }
16855
16856 pub fn row_for_block(
16857 &self,
16858 block_id: CustomBlockId,
16859 cx: &mut Context<Self>,
16860 ) -> Option<DisplayRow> {
16861 self.display_map
16862 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16863 }
16864
16865 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16866 self.focused_block = Some(focused_block);
16867 }
16868
16869 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16870 self.focused_block.take()
16871 }
16872
16873 pub fn insert_creases(
16874 &mut self,
16875 creases: impl IntoIterator<Item = Crease<Anchor>>,
16876 cx: &mut Context<Self>,
16877 ) -> Vec<CreaseId> {
16878 self.display_map
16879 .update(cx, |map, cx| map.insert_creases(creases, cx))
16880 }
16881
16882 pub fn remove_creases(
16883 &mut self,
16884 ids: impl IntoIterator<Item = CreaseId>,
16885 cx: &mut Context<Self>,
16886 ) -> Vec<(CreaseId, Range<Anchor>)> {
16887 self.display_map
16888 .update(cx, |map, cx| map.remove_creases(ids, cx))
16889 }
16890
16891 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16892 self.display_map
16893 .update(cx, |map, cx| map.snapshot(cx))
16894 .longest_row()
16895 }
16896
16897 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16898 self.display_map
16899 .update(cx, |map, cx| map.snapshot(cx))
16900 .max_point()
16901 }
16902
16903 pub fn text(&self, cx: &App) -> String {
16904 self.buffer.read(cx).read(cx).text()
16905 }
16906
16907 pub fn is_empty(&self, cx: &App) -> bool {
16908 self.buffer.read(cx).read(cx).is_empty()
16909 }
16910
16911 pub fn text_option(&self, cx: &App) -> Option<String> {
16912 let text = self.text(cx);
16913 let text = text.trim();
16914
16915 if text.is_empty() {
16916 return None;
16917 }
16918
16919 Some(text.to_string())
16920 }
16921
16922 pub fn set_text(
16923 &mut self,
16924 text: impl Into<Arc<str>>,
16925 window: &mut Window,
16926 cx: &mut Context<Self>,
16927 ) {
16928 self.transact(window, cx, |this, _, cx| {
16929 this.buffer
16930 .read(cx)
16931 .as_singleton()
16932 .expect("you can only call set_text on editors for singleton buffers")
16933 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16934 });
16935 }
16936
16937 pub fn display_text(&self, cx: &mut App) -> String {
16938 self.display_map
16939 .update(cx, |map, cx| map.snapshot(cx))
16940 .text()
16941 }
16942
16943 fn create_minimap(
16944 &self,
16945 minimap_settings: MinimapSettings,
16946 window: &mut Window,
16947 cx: &mut Context<Self>,
16948 ) -> Option<Entity<Self>> {
16949 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
16950 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
16951 }
16952
16953 fn initialize_new_minimap(
16954 &self,
16955 minimap_settings: MinimapSettings,
16956 window: &mut Window,
16957 cx: &mut Context<Self>,
16958 ) -> Entity<Self> {
16959 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
16960
16961 let mut minimap = Editor::new_internal(
16962 EditorMode::Minimap {
16963 parent: cx.weak_entity(),
16964 },
16965 self.buffer.clone(),
16966 self.project.clone(),
16967 Some(self.display_map.clone()),
16968 window,
16969 cx,
16970 );
16971 minimap.scroll_manager.clone_state(&self.scroll_manager);
16972 minimap.set_text_style_refinement(TextStyleRefinement {
16973 font_size: Some(MINIMAP_FONT_SIZE),
16974 font_weight: Some(MINIMAP_FONT_WEIGHT),
16975 ..Default::default()
16976 });
16977 minimap.update_minimap_configuration(minimap_settings, cx);
16978 cx.new(|_| minimap)
16979 }
16980
16981 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
16982 let current_line_highlight = minimap_settings
16983 .current_line_highlight
16984 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
16985 self.set_current_line_highlight(Some(current_line_highlight));
16986 }
16987
16988 pub fn minimap(&self) -> Option<&Entity<Self>> {
16989 self.minimap
16990 .as_ref()
16991 .filter(|_| self.minimap_visibility.visible())
16992 }
16993
16994 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16995 let mut wrap_guides = smallvec![];
16996
16997 if self.show_wrap_guides == Some(false) {
16998 return wrap_guides;
16999 }
17000
17001 let settings = self.buffer.read(cx).language_settings(cx);
17002 if settings.show_wrap_guides {
17003 match self.soft_wrap_mode(cx) {
17004 SoftWrap::Column(soft_wrap) => {
17005 wrap_guides.push((soft_wrap as usize, true));
17006 }
17007 SoftWrap::Bounded(soft_wrap) => {
17008 wrap_guides.push((soft_wrap as usize, true));
17009 }
17010 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17011 }
17012 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17013 }
17014
17015 wrap_guides
17016 }
17017
17018 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17019 let settings = self.buffer.read(cx).language_settings(cx);
17020 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17021 match mode {
17022 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17023 SoftWrap::None
17024 }
17025 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17026 language_settings::SoftWrap::PreferredLineLength => {
17027 SoftWrap::Column(settings.preferred_line_length)
17028 }
17029 language_settings::SoftWrap::Bounded => {
17030 SoftWrap::Bounded(settings.preferred_line_length)
17031 }
17032 }
17033 }
17034
17035 pub fn set_soft_wrap_mode(
17036 &mut self,
17037 mode: language_settings::SoftWrap,
17038
17039 cx: &mut Context<Self>,
17040 ) {
17041 self.soft_wrap_mode_override = Some(mode);
17042 cx.notify();
17043 }
17044
17045 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17046 self.hard_wrap = hard_wrap;
17047 cx.notify();
17048 }
17049
17050 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17051 self.text_style_refinement = Some(style);
17052 }
17053
17054 /// called by the Element so we know what style we were most recently rendered with.
17055 pub(crate) fn set_style(
17056 &mut self,
17057 style: EditorStyle,
17058 window: &mut Window,
17059 cx: &mut Context<Self>,
17060 ) {
17061 // We intentionally do not inform the display map about the minimap style
17062 // so that wrapping is not recalculated and stays consistent for the editor
17063 // and its linked minimap.
17064 if !self.mode.is_minimap() {
17065 let rem_size = window.rem_size();
17066 self.display_map.update(cx, |map, cx| {
17067 map.set_font(
17068 style.text.font(),
17069 style.text.font_size.to_pixels(rem_size),
17070 cx,
17071 )
17072 });
17073 }
17074 self.style = Some(style);
17075 }
17076
17077 pub fn style(&self) -> Option<&EditorStyle> {
17078 self.style.as_ref()
17079 }
17080
17081 // Called by the element. This method is not designed to be called outside of the editor
17082 // element's layout code because it does not notify when rewrapping is computed synchronously.
17083 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17084 self.display_map
17085 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17086 }
17087
17088 pub fn set_soft_wrap(&mut self) {
17089 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17090 }
17091
17092 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17093 if self.soft_wrap_mode_override.is_some() {
17094 self.soft_wrap_mode_override.take();
17095 } else {
17096 let soft_wrap = match self.soft_wrap_mode(cx) {
17097 SoftWrap::GitDiff => return,
17098 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17099 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17100 language_settings::SoftWrap::None
17101 }
17102 };
17103 self.soft_wrap_mode_override = Some(soft_wrap);
17104 }
17105 cx.notify();
17106 }
17107
17108 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17109 let Some(workspace) = self.workspace() else {
17110 return;
17111 };
17112 let fs = workspace.read(cx).app_state().fs.clone();
17113 let current_show = TabBarSettings::get_global(cx).show;
17114 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17115 setting.show = Some(!current_show);
17116 });
17117 }
17118
17119 pub fn toggle_indent_guides(
17120 &mut self,
17121 _: &ToggleIndentGuides,
17122 _: &mut Window,
17123 cx: &mut Context<Self>,
17124 ) {
17125 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17126 self.buffer
17127 .read(cx)
17128 .language_settings(cx)
17129 .indent_guides
17130 .enabled
17131 });
17132 self.show_indent_guides = Some(!currently_enabled);
17133 cx.notify();
17134 }
17135
17136 fn should_show_indent_guides(&self) -> Option<bool> {
17137 self.show_indent_guides
17138 }
17139
17140 pub fn toggle_line_numbers(
17141 &mut self,
17142 _: &ToggleLineNumbers,
17143 _: &mut Window,
17144 cx: &mut Context<Self>,
17145 ) {
17146 let mut editor_settings = EditorSettings::get_global(cx).clone();
17147 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17148 EditorSettings::override_global(editor_settings, cx);
17149 }
17150
17151 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17152 if let Some(show_line_numbers) = self.show_line_numbers {
17153 return show_line_numbers;
17154 }
17155 EditorSettings::get_global(cx).gutter.line_numbers
17156 }
17157
17158 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17159 self.use_relative_line_numbers
17160 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17161 }
17162
17163 pub fn toggle_relative_line_numbers(
17164 &mut self,
17165 _: &ToggleRelativeLineNumbers,
17166 _: &mut Window,
17167 cx: &mut Context<Self>,
17168 ) {
17169 let is_relative = self.should_use_relative_line_numbers(cx);
17170 self.set_relative_line_number(Some(!is_relative), cx)
17171 }
17172
17173 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17174 self.use_relative_line_numbers = is_relative;
17175 cx.notify();
17176 }
17177
17178 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17179 self.show_gutter = show_gutter;
17180 cx.notify();
17181 }
17182
17183 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17184 self.show_scrollbars = ScrollbarAxes {
17185 horizontal: show,
17186 vertical: show,
17187 };
17188 cx.notify();
17189 }
17190
17191 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17192 self.show_scrollbars.vertical = show;
17193 cx.notify();
17194 }
17195
17196 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17197 self.show_scrollbars.horizontal = show;
17198 cx.notify();
17199 }
17200
17201 pub fn set_minimap_visibility(
17202 &mut self,
17203 minimap_visibility: MinimapVisibility,
17204 window: &mut Window,
17205 cx: &mut Context<Self>,
17206 ) {
17207 if self.minimap_visibility != minimap_visibility {
17208 if minimap_visibility.visible() && self.minimap.is_none() {
17209 let minimap_settings = EditorSettings::get_global(cx).minimap;
17210 self.minimap =
17211 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17212 }
17213 self.minimap_visibility = minimap_visibility;
17214 cx.notify();
17215 }
17216 }
17217
17218 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17219 self.set_show_scrollbars(false, cx);
17220 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17221 }
17222
17223 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17224 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17225 }
17226
17227 /// Normally the text in full mode and auto height editors is padded on the
17228 /// left side by roughly half a character width for improved hit testing.
17229 ///
17230 /// Use this method to disable this for cases where this is not wanted (e.g.
17231 /// if you want to align the editor text with some other text above or below)
17232 /// or if you want to add this padding to single-line editors.
17233 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17234 self.offset_content = offset_content;
17235 cx.notify();
17236 }
17237
17238 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17239 self.show_line_numbers = Some(show_line_numbers);
17240 cx.notify();
17241 }
17242
17243 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17244 self.disable_expand_excerpt_buttons = true;
17245 cx.notify();
17246 }
17247
17248 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17249 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17250 cx.notify();
17251 }
17252
17253 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17254 self.show_code_actions = Some(show_code_actions);
17255 cx.notify();
17256 }
17257
17258 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17259 self.show_runnables = Some(show_runnables);
17260 cx.notify();
17261 }
17262
17263 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17264 self.show_breakpoints = Some(show_breakpoints);
17265 cx.notify();
17266 }
17267
17268 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17269 if self.display_map.read(cx).masked != masked {
17270 self.display_map.update(cx, |map, _| map.masked = masked);
17271 }
17272 cx.notify()
17273 }
17274
17275 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17276 self.show_wrap_guides = Some(show_wrap_guides);
17277 cx.notify();
17278 }
17279
17280 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17281 self.show_indent_guides = Some(show_indent_guides);
17282 cx.notify();
17283 }
17284
17285 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17286 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17287 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17288 if let Some(dir) = file.abs_path(cx).parent() {
17289 return Some(dir.to_owned());
17290 }
17291 }
17292
17293 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17294 return Some(project_path.path.to_path_buf());
17295 }
17296 }
17297
17298 None
17299 }
17300
17301 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17302 self.active_excerpt(cx)?
17303 .1
17304 .read(cx)
17305 .file()
17306 .and_then(|f| f.as_local())
17307 }
17308
17309 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17310 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17311 let buffer = buffer.read(cx);
17312 if let Some(project_path) = buffer.project_path(cx) {
17313 let project = self.project.as_ref()?.read(cx);
17314 project.absolute_path(&project_path, cx)
17315 } else {
17316 buffer
17317 .file()
17318 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17319 }
17320 })
17321 }
17322
17323 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17324 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17325 let project_path = buffer.read(cx).project_path(cx)?;
17326 let project = self.project.as_ref()?.read(cx);
17327 let entry = project.entry_for_path(&project_path, cx)?;
17328 let path = entry.path.to_path_buf();
17329 Some(path)
17330 })
17331 }
17332
17333 pub fn reveal_in_finder(
17334 &mut self,
17335 _: &RevealInFileManager,
17336 _window: &mut Window,
17337 cx: &mut Context<Self>,
17338 ) {
17339 if let Some(target) = self.target_file(cx) {
17340 cx.reveal_path(&target.abs_path(cx));
17341 }
17342 }
17343
17344 pub fn copy_path(
17345 &mut self,
17346 _: &zed_actions::workspace::CopyPath,
17347 _window: &mut Window,
17348 cx: &mut Context<Self>,
17349 ) {
17350 if let Some(path) = self.target_file_abs_path(cx) {
17351 if let Some(path) = path.to_str() {
17352 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17353 }
17354 }
17355 }
17356
17357 pub fn copy_relative_path(
17358 &mut self,
17359 _: &zed_actions::workspace::CopyRelativePath,
17360 _window: &mut Window,
17361 cx: &mut Context<Self>,
17362 ) {
17363 if let Some(path) = self.target_file_path(cx) {
17364 if let Some(path) = path.to_str() {
17365 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17366 }
17367 }
17368 }
17369
17370 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17371 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17372 buffer.read(cx).project_path(cx)
17373 } else {
17374 None
17375 }
17376 }
17377
17378 // Returns true if the editor handled a go-to-line request
17379 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17380 maybe!({
17381 let breakpoint_store = self.breakpoint_store.as_ref()?;
17382
17383 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17384 else {
17385 self.clear_row_highlights::<ActiveDebugLine>();
17386 return None;
17387 };
17388
17389 let position = active_stack_frame.position;
17390 let buffer_id = position.buffer_id?;
17391 let snapshot = self
17392 .project
17393 .as_ref()?
17394 .read(cx)
17395 .buffer_for_id(buffer_id, cx)?
17396 .read(cx)
17397 .snapshot();
17398
17399 let mut handled = false;
17400 for (id, ExcerptRange { context, .. }) in
17401 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17402 {
17403 if context.start.cmp(&position, &snapshot).is_ge()
17404 || context.end.cmp(&position, &snapshot).is_lt()
17405 {
17406 continue;
17407 }
17408 let snapshot = self.buffer.read(cx).snapshot(cx);
17409 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17410
17411 handled = true;
17412 self.clear_row_highlights::<ActiveDebugLine>();
17413
17414 self.go_to_line::<ActiveDebugLine>(
17415 multibuffer_anchor,
17416 Some(cx.theme().colors().editor_debugger_active_line_background),
17417 window,
17418 cx,
17419 );
17420
17421 cx.notify();
17422 }
17423
17424 handled.then_some(())
17425 })
17426 .is_some()
17427 }
17428
17429 pub fn copy_file_name_without_extension(
17430 &mut self,
17431 _: &CopyFileNameWithoutExtension,
17432 _: &mut Window,
17433 cx: &mut Context<Self>,
17434 ) {
17435 if let Some(file) = self.target_file(cx) {
17436 if let Some(file_stem) = file.path().file_stem() {
17437 if let Some(name) = file_stem.to_str() {
17438 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17439 }
17440 }
17441 }
17442 }
17443
17444 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17445 if let Some(file) = self.target_file(cx) {
17446 if let Some(file_name) = file.path().file_name() {
17447 if let Some(name) = file_name.to_str() {
17448 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17449 }
17450 }
17451 }
17452 }
17453
17454 pub fn toggle_git_blame(
17455 &mut self,
17456 _: &::git::Blame,
17457 window: &mut Window,
17458 cx: &mut Context<Self>,
17459 ) {
17460 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17461
17462 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17463 self.start_git_blame(true, window, cx);
17464 }
17465
17466 cx.notify();
17467 }
17468
17469 pub fn toggle_git_blame_inline(
17470 &mut self,
17471 _: &ToggleGitBlameInline,
17472 window: &mut Window,
17473 cx: &mut Context<Self>,
17474 ) {
17475 self.toggle_git_blame_inline_internal(true, window, cx);
17476 cx.notify();
17477 }
17478
17479 pub fn open_git_blame_commit(
17480 &mut self,
17481 _: &OpenGitBlameCommit,
17482 window: &mut Window,
17483 cx: &mut Context<Self>,
17484 ) {
17485 self.open_git_blame_commit_internal(window, cx);
17486 }
17487
17488 fn open_git_blame_commit_internal(
17489 &mut self,
17490 window: &mut Window,
17491 cx: &mut Context<Self>,
17492 ) -> Option<()> {
17493 let blame = self.blame.as_ref()?;
17494 let snapshot = self.snapshot(window, cx);
17495 let cursor = self.selections.newest::<Point>(cx).head();
17496 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17497 let blame_entry = blame
17498 .update(cx, |blame, cx| {
17499 blame
17500 .blame_for_rows(
17501 &[RowInfo {
17502 buffer_id: Some(buffer.remote_id()),
17503 buffer_row: Some(point.row),
17504 ..Default::default()
17505 }],
17506 cx,
17507 )
17508 .next()
17509 })
17510 .flatten()?;
17511 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17512 let repo = blame.read(cx).repository(cx)?;
17513 let workspace = self.workspace()?.downgrade();
17514 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17515 None
17516 }
17517
17518 pub fn git_blame_inline_enabled(&self) -> bool {
17519 self.git_blame_inline_enabled
17520 }
17521
17522 pub fn toggle_selection_menu(
17523 &mut self,
17524 _: &ToggleSelectionMenu,
17525 _: &mut Window,
17526 cx: &mut Context<Self>,
17527 ) {
17528 self.show_selection_menu = self
17529 .show_selection_menu
17530 .map(|show_selections_menu| !show_selections_menu)
17531 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17532
17533 cx.notify();
17534 }
17535
17536 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17537 self.show_selection_menu
17538 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17539 }
17540
17541 fn start_git_blame(
17542 &mut self,
17543 user_triggered: bool,
17544 window: &mut Window,
17545 cx: &mut Context<Self>,
17546 ) {
17547 if let Some(project) = self.project.as_ref() {
17548 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17549 return;
17550 };
17551
17552 if buffer.read(cx).file().is_none() {
17553 return;
17554 }
17555
17556 let focused = self.focus_handle(cx).contains_focused(window, cx);
17557
17558 let project = project.clone();
17559 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17560 self.blame_subscription =
17561 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17562 self.blame = Some(blame);
17563 }
17564 }
17565
17566 fn toggle_git_blame_inline_internal(
17567 &mut self,
17568 user_triggered: bool,
17569 window: &mut Window,
17570 cx: &mut Context<Self>,
17571 ) {
17572 if self.git_blame_inline_enabled {
17573 self.git_blame_inline_enabled = false;
17574 self.show_git_blame_inline = false;
17575 self.show_git_blame_inline_delay_task.take();
17576 } else {
17577 self.git_blame_inline_enabled = true;
17578 self.start_git_blame_inline(user_triggered, window, cx);
17579 }
17580
17581 cx.notify();
17582 }
17583
17584 fn start_git_blame_inline(
17585 &mut self,
17586 user_triggered: bool,
17587 window: &mut Window,
17588 cx: &mut Context<Self>,
17589 ) {
17590 self.start_git_blame(user_triggered, window, cx);
17591
17592 if ProjectSettings::get_global(cx)
17593 .git
17594 .inline_blame_delay()
17595 .is_some()
17596 {
17597 self.start_inline_blame_timer(window, cx);
17598 } else {
17599 self.show_git_blame_inline = true
17600 }
17601 }
17602
17603 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17604 self.blame.as_ref()
17605 }
17606
17607 pub fn show_git_blame_gutter(&self) -> bool {
17608 self.show_git_blame_gutter
17609 }
17610
17611 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17612 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17613 }
17614
17615 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17616 self.show_git_blame_inline
17617 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17618 && !self.newest_selection_head_on_empty_line(cx)
17619 && self.has_blame_entries(cx)
17620 }
17621
17622 fn has_blame_entries(&self, cx: &App) -> bool {
17623 self.blame()
17624 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17625 }
17626
17627 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17628 let cursor_anchor = self.selections.newest_anchor().head();
17629
17630 let snapshot = self.buffer.read(cx).snapshot(cx);
17631 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17632
17633 snapshot.line_len(buffer_row) == 0
17634 }
17635
17636 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17637 let buffer_and_selection = maybe!({
17638 let selection = self.selections.newest::<Point>(cx);
17639 let selection_range = selection.range();
17640
17641 let multi_buffer = self.buffer().read(cx);
17642 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17643 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17644
17645 let (buffer, range, _) = if selection.reversed {
17646 buffer_ranges.first()
17647 } else {
17648 buffer_ranges.last()
17649 }?;
17650
17651 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17652 ..text::ToPoint::to_point(&range.end, &buffer).row;
17653 Some((
17654 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17655 selection,
17656 ))
17657 });
17658
17659 let Some((buffer, selection)) = buffer_and_selection else {
17660 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17661 };
17662
17663 let Some(project) = self.project.as_ref() else {
17664 return Task::ready(Err(anyhow!("editor does not have project")));
17665 };
17666
17667 project.update(cx, |project, cx| {
17668 project.get_permalink_to_line(&buffer, selection, cx)
17669 })
17670 }
17671
17672 pub fn copy_permalink_to_line(
17673 &mut self,
17674 _: &CopyPermalinkToLine,
17675 window: &mut Window,
17676 cx: &mut Context<Self>,
17677 ) {
17678 let permalink_task = self.get_permalink_to_line(cx);
17679 let workspace = self.workspace();
17680
17681 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17682 Ok(permalink) => {
17683 cx.update(|_, cx| {
17684 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17685 })
17686 .ok();
17687 }
17688 Err(err) => {
17689 let message = format!("Failed to copy permalink: {err}");
17690
17691 anyhow::Result::<()>::Err(err).log_err();
17692
17693 if let Some(workspace) = workspace {
17694 workspace
17695 .update_in(cx, |workspace, _, cx| {
17696 struct CopyPermalinkToLine;
17697
17698 workspace.show_toast(
17699 Toast::new(
17700 NotificationId::unique::<CopyPermalinkToLine>(),
17701 message,
17702 ),
17703 cx,
17704 )
17705 })
17706 .ok();
17707 }
17708 }
17709 })
17710 .detach();
17711 }
17712
17713 pub fn copy_file_location(
17714 &mut self,
17715 _: &CopyFileLocation,
17716 _: &mut Window,
17717 cx: &mut Context<Self>,
17718 ) {
17719 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17720 if let Some(file) = self.target_file(cx) {
17721 if let Some(path) = file.path().to_str() {
17722 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17723 }
17724 }
17725 }
17726
17727 pub fn open_permalink_to_line(
17728 &mut self,
17729 _: &OpenPermalinkToLine,
17730 window: &mut Window,
17731 cx: &mut Context<Self>,
17732 ) {
17733 let permalink_task = self.get_permalink_to_line(cx);
17734 let workspace = self.workspace();
17735
17736 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17737 Ok(permalink) => {
17738 cx.update(|_, cx| {
17739 cx.open_url(permalink.as_ref());
17740 })
17741 .ok();
17742 }
17743 Err(err) => {
17744 let message = format!("Failed to open permalink: {err}");
17745
17746 anyhow::Result::<()>::Err(err).log_err();
17747
17748 if let Some(workspace) = workspace {
17749 workspace
17750 .update(cx, |workspace, cx| {
17751 struct OpenPermalinkToLine;
17752
17753 workspace.show_toast(
17754 Toast::new(
17755 NotificationId::unique::<OpenPermalinkToLine>(),
17756 message,
17757 ),
17758 cx,
17759 )
17760 })
17761 .ok();
17762 }
17763 }
17764 })
17765 .detach();
17766 }
17767
17768 pub fn insert_uuid_v4(
17769 &mut self,
17770 _: &InsertUuidV4,
17771 window: &mut Window,
17772 cx: &mut Context<Self>,
17773 ) {
17774 self.insert_uuid(UuidVersion::V4, window, cx);
17775 }
17776
17777 pub fn insert_uuid_v7(
17778 &mut self,
17779 _: &InsertUuidV7,
17780 window: &mut Window,
17781 cx: &mut Context<Self>,
17782 ) {
17783 self.insert_uuid(UuidVersion::V7, window, cx);
17784 }
17785
17786 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17787 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17788 self.transact(window, cx, |this, window, cx| {
17789 let edits = this
17790 .selections
17791 .all::<Point>(cx)
17792 .into_iter()
17793 .map(|selection| {
17794 let uuid = match version {
17795 UuidVersion::V4 => uuid::Uuid::new_v4(),
17796 UuidVersion::V7 => uuid::Uuid::now_v7(),
17797 };
17798
17799 (selection.range(), uuid.to_string())
17800 });
17801 this.edit(edits, cx);
17802 this.refresh_inline_completion(true, false, window, cx);
17803 });
17804 }
17805
17806 pub fn open_selections_in_multibuffer(
17807 &mut self,
17808 _: &OpenSelectionsInMultibuffer,
17809 window: &mut Window,
17810 cx: &mut Context<Self>,
17811 ) {
17812 let multibuffer = self.buffer.read(cx);
17813
17814 let Some(buffer) = multibuffer.as_singleton() else {
17815 return;
17816 };
17817
17818 let Some(workspace) = self.workspace() else {
17819 return;
17820 };
17821
17822 let locations = self
17823 .selections
17824 .disjoint_anchors()
17825 .iter()
17826 .map(|selection| {
17827 let range = if selection.reversed {
17828 selection.end.text_anchor..selection.start.text_anchor
17829 } else {
17830 selection.start.text_anchor..selection.end.text_anchor
17831 };
17832 Location {
17833 buffer: buffer.clone(),
17834 range,
17835 }
17836 })
17837 .collect::<Vec<_>>();
17838
17839 let title = multibuffer.title(cx).to_string();
17840
17841 cx.spawn_in(window, async move |_, cx| {
17842 workspace.update_in(cx, |workspace, window, cx| {
17843 Self::open_locations_in_multibuffer(
17844 workspace,
17845 locations,
17846 format!("Selections for '{title}'"),
17847 false,
17848 MultibufferSelectionMode::All,
17849 window,
17850 cx,
17851 );
17852 })
17853 })
17854 .detach();
17855 }
17856
17857 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17858 /// last highlight added will be used.
17859 ///
17860 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17861 pub fn highlight_rows<T: 'static>(
17862 &mut self,
17863 range: Range<Anchor>,
17864 color: Hsla,
17865 options: RowHighlightOptions,
17866 cx: &mut Context<Self>,
17867 ) {
17868 let snapshot = self.buffer().read(cx).snapshot(cx);
17869 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17870 let ix = row_highlights.binary_search_by(|highlight| {
17871 Ordering::Equal
17872 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17873 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17874 });
17875
17876 if let Err(mut ix) = ix {
17877 let index = post_inc(&mut self.highlight_order);
17878
17879 // If this range intersects with the preceding highlight, then merge it with
17880 // the preceding highlight. Otherwise insert a new highlight.
17881 let mut merged = false;
17882 if ix > 0 {
17883 let prev_highlight = &mut row_highlights[ix - 1];
17884 if prev_highlight
17885 .range
17886 .end
17887 .cmp(&range.start, &snapshot)
17888 .is_ge()
17889 {
17890 ix -= 1;
17891 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17892 prev_highlight.range.end = range.end;
17893 }
17894 merged = true;
17895 prev_highlight.index = index;
17896 prev_highlight.color = color;
17897 prev_highlight.options = options;
17898 }
17899 }
17900
17901 if !merged {
17902 row_highlights.insert(
17903 ix,
17904 RowHighlight {
17905 range: range.clone(),
17906 index,
17907 color,
17908 options,
17909 type_id: TypeId::of::<T>(),
17910 },
17911 );
17912 }
17913
17914 // If any of the following highlights intersect with this one, merge them.
17915 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17916 let highlight = &row_highlights[ix];
17917 if next_highlight
17918 .range
17919 .start
17920 .cmp(&highlight.range.end, &snapshot)
17921 .is_le()
17922 {
17923 if next_highlight
17924 .range
17925 .end
17926 .cmp(&highlight.range.end, &snapshot)
17927 .is_gt()
17928 {
17929 row_highlights[ix].range.end = next_highlight.range.end;
17930 }
17931 row_highlights.remove(ix + 1);
17932 } else {
17933 break;
17934 }
17935 }
17936 }
17937 }
17938
17939 /// Remove any highlighted row ranges of the given type that intersect the
17940 /// given ranges.
17941 pub fn remove_highlighted_rows<T: 'static>(
17942 &mut self,
17943 ranges_to_remove: Vec<Range<Anchor>>,
17944 cx: &mut Context<Self>,
17945 ) {
17946 let snapshot = self.buffer().read(cx).snapshot(cx);
17947 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17948 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17949 row_highlights.retain(|highlight| {
17950 while let Some(range_to_remove) = ranges_to_remove.peek() {
17951 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17952 Ordering::Less | Ordering::Equal => {
17953 ranges_to_remove.next();
17954 }
17955 Ordering::Greater => {
17956 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17957 Ordering::Less | Ordering::Equal => {
17958 return false;
17959 }
17960 Ordering::Greater => break,
17961 }
17962 }
17963 }
17964 }
17965
17966 true
17967 })
17968 }
17969
17970 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17971 pub fn clear_row_highlights<T: 'static>(&mut self) {
17972 self.highlighted_rows.remove(&TypeId::of::<T>());
17973 }
17974
17975 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17976 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17977 self.highlighted_rows
17978 .get(&TypeId::of::<T>())
17979 .map_or(&[] as &[_], |vec| vec.as_slice())
17980 .iter()
17981 .map(|highlight| (highlight.range.clone(), highlight.color))
17982 }
17983
17984 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17985 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17986 /// Allows to ignore certain kinds of highlights.
17987 pub fn highlighted_display_rows(
17988 &self,
17989 window: &mut Window,
17990 cx: &mut App,
17991 ) -> BTreeMap<DisplayRow, LineHighlight> {
17992 let snapshot = self.snapshot(window, cx);
17993 let mut used_highlight_orders = HashMap::default();
17994 self.highlighted_rows
17995 .iter()
17996 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17997 .fold(
17998 BTreeMap::<DisplayRow, LineHighlight>::new(),
17999 |mut unique_rows, highlight| {
18000 let start = highlight.range.start.to_display_point(&snapshot);
18001 let end = highlight.range.end.to_display_point(&snapshot);
18002 let start_row = start.row().0;
18003 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18004 && end.column() == 0
18005 {
18006 end.row().0.saturating_sub(1)
18007 } else {
18008 end.row().0
18009 };
18010 for row in start_row..=end_row {
18011 let used_index =
18012 used_highlight_orders.entry(row).or_insert(highlight.index);
18013 if highlight.index >= *used_index {
18014 *used_index = highlight.index;
18015 unique_rows.insert(
18016 DisplayRow(row),
18017 LineHighlight {
18018 include_gutter: highlight.options.include_gutter,
18019 border: None,
18020 background: highlight.color.into(),
18021 type_id: Some(highlight.type_id),
18022 },
18023 );
18024 }
18025 }
18026 unique_rows
18027 },
18028 )
18029 }
18030
18031 pub fn highlighted_display_row_for_autoscroll(
18032 &self,
18033 snapshot: &DisplaySnapshot,
18034 ) -> Option<DisplayRow> {
18035 self.highlighted_rows
18036 .values()
18037 .flat_map(|highlighted_rows| highlighted_rows.iter())
18038 .filter_map(|highlight| {
18039 if highlight.options.autoscroll {
18040 Some(highlight.range.start.to_display_point(snapshot).row())
18041 } else {
18042 None
18043 }
18044 })
18045 .min()
18046 }
18047
18048 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18049 self.highlight_background::<SearchWithinRange>(
18050 ranges,
18051 |colors| colors.editor_document_highlight_read_background,
18052 cx,
18053 )
18054 }
18055
18056 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18057 self.breadcrumb_header = Some(new_header);
18058 }
18059
18060 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18061 self.clear_background_highlights::<SearchWithinRange>(cx);
18062 }
18063
18064 pub fn highlight_background<T: 'static>(
18065 &mut self,
18066 ranges: &[Range<Anchor>],
18067 color_fetcher: fn(&ThemeColors) -> Hsla,
18068 cx: &mut Context<Self>,
18069 ) {
18070 self.background_highlights
18071 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18072 self.scrollbar_marker_state.dirty = true;
18073 cx.notify();
18074 }
18075
18076 pub fn clear_background_highlights<T: 'static>(
18077 &mut self,
18078 cx: &mut Context<Self>,
18079 ) -> Option<BackgroundHighlight> {
18080 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
18081 if !text_highlights.1.is_empty() {
18082 self.scrollbar_marker_state.dirty = true;
18083 cx.notify();
18084 }
18085 Some(text_highlights)
18086 }
18087
18088 pub fn highlight_gutter<T: 'static>(
18089 &mut self,
18090 ranges: &[Range<Anchor>],
18091 color_fetcher: fn(&App) -> Hsla,
18092 cx: &mut Context<Self>,
18093 ) {
18094 self.gutter_highlights
18095 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18096 cx.notify();
18097 }
18098
18099 pub fn clear_gutter_highlights<T: 'static>(
18100 &mut self,
18101 cx: &mut Context<Self>,
18102 ) -> Option<GutterHighlight> {
18103 cx.notify();
18104 self.gutter_highlights.remove(&TypeId::of::<T>())
18105 }
18106
18107 #[cfg(feature = "test-support")]
18108 pub fn all_text_background_highlights(
18109 &self,
18110 window: &mut Window,
18111 cx: &mut Context<Self>,
18112 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18113 let snapshot = self.snapshot(window, cx);
18114 let buffer = &snapshot.buffer_snapshot;
18115 let start = buffer.anchor_before(0);
18116 let end = buffer.anchor_after(buffer.len());
18117 let theme = cx.theme().colors();
18118 self.background_highlights_in_range(start..end, &snapshot, theme)
18119 }
18120
18121 #[cfg(feature = "test-support")]
18122 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18123 let snapshot = self.buffer().read(cx).snapshot(cx);
18124
18125 let highlights = self
18126 .background_highlights
18127 .get(&TypeId::of::<items::BufferSearchHighlights>());
18128
18129 if let Some((_color, ranges)) = highlights {
18130 ranges
18131 .iter()
18132 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18133 .collect_vec()
18134 } else {
18135 vec![]
18136 }
18137 }
18138
18139 fn document_highlights_for_position<'a>(
18140 &'a self,
18141 position: Anchor,
18142 buffer: &'a MultiBufferSnapshot,
18143 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18144 let read_highlights = self
18145 .background_highlights
18146 .get(&TypeId::of::<DocumentHighlightRead>())
18147 .map(|h| &h.1);
18148 let write_highlights = self
18149 .background_highlights
18150 .get(&TypeId::of::<DocumentHighlightWrite>())
18151 .map(|h| &h.1);
18152 let left_position = position.bias_left(buffer);
18153 let right_position = position.bias_right(buffer);
18154 read_highlights
18155 .into_iter()
18156 .chain(write_highlights)
18157 .flat_map(move |ranges| {
18158 let start_ix = match ranges.binary_search_by(|probe| {
18159 let cmp = probe.end.cmp(&left_position, buffer);
18160 if cmp.is_ge() {
18161 Ordering::Greater
18162 } else {
18163 Ordering::Less
18164 }
18165 }) {
18166 Ok(i) | Err(i) => i,
18167 };
18168
18169 ranges[start_ix..]
18170 .iter()
18171 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
18172 })
18173 }
18174
18175 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18176 self.background_highlights
18177 .get(&TypeId::of::<T>())
18178 .map_or(false, |(_, highlights)| !highlights.is_empty())
18179 }
18180
18181 pub fn background_highlights_in_range(
18182 &self,
18183 search_range: Range<Anchor>,
18184 display_snapshot: &DisplaySnapshot,
18185 theme: &ThemeColors,
18186 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18187 let mut results = Vec::new();
18188 for (color_fetcher, ranges) in self.background_highlights.values() {
18189 let color = color_fetcher(theme);
18190 let start_ix = match ranges.binary_search_by(|probe| {
18191 let cmp = probe
18192 .end
18193 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18194 if cmp.is_gt() {
18195 Ordering::Greater
18196 } else {
18197 Ordering::Less
18198 }
18199 }) {
18200 Ok(i) | Err(i) => i,
18201 };
18202 for range in &ranges[start_ix..] {
18203 if range
18204 .start
18205 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18206 .is_ge()
18207 {
18208 break;
18209 }
18210
18211 let start = range.start.to_display_point(display_snapshot);
18212 let end = range.end.to_display_point(display_snapshot);
18213 results.push((start..end, color))
18214 }
18215 }
18216 results
18217 }
18218
18219 pub fn background_highlight_row_ranges<T: 'static>(
18220 &self,
18221 search_range: Range<Anchor>,
18222 display_snapshot: &DisplaySnapshot,
18223 count: usize,
18224 ) -> Vec<RangeInclusive<DisplayPoint>> {
18225 let mut results = Vec::new();
18226 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
18227 return vec![];
18228 };
18229
18230 let start_ix = match ranges.binary_search_by(|probe| {
18231 let cmp = probe
18232 .end
18233 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18234 if cmp.is_gt() {
18235 Ordering::Greater
18236 } else {
18237 Ordering::Less
18238 }
18239 }) {
18240 Ok(i) | Err(i) => i,
18241 };
18242 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18243 if let (Some(start_display), Some(end_display)) = (start, end) {
18244 results.push(
18245 start_display.to_display_point(display_snapshot)
18246 ..=end_display.to_display_point(display_snapshot),
18247 );
18248 }
18249 };
18250 let mut start_row: Option<Point> = None;
18251 let mut end_row: Option<Point> = None;
18252 if ranges.len() > count {
18253 return Vec::new();
18254 }
18255 for range in &ranges[start_ix..] {
18256 if range
18257 .start
18258 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18259 .is_ge()
18260 {
18261 break;
18262 }
18263 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18264 if let Some(current_row) = &end_row {
18265 if end.row == current_row.row {
18266 continue;
18267 }
18268 }
18269 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18270 if start_row.is_none() {
18271 assert_eq!(end_row, None);
18272 start_row = Some(start);
18273 end_row = Some(end);
18274 continue;
18275 }
18276 if let Some(current_end) = end_row.as_mut() {
18277 if start.row > current_end.row + 1 {
18278 push_region(start_row, end_row);
18279 start_row = Some(start);
18280 end_row = Some(end);
18281 } else {
18282 // Merge two hunks.
18283 *current_end = end;
18284 }
18285 } else {
18286 unreachable!();
18287 }
18288 }
18289 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18290 push_region(start_row, end_row);
18291 results
18292 }
18293
18294 pub fn gutter_highlights_in_range(
18295 &self,
18296 search_range: Range<Anchor>,
18297 display_snapshot: &DisplaySnapshot,
18298 cx: &App,
18299 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18300 let mut results = Vec::new();
18301 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18302 let color = color_fetcher(cx);
18303 let start_ix = match ranges.binary_search_by(|probe| {
18304 let cmp = probe
18305 .end
18306 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18307 if cmp.is_gt() {
18308 Ordering::Greater
18309 } else {
18310 Ordering::Less
18311 }
18312 }) {
18313 Ok(i) | Err(i) => i,
18314 };
18315 for range in &ranges[start_ix..] {
18316 if range
18317 .start
18318 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18319 .is_ge()
18320 {
18321 break;
18322 }
18323
18324 let start = range.start.to_display_point(display_snapshot);
18325 let end = range.end.to_display_point(display_snapshot);
18326 results.push((start..end, color))
18327 }
18328 }
18329 results
18330 }
18331
18332 /// Get the text ranges corresponding to the redaction query
18333 pub fn redacted_ranges(
18334 &self,
18335 search_range: Range<Anchor>,
18336 display_snapshot: &DisplaySnapshot,
18337 cx: &App,
18338 ) -> Vec<Range<DisplayPoint>> {
18339 display_snapshot
18340 .buffer_snapshot
18341 .redacted_ranges(search_range, |file| {
18342 if let Some(file) = file {
18343 file.is_private()
18344 && EditorSettings::get(
18345 Some(SettingsLocation {
18346 worktree_id: file.worktree_id(cx),
18347 path: file.path().as_ref(),
18348 }),
18349 cx,
18350 )
18351 .redact_private_values
18352 } else {
18353 false
18354 }
18355 })
18356 .map(|range| {
18357 range.start.to_display_point(display_snapshot)
18358 ..range.end.to_display_point(display_snapshot)
18359 })
18360 .collect()
18361 }
18362
18363 pub fn highlight_text<T: 'static>(
18364 &mut self,
18365 ranges: Vec<Range<Anchor>>,
18366 style: HighlightStyle,
18367 cx: &mut Context<Self>,
18368 ) {
18369 self.display_map.update(cx, |map, _| {
18370 map.highlight_text(TypeId::of::<T>(), ranges, style)
18371 });
18372 cx.notify();
18373 }
18374
18375 pub(crate) fn highlight_inlays<T: 'static>(
18376 &mut self,
18377 highlights: Vec<InlayHighlight>,
18378 style: HighlightStyle,
18379 cx: &mut Context<Self>,
18380 ) {
18381 self.display_map.update(cx, |map, _| {
18382 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18383 });
18384 cx.notify();
18385 }
18386
18387 pub fn text_highlights<'a, T: 'static>(
18388 &'a self,
18389 cx: &'a App,
18390 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18391 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18392 }
18393
18394 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18395 let cleared = self
18396 .display_map
18397 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18398 if cleared {
18399 cx.notify();
18400 }
18401 }
18402
18403 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18404 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18405 && self.focus_handle.is_focused(window)
18406 }
18407
18408 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18409 self.show_cursor_when_unfocused = is_enabled;
18410 cx.notify();
18411 }
18412
18413 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18414 cx.notify();
18415 }
18416
18417 fn on_debug_session_event(
18418 &mut self,
18419 _session: Entity<Session>,
18420 event: &SessionEvent,
18421 cx: &mut Context<Self>,
18422 ) {
18423 match event {
18424 SessionEvent::InvalidateInlineValue => {
18425 self.refresh_inline_values(cx);
18426 }
18427 _ => {}
18428 }
18429 }
18430
18431 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18432 let Some(project) = self.project.clone() else {
18433 return;
18434 };
18435
18436 if !self.inline_value_cache.enabled {
18437 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18438 self.splice_inlays(&inlays, Vec::new(), cx);
18439 return;
18440 }
18441
18442 let current_execution_position = self
18443 .highlighted_rows
18444 .get(&TypeId::of::<ActiveDebugLine>())
18445 .and_then(|lines| lines.last().map(|line| line.range.start));
18446
18447 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18448 let inline_values = editor
18449 .update(cx, |editor, cx| {
18450 let Some(current_execution_position) = current_execution_position else {
18451 return Some(Task::ready(Ok(Vec::new())));
18452 };
18453
18454 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18455 let snapshot = buffer.snapshot(cx);
18456
18457 let excerpt = snapshot.excerpt_containing(
18458 current_execution_position..current_execution_position,
18459 )?;
18460
18461 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18462 })?;
18463
18464 let range =
18465 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18466
18467 project.inline_values(buffer, range, cx)
18468 })
18469 .ok()
18470 .flatten()?
18471 .await
18472 .context("refreshing debugger inlays")
18473 .log_err()?;
18474
18475 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18476
18477 for (buffer_id, inline_value) in inline_values
18478 .into_iter()
18479 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18480 {
18481 buffer_inline_values
18482 .entry(buffer_id)
18483 .or_default()
18484 .push(inline_value);
18485 }
18486
18487 editor
18488 .update(cx, |editor, cx| {
18489 let snapshot = editor.buffer.read(cx).snapshot(cx);
18490 let mut new_inlays = Vec::default();
18491
18492 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18493 let buffer_id = buffer_snapshot.remote_id();
18494 buffer_inline_values
18495 .get(&buffer_id)
18496 .into_iter()
18497 .flatten()
18498 .for_each(|hint| {
18499 let inlay = Inlay::debugger_hint(
18500 post_inc(&mut editor.next_inlay_id),
18501 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18502 hint.text(),
18503 );
18504
18505 new_inlays.push(inlay);
18506 });
18507 }
18508
18509 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18510 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18511
18512 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18513 })
18514 .ok()?;
18515 Some(())
18516 });
18517 }
18518
18519 fn on_buffer_event(
18520 &mut self,
18521 multibuffer: &Entity<MultiBuffer>,
18522 event: &multi_buffer::Event,
18523 window: &mut Window,
18524 cx: &mut Context<Self>,
18525 ) {
18526 match event {
18527 multi_buffer::Event::Edited {
18528 singleton_buffer_edited,
18529 edited_buffer: buffer_edited,
18530 } => {
18531 self.scrollbar_marker_state.dirty = true;
18532 self.active_indent_guides_state.dirty = true;
18533 self.refresh_active_diagnostics(cx);
18534 self.refresh_code_actions(window, cx);
18535 self.refresh_selected_text_highlights(true, window, cx);
18536 refresh_matching_bracket_highlights(self, window, cx);
18537 if self.has_active_inline_completion() {
18538 self.update_visible_inline_completion(window, cx);
18539 }
18540 if let Some(buffer) = buffer_edited {
18541 let buffer_id = buffer.read(cx).remote_id();
18542 if !self.registered_buffers.contains_key(&buffer_id) {
18543 if let Some(project) = self.project.as_ref() {
18544 project.update(cx, |project, cx| {
18545 self.registered_buffers.insert(
18546 buffer_id,
18547 project.register_buffer_with_language_servers(&buffer, cx),
18548 );
18549 })
18550 }
18551 }
18552 }
18553 cx.emit(EditorEvent::BufferEdited);
18554 cx.emit(SearchEvent::MatchesInvalidated);
18555 if *singleton_buffer_edited {
18556 if let Some(project) = &self.project {
18557 #[allow(clippy::mutable_key_type)]
18558 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18559 multibuffer
18560 .all_buffers()
18561 .into_iter()
18562 .filter_map(|buffer| {
18563 buffer.update(cx, |buffer, cx| {
18564 let language = buffer.language()?;
18565 let should_discard = project.update(cx, |project, cx| {
18566 project.is_local()
18567 && !project.has_language_servers_for(buffer, cx)
18568 });
18569 should_discard.not().then_some(language.clone())
18570 })
18571 })
18572 .collect::<HashSet<_>>()
18573 });
18574 if !languages_affected.is_empty() {
18575 self.refresh_inlay_hints(
18576 InlayHintRefreshReason::BufferEdited(languages_affected),
18577 cx,
18578 );
18579 }
18580 }
18581 }
18582
18583 let Some(project) = &self.project else { return };
18584 let (telemetry, is_via_ssh) = {
18585 let project = project.read(cx);
18586 let telemetry = project.client().telemetry().clone();
18587 let is_via_ssh = project.is_via_ssh();
18588 (telemetry, is_via_ssh)
18589 };
18590 refresh_linked_ranges(self, window, cx);
18591 telemetry.log_edit_event("editor", is_via_ssh);
18592 }
18593 multi_buffer::Event::ExcerptsAdded {
18594 buffer,
18595 predecessor,
18596 excerpts,
18597 } => {
18598 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18599 let buffer_id = buffer.read(cx).remote_id();
18600 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18601 if let Some(project) = &self.project {
18602 update_uncommitted_diff_for_buffer(
18603 cx.entity(),
18604 project,
18605 [buffer.clone()],
18606 self.buffer.clone(),
18607 cx,
18608 )
18609 .detach();
18610 }
18611 }
18612 cx.emit(EditorEvent::ExcerptsAdded {
18613 buffer: buffer.clone(),
18614 predecessor: *predecessor,
18615 excerpts: excerpts.clone(),
18616 });
18617 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18618 }
18619 multi_buffer::Event::ExcerptsRemoved {
18620 ids,
18621 removed_buffer_ids,
18622 } => {
18623 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18624 let buffer = self.buffer.read(cx);
18625 self.registered_buffers
18626 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18627 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18628 cx.emit(EditorEvent::ExcerptsRemoved {
18629 ids: ids.clone(),
18630 removed_buffer_ids: removed_buffer_ids.clone(),
18631 })
18632 }
18633 multi_buffer::Event::ExcerptsEdited {
18634 excerpt_ids,
18635 buffer_ids,
18636 } => {
18637 self.display_map.update(cx, |map, cx| {
18638 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18639 });
18640 cx.emit(EditorEvent::ExcerptsEdited {
18641 ids: excerpt_ids.clone(),
18642 })
18643 }
18644 multi_buffer::Event::ExcerptsExpanded { ids } => {
18645 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18646 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18647 }
18648 multi_buffer::Event::Reparsed(buffer_id) => {
18649 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18650 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18651
18652 cx.emit(EditorEvent::Reparsed(*buffer_id));
18653 }
18654 multi_buffer::Event::DiffHunksToggled => {
18655 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18656 }
18657 multi_buffer::Event::LanguageChanged(buffer_id) => {
18658 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18659 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18660 cx.emit(EditorEvent::Reparsed(*buffer_id));
18661 cx.notify();
18662 }
18663 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18664 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18665 multi_buffer::Event::FileHandleChanged
18666 | multi_buffer::Event::Reloaded
18667 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18668 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18669 multi_buffer::Event::DiagnosticsUpdated => {
18670 self.refresh_active_diagnostics(cx);
18671 self.refresh_inline_diagnostics(true, window, cx);
18672 self.scrollbar_marker_state.dirty = true;
18673 cx.notify();
18674 }
18675 _ => {}
18676 };
18677 }
18678
18679 pub fn start_temporary_diff_override(&mut self) {
18680 self.load_diff_task.take();
18681 self.temporary_diff_override = true;
18682 }
18683
18684 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18685 self.temporary_diff_override = false;
18686 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18687 self.buffer.update(cx, |buffer, cx| {
18688 buffer.set_all_diff_hunks_collapsed(cx);
18689 });
18690
18691 if let Some(project) = self.project.clone() {
18692 self.load_diff_task = Some(
18693 update_uncommitted_diff_for_buffer(
18694 cx.entity(),
18695 &project,
18696 self.buffer.read(cx).all_buffers(),
18697 self.buffer.clone(),
18698 cx,
18699 )
18700 .shared(),
18701 );
18702 }
18703 }
18704
18705 fn on_display_map_changed(
18706 &mut self,
18707 _: Entity<DisplayMap>,
18708 _: &mut Window,
18709 cx: &mut Context<Self>,
18710 ) {
18711 cx.notify();
18712 }
18713
18714 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18715 let new_severity = if self.diagnostics_enabled() {
18716 EditorSettings::get_global(cx)
18717 .diagnostics_max_severity
18718 .unwrap_or(DiagnosticSeverity::Hint)
18719 } else {
18720 DiagnosticSeverity::Off
18721 };
18722 self.set_max_diagnostics_severity(new_severity, cx);
18723 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18724 self.update_edit_prediction_settings(cx);
18725 self.refresh_inline_completion(true, false, window, cx);
18726 self.refresh_inlay_hints(
18727 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18728 self.selections.newest_anchor().head(),
18729 &self.buffer.read(cx).snapshot(cx),
18730 cx,
18731 )),
18732 cx,
18733 );
18734
18735 let old_cursor_shape = self.cursor_shape;
18736
18737 {
18738 let editor_settings = EditorSettings::get_global(cx);
18739 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18740 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18741 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18742 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18743 }
18744
18745 if old_cursor_shape != self.cursor_shape {
18746 cx.emit(EditorEvent::CursorShapeChanged);
18747 }
18748
18749 let project_settings = ProjectSettings::get_global(cx);
18750 self.serialize_dirty_buffers =
18751 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18752
18753 if self.mode.is_full() {
18754 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18755 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18756 if self.show_inline_diagnostics != show_inline_diagnostics {
18757 self.show_inline_diagnostics = show_inline_diagnostics;
18758 self.refresh_inline_diagnostics(false, window, cx);
18759 }
18760
18761 if self.git_blame_inline_enabled != inline_blame_enabled {
18762 self.toggle_git_blame_inline_internal(false, window, cx);
18763 }
18764
18765 let minimap_settings = EditorSettings::get_global(cx).minimap;
18766 if self.minimap_visibility != MinimapVisibility::Disabled {
18767 if self.minimap_visibility.settings_visibility()
18768 != minimap_settings.minimap_enabled()
18769 {
18770 self.set_minimap_visibility(
18771 MinimapVisibility::for_mode(self.mode(), cx),
18772 window,
18773 cx,
18774 );
18775 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18776 minimap_entity.update(cx, |minimap_editor, cx| {
18777 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18778 })
18779 }
18780 }
18781 }
18782
18783 cx.notify();
18784 }
18785
18786 pub fn set_searchable(&mut self, searchable: bool) {
18787 self.searchable = searchable;
18788 }
18789
18790 pub fn searchable(&self) -> bool {
18791 self.searchable
18792 }
18793
18794 fn open_proposed_changes_editor(
18795 &mut self,
18796 _: &OpenProposedChangesEditor,
18797 window: &mut Window,
18798 cx: &mut Context<Self>,
18799 ) {
18800 let Some(workspace) = self.workspace() else {
18801 cx.propagate();
18802 return;
18803 };
18804
18805 let selections = self.selections.all::<usize>(cx);
18806 let multi_buffer = self.buffer.read(cx);
18807 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18808 let mut new_selections_by_buffer = HashMap::default();
18809 for selection in selections {
18810 for (buffer, range, _) in
18811 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18812 {
18813 let mut range = range.to_point(buffer);
18814 range.start.column = 0;
18815 range.end.column = buffer.line_len(range.end.row);
18816 new_selections_by_buffer
18817 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18818 .or_insert(Vec::new())
18819 .push(range)
18820 }
18821 }
18822
18823 let proposed_changes_buffers = new_selections_by_buffer
18824 .into_iter()
18825 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18826 .collect::<Vec<_>>();
18827 let proposed_changes_editor = cx.new(|cx| {
18828 ProposedChangesEditor::new(
18829 "Proposed changes",
18830 proposed_changes_buffers,
18831 self.project.clone(),
18832 window,
18833 cx,
18834 )
18835 });
18836
18837 window.defer(cx, move |window, cx| {
18838 workspace.update(cx, |workspace, cx| {
18839 workspace.active_pane().update(cx, |pane, cx| {
18840 pane.add_item(
18841 Box::new(proposed_changes_editor),
18842 true,
18843 true,
18844 None,
18845 window,
18846 cx,
18847 );
18848 });
18849 });
18850 });
18851 }
18852
18853 pub fn open_excerpts_in_split(
18854 &mut self,
18855 _: &OpenExcerptsSplit,
18856 window: &mut Window,
18857 cx: &mut Context<Self>,
18858 ) {
18859 self.open_excerpts_common(None, true, window, cx)
18860 }
18861
18862 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18863 self.open_excerpts_common(None, false, window, cx)
18864 }
18865
18866 fn open_excerpts_common(
18867 &mut self,
18868 jump_data: Option<JumpData>,
18869 split: bool,
18870 window: &mut Window,
18871 cx: &mut Context<Self>,
18872 ) {
18873 let Some(workspace) = self.workspace() else {
18874 cx.propagate();
18875 return;
18876 };
18877
18878 if self.buffer.read(cx).is_singleton() {
18879 cx.propagate();
18880 return;
18881 }
18882
18883 let mut new_selections_by_buffer = HashMap::default();
18884 match &jump_data {
18885 Some(JumpData::MultiBufferPoint {
18886 excerpt_id,
18887 position,
18888 anchor,
18889 line_offset_from_top,
18890 }) => {
18891 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18892 if let Some(buffer) = multi_buffer_snapshot
18893 .buffer_id_for_excerpt(*excerpt_id)
18894 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18895 {
18896 let buffer_snapshot = buffer.read(cx).snapshot();
18897 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18898 language::ToPoint::to_point(anchor, &buffer_snapshot)
18899 } else {
18900 buffer_snapshot.clip_point(*position, Bias::Left)
18901 };
18902 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18903 new_selections_by_buffer.insert(
18904 buffer,
18905 (
18906 vec![jump_to_offset..jump_to_offset],
18907 Some(*line_offset_from_top),
18908 ),
18909 );
18910 }
18911 }
18912 Some(JumpData::MultiBufferRow {
18913 row,
18914 line_offset_from_top,
18915 }) => {
18916 let point = MultiBufferPoint::new(row.0, 0);
18917 if let Some((buffer, buffer_point, _)) =
18918 self.buffer.read(cx).point_to_buffer_point(point, cx)
18919 {
18920 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18921 new_selections_by_buffer
18922 .entry(buffer)
18923 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18924 .0
18925 .push(buffer_offset..buffer_offset)
18926 }
18927 }
18928 None => {
18929 let selections = self.selections.all::<usize>(cx);
18930 let multi_buffer = self.buffer.read(cx);
18931 for selection in selections {
18932 for (snapshot, range, _, anchor) in multi_buffer
18933 .snapshot(cx)
18934 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18935 {
18936 if let Some(anchor) = anchor {
18937 // selection is in a deleted hunk
18938 let Some(buffer_id) = anchor.buffer_id else {
18939 continue;
18940 };
18941 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18942 continue;
18943 };
18944 let offset = text::ToOffset::to_offset(
18945 &anchor.text_anchor,
18946 &buffer_handle.read(cx).snapshot(),
18947 );
18948 let range = offset..offset;
18949 new_selections_by_buffer
18950 .entry(buffer_handle)
18951 .or_insert((Vec::new(), None))
18952 .0
18953 .push(range)
18954 } else {
18955 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18956 else {
18957 continue;
18958 };
18959 new_selections_by_buffer
18960 .entry(buffer_handle)
18961 .or_insert((Vec::new(), None))
18962 .0
18963 .push(range)
18964 }
18965 }
18966 }
18967 }
18968 }
18969
18970 new_selections_by_buffer
18971 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18972
18973 if new_selections_by_buffer.is_empty() {
18974 return;
18975 }
18976
18977 // We defer the pane interaction because we ourselves are a workspace item
18978 // and activating a new item causes the pane to call a method on us reentrantly,
18979 // which panics if we're on the stack.
18980 window.defer(cx, move |window, cx| {
18981 workspace.update(cx, |workspace, cx| {
18982 let pane = if split {
18983 workspace.adjacent_pane(window, cx)
18984 } else {
18985 workspace.active_pane().clone()
18986 };
18987
18988 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18989 let editor = buffer
18990 .read(cx)
18991 .file()
18992 .is_none()
18993 .then(|| {
18994 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18995 // so `workspace.open_project_item` will never find them, always opening a new editor.
18996 // Instead, we try to activate the existing editor in the pane first.
18997 let (editor, pane_item_index) =
18998 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18999 let editor = item.downcast::<Editor>()?;
19000 let singleton_buffer =
19001 editor.read(cx).buffer().read(cx).as_singleton()?;
19002 if singleton_buffer == buffer {
19003 Some((editor, i))
19004 } else {
19005 None
19006 }
19007 })?;
19008 pane.update(cx, |pane, cx| {
19009 pane.activate_item(pane_item_index, true, true, window, cx)
19010 });
19011 Some(editor)
19012 })
19013 .flatten()
19014 .unwrap_or_else(|| {
19015 workspace.open_project_item::<Self>(
19016 pane.clone(),
19017 buffer,
19018 true,
19019 true,
19020 window,
19021 cx,
19022 )
19023 });
19024
19025 editor.update(cx, |editor, cx| {
19026 let autoscroll = match scroll_offset {
19027 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19028 None => Autoscroll::newest(),
19029 };
19030 let nav_history = editor.nav_history.take();
19031 editor.change_selections(Some(autoscroll), window, cx, |s| {
19032 s.select_ranges(ranges);
19033 });
19034 editor.nav_history = nav_history;
19035 });
19036 }
19037 })
19038 });
19039 }
19040
19041 // For now, don't allow opening excerpts in buffers that aren't backed by
19042 // regular project files.
19043 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19044 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19045 }
19046
19047 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19048 let snapshot = self.buffer.read(cx).read(cx);
19049 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
19050 Some(
19051 ranges
19052 .iter()
19053 .map(move |range| {
19054 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19055 })
19056 .collect(),
19057 )
19058 }
19059
19060 fn selection_replacement_ranges(
19061 &self,
19062 range: Range<OffsetUtf16>,
19063 cx: &mut App,
19064 ) -> Vec<Range<OffsetUtf16>> {
19065 let selections = self.selections.all::<OffsetUtf16>(cx);
19066 let newest_selection = selections
19067 .iter()
19068 .max_by_key(|selection| selection.id)
19069 .unwrap();
19070 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19071 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19072 let snapshot = self.buffer.read(cx).read(cx);
19073 selections
19074 .into_iter()
19075 .map(|mut selection| {
19076 selection.start.0 =
19077 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19078 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19079 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19080 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19081 })
19082 .collect()
19083 }
19084
19085 fn report_editor_event(
19086 &self,
19087 event_type: &'static str,
19088 file_extension: Option<String>,
19089 cx: &App,
19090 ) {
19091 if cfg!(any(test, feature = "test-support")) {
19092 return;
19093 }
19094
19095 let Some(project) = &self.project else { return };
19096
19097 // If None, we are in a file without an extension
19098 let file = self
19099 .buffer
19100 .read(cx)
19101 .as_singleton()
19102 .and_then(|b| b.read(cx).file());
19103 let file_extension = file_extension.or(file
19104 .as_ref()
19105 .and_then(|file| Path::new(file.file_name(cx)).extension())
19106 .and_then(|e| e.to_str())
19107 .map(|a| a.to_string()));
19108
19109 let vim_mode = vim_enabled(cx);
19110
19111 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19112 let copilot_enabled = edit_predictions_provider
19113 == language::language_settings::EditPredictionProvider::Copilot;
19114 let copilot_enabled_for_language = self
19115 .buffer
19116 .read(cx)
19117 .language_settings(cx)
19118 .show_edit_predictions;
19119
19120 let project = project.read(cx);
19121 telemetry::event!(
19122 event_type,
19123 file_extension,
19124 vim_mode,
19125 copilot_enabled,
19126 copilot_enabled_for_language,
19127 edit_predictions_provider,
19128 is_via_ssh = project.is_via_ssh(),
19129 );
19130 }
19131
19132 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19133 /// with each line being an array of {text, highlight} objects.
19134 fn copy_highlight_json(
19135 &mut self,
19136 _: &CopyHighlightJson,
19137 window: &mut Window,
19138 cx: &mut Context<Self>,
19139 ) {
19140 #[derive(Serialize)]
19141 struct Chunk<'a> {
19142 text: String,
19143 highlight: Option<&'a str>,
19144 }
19145
19146 let snapshot = self.buffer.read(cx).snapshot(cx);
19147 let range = self
19148 .selected_text_range(false, window, cx)
19149 .and_then(|selection| {
19150 if selection.range.is_empty() {
19151 None
19152 } else {
19153 Some(selection.range)
19154 }
19155 })
19156 .unwrap_or_else(|| 0..snapshot.len());
19157
19158 let chunks = snapshot.chunks(range, true);
19159 let mut lines = Vec::new();
19160 let mut line: VecDeque<Chunk> = VecDeque::new();
19161
19162 let Some(style) = self.style.as_ref() else {
19163 return;
19164 };
19165
19166 for chunk in chunks {
19167 let highlight = chunk
19168 .syntax_highlight_id
19169 .and_then(|id| id.name(&style.syntax));
19170 let mut chunk_lines = chunk.text.split('\n').peekable();
19171 while let Some(text) = chunk_lines.next() {
19172 let mut merged_with_last_token = false;
19173 if let Some(last_token) = line.back_mut() {
19174 if last_token.highlight == highlight {
19175 last_token.text.push_str(text);
19176 merged_with_last_token = true;
19177 }
19178 }
19179
19180 if !merged_with_last_token {
19181 line.push_back(Chunk {
19182 text: text.into(),
19183 highlight,
19184 });
19185 }
19186
19187 if chunk_lines.peek().is_some() {
19188 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19189 line.pop_front();
19190 }
19191 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19192 line.pop_back();
19193 }
19194
19195 lines.push(mem::take(&mut line));
19196 }
19197 }
19198 }
19199
19200 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19201 return;
19202 };
19203 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19204 }
19205
19206 pub fn open_context_menu(
19207 &mut self,
19208 _: &OpenContextMenu,
19209 window: &mut Window,
19210 cx: &mut Context<Self>,
19211 ) {
19212 self.request_autoscroll(Autoscroll::newest(), cx);
19213 let position = self.selections.newest_display(cx).start;
19214 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19215 }
19216
19217 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19218 &self.inlay_hint_cache
19219 }
19220
19221 pub fn replay_insert_event(
19222 &mut self,
19223 text: &str,
19224 relative_utf16_range: Option<Range<isize>>,
19225 window: &mut Window,
19226 cx: &mut Context<Self>,
19227 ) {
19228 if !self.input_enabled {
19229 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19230 return;
19231 }
19232 if let Some(relative_utf16_range) = relative_utf16_range {
19233 let selections = self.selections.all::<OffsetUtf16>(cx);
19234 self.change_selections(None, window, cx, |s| {
19235 let new_ranges = selections.into_iter().map(|range| {
19236 let start = OffsetUtf16(
19237 range
19238 .head()
19239 .0
19240 .saturating_add_signed(relative_utf16_range.start),
19241 );
19242 let end = OffsetUtf16(
19243 range
19244 .head()
19245 .0
19246 .saturating_add_signed(relative_utf16_range.end),
19247 );
19248 start..end
19249 });
19250 s.select_ranges(new_ranges);
19251 });
19252 }
19253
19254 self.handle_input(text, window, cx);
19255 }
19256
19257 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19258 let Some(provider) = self.semantics_provider.as_ref() else {
19259 return false;
19260 };
19261
19262 let mut supports = false;
19263 self.buffer().update(cx, |this, cx| {
19264 this.for_each_buffer(|buffer| {
19265 supports |= provider.supports_inlay_hints(buffer, cx);
19266 });
19267 });
19268
19269 supports
19270 }
19271
19272 pub fn is_focused(&self, window: &Window) -> bool {
19273 self.focus_handle.is_focused(window)
19274 }
19275
19276 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19277 cx.emit(EditorEvent::Focused);
19278
19279 if let Some(descendant) = self
19280 .last_focused_descendant
19281 .take()
19282 .and_then(|descendant| descendant.upgrade())
19283 {
19284 window.focus(&descendant);
19285 } else {
19286 if let Some(blame) = self.blame.as_ref() {
19287 blame.update(cx, GitBlame::focus)
19288 }
19289
19290 self.blink_manager.update(cx, BlinkManager::enable);
19291 self.show_cursor_names(window, cx);
19292 self.buffer.update(cx, |buffer, cx| {
19293 buffer.finalize_last_transaction(cx);
19294 if self.leader_id.is_none() {
19295 buffer.set_active_selections(
19296 &self.selections.disjoint_anchors(),
19297 self.selections.line_mode,
19298 self.cursor_shape,
19299 cx,
19300 );
19301 }
19302 });
19303 }
19304 }
19305
19306 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19307 cx.emit(EditorEvent::FocusedIn)
19308 }
19309
19310 fn handle_focus_out(
19311 &mut self,
19312 event: FocusOutEvent,
19313 _window: &mut Window,
19314 cx: &mut Context<Self>,
19315 ) {
19316 if event.blurred != self.focus_handle {
19317 self.last_focused_descendant = Some(event.blurred);
19318 }
19319 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19320 }
19321
19322 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19323 self.blink_manager.update(cx, BlinkManager::disable);
19324 self.buffer
19325 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19326
19327 if let Some(blame) = self.blame.as_ref() {
19328 blame.update(cx, GitBlame::blur)
19329 }
19330 if !self.hover_state.focused(window, cx) {
19331 hide_hover(self, cx);
19332 }
19333 if !self
19334 .context_menu
19335 .borrow()
19336 .as_ref()
19337 .is_some_and(|context_menu| context_menu.focused(window, cx))
19338 {
19339 self.hide_context_menu(window, cx);
19340 }
19341 self.discard_inline_completion(false, cx);
19342 cx.emit(EditorEvent::Blurred);
19343 cx.notify();
19344 }
19345
19346 pub fn register_action<A: Action>(
19347 &mut self,
19348 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19349 ) -> Subscription {
19350 let id = self.next_editor_action_id.post_inc();
19351 let listener = Arc::new(listener);
19352 self.editor_actions.borrow_mut().insert(
19353 id,
19354 Box::new(move |window, _| {
19355 let listener = listener.clone();
19356 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19357 let action = action.downcast_ref().unwrap();
19358 if phase == DispatchPhase::Bubble {
19359 listener(action, window, cx)
19360 }
19361 })
19362 }),
19363 );
19364
19365 let editor_actions = self.editor_actions.clone();
19366 Subscription::new(move || {
19367 editor_actions.borrow_mut().remove(&id);
19368 })
19369 }
19370
19371 pub fn file_header_size(&self) -> u32 {
19372 FILE_HEADER_HEIGHT
19373 }
19374
19375 pub fn restore(
19376 &mut self,
19377 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19378 window: &mut Window,
19379 cx: &mut Context<Self>,
19380 ) {
19381 let workspace = self.workspace();
19382 let project = self.project.as_ref();
19383 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19384 let mut tasks = Vec::new();
19385 for (buffer_id, changes) in revert_changes {
19386 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19387 buffer.update(cx, |buffer, cx| {
19388 buffer.edit(
19389 changes
19390 .into_iter()
19391 .map(|(range, text)| (range, text.to_string())),
19392 None,
19393 cx,
19394 );
19395 });
19396
19397 if let Some(project) =
19398 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19399 {
19400 project.update(cx, |project, cx| {
19401 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19402 })
19403 }
19404 }
19405 }
19406 tasks
19407 });
19408 cx.spawn_in(window, async move |_, cx| {
19409 for (buffer, task) in save_tasks {
19410 let result = task.await;
19411 if result.is_err() {
19412 let Some(path) = buffer
19413 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19414 .ok()
19415 else {
19416 continue;
19417 };
19418 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19419 let Some(task) = cx
19420 .update_window_entity(&workspace, |workspace, window, cx| {
19421 workspace
19422 .open_path_preview(path, None, false, false, false, window, cx)
19423 })
19424 .ok()
19425 else {
19426 continue;
19427 };
19428 task.await.log_err();
19429 }
19430 }
19431 }
19432 })
19433 .detach();
19434 self.change_selections(None, window, cx, |selections| selections.refresh());
19435 }
19436
19437 pub fn to_pixel_point(
19438 &self,
19439 source: multi_buffer::Anchor,
19440 editor_snapshot: &EditorSnapshot,
19441 window: &mut Window,
19442 ) -> Option<gpui::Point<Pixels>> {
19443 let source_point = source.to_display_point(editor_snapshot);
19444 self.display_to_pixel_point(source_point, editor_snapshot, window)
19445 }
19446
19447 pub fn display_to_pixel_point(
19448 &self,
19449 source: DisplayPoint,
19450 editor_snapshot: &EditorSnapshot,
19451 window: &mut Window,
19452 ) -> Option<gpui::Point<Pixels>> {
19453 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19454 let text_layout_details = self.text_layout_details(window);
19455 let scroll_top = text_layout_details
19456 .scroll_anchor
19457 .scroll_position(editor_snapshot)
19458 .y;
19459
19460 if source.row().as_f32() < scroll_top.floor() {
19461 return None;
19462 }
19463 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19464 let source_y = line_height * (source.row().as_f32() - scroll_top);
19465 Some(gpui::Point::new(source_x, source_y))
19466 }
19467
19468 pub fn has_visible_completions_menu(&self) -> bool {
19469 !self.edit_prediction_preview_is_active()
19470 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19471 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19472 })
19473 }
19474
19475 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19476 if self.mode.is_minimap() {
19477 return;
19478 }
19479 self.addons
19480 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19481 }
19482
19483 pub fn unregister_addon<T: Addon>(&mut self) {
19484 self.addons.remove(&std::any::TypeId::of::<T>());
19485 }
19486
19487 pub fn addon<T: Addon>(&self) -> Option<&T> {
19488 let type_id = std::any::TypeId::of::<T>();
19489 self.addons
19490 .get(&type_id)
19491 .and_then(|item| item.to_any().downcast_ref::<T>())
19492 }
19493
19494 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19495 let type_id = std::any::TypeId::of::<T>();
19496 self.addons
19497 .get_mut(&type_id)
19498 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19499 }
19500
19501 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19502 let text_layout_details = self.text_layout_details(window);
19503 let style = &text_layout_details.editor_style;
19504 let font_id = window.text_system().resolve_font(&style.text.font());
19505 let font_size = style.text.font_size.to_pixels(window.rem_size());
19506 let line_height = style.text.line_height_in_pixels(window.rem_size());
19507 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19508
19509 gpui::Size::new(em_width, line_height)
19510 }
19511
19512 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
19513 self.load_diff_task.clone()
19514 }
19515
19516 fn read_metadata_from_db(
19517 &mut self,
19518 item_id: u64,
19519 workspace_id: WorkspaceId,
19520 window: &mut Window,
19521 cx: &mut Context<Editor>,
19522 ) {
19523 if self.is_singleton(cx)
19524 && !self.mode.is_minimap()
19525 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
19526 {
19527 let buffer_snapshot = OnceCell::new();
19528
19529 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
19530 if !folds.is_empty() {
19531 let snapshot =
19532 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19533 self.fold_ranges(
19534 folds
19535 .into_iter()
19536 .map(|(start, end)| {
19537 snapshot.clip_offset(start, Bias::Left)
19538 ..snapshot.clip_offset(end, Bias::Right)
19539 })
19540 .collect(),
19541 false,
19542 window,
19543 cx,
19544 );
19545 }
19546 }
19547
19548 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
19549 if !selections.is_empty() {
19550 let snapshot =
19551 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19552 self.change_selections(None, window, cx, |s| {
19553 s.select_ranges(selections.into_iter().map(|(start, end)| {
19554 snapshot.clip_offset(start, Bias::Left)
19555 ..snapshot.clip_offset(end, Bias::Right)
19556 }));
19557 });
19558 }
19559 };
19560 }
19561
19562 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
19563 }
19564}
19565
19566fn vim_enabled(cx: &App) -> bool {
19567 cx.global::<SettingsStore>()
19568 .raw_user_settings()
19569 .get("vim_mode")
19570 == Some(&serde_json::Value::Bool(true))
19571}
19572
19573fn process_completion_for_edit(
19574 completion: &Completion,
19575 intent: CompletionIntent,
19576 buffer: &Entity<Buffer>,
19577 cursor_position: &text::Anchor,
19578 cx: &mut Context<Editor>,
19579) -> CompletionEdit {
19580 let buffer = buffer.read(cx);
19581 let buffer_snapshot = buffer.snapshot();
19582 let (snippet, new_text) = if completion.is_snippet() {
19583 let mut snippet_source = completion.new_text.clone();
19584 if let Some(scope) = buffer_snapshot.language_scope_at(cursor_position) {
19585 if scope.prefers_label_for_snippet_in_completion() {
19586 if let Some(label) = completion.label() {
19587 if matches!(
19588 completion.kind(),
19589 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
19590 ) {
19591 snippet_source = label;
19592 }
19593 }
19594 }
19595 }
19596 match Snippet::parse(&snippet_source).log_err() {
19597 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
19598 None => (None, completion.new_text.clone()),
19599 }
19600 } else {
19601 (None, completion.new_text.clone())
19602 };
19603
19604 let mut range_to_replace = {
19605 let replace_range = &completion.replace_range;
19606 if let CompletionSource::Lsp {
19607 insert_range: Some(insert_range),
19608 ..
19609 } = &completion.source
19610 {
19611 debug_assert_eq!(
19612 insert_range.start, replace_range.start,
19613 "insert_range and replace_range should start at the same position"
19614 );
19615 debug_assert!(
19616 insert_range
19617 .start
19618 .cmp(&cursor_position, &buffer_snapshot)
19619 .is_le(),
19620 "insert_range should start before or at cursor position"
19621 );
19622 debug_assert!(
19623 replace_range
19624 .start
19625 .cmp(&cursor_position, &buffer_snapshot)
19626 .is_le(),
19627 "replace_range should start before or at cursor position"
19628 );
19629 debug_assert!(
19630 insert_range
19631 .end
19632 .cmp(&cursor_position, &buffer_snapshot)
19633 .is_le(),
19634 "insert_range should end before or at cursor position"
19635 );
19636
19637 let should_replace = match intent {
19638 CompletionIntent::CompleteWithInsert => false,
19639 CompletionIntent::CompleteWithReplace => true,
19640 CompletionIntent::Complete | CompletionIntent::Compose => {
19641 let insert_mode =
19642 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
19643 .completions
19644 .lsp_insert_mode;
19645 match insert_mode {
19646 LspInsertMode::Insert => false,
19647 LspInsertMode::Replace => true,
19648 LspInsertMode::ReplaceSubsequence => {
19649 let mut text_to_replace = buffer.chars_for_range(
19650 buffer.anchor_before(replace_range.start)
19651 ..buffer.anchor_after(replace_range.end),
19652 );
19653 let mut current_needle = text_to_replace.next();
19654 for haystack_ch in completion.label.text.chars() {
19655 if let Some(needle_ch) = current_needle {
19656 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
19657 current_needle = text_to_replace.next();
19658 }
19659 }
19660 }
19661 current_needle.is_none()
19662 }
19663 LspInsertMode::ReplaceSuffix => {
19664 if replace_range
19665 .end
19666 .cmp(&cursor_position, &buffer_snapshot)
19667 .is_gt()
19668 {
19669 let range_after_cursor = *cursor_position..replace_range.end;
19670 let text_after_cursor = buffer
19671 .text_for_range(
19672 buffer.anchor_before(range_after_cursor.start)
19673 ..buffer.anchor_after(range_after_cursor.end),
19674 )
19675 .collect::<String>()
19676 .to_ascii_lowercase();
19677 completion
19678 .label
19679 .text
19680 .to_ascii_lowercase()
19681 .ends_with(&text_after_cursor)
19682 } else {
19683 true
19684 }
19685 }
19686 }
19687 }
19688 };
19689
19690 if should_replace {
19691 replace_range.clone()
19692 } else {
19693 insert_range.clone()
19694 }
19695 } else {
19696 replace_range.clone()
19697 }
19698 };
19699
19700 if range_to_replace
19701 .end
19702 .cmp(&cursor_position, &buffer_snapshot)
19703 .is_lt()
19704 {
19705 range_to_replace.end = *cursor_position;
19706 }
19707
19708 CompletionEdit {
19709 new_text,
19710 replace_range: range_to_replace.to_offset(&buffer),
19711 snippet,
19712 }
19713}
19714
19715struct CompletionEdit {
19716 new_text: String,
19717 replace_range: Range<usize>,
19718 snippet: Option<Snippet>,
19719}
19720
19721fn insert_extra_newline_brackets(
19722 buffer: &MultiBufferSnapshot,
19723 range: Range<usize>,
19724 language: &language::LanguageScope,
19725) -> bool {
19726 let leading_whitespace_len = buffer
19727 .reversed_chars_at(range.start)
19728 .take_while(|c| c.is_whitespace() && *c != '\n')
19729 .map(|c| c.len_utf8())
19730 .sum::<usize>();
19731 let trailing_whitespace_len = buffer
19732 .chars_at(range.end)
19733 .take_while(|c| c.is_whitespace() && *c != '\n')
19734 .map(|c| c.len_utf8())
19735 .sum::<usize>();
19736 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
19737
19738 language.brackets().any(|(pair, enabled)| {
19739 let pair_start = pair.start.trim_end();
19740 let pair_end = pair.end.trim_start();
19741
19742 enabled
19743 && pair.newline
19744 && buffer.contains_str_at(range.end, pair_end)
19745 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
19746 })
19747}
19748
19749fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
19750 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
19751 [(buffer, range, _)] => (*buffer, range.clone()),
19752 _ => return false,
19753 };
19754 let pair = {
19755 let mut result: Option<BracketMatch> = None;
19756
19757 for pair in buffer
19758 .all_bracket_ranges(range.clone())
19759 .filter(move |pair| {
19760 pair.open_range.start <= range.start && pair.close_range.end >= range.end
19761 })
19762 {
19763 let len = pair.close_range.end - pair.open_range.start;
19764
19765 if let Some(existing) = &result {
19766 let existing_len = existing.close_range.end - existing.open_range.start;
19767 if len > existing_len {
19768 continue;
19769 }
19770 }
19771
19772 result = Some(pair);
19773 }
19774
19775 result
19776 };
19777 let Some(pair) = pair else {
19778 return false;
19779 };
19780 pair.newline_only
19781 && buffer
19782 .chars_for_range(pair.open_range.end..range.start)
19783 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
19784 .all(|c| c.is_whitespace() && c != '\n')
19785}
19786
19787fn update_uncommitted_diff_for_buffer(
19788 editor: Entity<Editor>,
19789 project: &Entity<Project>,
19790 buffers: impl IntoIterator<Item = Entity<Buffer>>,
19791 buffer: Entity<MultiBuffer>,
19792 cx: &mut App,
19793) -> Task<()> {
19794 let mut tasks = Vec::new();
19795 project.update(cx, |project, cx| {
19796 for buffer in buffers {
19797 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
19798 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
19799 }
19800 }
19801 });
19802 cx.spawn(async move |cx| {
19803 let diffs = future::join_all(tasks).await;
19804 if editor
19805 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19806 .unwrap_or(false)
19807 {
19808 return;
19809 }
19810
19811 buffer
19812 .update(cx, |buffer, cx| {
19813 for diff in diffs.into_iter().flatten() {
19814 buffer.add_diff(diff, cx);
19815 }
19816 })
19817 .ok();
19818 })
19819}
19820
19821fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
19822 let tab_size = tab_size.get() as usize;
19823 let mut width = offset;
19824
19825 for ch in text.chars() {
19826 width += if ch == '\t' {
19827 tab_size - (width % tab_size)
19828 } else {
19829 1
19830 };
19831 }
19832
19833 width - offset
19834}
19835
19836#[cfg(test)]
19837mod tests {
19838 use super::*;
19839
19840 #[test]
19841 fn test_string_size_with_expanded_tabs() {
19842 let nz = |val| NonZeroU32::new(val).unwrap();
19843 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
19844 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
19845 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
19846 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
19847 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
19848 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
19849 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
19850 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
19851 }
19852}
19853
19854/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
19855struct WordBreakingTokenizer<'a> {
19856 input: &'a str,
19857}
19858
19859impl<'a> WordBreakingTokenizer<'a> {
19860 fn new(input: &'a str) -> Self {
19861 Self { input }
19862 }
19863}
19864
19865fn is_char_ideographic(ch: char) -> bool {
19866 use unicode_script::Script::*;
19867 use unicode_script::UnicodeScript;
19868 matches!(ch.script(), Han | Tangut | Yi)
19869}
19870
19871fn is_grapheme_ideographic(text: &str) -> bool {
19872 text.chars().any(is_char_ideographic)
19873}
19874
19875fn is_grapheme_whitespace(text: &str) -> bool {
19876 text.chars().any(|x| x.is_whitespace())
19877}
19878
19879fn should_stay_with_preceding_ideograph(text: &str) -> bool {
19880 text.chars().next().map_or(false, |ch| {
19881 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
19882 })
19883}
19884
19885#[derive(PartialEq, Eq, Debug, Clone, Copy)]
19886enum WordBreakToken<'a> {
19887 Word { token: &'a str, grapheme_len: usize },
19888 InlineWhitespace { token: &'a str, grapheme_len: usize },
19889 Newline,
19890}
19891
19892impl<'a> Iterator for WordBreakingTokenizer<'a> {
19893 /// Yields a span, the count of graphemes in the token, and whether it was
19894 /// whitespace. Note that it also breaks at word boundaries.
19895 type Item = WordBreakToken<'a>;
19896
19897 fn next(&mut self) -> Option<Self::Item> {
19898 use unicode_segmentation::UnicodeSegmentation;
19899 if self.input.is_empty() {
19900 return None;
19901 }
19902
19903 let mut iter = self.input.graphemes(true).peekable();
19904 let mut offset = 0;
19905 let mut grapheme_len = 0;
19906 if let Some(first_grapheme) = iter.next() {
19907 let is_newline = first_grapheme == "\n";
19908 let is_whitespace = is_grapheme_whitespace(first_grapheme);
19909 offset += first_grapheme.len();
19910 grapheme_len += 1;
19911 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
19912 if let Some(grapheme) = iter.peek().copied() {
19913 if should_stay_with_preceding_ideograph(grapheme) {
19914 offset += grapheme.len();
19915 grapheme_len += 1;
19916 }
19917 }
19918 } else {
19919 let mut words = self.input[offset..].split_word_bound_indices().peekable();
19920 let mut next_word_bound = words.peek().copied();
19921 if next_word_bound.map_or(false, |(i, _)| i == 0) {
19922 next_word_bound = words.next();
19923 }
19924 while let Some(grapheme) = iter.peek().copied() {
19925 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19926 break;
19927 };
19928 if is_grapheme_whitespace(grapheme) != is_whitespace
19929 || (grapheme == "\n") != is_newline
19930 {
19931 break;
19932 };
19933 offset += grapheme.len();
19934 grapheme_len += 1;
19935 iter.next();
19936 }
19937 }
19938 let token = &self.input[..offset];
19939 self.input = &self.input[offset..];
19940 if token == "\n" {
19941 Some(WordBreakToken::Newline)
19942 } else if is_whitespace {
19943 Some(WordBreakToken::InlineWhitespace {
19944 token,
19945 grapheme_len,
19946 })
19947 } else {
19948 Some(WordBreakToken::Word {
19949 token,
19950 grapheme_len,
19951 })
19952 }
19953 } else {
19954 None
19955 }
19956 }
19957}
19958
19959#[test]
19960fn test_word_breaking_tokenizer() {
19961 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
19962 ("", &[]),
19963 (" ", &[whitespace(" ", 2)]),
19964 ("Ʒ", &[word("Ʒ", 1)]),
19965 ("Ǽ", &[word("Ǽ", 1)]),
19966 ("⋑", &[word("⋑", 1)]),
19967 ("⋑⋑", &[word("⋑⋑", 2)]),
19968 (
19969 "原理,进而",
19970 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
19971 ),
19972 (
19973 "hello world",
19974 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
19975 ),
19976 (
19977 "hello, world",
19978 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
19979 ),
19980 (
19981 " hello world",
19982 &[
19983 whitespace(" ", 2),
19984 word("hello", 5),
19985 whitespace(" ", 1),
19986 word("world", 5),
19987 ],
19988 ),
19989 (
19990 "这是什么 \n 钢笔",
19991 &[
19992 word("这", 1),
19993 word("是", 1),
19994 word("什", 1),
19995 word("么", 1),
19996 whitespace(" ", 1),
19997 newline(),
19998 whitespace(" ", 1),
19999 word("钢", 1),
20000 word("笔", 1),
20001 ],
20002 ),
20003 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
20004 ];
20005
20006 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20007 WordBreakToken::Word {
20008 token,
20009 grapheme_len,
20010 }
20011 }
20012
20013 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20014 WordBreakToken::InlineWhitespace {
20015 token,
20016 grapheme_len,
20017 }
20018 }
20019
20020 fn newline() -> WordBreakToken<'static> {
20021 WordBreakToken::Newline
20022 }
20023
20024 for (input, result) in tests {
20025 assert_eq!(
20026 WordBreakingTokenizer::new(input)
20027 .collect::<Vec<_>>()
20028 .as_slice(),
20029 *result,
20030 );
20031 }
20032}
20033
20034fn wrap_with_prefix(
20035 line_prefix: String,
20036 unwrapped_text: String,
20037 wrap_column: usize,
20038 tab_size: NonZeroU32,
20039 preserve_existing_whitespace: bool,
20040) -> String {
20041 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
20042 let mut wrapped_text = String::new();
20043 let mut current_line = line_prefix.clone();
20044
20045 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
20046 let mut current_line_len = line_prefix_len;
20047 let mut in_whitespace = false;
20048 for token in tokenizer {
20049 let have_preceding_whitespace = in_whitespace;
20050 match token {
20051 WordBreakToken::Word {
20052 token,
20053 grapheme_len,
20054 } => {
20055 in_whitespace = false;
20056 if current_line_len + grapheme_len > wrap_column
20057 && current_line_len != line_prefix_len
20058 {
20059 wrapped_text.push_str(current_line.trim_end());
20060 wrapped_text.push('\n');
20061 current_line.truncate(line_prefix.len());
20062 current_line_len = line_prefix_len;
20063 }
20064 current_line.push_str(token);
20065 current_line_len += grapheme_len;
20066 }
20067 WordBreakToken::InlineWhitespace {
20068 mut token,
20069 mut grapheme_len,
20070 } => {
20071 in_whitespace = true;
20072 if have_preceding_whitespace && !preserve_existing_whitespace {
20073 continue;
20074 }
20075 if !preserve_existing_whitespace {
20076 token = " ";
20077 grapheme_len = 1;
20078 }
20079 if current_line_len + grapheme_len > wrap_column {
20080 wrapped_text.push_str(current_line.trim_end());
20081 wrapped_text.push('\n');
20082 current_line.truncate(line_prefix.len());
20083 current_line_len = line_prefix_len;
20084 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
20085 current_line.push_str(token);
20086 current_line_len += grapheme_len;
20087 }
20088 }
20089 WordBreakToken::Newline => {
20090 in_whitespace = true;
20091 if preserve_existing_whitespace {
20092 wrapped_text.push_str(current_line.trim_end());
20093 wrapped_text.push('\n');
20094 current_line.truncate(line_prefix.len());
20095 current_line_len = line_prefix_len;
20096 } else if have_preceding_whitespace {
20097 continue;
20098 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
20099 {
20100 wrapped_text.push_str(current_line.trim_end());
20101 wrapped_text.push('\n');
20102 current_line.truncate(line_prefix.len());
20103 current_line_len = line_prefix_len;
20104 } else if current_line_len != line_prefix_len {
20105 current_line.push(' ');
20106 current_line_len += 1;
20107 }
20108 }
20109 }
20110 }
20111
20112 if !current_line.is_empty() {
20113 wrapped_text.push_str(¤t_line);
20114 }
20115 wrapped_text
20116}
20117
20118#[test]
20119fn test_wrap_with_prefix() {
20120 assert_eq!(
20121 wrap_with_prefix(
20122 "# ".to_string(),
20123 "abcdefg".to_string(),
20124 4,
20125 NonZeroU32::new(4).unwrap(),
20126 false,
20127 ),
20128 "# abcdefg"
20129 );
20130 assert_eq!(
20131 wrap_with_prefix(
20132 "".to_string(),
20133 "\thello world".to_string(),
20134 8,
20135 NonZeroU32::new(4).unwrap(),
20136 false,
20137 ),
20138 "hello\nworld"
20139 );
20140 assert_eq!(
20141 wrap_with_prefix(
20142 "// ".to_string(),
20143 "xx \nyy zz aa bb cc".to_string(),
20144 12,
20145 NonZeroU32::new(4).unwrap(),
20146 false,
20147 ),
20148 "// xx yy zz\n// aa bb cc"
20149 );
20150 assert_eq!(
20151 wrap_with_prefix(
20152 String::new(),
20153 "这是什么 \n 钢笔".to_string(),
20154 3,
20155 NonZeroU32::new(4).unwrap(),
20156 false,
20157 ),
20158 "这是什\n么 钢\n笔"
20159 );
20160}
20161
20162pub trait CollaborationHub {
20163 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20164 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20165 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20166}
20167
20168impl CollaborationHub for Entity<Project> {
20169 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20170 self.read(cx).collaborators()
20171 }
20172
20173 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
20174 self.read(cx).user_store().read(cx).participant_indices()
20175 }
20176
20177 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
20178 let this = self.read(cx);
20179 let user_ids = this.collaborators().values().map(|c| c.user_id);
20180 this.user_store().read(cx).participant_names(user_ids, cx)
20181 }
20182}
20183
20184pub trait SemanticsProvider {
20185 fn hover(
20186 &self,
20187 buffer: &Entity<Buffer>,
20188 position: text::Anchor,
20189 cx: &mut App,
20190 ) -> Option<Task<Vec<project::Hover>>>;
20191
20192 fn inline_values(
20193 &self,
20194 buffer_handle: Entity<Buffer>,
20195 range: Range<text::Anchor>,
20196 cx: &mut App,
20197 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20198
20199 fn inlay_hints(
20200 &self,
20201 buffer_handle: Entity<Buffer>,
20202 range: Range<text::Anchor>,
20203 cx: &mut App,
20204 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20205
20206 fn resolve_inlay_hint(
20207 &self,
20208 hint: InlayHint,
20209 buffer_handle: Entity<Buffer>,
20210 server_id: LanguageServerId,
20211 cx: &mut App,
20212 ) -> Option<Task<anyhow::Result<InlayHint>>>;
20213
20214 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
20215
20216 fn document_highlights(
20217 &self,
20218 buffer: &Entity<Buffer>,
20219 position: text::Anchor,
20220 cx: &mut App,
20221 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
20222
20223 fn definitions(
20224 &self,
20225 buffer: &Entity<Buffer>,
20226 position: text::Anchor,
20227 kind: GotoDefinitionKind,
20228 cx: &mut App,
20229 ) -> Option<Task<Result<Vec<LocationLink>>>>;
20230
20231 fn range_for_rename(
20232 &self,
20233 buffer: &Entity<Buffer>,
20234 position: text::Anchor,
20235 cx: &mut App,
20236 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
20237
20238 fn perform_rename(
20239 &self,
20240 buffer: &Entity<Buffer>,
20241 position: text::Anchor,
20242 new_name: String,
20243 cx: &mut App,
20244 ) -> Option<Task<Result<ProjectTransaction>>>;
20245}
20246
20247pub trait CompletionProvider {
20248 fn completions(
20249 &self,
20250 excerpt_id: ExcerptId,
20251 buffer: &Entity<Buffer>,
20252 buffer_position: text::Anchor,
20253 trigger: CompletionContext,
20254 window: &mut Window,
20255 cx: &mut Context<Editor>,
20256 ) -> Task<Result<Vec<CompletionResponse>>>;
20257
20258 fn resolve_completions(
20259 &self,
20260 buffer: Entity<Buffer>,
20261 completion_indices: Vec<usize>,
20262 completions: Rc<RefCell<Box<[Completion]>>>,
20263 cx: &mut Context<Editor>,
20264 ) -> Task<Result<bool>>;
20265
20266 fn apply_additional_edits_for_completion(
20267 &self,
20268 _buffer: Entity<Buffer>,
20269 _completions: Rc<RefCell<Box<[Completion]>>>,
20270 _completion_index: usize,
20271 _push_to_history: bool,
20272 _cx: &mut Context<Editor>,
20273 ) -> Task<Result<Option<language::Transaction>>> {
20274 Task::ready(Ok(None))
20275 }
20276
20277 fn is_completion_trigger(
20278 &self,
20279 buffer: &Entity<Buffer>,
20280 position: language::Anchor,
20281 text: &str,
20282 trigger_in_words: bool,
20283 cx: &mut Context<Editor>,
20284 ) -> bool;
20285
20286 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
20287
20288 fn sort_completions(&self) -> bool {
20289 true
20290 }
20291
20292 fn filter_completions(&self) -> bool {
20293 true
20294 }
20295}
20296
20297pub trait CodeActionProvider {
20298 fn id(&self) -> Arc<str>;
20299
20300 fn code_actions(
20301 &self,
20302 buffer: &Entity<Buffer>,
20303 range: Range<text::Anchor>,
20304 window: &mut Window,
20305 cx: &mut App,
20306 ) -> Task<Result<Vec<CodeAction>>>;
20307
20308 fn apply_code_action(
20309 &self,
20310 buffer_handle: Entity<Buffer>,
20311 action: CodeAction,
20312 excerpt_id: ExcerptId,
20313 push_to_history: bool,
20314 window: &mut Window,
20315 cx: &mut App,
20316 ) -> Task<Result<ProjectTransaction>>;
20317}
20318
20319impl CodeActionProvider for Entity<Project> {
20320 fn id(&self) -> Arc<str> {
20321 "project".into()
20322 }
20323
20324 fn code_actions(
20325 &self,
20326 buffer: &Entity<Buffer>,
20327 range: Range<text::Anchor>,
20328 _window: &mut Window,
20329 cx: &mut App,
20330 ) -> Task<Result<Vec<CodeAction>>> {
20331 self.update(cx, |project, cx| {
20332 let code_lens = project.code_lens(buffer, range.clone(), cx);
20333 let code_actions = project.code_actions(buffer, range, None, cx);
20334 cx.background_spawn(async move {
20335 let (code_lens, code_actions) = join(code_lens, code_actions).await;
20336 Ok(code_lens
20337 .context("code lens fetch")?
20338 .into_iter()
20339 .chain(code_actions.context("code action fetch")?)
20340 .collect())
20341 })
20342 })
20343 }
20344
20345 fn apply_code_action(
20346 &self,
20347 buffer_handle: Entity<Buffer>,
20348 action: CodeAction,
20349 _excerpt_id: ExcerptId,
20350 push_to_history: bool,
20351 _window: &mut Window,
20352 cx: &mut App,
20353 ) -> Task<Result<ProjectTransaction>> {
20354 self.update(cx, |project, cx| {
20355 project.apply_code_action(buffer_handle, action, push_to_history, cx)
20356 })
20357 }
20358}
20359
20360fn snippet_completions(
20361 project: &Project,
20362 buffer: &Entity<Buffer>,
20363 buffer_position: text::Anchor,
20364 cx: &mut App,
20365) -> Task<Result<CompletionResponse>> {
20366 let languages = buffer.read(cx).languages_at(buffer_position);
20367 let snippet_store = project.snippets().read(cx);
20368
20369 let scopes: Vec<_> = languages
20370 .iter()
20371 .filter_map(|language| {
20372 let language_name = language.lsp_id();
20373 let snippets = snippet_store.snippets_for(Some(language_name), cx);
20374
20375 if snippets.is_empty() {
20376 None
20377 } else {
20378 Some((language.default_scope(), snippets))
20379 }
20380 })
20381 .collect();
20382
20383 if scopes.is_empty() {
20384 return Task::ready(Ok(CompletionResponse {
20385 completions: vec![],
20386 is_incomplete: false,
20387 }));
20388 }
20389
20390 let snapshot = buffer.read(cx).text_snapshot();
20391 let chars: String = snapshot
20392 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
20393 .collect();
20394 let executor = cx.background_executor().clone();
20395
20396 cx.background_spawn(async move {
20397 let mut is_incomplete = false;
20398 let mut completions: Vec<Completion> = Vec::new();
20399 for (scope, snippets) in scopes.into_iter() {
20400 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
20401 let mut last_word = chars
20402 .chars()
20403 .take_while(|c| classifier.is_word(*c))
20404 .collect::<String>();
20405 last_word = last_word.chars().rev().collect();
20406
20407 if last_word.is_empty() {
20408 return Ok(CompletionResponse {
20409 completions: vec![],
20410 is_incomplete: true,
20411 });
20412 }
20413
20414 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
20415 let to_lsp = |point: &text::Anchor| {
20416 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
20417 point_to_lsp(end)
20418 };
20419 let lsp_end = to_lsp(&buffer_position);
20420
20421 let candidates = snippets
20422 .iter()
20423 .enumerate()
20424 .flat_map(|(ix, snippet)| {
20425 snippet
20426 .prefix
20427 .iter()
20428 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
20429 })
20430 .collect::<Vec<StringMatchCandidate>>();
20431
20432 const MAX_RESULTS: usize = 100;
20433 let mut matches = fuzzy::match_strings(
20434 &candidates,
20435 &last_word,
20436 last_word.chars().any(|c| c.is_uppercase()),
20437 MAX_RESULTS,
20438 &Default::default(),
20439 executor.clone(),
20440 )
20441 .await;
20442
20443 if matches.len() >= MAX_RESULTS {
20444 is_incomplete = true;
20445 }
20446
20447 // Remove all candidates where the query's start does not match the start of any word in the candidate
20448 if let Some(query_start) = last_word.chars().next() {
20449 matches.retain(|string_match| {
20450 split_words(&string_match.string).any(|word| {
20451 // Check that the first codepoint of the word as lowercase matches the first
20452 // codepoint of the query as lowercase
20453 word.chars()
20454 .flat_map(|codepoint| codepoint.to_lowercase())
20455 .zip(query_start.to_lowercase())
20456 .all(|(word_cp, query_cp)| word_cp == query_cp)
20457 })
20458 });
20459 }
20460
20461 let matched_strings = matches
20462 .into_iter()
20463 .map(|m| m.string)
20464 .collect::<HashSet<_>>();
20465
20466 completions.extend(snippets.iter().filter_map(|snippet| {
20467 let matching_prefix = snippet
20468 .prefix
20469 .iter()
20470 .find(|prefix| matched_strings.contains(*prefix))?;
20471 let start = as_offset - last_word.len();
20472 let start = snapshot.anchor_before(start);
20473 let range = start..buffer_position;
20474 let lsp_start = to_lsp(&start);
20475 let lsp_range = lsp::Range {
20476 start: lsp_start,
20477 end: lsp_end,
20478 };
20479 Some(Completion {
20480 replace_range: range,
20481 new_text: snippet.body.clone(),
20482 source: CompletionSource::Lsp {
20483 insert_range: None,
20484 server_id: LanguageServerId(usize::MAX),
20485 resolved: true,
20486 lsp_completion: Box::new(lsp::CompletionItem {
20487 label: snippet.prefix.first().unwrap().clone(),
20488 kind: Some(CompletionItemKind::SNIPPET),
20489 label_details: snippet.description.as_ref().map(|description| {
20490 lsp::CompletionItemLabelDetails {
20491 detail: Some(description.clone()),
20492 description: None,
20493 }
20494 }),
20495 insert_text_format: Some(InsertTextFormat::SNIPPET),
20496 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20497 lsp::InsertReplaceEdit {
20498 new_text: snippet.body.clone(),
20499 insert: lsp_range,
20500 replace: lsp_range,
20501 },
20502 )),
20503 filter_text: Some(snippet.body.clone()),
20504 sort_text: Some(char::MAX.to_string()),
20505 ..lsp::CompletionItem::default()
20506 }),
20507 lsp_defaults: None,
20508 },
20509 label: CodeLabel {
20510 text: matching_prefix.clone(),
20511 runs: Vec::new(),
20512 filter_range: 0..matching_prefix.len(),
20513 },
20514 icon_path: None,
20515 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
20516 single_line: snippet.name.clone().into(),
20517 plain_text: snippet
20518 .description
20519 .clone()
20520 .map(|description| description.into()),
20521 }),
20522 insert_text_mode: None,
20523 confirm: None,
20524 })
20525 }))
20526 }
20527
20528 Ok(CompletionResponse {
20529 completions,
20530 is_incomplete,
20531 })
20532 })
20533}
20534
20535impl CompletionProvider for Entity<Project> {
20536 fn completions(
20537 &self,
20538 _excerpt_id: ExcerptId,
20539 buffer: &Entity<Buffer>,
20540 buffer_position: text::Anchor,
20541 options: CompletionContext,
20542 _window: &mut Window,
20543 cx: &mut Context<Editor>,
20544 ) -> Task<Result<Vec<CompletionResponse>>> {
20545 self.update(cx, |project, cx| {
20546 let snippets = snippet_completions(project, buffer, buffer_position, cx);
20547 let project_completions = project.completions(buffer, buffer_position, options, cx);
20548 cx.background_spawn(async move {
20549 let mut responses = project_completions.await?;
20550 let snippets = snippets.await?;
20551 if !snippets.completions.is_empty() {
20552 responses.push(snippets);
20553 }
20554 Ok(responses)
20555 })
20556 })
20557 }
20558
20559 fn resolve_completions(
20560 &self,
20561 buffer: Entity<Buffer>,
20562 completion_indices: Vec<usize>,
20563 completions: Rc<RefCell<Box<[Completion]>>>,
20564 cx: &mut Context<Editor>,
20565 ) -> Task<Result<bool>> {
20566 self.update(cx, |project, cx| {
20567 project.lsp_store().update(cx, |lsp_store, cx| {
20568 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
20569 })
20570 })
20571 }
20572
20573 fn apply_additional_edits_for_completion(
20574 &self,
20575 buffer: Entity<Buffer>,
20576 completions: Rc<RefCell<Box<[Completion]>>>,
20577 completion_index: usize,
20578 push_to_history: bool,
20579 cx: &mut Context<Editor>,
20580 ) -> Task<Result<Option<language::Transaction>>> {
20581 self.update(cx, |project, cx| {
20582 project.lsp_store().update(cx, |lsp_store, cx| {
20583 lsp_store.apply_additional_edits_for_completion(
20584 buffer,
20585 completions,
20586 completion_index,
20587 push_to_history,
20588 cx,
20589 )
20590 })
20591 })
20592 }
20593
20594 fn is_completion_trigger(
20595 &self,
20596 buffer: &Entity<Buffer>,
20597 position: language::Anchor,
20598 text: &str,
20599 trigger_in_words: bool,
20600 cx: &mut Context<Editor>,
20601 ) -> bool {
20602 let mut chars = text.chars();
20603 let char = if let Some(char) = chars.next() {
20604 char
20605 } else {
20606 return false;
20607 };
20608 if chars.next().is_some() {
20609 return false;
20610 }
20611
20612 let buffer = buffer.read(cx);
20613 let snapshot = buffer.snapshot();
20614 if !snapshot.settings_at(position, cx).show_completions_on_input {
20615 return false;
20616 }
20617 let classifier = snapshot.char_classifier_at(position).for_completion(true);
20618 if trigger_in_words && classifier.is_word(char) {
20619 return true;
20620 }
20621
20622 buffer.completion_triggers().contains(text)
20623 }
20624}
20625
20626impl SemanticsProvider for Entity<Project> {
20627 fn hover(
20628 &self,
20629 buffer: &Entity<Buffer>,
20630 position: text::Anchor,
20631 cx: &mut App,
20632 ) -> Option<Task<Vec<project::Hover>>> {
20633 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
20634 }
20635
20636 fn document_highlights(
20637 &self,
20638 buffer: &Entity<Buffer>,
20639 position: text::Anchor,
20640 cx: &mut App,
20641 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
20642 Some(self.update(cx, |project, cx| {
20643 project.document_highlights(buffer, position, cx)
20644 }))
20645 }
20646
20647 fn definitions(
20648 &self,
20649 buffer: &Entity<Buffer>,
20650 position: text::Anchor,
20651 kind: GotoDefinitionKind,
20652 cx: &mut App,
20653 ) -> Option<Task<Result<Vec<LocationLink>>>> {
20654 Some(self.update(cx, |project, cx| match kind {
20655 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
20656 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
20657 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
20658 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
20659 }))
20660 }
20661
20662 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
20663 // TODO: make this work for remote projects
20664 self.update(cx, |project, cx| {
20665 if project
20666 .active_debug_session(cx)
20667 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
20668 {
20669 return true;
20670 }
20671
20672 buffer.update(cx, |buffer, cx| {
20673 project.any_language_server_supports_inlay_hints(buffer, cx)
20674 })
20675 })
20676 }
20677
20678 fn inline_values(
20679 &self,
20680 buffer_handle: Entity<Buffer>,
20681
20682 range: Range<text::Anchor>,
20683 cx: &mut App,
20684 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20685 self.update(cx, |project, cx| {
20686 let (session, active_stack_frame) = project.active_debug_session(cx)?;
20687
20688 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
20689 })
20690 }
20691
20692 fn inlay_hints(
20693 &self,
20694 buffer_handle: Entity<Buffer>,
20695 range: Range<text::Anchor>,
20696 cx: &mut App,
20697 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20698 Some(self.update(cx, |project, cx| {
20699 project.inlay_hints(buffer_handle, range, cx)
20700 }))
20701 }
20702
20703 fn resolve_inlay_hint(
20704 &self,
20705 hint: InlayHint,
20706 buffer_handle: Entity<Buffer>,
20707 server_id: LanguageServerId,
20708 cx: &mut App,
20709 ) -> Option<Task<anyhow::Result<InlayHint>>> {
20710 Some(self.update(cx, |project, cx| {
20711 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
20712 }))
20713 }
20714
20715 fn range_for_rename(
20716 &self,
20717 buffer: &Entity<Buffer>,
20718 position: text::Anchor,
20719 cx: &mut App,
20720 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
20721 Some(self.update(cx, |project, cx| {
20722 let buffer = buffer.clone();
20723 let task = project.prepare_rename(buffer.clone(), position, cx);
20724 cx.spawn(async move |_, cx| {
20725 Ok(match task.await? {
20726 PrepareRenameResponse::Success(range) => Some(range),
20727 PrepareRenameResponse::InvalidPosition => None,
20728 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
20729 // Fallback on using TreeSitter info to determine identifier range
20730 buffer.read_with(cx, |buffer, _| {
20731 let snapshot = buffer.snapshot();
20732 let (range, kind) = snapshot.surrounding_word(position);
20733 if kind != Some(CharKind::Word) {
20734 return None;
20735 }
20736 Some(
20737 snapshot.anchor_before(range.start)
20738 ..snapshot.anchor_after(range.end),
20739 )
20740 })?
20741 }
20742 })
20743 })
20744 }))
20745 }
20746
20747 fn perform_rename(
20748 &self,
20749 buffer: &Entity<Buffer>,
20750 position: text::Anchor,
20751 new_name: String,
20752 cx: &mut App,
20753 ) -> Option<Task<Result<ProjectTransaction>>> {
20754 Some(self.update(cx, |project, cx| {
20755 project.perform_rename(buffer.clone(), position, new_name, cx)
20756 }))
20757 }
20758}
20759
20760fn inlay_hint_settings(
20761 location: Anchor,
20762 snapshot: &MultiBufferSnapshot,
20763 cx: &mut Context<Editor>,
20764) -> InlayHintSettings {
20765 let file = snapshot.file_at(location);
20766 let language = snapshot.language_at(location).map(|l| l.name());
20767 language_settings(language, file, cx).inlay_hints
20768}
20769
20770fn consume_contiguous_rows(
20771 contiguous_row_selections: &mut Vec<Selection<Point>>,
20772 selection: &Selection<Point>,
20773 display_map: &DisplaySnapshot,
20774 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
20775) -> (MultiBufferRow, MultiBufferRow) {
20776 contiguous_row_selections.push(selection.clone());
20777 let start_row = MultiBufferRow(selection.start.row);
20778 let mut end_row = ending_row(selection, display_map);
20779
20780 while let Some(next_selection) = selections.peek() {
20781 if next_selection.start.row <= end_row.0 {
20782 end_row = ending_row(next_selection, display_map);
20783 contiguous_row_selections.push(selections.next().unwrap().clone());
20784 } else {
20785 break;
20786 }
20787 }
20788 (start_row, end_row)
20789}
20790
20791fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
20792 if next_selection.end.column > 0 || next_selection.is_empty() {
20793 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
20794 } else {
20795 MultiBufferRow(next_selection.end.row)
20796 }
20797}
20798
20799impl EditorSnapshot {
20800 pub fn remote_selections_in_range<'a>(
20801 &'a self,
20802 range: &'a Range<Anchor>,
20803 collaboration_hub: &dyn CollaborationHub,
20804 cx: &'a App,
20805 ) -> impl 'a + Iterator<Item = RemoteSelection> {
20806 let participant_names = collaboration_hub.user_names(cx);
20807 let participant_indices = collaboration_hub.user_participant_indices(cx);
20808 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20809 let collaborators_by_replica_id = collaborators_by_peer_id
20810 .values()
20811 .map(|collaborator| (collaborator.replica_id, collaborator))
20812 .collect::<HashMap<_, _>>();
20813 self.buffer_snapshot
20814 .selections_in_range(range, false)
20815 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20816 if replica_id == AGENT_REPLICA_ID {
20817 Some(RemoteSelection {
20818 replica_id,
20819 selection,
20820 cursor_shape,
20821 line_mode,
20822 collaborator_id: CollaboratorId::Agent,
20823 user_name: Some("Agent".into()),
20824 color: cx.theme().players().agent(),
20825 })
20826 } else {
20827 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20828 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20829 let user_name = participant_names.get(&collaborator.user_id).cloned();
20830 Some(RemoteSelection {
20831 replica_id,
20832 selection,
20833 cursor_shape,
20834 line_mode,
20835 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20836 user_name,
20837 color: if let Some(index) = participant_index {
20838 cx.theme().players().color_for_participant(index.0)
20839 } else {
20840 cx.theme().players().absent()
20841 },
20842 })
20843 }
20844 })
20845 }
20846
20847 pub fn hunks_for_ranges(
20848 &self,
20849 ranges: impl IntoIterator<Item = Range<Point>>,
20850 ) -> Vec<MultiBufferDiffHunk> {
20851 let mut hunks = Vec::new();
20852 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20853 HashMap::default();
20854 for query_range in ranges {
20855 let query_rows =
20856 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20857 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20858 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20859 ) {
20860 // Include deleted hunks that are adjacent to the query range, because
20861 // otherwise they would be missed.
20862 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20863 if hunk.status().is_deleted() {
20864 intersects_range |= hunk.row_range.start == query_rows.end;
20865 intersects_range |= hunk.row_range.end == query_rows.start;
20866 }
20867 if intersects_range {
20868 if !processed_buffer_rows
20869 .entry(hunk.buffer_id)
20870 .or_default()
20871 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20872 {
20873 continue;
20874 }
20875 hunks.push(hunk);
20876 }
20877 }
20878 }
20879
20880 hunks
20881 }
20882
20883 fn display_diff_hunks_for_rows<'a>(
20884 &'a self,
20885 display_rows: Range<DisplayRow>,
20886 folded_buffers: &'a HashSet<BufferId>,
20887 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20888 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20889 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20890
20891 self.buffer_snapshot
20892 .diff_hunks_in_range(buffer_start..buffer_end)
20893 .filter_map(|hunk| {
20894 if folded_buffers.contains(&hunk.buffer_id) {
20895 return None;
20896 }
20897
20898 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20899 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20900
20901 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20902 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20903
20904 let display_hunk = if hunk_display_start.column() != 0 {
20905 DisplayDiffHunk::Folded {
20906 display_row: hunk_display_start.row(),
20907 }
20908 } else {
20909 let mut end_row = hunk_display_end.row();
20910 if hunk_display_end.column() > 0 {
20911 end_row.0 += 1;
20912 }
20913 let is_created_file = hunk.is_created_file();
20914 DisplayDiffHunk::Unfolded {
20915 status: hunk.status(),
20916 diff_base_byte_range: hunk.diff_base_byte_range,
20917 display_row_range: hunk_display_start.row()..end_row,
20918 multi_buffer_range: Anchor::range_in_buffer(
20919 hunk.excerpt_id,
20920 hunk.buffer_id,
20921 hunk.buffer_range,
20922 ),
20923 is_created_file,
20924 }
20925 };
20926
20927 Some(display_hunk)
20928 })
20929 }
20930
20931 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20932 self.display_snapshot.buffer_snapshot.language_at(position)
20933 }
20934
20935 pub fn is_focused(&self) -> bool {
20936 self.is_focused
20937 }
20938
20939 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20940 self.placeholder_text.as_ref()
20941 }
20942
20943 pub fn scroll_position(&self) -> gpui::Point<f32> {
20944 self.scroll_anchor.scroll_position(&self.display_snapshot)
20945 }
20946
20947 fn gutter_dimensions(
20948 &self,
20949 font_id: FontId,
20950 font_size: Pixels,
20951 max_line_number_width: Pixels,
20952 cx: &App,
20953 ) -> Option<GutterDimensions> {
20954 if !self.show_gutter {
20955 return None;
20956 }
20957
20958 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20959 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20960
20961 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20962 matches!(
20963 ProjectSettings::get_global(cx).git.git_gutter,
20964 Some(GitGutterSetting::TrackedFiles)
20965 )
20966 });
20967 let gutter_settings = EditorSettings::get_global(cx).gutter;
20968 let show_line_numbers = self
20969 .show_line_numbers
20970 .unwrap_or(gutter_settings.line_numbers);
20971 let line_gutter_width = if show_line_numbers {
20972 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20973 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20974 max_line_number_width.max(min_width_for_number_on_gutter)
20975 } else {
20976 0.0.into()
20977 };
20978
20979 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20980 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20981
20982 let git_blame_entries_width =
20983 self.git_blame_gutter_max_author_length
20984 .map(|max_author_length| {
20985 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20986 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20987
20988 /// The number of characters to dedicate to gaps and margins.
20989 const SPACING_WIDTH: usize = 4;
20990
20991 let max_char_count = max_author_length.min(renderer.max_author_length())
20992 + ::git::SHORT_SHA_LENGTH
20993 + MAX_RELATIVE_TIMESTAMP.len()
20994 + SPACING_WIDTH;
20995
20996 em_advance * max_char_count
20997 });
20998
20999 let is_singleton = self.buffer_snapshot.is_singleton();
21000
21001 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
21002 left_padding += if !is_singleton {
21003 em_width * 4.0
21004 } else if show_runnables || show_breakpoints {
21005 em_width * 3.0
21006 } else if show_git_gutter && show_line_numbers {
21007 em_width * 2.0
21008 } else if show_git_gutter || show_line_numbers {
21009 em_width
21010 } else {
21011 px(0.)
21012 };
21013
21014 let shows_folds = is_singleton && gutter_settings.folds;
21015
21016 let right_padding = if shows_folds && show_line_numbers {
21017 em_width * 4.0
21018 } else if shows_folds || (!is_singleton && show_line_numbers) {
21019 em_width * 3.0
21020 } else if show_line_numbers {
21021 em_width
21022 } else {
21023 px(0.)
21024 };
21025
21026 Some(GutterDimensions {
21027 left_padding,
21028 right_padding,
21029 width: line_gutter_width + left_padding + right_padding,
21030 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
21031 git_blame_entries_width,
21032 })
21033 }
21034
21035 pub fn render_crease_toggle(
21036 &self,
21037 buffer_row: MultiBufferRow,
21038 row_contains_cursor: bool,
21039 editor: Entity<Editor>,
21040 window: &mut Window,
21041 cx: &mut App,
21042 ) -> Option<AnyElement> {
21043 let folded = self.is_line_folded(buffer_row);
21044 let mut is_foldable = false;
21045
21046 if let Some(crease) = self
21047 .crease_snapshot
21048 .query_row(buffer_row, &self.buffer_snapshot)
21049 {
21050 is_foldable = true;
21051 match crease {
21052 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
21053 if let Some(render_toggle) = render_toggle {
21054 let toggle_callback =
21055 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
21056 if folded {
21057 editor.update(cx, |editor, cx| {
21058 editor.fold_at(buffer_row, window, cx)
21059 });
21060 } else {
21061 editor.update(cx, |editor, cx| {
21062 editor.unfold_at(buffer_row, window, cx)
21063 });
21064 }
21065 });
21066 return Some((render_toggle)(
21067 buffer_row,
21068 folded,
21069 toggle_callback,
21070 window,
21071 cx,
21072 ));
21073 }
21074 }
21075 }
21076 }
21077
21078 is_foldable |= self.starts_indent(buffer_row);
21079
21080 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
21081 Some(
21082 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
21083 .toggle_state(folded)
21084 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
21085 if folded {
21086 this.unfold_at(buffer_row, window, cx);
21087 } else {
21088 this.fold_at(buffer_row, window, cx);
21089 }
21090 }))
21091 .into_any_element(),
21092 )
21093 } else {
21094 None
21095 }
21096 }
21097
21098 pub fn render_crease_trailer(
21099 &self,
21100 buffer_row: MultiBufferRow,
21101 window: &mut Window,
21102 cx: &mut App,
21103 ) -> Option<AnyElement> {
21104 let folded = self.is_line_folded(buffer_row);
21105 if let Crease::Inline { render_trailer, .. } = self
21106 .crease_snapshot
21107 .query_row(buffer_row, &self.buffer_snapshot)?
21108 {
21109 let render_trailer = render_trailer.as_ref()?;
21110 Some(render_trailer(buffer_row, folded, window, cx))
21111 } else {
21112 None
21113 }
21114 }
21115}
21116
21117impl Deref for EditorSnapshot {
21118 type Target = DisplaySnapshot;
21119
21120 fn deref(&self) -> &Self::Target {
21121 &self.display_snapshot
21122 }
21123}
21124
21125#[derive(Clone, Debug, PartialEq, Eq)]
21126pub enum EditorEvent {
21127 InputIgnored {
21128 text: Arc<str>,
21129 },
21130 InputHandled {
21131 utf16_range_to_replace: Option<Range<isize>>,
21132 text: Arc<str>,
21133 },
21134 ExcerptsAdded {
21135 buffer: Entity<Buffer>,
21136 predecessor: ExcerptId,
21137 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21138 },
21139 ExcerptsRemoved {
21140 ids: Vec<ExcerptId>,
21141 removed_buffer_ids: Vec<BufferId>,
21142 },
21143 BufferFoldToggled {
21144 ids: Vec<ExcerptId>,
21145 folded: bool,
21146 },
21147 ExcerptsEdited {
21148 ids: Vec<ExcerptId>,
21149 },
21150 ExcerptsExpanded {
21151 ids: Vec<ExcerptId>,
21152 },
21153 BufferEdited,
21154 Edited {
21155 transaction_id: clock::Lamport,
21156 },
21157 Reparsed(BufferId),
21158 Focused,
21159 FocusedIn,
21160 Blurred,
21161 DirtyChanged,
21162 Saved,
21163 TitleChanged,
21164 DiffBaseChanged,
21165 SelectionsChanged {
21166 local: bool,
21167 },
21168 ScrollPositionChanged {
21169 local: bool,
21170 autoscroll: bool,
21171 },
21172 Closed,
21173 TransactionUndone {
21174 transaction_id: clock::Lamport,
21175 },
21176 TransactionBegun {
21177 transaction_id: clock::Lamport,
21178 },
21179 Reloaded,
21180 CursorShapeChanged,
21181 PushedToNavHistory {
21182 anchor: Anchor,
21183 is_deactivate: bool,
21184 },
21185}
21186
21187impl EventEmitter<EditorEvent> for Editor {}
21188
21189impl Focusable for Editor {
21190 fn focus_handle(&self, _cx: &App) -> FocusHandle {
21191 self.focus_handle.clone()
21192 }
21193}
21194
21195impl Render for Editor {
21196 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21197 let settings = ThemeSettings::get_global(cx);
21198
21199 let mut text_style = match self.mode {
21200 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21201 color: cx.theme().colors().editor_foreground,
21202 font_family: settings.ui_font.family.clone(),
21203 font_features: settings.ui_font.features.clone(),
21204 font_fallbacks: settings.ui_font.fallbacks.clone(),
21205 font_size: rems(0.875).into(),
21206 font_weight: settings.ui_font.weight,
21207 line_height: relative(settings.buffer_line_height.value()),
21208 ..Default::default()
21209 },
21210 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
21211 color: cx.theme().colors().editor_foreground,
21212 font_family: settings.buffer_font.family.clone(),
21213 font_features: settings.buffer_font.features.clone(),
21214 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21215 font_size: settings.buffer_font_size(cx).into(),
21216 font_weight: settings.buffer_font.weight,
21217 line_height: relative(settings.buffer_line_height.value()),
21218 ..Default::default()
21219 },
21220 };
21221 if let Some(text_style_refinement) = &self.text_style_refinement {
21222 text_style.refine(text_style_refinement)
21223 }
21224
21225 let background = match self.mode {
21226 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
21227 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
21228 EditorMode::Full { .. } => cx.theme().colors().editor_background,
21229 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
21230 };
21231
21232 EditorElement::new(
21233 &cx.entity(),
21234 EditorStyle {
21235 background,
21236 local_player: cx.theme().players().local(),
21237 text: text_style,
21238 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
21239 syntax: cx.theme().syntax().clone(),
21240 status: cx.theme().status().clone(),
21241 inlay_hints_style: make_inlay_hints_style(cx),
21242 inline_completion_styles: make_suggestion_styles(cx),
21243 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
21244 show_underlines: !self.mode.is_minimap(),
21245 },
21246 )
21247 }
21248}
21249
21250impl EntityInputHandler for Editor {
21251 fn text_for_range(
21252 &mut self,
21253 range_utf16: Range<usize>,
21254 adjusted_range: &mut Option<Range<usize>>,
21255 _: &mut Window,
21256 cx: &mut Context<Self>,
21257 ) -> Option<String> {
21258 let snapshot = self.buffer.read(cx).read(cx);
21259 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
21260 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
21261 if (start.0..end.0) != range_utf16 {
21262 adjusted_range.replace(start.0..end.0);
21263 }
21264 Some(snapshot.text_for_range(start..end).collect())
21265 }
21266
21267 fn selected_text_range(
21268 &mut self,
21269 ignore_disabled_input: bool,
21270 _: &mut Window,
21271 cx: &mut Context<Self>,
21272 ) -> Option<UTF16Selection> {
21273 // Prevent the IME menu from appearing when holding down an alphabetic key
21274 // while input is disabled.
21275 if !ignore_disabled_input && !self.input_enabled {
21276 return None;
21277 }
21278
21279 let selection = self.selections.newest::<OffsetUtf16>(cx);
21280 let range = selection.range();
21281
21282 Some(UTF16Selection {
21283 range: range.start.0..range.end.0,
21284 reversed: selection.reversed,
21285 })
21286 }
21287
21288 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
21289 let snapshot = self.buffer.read(cx).read(cx);
21290 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
21291 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
21292 }
21293
21294 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21295 self.clear_highlights::<InputComposition>(cx);
21296 self.ime_transaction.take();
21297 }
21298
21299 fn replace_text_in_range(
21300 &mut self,
21301 range_utf16: Option<Range<usize>>,
21302 text: &str,
21303 window: &mut Window,
21304 cx: &mut Context<Self>,
21305 ) {
21306 if !self.input_enabled {
21307 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21308 return;
21309 }
21310
21311 self.transact(window, cx, |this, window, cx| {
21312 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
21313 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21314 Some(this.selection_replacement_ranges(range_utf16, cx))
21315 } else {
21316 this.marked_text_ranges(cx)
21317 };
21318
21319 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
21320 let newest_selection_id = this.selections.newest_anchor().id;
21321 this.selections
21322 .all::<OffsetUtf16>(cx)
21323 .iter()
21324 .zip(ranges_to_replace.iter())
21325 .find_map(|(selection, range)| {
21326 if selection.id == newest_selection_id {
21327 Some(
21328 (range.start.0 as isize - selection.head().0 as isize)
21329 ..(range.end.0 as isize - selection.head().0 as isize),
21330 )
21331 } else {
21332 None
21333 }
21334 })
21335 });
21336
21337 cx.emit(EditorEvent::InputHandled {
21338 utf16_range_to_replace: range_to_replace,
21339 text: text.into(),
21340 });
21341
21342 if let Some(new_selected_ranges) = new_selected_ranges {
21343 this.change_selections(None, window, cx, |selections| {
21344 selections.select_ranges(new_selected_ranges)
21345 });
21346 this.backspace(&Default::default(), window, cx);
21347 }
21348
21349 this.handle_input(text, window, cx);
21350 });
21351
21352 if let Some(transaction) = self.ime_transaction {
21353 self.buffer.update(cx, |buffer, cx| {
21354 buffer.group_until_transaction(transaction, cx);
21355 });
21356 }
21357
21358 self.unmark_text(window, cx);
21359 }
21360
21361 fn replace_and_mark_text_in_range(
21362 &mut self,
21363 range_utf16: Option<Range<usize>>,
21364 text: &str,
21365 new_selected_range_utf16: Option<Range<usize>>,
21366 window: &mut Window,
21367 cx: &mut Context<Self>,
21368 ) {
21369 if !self.input_enabled {
21370 return;
21371 }
21372
21373 let transaction = self.transact(window, cx, |this, window, cx| {
21374 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
21375 let snapshot = this.buffer.read(cx).read(cx);
21376 if let Some(relative_range_utf16) = range_utf16.as_ref() {
21377 for marked_range in &mut marked_ranges {
21378 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
21379 marked_range.start.0 += relative_range_utf16.start;
21380 marked_range.start =
21381 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
21382 marked_range.end =
21383 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
21384 }
21385 }
21386 Some(marked_ranges)
21387 } else if let Some(range_utf16) = range_utf16 {
21388 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21389 Some(this.selection_replacement_ranges(range_utf16, cx))
21390 } else {
21391 None
21392 };
21393
21394 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
21395 let newest_selection_id = this.selections.newest_anchor().id;
21396 this.selections
21397 .all::<OffsetUtf16>(cx)
21398 .iter()
21399 .zip(ranges_to_replace.iter())
21400 .find_map(|(selection, range)| {
21401 if selection.id == newest_selection_id {
21402 Some(
21403 (range.start.0 as isize - selection.head().0 as isize)
21404 ..(range.end.0 as isize - selection.head().0 as isize),
21405 )
21406 } else {
21407 None
21408 }
21409 })
21410 });
21411
21412 cx.emit(EditorEvent::InputHandled {
21413 utf16_range_to_replace: range_to_replace,
21414 text: text.into(),
21415 });
21416
21417 if let Some(ranges) = ranges_to_replace {
21418 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
21419 }
21420
21421 let marked_ranges = {
21422 let snapshot = this.buffer.read(cx).read(cx);
21423 this.selections
21424 .disjoint_anchors()
21425 .iter()
21426 .map(|selection| {
21427 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
21428 })
21429 .collect::<Vec<_>>()
21430 };
21431
21432 if text.is_empty() {
21433 this.unmark_text(window, cx);
21434 } else {
21435 this.highlight_text::<InputComposition>(
21436 marked_ranges.clone(),
21437 HighlightStyle {
21438 underline: Some(UnderlineStyle {
21439 thickness: px(1.),
21440 color: None,
21441 wavy: false,
21442 }),
21443 ..Default::default()
21444 },
21445 cx,
21446 );
21447 }
21448
21449 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
21450 let use_autoclose = this.use_autoclose;
21451 let use_auto_surround = this.use_auto_surround;
21452 this.set_use_autoclose(false);
21453 this.set_use_auto_surround(false);
21454 this.handle_input(text, window, cx);
21455 this.set_use_autoclose(use_autoclose);
21456 this.set_use_auto_surround(use_auto_surround);
21457
21458 if let Some(new_selected_range) = new_selected_range_utf16 {
21459 let snapshot = this.buffer.read(cx).read(cx);
21460 let new_selected_ranges = marked_ranges
21461 .into_iter()
21462 .map(|marked_range| {
21463 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
21464 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
21465 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
21466 snapshot.clip_offset_utf16(new_start, Bias::Left)
21467 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
21468 })
21469 .collect::<Vec<_>>();
21470
21471 drop(snapshot);
21472 this.change_selections(None, window, cx, |selections| {
21473 selections.select_ranges(new_selected_ranges)
21474 });
21475 }
21476 });
21477
21478 self.ime_transaction = self.ime_transaction.or(transaction);
21479 if let Some(transaction) = self.ime_transaction {
21480 self.buffer.update(cx, |buffer, cx| {
21481 buffer.group_until_transaction(transaction, cx);
21482 });
21483 }
21484
21485 if self.text_highlights::<InputComposition>(cx).is_none() {
21486 self.ime_transaction.take();
21487 }
21488 }
21489
21490 fn bounds_for_range(
21491 &mut self,
21492 range_utf16: Range<usize>,
21493 element_bounds: gpui::Bounds<Pixels>,
21494 window: &mut Window,
21495 cx: &mut Context<Self>,
21496 ) -> Option<gpui::Bounds<Pixels>> {
21497 let text_layout_details = self.text_layout_details(window);
21498 let gpui::Size {
21499 width: em_width,
21500 height: line_height,
21501 } = self.character_size(window);
21502
21503 let snapshot = self.snapshot(window, cx);
21504 let scroll_position = snapshot.scroll_position();
21505 let scroll_left = scroll_position.x * em_width;
21506
21507 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
21508 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
21509 + self.gutter_dimensions.width
21510 + self.gutter_dimensions.margin;
21511 let y = line_height * (start.row().as_f32() - scroll_position.y);
21512
21513 Some(Bounds {
21514 origin: element_bounds.origin + point(x, y),
21515 size: size(em_width, line_height),
21516 })
21517 }
21518
21519 fn character_index_for_point(
21520 &mut self,
21521 point: gpui::Point<Pixels>,
21522 _window: &mut Window,
21523 _cx: &mut Context<Self>,
21524 ) -> Option<usize> {
21525 let position_map = self.last_position_map.as_ref()?;
21526 if !position_map.text_hitbox.contains(&point) {
21527 return None;
21528 }
21529 let display_point = position_map.point_for_position(point).previous_valid;
21530 let anchor = position_map
21531 .snapshot
21532 .display_point_to_anchor(display_point, Bias::Left);
21533 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
21534 Some(utf16_offset.0)
21535 }
21536}
21537
21538trait SelectionExt {
21539 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
21540 fn spanned_rows(
21541 &self,
21542 include_end_if_at_line_start: bool,
21543 map: &DisplaySnapshot,
21544 ) -> Range<MultiBufferRow>;
21545}
21546
21547impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
21548 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
21549 let start = self
21550 .start
21551 .to_point(&map.buffer_snapshot)
21552 .to_display_point(map);
21553 let end = self
21554 .end
21555 .to_point(&map.buffer_snapshot)
21556 .to_display_point(map);
21557 if self.reversed {
21558 end..start
21559 } else {
21560 start..end
21561 }
21562 }
21563
21564 fn spanned_rows(
21565 &self,
21566 include_end_if_at_line_start: bool,
21567 map: &DisplaySnapshot,
21568 ) -> Range<MultiBufferRow> {
21569 let start = self.start.to_point(&map.buffer_snapshot);
21570 let mut end = self.end.to_point(&map.buffer_snapshot);
21571 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
21572 end.row -= 1;
21573 }
21574
21575 let buffer_start = map.prev_line_boundary(start).0;
21576 let buffer_end = map.next_line_boundary(end).0;
21577 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
21578 }
21579}
21580
21581impl<T: InvalidationRegion> InvalidationStack<T> {
21582 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
21583 where
21584 S: Clone + ToOffset,
21585 {
21586 while let Some(region) = self.last() {
21587 let all_selections_inside_invalidation_ranges =
21588 if selections.len() == region.ranges().len() {
21589 selections
21590 .iter()
21591 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
21592 .all(|(selection, invalidation_range)| {
21593 let head = selection.head().to_offset(buffer);
21594 invalidation_range.start <= head && invalidation_range.end >= head
21595 })
21596 } else {
21597 false
21598 };
21599
21600 if all_selections_inside_invalidation_ranges {
21601 break;
21602 } else {
21603 self.pop();
21604 }
21605 }
21606 }
21607}
21608
21609impl<T> Default for InvalidationStack<T> {
21610 fn default() -> Self {
21611 Self(Default::default())
21612 }
21613}
21614
21615impl<T> Deref for InvalidationStack<T> {
21616 type Target = Vec<T>;
21617
21618 fn deref(&self) -> &Self::Target {
21619 &self.0
21620 }
21621}
21622
21623impl<T> DerefMut for InvalidationStack<T> {
21624 fn deref_mut(&mut self) -> &mut Self::Target {
21625 &mut self.0
21626 }
21627}
21628
21629impl InvalidationRegion for SnippetState {
21630 fn ranges(&self) -> &[Range<Anchor>] {
21631 &self.ranges[self.active_index]
21632 }
21633}
21634
21635fn inline_completion_edit_text(
21636 current_snapshot: &BufferSnapshot,
21637 edits: &[(Range<Anchor>, String)],
21638 edit_preview: &EditPreview,
21639 include_deletions: bool,
21640 cx: &App,
21641) -> HighlightedText {
21642 let edits = edits
21643 .iter()
21644 .map(|(anchor, text)| {
21645 (
21646 anchor.start.text_anchor..anchor.end.text_anchor,
21647 text.clone(),
21648 )
21649 })
21650 .collect::<Vec<_>>();
21651
21652 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
21653}
21654
21655pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
21656 match severity {
21657 lsp::DiagnosticSeverity::ERROR => colors.error,
21658 lsp::DiagnosticSeverity::WARNING => colors.warning,
21659 lsp::DiagnosticSeverity::INFORMATION => colors.info,
21660 lsp::DiagnosticSeverity::HINT => colors.info,
21661 _ => colors.ignored,
21662 }
21663}
21664
21665pub fn styled_runs_for_code_label<'a>(
21666 label: &'a CodeLabel,
21667 syntax_theme: &'a theme::SyntaxTheme,
21668) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
21669 let fade_out = HighlightStyle {
21670 fade_out: Some(0.35),
21671 ..Default::default()
21672 };
21673
21674 let mut prev_end = label.filter_range.end;
21675 label
21676 .runs
21677 .iter()
21678 .enumerate()
21679 .flat_map(move |(ix, (range, highlight_id))| {
21680 let style = if let Some(style) = highlight_id.style(syntax_theme) {
21681 style
21682 } else {
21683 return Default::default();
21684 };
21685 let mut muted_style = style;
21686 muted_style.highlight(fade_out);
21687
21688 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
21689 if range.start >= label.filter_range.end {
21690 if range.start > prev_end {
21691 runs.push((prev_end..range.start, fade_out));
21692 }
21693 runs.push((range.clone(), muted_style));
21694 } else if range.end <= label.filter_range.end {
21695 runs.push((range.clone(), style));
21696 } else {
21697 runs.push((range.start..label.filter_range.end, style));
21698 runs.push((label.filter_range.end..range.end, muted_style));
21699 }
21700 prev_end = cmp::max(prev_end, range.end);
21701
21702 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
21703 runs.push((prev_end..label.text.len(), fade_out));
21704 }
21705
21706 runs
21707 })
21708}
21709
21710pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
21711 let mut prev_index = 0;
21712 let mut prev_codepoint: Option<char> = None;
21713 text.char_indices()
21714 .chain([(text.len(), '\0')])
21715 .filter_map(move |(index, codepoint)| {
21716 let prev_codepoint = prev_codepoint.replace(codepoint)?;
21717 let is_boundary = index == text.len()
21718 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
21719 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
21720 if is_boundary {
21721 let chunk = &text[prev_index..index];
21722 prev_index = index;
21723 Some(chunk)
21724 } else {
21725 None
21726 }
21727 })
21728}
21729
21730pub trait RangeToAnchorExt: Sized {
21731 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
21732
21733 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
21734 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
21735 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
21736 }
21737}
21738
21739impl<T: ToOffset> RangeToAnchorExt for Range<T> {
21740 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
21741 let start_offset = self.start.to_offset(snapshot);
21742 let end_offset = self.end.to_offset(snapshot);
21743 if start_offset == end_offset {
21744 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
21745 } else {
21746 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
21747 }
21748 }
21749}
21750
21751pub trait RowExt {
21752 fn as_f32(&self) -> f32;
21753
21754 fn next_row(&self) -> Self;
21755
21756 fn previous_row(&self) -> Self;
21757
21758 fn minus(&self, other: Self) -> u32;
21759}
21760
21761impl RowExt for DisplayRow {
21762 fn as_f32(&self) -> f32 {
21763 self.0 as f32
21764 }
21765
21766 fn next_row(&self) -> Self {
21767 Self(self.0 + 1)
21768 }
21769
21770 fn previous_row(&self) -> Self {
21771 Self(self.0.saturating_sub(1))
21772 }
21773
21774 fn minus(&self, other: Self) -> u32 {
21775 self.0 - other.0
21776 }
21777}
21778
21779impl RowExt for MultiBufferRow {
21780 fn as_f32(&self) -> f32 {
21781 self.0 as f32
21782 }
21783
21784 fn next_row(&self) -> Self {
21785 Self(self.0 + 1)
21786 }
21787
21788 fn previous_row(&self) -> Self {
21789 Self(self.0.saturating_sub(1))
21790 }
21791
21792 fn minus(&self, other: Self) -> u32 {
21793 self.0 - other.0
21794 }
21795}
21796
21797trait RowRangeExt {
21798 type Row;
21799
21800 fn len(&self) -> usize;
21801
21802 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
21803}
21804
21805impl RowRangeExt for Range<MultiBufferRow> {
21806 type Row = MultiBufferRow;
21807
21808 fn len(&self) -> usize {
21809 (self.end.0 - self.start.0) as usize
21810 }
21811
21812 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21813 (self.start.0..self.end.0).map(MultiBufferRow)
21814 }
21815}
21816
21817impl RowRangeExt for Range<DisplayRow> {
21818 type Row = DisplayRow;
21819
21820 fn len(&self) -> usize {
21821 (self.end.0 - self.start.0) as usize
21822 }
21823
21824 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21825 (self.start.0..self.end.0).map(DisplayRow)
21826 }
21827}
21828
21829/// If select range has more than one line, we
21830/// just point the cursor to range.start.
21831fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21832 if range.start.row == range.end.row {
21833 range
21834 } else {
21835 range.start..range.start
21836 }
21837}
21838pub struct KillRing(ClipboardItem);
21839impl Global for KillRing {}
21840
21841const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21842
21843enum BreakpointPromptEditAction {
21844 Log,
21845 Condition,
21846 HitCondition,
21847}
21848
21849struct BreakpointPromptEditor {
21850 pub(crate) prompt: Entity<Editor>,
21851 editor: WeakEntity<Editor>,
21852 breakpoint_anchor: Anchor,
21853 breakpoint: Breakpoint,
21854 edit_action: BreakpointPromptEditAction,
21855 block_ids: HashSet<CustomBlockId>,
21856 editor_margins: Arc<Mutex<EditorMargins>>,
21857 _subscriptions: Vec<Subscription>,
21858}
21859
21860impl BreakpointPromptEditor {
21861 const MAX_LINES: u8 = 4;
21862
21863 fn new(
21864 editor: WeakEntity<Editor>,
21865 breakpoint_anchor: Anchor,
21866 breakpoint: Breakpoint,
21867 edit_action: BreakpointPromptEditAction,
21868 window: &mut Window,
21869 cx: &mut Context<Self>,
21870 ) -> Self {
21871 let base_text = match edit_action {
21872 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21873 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21874 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21875 }
21876 .map(|msg| msg.to_string())
21877 .unwrap_or_default();
21878
21879 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21880 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21881
21882 let prompt = cx.new(|cx| {
21883 let mut prompt = Editor::new(
21884 EditorMode::AutoHeight {
21885 max_lines: Self::MAX_LINES as usize,
21886 },
21887 buffer,
21888 None,
21889 window,
21890 cx,
21891 );
21892 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21893 prompt.set_show_cursor_when_unfocused(false, cx);
21894 prompt.set_placeholder_text(
21895 match edit_action {
21896 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21897 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21898 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21899 },
21900 cx,
21901 );
21902
21903 prompt
21904 });
21905
21906 Self {
21907 prompt,
21908 editor,
21909 breakpoint_anchor,
21910 breakpoint,
21911 edit_action,
21912 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21913 block_ids: Default::default(),
21914 _subscriptions: vec![],
21915 }
21916 }
21917
21918 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21919 self.block_ids.extend(block_ids)
21920 }
21921
21922 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21923 if let Some(editor) = self.editor.upgrade() {
21924 let message = self
21925 .prompt
21926 .read(cx)
21927 .buffer
21928 .read(cx)
21929 .as_singleton()
21930 .expect("A multi buffer in breakpoint prompt isn't possible")
21931 .read(cx)
21932 .as_rope()
21933 .to_string();
21934
21935 editor.update(cx, |editor, cx| {
21936 editor.edit_breakpoint_at_anchor(
21937 self.breakpoint_anchor,
21938 self.breakpoint.clone(),
21939 match self.edit_action {
21940 BreakpointPromptEditAction::Log => {
21941 BreakpointEditAction::EditLogMessage(message.into())
21942 }
21943 BreakpointPromptEditAction::Condition => {
21944 BreakpointEditAction::EditCondition(message.into())
21945 }
21946 BreakpointPromptEditAction::HitCondition => {
21947 BreakpointEditAction::EditHitCondition(message.into())
21948 }
21949 },
21950 cx,
21951 );
21952
21953 editor.remove_blocks(self.block_ids.clone(), None, cx);
21954 cx.focus_self(window);
21955 });
21956 }
21957 }
21958
21959 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21960 self.editor
21961 .update(cx, |editor, cx| {
21962 editor.remove_blocks(self.block_ids.clone(), None, cx);
21963 window.focus(&editor.focus_handle);
21964 })
21965 .log_err();
21966 }
21967
21968 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21969 let settings = ThemeSettings::get_global(cx);
21970 let text_style = TextStyle {
21971 color: if self.prompt.read(cx).read_only(cx) {
21972 cx.theme().colors().text_disabled
21973 } else {
21974 cx.theme().colors().text
21975 },
21976 font_family: settings.buffer_font.family.clone(),
21977 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21978 font_size: settings.buffer_font_size(cx).into(),
21979 font_weight: settings.buffer_font.weight,
21980 line_height: relative(settings.buffer_line_height.value()),
21981 ..Default::default()
21982 };
21983 EditorElement::new(
21984 &self.prompt,
21985 EditorStyle {
21986 background: cx.theme().colors().editor_background,
21987 local_player: cx.theme().players().local(),
21988 text: text_style,
21989 ..Default::default()
21990 },
21991 )
21992 }
21993}
21994
21995impl Render for BreakpointPromptEditor {
21996 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21997 let editor_margins = *self.editor_margins.lock();
21998 let gutter_dimensions = editor_margins.gutter;
21999 h_flex()
22000 .key_context("Editor")
22001 .bg(cx.theme().colors().editor_background)
22002 .border_y_1()
22003 .border_color(cx.theme().status().info_border)
22004 .size_full()
22005 .py(window.line_height() / 2.5)
22006 .on_action(cx.listener(Self::confirm))
22007 .on_action(cx.listener(Self::cancel))
22008 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
22009 .child(div().flex_1().child(self.render_prompt_editor(cx)))
22010 }
22011}
22012
22013impl Focusable for BreakpointPromptEditor {
22014 fn focus_handle(&self, cx: &App) -> FocusHandle {
22015 self.prompt.focus_handle(cx)
22016 }
22017}
22018
22019fn all_edits_insertions_or_deletions(
22020 edits: &Vec<(Range<Anchor>, String)>,
22021 snapshot: &MultiBufferSnapshot,
22022) -> bool {
22023 let mut all_insertions = true;
22024 let mut all_deletions = true;
22025
22026 for (range, new_text) in edits.iter() {
22027 let range_is_empty = range.to_offset(&snapshot).is_empty();
22028 let text_is_empty = new_text.is_empty();
22029
22030 if range_is_empty != text_is_empty {
22031 if range_is_empty {
22032 all_deletions = false;
22033 } else {
22034 all_insertions = false;
22035 }
22036 } else {
22037 return false;
22038 }
22039
22040 if !all_insertions && !all_deletions {
22041 return false;
22042 }
22043 }
22044 all_insertions || all_deletions
22045}
22046
22047struct MissingEditPredictionKeybindingTooltip;
22048
22049impl Render for MissingEditPredictionKeybindingTooltip {
22050 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22051 ui::tooltip_container(window, cx, |container, _, cx| {
22052 container
22053 .flex_shrink_0()
22054 .max_w_80()
22055 .min_h(rems_from_px(124.))
22056 .justify_between()
22057 .child(
22058 v_flex()
22059 .flex_1()
22060 .text_ui_sm(cx)
22061 .child(Label::new("Conflict with Accept Keybinding"))
22062 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
22063 )
22064 .child(
22065 h_flex()
22066 .pb_1()
22067 .gap_1()
22068 .items_end()
22069 .w_full()
22070 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
22071 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
22072 }))
22073 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
22074 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
22075 })),
22076 )
22077 })
22078 }
22079}
22080
22081#[derive(Debug, Clone, Copy, PartialEq)]
22082pub struct LineHighlight {
22083 pub background: Background,
22084 pub border: Option<gpui::Hsla>,
22085 pub include_gutter: bool,
22086 pub type_id: Option<TypeId>,
22087}
22088
22089fn render_diff_hunk_controls(
22090 row: u32,
22091 status: &DiffHunkStatus,
22092 hunk_range: Range<Anchor>,
22093 is_created_file: bool,
22094 line_height: Pixels,
22095 editor: &Entity<Editor>,
22096 _window: &mut Window,
22097 cx: &mut App,
22098) -> AnyElement {
22099 h_flex()
22100 .h(line_height)
22101 .mr_1()
22102 .gap_1()
22103 .px_0p5()
22104 .pb_1()
22105 .border_x_1()
22106 .border_b_1()
22107 .border_color(cx.theme().colors().border_variant)
22108 .rounded_b_lg()
22109 .bg(cx.theme().colors().editor_background)
22110 .gap_1()
22111 .block_mouse_except_scroll()
22112 .shadow_md()
22113 .child(if status.has_secondary_hunk() {
22114 Button::new(("stage", row as u64), "Stage")
22115 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22116 .tooltip({
22117 let focus_handle = editor.focus_handle(cx);
22118 move |window, cx| {
22119 Tooltip::for_action_in(
22120 "Stage Hunk",
22121 &::git::ToggleStaged,
22122 &focus_handle,
22123 window,
22124 cx,
22125 )
22126 }
22127 })
22128 .on_click({
22129 let editor = editor.clone();
22130 move |_event, _window, cx| {
22131 editor.update(cx, |editor, cx| {
22132 editor.stage_or_unstage_diff_hunks(
22133 true,
22134 vec![hunk_range.start..hunk_range.start],
22135 cx,
22136 );
22137 });
22138 }
22139 })
22140 } else {
22141 Button::new(("unstage", row as u64), "Unstage")
22142 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22143 .tooltip({
22144 let focus_handle = editor.focus_handle(cx);
22145 move |window, cx| {
22146 Tooltip::for_action_in(
22147 "Unstage Hunk",
22148 &::git::ToggleStaged,
22149 &focus_handle,
22150 window,
22151 cx,
22152 )
22153 }
22154 })
22155 .on_click({
22156 let editor = editor.clone();
22157 move |_event, _window, cx| {
22158 editor.update(cx, |editor, cx| {
22159 editor.stage_or_unstage_diff_hunks(
22160 false,
22161 vec![hunk_range.start..hunk_range.start],
22162 cx,
22163 );
22164 });
22165 }
22166 })
22167 })
22168 .child(
22169 Button::new(("restore", row as u64), "Restore")
22170 .tooltip({
22171 let focus_handle = editor.focus_handle(cx);
22172 move |window, cx| {
22173 Tooltip::for_action_in(
22174 "Restore Hunk",
22175 &::git::Restore,
22176 &focus_handle,
22177 window,
22178 cx,
22179 )
22180 }
22181 })
22182 .on_click({
22183 let editor = editor.clone();
22184 move |_event, window, cx| {
22185 editor.update(cx, |editor, cx| {
22186 let snapshot = editor.snapshot(window, cx);
22187 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
22188 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
22189 });
22190 }
22191 })
22192 .disabled(is_created_file),
22193 )
22194 .when(
22195 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
22196 |el| {
22197 el.child(
22198 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
22199 .shape(IconButtonShape::Square)
22200 .icon_size(IconSize::Small)
22201 // .disabled(!has_multiple_hunks)
22202 .tooltip({
22203 let focus_handle = editor.focus_handle(cx);
22204 move |window, cx| {
22205 Tooltip::for_action_in(
22206 "Next Hunk",
22207 &GoToHunk,
22208 &focus_handle,
22209 window,
22210 cx,
22211 )
22212 }
22213 })
22214 .on_click({
22215 let editor = editor.clone();
22216 move |_event, window, cx| {
22217 editor.update(cx, |editor, cx| {
22218 let snapshot = editor.snapshot(window, cx);
22219 let position =
22220 hunk_range.end.to_point(&snapshot.buffer_snapshot);
22221 editor.go_to_hunk_before_or_after_position(
22222 &snapshot,
22223 position,
22224 Direction::Next,
22225 window,
22226 cx,
22227 );
22228 editor.expand_selected_diff_hunks(cx);
22229 });
22230 }
22231 }),
22232 )
22233 .child(
22234 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
22235 .shape(IconButtonShape::Square)
22236 .icon_size(IconSize::Small)
22237 // .disabled(!has_multiple_hunks)
22238 .tooltip({
22239 let focus_handle = editor.focus_handle(cx);
22240 move |window, cx| {
22241 Tooltip::for_action_in(
22242 "Previous Hunk",
22243 &GoToPreviousHunk,
22244 &focus_handle,
22245 window,
22246 cx,
22247 )
22248 }
22249 })
22250 .on_click({
22251 let editor = editor.clone();
22252 move |_event, window, cx| {
22253 editor.update(cx, |editor, cx| {
22254 let snapshot = editor.snapshot(window, cx);
22255 let point =
22256 hunk_range.start.to_point(&snapshot.buffer_snapshot);
22257 editor.go_to_hunk_before_or_after_position(
22258 &snapshot,
22259 point,
22260 Direction::Prev,
22261 window,
22262 cx,
22263 );
22264 editor.expand_selected_diff_hunks(cx);
22265 });
22266 }
22267 }),
22268 )
22269 },
22270 )
22271 .into_any_element()
22272}