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 let mut common_prefix_len = 0;
5535 for (a, b) in old_text.chars().zip(new_text.chars()) {
5536 if a == b {
5537 common_prefix_len += a.len_utf8();
5538 } else {
5539 break;
5540 }
5541 }
5542
5543 cx.emit(EditorEvent::InputHandled {
5544 utf16_range_to_replace: None,
5545 text: new_text[common_prefix_len..].into(),
5546 });
5547
5548 self.transact(window, cx, |this, window, cx| {
5549 if let Some(mut snippet) = snippet {
5550 snippet.text = new_text.to_string();
5551 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5552 } else {
5553 this.buffer.update(cx, |buffer, cx| {
5554 let auto_indent = match completion.insert_text_mode {
5555 Some(InsertTextMode::AS_IS) => None,
5556 _ => this.autoindent_mode.clone(),
5557 };
5558 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5559 buffer.edit(edits, auto_indent, cx);
5560 });
5561 }
5562 for (buffer, edits) in linked_edits {
5563 buffer.update(cx, |buffer, cx| {
5564 let snapshot = buffer.snapshot();
5565 let edits = edits
5566 .into_iter()
5567 .map(|(range, text)| {
5568 use text::ToPoint as TP;
5569 let end_point = TP::to_point(&range.end, &snapshot);
5570 let start_point = TP::to_point(&range.start, &snapshot);
5571 (start_point..end_point, text)
5572 })
5573 .sorted_by_key(|(range, _)| range.start);
5574 buffer.edit(edits, None, cx);
5575 })
5576 }
5577
5578 this.refresh_inline_completion(true, false, window, cx);
5579 });
5580
5581 let show_new_completions_on_confirm = completion
5582 .confirm
5583 .as_ref()
5584 .map_or(false, |confirm| confirm(intent, window, cx));
5585 if show_new_completions_on_confirm {
5586 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5587 }
5588
5589 let provider = self.completion_provider.as_ref()?;
5590 drop(completion);
5591 let apply_edits = provider.apply_additional_edits_for_completion(
5592 buffer_handle,
5593 completions_menu.completions.clone(),
5594 candidate_id,
5595 true,
5596 cx,
5597 );
5598
5599 let editor_settings = EditorSettings::get_global(cx);
5600 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5601 // After the code completion is finished, users often want to know what signatures are needed.
5602 // so we should automatically call signature_help
5603 self.show_signature_help(&ShowSignatureHelp, window, cx);
5604 }
5605
5606 Some(cx.foreground_executor().spawn(async move {
5607 apply_edits.await?;
5608 Ok(())
5609 }))
5610 }
5611
5612 pub fn toggle_code_actions(
5613 &mut self,
5614 action: &ToggleCodeActions,
5615 window: &mut Window,
5616 cx: &mut Context<Self>,
5617 ) {
5618 let quick_launch = action.quick_launch;
5619 let mut context_menu = self.context_menu.borrow_mut();
5620 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5621 if code_actions.deployed_from == action.deployed_from {
5622 // Toggle if we're selecting the same one
5623 *context_menu = None;
5624 cx.notify();
5625 return;
5626 } else {
5627 // Otherwise, clear it and start a new one
5628 *context_menu = None;
5629 cx.notify();
5630 }
5631 }
5632 drop(context_menu);
5633 let snapshot = self.snapshot(window, cx);
5634 let deployed_from = action.deployed_from.clone();
5635 let mut task = self.code_actions_task.take();
5636 let action = action.clone();
5637 cx.spawn_in(window, async move |editor, cx| {
5638 while let Some(prev_task) = task {
5639 prev_task.await.log_err();
5640 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5641 }
5642
5643 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5644 if editor.focus_handle.is_focused(window) {
5645 let multibuffer_point = match &action.deployed_from {
5646 Some(CodeActionSource::Indicator(row)) => {
5647 DisplayPoint::new(*row, 0).to_point(&snapshot)
5648 }
5649 _ => editor.selections.newest::<Point>(cx).head(),
5650 };
5651 let (buffer, buffer_row) = snapshot
5652 .buffer_snapshot
5653 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5654 .and_then(|(buffer_snapshot, range)| {
5655 editor
5656 .buffer
5657 .read(cx)
5658 .buffer(buffer_snapshot.remote_id())
5659 .map(|buffer| (buffer, range.start.row))
5660 })?;
5661 let (_, code_actions) = editor
5662 .available_code_actions
5663 .clone()
5664 .and_then(|(location, code_actions)| {
5665 let snapshot = location.buffer.read(cx).snapshot();
5666 let point_range = location.range.to_point(&snapshot);
5667 let point_range = point_range.start.row..=point_range.end.row;
5668 if point_range.contains(&buffer_row) {
5669 Some((location, code_actions))
5670 } else {
5671 None
5672 }
5673 })
5674 .unzip();
5675 let buffer_id = buffer.read(cx).remote_id();
5676 let tasks = editor
5677 .tasks
5678 .get(&(buffer_id, buffer_row))
5679 .map(|t| Arc::new(t.to_owned()));
5680 if tasks.is_none() && code_actions.is_none() {
5681 return None;
5682 }
5683
5684 editor.completion_tasks.clear();
5685 editor.discard_inline_completion(false, cx);
5686 let task_context =
5687 tasks
5688 .as_ref()
5689 .zip(editor.project.clone())
5690 .map(|(tasks, project)| {
5691 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5692 });
5693
5694 Some(cx.spawn_in(window, async move |editor, cx| {
5695 let task_context = match task_context {
5696 Some(task_context) => task_context.await,
5697 None => None,
5698 };
5699 let resolved_tasks =
5700 tasks
5701 .zip(task_context.clone())
5702 .map(|(tasks, task_context)| ResolvedTasks {
5703 templates: tasks.resolve(&task_context).collect(),
5704 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5705 multibuffer_point.row,
5706 tasks.column,
5707 )),
5708 });
5709 let debug_scenarios = editor.update(cx, |editor, cx| {
5710 if cx.has_flag::<DebuggerFeatureFlag>() {
5711 maybe!({
5712 let project = editor.project.as_ref()?;
5713 let dap_store = project.read(cx).dap_store();
5714 let mut scenarios = vec![];
5715 let resolved_tasks = resolved_tasks.as_ref()?;
5716 let buffer = buffer.read(cx);
5717 let language = buffer.language()?;
5718 let file = buffer.file();
5719 let debug_adapter =
5720 language_settings(language.name().into(), file, cx)
5721 .debuggers
5722 .first()
5723 .map(SharedString::from)
5724 .or_else(|| {
5725 language
5726 .config()
5727 .debuggers
5728 .first()
5729 .map(SharedString::from)
5730 })?;
5731
5732 dap_store.update(cx, |dap_store, cx| {
5733 for (_, task) in &resolved_tasks.templates {
5734 if let Some(scenario) = dap_store
5735 .debug_scenario_for_build_task(
5736 task.original_task().clone(),
5737 debug_adapter.clone().into(),
5738 task.display_label().to_owned().into(),
5739 cx,
5740 )
5741 {
5742 scenarios.push(scenario);
5743 }
5744 }
5745 });
5746 Some(scenarios)
5747 })
5748 .unwrap_or_default()
5749 } else {
5750 vec![]
5751 }
5752 })?;
5753 let spawn_straight_away = quick_launch
5754 && resolved_tasks
5755 .as_ref()
5756 .map_or(false, |tasks| tasks.templates.len() == 1)
5757 && code_actions
5758 .as_ref()
5759 .map_or(true, |actions| actions.is_empty())
5760 && debug_scenarios.is_empty();
5761 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5762 crate::hover_popover::hide_hover(editor, cx);
5763 *editor.context_menu.borrow_mut() =
5764 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5765 buffer,
5766 actions: CodeActionContents::new(
5767 resolved_tasks,
5768 code_actions,
5769 debug_scenarios,
5770 task_context.unwrap_or_default(),
5771 ),
5772 selected_item: Default::default(),
5773 scroll_handle: UniformListScrollHandle::default(),
5774 deployed_from,
5775 }));
5776 if spawn_straight_away {
5777 if let Some(task) = editor.confirm_code_action(
5778 &ConfirmCodeAction { item_ix: Some(0) },
5779 window,
5780 cx,
5781 ) {
5782 cx.notify();
5783 return task;
5784 }
5785 }
5786 cx.notify();
5787 Task::ready(Ok(()))
5788 }) {
5789 task.await
5790 } else {
5791 Ok(())
5792 }
5793 }))
5794 } else {
5795 Some(Task::ready(Ok(())))
5796 }
5797 })?;
5798 if let Some(task) = spawned_test_task {
5799 task.await?;
5800 }
5801
5802 anyhow::Ok(())
5803 })
5804 .detach_and_log_err(cx);
5805 }
5806
5807 pub fn confirm_code_action(
5808 &mut self,
5809 action: &ConfirmCodeAction,
5810 window: &mut Window,
5811 cx: &mut Context<Self>,
5812 ) -> Option<Task<Result<()>>> {
5813 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5814
5815 let actions_menu =
5816 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5817 menu
5818 } else {
5819 return None;
5820 };
5821
5822 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5823 let action = actions_menu.actions.get(action_ix)?;
5824 let title = action.label();
5825 let buffer = actions_menu.buffer;
5826 let workspace = self.workspace()?;
5827
5828 match action {
5829 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5830 workspace.update(cx, |workspace, cx| {
5831 workspace.schedule_resolved_task(
5832 task_source_kind,
5833 resolved_task,
5834 false,
5835 window,
5836 cx,
5837 );
5838
5839 Some(Task::ready(Ok(())))
5840 })
5841 }
5842 CodeActionsItem::CodeAction {
5843 excerpt_id,
5844 action,
5845 provider,
5846 } => {
5847 let apply_code_action =
5848 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5849 let workspace = workspace.downgrade();
5850 Some(cx.spawn_in(window, async move |editor, cx| {
5851 let project_transaction = apply_code_action.await?;
5852 Self::open_project_transaction(
5853 &editor,
5854 workspace,
5855 project_transaction,
5856 title,
5857 cx,
5858 )
5859 .await
5860 }))
5861 }
5862 CodeActionsItem::DebugScenario(scenario) => {
5863 let context = actions_menu.actions.context.clone();
5864
5865 workspace.update(cx, |workspace, cx| {
5866 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
5867 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5868 });
5869 Some(Task::ready(Ok(())))
5870 }
5871 }
5872 }
5873
5874 pub async fn open_project_transaction(
5875 this: &WeakEntity<Editor>,
5876 workspace: WeakEntity<Workspace>,
5877 transaction: ProjectTransaction,
5878 title: String,
5879 cx: &mut AsyncWindowContext,
5880 ) -> Result<()> {
5881 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5882 cx.update(|_, cx| {
5883 entries.sort_unstable_by_key(|(buffer, _)| {
5884 buffer.read(cx).file().map(|f| f.path().clone())
5885 });
5886 })?;
5887
5888 // If the project transaction's edits are all contained within this editor, then
5889 // avoid opening a new editor to display them.
5890
5891 if let Some((buffer, transaction)) = entries.first() {
5892 if entries.len() == 1 {
5893 let excerpt = this.update(cx, |editor, cx| {
5894 editor
5895 .buffer()
5896 .read(cx)
5897 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5898 })?;
5899 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5900 if excerpted_buffer == *buffer {
5901 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5902 let excerpt_range = excerpt_range.to_offset(buffer);
5903 buffer
5904 .edited_ranges_for_transaction::<usize>(transaction)
5905 .all(|range| {
5906 excerpt_range.start <= range.start
5907 && excerpt_range.end >= range.end
5908 })
5909 })?;
5910
5911 if all_edits_within_excerpt {
5912 return Ok(());
5913 }
5914 }
5915 }
5916 }
5917 } else {
5918 return Ok(());
5919 }
5920
5921 let mut ranges_to_highlight = Vec::new();
5922 let excerpt_buffer = cx.new(|cx| {
5923 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5924 for (buffer_handle, transaction) in &entries {
5925 let edited_ranges = buffer_handle
5926 .read(cx)
5927 .edited_ranges_for_transaction::<Point>(transaction)
5928 .collect::<Vec<_>>();
5929 let (ranges, _) = multibuffer.set_excerpts_for_path(
5930 PathKey::for_buffer(buffer_handle, cx),
5931 buffer_handle.clone(),
5932 edited_ranges,
5933 DEFAULT_MULTIBUFFER_CONTEXT,
5934 cx,
5935 );
5936
5937 ranges_to_highlight.extend(ranges);
5938 }
5939 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5940 multibuffer
5941 })?;
5942
5943 workspace.update_in(cx, |workspace, window, cx| {
5944 let project = workspace.project().clone();
5945 let editor =
5946 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5947 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5948 editor.update(cx, |editor, cx| {
5949 editor.highlight_background::<Self>(
5950 &ranges_to_highlight,
5951 |theme| theme.editor_highlighted_line_background,
5952 cx,
5953 );
5954 });
5955 })?;
5956
5957 Ok(())
5958 }
5959
5960 pub fn clear_code_action_providers(&mut self) {
5961 self.code_action_providers.clear();
5962 self.available_code_actions.take();
5963 }
5964
5965 pub fn add_code_action_provider(
5966 &mut self,
5967 provider: Rc<dyn CodeActionProvider>,
5968 window: &mut Window,
5969 cx: &mut Context<Self>,
5970 ) {
5971 if self
5972 .code_action_providers
5973 .iter()
5974 .any(|existing_provider| existing_provider.id() == provider.id())
5975 {
5976 return;
5977 }
5978
5979 self.code_action_providers.push(provider);
5980 self.refresh_code_actions(window, cx);
5981 }
5982
5983 pub fn remove_code_action_provider(
5984 &mut self,
5985 id: Arc<str>,
5986 window: &mut Window,
5987 cx: &mut Context<Self>,
5988 ) {
5989 self.code_action_providers
5990 .retain(|provider| provider.id() != id);
5991 self.refresh_code_actions(window, cx);
5992 }
5993
5994 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
5995 !self.code_action_providers.is_empty()
5996 && EditorSettings::get_global(cx).toolbar.code_actions
5997 }
5998
5999 pub fn has_available_code_actions(&self) -> bool {
6000 self.available_code_actions
6001 .as_ref()
6002 .is_some_and(|(_, actions)| !actions.is_empty())
6003 }
6004
6005 fn render_inline_code_actions(
6006 &self,
6007 icon_size: ui::IconSize,
6008 display_row: DisplayRow,
6009 is_active: bool,
6010 cx: &mut Context<Self>,
6011 ) -> AnyElement {
6012 let show_tooltip = !self.context_menu_visible();
6013 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6014 .icon_size(icon_size)
6015 .shape(ui::IconButtonShape::Square)
6016 .style(ButtonStyle::Transparent)
6017 .icon_color(ui::Color::Hidden)
6018 .toggle_state(is_active)
6019 .when(show_tooltip, |this| {
6020 this.tooltip({
6021 let focus_handle = self.focus_handle.clone();
6022 move |window, cx| {
6023 Tooltip::for_action_in(
6024 "Toggle Code Actions",
6025 &ToggleCodeActions {
6026 deployed_from: None,
6027 quick_launch: false,
6028 },
6029 &focus_handle,
6030 window,
6031 cx,
6032 )
6033 }
6034 })
6035 })
6036 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6037 window.focus(&editor.focus_handle(cx));
6038 editor.toggle_code_actions(
6039 &crate::actions::ToggleCodeActions {
6040 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6041 display_row,
6042 )),
6043 quick_launch: false,
6044 },
6045 window,
6046 cx,
6047 );
6048 }))
6049 .into_any_element()
6050 }
6051
6052 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6053 &self.context_menu
6054 }
6055
6056 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6057 let newest_selection = self.selections.newest_anchor().clone();
6058 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6059 let buffer = self.buffer.read(cx);
6060 if newest_selection.head().diff_base_anchor.is_some() {
6061 return None;
6062 }
6063 let (start_buffer, start) =
6064 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6065 let (end_buffer, end) =
6066 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6067 if start_buffer != end_buffer {
6068 return None;
6069 }
6070
6071 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6072 cx.background_executor()
6073 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6074 .await;
6075
6076 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6077 let providers = this.code_action_providers.clone();
6078 let tasks = this
6079 .code_action_providers
6080 .iter()
6081 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6082 .collect::<Vec<_>>();
6083 (providers, tasks)
6084 })?;
6085
6086 let mut actions = Vec::new();
6087 for (provider, provider_actions) in
6088 providers.into_iter().zip(future::join_all(tasks).await)
6089 {
6090 if let Some(provider_actions) = provider_actions.log_err() {
6091 actions.extend(provider_actions.into_iter().map(|action| {
6092 AvailableCodeAction {
6093 excerpt_id: newest_selection.start.excerpt_id,
6094 action,
6095 provider: provider.clone(),
6096 }
6097 }));
6098 }
6099 }
6100
6101 this.update(cx, |this, cx| {
6102 this.available_code_actions = if actions.is_empty() {
6103 None
6104 } else {
6105 Some((
6106 Location {
6107 buffer: start_buffer,
6108 range: start..end,
6109 },
6110 actions.into(),
6111 ))
6112 };
6113 cx.notify();
6114 })
6115 }));
6116 None
6117 }
6118
6119 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6120 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6121 self.show_git_blame_inline = false;
6122
6123 self.show_git_blame_inline_delay_task =
6124 Some(cx.spawn_in(window, async move |this, cx| {
6125 cx.background_executor().timer(delay).await;
6126
6127 this.update(cx, |this, cx| {
6128 this.show_git_blame_inline = true;
6129 cx.notify();
6130 })
6131 .log_err();
6132 }));
6133 }
6134 }
6135
6136 fn show_blame_popover(
6137 &mut self,
6138 blame_entry: &BlameEntry,
6139 position: gpui::Point<Pixels>,
6140 cx: &mut Context<Self>,
6141 ) {
6142 if let Some(state) = &mut self.inline_blame_popover {
6143 state.hide_task.take();
6144 cx.notify();
6145 } else {
6146 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6147 let show_task = cx.spawn(async move |editor, cx| {
6148 cx.background_executor()
6149 .timer(std::time::Duration::from_millis(delay))
6150 .await;
6151 editor
6152 .update(cx, |editor, cx| {
6153 if let Some(state) = &mut editor.inline_blame_popover {
6154 state.show_task = None;
6155 cx.notify();
6156 }
6157 })
6158 .ok();
6159 });
6160 let Some(blame) = self.blame.as_ref() else {
6161 return;
6162 };
6163 let blame = blame.read(cx);
6164 let details = blame.details_for_entry(&blame_entry);
6165 let markdown = cx.new(|cx| {
6166 Markdown::new(
6167 details
6168 .as_ref()
6169 .map(|message| message.message.clone())
6170 .unwrap_or_default(),
6171 None,
6172 None,
6173 cx,
6174 )
6175 });
6176 self.inline_blame_popover = Some(InlineBlamePopover {
6177 position,
6178 show_task: Some(show_task),
6179 hide_task: None,
6180 popover_bounds: None,
6181 popover_state: InlineBlamePopoverState {
6182 scroll_handle: ScrollHandle::new(),
6183 commit_message: details,
6184 markdown,
6185 },
6186 });
6187 }
6188 }
6189
6190 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6191 if let Some(state) = &mut self.inline_blame_popover {
6192 if state.show_task.is_some() {
6193 self.inline_blame_popover.take();
6194 cx.notify();
6195 } else {
6196 let hide_task = cx.spawn(async move |editor, cx| {
6197 cx.background_executor()
6198 .timer(std::time::Duration::from_millis(100))
6199 .await;
6200 editor
6201 .update(cx, |editor, cx| {
6202 editor.inline_blame_popover.take();
6203 cx.notify();
6204 })
6205 .ok();
6206 });
6207 state.hide_task = Some(hide_task);
6208 }
6209 }
6210 }
6211
6212 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6213 if self.pending_rename.is_some() {
6214 return None;
6215 }
6216
6217 let provider = self.semantics_provider.clone()?;
6218 let buffer = self.buffer.read(cx);
6219 let newest_selection = self.selections.newest_anchor().clone();
6220 let cursor_position = newest_selection.head();
6221 let (cursor_buffer, cursor_buffer_position) =
6222 buffer.text_anchor_for_position(cursor_position, cx)?;
6223 let (tail_buffer, tail_buffer_position) =
6224 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6225 if cursor_buffer != tail_buffer {
6226 return None;
6227 }
6228
6229 let snapshot = cursor_buffer.read(cx).snapshot();
6230 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6231 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6232 if start_word_range != end_word_range {
6233 self.document_highlights_task.take();
6234 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6235 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6236 return None;
6237 }
6238
6239 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6240 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6241 cx.background_executor()
6242 .timer(Duration::from_millis(debounce))
6243 .await;
6244
6245 let highlights = if let Some(highlights) = cx
6246 .update(|cx| {
6247 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6248 })
6249 .ok()
6250 .flatten()
6251 {
6252 highlights.await.log_err()
6253 } else {
6254 None
6255 };
6256
6257 if let Some(highlights) = highlights {
6258 this.update(cx, |this, cx| {
6259 if this.pending_rename.is_some() {
6260 return;
6261 }
6262
6263 let buffer_id = cursor_position.buffer_id;
6264 let buffer = this.buffer.read(cx);
6265 if !buffer
6266 .text_anchor_for_position(cursor_position, cx)
6267 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6268 {
6269 return;
6270 }
6271
6272 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6273 let mut write_ranges = Vec::new();
6274 let mut read_ranges = Vec::new();
6275 for highlight in highlights {
6276 for (excerpt_id, excerpt_range) in
6277 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6278 {
6279 let start = highlight
6280 .range
6281 .start
6282 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6283 let end = highlight
6284 .range
6285 .end
6286 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6287 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6288 continue;
6289 }
6290
6291 let range = Anchor {
6292 buffer_id,
6293 excerpt_id,
6294 text_anchor: start,
6295 diff_base_anchor: None,
6296 }..Anchor {
6297 buffer_id,
6298 excerpt_id,
6299 text_anchor: end,
6300 diff_base_anchor: None,
6301 };
6302 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6303 write_ranges.push(range);
6304 } else {
6305 read_ranges.push(range);
6306 }
6307 }
6308 }
6309
6310 this.highlight_background::<DocumentHighlightRead>(
6311 &read_ranges,
6312 |theme| theme.editor_document_highlight_read_background,
6313 cx,
6314 );
6315 this.highlight_background::<DocumentHighlightWrite>(
6316 &write_ranges,
6317 |theme| theme.editor_document_highlight_write_background,
6318 cx,
6319 );
6320 cx.notify();
6321 })
6322 .log_err();
6323 }
6324 }));
6325 None
6326 }
6327
6328 fn prepare_highlight_query_from_selection(
6329 &mut self,
6330 cx: &mut Context<Editor>,
6331 ) -> Option<(String, Range<Anchor>)> {
6332 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6333 return None;
6334 }
6335 if !EditorSettings::get_global(cx).selection_highlight {
6336 return None;
6337 }
6338 if self.selections.count() != 1 || self.selections.line_mode {
6339 return None;
6340 }
6341 let selection = self.selections.newest::<Point>(cx);
6342 if selection.is_empty() || selection.start.row != selection.end.row {
6343 return None;
6344 }
6345 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6346 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6347 let query = multi_buffer_snapshot
6348 .text_for_range(selection_anchor_range.clone())
6349 .collect::<String>();
6350 if query.trim().is_empty() {
6351 return None;
6352 }
6353 Some((query, selection_anchor_range))
6354 }
6355
6356 fn update_selection_occurrence_highlights(
6357 &mut self,
6358 query_text: String,
6359 query_range: Range<Anchor>,
6360 multi_buffer_range_to_query: Range<Point>,
6361 use_debounce: bool,
6362 window: &mut Window,
6363 cx: &mut Context<Editor>,
6364 ) -> Task<()> {
6365 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6366 cx.spawn_in(window, async move |editor, cx| {
6367 if use_debounce {
6368 cx.background_executor()
6369 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6370 .await;
6371 }
6372 let match_task = cx.background_spawn(async move {
6373 let buffer_ranges = multi_buffer_snapshot
6374 .range_to_buffer_ranges(multi_buffer_range_to_query)
6375 .into_iter()
6376 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6377 let mut match_ranges = Vec::new();
6378 let Ok(regex) = project::search::SearchQuery::text(
6379 query_text.clone(),
6380 false,
6381 false,
6382 false,
6383 Default::default(),
6384 Default::default(),
6385 false,
6386 None,
6387 ) else {
6388 return Vec::default();
6389 };
6390 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6391 match_ranges.extend(
6392 regex
6393 .search(&buffer_snapshot, Some(search_range.clone()))
6394 .await
6395 .into_iter()
6396 .filter_map(|match_range| {
6397 let match_start = buffer_snapshot
6398 .anchor_after(search_range.start + match_range.start);
6399 let match_end = buffer_snapshot
6400 .anchor_before(search_range.start + match_range.end);
6401 let match_anchor_range = Anchor::range_in_buffer(
6402 excerpt_id,
6403 buffer_snapshot.remote_id(),
6404 match_start..match_end,
6405 );
6406 (match_anchor_range != query_range).then_some(match_anchor_range)
6407 }),
6408 );
6409 }
6410 match_ranges
6411 });
6412 let match_ranges = match_task.await;
6413 editor
6414 .update_in(cx, |editor, _, cx| {
6415 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6416 if !match_ranges.is_empty() {
6417 editor.highlight_background::<SelectedTextHighlight>(
6418 &match_ranges,
6419 |theme| theme.editor_document_highlight_bracket_background,
6420 cx,
6421 )
6422 }
6423 })
6424 .log_err();
6425 })
6426 }
6427
6428 fn refresh_selected_text_highlights(
6429 &mut self,
6430 on_buffer_edit: bool,
6431 window: &mut Window,
6432 cx: &mut Context<Editor>,
6433 ) {
6434 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6435 else {
6436 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6437 self.quick_selection_highlight_task.take();
6438 self.debounced_selection_highlight_task.take();
6439 return;
6440 };
6441 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6442 if on_buffer_edit
6443 || self
6444 .quick_selection_highlight_task
6445 .as_ref()
6446 .map_or(true, |(prev_anchor_range, _)| {
6447 prev_anchor_range != &query_range
6448 })
6449 {
6450 let multi_buffer_visible_start = self
6451 .scroll_manager
6452 .anchor()
6453 .anchor
6454 .to_point(&multi_buffer_snapshot);
6455 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6456 multi_buffer_visible_start
6457 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6458 Bias::Left,
6459 );
6460 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6461 self.quick_selection_highlight_task = Some((
6462 query_range.clone(),
6463 self.update_selection_occurrence_highlights(
6464 query_text.clone(),
6465 query_range.clone(),
6466 multi_buffer_visible_range,
6467 false,
6468 window,
6469 cx,
6470 ),
6471 ));
6472 }
6473 if on_buffer_edit
6474 || self
6475 .debounced_selection_highlight_task
6476 .as_ref()
6477 .map_or(true, |(prev_anchor_range, _)| {
6478 prev_anchor_range != &query_range
6479 })
6480 {
6481 let multi_buffer_start = multi_buffer_snapshot
6482 .anchor_before(0)
6483 .to_point(&multi_buffer_snapshot);
6484 let multi_buffer_end = multi_buffer_snapshot
6485 .anchor_after(multi_buffer_snapshot.len())
6486 .to_point(&multi_buffer_snapshot);
6487 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6488 self.debounced_selection_highlight_task = Some((
6489 query_range.clone(),
6490 self.update_selection_occurrence_highlights(
6491 query_text,
6492 query_range,
6493 multi_buffer_full_range,
6494 true,
6495 window,
6496 cx,
6497 ),
6498 ));
6499 }
6500 }
6501
6502 pub fn refresh_inline_completion(
6503 &mut self,
6504 debounce: bool,
6505 user_requested: bool,
6506 window: &mut Window,
6507 cx: &mut Context<Self>,
6508 ) -> Option<()> {
6509 let provider = self.edit_prediction_provider()?;
6510 let cursor = self.selections.newest_anchor().head();
6511 let (buffer, cursor_buffer_position) =
6512 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6513
6514 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6515 self.discard_inline_completion(false, cx);
6516 return None;
6517 }
6518
6519 if !user_requested
6520 && (!self.should_show_edit_predictions()
6521 || !self.is_focused(window)
6522 || buffer.read(cx).is_empty())
6523 {
6524 self.discard_inline_completion(false, cx);
6525 return None;
6526 }
6527
6528 self.update_visible_inline_completion(window, cx);
6529 provider.refresh(
6530 self.project.clone(),
6531 buffer,
6532 cursor_buffer_position,
6533 debounce,
6534 cx,
6535 );
6536 Some(())
6537 }
6538
6539 fn show_edit_predictions_in_menu(&self) -> bool {
6540 match self.edit_prediction_settings {
6541 EditPredictionSettings::Disabled => false,
6542 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6543 }
6544 }
6545
6546 pub fn edit_predictions_enabled(&self) -> bool {
6547 match self.edit_prediction_settings {
6548 EditPredictionSettings::Disabled => false,
6549 EditPredictionSettings::Enabled { .. } => true,
6550 }
6551 }
6552
6553 fn edit_prediction_requires_modifier(&self) -> bool {
6554 match self.edit_prediction_settings {
6555 EditPredictionSettings::Disabled => false,
6556 EditPredictionSettings::Enabled {
6557 preview_requires_modifier,
6558 ..
6559 } => preview_requires_modifier,
6560 }
6561 }
6562
6563 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6564 if self.edit_prediction_provider.is_none() {
6565 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6566 } else {
6567 let selection = self.selections.newest_anchor();
6568 let cursor = selection.head();
6569
6570 if let Some((buffer, cursor_buffer_position)) =
6571 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6572 {
6573 self.edit_prediction_settings =
6574 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6575 }
6576 }
6577 }
6578
6579 fn edit_prediction_settings_at_position(
6580 &self,
6581 buffer: &Entity<Buffer>,
6582 buffer_position: language::Anchor,
6583 cx: &App,
6584 ) -> EditPredictionSettings {
6585 if !self.mode.is_full()
6586 || !self.show_inline_completions_override.unwrap_or(true)
6587 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6588 {
6589 return EditPredictionSettings::Disabled;
6590 }
6591
6592 let buffer = buffer.read(cx);
6593
6594 let file = buffer.file();
6595
6596 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6597 return EditPredictionSettings::Disabled;
6598 };
6599
6600 let by_provider = matches!(
6601 self.menu_inline_completions_policy,
6602 MenuInlineCompletionsPolicy::ByProvider
6603 );
6604
6605 let show_in_menu = by_provider
6606 && self
6607 .edit_prediction_provider
6608 .as_ref()
6609 .map_or(false, |provider| {
6610 provider.provider.show_completions_in_menu()
6611 });
6612
6613 let preview_requires_modifier =
6614 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6615
6616 EditPredictionSettings::Enabled {
6617 show_in_menu,
6618 preview_requires_modifier,
6619 }
6620 }
6621
6622 fn should_show_edit_predictions(&self) -> bool {
6623 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6624 }
6625
6626 pub fn edit_prediction_preview_is_active(&self) -> bool {
6627 matches!(
6628 self.edit_prediction_preview,
6629 EditPredictionPreview::Active { .. }
6630 )
6631 }
6632
6633 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6634 let cursor = self.selections.newest_anchor().head();
6635 if let Some((buffer, cursor_position)) =
6636 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6637 {
6638 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6639 } else {
6640 false
6641 }
6642 }
6643
6644 pub fn supports_minimap(&self, cx: &App) -> bool {
6645 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6646 }
6647
6648 fn edit_predictions_enabled_in_buffer(
6649 &self,
6650 buffer: &Entity<Buffer>,
6651 buffer_position: language::Anchor,
6652 cx: &App,
6653 ) -> bool {
6654 maybe!({
6655 if self.read_only(cx) {
6656 return Some(false);
6657 }
6658 let provider = self.edit_prediction_provider()?;
6659 if !provider.is_enabled(&buffer, buffer_position, cx) {
6660 return Some(false);
6661 }
6662 let buffer = buffer.read(cx);
6663 let Some(file) = buffer.file() else {
6664 return Some(true);
6665 };
6666 let settings = all_language_settings(Some(file), cx);
6667 Some(settings.edit_predictions_enabled_for_file(file, cx))
6668 })
6669 .unwrap_or(false)
6670 }
6671
6672 fn cycle_inline_completion(
6673 &mut self,
6674 direction: Direction,
6675 window: &mut Window,
6676 cx: &mut Context<Self>,
6677 ) -> Option<()> {
6678 let provider = self.edit_prediction_provider()?;
6679 let cursor = self.selections.newest_anchor().head();
6680 let (buffer, cursor_buffer_position) =
6681 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6682 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6683 return None;
6684 }
6685
6686 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6687 self.update_visible_inline_completion(window, cx);
6688
6689 Some(())
6690 }
6691
6692 pub fn show_inline_completion(
6693 &mut self,
6694 _: &ShowEditPrediction,
6695 window: &mut Window,
6696 cx: &mut Context<Self>,
6697 ) {
6698 if !self.has_active_inline_completion() {
6699 self.refresh_inline_completion(false, true, window, cx);
6700 return;
6701 }
6702
6703 self.update_visible_inline_completion(window, cx);
6704 }
6705
6706 pub fn display_cursor_names(
6707 &mut self,
6708 _: &DisplayCursorNames,
6709 window: &mut Window,
6710 cx: &mut Context<Self>,
6711 ) {
6712 self.show_cursor_names(window, cx);
6713 }
6714
6715 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6716 self.show_cursor_names = true;
6717 cx.notify();
6718 cx.spawn_in(window, async move |this, cx| {
6719 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6720 this.update(cx, |this, cx| {
6721 this.show_cursor_names = false;
6722 cx.notify()
6723 })
6724 .ok()
6725 })
6726 .detach();
6727 }
6728
6729 pub fn next_edit_prediction(
6730 &mut self,
6731 _: &NextEditPrediction,
6732 window: &mut Window,
6733 cx: &mut Context<Self>,
6734 ) {
6735 if self.has_active_inline_completion() {
6736 self.cycle_inline_completion(Direction::Next, window, cx);
6737 } else {
6738 let is_copilot_disabled = self
6739 .refresh_inline_completion(false, true, window, cx)
6740 .is_none();
6741 if is_copilot_disabled {
6742 cx.propagate();
6743 }
6744 }
6745 }
6746
6747 pub fn previous_edit_prediction(
6748 &mut self,
6749 _: &PreviousEditPrediction,
6750 window: &mut Window,
6751 cx: &mut Context<Self>,
6752 ) {
6753 if self.has_active_inline_completion() {
6754 self.cycle_inline_completion(Direction::Prev, window, cx);
6755 } else {
6756 let is_copilot_disabled = self
6757 .refresh_inline_completion(false, true, window, cx)
6758 .is_none();
6759 if is_copilot_disabled {
6760 cx.propagate();
6761 }
6762 }
6763 }
6764
6765 pub fn accept_edit_prediction(
6766 &mut self,
6767 _: &AcceptEditPrediction,
6768 window: &mut Window,
6769 cx: &mut Context<Self>,
6770 ) {
6771 if self.show_edit_predictions_in_menu() {
6772 self.hide_context_menu(window, cx);
6773 }
6774
6775 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6776 return;
6777 };
6778
6779 self.report_inline_completion_event(
6780 active_inline_completion.completion_id.clone(),
6781 true,
6782 cx,
6783 );
6784
6785 match &active_inline_completion.completion {
6786 InlineCompletion::Move { target, .. } => {
6787 let target = *target;
6788
6789 if let Some(position_map) = &self.last_position_map {
6790 if position_map
6791 .visible_row_range
6792 .contains(&target.to_display_point(&position_map.snapshot).row())
6793 || !self.edit_prediction_requires_modifier()
6794 {
6795 self.unfold_ranges(&[target..target], true, false, cx);
6796 // Note that this is also done in vim's handler of the Tab action.
6797 self.change_selections(
6798 Some(Autoscroll::newest()),
6799 window,
6800 cx,
6801 |selections| {
6802 selections.select_anchor_ranges([target..target]);
6803 },
6804 );
6805 self.clear_row_highlights::<EditPredictionPreview>();
6806
6807 self.edit_prediction_preview
6808 .set_previous_scroll_position(None);
6809 } else {
6810 self.edit_prediction_preview
6811 .set_previous_scroll_position(Some(
6812 position_map.snapshot.scroll_anchor,
6813 ));
6814
6815 self.highlight_rows::<EditPredictionPreview>(
6816 target..target,
6817 cx.theme().colors().editor_highlighted_line_background,
6818 RowHighlightOptions {
6819 autoscroll: true,
6820 ..Default::default()
6821 },
6822 cx,
6823 );
6824 self.request_autoscroll(Autoscroll::fit(), cx);
6825 }
6826 }
6827 }
6828 InlineCompletion::Edit { edits, .. } => {
6829 if let Some(provider) = self.edit_prediction_provider() {
6830 provider.accept(cx);
6831 }
6832
6833 // Store the transaction ID and selections before applying the edit
6834 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
6835
6836 let snapshot = self.buffer.read(cx).snapshot(cx);
6837 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6838
6839 self.buffer.update(cx, |buffer, cx| {
6840 buffer.edit(edits.iter().cloned(), None, cx)
6841 });
6842
6843 self.change_selections(None, window, cx, |s| {
6844 s.select_anchor_ranges([last_edit_end..last_edit_end]);
6845 });
6846
6847 let selections = self.selections.disjoint_anchors();
6848 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
6849 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
6850 if has_new_transaction {
6851 self.selection_history
6852 .insert_transaction(transaction_id_now, selections);
6853 }
6854 }
6855
6856 self.update_visible_inline_completion(window, cx);
6857 if self.active_inline_completion.is_none() {
6858 self.refresh_inline_completion(true, true, window, cx);
6859 }
6860
6861 cx.notify();
6862 }
6863 }
6864
6865 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6866 }
6867
6868 pub fn accept_partial_inline_completion(
6869 &mut self,
6870 _: &AcceptPartialEditPrediction,
6871 window: &mut Window,
6872 cx: &mut Context<Self>,
6873 ) {
6874 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6875 return;
6876 };
6877 if self.selections.count() != 1 {
6878 return;
6879 }
6880
6881 self.report_inline_completion_event(
6882 active_inline_completion.completion_id.clone(),
6883 true,
6884 cx,
6885 );
6886
6887 match &active_inline_completion.completion {
6888 InlineCompletion::Move { target, .. } => {
6889 let target = *target;
6890 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6891 selections.select_anchor_ranges([target..target]);
6892 });
6893 }
6894 InlineCompletion::Edit { edits, .. } => {
6895 // Find an insertion that starts at the cursor position.
6896 let snapshot = self.buffer.read(cx).snapshot(cx);
6897 let cursor_offset = self.selections.newest::<usize>(cx).head();
6898 let insertion = edits.iter().find_map(|(range, text)| {
6899 let range = range.to_offset(&snapshot);
6900 if range.is_empty() && range.start == cursor_offset {
6901 Some(text)
6902 } else {
6903 None
6904 }
6905 });
6906
6907 if let Some(text) = insertion {
6908 let mut partial_completion = text
6909 .chars()
6910 .by_ref()
6911 .take_while(|c| c.is_alphabetic())
6912 .collect::<String>();
6913 if partial_completion.is_empty() {
6914 partial_completion = text
6915 .chars()
6916 .by_ref()
6917 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6918 .collect::<String>();
6919 }
6920
6921 cx.emit(EditorEvent::InputHandled {
6922 utf16_range_to_replace: None,
6923 text: partial_completion.clone().into(),
6924 });
6925
6926 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6927
6928 self.refresh_inline_completion(true, true, window, cx);
6929 cx.notify();
6930 } else {
6931 self.accept_edit_prediction(&Default::default(), window, cx);
6932 }
6933 }
6934 }
6935 }
6936
6937 fn discard_inline_completion(
6938 &mut self,
6939 should_report_inline_completion_event: bool,
6940 cx: &mut Context<Self>,
6941 ) -> bool {
6942 if should_report_inline_completion_event {
6943 let completion_id = self
6944 .active_inline_completion
6945 .as_ref()
6946 .and_then(|active_completion| active_completion.completion_id.clone());
6947
6948 self.report_inline_completion_event(completion_id, false, cx);
6949 }
6950
6951 if let Some(provider) = self.edit_prediction_provider() {
6952 provider.discard(cx);
6953 }
6954
6955 self.take_active_inline_completion(cx)
6956 }
6957
6958 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6959 let Some(provider) = self.edit_prediction_provider() else {
6960 return;
6961 };
6962
6963 let Some((_, buffer, _)) = self
6964 .buffer
6965 .read(cx)
6966 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6967 else {
6968 return;
6969 };
6970
6971 let extension = buffer
6972 .read(cx)
6973 .file()
6974 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6975
6976 let event_type = match accepted {
6977 true => "Edit Prediction Accepted",
6978 false => "Edit Prediction Discarded",
6979 };
6980 telemetry::event!(
6981 event_type,
6982 provider = provider.name(),
6983 prediction_id = id,
6984 suggestion_accepted = accepted,
6985 file_extension = extension,
6986 );
6987 }
6988
6989 pub fn has_active_inline_completion(&self) -> bool {
6990 self.active_inline_completion.is_some()
6991 }
6992
6993 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6994 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6995 return false;
6996 };
6997
6998 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6999 self.clear_highlights::<InlineCompletionHighlight>(cx);
7000 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7001 true
7002 }
7003
7004 /// Returns true when we're displaying the edit prediction popover below the cursor
7005 /// like we are not previewing and the LSP autocomplete menu is visible
7006 /// or we are in `when_holding_modifier` mode.
7007 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7008 if self.edit_prediction_preview_is_active()
7009 || !self.show_edit_predictions_in_menu()
7010 || !self.edit_predictions_enabled()
7011 {
7012 return false;
7013 }
7014
7015 if self.has_visible_completions_menu() {
7016 return true;
7017 }
7018
7019 has_completion && self.edit_prediction_requires_modifier()
7020 }
7021
7022 fn handle_modifiers_changed(
7023 &mut self,
7024 modifiers: Modifiers,
7025 position_map: &PositionMap,
7026 window: &mut Window,
7027 cx: &mut Context<Self>,
7028 ) {
7029 if self.show_edit_predictions_in_menu() {
7030 self.update_edit_prediction_preview(&modifiers, window, cx);
7031 }
7032
7033 self.update_selection_mode(&modifiers, position_map, window, cx);
7034
7035 let mouse_position = window.mouse_position();
7036 if !position_map.text_hitbox.is_hovered(window) {
7037 return;
7038 }
7039
7040 self.update_hovered_link(
7041 position_map.point_for_position(mouse_position),
7042 &position_map.snapshot,
7043 modifiers,
7044 window,
7045 cx,
7046 )
7047 }
7048
7049 fn update_selection_mode(
7050 &mut self,
7051 modifiers: &Modifiers,
7052 position_map: &PositionMap,
7053 window: &mut Window,
7054 cx: &mut Context<Self>,
7055 ) {
7056 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
7057 return;
7058 }
7059
7060 let mouse_position = window.mouse_position();
7061 let point_for_position = position_map.point_for_position(mouse_position);
7062 let position = point_for_position.previous_valid;
7063
7064 self.select(
7065 SelectPhase::BeginColumnar {
7066 position,
7067 reset: false,
7068 goal_column: point_for_position.exact_unclipped.column(),
7069 },
7070 window,
7071 cx,
7072 );
7073 }
7074
7075 fn update_edit_prediction_preview(
7076 &mut self,
7077 modifiers: &Modifiers,
7078 window: &mut Window,
7079 cx: &mut Context<Self>,
7080 ) {
7081 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
7082 let Some(accept_keystroke) = accept_keybind.keystroke() else {
7083 return;
7084 };
7085
7086 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
7087 if matches!(
7088 self.edit_prediction_preview,
7089 EditPredictionPreview::Inactive { .. }
7090 ) {
7091 self.edit_prediction_preview = EditPredictionPreview::Active {
7092 previous_scroll_position: None,
7093 since: Instant::now(),
7094 };
7095
7096 self.update_visible_inline_completion(window, cx);
7097 cx.notify();
7098 }
7099 } else if let EditPredictionPreview::Active {
7100 previous_scroll_position,
7101 since,
7102 } = self.edit_prediction_preview
7103 {
7104 if let (Some(previous_scroll_position), Some(position_map)) =
7105 (previous_scroll_position, self.last_position_map.as_ref())
7106 {
7107 self.set_scroll_position(
7108 previous_scroll_position
7109 .scroll_position(&position_map.snapshot.display_snapshot),
7110 window,
7111 cx,
7112 );
7113 }
7114
7115 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7116 released_too_fast: since.elapsed() < Duration::from_millis(200),
7117 };
7118 self.clear_row_highlights::<EditPredictionPreview>();
7119 self.update_visible_inline_completion(window, cx);
7120 cx.notify();
7121 }
7122 }
7123
7124 fn update_visible_inline_completion(
7125 &mut self,
7126 _window: &mut Window,
7127 cx: &mut Context<Self>,
7128 ) -> Option<()> {
7129 let selection = self.selections.newest_anchor();
7130 let cursor = selection.head();
7131 let multibuffer = self.buffer.read(cx).snapshot(cx);
7132 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7133 let excerpt_id = cursor.excerpt_id;
7134
7135 let show_in_menu = self.show_edit_predictions_in_menu();
7136 let completions_menu_has_precedence = !show_in_menu
7137 && (self.context_menu.borrow().is_some()
7138 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7139
7140 if completions_menu_has_precedence
7141 || !offset_selection.is_empty()
7142 || self
7143 .active_inline_completion
7144 .as_ref()
7145 .map_or(false, |completion| {
7146 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7147 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7148 !invalidation_range.contains(&offset_selection.head())
7149 })
7150 {
7151 self.discard_inline_completion(false, cx);
7152 return None;
7153 }
7154
7155 self.take_active_inline_completion(cx);
7156 let Some(provider) = self.edit_prediction_provider() else {
7157 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7158 return None;
7159 };
7160
7161 let (buffer, cursor_buffer_position) =
7162 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7163
7164 self.edit_prediction_settings =
7165 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7166
7167 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7168
7169 if self.edit_prediction_indent_conflict {
7170 let cursor_point = cursor.to_point(&multibuffer);
7171
7172 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7173
7174 if let Some((_, indent)) = indents.iter().next() {
7175 if indent.len == cursor_point.column {
7176 self.edit_prediction_indent_conflict = false;
7177 }
7178 }
7179 }
7180
7181 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7182 let edits = inline_completion
7183 .edits
7184 .into_iter()
7185 .flat_map(|(range, new_text)| {
7186 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7187 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7188 Some((start..end, new_text))
7189 })
7190 .collect::<Vec<_>>();
7191 if edits.is_empty() {
7192 return None;
7193 }
7194
7195 let first_edit_start = edits.first().unwrap().0.start;
7196 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7197 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7198
7199 let last_edit_end = edits.last().unwrap().0.end;
7200 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7201 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7202
7203 let cursor_row = cursor.to_point(&multibuffer).row;
7204
7205 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7206
7207 let mut inlay_ids = Vec::new();
7208 let invalidation_row_range;
7209 let move_invalidation_row_range = if cursor_row < edit_start_row {
7210 Some(cursor_row..edit_end_row)
7211 } else if cursor_row > edit_end_row {
7212 Some(edit_start_row..cursor_row)
7213 } else {
7214 None
7215 };
7216 let is_move =
7217 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7218 let completion = if is_move {
7219 invalidation_row_range =
7220 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7221 let target = first_edit_start;
7222 InlineCompletion::Move { target, snapshot }
7223 } else {
7224 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7225 && !self.inline_completions_hidden_for_vim_mode;
7226
7227 if show_completions_in_buffer {
7228 if edits
7229 .iter()
7230 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7231 {
7232 let mut inlays = Vec::new();
7233 for (range, new_text) in &edits {
7234 let inlay = Inlay::inline_completion(
7235 post_inc(&mut self.next_inlay_id),
7236 range.start,
7237 new_text.as_str(),
7238 );
7239 inlay_ids.push(inlay.id);
7240 inlays.push(inlay);
7241 }
7242
7243 self.splice_inlays(&[], inlays, cx);
7244 } else {
7245 let background_color = cx.theme().status().deleted_background;
7246 self.highlight_text::<InlineCompletionHighlight>(
7247 edits.iter().map(|(range, _)| range.clone()).collect(),
7248 HighlightStyle {
7249 background_color: Some(background_color),
7250 ..Default::default()
7251 },
7252 cx,
7253 );
7254 }
7255 }
7256
7257 invalidation_row_range = edit_start_row..edit_end_row;
7258
7259 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7260 if provider.show_tab_accept_marker() {
7261 EditDisplayMode::TabAccept
7262 } else {
7263 EditDisplayMode::Inline
7264 }
7265 } else {
7266 EditDisplayMode::DiffPopover
7267 };
7268
7269 InlineCompletion::Edit {
7270 edits,
7271 edit_preview: inline_completion.edit_preview,
7272 display_mode,
7273 snapshot,
7274 }
7275 };
7276
7277 let invalidation_range = multibuffer
7278 .anchor_before(Point::new(invalidation_row_range.start, 0))
7279 ..multibuffer.anchor_after(Point::new(
7280 invalidation_row_range.end,
7281 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7282 ));
7283
7284 self.stale_inline_completion_in_menu = None;
7285 self.active_inline_completion = Some(InlineCompletionState {
7286 inlay_ids,
7287 completion,
7288 completion_id: inline_completion.id,
7289 invalidation_range,
7290 });
7291
7292 cx.notify();
7293
7294 Some(())
7295 }
7296
7297 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7298 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7299 }
7300
7301 fn clear_tasks(&mut self) {
7302 self.tasks.clear()
7303 }
7304
7305 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7306 if self.tasks.insert(key, value).is_some() {
7307 // This case should hopefully be rare, but just in case...
7308 log::error!(
7309 "multiple different run targets found on a single line, only the last target will be rendered"
7310 )
7311 }
7312 }
7313
7314 /// Get all display points of breakpoints that will be rendered within editor
7315 ///
7316 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7317 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7318 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7319 fn active_breakpoints(
7320 &self,
7321 range: Range<DisplayRow>,
7322 window: &mut Window,
7323 cx: &mut Context<Self>,
7324 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7325 let mut breakpoint_display_points = HashMap::default();
7326
7327 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7328 return breakpoint_display_points;
7329 };
7330
7331 let snapshot = self.snapshot(window, cx);
7332
7333 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7334 let Some(project) = self.project.as_ref() else {
7335 return breakpoint_display_points;
7336 };
7337
7338 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7339 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7340
7341 for (buffer_snapshot, range, excerpt_id) in
7342 multi_buffer_snapshot.range_to_buffer_ranges(range)
7343 {
7344 let Some(buffer) = project
7345 .read(cx)
7346 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7347 else {
7348 continue;
7349 };
7350 let breakpoints = breakpoint_store.read(cx).breakpoints(
7351 &buffer,
7352 Some(
7353 buffer_snapshot.anchor_before(range.start)
7354 ..buffer_snapshot.anchor_after(range.end),
7355 ),
7356 buffer_snapshot,
7357 cx,
7358 );
7359 for (breakpoint, state) in breakpoints {
7360 let multi_buffer_anchor =
7361 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7362 let position = multi_buffer_anchor
7363 .to_point(&multi_buffer_snapshot)
7364 .to_display_point(&snapshot);
7365
7366 breakpoint_display_points.insert(
7367 position.row(),
7368 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7369 );
7370 }
7371 }
7372
7373 breakpoint_display_points
7374 }
7375
7376 fn breakpoint_context_menu(
7377 &self,
7378 anchor: Anchor,
7379 window: &mut Window,
7380 cx: &mut Context<Self>,
7381 ) -> Entity<ui::ContextMenu> {
7382 let weak_editor = cx.weak_entity();
7383 let focus_handle = self.focus_handle(cx);
7384
7385 let row = self
7386 .buffer
7387 .read(cx)
7388 .snapshot(cx)
7389 .summary_for_anchor::<Point>(&anchor)
7390 .row;
7391
7392 let breakpoint = self
7393 .breakpoint_at_row(row, window, cx)
7394 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7395
7396 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7397 "Edit Log Breakpoint"
7398 } else {
7399 "Set Log Breakpoint"
7400 };
7401
7402 let condition_breakpoint_msg = if breakpoint
7403 .as_ref()
7404 .is_some_and(|bp| bp.1.condition.is_some())
7405 {
7406 "Edit Condition Breakpoint"
7407 } else {
7408 "Set Condition Breakpoint"
7409 };
7410
7411 let hit_condition_breakpoint_msg = if breakpoint
7412 .as_ref()
7413 .is_some_and(|bp| bp.1.hit_condition.is_some())
7414 {
7415 "Edit Hit Condition Breakpoint"
7416 } else {
7417 "Set Hit Condition Breakpoint"
7418 };
7419
7420 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7421 "Unset Breakpoint"
7422 } else {
7423 "Set Breakpoint"
7424 };
7425
7426 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
7427 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
7428
7429 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7430 BreakpointState::Enabled => Some("Disable"),
7431 BreakpointState::Disabled => Some("Enable"),
7432 });
7433
7434 let (anchor, breakpoint) =
7435 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7436
7437 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7438 menu.on_blur_subscription(Subscription::new(|| {}))
7439 .context(focus_handle)
7440 .when(run_to_cursor, |this| {
7441 let weak_editor = weak_editor.clone();
7442 this.entry("Run to cursor", None, move |window, cx| {
7443 weak_editor
7444 .update(cx, |editor, cx| {
7445 editor.change_selections(None, window, cx, |s| {
7446 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7447 });
7448 })
7449 .ok();
7450
7451 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
7452 })
7453 .separator()
7454 })
7455 .when_some(toggle_state_msg, |this, msg| {
7456 this.entry(msg, None, {
7457 let weak_editor = weak_editor.clone();
7458 let breakpoint = breakpoint.clone();
7459 move |_window, cx| {
7460 weak_editor
7461 .update(cx, |this, cx| {
7462 this.edit_breakpoint_at_anchor(
7463 anchor,
7464 breakpoint.as_ref().clone(),
7465 BreakpointEditAction::InvertState,
7466 cx,
7467 );
7468 })
7469 .log_err();
7470 }
7471 })
7472 })
7473 .entry(set_breakpoint_msg, None, {
7474 let weak_editor = weak_editor.clone();
7475 let breakpoint = breakpoint.clone();
7476 move |_window, cx| {
7477 weak_editor
7478 .update(cx, |this, cx| {
7479 this.edit_breakpoint_at_anchor(
7480 anchor,
7481 breakpoint.as_ref().clone(),
7482 BreakpointEditAction::Toggle,
7483 cx,
7484 );
7485 })
7486 .log_err();
7487 }
7488 })
7489 .entry(log_breakpoint_msg, None, {
7490 let breakpoint = breakpoint.clone();
7491 let weak_editor = weak_editor.clone();
7492 move |window, cx| {
7493 weak_editor
7494 .update(cx, |this, cx| {
7495 this.add_edit_breakpoint_block(
7496 anchor,
7497 breakpoint.as_ref(),
7498 BreakpointPromptEditAction::Log,
7499 window,
7500 cx,
7501 );
7502 })
7503 .log_err();
7504 }
7505 })
7506 .entry(condition_breakpoint_msg, None, {
7507 let breakpoint = breakpoint.clone();
7508 let weak_editor = weak_editor.clone();
7509 move |window, cx| {
7510 weak_editor
7511 .update(cx, |this, cx| {
7512 this.add_edit_breakpoint_block(
7513 anchor,
7514 breakpoint.as_ref(),
7515 BreakpointPromptEditAction::Condition,
7516 window,
7517 cx,
7518 );
7519 })
7520 .log_err();
7521 }
7522 })
7523 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7524 weak_editor
7525 .update(cx, |this, cx| {
7526 this.add_edit_breakpoint_block(
7527 anchor,
7528 breakpoint.as_ref(),
7529 BreakpointPromptEditAction::HitCondition,
7530 window,
7531 cx,
7532 );
7533 })
7534 .log_err();
7535 })
7536 })
7537 }
7538
7539 fn render_breakpoint(
7540 &self,
7541 position: Anchor,
7542 row: DisplayRow,
7543 breakpoint: &Breakpoint,
7544 state: Option<BreakpointSessionState>,
7545 cx: &mut Context<Self>,
7546 ) -> IconButton {
7547 let is_rejected = state.is_some_and(|s| !s.verified);
7548 // Is it a breakpoint that shows up when hovering over gutter?
7549 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7550 (false, false),
7551 |PhantomBreakpointIndicator {
7552 is_active,
7553 display_row,
7554 collides_with_existing_breakpoint,
7555 }| {
7556 (
7557 is_active && display_row == row,
7558 collides_with_existing_breakpoint,
7559 )
7560 },
7561 );
7562
7563 let (color, icon) = {
7564 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7565 (false, false) => ui::IconName::DebugBreakpoint,
7566 (true, false) => ui::IconName::DebugLogBreakpoint,
7567 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7568 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7569 };
7570
7571 let color = if is_phantom {
7572 Color::Hint
7573 } else if is_rejected {
7574 Color::Disabled
7575 } else {
7576 Color::Debugger
7577 };
7578
7579 (color, icon)
7580 };
7581
7582 let breakpoint = Arc::from(breakpoint.clone());
7583
7584 let alt_as_text = gpui::Keystroke {
7585 modifiers: Modifiers::secondary_key(),
7586 ..Default::default()
7587 };
7588 let primary_action_text = if breakpoint.is_disabled() {
7589 "Enable breakpoint"
7590 } else if is_phantom && !collides_with_existing {
7591 "Set breakpoint"
7592 } else {
7593 "Unset breakpoint"
7594 };
7595 let focus_handle = self.focus_handle.clone();
7596
7597 let meta = if is_rejected {
7598 SharedString::from("No executable code is associated with this line.")
7599 } else if collides_with_existing && !breakpoint.is_disabled() {
7600 SharedString::from(format!(
7601 "{alt_as_text}-click to disable,\nright-click for more options."
7602 ))
7603 } else {
7604 SharedString::from("Right-click for more options.")
7605 };
7606 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7607 .icon_size(IconSize::XSmall)
7608 .size(ui::ButtonSize::None)
7609 .when(is_rejected, |this| {
7610 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7611 })
7612 .icon_color(color)
7613 .style(ButtonStyle::Transparent)
7614 .on_click(cx.listener({
7615 let breakpoint = breakpoint.clone();
7616
7617 move |editor, event: &ClickEvent, window, cx| {
7618 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7619 BreakpointEditAction::InvertState
7620 } else {
7621 BreakpointEditAction::Toggle
7622 };
7623
7624 window.focus(&editor.focus_handle(cx));
7625 editor.edit_breakpoint_at_anchor(
7626 position,
7627 breakpoint.as_ref().clone(),
7628 edit_action,
7629 cx,
7630 );
7631 }
7632 }))
7633 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7634 editor.set_breakpoint_context_menu(
7635 row,
7636 Some(position),
7637 event.down.position,
7638 window,
7639 cx,
7640 );
7641 }))
7642 .tooltip(move |window, cx| {
7643 Tooltip::with_meta_in(
7644 primary_action_text,
7645 Some(&ToggleBreakpoint),
7646 meta.clone(),
7647 &focus_handle,
7648 window,
7649 cx,
7650 )
7651 })
7652 }
7653
7654 fn build_tasks_context(
7655 project: &Entity<Project>,
7656 buffer: &Entity<Buffer>,
7657 buffer_row: u32,
7658 tasks: &Arc<RunnableTasks>,
7659 cx: &mut Context<Self>,
7660 ) -> Task<Option<task::TaskContext>> {
7661 let position = Point::new(buffer_row, tasks.column);
7662 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7663 let location = Location {
7664 buffer: buffer.clone(),
7665 range: range_start..range_start,
7666 };
7667 // Fill in the environmental variables from the tree-sitter captures
7668 let mut captured_task_variables = TaskVariables::default();
7669 for (capture_name, value) in tasks.extra_variables.clone() {
7670 captured_task_variables.insert(
7671 task::VariableName::Custom(capture_name.into()),
7672 value.clone(),
7673 );
7674 }
7675 project.update(cx, |project, cx| {
7676 project.task_store().update(cx, |task_store, cx| {
7677 task_store.task_context_for_location(captured_task_variables, location, cx)
7678 })
7679 })
7680 }
7681
7682 pub fn spawn_nearest_task(
7683 &mut self,
7684 action: &SpawnNearestTask,
7685 window: &mut Window,
7686 cx: &mut Context<Self>,
7687 ) {
7688 let Some((workspace, _)) = self.workspace.clone() else {
7689 return;
7690 };
7691 let Some(project) = self.project.clone() else {
7692 return;
7693 };
7694
7695 // Try to find a closest, enclosing node using tree-sitter that has a
7696 // task
7697 let Some((buffer, buffer_row, tasks)) = self
7698 .find_enclosing_node_task(cx)
7699 // Or find the task that's closest in row-distance.
7700 .or_else(|| self.find_closest_task(cx))
7701 else {
7702 return;
7703 };
7704
7705 let reveal_strategy = action.reveal;
7706 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7707 cx.spawn_in(window, async move |_, cx| {
7708 let context = task_context.await?;
7709 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7710
7711 let resolved = &mut resolved_task.resolved;
7712 resolved.reveal = reveal_strategy;
7713
7714 workspace
7715 .update_in(cx, |workspace, window, cx| {
7716 workspace.schedule_resolved_task(
7717 task_source_kind,
7718 resolved_task,
7719 false,
7720 window,
7721 cx,
7722 );
7723 })
7724 .ok()
7725 })
7726 .detach();
7727 }
7728
7729 fn find_closest_task(
7730 &mut self,
7731 cx: &mut Context<Self>,
7732 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7733 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7734
7735 let ((buffer_id, row), tasks) = self
7736 .tasks
7737 .iter()
7738 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7739
7740 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7741 let tasks = Arc::new(tasks.to_owned());
7742 Some((buffer, *row, tasks))
7743 }
7744
7745 fn find_enclosing_node_task(
7746 &mut self,
7747 cx: &mut Context<Self>,
7748 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7749 let snapshot = self.buffer.read(cx).snapshot(cx);
7750 let offset = self.selections.newest::<usize>(cx).head();
7751 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7752 let buffer_id = excerpt.buffer().remote_id();
7753
7754 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7755 let mut cursor = layer.node().walk();
7756
7757 while cursor.goto_first_child_for_byte(offset).is_some() {
7758 if cursor.node().end_byte() == offset {
7759 cursor.goto_next_sibling();
7760 }
7761 }
7762
7763 // Ascend to the smallest ancestor that contains the range and has a task.
7764 loop {
7765 let node = cursor.node();
7766 let node_range = node.byte_range();
7767 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7768
7769 // Check if this node contains our offset
7770 if node_range.start <= offset && node_range.end >= offset {
7771 // If it contains offset, check for task
7772 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7773 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7774 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7775 }
7776 }
7777
7778 if !cursor.goto_parent() {
7779 break;
7780 }
7781 }
7782 None
7783 }
7784
7785 fn render_run_indicator(
7786 &self,
7787 _style: &EditorStyle,
7788 is_active: bool,
7789 row: DisplayRow,
7790 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7791 cx: &mut Context<Self>,
7792 ) -> IconButton {
7793 let color = Color::Muted;
7794 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7795
7796 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7797 .shape(ui::IconButtonShape::Square)
7798 .icon_size(IconSize::XSmall)
7799 .icon_color(color)
7800 .toggle_state(is_active)
7801 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7802 let quick_launch = e.down.button == MouseButton::Left;
7803 window.focus(&editor.focus_handle(cx));
7804 editor.toggle_code_actions(
7805 &ToggleCodeActions {
7806 deployed_from: Some(CodeActionSource::Indicator(row)),
7807 quick_launch,
7808 },
7809 window,
7810 cx,
7811 );
7812 }))
7813 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7814 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7815 }))
7816 }
7817
7818 pub fn context_menu_visible(&self) -> bool {
7819 !self.edit_prediction_preview_is_active()
7820 && self
7821 .context_menu
7822 .borrow()
7823 .as_ref()
7824 .map_or(false, |menu| menu.visible())
7825 }
7826
7827 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7828 self.context_menu
7829 .borrow()
7830 .as_ref()
7831 .map(|menu| menu.origin())
7832 }
7833
7834 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7835 self.context_menu_options = Some(options);
7836 }
7837
7838 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7839 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7840
7841 fn render_edit_prediction_popover(
7842 &mut self,
7843 text_bounds: &Bounds<Pixels>,
7844 content_origin: gpui::Point<Pixels>,
7845 right_margin: Pixels,
7846 editor_snapshot: &EditorSnapshot,
7847 visible_row_range: Range<DisplayRow>,
7848 scroll_top: f32,
7849 scroll_bottom: f32,
7850 line_layouts: &[LineWithInvisibles],
7851 line_height: Pixels,
7852 scroll_pixel_position: gpui::Point<Pixels>,
7853 newest_selection_head: Option<DisplayPoint>,
7854 editor_width: Pixels,
7855 style: &EditorStyle,
7856 window: &mut Window,
7857 cx: &mut App,
7858 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7859 if self.mode().is_minimap() {
7860 return None;
7861 }
7862 let active_inline_completion = self.active_inline_completion.as_ref()?;
7863
7864 if self.edit_prediction_visible_in_cursor_popover(true) {
7865 return None;
7866 }
7867
7868 match &active_inline_completion.completion {
7869 InlineCompletion::Move { target, .. } => {
7870 let target_display_point = target.to_display_point(editor_snapshot);
7871
7872 if self.edit_prediction_requires_modifier() {
7873 if !self.edit_prediction_preview_is_active() {
7874 return None;
7875 }
7876
7877 self.render_edit_prediction_modifier_jump_popover(
7878 text_bounds,
7879 content_origin,
7880 visible_row_range,
7881 line_layouts,
7882 line_height,
7883 scroll_pixel_position,
7884 newest_selection_head,
7885 target_display_point,
7886 window,
7887 cx,
7888 )
7889 } else {
7890 self.render_edit_prediction_eager_jump_popover(
7891 text_bounds,
7892 content_origin,
7893 editor_snapshot,
7894 visible_row_range,
7895 scroll_top,
7896 scroll_bottom,
7897 line_height,
7898 scroll_pixel_position,
7899 target_display_point,
7900 editor_width,
7901 window,
7902 cx,
7903 )
7904 }
7905 }
7906 InlineCompletion::Edit {
7907 display_mode: EditDisplayMode::Inline,
7908 ..
7909 } => None,
7910 InlineCompletion::Edit {
7911 display_mode: EditDisplayMode::TabAccept,
7912 edits,
7913 ..
7914 } => {
7915 let range = &edits.first()?.0;
7916 let target_display_point = range.end.to_display_point(editor_snapshot);
7917
7918 self.render_edit_prediction_end_of_line_popover(
7919 "Accept",
7920 editor_snapshot,
7921 visible_row_range,
7922 target_display_point,
7923 line_height,
7924 scroll_pixel_position,
7925 content_origin,
7926 editor_width,
7927 window,
7928 cx,
7929 )
7930 }
7931 InlineCompletion::Edit {
7932 edits,
7933 edit_preview,
7934 display_mode: EditDisplayMode::DiffPopover,
7935 snapshot,
7936 } => self.render_edit_prediction_diff_popover(
7937 text_bounds,
7938 content_origin,
7939 right_margin,
7940 editor_snapshot,
7941 visible_row_range,
7942 line_layouts,
7943 line_height,
7944 scroll_pixel_position,
7945 newest_selection_head,
7946 editor_width,
7947 style,
7948 edits,
7949 edit_preview,
7950 snapshot,
7951 window,
7952 cx,
7953 ),
7954 }
7955 }
7956
7957 fn render_edit_prediction_modifier_jump_popover(
7958 &mut self,
7959 text_bounds: &Bounds<Pixels>,
7960 content_origin: gpui::Point<Pixels>,
7961 visible_row_range: Range<DisplayRow>,
7962 line_layouts: &[LineWithInvisibles],
7963 line_height: Pixels,
7964 scroll_pixel_position: gpui::Point<Pixels>,
7965 newest_selection_head: Option<DisplayPoint>,
7966 target_display_point: DisplayPoint,
7967 window: &mut Window,
7968 cx: &mut App,
7969 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7970 let scrolled_content_origin =
7971 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7972
7973 const SCROLL_PADDING_Y: Pixels = px(12.);
7974
7975 if target_display_point.row() < visible_row_range.start {
7976 return self.render_edit_prediction_scroll_popover(
7977 |_| SCROLL_PADDING_Y,
7978 IconName::ArrowUp,
7979 visible_row_range,
7980 line_layouts,
7981 newest_selection_head,
7982 scrolled_content_origin,
7983 window,
7984 cx,
7985 );
7986 } else if target_display_point.row() >= visible_row_range.end {
7987 return self.render_edit_prediction_scroll_popover(
7988 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7989 IconName::ArrowDown,
7990 visible_row_range,
7991 line_layouts,
7992 newest_selection_head,
7993 scrolled_content_origin,
7994 window,
7995 cx,
7996 );
7997 }
7998
7999 const POLE_WIDTH: Pixels = px(2.);
8000
8001 let line_layout =
8002 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8003 let target_column = target_display_point.column() as usize;
8004
8005 let target_x = line_layout.x_for_index(target_column);
8006 let target_y =
8007 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8008
8009 let flag_on_right = target_x < text_bounds.size.width / 2.;
8010
8011 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8012 border_color.l += 0.001;
8013
8014 let mut element = v_flex()
8015 .items_end()
8016 .when(flag_on_right, |el| el.items_start())
8017 .child(if flag_on_right {
8018 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8019 .rounded_bl(px(0.))
8020 .rounded_tl(px(0.))
8021 .border_l_2()
8022 .border_color(border_color)
8023 } else {
8024 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8025 .rounded_br(px(0.))
8026 .rounded_tr(px(0.))
8027 .border_r_2()
8028 .border_color(border_color)
8029 })
8030 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8031 .into_any();
8032
8033 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8034
8035 let mut origin = scrolled_content_origin + point(target_x, target_y)
8036 - point(
8037 if flag_on_right {
8038 POLE_WIDTH
8039 } else {
8040 size.width - POLE_WIDTH
8041 },
8042 size.height - line_height,
8043 );
8044
8045 origin.x = origin.x.max(content_origin.x);
8046
8047 element.prepaint_at(origin, window, cx);
8048
8049 Some((element, origin))
8050 }
8051
8052 fn render_edit_prediction_scroll_popover(
8053 &mut self,
8054 to_y: impl Fn(Size<Pixels>) -> Pixels,
8055 scroll_icon: IconName,
8056 visible_row_range: Range<DisplayRow>,
8057 line_layouts: &[LineWithInvisibles],
8058 newest_selection_head: Option<DisplayPoint>,
8059 scrolled_content_origin: gpui::Point<Pixels>,
8060 window: &mut Window,
8061 cx: &mut App,
8062 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8063 let mut element = self
8064 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8065 .into_any();
8066
8067 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8068
8069 let cursor = newest_selection_head?;
8070 let cursor_row_layout =
8071 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8072 let cursor_column = cursor.column() as usize;
8073
8074 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8075
8076 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8077
8078 element.prepaint_at(origin, window, cx);
8079 Some((element, origin))
8080 }
8081
8082 fn render_edit_prediction_eager_jump_popover(
8083 &mut self,
8084 text_bounds: &Bounds<Pixels>,
8085 content_origin: gpui::Point<Pixels>,
8086 editor_snapshot: &EditorSnapshot,
8087 visible_row_range: Range<DisplayRow>,
8088 scroll_top: f32,
8089 scroll_bottom: f32,
8090 line_height: Pixels,
8091 scroll_pixel_position: gpui::Point<Pixels>,
8092 target_display_point: DisplayPoint,
8093 editor_width: Pixels,
8094 window: &mut Window,
8095 cx: &mut App,
8096 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8097 if target_display_point.row().as_f32() < scroll_top {
8098 let mut element = self
8099 .render_edit_prediction_line_popover(
8100 "Jump to Edit",
8101 Some(IconName::ArrowUp),
8102 window,
8103 cx,
8104 )?
8105 .into_any();
8106
8107 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8108 let offset = point(
8109 (text_bounds.size.width - size.width) / 2.,
8110 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8111 );
8112
8113 let origin = text_bounds.origin + offset;
8114 element.prepaint_at(origin, window, cx);
8115 Some((element, origin))
8116 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8117 let mut element = self
8118 .render_edit_prediction_line_popover(
8119 "Jump to Edit",
8120 Some(IconName::ArrowDown),
8121 window,
8122 cx,
8123 )?
8124 .into_any();
8125
8126 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8127 let offset = point(
8128 (text_bounds.size.width - size.width) / 2.,
8129 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8130 );
8131
8132 let origin = text_bounds.origin + offset;
8133 element.prepaint_at(origin, window, cx);
8134 Some((element, origin))
8135 } else {
8136 self.render_edit_prediction_end_of_line_popover(
8137 "Jump to Edit",
8138 editor_snapshot,
8139 visible_row_range,
8140 target_display_point,
8141 line_height,
8142 scroll_pixel_position,
8143 content_origin,
8144 editor_width,
8145 window,
8146 cx,
8147 )
8148 }
8149 }
8150
8151 fn render_edit_prediction_end_of_line_popover(
8152 self: &mut Editor,
8153 label: &'static str,
8154 editor_snapshot: &EditorSnapshot,
8155 visible_row_range: Range<DisplayRow>,
8156 target_display_point: DisplayPoint,
8157 line_height: Pixels,
8158 scroll_pixel_position: gpui::Point<Pixels>,
8159 content_origin: gpui::Point<Pixels>,
8160 editor_width: Pixels,
8161 window: &mut Window,
8162 cx: &mut App,
8163 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8164 let target_line_end = DisplayPoint::new(
8165 target_display_point.row(),
8166 editor_snapshot.line_len(target_display_point.row()),
8167 );
8168
8169 let mut element = self
8170 .render_edit_prediction_line_popover(label, None, window, cx)?
8171 .into_any();
8172
8173 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8174
8175 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8176
8177 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8178 let mut origin = start_point
8179 + line_origin
8180 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8181 origin.x = origin.x.max(content_origin.x);
8182
8183 let max_x = content_origin.x + editor_width - size.width;
8184
8185 if origin.x > max_x {
8186 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8187
8188 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8189 origin.y += offset;
8190 IconName::ArrowUp
8191 } else {
8192 origin.y -= offset;
8193 IconName::ArrowDown
8194 };
8195
8196 element = self
8197 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8198 .into_any();
8199
8200 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8201
8202 origin.x = content_origin.x + editor_width - size.width - px(2.);
8203 }
8204
8205 element.prepaint_at(origin, window, cx);
8206 Some((element, origin))
8207 }
8208
8209 fn render_edit_prediction_diff_popover(
8210 self: &Editor,
8211 text_bounds: &Bounds<Pixels>,
8212 content_origin: gpui::Point<Pixels>,
8213 right_margin: Pixels,
8214 editor_snapshot: &EditorSnapshot,
8215 visible_row_range: Range<DisplayRow>,
8216 line_layouts: &[LineWithInvisibles],
8217 line_height: Pixels,
8218 scroll_pixel_position: gpui::Point<Pixels>,
8219 newest_selection_head: Option<DisplayPoint>,
8220 editor_width: Pixels,
8221 style: &EditorStyle,
8222 edits: &Vec<(Range<Anchor>, String)>,
8223 edit_preview: &Option<language::EditPreview>,
8224 snapshot: &language::BufferSnapshot,
8225 window: &mut Window,
8226 cx: &mut App,
8227 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8228 let edit_start = edits
8229 .first()
8230 .unwrap()
8231 .0
8232 .start
8233 .to_display_point(editor_snapshot);
8234 let edit_end = edits
8235 .last()
8236 .unwrap()
8237 .0
8238 .end
8239 .to_display_point(editor_snapshot);
8240
8241 let is_visible = visible_row_range.contains(&edit_start.row())
8242 || visible_row_range.contains(&edit_end.row());
8243 if !is_visible {
8244 return None;
8245 }
8246
8247 let highlighted_edits =
8248 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8249
8250 let styled_text = highlighted_edits.to_styled_text(&style.text);
8251 let line_count = highlighted_edits.text.lines().count();
8252
8253 const BORDER_WIDTH: Pixels = px(1.);
8254
8255 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8256 let has_keybind = keybind.is_some();
8257
8258 let mut element = h_flex()
8259 .items_start()
8260 .child(
8261 h_flex()
8262 .bg(cx.theme().colors().editor_background)
8263 .border(BORDER_WIDTH)
8264 .shadow_sm()
8265 .border_color(cx.theme().colors().border)
8266 .rounded_l_lg()
8267 .when(line_count > 1, |el| el.rounded_br_lg())
8268 .pr_1()
8269 .child(styled_text),
8270 )
8271 .child(
8272 h_flex()
8273 .h(line_height + BORDER_WIDTH * 2.)
8274 .px_1p5()
8275 .gap_1()
8276 // Workaround: For some reason, there's a gap if we don't do this
8277 .ml(-BORDER_WIDTH)
8278 .shadow(vec![gpui::BoxShadow {
8279 color: gpui::black().opacity(0.05),
8280 offset: point(px(1.), px(1.)),
8281 blur_radius: px(2.),
8282 spread_radius: px(0.),
8283 }])
8284 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8285 .border(BORDER_WIDTH)
8286 .border_color(cx.theme().colors().border)
8287 .rounded_r_lg()
8288 .id("edit_prediction_diff_popover_keybind")
8289 .when(!has_keybind, |el| {
8290 let status_colors = cx.theme().status();
8291
8292 el.bg(status_colors.error_background)
8293 .border_color(status_colors.error.opacity(0.6))
8294 .child(Icon::new(IconName::Info).color(Color::Error))
8295 .cursor_default()
8296 .hoverable_tooltip(move |_window, cx| {
8297 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8298 })
8299 })
8300 .children(keybind),
8301 )
8302 .into_any();
8303
8304 let longest_row =
8305 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8306 let longest_line_width = if visible_row_range.contains(&longest_row) {
8307 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8308 } else {
8309 layout_line(
8310 longest_row,
8311 editor_snapshot,
8312 style,
8313 editor_width,
8314 |_| false,
8315 window,
8316 cx,
8317 )
8318 .width
8319 };
8320
8321 let viewport_bounds =
8322 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8323 right: -right_margin,
8324 ..Default::default()
8325 });
8326
8327 let x_after_longest =
8328 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8329 - scroll_pixel_position.x;
8330
8331 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8332
8333 // Fully visible if it can be displayed within the window (allow overlapping other
8334 // panes). However, this is only allowed if the popover starts within text_bounds.
8335 let can_position_to_the_right = x_after_longest < text_bounds.right()
8336 && x_after_longest + element_bounds.width < viewport_bounds.right();
8337
8338 let mut origin = if can_position_to_the_right {
8339 point(
8340 x_after_longest,
8341 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8342 - scroll_pixel_position.y,
8343 )
8344 } else {
8345 let cursor_row = newest_selection_head.map(|head| head.row());
8346 let above_edit = edit_start
8347 .row()
8348 .0
8349 .checked_sub(line_count as u32)
8350 .map(DisplayRow);
8351 let below_edit = Some(edit_end.row() + 1);
8352 let above_cursor =
8353 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8354 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8355
8356 // Place the edit popover adjacent to the edit if there is a location
8357 // available that is onscreen and does not obscure the cursor. Otherwise,
8358 // place it adjacent to the cursor.
8359 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8360 .into_iter()
8361 .flatten()
8362 .find(|&start_row| {
8363 let end_row = start_row + line_count as u32;
8364 visible_row_range.contains(&start_row)
8365 && visible_row_range.contains(&end_row)
8366 && cursor_row.map_or(true, |cursor_row| {
8367 !((start_row..end_row).contains(&cursor_row))
8368 })
8369 })?;
8370
8371 content_origin
8372 + point(
8373 -scroll_pixel_position.x,
8374 row_target.as_f32() * line_height - scroll_pixel_position.y,
8375 )
8376 };
8377
8378 origin.x -= BORDER_WIDTH;
8379
8380 window.defer_draw(element, origin, 1);
8381
8382 // Do not return an element, since it will already be drawn due to defer_draw.
8383 None
8384 }
8385
8386 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8387 px(30.)
8388 }
8389
8390 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8391 if self.read_only(cx) {
8392 cx.theme().players().read_only()
8393 } else {
8394 self.style.as_ref().unwrap().local_player
8395 }
8396 }
8397
8398 fn render_edit_prediction_accept_keybind(
8399 &self,
8400 window: &mut Window,
8401 cx: &App,
8402 ) -> Option<AnyElement> {
8403 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
8404 let accept_keystroke = accept_binding.keystroke()?;
8405
8406 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8407
8408 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8409 Color::Accent
8410 } else {
8411 Color::Muted
8412 };
8413
8414 h_flex()
8415 .px_0p5()
8416 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8417 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8418 .text_size(TextSize::XSmall.rems(cx))
8419 .child(h_flex().children(ui::render_modifiers(
8420 &accept_keystroke.modifiers,
8421 PlatformStyle::platform(),
8422 Some(modifiers_color),
8423 Some(IconSize::XSmall.rems().into()),
8424 true,
8425 )))
8426 .when(is_platform_style_mac, |parent| {
8427 parent.child(accept_keystroke.key.clone())
8428 })
8429 .when(!is_platform_style_mac, |parent| {
8430 parent.child(
8431 Key::new(
8432 util::capitalize(&accept_keystroke.key),
8433 Some(Color::Default),
8434 )
8435 .size(Some(IconSize::XSmall.rems().into())),
8436 )
8437 })
8438 .into_any()
8439 .into()
8440 }
8441
8442 fn render_edit_prediction_line_popover(
8443 &self,
8444 label: impl Into<SharedString>,
8445 icon: Option<IconName>,
8446 window: &mut Window,
8447 cx: &App,
8448 ) -> Option<Stateful<Div>> {
8449 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8450
8451 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8452 let has_keybind = keybind.is_some();
8453
8454 let result = h_flex()
8455 .id("ep-line-popover")
8456 .py_0p5()
8457 .pl_1()
8458 .pr(padding_right)
8459 .gap_1()
8460 .rounded_md()
8461 .border_1()
8462 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8463 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8464 .shadow_sm()
8465 .when(!has_keybind, |el| {
8466 let status_colors = cx.theme().status();
8467
8468 el.bg(status_colors.error_background)
8469 .border_color(status_colors.error.opacity(0.6))
8470 .pl_2()
8471 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8472 .cursor_default()
8473 .hoverable_tooltip(move |_window, cx| {
8474 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8475 })
8476 })
8477 .children(keybind)
8478 .child(
8479 Label::new(label)
8480 .size(LabelSize::Small)
8481 .when(!has_keybind, |el| {
8482 el.color(cx.theme().status().error.into()).strikethrough()
8483 }),
8484 )
8485 .when(!has_keybind, |el| {
8486 el.child(
8487 h_flex().ml_1().child(
8488 Icon::new(IconName::Info)
8489 .size(IconSize::Small)
8490 .color(cx.theme().status().error.into()),
8491 ),
8492 )
8493 })
8494 .when_some(icon, |element, icon| {
8495 element.child(
8496 div()
8497 .mt(px(1.5))
8498 .child(Icon::new(icon).size(IconSize::Small)),
8499 )
8500 });
8501
8502 Some(result)
8503 }
8504
8505 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8506 let accent_color = cx.theme().colors().text_accent;
8507 let editor_bg_color = cx.theme().colors().editor_background;
8508 editor_bg_color.blend(accent_color.opacity(0.1))
8509 }
8510
8511 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8512 let accent_color = cx.theme().colors().text_accent;
8513 let editor_bg_color = cx.theme().colors().editor_background;
8514 editor_bg_color.blend(accent_color.opacity(0.6))
8515 }
8516
8517 fn render_edit_prediction_cursor_popover(
8518 &self,
8519 min_width: Pixels,
8520 max_width: Pixels,
8521 cursor_point: Point,
8522 style: &EditorStyle,
8523 accept_keystroke: Option<&gpui::Keystroke>,
8524 _window: &Window,
8525 cx: &mut Context<Editor>,
8526 ) -> Option<AnyElement> {
8527 let provider = self.edit_prediction_provider.as_ref()?;
8528
8529 if provider.provider.needs_terms_acceptance(cx) {
8530 return Some(
8531 h_flex()
8532 .min_w(min_width)
8533 .flex_1()
8534 .px_2()
8535 .py_1()
8536 .gap_3()
8537 .elevation_2(cx)
8538 .hover(|style| style.bg(cx.theme().colors().element_hover))
8539 .id("accept-terms")
8540 .cursor_pointer()
8541 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8542 .on_click(cx.listener(|this, _event, window, cx| {
8543 cx.stop_propagation();
8544 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8545 window.dispatch_action(
8546 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8547 cx,
8548 );
8549 }))
8550 .child(
8551 h_flex()
8552 .flex_1()
8553 .gap_2()
8554 .child(Icon::new(IconName::ZedPredict))
8555 .child(Label::new("Accept Terms of Service"))
8556 .child(div().w_full())
8557 .child(
8558 Icon::new(IconName::ArrowUpRight)
8559 .color(Color::Muted)
8560 .size(IconSize::Small),
8561 )
8562 .into_any_element(),
8563 )
8564 .into_any(),
8565 );
8566 }
8567
8568 let is_refreshing = provider.provider.is_refreshing(cx);
8569
8570 fn pending_completion_container() -> Div {
8571 h_flex()
8572 .h_full()
8573 .flex_1()
8574 .gap_2()
8575 .child(Icon::new(IconName::ZedPredict))
8576 }
8577
8578 let completion = match &self.active_inline_completion {
8579 Some(prediction) => {
8580 if !self.has_visible_completions_menu() {
8581 const RADIUS: Pixels = px(6.);
8582 const BORDER_WIDTH: Pixels = px(1.);
8583
8584 return Some(
8585 h_flex()
8586 .elevation_2(cx)
8587 .border(BORDER_WIDTH)
8588 .border_color(cx.theme().colors().border)
8589 .when(accept_keystroke.is_none(), |el| {
8590 el.border_color(cx.theme().status().error)
8591 })
8592 .rounded(RADIUS)
8593 .rounded_tl(px(0.))
8594 .overflow_hidden()
8595 .child(div().px_1p5().child(match &prediction.completion {
8596 InlineCompletion::Move { target, snapshot } => {
8597 use text::ToPoint as _;
8598 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8599 {
8600 Icon::new(IconName::ZedPredictDown)
8601 } else {
8602 Icon::new(IconName::ZedPredictUp)
8603 }
8604 }
8605 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8606 }))
8607 .child(
8608 h_flex()
8609 .gap_1()
8610 .py_1()
8611 .px_2()
8612 .rounded_r(RADIUS - BORDER_WIDTH)
8613 .border_l_1()
8614 .border_color(cx.theme().colors().border)
8615 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8616 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8617 el.child(
8618 Label::new("Hold")
8619 .size(LabelSize::Small)
8620 .when(accept_keystroke.is_none(), |el| {
8621 el.strikethrough()
8622 })
8623 .line_height_style(LineHeightStyle::UiLabel),
8624 )
8625 })
8626 .id("edit_prediction_cursor_popover_keybind")
8627 .when(accept_keystroke.is_none(), |el| {
8628 let status_colors = cx.theme().status();
8629
8630 el.bg(status_colors.error_background)
8631 .border_color(status_colors.error.opacity(0.6))
8632 .child(Icon::new(IconName::Info).color(Color::Error))
8633 .cursor_default()
8634 .hoverable_tooltip(move |_window, cx| {
8635 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8636 .into()
8637 })
8638 })
8639 .when_some(
8640 accept_keystroke.as_ref(),
8641 |el, accept_keystroke| {
8642 el.child(h_flex().children(ui::render_modifiers(
8643 &accept_keystroke.modifiers,
8644 PlatformStyle::platform(),
8645 Some(Color::Default),
8646 Some(IconSize::XSmall.rems().into()),
8647 false,
8648 )))
8649 },
8650 ),
8651 )
8652 .into_any(),
8653 );
8654 }
8655
8656 self.render_edit_prediction_cursor_popover_preview(
8657 prediction,
8658 cursor_point,
8659 style,
8660 cx,
8661 )?
8662 }
8663
8664 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8665 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8666 stale_completion,
8667 cursor_point,
8668 style,
8669 cx,
8670 )?,
8671
8672 None => {
8673 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8674 }
8675 },
8676
8677 None => pending_completion_container().child(Label::new("No Prediction")),
8678 };
8679
8680 let completion = if is_refreshing {
8681 completion
8682 .with_animation(
8683 "loading-completion",
8684 Animation::new(Duration::from_secs(2))
8685 .repeat()
8686 .with_easing(pulsating_between(0.4, 0.8)),
8687 |label, delta| label.opacity(delta),
8688 )
8689 .into_any_element()
8690 } else {
8691 completion.into_any_element()
8692 };
8693
8694 let has_completion = self.active_inline_completion.is_some();
8695
8696 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8697 Some(
8698 h_flex()
8699 .min_w(min_width)
8700 .max_w(max_width)
8701 .flex_1()
8702 .elevation_2(cx)
8703 .border_color(cx.theme().colors().border)
8704 .child(
8705 div()
8706 .flex_1()
8707 .py_1()
8708 .px_2()
8709 .overflow_hidden()
8710 .child(completion),
8711 )
8712 .when_some(accept_keystroke, |el, accept_keystroke| {
8713 if !accept_keystroke.modifiers.modified() {
8714 return el;
8715 }
8716
8717 el.child(
8718 h_flex()
8719 .h_full()
8720 .border_l_1()
8721 .rounded_r_lg()
8722 .border_color(cx.theme().colors().border)
8723 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8724 .gap_1()
8725 .py_1()
8726 .px_2()
8727 .child(
8728 h_flex()
8729 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8730 .when(is_platform_style_mac, |parent| parent.gap_1())
8731 .child(h_flex().children(ui::render_modifiers(
8732 &accept_keystroke.modifiers,
8733 PlatformStyle::platform(),
8734 Some(if !has_completion {
8735 Color::Muted
8736 } else {
8737 Color::Default
8738 }),
8739 None,
8740 false,
8741 ))),
8742 )
8743 .child(Label::new("Preview").into_any_element())
8744 .opacity(if has_completion { 1.0 } else { 0.4 }),
8745 )
8746 })
8747 .into_any(),
8748 )
8749 }
8750
8751 fn render_edit_prediction_cursor_popover_preview(
8752 &self,
8753 completion: &InlineCompletionState,
8754 cursor_point: Point,
8755 style: &EditorStyle,
8756 cx: &mut Context<Editor>,
8757 ) -> Option<Div> {
8758 use text::ToPoint as _;
8759
8760 fn render_relative_row_jump(
8761 prefix: impl Into<String>,
8762 current_row: u32,
8763 target_row: u32,
8764 ) -> Div {
8765 let (row_diff, arrow) = if target_row < current_row {
8766 (current_row - target_row, IconName::ArrowUp)
8767 } else {
8768 (target_row - current_row, IconName::ArrowDown)
8769 };
8770
8771 h_flex()
8772 .child(
8773 Label::new(format!("{}{}", prefix.into(), row_diff))
8774 .color(Color::Muted)
8775 .size(LabelSize::Small),
8776 )
8777 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8778 }
8779
8780 match &completion.completion {
8781 InlineCompletion::Move {
8782 target, snapshot, ..
8783 } => Some(
8784 h_flex()
8785 .px_2()
8786 .gap_2()
8787 .flex_1()
8788 .child(
8789 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8790 Icon::new(IconName::ZedPredictDown)
8791 } else {
8792 Icon::new(IconName::ZedPredictUp)
8793 },
8794 )
8795 .child(Label::new("Jump to Edit")),
8796 ),
8797
8798 InlineCompletion::Edit {
8799 edits,
8800 edit_preview,
8801 snapshot,
8802 display_mode: _,
8803 } => {
8804 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8805
8806 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8807 &snapshot,
8808 &edits,
8809 edit_preview.as_ref()?,
8810 true,
8811 cx,
8812 )
8813 .first_line_preview();
8814
8815 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8816 .with_default_highlights(&style.text, highlighted_edits.highlights);
8817
8818 let preview = h_flex()
8819 .gap_1()
8820 .min_w_16()
8821 .child(styled_text)
8822 .when(has_more_lines, |parent| parent.child("…"));
8823
8824 let left = if first_edit_row != cursor_point.row {
8825 render_relative_row_jump("", cursor_point.row, first_edit_row)
8826 .into_any_element()
8827 } else {
8828 Icon::new(IconName::ZedPredict).into_any_element()
8829 };
8830
8831 Some(
8832 h_flex()
8833 .h_full()
8834 .flex_1()
8835 .gap_2()
8836 .pr_1()
8837 .overflow_x_hidden()
8838 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8839 .child(left)
8840 .child(preview),
8841 )
8842 }
8843 }
8844 }
8845
8846 pub fn render_context_menu(
8847 &self,
8848 style: &EditorStyle,
8849 max_height_in_lines: u32,
8850 window: &mut Window,
8851 cx: &mut Context<Editor>,
8852 ) -> Option<AnyElement> {
8853 let menu = self.context_menu.borrow();
8854 let menu = menu.as_ref()?;
8855 if !menu.visible() {
8856 return None;
8857 };
8858 Some(menu.render(style, max_height_in_lines, window, cx))
8859 }
8860
8861 fn render_context_menu_aside(
8862 &mut self,
8863 max_size: Size<Pixels>,
8864 window: &mut Window,
8865 cx: &mut Context<Editor>,
8866 ) -> Option<AnyElement> {
8867 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8868 if menu.visible() {
8869 menu.render_aside(max_size, window, cx)
8870 } else {
8871 None
8872 }
8873 })
8874 }
8875
8876 fn hide_context_menu(
8877 &mut self,
8878 window: &mut Window,
8879 cx: &mut Context<Self>,
8880 ) -> Option<CodeContextMenu> {
8881 cx.notify();
8882 self.completion_tasks.clear();
8883 let context_menu = self.context_menu.borrow_mut().take();
8884 self.stale_inline_completion_in_menu.take();
8885 self.update_visible_inline_completion(window, cx);
8886 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
8887 if let Some(completion_provider) = &self.completion_provider {
8888 completion_provider.selection_changed(None, window, cx);
8889 }
8890 }
8891 context_menu
8892 }
8893
8894 fn show_snippet_choices(
8895 &mut self,
8896 choices: &Vec<String>,
8897 selection: Range<Anchor>,
8898 cx: &mut Context<Self>,
8899 ) {
8900 if selection.start.buffer_id.is_none() {
8901 return;
8902 }
8903 let buffer_id = selection.start.buffer_id.unwrap();
8904 let buffer = self.buffer().read(cx).buffer(buffer_id);
8905 let id = post_inc(&mut self.next_completion_id);
8906 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8907
8908 if let Some(buffer) = buffer {
8909 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8910 CompletionsMenu::new_snippet_choices(
8911 id,
8912 true,
8913 choices,
8914 selection,
8915 buffer,
8916 snippet_sort_order,
8917 ),
8918 ));
8919 }
8920 }
8921
8922 pub fn insert_snippet(
8923 &mut self,
8924 insertion_ranges: &[Range<usize>],
8925 snippet: Snippet,
8926 window: &mut Window,
8927 cx: &mut Context<Self>,
8928 ) -> Result<()> {
8929 struct Tabstop<T> {
8930 is_end_tabstop: bool,
8931 ranges: Vec<Range<T>>,
8932 choices: Option<Vec<String>>,
8933 }
8934
8935 let tabstops = self.buffer.update(cx, |buffer, cx| {
8936 let snippet_text: Arc<str> = snippet.text.clone().into();
8937 let edits = insertion_ranges
8938 .iter()
8939 .cloned()
8940 .map(|range| (range, snippet_text.clone()));
8941 let autoindent_mode = AutoindentMode::Block {
8942 original_indent_columns: Vec::new(),
8943 };
8944 buffer.edit(edits, Some(autoindent_mode), cx);
8945
8946 let snapshot = &*buffer.read(cx);
8947 let snippet = &snippet;
8948 snippet
8949 .tabstops
8950 .iter()
8951 .map(|tabstop| {
8952 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8953 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8954 });
8955 let mut tabstop_ranges = tabstop
8956 .ranges
8957 .iter()
8958 .flat_map(|tabstop_range| {
8959 let mut delta = 0_isize;
8960 insertion_ranges.iter().map(move |insertion_range| {
8961 let insertion_start = insertion_range.start as isize + delta;
8962 delta +=
8963 snippet.text.len() as isize - insertion_range.len() as isize;
8964
8965 let start = ((insertion_start + tabstop_range.start) as usize)
8966 .min(snapshot.len());
8967 let end = ((insertion_start + tabstop_range.end) as usize)
8968 .min(snapshot.len());
8969 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8970 })
8971 })
8972 .collect::<Vec<_>>();
8973 // Sort in reverse order so that the first range is the newest created
8974 // selection. Completions will use it and autoscroll will prioritize it.
8975 tabstop_ranges.sort_unstable_by(|a, b| b.start.cmp(&a.start, snapshot));
8976
8977 Tabstop {
8978 is_end_tabstop,
8979 ranges: tabstop_ranges,
8980 choices: tabstop.choices.clone(),
8981 }
8982 })
8983 .collect::<Vec<_>>()
8984 });
8985 if let Some(tabstop) = tabstops.first() {
8986 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8987 s.select_ranges(tabstop.ranges.iter().cloned());
8988 });
8989
8990 if let Some(choices) = &tabstop.choices {
8991 if let Some(selection) = tabstop.ranges.first() {
8992 self.show_snippet_choices(choices, selection.clone(), cx)
8993 }
8994 }
8995
8996 // If we're already at the last tabstop and it's at the end of the snippet,
8997 // we're done, we don't need to keep the state around.
8998 if !tabstop.is_end_tabstop {
8999 let choices = tabstops
9000 .iter()
9001 .map(|tabstop| tabstop.choices.clone())
9002 .collect();
9003
9004 let ranges = tabstops
9005 .into_iter()
9006 .map(|tabstop| tabstop.ranges)
9007 .collect::<Vec<_>>();
9008
9009 self.snippet_stack.push(SnippetState {
9010 active_index: 0,
9011 ranges,
9012 choices,
9013 });
9014 }
9015
9016 // Check whether the just-entered snippet ends with an auto-closable bracket.
9017 if self.autoclose_regions.is_empty() {
9018 let snapshot = self.buffer.read(cx).snapshot(cx);
9019 for selection in &mut self.selections.all::<Point>(cx) {
9020 let selection_head = selection.head();
9021 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9022 continue;
9023 };
9024
9025 let mut bracket_pair = None;
9026 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9027 let prev_chars = snapshot
9028 .reversed_chars_at(selection_head)
9029 .collect::<String>();
9030 for (pair, enabled) in scope.brackets() {
9031 if enabled
9032 && pair.close
9033 && prev_chars.starts_with(pair.start.as_str())
9034 && next_chars.starts_with(pair.end.as_str())
9035 {
9036 bracket_pair = Some(pair.clone());
9037 break;
9038 }
9039 }
9040 if let Some(pair) = bracket_pair {
9041 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9042 let autoclose_enabled =
9043 self.use_autoclose && snapshot_settings.use_autoclose;
9044 if autoclose_enabled {
9045 let start = snapshot.anchor_after(selection_head);
9046 let end = snapshot.anchor_after(selection_head);
9047 self.autoclose_regions.push(AutocloseRegion {
9048 selection_id: selection.id,
9049 range: start..end,
9050 pair,
9051 });
9052 }
9053 }
9054 }
9055 }
9056 }
9057 Ok(())
9058 }
9059
9060 pub fn move_to_next_snippet_tabstop(
9061 &mut self,
9062 window: &mut Window,
9063 cx: &mut Context<Self>,
9064 ) -> bool {
9065 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9066 }
9067
9068 pub fn move_to_prev_snippet_tabstop(
9069 &mut self,
9070 window: &mut Window,
9071 cx: &mut Context<Self>,
9072 ) -> bool {
9073 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9074 }
9075
9076 pub fn move_to_snippet_tabstop(
9077 &mut self,
9078 bias: Bias,
9079 window: &mut Window,
9080 cx: &mut Context<Self>,
9081 ) -> bool {
9082 if let Some(mut snippet) = self.snippet_stack.pop() {
9083 match bias {
9084 Bias::Left => {
9085 if snippet.active_index > 0 {
9086 snippet.active_index -= 1;
9087 } else {
9088 self.snippet_stack.push(snippet);
9089 return false;
9090 }
9091 }
9092 Bias::Right => {
9093 if snippet.active_index + 1 < snippet.ranges.len() {
9094 snippet.active_index += 1;
9095 } else {
9096 self.snippet_stack.push(snippet);
9097 return false;
9098 }
9099 }
9100 }
9101 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9102 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9103 s.select_ranges(current_ranges.iter().cloned())
9104 });
9105
9106 if let Some(choices) = &snippet.choices[snippet.active_index] {
9107 if let Some(selection) = current_ranges.first() {
9108 self.show_snippet_choices(&choices, selection.clone(), cx);
9109 }
9110 }
9111
9112 // If snippet state is not at the last tabstop, push it back on the stack
9113 if snippet.active_index + 1 < snippet.ranges.len() {
9114 self.snippet_stack.push(snippet);
9115 }
9116 return true;
9117 }
9118 }
9119
9120 false
9121 }
9122
9123 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9124 self.transact(window, cx, |this, window, cx| {
9125 this.select_all(&SelectAll, window, cx);
9126 this.insert("", window, cx);
9127 });
9128 }
9129
9130 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9131 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9132 self.transact(window, cx, |this, window, cx| {
9133 this.select_autoclose_pair(window, cx);
9134 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9135 if !this.linked_edit_ranges.is_empty() {
9136 let selections = this.selections.all::<MultiBufferPoint>(cx);
9137 let snapshot = this.buffer.read(cx).snapshot(cx);
9138
9139 for selection in selections.iter() {
9140 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9141 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9142 if selection_start.buffer_id != selection_end.buffer_id {
9143 continue;
9144 }
9145 if let Some(ranges) =
9146 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9147 {
9148 for (buffer, entries) in ranges {
9149 linked_ranges.entry(buffer).or_default().extend(entries);
9150 }
9151 }
9152 }
9153 }
9154
9155 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9156 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9157 for selection in &mut selections {
9158 if selection.is_empty() {
9159 let old_head = selection.head();
9160 let mut new_head =
9161 movement::left(&display_map, old_head.to_display_point(&display_map))
9162 .to_point(&display_map);
9163 if let Some((buffer, line_buffer_range)) = display_map
9164 .buffer_snapshot
9165 .buffer_line_for_row(MultiBufferRow(old_head.row))
9166 {
9167 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9168 let indent_len = match indent_size.kind {
9169 IndentKind::Space => {
9170 buffer.settings_at(line_buffer_range.start, cx).tab_size
9171 }
9172 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9173 };
9174 if old_head.column <= indent_size.len && old_head.column > 0 {
9175 let indent_len = indent_len.get();
9176 new_head = cmp::min(
9177 new_head,
9178 MultiBufferPoint::new(
9179 old_head.row,
9180 ((old_head.column - 1) / indent_len) * indent_len,
9181 ),
9182 );
9183 }
9184 }
9185
9186 selection.set_head(new_head, SelectionGoal::None);
9187 }
9188 }
9189
9190 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9191 s.select(selections)
9192 });
9193 this.insert("", window, cx);
9194 let empty_str: Arc<str> = Arc::from("");
9195 for (buffer, edits) in linked_ranges {
9196 let snapshot = buffer.read(cx).snapshot();
9197 use text::ToPoint as TP;
9198
9199 let edits = edits
9200 .into_iter()
9201 .map(|range| {
9202 let end_point = TP::to_point(&range.end, &snapshot);
9203 let mut start_point = TP::to_point(&range.start, &snapshot);
9204
9205 if end_point == start_point {
9206 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9207 .saturating_sub(1);
9208 start_point =
9209 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9210 };
9211
9212 (start_point..end_point, empty_str.clone())
9213 })
9214 .sorted_by_key(|(range, _)| range.start)
9215 .collect::<Vec<_>>();
9216 buffer.update(cx, |this, cx| {
9217 this.edit(edits, None, cx);
9218 })
9219 }
9220 this.refresh_inline_completion(true, false, window, cx);
9221 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9222 });
9223 }
9224
9225 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9226 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9227 self.transact(window, cx, |this, window, cx| {
9228 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9229 s.move_with(|map, selection| {
9230 if selection.is_empty() {
9231 let cursor = movement::right(map, selection.head());
9232 selection.end = cursor;
9233 selection.reversed = true;
9234 selection.goal = SelectionGoal::None;
9235 }
9236 })
9237 });
9238 this.insert("", window, cx);
9239 this.refresh_inline_completion(true, false, window, cx);
9240 });
9241 }
9242
9243 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9244 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9245 if self.move_to_prev_snippet_tabstop(window, cx) {
9246 return;
9247 }
9248 self.outdent(&Outdent, window, cx);
9249 }
9250
9251 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9252 if self.move_to_next_snippet_tabstop(window, cx) {
9253 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9254 return;
9255 }
9256 if self.read_only(cx) {
9257 return;
9258 }
9259 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9260 let mut selections = self.selections.all_adjusted(cx);
9261 let buffer = self.buffer.read(cx);
9262 let snapshot = buffer.snapshot(cx);
9263 let rows_iter = selections.iter().map(|s| s.head().row);
9264 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9265
9266 let has_some_cursor_in_whitespace = selections
9267 .iter()
9268 .filter(|selection| selection.is_empty())
9269 .any(|selection| {
9270 let cursor = selection.head();
9271 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9272 cursor.column < current_indent.len
9273 });
9274
9275 let mut edits = Vec::new();
9276 let mut prev_edited_row = 0;
9277 let mut row_delta = 0;
9278 for selection in &mut selections {
9279 if selection.start.row != prev_edited_row {
9280 row_delta = 0;
9281 }
9282 prev_edited_row = selection.end.row;
9283
9284 // If the selection is non-empty, then increase the indentation of the selected lines.
9285 if !selection.is_empty() {
9286 row_delta =
9287 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9288 continue;
9289 }
9290
9291 let cursor = selection.head();
9292 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9293 if let Some(suggested_indent) =
9294 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9295 {
9296 // Don't do anything if already at suggested indent
9297 // and there is any other cursor which is not
9298 if has_some_cursor_in_whitespace
9299 && cursor.column == current_indent.len
9300 && current_indent.len == suggested_indent.len
9301 {
9302 continue;
9303 }
9304
9305 // Adjust line and move cursor to suggested indent
9306 // if cursor is not at suggested indent
9307 if cursor.column < suggested_indent.len
9308 && cursor.column <= current_indent.len
9309 && current_indent.len <= suggested_indent.len
9310 {
9311 selection.start = Point::new(cursor.row, suggested_indent.len);
9312 selection.end = selection.start;
9313 if row_delta == 0 {
9314 edits.extend(Buffer::edit_for_indent_size_adjustment(
9315 cursor.row,
9316 current_indent,
9317 suggested_indent,
9318 ));
9319 row_delta = suggested_indent.len - current_indent.len;
9320 }
9321 continue;
9322 }
9323
9324 // If current indent is more than suggested indent
9325 // only move cursor to current indent and skip indent
9326 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9327 selection.start = Point::new(cursor.row, current_indent.len);
9328 selection.end = selection.start;
9329 continue;
9330 }
9331 }
9332
9333 // Otherwise, insert a hard or soft tab.
9334 let settings = buffer.language_settings_at(cursor, cx);
9335 let tab_size = if settings.hard_tabs {
9336 IndentSize::tab()
9337 } else {
9338 let tab_size = settings.tab_size.get();
9339 let indent_remainder = snapshot
9340 .text_for_range(Point::new(cursor.row, 0)..cursor)
9341 .flat_map(str::chars)
9342 .fold(row_delta % tab_size, |counter: u32, c| {
9343 if c == '\t' {
9344 0
9345 } else {
9346 (counter + 1) % tab_size
9347 }
9348 });
9349
9350 let chars_to_next_tab_stop = tab_size - indent_remainder;
9351 IndentSize::spaces(chars_to_next_tab_stop)
9352 };
9353 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9354 selection.end = selection.start;
9355 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9356 row_delta += tab_size.len;
9357 }
9358
9359 self.transact(window, cx, |this, window, cx| {
9360 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9361 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9362 s.select(selections)
9363 });
9364 this.refresh_inline_completion(true, false, window, cx);
9365 });
9366 }
9367
9368 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9369 if self.read_only(cx) {
9370 return;
9371 }
9372 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9373 let mut selections = self.selections.all::<Point>(cx);
9374 let mut prev_edited_row = 0;
9375 let mut row_delta = 0;
9376 let mut edits = Vec::new();
9377 let buffer = self.buffer.read(cx);
9378 let snapshot = buffer.snapshot(cx);
9379 for selection in &mut selections {
9380 if selection.start.row != prev_edited_row {
9381 row_delta = 0;
9382 }
9383 prev_edited_row = selection.end.row;
9384
9385 row_delta =
9386 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9387 }
9388
9389 self.transact(window, cx, |this, window, cx| {
9390 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9391 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9392 s.select(selections)
9393 });
9394 });
9395 }
9396
9397 fn indent_selection(
9398 buffer: &MultiBuffer,
9399 snapshot: &MultiBufferSnapshot,
9400 selection: &mut Selection<Point>,
9401 edits: &mut Vec<(Range<Point>, String)>,
9402 delta_for_start_row: u32,
9403 cx: &App,
9404 ) -> u32 {
9405 let settings = buffer.language_settings_at(selection.start, cx);
9406 let tab_size = settings.tab_size.get();
9407 let indent_kind = if settings.hard_tabs {
9408 IndentKind::Tab
9409 } else {
9410 IndentKind::Space
9411 };
9412 let mut start_row = selection.start.row;
9413 let mut end_row = selection.end.row + 1;
9414
9415 // If a selection ends at the beginning of a line, don't indent
9416 // that last line.
9417 if selection.end.column == 0 && selection.end.row > selection.start.row {
9418 end_row -= 1;
9419 }
9420
9421 // Avoid re-indenting a row that has already been indented by a
9422 // previous selection, but still update this selection's column
9423 // to reflect that indentation.
9424 if delta_for_start_row > 0 {
9425 start_row += 1;
9426 selection.start.column += delta_for_start_row;
9427 if selection.end.row == selection.start.row {
9428 selection.end.column += delta_for_start_row;
9429 }
9430 }
9431
9432 let mut delta_for_end_row = 0;
9433 let has_multiple_rows = start_row + 1 != end_row;
9434 for row in start_row..end_row {
9435 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9436 let indent_delta = match (current_indent.kind, indent_kind) {
9437 (IndentKind::Space, IndentKind::Space) => {
9438 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9439 IndentSize::spaces(columns_to_next_tab_stop)
9440 }
9441 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9442 (_, IndentKind::Tab) => IndentSize::tab(),
9443 };
9444
9445 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9446 0
9447 } else {
9448 selection.start.column
9449 };
9450 let row_start = Point::new(row, start);
9451 edits.push((
9452 row_start..row_start,
9453 indent_delta.chars().collect::<String>(),
9454 ));
9455
9456 // Update this selection's endpoints to reflect the indentation.
9457 if row == selection.start.row {
9458 selection.start.column += indent_delta.len;
9459 }
9460 if row == selection.end.row {
9461 selection.end.column += indent_delta.len;
9462 delta_for_end_row = indent_delta.len;
9463 }
9464 }
9465
9466 if selection.start.row == selection.end.row {
9467 delta_for_start_row + delta_for_end_row
9468 } else {
9469 delta_for_end_row
9470 }
9471 }
9472
9473 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9474 if self.read_only(cx) {
9475 return;
9476 }
9477 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9478 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9479 let selections = self.selections.all::<Point>(cx);
9480 let mut deletion_ranges = Vec::new();
9481 let mut last_outdent = None;
9482 {
9483 let buffer = self.buffer.read(cx);
9484 let snapshot = buffer.snapshot(cx);
9485 for selection in &selections {
9486 let settings = buffer.language_settings_at(selection.start, cx);
9487 let tab_size = settings.tab_size.get();
9488 let mut rows = selection.spanned_rows(false, &display_map);
9489
9490 // Avoid re-outdenting a row that has already been outdented by a
9491 // previous selection.
9492 if let Some(last_row) = last_outdent {
9493 if last_row == rows.start {
9494 rows.start = rows.start.next_row();
9495 }
9496 }
9497 let has_multiple_rows = rows.len() > 1;
9498 for row in rows.iter_rows() {
9499 let indent_size = snapshot.indent_size_for_line(row);
9500 if indent_size.len > 0 {
9501 let deletion_len = match indent_size.kind {
9502 IndentKind::Space => {
9503 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9504 if columns_to_prev_tab_stop == 0 {
9505 tab_size
9506 } else {
9507 columns_to_prev_tab_stop
9508 }
9509 }
9510 IndentKind::Tab => 1,
9511 };
9512 let start = if has_multiple_rows
9513 || deletion_len > selection.start.column
9514 || indent_size.len < selection.start.column
9515 {
9516 0
9517 } else {
9518 selection.start.column - deletion_len
9519 };
9520 deletion_ranges.push(
9521 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9522 );
9523 last_outdent = Some(row);
9524 }
9525 }
9526 }
9527 }
9528
9529 self.transact(window, cx, |this, window, cx| {
9530 this.buffer.update(cx, |buffer, cx| {
9531 let empty_str: Arc<str> = Arc::default();
9532 buffer.edit(
9533 deletion_ranges
9534 .into_iter()
9535 .map(|range| (range, empty_str.clone())),
9536 None,
9537 cx,
9538 );
9539 });
9540 let selections = this.selections.all::<usize>(cx);
9541 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9542 s.select(selections)
9543 });
9544 });
9545 }
9546
9547 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9548 if self.read_only(cx) {
9549 return;
9550 }
9551 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9552 let selections = self
9553 .selections
9554 .all::<usize>(cx)
9555 .into_iter()
9556 .map(|s| s.range());
9557
9558 self.transact(window, cx, |this, window, cx| {
9559 this.buffer.update(cx, |buffer, cx| {
9560 buffer.autoindent_ranges(selections, cx);
9561 });
9562 let selections = this.selections.all::<usize>(cx);
9563 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9564 s.select(selections)
9565 });
9566 });
9567 }
9568
9569 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9570 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9571 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9572 let selections = self.selections.all::<Point>(cx);
9573
9574 let mut new_cursors = Vec::new();
9575 let mut edit_ranges = Vec::new();
9576 let mut selections = selections.iter().peekable();
9577 while let Some(selection) = selections.next() {
9578 let mut rows = selection.spanned_rows(false, &display_map);
9579 let goal_display_column = selection.head().to_display_point(&display_map).column();
9580
9581 // Accumulate contiguous regions of rows that we want to delete.
9582 while let Some(next_selection) = selections.peek() {
9583 let next_rows = next_selection.spanned_rows(false, &display_map);
9584 if next_rows.start <= rows.end {
9585 rows.end = next_rows.end;
9586 selections.next().unwrap();
9587 } else {
9588 break;
9589 }
9590 }
9591
9592 let buffer = &display_map.buffer_snapshot;
9593 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9594 let edit_end;
9595 let cursor_buffer_row;
9596 if buffer.max_point().row >= rows.end.0 {
9597 // If there's a line after the range, delete the \n from the end of the row range
9598 // and position the cursor on the next line.
9599 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9600 cursor_buffer_row = rows.end;
9601 } else {
9602 // If there isn't a line after the range, delete the \n from the line before the
9603 // start of the row range and position the cursor there.
9604 edit_start = edit_start.saturating_sub(1);
9605 edit_end = buffer.len();
9606 cursor_buffer_row = rows.start.previous_row();
9607 }
9608
9609 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9610 *cursor.column_mut() =
9611 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9612
9613 new_cursors.push((
9614 selection.id,
9615 buffer.anchor_after(cursor.to_point(&display_map)),
9616 ));
9617 edit_ranges.push(edit_start..edit_end);
9618 }
9619
9620 self.transact(window, cx, |this, window, cx| {
9621 let buffer = this.buffer.update(cx, |buffer, cx| {
9622 let empty_str: Arc<str> = Arc::default();
9623 buffer.edit(
9624 edit_ranges
9625 .into_iter()
9626 .map(|range| (range, empty_str.clone())),
9627 None,
9628 cx,
9629 );
9630 buffer.snapshot(cx)
9631 });
9632 let new_selections = new_cursors
9633 .into_iter()
9634 .map(|(id, cursor)| {
9635 let cursor = cursor.to_point(&buffer);
9636 Selection {
9637 id,
9638 start: cursor,
9639 end: cursor,
9640 reversed: false,
9641 goal: SelectionGoal::None,
9642 }
9643 })
9644 .collect();
9645
9646 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9647 s.select(new_selections);
9648 });
9649 });
9650 }
9651
9652 pub fn join_lines_impl(
9653 &mut self,
9654 insert_whitespace: bool,
9655 window: &mut Window,
9656 cx: &mut Context<Self>,
9657 ) {
9658 if self.read_only(cx) {
9659 return;
9660 }
9661 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9662 for selection in self.selections.all::<Point>(cx) {
9663 let start = MultiBufferRow(selection.start.row);
9664 // Treat single line selections as if they include the next line. Otherwise this action
9665 // would do nothing for single line selections individual cursors.
9666 let end = if selection.start.row == selection.end.row {
9667 MultiBufferRow(selection.start.row + 1)
9668 } else {
9669 MultiBufferRow(selection.end.row)
9670 };
9671
9672 if let Some(last_row_range) = row_ranges.last_mut() {
9673 if start <= last_row_range.end {
9674 last_row_range.end = end;
9675 continue;
9676 }
9677 }
9678 row_ranges.push(start..end);
9679 }
9680
9681 let snapshot = self.buffer.read(cx).snapshot(cx);
9682 let mut cursor_positions = Vec::new();
9683 for row_range in &row_ranges {
9684 let anchor = snapshot.anchor_before(Point::new(
9685 row_range.end.previous_row().0,
9686 snapshot.line_len(row_range.end.previous_row()),
9687 ));
9688 cursor_positions.push(anchor..anchor);
9689 }
9690
9691 self.transact(window, cx, |this, window, cx| {
9692 for row_range in row_ranges.into_iter().rev() {
9693 for row in row_range.iter_rows().rev() {
9694 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9695 let next_line_row = row.next_row();
9696 let indent = snapshot.indent_size_for_line(next_line_row);
9697 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9698
9699 let replace =
9700 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9701 " "
9702 } else {
9703 ""
9704 };
9705
9706 this.buffer.update(cx, |buffer, cx| {
9707 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9708 });
9709 }
9710 }
9711
9712 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9713 s.select_anchor_ranges(cursor_positions)
9714 });
9715 });
9716 }
9717
9718 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9719 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9720 self.join_lines_impl(true, window, cx);
9721 }
9722
9723 pub fn sort_lines_case_sensitive(
9724 &mut self,
9725 _: &SortLinesCaseSensitive,
9726 window: &mut Window,
9727 cx: &mut Context<Self>,
9728 ) {
9729 self.manipulate_lines(window, cx, |lines| lines.sort())
9730 }
9731
9732 pub fn sort_lines_case_insensitive(
9733 &mut self,
9734 _: &SortLinesCaseInsensitive,
9735 window: &mut Window,
9736 cx: &mut Context<Self>,
9737 ) {
9738 self.manipulate_lines(window, cx, |lines| {
9739 lines.sort_by_key(|line| line.to_lowercase())
9740 })
9741 }
9742
9743 pub fn unique_lines_case_insensitive(
9744 &mut self,
9745 _: &UniqueLinesCaseInsensitive,
9746 window: &mut Window,
9747 cx: &mut Context<Self>,
9748 ) {
9749 self.manipulate_lines(window, cx, |lines| {
9750 let mut seen = HashSet::default();
9751 lines.retain(|line| seen.insert(line.to_lowercase()));
9752 })
9753 }
9754
9755 pub fn unique_lines_case_sensitive(
9756 &mut self,
9757 _: &UniqueLinesCaseSensitive,
9758 window: &mut Window,
9759 cx: &mut Context<Self>,
9760 ) {
9761 self.manipulate_lines(window, cx, |lines| {
9762 let mut seen = HashSet::default();
9763 lines.retain(|line| seen.insert(*line));
9764 })
9765 }
9766
9767 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9768 let Some(project) = self.project.clone() else {
9769 return;
9770 };
9771 self.reload(project, window, cx)
9772 .detach_and_notify_err(window, cx);
9773 }
9774
9775 pub fn restore_file(
9776 &mut self,
9777 _: &::git::RestoreFile,
9778 window: &mut Window,
9779 cx: &mut Context<Self>,
9780 ) {
9781 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9782 let mut buffer_ids = HashSet::default();
9783 let snapshot = self.buffer().read(cx).snapshot(cx);
9784 for selection in self.selections.all::<usize>(cx) {
9785 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9786 }
9787
9788 let buffer = self.buffer().read(cx);
9789 let ranges = buffer_ids
9790 .into_iter()
9791 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9792 .collect::<Vec<_>>();
9793
9794 self.restore_hunks_in_ranges(ranges, window, cx);
9795 }
9796
9797 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9798 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9799 let selections = self
9800 .selections
9801 .all(cx)
9802 .into_iter()
9803 .map(|s| s.range())
9804 .collect();
9805 self.restore_hunks_in_ranges(selections, window, cx);
9806 }
9807
9808 pub fn restore_hunks_in_ranges(
9809 &mut self,
9810 ranges: Vec<Range<Point>>,
9811 window: &mut Window,
9812 cx: &mut Context<Editor>,
9813 ) {
9814 let mut revert_changes = HashMap::default();
9815 let chunk_by = self
9816 .snapshot(window, cx)
9817 .hunks_for_ranges(ranges)
9818 .into_iter()
9819 .chunk_by(|hunk| hunk.buffer_id);
9820 for (buffer_id, hunks) in &chunk_by {
9821 let hunks = hunks.collect::<Vec<_>>();
9822 for hunk in &hunks {
9823 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9824 }
9825 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9826 }
9827 drop(chunk_by);
9828 if !revert_changes.is_empty() {
9829 self.transact(window, cx, |editor, window, cx| {
9830 editor.restore(revert_changes, window, cx);
9831 });
9832 }
9833 }
9834
9835 pub fn open_active_item_in_terminal(
9836 &mut self,
9837 _: &OpenInTerminal,
9838 window: &mut Window,
9839 cx: &mut Context<Self>,
9840 ) {
9841 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9842 let project_path = buffer.read(cx).project_path(cx)?;
9843 let project = self.project.as_ref()?.read(cx);
9844 let entry = project.entry_for_path(&project_path, cx)?;
9845 let parent = match &entry.canonical_path {
9846 Some(canonical_path) => canonical_path.to_path_buf(),
9847 None => project.absolute_path(&project_path, cx)?,
9848 }
9849 .parent()?
9850 .to_path_buf();
9851 Some(parent)
9852 }) {
9853 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9854 }
9855 }
9856
9857 fn set_breakpoint_context_menu(
9858 &mut self,
9859 display_row: DisplayRow,
9860 position: Option<Anchor>,
9861 clicked_point: gpui::Point<Pixels>,
9862 window: &mut Window,
9863 cx: &mut Context<Self>,
9864 ) {
9865 if !cx.has_flag::<DebuggerFeatureFlag>() {
9866 return;
9867 }
9868 let source = self
9869 .buffer
9870 .read(cx)
9871 .snapshot(cx)
9872 .anchor_before(Point::new(display_row.0, 0u32));
9873
9874 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9875
9876 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9877 self,
9878 source,
9879 clicked_point,
9880 context_menu,
9881 window,
9882 cx,
9883 );
9884 }
9885
9886 fn add_edit_breakpoint_block(
9887 &mut self,
9888 anchor: Anchor,
9889 breakpoint: &Breakpoint,
9890 edit_action: BreakpointPromptEditAction,
9891 window: &mut Window,
9892 cx: &mut Context<Self>,
9893 ) {
9894 let weak_editor = cx.weak_entity();
9895 let bp_prompt = cx.new(|cx| {
9896 BreakpointPromptEditor::new(
9897 weak_editor,
9898 anchor,
9899 breakpoint.clone(),
9900 edit_action,
9901 window,
9902 cx,
9903 )
9904 });
9905
9906 let height = bp_prompt.update(cx, |this, cx| {
9907 this.prompt
9908 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9909 });
9910 let cloned_prompt = bp_prompt.clone();
9911 let blocks = vec![BlockProperties {
9912 style: BlockStyle::Sticky,
9913 placement: BlockPlacement::Above(anchor),
9914 height: Some(height),
9915 render: Arc::new(move |cx| {
9916 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9917 cloned_prompt.clone().into_any_element()
9918 }),
9919 priority: 0,
9920 render_in_minimap: true,
9921 }];
9922
9923 let focus_handle = bp_prompt.focus_handle(cx);
9924 window.focus(&focus_handle);
9925
9926 let block_ids = self.insert_blocks(blocks, None, cx);
9927 bp_prompt.update(cx, |prompt, _| {
9928 prompt.add_block_ids(block_ids);
9929 });
9930 }
9931
9932 pub(crate) fn breakpoint_at_row(
9933 &self,
9934 row: u32,
9935 window: &mut Window,
9936 cx: &mut Context<Self>,
9937 ) -> Option<(Anchor, Breakpoint)> {
9938 let snapshot = self.snapshot(window, cx);
9939 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9940
9941 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9942 }
9943
9944 pub(crate) fn breakpoint_at_anchor(
9945 &self,
9946 breakpoint_position: Anchor,
9947 snapshot: &EditorSnapshot,
9948 cx: &mut Context<Self>,
9949 ) -> Option<(Anchor, Breakpoint)> {
9950 let project = self.project.clone()?;
9951
9952 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9953 snapshot
9954 .buffer_snapshot
9955 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9956 })?;
9957
9958 let enclosing_excerpt = breakpoint_position.excerpt_id;
9959 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
9960 let buffer_snapshot = buffer.read(cx).snapshot();
9961
9962 let row = buffer_snapshot
9963 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9964 .row;
9965
9966 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9967 let anchor_end = snapshot
9968 .buffer_snapshot
9969 .anchor_after(Point::new(row, line_len));
9970
9971 let bp = self
9972 .breakpoint_store
9973 .as_ref()?
9974 .read_with(cx, |breakpoint_store, cx| {
9975 breakpoint_store
9976 .breakpoints(
9977 &buffer,
9978 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9979 &buffer_snapshot,
9980 cx,
9981 )
9982 .next()
9983 .and_then(|(bp, _)| {
9984 let breakpoint_row = buffer_snapshot
9985 .summary_for_anchor::<text::PointUtf16>(&bp.position)
9986 .row;
9987
9988 if breakpoint_row == row {
9989 snapshot
9990 .buffer_snapshot
9991 .anchor_in_excerpt(enclosing_excerpt, bp.position)
9992 .map(|position| (position, bp.bp.clone()))
9993 } else {
9994 None
9995 }
9996 })
9997 });
9998 bp
9999 }
10000
10001 pub fn edit_log_breakpoint(
10002 &mut self,
10003 _: &EditLogBreakpoint,
10004 window: &mut Window,
10005 cx: &mut Context<Self>,
10006 ) {
10007 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10008 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10009 message: None,
10010 state: BreakpointState::Enabled,
10011 condition: None,
10012 hit_condition: None,
10013 });
10014
10015 self.add_edit_breakpoint_block(
10016 anchor,
10017 &breakpoint,
10018 BreakpointPromptEditAction::Log,
10019 window,
10020 cx,
10021 );
10022 }
10023 }
10024
10025 fn breakpoints_at_cursors(
10026 &self,
10027 window: &mut Window,
10028 cx: &mut Context<Self>,
10029 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10030 let snapshot = self.snapshot(window, cx);
10031 let cursors = self
10032 .selections
10033 .disjoint_anchors()
10034 .into_iter()
10035 .map(|selection| {
10036 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10037
10038 let breakpoint_position = self
10039 .breakpoint_at_row(cursor_position.row, window, cx)
10040 .map(|bp| bp.0)
10041 .unwrap_or_else(|| {
10042 snapshot
10043 .display_snapshot
10044 .buffer_snapshot
10045 .anchor_after(Point::new(cursor_position.row, 0))
10046 });
10047
10048 let breakpoint = self
10049 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10050 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10051
10052 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10053 })
10054 // 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.
10055 .collect::<HashMap<Anchor, _>>();
10056
10057 cursors.into_iter().collect()
10058 }
10059
10060 pub fn enable_breakpoint(
10061 &mut self,
10062 _: &crate::actions::EnableBreakpoint,
10063 window: &mut Window,
10064 cx: &mut Context<Self>,
10065 ) {
10066 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10067 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10068 continue;
10069 };
10070 self.edit_breakpoint_at_anchor(
10071 anchor,
10072 breakpoint,
10073 BreakpointEditAction::InvertState,
10074 cx,
10075 );
10076 }
10077 }
10078
10079 pub fn disable_breakpoint(
10080 &mut self,
10081 _: &crate::actions::DisableBreakpoint,
10082 window: &mut Window,
10083 cx: &mut Context<Self>,
10084 ) {
10085 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10086 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10087 continue;
10088 };
10089 self.edit_breakpoint_at_anchor(
10090 anchor,
10091 breakpoint,
10092 BreakpointEditAction::InvertState,
10093 cx,
10094 );
10095 }
10096 }
10097
10098 pub fn toggle_breakpoint(
10099 &mut self,
10100 _: &crate::actions::ToggleBreakpoint,
10101 window: &mut Window,
10102 cx: &mut Context<Self>,
10103 ) {
10104 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10105 if let Some(breakpoint) = breakpoint {
10106 self.edit_breakpoint_at_anchor(
10107 anchor,
10108 breakpoint,
10109 BreakpointEditAction::Toggle,
10110 cx,
10111 );
10112 } else {
10113 self.edit_breakpoint_at_anchor(
10114 anchor,
10115 Breakpoint::new_standard(),
10116 BreakpointEditAction::Toggle,
10117 cx,
10118 );
10119 }
10120 }
10121 }
10122
10123 pub fn edit_breakpoint_at_anchor(
10124 &mut self,
10125 breakpoint_position: Anchor,
10126 breakpoint: Breakpoint,
10127 edit_action: BreakpointEditAction,
10128 cx: &mut Context<Self>,
10129 ) {
10130 let Some(breakpoint_store) = &self.breakpoint_store else {
10131 return;
10132 };
10133
10134 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10135 if breakpoint_position == Anchor::min() {
10136 self.buffer()
10137 .read(cx)
10138 .excerpt_buffer_ids()
10139 .into_iter()
10140 .next()
10141 } else {
10142 None
10143 }
10144 }) else {
10145 return;
10146 };
10147
10148 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10149 return;
10150 };
10151
10152 breakpoint_store.update(cx, |breakpoint_store, cx| {
10153 breakpoint_store.toggle_breakpoint(
10154 buffer,
10155 BreakpointWithPosition {
10156 position: breakpoint_position.text_anchor,
10157 bp: breakpoint,
10158 },
10159 edit_action,
10160 cx,
10161 );
10162 });
10163
10164 cx.notify();
10165 }
10166
10167 #[cfg(any(test, feature = "test-support"))]
10168 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10169 self.breakpoint_store.clone()
10170 }
10171
10172 pub fn prepare_restore_change(
10173 &self,
10174 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10175 hunk: &MultiBufferDiffHunk,
10176 cx: &mut App,
10177 ) -> Option<()> {
10178 if hunk.is_created_file() {
10179 return None;
10180 }
10181 let buffer = self.buffer.read(cx);
10182 let diff = buffer.diff_for(hunk.buffer_id)?;
10183 let buffer = buffer.buffer(hunk.buffer_id)?;
10184 let buffer = buffer.read(cx);
10185 let original_text = diff
10186 .read(cx)
10187 .base_text()
10188 .as_rope()
10189 .slice(hunk.diff_base_byte_range.clone());
10190 let buffer_snapshot = buffer.snapshot();
10191 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10192 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10193 probe
10194 .0
10195 .start
10196 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10197 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10198 }) {
10199 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10200 Some(())
10201 } else {
10202 None
10203 }
10204 }
10205
10206 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10207 self.manipulate_lines(window, cx, |lines| lines.reverse())
10208 }
10209
10210 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10211 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10212 }
10213
10214 fn manipulate_lines<Fn>(
10215 &mut self,
10216 window: &mut Window,
10217 cx: &mut Context<Self>,
10218 mut callback: Fn,
10219 ) where
10220 Fn: FnMut(&mut Vec<&str>),
10221 {
10222 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10223
10224 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10225 let buffer = self.buffer.read(cx).snapshot(cx);
10226
10227 let mut edits = Vec::new();
10228
10229 let selections = self.selections.all::<Point>(cx);
10230 let mut selections = selections.iter().peekable();
10231 let mut contiguous_row_selections = Vec::new();
10232 let mut new_selections = Vec::new();
10233 let mut added_lines = 0;
10234 let mut removed_lines = 0;
10235
10236 while let Some(selection) = selections.next() {
10237 let (start_row, end_row) = consume_contiguous_rows(
10238 &mut contiguous_row_selections,
10239 selection,
10240 &display_map,
10241 &mut selections,
10242 );
10243
10244 let start_point = Point::new(start_row.0, 0);
10245 let end_point = Point::new(
10246 end_row.previous_row().0,
10247 buffer.line_len(end_row.previous_row()),
10248 );
10249 let text = buffer
10250 .text_for_range(start_point..end_point)
10251 .collect::<String>();
10252
10253 let mut lines = text.split('\n').collect_vec();
10254
10255 let lines_before = lines.len();
10256 callback(&mut lines);
10257 let lines_after = lines.len();
10258
10259 edits.push((start_point..end_point, lines.join("\n")));
10260
10261 // Selections must change based on added and removed line count
10262 let start_row =
10263 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10264 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10265 new_selections.push(Selection {
10266 id: selection.id,
10267 start: start_row,
10268 end: end_row,
10269 goal: SelectionGoal::None,
10270 reversed: selection.reversed,
10271 });
10272
10273 if lines_after > lines_before {
10274 added_lines += lines_after - lines_before;
10275 } else if lines_before > lines_after {
10276 removed_lines += lines_before - lines_after;
10277 }
10278 }
10279
10280 self.transact(window, cx, |this, window, cx| {
10281 let buffer = this.buffer.update(cx, |buffer, cx| {
10282 buffer.edit(edits, None, cx);
10283 buffer.snapshot(cx)
10284 });
10285
10286 // Recalculate offsets on newly edited buffer
10287 let new_selections = new_selections
10288 .iter()
10289 .map(|s| {
10290 let start_point = Point::new(s.start.0, 0);
10291 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10292 Selection {
10293 id: s.id,
10294 start: buffer.point_to_offset(start_point),
10295 end: buffer.point_to_offset(end_point),
10296 goal: s.goal,
10297 reversed: s.reversed,
10298 }
10299 })
10300 .collect();
10301
10302 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10303 s.select(new_selections);
10304 });
10305
10306 this.request_autoscroll(Autoscroll::fit(), cx);
10307 });
10308 }
10309
10310 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10311 self.manipulate_text(window, cx, |text| {
10312 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10313 if has_upper_case_characters {
10314 text.to_lowercase()
10315 } else {
10316 text.to_uppercase()
10317 }
10318 })
10319 }
10320
10321 pub fn convert_to_upper_case(
10322 &mut self,
10323 _: &ConvertToUpperCase,
10324 window: &mut Window,
10325 cx: &mut Context<Self>,
10326 ) {
10327 self.manipulate_text(window, cx, |text| text.to_uppercase())
10328 }
10329
10330 pub fn convert_to_lower_case(
10331 &mut self,
10332 _: &ConvertToLowerCase,
10333 window: &mut Window,
10334 cx: &mut Context<Self>,
10335 ) {
10336 self.manipulate_text(window, cx, |text| text.to_lowercase())
10337 }
10338
10339 pub fn convert_to_title_case(
10340 &mut self,
10341 _: &ConvertToTitleCase,
10342 window: &mut Window,
10343 cx: &mut Context<Self>,
10344 ) {
10345 self.manipulate_text(window, cx, |text| {
10346 text.split('\n')
10347 .map(|line| line.to_case(Case::Title))
10348 .join("\n")
10349 })
10350 }
10351
10352 pub fn convert_to_snake_case(
10353 &mut self,
10354 _: &ConvertToSnakeCase,
10355 window: &mut Window,
10356 cx: &mut Context<Self>,
10357 ) {
10358 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10359 }
10360
10361 pub fn convert_to_kebab_case(
10362 &mut self,
10363 _: &ConvertToKebabCase,
10364 window: &mut Window,
10365 cx: &mut Context<Self>,
10366 ) {
10367 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10368 }
10369
10370 pub fn convert_to_upper_camel_case(
10371 &mut self,
10372 _: &ConvertToUpperCamelCase,
10373 window: &mut Window,
10374 cx: &mut Context<Self>,
10375 ) {
10376 self.manipulate_text(window, cx, |text| {
10377 text.split('\n')
10378 .map(|line| line.to_case(Case::UpperCamel))
10379 .join("\n")
10380 })
10381 }
10382
10383 pub fn convert_to_lower_camel_case(
10384 &mut self,
10385 _: &ConvertToLowerCamelCase,
10386 window: &mut Window,
10387 cx: &mut Context<Self>,
10388 ) {
10389 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10390 }
10391
10392 pub fn convert_to_opposite_case(
10393 &mut self,
10394 _: &ConvertToOppositeCase,
10395 window: &mut Window,
10396 cx: &mut Context<Self>,
10397 ) {
10398 self.manipulate_text(window, cx, |text| {
10399 text.chars()
10400 .fold(String::with_capacity(text.len()), |mut t, c| {
10401 if c.is_uppercase() {
10402 t.extend(c.to_lowercase());
10403 } else {
10404 t.extend(c.to_uppercase());
10405 }
10406 t
10407 })
10408 })
10409 }
10410
10411 pub fn convert_to_rot13(
10412 &mut self,
10413 _: &ConvertToRot13,
10414 window: &mut Window,
10415 cx: &mut Context<Self>,
10416 ) {
10417 self.manipulate_text(window, cx, |text| {
10418 text.chars()
10419 .map(|c| match c {
10420 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10421 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10422 _ => c,
10423 })
10424 .collect()
10425 })
10426 }
10427
10428 pub fn convert_to_rot47(
10429 &mut self,
10430 _: &ConvertToRot47,
10431 window: &mut Window,
10432 cx: &mut Context<Self>,
10433 ) {
10434 self.manipulate_text(window, cx, |text| {
10435 text.chars()
10436 .map(|c| {
10437 let code_point = c as u32;
10438 if code_point >= 33 && code_point <= 126 {
10439 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10440 }
10441 c
10442 })
10443 .collect()
10444 })
10445 }
10446
10447 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10448 where
10449 Fn: FnMut(&str) -> String,
10450 {
10451 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10452 let buffer = self.buffer.read(cx).snapshot(cx);
10453
10454 let mut new_selections = Vec::new();
10455 let mut edits = Vec::new();
10456 let mut selection_adjustment = 0i32;
10457
10458 for selection in self.selections.all::<usize>(cx) {
10459 let selection_is_empty = selection.is_empty();
10460
10461 let (start, end) = if selection_is_empty {
10462 let word_range = movement::surrounding_word(
10463 &display_map,
10464 selection.start.to_display_point(&display_map),
10465 );
10466 let start = word_range.start.to_offset(&display_map, Bias::Left);
10467 let end = word_range.end.to_offset(&display_map, Bias::Left);
10468 (start, end)
10469 } else {
10470 (selection.start, selection.end)
10471 };
10472
10473 let text = buffer.text_for_range(start..end).collect::<String>();
10474 let old_length = text.len() as i32;
10475 let text = callback(&text);
10476
10477 new_selections.push(Selection {
10478 start: (start as i32 - selection_adjustment) as usize,
10479 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10480 goal: SelectionGoal::None,
10481 ..selection
10482 });
10483
10484 selection_adjustment += old_length - text.len() as i32;
10485
10486 edits.push((start..end, text));
10487 }
10488
10489 self.transact(window, cx, |this, window, cx| {
10490 this.buffer.update(cx, |buffer, cx| {
10491 buffer.edit(edits, None, cx);
10492 });
10493
10494 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10495 s.select(new_selections);
10496 });
10497
10498 this.request_autoscroll(Autoscroll::fit(), cx);
10499 });
10500 }
10501
10502 pub fn duplicate(
10503 &mut self,
10504 upwards: bool,
10505 whole_lines: bool,
10506 window: &mut Window,
10507 cx: &mut Context<Self>,
10508 ) {
10509 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10510
10511 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10512 let buffer = &display_map.buffer_snapshot;
10513 let selections = self.selections.all::<Point>(cx);
10514
10515 let mut edits = Vec::new();
10516 let mut selections_iter = selections.iter().peekable();
10517 while let Some(selection) = selections_iter.next() {
10518 let mut rows = selection.spanned_rows(false, &display_map);
10519 // duplicate line-wise
10520 if whole_lines || selection.start == selection.end {
10521 // Avoid duplicating the same lines twice.
10522 while let Some(next_selection) = selections_iter.peek() {
10523 let next_rows = next_selection.spanned_rows(false, &display_map);
10524 if next_rows.start < rows.end {
10525 rows.end = next_rows.end;
10526 selections_iter.next().unwrap();
10527 } else {
10528 break;
10529 }
10530 }
10531
10532 // Copy the text from the selected row region and splice it either at the start
10533 // or end of the region.
10534 let start = Point::new(rows.start.0, 0);
10535 let end = Point::new(
10536 rows.end.previous_row().0,
10537 buffer.line_len(rows.end.previous_row()),
10538 );
10539 let text = buffer
10540 .text_for_range(start..end)
10541 .chain(Some("\n"))
10542 .collect::<String>();
10543 let insert_location = if upwards {
10544 Point::new(rows.end.0, 0)
10545 } else {
10546 start
10547 };
10548 edits.push((insert_location..insert_location, text));
10549 } else {
10550 // duplicate character-wise
10551 let start = selection.start;
10552 let end = selection.end;
10553 let text = buffer.text_for_range(start..end).collect::<String>();
10554 edits.push((selection.end..selection.end, text));
10555 }
10556 }
10557
10558 self.transact(window, cx, |this, _, cx| {
10559 this.buffer.update(cx, |buffer, cx| {
10560 buffer.edit(edits, None, cx);
10561 });
10562
10563 this.request_autoscroll(Autoscroll::fit(), cx);
10564 });
10565 }
10566
10567 pub fn duplicate_line_up(
10568 &mut self,
10569 _: &DuplicateLineUp,
10570 window: &mut Window,
10571 cx: &mut Context<Self>,
10572 ) {
10573 self.duplicate(true, true, window, cx);
10574 }
10575
10576 pub fn duplicate_line_down(
10577 &mut self,
10578 _: &DuplicateLineDown,
10579 window: &mut Window,
10580 cx: &mut Context<Self>,
10581 ) {
10582 self.duplicate(false, true, window, cx);
10583 }
10584
10585 pub fn duplicate_selection(
10586 &mut self,
10587 _: &DuplicateSelection,
10588 window: &mut Window,
10589 cx: &mut Context<Self>,
10590 ) {
10591 self.duplicate(false, false, window, cx);
10592 }
10593
10594 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10595 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10596
10597 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10598 let buffer = self.buffer.read(cx).snapshot(cx);
10599
10600 let mut edits = Vec::new();
10601 let mut unfold_ranges = Vec::new();
10602 let mut refold_creases = Vec::new();
10603
10604 let selections = self.selections.all::<Point>(cx);
10605 let mut selections = selections.iter().peekable();
10606 let mut contiguous_row_selections = Vec::new();
10607 let mut new_selections = Vec::new();
10608
10609 while let Some(selection) = selections.next() {
10610 // Find all the selections that span a contiguous row range
10611 let (start_row, end_row) = consume_contiguous_rows(
10612 &mut contiguous_row_selections,
10613 selection,
10614 &display_map,
10615 &mut selections,
10616 );
10617
10618 // Move the text spanned by the row range to be before the line preceding the row range
10619 if start_row.0 > 0 {
10620 let range_to_move = Point::new(
10621 start_row.previous_row().0,
10622 buffer.line_len(start_row.previous_row()),
10623 )
10624 ..Point::new(
10625 end_row.previous_row().0,
10626 buffer.line_len(end_row.previous_row()),
10627 );
10628 let insertion_point = display_map
10629 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10630 .0;
10631
10632 // Don't move lines across excerpts
10633 if buffer
10634 .excerpt_containing(insertion_point..range_to_move.end)
10635 .is_some()
10636 {
10637 let text = buffer
10638 .text_for_range(range_to_move.clone())
10639 .flat_map(|s| s.chars())
10640 .skip(1)
10641 .chain(['\n'])
10642 .collect::<String>();
10643
10644 edits.push((
10645 buffer.anchor_after(range_to_move.start)
10646 ..buffer.anchor_before(range_to_move.end),
10647 String::new(),
10648 ));
10649 let insertion_anchor = buffer.anchor_after(insertion_point);
10650 edits.push((insertion_anchor..insertion_anchor, text));
10651
10652 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10653
10654 // Move selections up
10655 new_selections.extend(contiguous_row_selections.drain(..).map(
10656 |mut selection| {
10657 selection.start.row -= row_delta;
10658 selection.end.row -= row_delta;
10659 selection
10660 },
10661 ));
10662
10663 // Move folds up
10664 unfold_ranges.push(range_to_move.clone());
10665 for fold in display_map.folds_in_range(
10666 buffer.anchor_before(range_to_move.start)
10667 ..buffer.anchor_after(range_to_move.end),
10668 ) {
10669 let mut start = fold.range.start.to_point(&buffer);
10670 let mut end = fold.range.end.to_point(&buffer);
10671 start.row -= row_delta;
10672 end.row -= row_delta;
10673 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10674 }
10675 }
10676 }
10677
10678 // If we didn't move line(s), preserve the existing selections
10679 new_selections.append(&mut contiguous_row_selections);
10680 }
10681
10682 self.transact(window, cx, |this, window, cx| {
10683 this.unfold_ranges(&unfold_ranges, true, true, cx);
10684 this.buffer.update(cx, |buffer, cx| {
10685 for (range, text) in edits {
10686 buffer.edit([(range, text)], None, cx);
10687 }
10688 });
10689 this.fold_creases(refold_creases, true, window, cx);
10690 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10691 s.select(new_selections);
10692 })
10693 });
10694 }
10695
10696 pub fn move_line_down(
10697 &mut self,
10698 _: &MoveLineDown,
10699 window: &mut Window,
10700 cx: &mut Context<Self>,
10701 ) {
10702 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10703
10704 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10705 let buffer = self.buffer.read(cx).snapshot(cx);
10706
10707 let mut edits = Vec::new();
10708 let mut unfold_ranges = Vec::new();
10709 let mut refold_creases = Vec::new();
10710
10711 let selections = self.selections.all::<Point>(cx);
10712 let mut selections = selections.iter().peekable();
10713 let mut contiguous_row_selections = Vec::new();
10714 let mut new_selections = Vec::new();
10715
10716 while let Some(selection) = selections.next() {
10717 // Find all the selections that span a contiguous row range
10718 let (start_row, end_row) = consume_contiguous_rows(
10719 &mut contiguous_row_selections,
10720 selection,
10721 &display_map,
10722 &mut selections,
10723 );
10724
10725 // Move the text spanned by the row range to be after the last line of the row range
10726 if end_row.0 <= buffer.max_point().row {
10727 let range_to_move =
10728 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10729 let insertion_point = display_map
10730 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10731 .0;
10732
10733 // Don't move lines across excerpt boundaries
10734 if buffer
10735 .excerpt_containing(range_to_move.start..insertion_point)
10736 .is_some()
10737 {
10738 let mut text = String::from("\n");
10739 text.extend(buffer.text_for_range(range_to_move.clone()));
10740 text.pop(); // Drop trailing newline
10741 edits.push((
10742 buffer.anchor_after(range_to_move.start)
10743 ..buffer.anchor_before(range_to_move.end),
10744 String::new(),
10745 ));
10746 let insertion_anchor = buffer.anchor_after(insertion_point);
10747 edits.push((insertion_anchor..insertion_anchor, text));
10748
10749 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10750
10751 // Move selections down
10752 new_selections.extend(contiguous_row_selections.drain(..).map(
10753 |mut selection| {
10754 selection.start.row += row_delta;
10755 selection.end.row += row_delta;
10756 selection
10757 },
10758 ));
10759
10760 // Move folds down
10761 unfold_ranges.push(range_to_move.clone());
10762 for fold in display_map.folds_in_range(
10763 buffer.anchor_before(range_to_move.start)
10764 ..buffer.anchor_after(range_to_move.end),
10765 ) {
10766 let mut start = fold.range.start.to_point(&buffer);
10767 let mut end = fold.range.end.to_point(&buffer);
10768 start.row += row_delta;
10769 end.row += row_delta;
10770 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10771 }
10772 }
10773 }
10774
10775 // If we didn't move line(s), preserve the existing selections
10776 new_selections.append(&mut contiguous_row_selections);
10777 }
10778
10779 self.transact(window, cx, |this, window, cx| {
10780 this.unfold_ranges(&unfold_ranges, true, true, cx);
10781 this.buffer.update(cx, |buffer, cx| {
10782 for (range, text) in edits {
10783 buffer.edit([(range, text)], None, cx);
10784 }
10785 });
10786 this.fold_creases(refold_creases, true, window, cx);
10787 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10788 s.select(new_selections)
10789 });
10790 });
10791 }
10792
10793 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10794 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10795 let text_layout_details = &self.text_layout_details(window);
10796 self.transact(window, cx, |this, window, cx| {
10797 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10798 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10799 s.move_with(|display_map, selection| {
10800 if !selection.is_empty() {
10801 return;
10802 }
10803
10804 let mut head = selection.head();
10805 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10806 if head.column() == display_map.line_len(head.row()) {
10807 transpose_offset = display_map
10808 .buffer_snapshot
10809 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10810 }
10811
10812 if transpose_offset == 0 {
10813 return;
10814 }
10815
10816 *head.column_mut() += 1;
10817 head = display_map.clip_point(head, Bias::Right);
10818 let goal = SelectionGoal::HorizontalPosition(
10819 display_map
10820 .x_for_display_point(head, text_layout_details)
10821 .into(),
10822 );
10823 selection.collapse_to(head, goal);
10824
10825 let transpose_start = display_map
10826 .buffer_snapshot
10827 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10828 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10829 let transpose_end = display_map
10830 .buffer_snapshot
10831 .clip_offset(transpose_offset + 1, Bias::Right);
10832 if let Some(ch) =
10833 display_map.buffer_snapshot.chars_at(transpose_start).next()
10834 {
10835 edits.push((transpose_start..transpose_offset, String::new()));
10836 edits.push((transpose_end..transpose_end, ch.to_string()));
10837 }
10838 }
10839 });
10840 edits
10841 });
10842 this.buffer
10843 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10844 let selections = this.selections.all::<usize>(cx);
10845 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10846 s.select(selections);
10847 });
10848 });
10849 }
10850
10851 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10852 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10853 self.rewrap_impl(RewrapOptions::default(), cx)
10854 }
10855
10856 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10857 let buffer = self.buffer.read(cx).snapshot(cx);
10858 let selections = self.selections.all::<Point>(cx);
10859 let mut selections = selections.iter().peekable();
10860
10861 let mut edits = Vec::new();
10862 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10863
10864 while let Some(selection) = selections.next() {
10865 let mut start_row = selection.start.row;
10866 let mut end_row = selection.end.row;
10867
10868 // Skip selections that overlap with a range that has already been rewrapped.
10869 let selection_range = start_row..end_row;
10870 if rewrapped_row_ranges
10871 .iter()
10872 .any(|range| range.overlaps(&selection_range))
10873 {
10874 continue;
10875 }
10876
10877 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10878
10879 // Since not all lines in the selection may be at the same indent
10880 // level, choose the indent size that is the most common between all
10881 // of the lines.
10882 //
10883 // If there is a tie, we use the deepest indent.
10884 let (indent_size, indent_end) = {
10885 let mut indent_size_occurrences = HashMap::default();
10886 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10887
10888 for row in start_row..=end_row {
10889 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10890 rows_by_indent_size.entry(indent).or_default().push(row);
10891 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10892 }
10893
10894 let indent_size = indent_size_occurrences
10895 .into_iter()
10896 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10897 .map(|(indent, _)| indent)
10898 .unwrap_or_default();
10899 let row = rows_by_indent_size[&indent_size][0];
10900 let indent_end = Point::new(row, indent_size.len);
10901
10902 (indent_size, indent_end)
10903 };
10904
10905 let mut line_prefix = indent_size.chars().collect::<String>();
10906
10907 let mut inside_comment = false;
10908 if let Some(comment_prefix) =
10909 buffer
10910 .language_scope_at(selection.head())
10911 .and_then(|language| {
10912 language
10913 .line_comment_prefixes()
10914 .iter()
10915 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10916 .cloned()
10917 })
10918 {
10919 line_prefix.push_str(&comment_prefix);
10920 inside_comment = true;
10921 }
10922
10923 let language_settings = buffer.language_settings_at(selection.head(), cx);
10924 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10925 RewrapBehavior::InComments => inside_comment,
10926 RewrapBehavior::InSelections => !selection.is_empty(),
10927 RewrapBehavior::Anywhere => true,
10928 };
10929
10930 let should_rewrap = options.override_language_settings
10931 || allow_rewrap_based_on_language
10932 || self.hard_wrap.is_some();
10933 if !should_rewrap {
10934 continue;
10935 }
10936
10937 if selection.is_empty() {
10938 'expand_upwards: while start_row > 0 {
10939 let prev_row = start_row - 1;
10940 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10941 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10942 {
10943 start_row = prev_row;
10944 } else {
10945 break 'expand_upwards;
10946 }
10947 }
10948
10949 'expand_downwards: while end_row < buffer.max_point().row {
10950 let next_row = end_row + 1;
10951 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10952 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10953 {
10954 end_row = next_row;
10955 } else {
10956 break 'expand_downwards;
10957 }
10958 }
10959 }
10960
10961 let start = Point::new(start_row, 0);
10962 let start_offset = start.to_offset(&buffer);
10963 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10964 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10965 let Some(lines_without_prefixes) = selection_text
10966 .lines()
10967 .map(|line| {
10968 line.strip_prefix(&line_prefix)
10969 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10970 .with_context(|| {
10971 format!("line did not start with prefix {line_prefix:?}: {line:?}")
10972 })
10973 })
10974 .collect::<Result<Vec<_>, _>>()
10975 .log_err()
10976 else {
10977 continue;
10978 };
10979
10980 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10981 buffer
10982 .language_settings_at(Point::new(start_row, 0), cx)
10983 .preferred_line_length as usize
10984 });
10985 let wrapped_text = wrap_with_prefix(
10986 line_prefix,
10987 lines_without_prefixes.join("\n"),
10988 wrap_column,
10989 tab_size,
10990 options.preserve_existing_whitespace,
10991 );
10992
10993 // TODO: should always use char-based diff while still supporting cursor behavior that
10994 // matches vim.
10995 let mut diff_options = DiffOptions::default();
10996 if options.override_language_settings {
10997 diff_options.max_word_diff_len = 0;
10998 diff_options.max_word_diff_line_count = 0;
10999 } else {
11000 diff_options.max_word_diff_len = usize::MAX;
11001 diff_options.max_word_diff_line_count = usize::MAX;
11002 }
11003
11004 for (old_range, new_text) in
11005 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11006 {
11007 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11008 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11009 edits.push((edit_start..edit_end, new_text));
11010 }
11011
11012 rewrapped_row_ranges.push(start_row..=end_row);
11013 }
11014
11015 self.buffer
11016 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11017 }
11018
11019 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11020 let mut text = String::new();
11021 let buffer = self.buffer.read(cx).snapshot(cx);
11022 let mut selections = self.selections.all::<Point>(cx);
11023 let mut clipboard_selections = Vec::with_capacity(selections.len());
11024 {
11025 let max_point = buffer.max_point();
11026 let mut is_first = true;
11027 for selection in &mut selections {
11028 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11029 if is_entire_line {
11030 selection.start = Point::new(selection.start.row, 0);
11031 if !selection.is_empty() && selection.end.column == 0 {
11032 selection.end = cmp::min(max_point, selection.end);
11033 } else {
11034 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11035 }
11036 selection.goal = SelectionGoal::None;
11037 }
11038 if is_first {
11039 is_first = false;
11040 } else {
11041 text += "\n";
11042 }
11043 let mut len = 0;
11044 for chunk in buffer.text_for_range(selection.start..selection.end) {
11045 text.push_str(chunk);
11046 len += chunk.len();
11047 }
11048 clipboard_selections.push(ClipboardSelection {
11049 len,
11050 is_entire_line,
11051 first_line_indent: buffer
11052 .indent_size_for_line(MultiBufferRow(selection.start.row))
11053 .len,
11054 });
11055 }
11056 }
11057
11058 self.transact(window, cx, |this, window, cx| {
11059 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11060 s.select(selections);
11061 });
11062 this.insert("", window, cx);
11063 });
11064 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11065 }
11066
11067 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11068 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11069 let item = self.cut_common(window, cx);
11070 cx.write_to_clipboard(item);
11071 }
11072
11073 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11074 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11075 self.change_selections(None, window, cx, |s| {
11076 s.move_with(|snapshot, sel| {
11077 if sel.is_empty() {
11078 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11079 }
11080 });
11081 });
11082 let item = self.cut_common(window, cx);
11083 cx.set_global(KillRing(item))
11084 }
11085
11086 pub fn kill_ring_yank(
11087 &mut self,
11088 _: &KillRingYank,
11089 window: &mut Window,
11090 cx: &mut Context<Self>,
11091 ) {
11092 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11093 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11094 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11095 (kill_ring.text().to_string(), kill_ring.metadata_json())
11096 } else {
11097 return;
11098 }
11099 } else {
11100 return;
11101 };
11102 self.do_paste(&text, metadata, false, window, cx);
11103 }
11104
11105 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11106 self.do_copy(true, cx);
11107 }
11108
11109 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11110 self.do_copy(false, cx);
11111 }
11112
11113 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11114 let selections = self.selections.all::<Point>(cx);
11115 let buffer = self.buffer.read(cx).read(cx);
11116 let mut text = String::new();
11117
11118 let mut clipboard_selections = Vec::with_capacity(selections.len());
11119 {
11120 let max_point = buffer.max_point();
11121 let mut is_first = true;
11122 for selection in &selections {
11123 let mut start = selection.start;
11124 let mut end = selection.end;
11125 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11126 if is_entire_line {
11127 start = Point::new(start.row, 0);
11128 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11129 }
11130
11131 let mut trimmed_selections = Vec::new();
11132 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11133 let row = MultiBufferRow(start.row);
11134 let first_indent = buffer.indent_size_for_line(row);
11135 if first_indent.len == 0 || start.column > first_indent.len {
11136 trimmed_selections.push(start..end);
11137 } else {
11138 trimmed_selections.push(
11139 Point::new(row.0, first_indent.len)
11140 ..Point::new(row.0, buffer.line_len(row)),
11141 );
11142 for row in start.row + 1..=end.row {
11143 let mut line_len = buffer.line_len(MultiBufferRow(row));
11144 if row == end.row {
11145 line_len = end.column;
11146 }
11147 if line_len == 0 {
11148 trimmed_selections
11149 .push(Point::new(row, 0)..Point::new(row, line_len));
11150 continue;
11151 }
11152 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11153 if row_indent_size.len >= first_indent.len {
11154 trimmed_selections.push(
11155 Point::new(row, first_indent.len)..Point::new(row, line_len),
11156 );
11157 } else {
11158 trimmed_selections.clear();
11159 trimmed_selections.push(start..end);
11160 break;
11161 }
11162 }
11163 }
11164 } else {
11165 trimmed_selections.push(start..end);
11166 }
11167
11168 for trimmed_range in trimmed_selections {
11169 if is_first {
11170 is_first = false;
11171 } else {
11172 text += "\n";
11173 }
11174 let mut len = 0;
11175 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11176 text.push_str(chunk);
11177 len += chunk.len();
11178 }
11179 clipboard_selections.push(ClipboardSelection {
11180 len,
11181 is_entire_line,
11182 first_line_indent: buffer
11183 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11184 .len,
11185 });
11186 }
11187 }
11188 }
11189
11190 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11191 text,
11192 clipboard_selections,
11193 ));
11194 }
11195
11196 pub fn do_paste(
11197 &mut self,
11198 text: &String,
11199 clipboard_selections: Option<Vec<ClipboardSelection>>,
11200 handle_entire_lines: bool,
11201 window: &mut Window,
11202 cx: &mut Context<Self>,
11203 ) {
11204 if self.read_only(cx) {
11205 return;
11206 }
11207
11208 let clipboard_text = Cow::Borrowed(text);
11209
11210 self.transact(window, cx, |this, window, cx| {
11211 if let Some(mut clipboard_selections) = clipboard_selections {
11212 let old_selections = this.selections.all::<usize>(cx);
11213 let all_selections_were_entire_line =
11214 clipboard_selections.iter().all(|s| s.is_entire_line);
11215 let first_selection_indent_column =
11216 clipboard_selections.first().map(|s| s.first_line_indent);
11217 if clipboard_selections.len() != old_selections.len() {
11218 clipboard_selections.drain(..);
11219 }
11220 let cursor_offset = this.selections.last::<usize>(cx).head();
11221 let mut auto_indent_on_paste = true;
11222
11223 this.buffer.update(cx, |buffer, cx| {
11224 let snapshot = buffer.read(cx);
11225 auto_indent_on_paste = snapshot
11226 .language_settings_at(cursor_offset, cx)
11227 .auto_indent_on_paste;
11228
11229 let mut start_offset = 0;
11230 let mut edits = Vec::new();
11231 let mut original_indent_columns = Vec::new();
11232 for (ix, selection) in old_selections.iter().enumerate() {
11233 let to_insert;
11234 let entire_line;
11235 let original_indent_column;
11236 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11237 let end_offset = start_offset + clipboard_selection.len;
11238 to_insert = &clipboard_text[start_offset..end_offset];
11239 entire_line = clipboard_selection.is_entire_line;
11240 start_offset = end_offset + 1;
11241 original_indent_column = Some(clipboard_selection.first_line_indent);
11242 } else {
11243 to_insert = clipboard_text.as_str();
11244 entire_line = all_selections_were_entire_line;
11245 original_indent_column = first_selection_indent_column
11246 }
11247
11248 // If the corresponding selection was empty when this slice of the
11249 // clipboard text was written, then the entire line containing the
11250 // selection was copied. If this selection is also currently empty,
11251 // then paste the line before the current line of the buffer.
11252 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11253 let column = selection.start.to_point(&snapshot).column as usize;
11254 let line_start = selection.start - column;
11255 line_start..line_start
11256 } else {
11257 selection.range()
11258 };
11259
11260 edits.push((range, to_insert));
11261 original_indent_columns.push(original_indent_column);
11262 }
11263 drop(snapshot);
11264
11265 buffer.edit(
11266 edits,
11267 if auto_indent_on_paste {
11268 Some(AutoindentMode::Block {
11269 original_indent_columns,
11270 })
11271 } else {
11272 None
11273 },
11274 cx,
11275 );
11276 });
11277
11278 let selections = this.selections.all::<usize>(cx);
11279 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11280 s.select(selections)
11281 });
11282 } else {
11283 this.insert(&clipboard_text, window, cx);
11284 }
11285 });
11286 }
11287
11288 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11289 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11290 if let Some(item) = cx.read_from_clipboard() {
11291 let entries = item.entries();
11292
11293 match entries.first() {
11294 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11295 // of all the pasted entries.
11296 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11297 .do_paste(
11298 clipboard_string.text(),
11299 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11300 true,
11301 window,
11302 cx,
11303 ),
11304 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11305 }
11306 }
11307 }
11308
11309 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11310 if self.read_only(cx) {
11311 return;
11312 }
11313
11314 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11315
11316 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11317 if let Some((selections, _)) =
11318 self.selection_history.transaction(transaction_id).cloned()
11319 {
11320 self.change_selections(None, window, cx, |s| {
11321 s.select_anchors(selections.to_vec());
11322 });
11323 } else {
11324 log::error!(
11325 "No entry in selection_history found for undo. \
11326 This may correspond to a bug where undo does not update the selection. \
11327 If this is occurring, please add details to \
11328 https://github.com/zed-industries/zed/issues/22692"
11329 );
11330 }
11331 self.request_autoscroll(Autoscroll::fit(), cx);
11332 self.unmark_text(window, cx);
11333 self.refresh_inline_completion(true, false, window, cx);
11334 cx.emit(EditorEvent::Edited { transaction_id });
11335 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11336 }
11337 }
11338
11339 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11340 if self.read_only(cx) {
11341 return;
11342 }
11343
11344 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11345
11346 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11347 if let Some((_, Some(selections))) =
11348 self.selection_history.transaction(transaction_id).cloned()
11349 {
11350 self.change_selections(None, window, cx, |s| {
11351 s.select_anchors(selections.to_vec());
11352 });
11353 } else {
11354 log::error!(
11355 "No entry in selection_history found for redo. \
11356 This may correspond to a bug where undo does not update the selection. \
11357 If this is occurring, please add details to \
11358 https://github.com/zed-industries/zed/issues/22692"
11359 );
11360 }
11361 self.request_autoscroll(Autoscroll::fit(), cx);
11362 self.unmark_text(window, cx);
11363 self.refresh_inline_completion(true, false, window, cx);
11364 cx.emit(EditorEvent::Edited { transaction_id });
11365 }
11366 }
11367
11368 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11369 self.buffer
11370 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11371 }
11372
11373 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11374 self.buffer
11375 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11376 }
11377
11378 pub fn move_left(&mut self, _: &MoveLeft, 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_with(|map, selection| {
11382 let cursor = if selection.is_empty() {
11383 movement::left(map, selection.start)
11384 } else {
11385 selection.start
11386 };
11387 selection.collapse_to(cursor, SelectionGoal::None);
11388 });
11389 })
11390 }
11391
11392 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11393 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11394 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11395 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11396 })
11397 }
11398
11399 pub fn move_right(&mut self, _: &MoveRight, 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_with(|map, selection| {
11403 let cursor = if selection.is_empty() {
11404 movement::right(map, selection.end)
11405 } else {
11406 selection.end
11407 };
11408 selection.collapse_to(cursor, SelectionGoal::None)
11409 });
11410 })
11411 }
11412
11413 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11414 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11415 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11416 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11417 })
11418 }
11419
11420 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11421 if self.take_rename(true, window, cx).is_some() {
11422 return;
11423 }
11424
11425 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11426 cx.propagate();
11427 return;
11428 }
11429
11430 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11431
11432 let text_layout_details = &self.text_layout_details(window);
11433 let selection_count = self.selections.count();
11434 let first_selection = self.selections.first_anchor();
11435
11436 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11437 s.move_with(|map, selection| {
11438 if !selection.is_empty() {
11439 selection.goal = SelectionGoal::None;
11440 }
11441 let (cursor, goal) = movement::up(
11442 map,
11443 selection.start,
11444 selection.goal,
11445 false,
11446 text_layout_details,
11447 );
11448 selection.collapse_to(cursor, goal);
11449 });
11450 });
11451
11452 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11453 {
11454 cx.propagate();
11455 }
11456 }
11457
11458 pub fn move_up_by_lines(
11459 &mut self,
11460 action: &MoveUpByLines,
11461 window: &mut Window,
11462 cx: &mut Context<Self>,
11463 ) {
11464 if self.take_rename(true, window, cx).is_some() {
11465 return;
11466 }
11467
11468 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11469 cx.propagate();
11470 return;
11471 }
11472
11473 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11474
11475 let text_layout_details = &self.text_layout_details(window);
11476
11477 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11478 s.move_with(|map, selection| {
11479 if !selection.is_empty() {
11480 selection.goal = SelectionGoal::None;
11481 }
11482 let (cursor, goal) = movement::up_by_rows(
11483 map,
11484 selection.start,
11485 action.lines,
11486 selection.goal,
11487 false,
11488 text_layout_details,
11489 );
11490 selection.collapse_to(cursor, goal);
11491 });
11492 })
11493 }
11494
11495 pub fn move_down_by_lines(
11496 &mut self,
11497 action: &MoveDownByLines,
11498 window: &mut Window,
11499 cx: &mut Context<Self>,
11500 ) {
11501 if self.take_rename(true, window, cx).is_some() {
11502 return;
11503 }
11504
11505 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11506 cx.propagate();
11507 return;
11508 }
11509
11510 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11511
11512 let text_layout_details = &self.text_layout_details(window);
11513
11514 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11515 s.move_with(|map, selection| {
11516 if !selection.is_empty() {
11517 selection.goal = SelectionGoal::None;
11518 }
11519 let (cursor, goal) = movement::down_by_rows(
11520 map,
11521 selection.start,
11522 action.lines,
11523 selection.goal,
11524 false,
11525 text_layout_details,
11526 );
11527 selection.collapse_to(cursor, goal);
11528 });
11529 })
11530 }
11531
11532 pub fn select_down_by_lines(
11533 &mut self,
11534 action: &SelectDownByLines,
11535 window: &mut Window,
11536 cx: &mut Context<Self>,
11537 ) {
11538 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11539 let text_layout_details = &self.text_layout_details(window);
11540 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11541 s.move_heads_with(|map, head, goal| {
11542 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11543 })
11544 })
11545 }
11546
11547 pub fn select_up_by_lines(
11548 &mut self,
11549 action: &SelectUpByLines,
11550 window: &mut Window,
11551 cx: &mut Context<Self>,
11552 ) {
11553 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11554 let text_layout_details = &self.text_layout_details(window);
11555 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11556 s.move_heads_with(|map, head, goal| {
11557 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11558 })
11559 })
11560 }
11561
11562 pub fn select_page_up(
11563 &mut self,
11564 _: &SelectPageUp,
11565 window: &mut Window,
11566 cx: &mut Context<Self>,
11567 ) {
11568 let Some(row_count) = self.visible_row_count() else {
11569 return;
11570 };
11571
11572 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11573
11574 let text_layout_details = &self.text_layout_details(window);
11575
11576 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11577 s.move_heads_with(|map, head, goal| {
11578 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11579 })
11580 })
11581 }
11582
11583 pub fn move_page_up(
11584 &mut self,
11585 action: &MovePageUp,
11586 window: &mut Window,
11587 cx: &mut Context<Self>,
11588 ) {
11589 if self.take_rename(true, window, cx).is_some() {
11590 return;
11591 }
11592
11593 if self
11594 .context_menu
11595 .borrow_mut()
11596 .as_mut()
11597 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11598 .unwrap_or(false)
11599 {
11600 return;
11601 }
11602
11603 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11604 cx.propagate();
11605 return;
11606 }
11607
11608 let Some(row_count) = self.visible_row_count() else {
11609 return;
11610 };
11611
11612 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11613
11614 let autoscroll = if action.center_cursor {
11615 Autoscroll::center()
11616 } else {
11617 Autoscroll::fit()
11618 };
11619
11620 let text_layout_details = &self.text_layout_details(window);
11621
11622 self.change_selections(Some(autoscroll), window, cx, |s| {
11623 s.move_with(|map, selection| {
11624 if !selection.is_empty() {
11625 selection.goal = SelectionGoal::None;
11626 }
11627 let (cursor, goal) = movement::up_by_rows(
11628 map,
11629 selection.end,
11630 row_count,
11631 selection.goal,
11632 false,
11633 text_layout_details,
11634 );
11635 selection.collapse_to(cursor, goal);
11636 });
11637 });
11638 }
11639
11640 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11641 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11642 let text_layout_details = &self.text_layout_details(window);
11643 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11644 s.move_heads_with(|map, head, goal| {
11645 movement::up(map, head, goal, false, text_layout_details)
11646 })
11647 })
11648 }
11649
11650 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11651 self.take_rename(true, window, cx);
11652
11653 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11654 cx.propagate();
11655 return;
11656 }
11657
11658 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11659
11660 let text_layout_details = &self.text_layout_details(window);
11661 let selection_count = self.selections.count();
11662 let first_selection = self.selections.first_anchor();
11663
11664 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11665 s.move_with(|map, selection| {
11666 if !selection.is_empty() {
11667 selection.goal = SelectionGoal::None;
11668 }
11669 let (cursor, goal) = movement::down(
11670 map,
11671 selection.end,
11672 selection.goal,
11673 false,
11674 text_layout_details,
11675 );
11676 selection.collapse_to(cursor, goal);
11677 });
11678 });
11679
11680 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11681 {
11682 cx.propagate();
11683 }
11684 }
11685
11686 pub fn select_page_down(
11687 &mut self,
11688 _: &SelectPageDown,
11689 window: &mut Window,
11690 cx: &mut Context<Self>,
11691 ) {
11692 let Some(row_count) = self.visible_row_count() else {
11693 return;
11694 };
11695
11696 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11697
11698 let text_layout_details = &self.text_layout_details(window);
11699
11700 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11701 s.move_heads_with(|map, head, goal| {
11702 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11703 })
11704 })
11705 }
11706
11707 pub fn move_page_down(
11708 &mut self,
11709 action: &MovePageDown,
11710 window: &mut Window,
11711 cx: &mut Context<Self>,
11712 ) {
11713 if self.take_rename(true, window, cx).is_some() {
11714 return;
11715 }
11716
11717 if self
11718 .context_menu
11719 .borrow_mut()
11720 .as_mut()
11721 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
11722 .unwrap_or(false)
11723 {
11724 return;
11725 }
11726
11727 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11728 cx.propagate();
11729 return;
11730 }
11731
11732 let Some(row_count) = self.visible_row_count() else {
11733 return;
11734 };
11735
11736 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11737
11738 let autoscroll = if action.center_cursor {
11739 Autoscroll::center()
11740 } else {
11741 Autoscroll::fit()
11742 };
11743
11744 let text_layout_details = &self.text_layout_details(window);
11745 self.change_selections(Some(autoscroll), window, cx, |s| {
11746 s.move_with(|map, selection| {
11747 if !selection.is_empty() {
11748 selection.goal = SelectionGoal::None;
11749 }
11750 let (cursor, goal) = movement::down_by_rows(
11751 map,
11752 selection.end,
11753 row_count,
11754 selection.goal,
11755 false,
11756 text_layout_details,
11757 );
11758 selection.collapse_to(cursor, goal);
11759 });
11760 });
11761 }
11762
11763 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11764 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11765 let text_layout_details = &self.text_layout_details(window);
11766 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11767 s.move_heads_with(|map, head, goal| {
11768 movement::down(map, head, goal, false, text_layout_details)
11769 })
11770 });
11771 }
11772
11773 pub fn context_menu_first(
11774 &mut self,
11775 _: &ContextMenuFirst,
11776 window: &mut Window,
11777 cx: &mut Context<Self>,
11778 ) {
11779 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11780 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
11781 }
11782 }
11783
11784 pub fn context_menu_prev(
11785 &mut self,
11786 _: &ContextMenuPrevious,
11787 window: &mut Window,
11788 cx: &mut Context<Self>,
11789 ) {
11790 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11791 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
11792 }
11793 }
11794
11795 pub fn context_menu_next(
11796 &mut self,
11797 _: &ContextMenuNext,
11798 window: &mut Window,
11799 cx: &mut Context<Self>,
11800 ) {
11801 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11802 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
11803 }
11804 }
11805
11806 pub fn context_menu_last(
11807 &mut self,
11808 _: &ContextMenuLast,
11809 window: &mut Window,
11810 cx: &mut Context<Self>,
11811 ) {
11812 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11813 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
11814 }
11815 }
11816
11817 pub fn move_to_previous_word_start(
11818 &mut self,
11819 _: &MoveToPreviousWordStart,
11820 window: &mut Window,
11821 cx: &mut Context<Self>,
11822 ) {
11823 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11824 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11825 s.move_cursors_with(|map, head, _| {
11826 (
11827 movement::previous_word_start(map, head),
11828 SelectionGoal::None,
11829 )
11830 });
11831 })
11832 }
11833
11834 pub fn move_to_previous_subword_start(
11835 &mut self,
11836 _: &MoveToPreviousSubwordStart,
11837 window: &mut Window,
11838 cx: &mut Context<Self>,
11839 ) {
11840 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11841 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11842 s.move_cursors_with(|map, head, _| {
11843 (
11844 movement::previous_subword_start(map, head),
11845 SelectionGoal::None,
11846 )
11847 });
11848 })
11849 }
11850
11851 pub fn select_to_previous_word_start(
11852 &mut self,
11853 _: &SelectToPreviousWordStart,
11854 window: &mut Window,
11855 cx: &mut Context<Self>,
11856 ) {
11857 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11858 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11859 s.move_heads_with(|map, head, _| {
11860 (
11861 movement::previous_word_start(map, head),
11862 SelectionGoal::None,
11863 )
11864 });
11865 })
11866 }
11867
11868 pub fn select_to_previous_subword_start(
11869 &mut self,
11870 _: &SelectToPreviousSubwordStart,
11871 window: &mut Window,
11872 cx: &mut Context<Self>,
11873 ) {
11874 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11875 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11876 s.move_heads_with(|map, head, _| {
11877 (
11878 movement::previous_subword_start(map, head),
11879 SelectionGoal::None,
11880 )
11881 });
11882 })
11883 }
11884
11885 pub fn delete_to_previous_word_start(
11886 &mut self,
11887 action: &DeleteToPreviousWordStart,
11888 window: &mut Window,
11889 cx: &mut Context<Self>,
11890 ) {
11891 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11892 self.transact(window, cx, |this, window, cx| {
11893 this.select_autoclose_pair(window, cx);
11894 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11895 s.move_with(|map, selection| {
11896 if selection.is_empty() {
11897 let cursor = if action.ignore_newlines {
11898 movement::previous_word_start(map, selection.head())
11899 } else {
11900 movement::previous_word_start_or_newline(map, selection.head())
11901 };
11902 selection.set_head(cursor, SelectionGoal::None);
11903 }
11904 });
11905 });
11906 this.insert("", window, cx);
11907 });
11908 }
11909
11910 pub fn delete_to_previous_subword_start(
11911 &mut self,
11912 _: &DeleteToPreviousSubwordStart,
11913 window: &mut Window,
11914 cx: &mut Context<Self>,
11915 ) {
11916 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11917 self.transact(window, cx, |this, window, cx| {
11918 this.select_autoclose_pair(window, cx);
11919 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11920 s.move_with(|map, selection| {
11921 if selection.is_empty() {
11922 let cursor = movement::previous_subword_start(map, selection.head());
11923 selection.set_head(cursor, SelectionGoal::None);
11924 }
11925 });
11926 });
11927 this.insert("", window, cx);
11928 });
11929 }
11930
11931 pub fn move_to_next_word_end(
11932 &mut self,
11933 _: &MoveToNextWordEnd,
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_word_end(map, head), SelectionGoal::None)
11941 });
11942 })
11943 }
11944
11945 pub fn move_to_next_subword_end(
11946 &mut self,
11947 _: &MoveToNextSubwordEnd,
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_cursors_with(|map, head, _| {
11954 (movement::next_subword_end(map, head), SelectionGoal::None)
11955 });
11956 })
11957 }
11958
11959 pub fn select_to_next_word_end(
11960 &mut self,
11961 _: &SelectToNextWordEnd,
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_word_end(map, head), SelectionGoal::None)
11969 });
11970 })
11971 }
11972
11973 pub fn select_to_next_subword_end(
11974 &mut self,
11975 _: &SelectToNextSubwordEnd,
11976 window: &mut Window,
11977 cx: &mut Context<Self>,
11978 ) {
11979 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11980 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11981 s.move_heads_with(|map, head, _| {
11982 (movement::next_subword_end(map, head), SelectionGoal::None)
11983 });
11984 })
11985 }
11986
11987 pub fn delete_to_next_word_end(
11988 &mut self,
11989 action: &DeleteToNextWordEnd,
11990 window: &mut Window,
11991 cx: &mut Context<Self>,
11992 ) {
11993 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11994 self.transact(window, cx, |this, window, cx| {
11995 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11996 s.move_with(|map, selection| {
11997 if selection.is_empty() {
11998 let cursor = if action.ignore_newlines {
11999 movement::next_word_end(map, selection.head())
12000 } else {
12001 movement::next_word_end_or_newline(map, selection.head())
12002 };
12003 selection.set_head(cursor, SelectionGoal::None);
12004 }
12005 });
12006 });
12007 this.insert("", window, cx);
12008 });
12009 }
12010
12011 pub fn delete_to_next_subword_end(
12012 &mut self,
12013 _: &DeleteToNextSubwordEnd,
12014 window: &mut Window,
12015 cx: &mut Context<Self>,
12016 ) {
12017 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12018 self.transact(window, cx, |this, window, cx| {
12019 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12020 s.move_with(|map, selection| {
12021 if selection.is_empty() {
12022 let cursor = movement::next_subword_end(map, selection.head());
12023 selection.set_head(cursor, SelectionGoal::None);
12024 }
12025 });
12026 });
12027 this.insert("", window, cx);
12028 });
12029 }
12030
12031 pub fn move_to_beginning_of_line(
12032 &mut self,
12033 action: &MoveToBeginningOfLine,
12034 window: &mut Window,
12035 cx: &mut Context<Self>,
12036 ) {
12037 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12038 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12039 s.move_cursors_with(|map, head, _| {
12040 (
12041 movement::indented_line_beginning(
12042 map,
12043 head,
12044 action.stop_at_soft_wraps,
12045 action.stop_at_indent,
12046 ),
12047 SelectionGoal::None,
12048 )
12049 });
12050 })
12051 }
12052
12053 pub fn select_to_beginning_of_line(
12054 &mut self,
12055 action: &SelectToBeginningOfLine,
12056 window: &mut Window,
12057 cx: &mut Context<Self>,
12058 ) {
12059 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12060 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12061 s.move_heads_with(|map, head, _| {
12062 (
12063 movement::indented_line_beginning(
12064 map,
12065 head,
12066 action.stop_at_soft_wraps,
12067 action.stop_at_indent,
12068 ),
12069 SelectionGoal::None,
12070 )
12071 });
12072 });
12073 }
12074
12075 pub fn delete_to_beginning_of_line(
12076 &mut self,
12077 action: &DeleteToBeginningOfLine,
12078 window: &mut Window,
12079 cx: &mut Context<Self>,
12080 ) {
12081 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12082 self.transact(window, cx, |this, window, cx| {
12083 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12084 s.move_with(|_, selection| {
12085 selection.reversed = true;
12086 });
12087 });
12088
12089 this.select_to_beginning_of_line(
12090 &SelectToBeginningOfLine {
12091 stop_at_soft_wraps: false,
12092 stop_at_indent: action.stop_at_indent,
12093 },
12094 window,
12095 cx,
12096 );
12097 this.backspace(&Backspace, window, cx);
12098 });
12099 }
12100
12101 pub fn move_to_end_of_line(
12102 &mut self,
12103 action: &MoveToEndOfLine,
12104 window: &mut Window,
12105 cx: &mut Context<Self>,
12106 ) {
12107 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12108 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12109 s.move_cursors_with(|map, head, _| {
12110 (
12111 movement::line_end(map, head, action.stop_at_soft_wraps),
12112 SelectionGoal::None,
12113 )
12114 });
12115 })
12116 }
12117
12118 pub fn select_to_end_of_line(
12119 &mut self,
12120 action: &SelectToEndOfLine,
12121 window: &mut Window,
12122 cx: &mut Context<Self>,
12123 ) {
12124 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12125 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12126 s.move_heads_with(|map, head, _| {
12127 (
12128 movement::line_end(map, head, action.stop_at_soft_wraps),
12129 SelectionGoal::None,
12130 )
12131 });
12132 })
12133 }
12134
12135 pub fn delete_to_end_of_line(
12136 &mut self,
12137 _: &DeleteToEndOfLine,
12138 window: &mut Window,
12139 cx: &mut Context<Self>,
12140 ) {
12141 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12142 self.transact(window, cx, |this, window, cx| {
12143 this.select_to_end_of_line(
12144 &SelectToEndOfLine {
12145 stop_at_soft_wraps: false,
12146 },
12147 window,
12148 cx,
12149 );
12150 this.delete(&Delete, window, cx);
12151 });
12152 }
12153
12154 pub fn cut_to_end_of_line(
12155 &mut self,
12156 _: &CutToEndOfLine,
12157 window: &mut Window,
12158 cx: &mut Context<Self>,
12159 ) {
12160 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12161 self.transact(window, cx, |this, window, cx| {
12162 this.select_to_end_of_line(
12163 &SelectToEndOfLine {
12164 stop_at_soft_wraps: false,
12165 },
12166 window,
12167 cx,
12168 );
12169 this.cut(&Cut, window, cx);
12170 });
12171 }
12172
12173 pub fn move_to_start_of_paragraph(
12174 &mut self,
12175 _: &MoveToStartOfParagraph,
12176 window: &mut Window,
12177 cx: &mut Context<Self>,
12178 ) {
12179 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12180 cx.propagate();
12181 return;
12182 }
12183 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12184 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12185 s.move_with(|map, selection| {
12186 selection.collapse_to(
12187 movement::start_of_paragraph(map, selection.head(), 1),
12188 SelectionGoal::None,
12189 )
12190 });
12191 })
12192 }
12193
12194 pub fn move_to_end_of_paragraph(
12195 &mut self,
12196 _: &MoveToEndOfParagraph,
12197 window: &mut Window,
12198 cx: &mut Context<Self>,
12199 ) {
12200 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12201 cx.propagate();
12202 return;
12203 }
12204 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12205 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12206 s.move_with(|map, selection| {
12207 selection.collapse_to(
12208 movement::end_of_paragraph(map, selection.head(), 1),
12209 SelectionGoal::None,
12210 )
12211 });
12212 })
12213 }
12214
12215 pub fn select_to_start_of_paragraph(
12216 &mut self,
12217 _: &SelectToStartOfParagraph,
12218 window: &mut Window,
12219 cx: &mut Context<Self>,
12220 ) {
12221 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12222 cx.propagate();
12223 return;
12224 }
12225 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12226 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12227 s.move_heads_with(|map, head, _| {
12228 (
12229 movement::start_of_paragraph(map, head, 1),
12230 SelectionGoal::None,
12231 )
12232 });
12233 })
12234 }
12235
12236 pub fn select_to_end_of_paragraph(
12237 &mut self,
12238 _: &SelectToEndOfParagraph,
12239 window: &mut Window,
12240 cx: &mut Context<Self>,
12241 ) {
12242 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12243 cx.propagate();
12244 return;
12245 }
12246 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12247 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12248 s.move_heads_with(|map, head, _| {
12249 (
12250 movement::end_of_paragraph(map, head, 1),
12251 SelectionGoal::None,
12252 )
12253 });
12254 })
12255 }
12256
12257 pub fn move_to_start_of_excerpt(
12258 &mut self,
12259 _: &MoveToStartOfExcerpt,
12260 window: &mut Window,
12261 cx: &mut Context<Self>,
12262 ) {
12263 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12264 cx.propagate();
12265 return;
12266 }
12267 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12268 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12269 s.move_with(|map, selection| {
12270 selection.collapse_to(
12271 movement::start_of_excerpt(
12272 map,
12273 selection.head(),
12274 workspace::searchable::Direction::Prev,
12275 ),
12276 SelectionGoal::None,
12277 )
12278 });
12279 })
12280 }
12281
12282 pub fn move_to_start_of_next_excerpt(
12283 &mut self,
12284 _: &MoveToStartOfNextExcerpt,
12285 window: &mut Window,
12286 cx: &mut Context<Self>,
12287 ) {
12288 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12289 cx.propagate();
12290 return;
12291 }
12292
12293 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12294 s.move_with(|map, selection| {
12295 selection.collapse_to(
12296 movement::start_of_excerpt(
12297 map,
12298 selection.head(),
12299 workspace::searchable::Direction::Next,
12300 ),
12301 SelectionGoal::None,
12302 )
12303 });
12304 })
12305 }
12306
12307 pub fn move_to_end_of_excerpt(
12308 &mut self,
12309 _: &MoveToEndOfExcerpt,
12310 window: &mut Window,
12311 cx: &mut Context<Self>,
12312 ) {
12313 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12314 cx.propagate();
12315 return;
12316 }
12317 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12318 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12319 s.move_with(|map, selection| {
12320 selection.collapse_to(
12321 movement::end_of_excerpt(
12322 map,
12323 selection.head(),
12324 workspace::searchable::Direction::Next,
12325 ),
12326 SelectionGoal::None,
12327 )
12328 });
12329 })
12330 }
12331
12332 pub fn move_to_end_of_previous_excerpt(
12333 &mut self,
12334 _: &MoveToEndOfPreviousExcerpt,
12335 window: &mut Window,
12336 cx: &mut Context<Self>,
12337 ) {
12338 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12339 cx.propagate();
12340 return;
12341 }
12342 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12343 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12344 s.move_with(|map, selection| {
12345 selection.collapse_to(
12346 movement::end_of_excerpt(
12347 map,
12348 selection.head(),
12349 workspace::searchable::Direction::Prev,
12350 ),
12351 SelectionGoal::None,
12352 )
12353 });
12354 })
12355 }
12356
12357 pub fn select_to_start_of_excerpt(
12358 &mut self,
12359 _: &SelectToStartOfExcerpt,
12360 window: &mut Window,
12361 cx: &mut Context<Self>,
12362 ) {
12363 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12364 cx.propagate();
12365 return;
12366 }
12367 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12368 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12369 s.move_heads_with(|map, head, _| {
12370 (
12371 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12372 SelectionGoal::None,
12373 )
12374 });
12375 })
12376 }
12377
12378 pub fn select_to_start_of_next_excerpt(
12379 &mut self,
12380 _: &SelectToStartOfNextExcerpt,
12381 window: &mut Window,
12382 cx: &mut Context<Self>,
12383 ) {
12384 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12385 cx.propagate();
12386 return;
12387 }
12388 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12389 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12390 s.move_heads_with(|map, head, _| {
12391 (
12392 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12393 SelectionGoal::None,
12394 )
12395 });
12396 })
12397 }
12398
12399 pub fn select_to_end_of_excerpt(
12400 &mut self,
12401 _: &SelectToEndOfExcerpt,
12402 window: &mut Window,
12403 cx: &mut Context<Self>,
12404 ) {
12405 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12406 cx.propagate();
12407 return;
12408 }
12409 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12410 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12411 s.move_heads_with(|map, head, _| {
12412 (
12413 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12414 SelectionGoal::None,
12415 )
12416 });
12417 })
12418 }
12419
12420 pub fn select_to_end_of_previous_excerpt(
12421 &mut self,
12422 _: &SelectToEndOfPreviousExcerpt,
12423 window: &mut Window,
12424 cx: &mut Context<Self>,
12425 ) {
12426 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12427 cx.propagate();
12428 return;
12429 }
12430 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12431 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12432 s.move_heads_with(|map, head, _| {
12433 (
12434 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12435 SelectionGoal::None,
12436 )
12437 });
12438 })
12439 }
12440
12441 pub fn move_to_beginning(
12442 &mut self,
12443 _: &MoveToBeginning,
12444 window: &mut Window,
12445 cx: &mut Context<Self>,
12446 ) {
12447 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12448 cx.propagate();
12449 return;
12450 }
12451 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12452 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12453 s.select_ranges(vec![0..0]);
12454 });
12455 }
12456
12457 pub fn select_to_beginning(
12458 &mut self,
12459 _: &SelectToBeginning,
12460 window: &mut Window,
12461 cx: &mut Context<Self>,
12462 ) {
12463 let mut selection = self.selections.last::<Point>(cx);
12464 selection.set_head(Point::zero(), SelectionGoal::None);
12465 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12466 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12467 s.select(vec![selection]);
12468 });
12469 }
12470
12471 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12472 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12473 cx.propagate();
12474 return;
12475 }
12476 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12477 let cursor = self.buffer.read(cx).read(cx).len();
12478 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12479 s.select_ranges(vec![cursor..cursor])
12480 });
12481 }
12482
12483 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12484 self.nav_history = nav_history;
12485 }
12486
12487 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12488 self.nav_history.as_ref()
12489 }
12490
12491 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12492 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12493 }
12494
12495 fn push_to_nav_history(
12496 &mut self,
12497 cursor_anchor: Anchor,
12498 new_position: Option<Point>,
12499 is_deactivate: bool,
12500 cx: &mut Context<Self>,
12501 ) {
12502 if let Some(nav_history) = self.nav_history.as_mut() {
12503 let buffer = self.buffer.read(cx).read(cx);
12504 let cursor_position = cursor_anchor.to_point(&buffer);
12505 let scroll_state = self.scroll_manager.anchor();
12506 let scroll_top_row = scroll_state.top_row(&buffer);
12507 drop(buffer);
12508
12509 if let Some(new_position) = new_position {
12510 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12511 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12512 return;
12513 }
12514 }
12515
12516 nav_history.push(
12517 Some(NavigationData {
12518 cursor_anchor,
12519 cursor_position,
12520 scroll_anchor: scroll_state,
12521 scroll_top_row,
12522 }),
12523 cx,
12524 );
12525 cx.emit(EditorEvent::PushedToNavHistory {
12526 anchor: cursor_anchor,
12527 is_deactivate,
12528 })
12529 }
12530 }
12531
12532 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12533 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12534 let buffer = self.buffer.read(cx).snapshot(cx);
12535 let mut selection = self.selections.first::<usize>(cx);
12536 selection.set_head(buffer.len(), SelectionGoal::None);
12537 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12538 s.select(vec![selection]);
12539 });
12540 }
12541
12542 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12543 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12544 let end = self.buffer.read(cx).read(cx).len();
12545 self.change_selections(None, window, cx, |s| {
12546 s.select_ranges(vec![0..end]);
12547 });
12548 }
12549
12550 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12551 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12552 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12553 let mut selections = self.selections.all::<Point>(cx);
12554 let max_point = display_map.buffer_snapshot.max_point();
12555 for selection in &mut selections {
12556 let rows = selection.spanned_rows(true, &display_map);
12557 selection.start = Point::new(rows.start.0, 0);
12558 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12559 selection.reversed = false;
12560 }
12561 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12562 s.select(selections);
12563 });
12564 }
12565
12566 pub fn split_selection_into_lines(
12567 &mut self,
12568 _: &SplitSelectionIntoLines,
12569 window: &mut Window,
12570 cx: &mut Context<Self>,
12571 ) {
12572 let selections = self
12573 .selections
12574 .all::<Point>(cx)
12575 .into_iter()
12576 .map(|selection| selection.start..selection.end)
12577 .collect::<Vec<_>>();
12578 self.unfold_ranges(&selections, true, true, cx);
12579
12580 let mut new_selection_ranges = Vec::new();
12581 {
12582 let buffer = self.buffer.read(cx).read(cx);
12583 for selection in selections {
12584 for row in selection.start.row..selection.end.row {
12585 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12586 new_selection_ranges.push(cursor..cursor);
12587 }
12588
12589 let is_multiline_selection = selection.start.row != selection.end.row;
12590 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12591 // so this action feels more ergonomic when paired with other selection operations
12592 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12593 if !should_skip_last {
12594 new_selection_ranges.push(selection.end..selection.end);
12595 }
12596 }
12597 }
12598 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12599 s.select_ranges(new_selection_ranges);
12600 });
12601 }
12602
12603 pub fn add_selection_above(
12604 &mut self,
12605 _: &AddSelectionAbove,
12606 window: &mut Window,
12607 cx: &mut Context<Self>,
12608 ) {
12609 self.add_selection(true, window, cx);
12610 }
12611
12612 pub fn add_selection_below(
12613 &mut self,
12614 _: &AddSelectionBelow,
12615 window: &mut Window,
12616 cx: &mut Context<Self>,
12617 ) {
12618 self.add_selection(false, window, cx);
12619 }
12620
12621 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12622 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12623
12624 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12625 let mut selections = self.selections.all::<Point>(cx);
12626 let text_layout_details = self.text_layout_details(window);
12627 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12628 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12629 let range = oldest_selection.display_range(&display_map).sorted();
12630
12631 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12632 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12633 let positions = start_x.min(end_x)..start_x.max(end_x);
12634
12635 selections.clear();
12636 let mut stack = Vec::new();
12637 for row in range.start.row().0..=range.end.row().0 {
12638 if let Some(selection) = self.selections.build_columnar_selection(
12639 &display_map,
12640 DisplayRow(row),
12641 &positions,
12642 oldest_selection.reversed,
12643 &text_layout_details,
12644 ) {
12645 stack.push(selection.id);
12646 selections.push(selection);
12647 }
12648 }
12649
12650 if above {
12651 stack.reverse();
12652 }
12653
12654 AddSelectionsState { above, stack }
12655 });
12656
12657 let last_added_selection = *state.stack.last().unwrap();
12658 let mut new_selections = Vec::new();
12659 if above == state.above {
12660 let end_row = if above {
12661 DisplayRow(0)
12662 } else {
12663 display_map.max_point().row()
12664 };
12665
12666 'outer: for selection in selections {
12667 if selection.id == last_added_selection {
12668 let range = selection.display_range(&display_map).sorted();
12669 debug_assert_eq!(range.start.row(), range.end.row());
12670 let mut row = range.start.row();
12671 let positions =
12672 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12673 px(start)..px(end)
12674 } else {
12675 let start_x =
12676 display_map.x_for_display_point(range.start, &text_layout_details);
12677 let end_x =
12678 display_map.x_for_display_point(range.end, &text_layout_details);
12679 start_x.min(end_x)..start_x.max(end_x)
12680 };
12681
12682 while row != end_row {
12683 if above {
12684 row.0 -= 1;
12685 } else {
12686 row.0 += 1;
12687 }
12688
12689 if let Some(new_selection) = self.selections.build_columnar_selection(
12690 &display_map,
12691 row,
12692 &positions,
12693 selection.reversed,
12694 &text_layout_details,
12695 ) {
12696 state.stack.push(new_selection.id);
12697 if above {
12698 new_selections.push(new_selection);
12699 new_selections.push(selection);
12700 } else {
12701 new_selections.push(selection);
12702 new_selections.push(new_selection);
12703 }
12704
12705 continue 'outer;
12706 }
12707 }
12708 }
12709
12710 new_selections.push(selection);
12711 }
12712 } else {
12713 new_selections = selections;
12714 new_selections.retain(|s| s.id != last_added_selection);
12715 state.stack.pop();
12716 }
12717
12718 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12719 s.select(new_selections);
12720 });
12721 if state.stack.len() > 1 {
12722 self.add_selections_state = Some(state);
12723 }
12724 }
12725
12726 fn select_match_ranges(
12727 &mut self,
12728 range: Range<usize>,
12729 reversed: bool,
12730 replace_newest: bool,
12731 auto_scroll: Option<Autoscroll>,
12732 window: &mut Window,
12733 cx: &mut Context<Editor>,
12734 ) {
12735 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12736 self.change_selections(auto_scroll, window, cx, |s| {
12737 if replace_newest {
12738 s.delete(s.newest_anchor().id);
12739 }
12740 if reversed {
12741 s.insert_range(range.end..range.start);
12742 } else {
12743 s.insert_range(range);
12744 }
12745 });
12746 }
12747
12748 pub fn select_next_match_internal(
12749 &mut self,
12750 display_map: &DisplaySnapshot,
12751 replace_newest: bool,
12752 autoscroll: Option<Autoscroll>,
12753 window: &mut Window,
12754 cx: &mut Context<Self>,
12755 ) -> Result<()> {
12756 let buffer = &display_map.buffer_snapshot;
12757 let mut selections = self.selections.all::<usize>(cx);
12758 if let Some(mut select_next_state) = self.select_next_state.take() {
12759 let query = &select_next_state.query;
12760 if !select_next_state.done {
12761 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12762 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12763 let mut next_selected_range = None;
12764
12765 let bytes_after_last_selection =
12766 buffer.bytes_in_range(last_selection.end..buffer.len());
12767 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12768 let query_matches = query
12769 .stream_find_iter(bytes_after_last_selection)
12770 .map(|result| (last_selection.end, result))
12771 .chain(
12772 query
12773 .stream_find_iter(bytes_before_first_selection)
12774 .map(|result| (0, result)),
12775 );
12776
12777 for (start_offset, query_match) in query_matches {
12778 let query_match = query_match.unwrap(); // can only fail due to I/O
12779 let offset_range =
12780 start_offset + query_match.start()..start_offset + query_match.end();
12781 let display_range = offset_range.start.to_display_point(display_map)
12782 ..offset_range.end.to_display_point(display_map);
12783
12784 if !select_next_state.wordwise
12785 || (!movement::is_inside_word(display_map, display_range.start)
12786 && !movement::is_inside_word(display_map, display_range.end))
12787 {
12788 // TODO: This is n^2, because we might check all the selections
12789 if !selections
12790 .iter()
12791 .any(|selection| selection.range().overlaps(&offset_range))
12792 {
12793 next_selected_range = Some(offset_range);
12794 break;
12795 }
12796 }
12797 }
12798
12799 if let Some(next_selected_range) = next_selected_range {
12800 self.select_match_ranges(
12801 next_selected_range,
12802 last_selection.reversed,
12803 replace_newest,
12804 autoscroll,
12805 window,
12806 cx,
12807 );
12808 } else {
12809 select_next_state.done = true;
12810 }
12811 }
12812
12813 self.select_next_state = Some(select_next_state);
12814 } else {
12815 let mut only_carets = true;
12816 let mut same_text_selected = true;
12817 let mut selected_text = None;
12818
12819 let mut selections_iter = selections.iter().peekable();
12820 while let Some(selection) = selections_iter.next() {
12821 if selection.start != selection.end {
12822 only_carets = false;
12823 }
12824
12825 if same_text_selected {
12826 if selected_text.is_none() {
12827 selected_text =
12828 Some(buffer.text_for_range(selection.range()).collect::<String>());
12829 }
12830
12831 if let Some(next_selection) = selections_iter.peek() {
12832 if next_selection.range().len() == selection.range().len() {
12833 let next_selected_text = buffer
12834 .text_for_range(next_selection.range())
12835 .collect::<String>();
12836 if Some(next_selected_text) != selected_text {
12837 same_text_selected = false;
12838 selected_text = None;
12839 }
12840 } else {
12841 same_text_selected = false;
12842 selected_text = None;
12843 }
12844 }
12845 }
12846 }
12847
12848 if only_carets {
12849 for selection in &mut selections {
12850 let word_range = movement::surrounding_word(
12851 display_map,
12852 selection.start.to_display_point(display_map),
12853 );
12854 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12855 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12856 selection.goal = SelectionGoal::None;
12857 selection.reversed = false;
12858 self.select_match_ranges(
12859 selection.start..selection.end,
12860 selection.reversed,
12861 replace_newest,
12862 autoscroll,
12863 window,
12864 cx,
12865 );
12866 }
12867
12868 if selections.len() == 1 {
12869 let selection = selections
12870 .last()
12871 .expect("ensured that there's only one selection");
12872 let query = buffer
12873 .text_for_range(selection.start..selection.end)
12874 .collect::<String>();
12875 let is_empty = query.is_empty();
12876 let select_state = SelectNextState {
12877 query: AhoCorasick::new(&[query])?,
12878 wordwise: true,
12879 done: is_empty,
12880 };
12881 self.select_next_state = Some(select_state);
12882 } else {
12883 self.select_next_state = None;
12884 }
12885 } else if let Some(selected_text) = selected_text {
12886 self.select_next_state = Some(SelectNextState {
12887 query: AhoCorasick::new(&[selected_text])?,
12888 wordwise: false,
12889 done: false,
12890 });
12891 self.select_next_match_internal(
12892 display_map,
12893 replace_newest,
12894 autoscroll,
12895 window,
12896 cx,
12897 )?;
12898 }
12899 }
12900 Ok(())
12901 }
12902
12903 pub fn select_all_matches(
12904 &mut self,
12905 _action: &SelectAllMatches,
12906 window: &mut Window,
12907 cx: &mut Context<Self>,
12908 ) -> Result<()> {
12909 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12910
12911 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12912
12913 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12914 let Some(select_next_state) = self.select_next_state.as_mut() else {
12915 return Ok(());
12916 };
12917 if select_next_state.done {
12918 return Ok(());
12919 }
12920
12921 let mut new_selections = Vec::new();
12922
12923 let reversed = self.selections.oldest::<usize>(cx).reversed;
12924 let buffer = &display_map.buffer_snapshot;
12925 let query_matches = select_next_state
12926 .query
12927 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12928
12929 for query_match in query_matches.into_iter() {
12930 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12931 let offset_range = if reversed {
12932 query_match.end()..query_match.start()
12933 } else {
12934 query_match.start()..query_match.end()
12935 };
12936 let display_range = offset_range.start.to_display_point(&display_map)
12937 ..offset_range.end.to_display_point(&display_map);
12938
12939 if !select_next_state.wordwise
12940 || (!movement::is_inside_word(&display_map, display_range.start)
12941 && !movement::is_inside_word(&display_map, display_range.end))
12942 {
12943 new_selections.push(offset_range.start..offset_range.end);
12944 }
12945 }
12946
12947 select_next_state.done = true;
12948 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12949 self.change_selections(None, window, cx, |selections| {
12950 selections.select_ranges(new_selections)
12951 });
12952
12953 Ok(())
12954 }
12955
12956 pub fn select_next(
12957 &mut self,
12958 action: &SelectNext,
12959 window: &mut Window,
12960 cx: &mut Context<Self>,
12961 ) -> Result<()> {
12962 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12963 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12964 self.select_next_match_internal(
12965 &display_map,
12966 action.replace_newest,
12967 Some(Autoscroll::newest()),
12968 window,
12969 cx,
12970 )?;
12971 Ok(())
12972 }
12973
12974 pub fn select_previous(
12975 &mut self,
12976 action: &SelectPrevious,
12977 window: &mut Window,
12978 cx: &mut Context<Self>,
12979 ) -> Result<()> {
12980 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12981 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12982 let buffer = &display_map.buffer_snapshot;
12983 let mut selections = self.selections.all::<usize>(cx);
12984 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12985 let query = &select_prev_state.query;
12986 if !select_prev_state.done {
12987 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12988 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12989 let mut next_selected_range = None;
12990 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12991 let bytes_before_last_selection =
12992 buffer.reversed_bytes_in_range(0..last_selection.start);
12993 let bytes_after_first_selection =
12994 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12995 let query_matches = query
12996 .stream_find_iter(bytes_before_last_selection)
12997 .map(|result| (last_selection.start, result))
12998 .chain(
12999 query
13000 .stream_find_iter(bytes_after_first_selection)
13001 .map(|result| (buffer.len(), result)),
13002 );
13003 for (end_offset, query_match) in query_matches {
13004 let query_match = query_match.unwrap(); // can only fail due to I/O
13005 let offset_range =
13006 end_offset - query_match.end()..end_offset - query_match.start();
13007 let display_range = offset_range.start.to_display_point(&display_map)
13008 ..offset_range.end.to_display_point(&display_map);
13009
13010 if !select_prev_state.wordwise
13011 || (!movement::is_inside_word(&display_map, display_range.start)
13012 && !movement::is_inside_word(&display_map, display_range.end))
13013 {
13014 next_selected_range = Some(offset_range);
13015 break;
13016 }
13017 }
13018
13019 if let Some(next_selected_range) = next_selected_range {
13020 self.select_match_ranges(
13021 next_selected_range,
13022 last_selection.reversed,
13023 action.replace_newest,
13024 Some(Autoscroll::newest()),
13025 window,
13026 cx,
13027 );
13028 } else {
13029 select_prev_state.done = true;
13030 }
13031 }
13032
13033 self.select_prev_state = Some(select_prev_state);
13034 } else {
13035 let mut only_carets = true;
13036 let mut same_text_selected = true;
13037 let mut selected_text = None;
13038
13039 let mut selections_iter = selections.iter().peekable();
13040 while let Some(selection) = selections_iter.next() {
13041 if selection.start != selection.end {
13042 only_carets = false;
13043 }
13044
13045 if same_text_selected {
13046 if selected_text.is_none() {
13047 selected_text =
13048 Some(buffer.text_for_range(selection.range()).collect::<String>());
13049 }
13050
13051 if let Some(next_selection) = selections_iter.peek() {
13052 if next_selection.range().len() == selection.range().len() {
13053 let next_selected_text = buffer
13054 .text_for_range(next_selection.range())
13055 .collect::<String>();
13056 if Some(next_selected_text) != selected_text {
13057 same_text_selected = false;
13058 selected_text = None;
13059 }
13060 } else {
13061 same_text_selected = false;
13062 selected_text = None;
13063 }
13064 }
13065 }
13066 }
13067
13068 if only_carets {
13069 for selection in &mut selections {
13070 let word_range = movement::surrounding_word(
13071 &display_map,
13072 selection.start.to_display_point(&display_map),
13073 );
13074 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
13075 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
13076 selection.goal = SelectionGoal::None;
13077 selection.reversed = false;
13078 self.select_match_ranges(
13079 selection.start..selection.end,
13080 selection.reversed,
13081 action.replace_newest,
13082 Some(Autoscroll::newest()),
13083 window,
13084 cx,
13085 );
13086 }
13087 if selections.len() == 1 {
13088 let selection = selections
13089 .last()
13090 .expect("ensured that there's only one selection");
13091 let query = buffer
13092 .text_for_range(selection.start..selection.end)
13093 .collect::<String>();
13094 let is_empty = query.is_empty();
13095 let select_state = SelectNextState {
13096 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13097 wordwise: true,
13098 done: is_empty,
13099 };
13100 self.select_prev_state = Some(select_state);
13101 } else {
13102 self.select_prev_state = None;
13103 }
13104 } else if let Some(selected_text) = selected_text {
13105 self.select_prev_state = Some(SelectNextState {
13106 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13107 wordwise: false,
13108 done: false,
13109 });
13110 self.select_previous(action, window, cx)?;
13111 }
13112 }
13113 Ok(())
13114 }
13115
13116 pub fn find_next_match(
13117 &mut self,
13118 _: &FindNextMatch,
13119 window: &mut Window,
13120 cx: &mut Context<Self>,
13121 ) -> Result<()> {
13122 let selections = self.selections.disjoint_anchors();
13123 match selections.first() {
13124 Some(first) if selections.len() >= 2 => {
13125 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13126 s.select_ranges([first.range()]);
13127 });
13128 }
13129 _ => self.select_next(
13130 &SelectNext {
13131 replace_newest: true,
13132 },
13133 window,
13134 cx,
13135 )?,
13136 }
13137 Ok(())
13138 }
13139
13140 pub fn find_previous_match(
13141 &mut self,
13142 _: &FindPreviousMatch,
13143 window: &mut Window,
13144 cx: &mut Context<Self>,
13145 ) -> Result<()> {
13146 let selections = self.selections.disjoint_anchors();
13147 match selections.last() {
13148 Some(last) if selections.len() >= 2 => {
13149 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13150 s.select_ranges([last.range()]);
13151 });
13152 }
13153 _ => self.select_previous(
13154 &SelectPrevious {
13155 replace_newest: true,
13156 },
13157 window,
13158 cx,
13159 )?,
13160 }
13161 Ok(())
13162 }
13163
13164 pub fn toggle_comments(
13165 &mut self,
13166 action: &ToggleComments,
13167 window: &mut Window,
13168 cx: &mut Context<Self>,
13169 ) {
13170 if self.read_only(cx) {
13171 return;
13172 }
13173 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13174 let text_layout_details = &self.text_layout_details(window);
13175 self.transact(window, cx, |this, window, cx| {
13176 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13177 let mut edits = Vec::new();
13178 let mut selection_edit_ranges = Vec::new();
13179 let mut last_toggled_row = None;
13180 let snapshot = this.buffer.read(cx).read(cx);
13181 let empty_str: Arc<str> = Arc::default();
13182 let mut suffixes_inserted = Vec::new();
13183 let ignore_indent = action.ignore_indent;
13184
13185 fn comment_prefix_range(
13186 snapshot: &MultiBufferSnapshot,
13187 row: MultiBufferRow,
13188 comment_prefix: &str,
13189 comment_prefix_whitespace: &str,
13190 ignore_indent: bool,
13191 ) -> Range<Point> {
13192 let indent_size = if ignore_indent {
13193 0
13194 } else {
13195 snapshot.indent_size_for_line(row).len
13196 };
13197
13198 let start = Point::new(row.0, indent_size);
13199
13200 let mut line_bytes = snapshot
13201 .bytes_in_range(start..snapshot.max_point())
13202 .flatten()
13203 .copied();
13204
13205 // If this line currently begins with the line comment prefix, then record
13206 // the range containing the prefix.
13207 if line_bytes
13208 .by_ref()
13209 .take(comment_prefix.len())
13210 .eq(comment_prefix.bytes())
13211 {
13212 // Include any whitespace that matches the comment prefix.
13213 let matching_whitespace_len = line_bytes
13214 .zip(comment_prefix_whitespace.bytes())
13215 .take_while(|(a, b)| a == b)
13216 .count() as u32;
13217 let end = Point::new(
13218 start.row,
13219 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13220 );
13221 start..end
13222 } else {
13223 start..start
13224 }
13225 }
13226
13227 fn comment_suffix_range(
13228 snapshot: &MultiBufferSnapshot,
13229 row: MultiBufferRow,
13230 comment_suffix: &str,
13231 comment_suffix_has_leading_space: bool,
13232 ) -> Range<Point> {
13233 let end = Point::new(row.0, snapshot.line_len(row));
13234 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13235
13236 let mut line_end_bytes = snapshot
13237 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13238 .flatten()
13239 .copied();
13240
13241 let leading_space_len = if suffix_start_column > 0
13242 && line_end_bytes.next() == Some(b' ')
13243 && comment_suffix_has_leading_space
13244 {
13245 1
13246 } else {
13247 0
13248 };
13249
13250 // If this line currently begins with the line comment prefix, then record
13251 // the range containing the prefix.
13252 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13253 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13254 start..end
13255 } else {
13256 end..end
13257 }
13258 }
13259
13260 // TODO: Handle selections that cross excerpts
13261 for selection in &mut selections {
13262 let start_column = snapshot
13263 .indent_size_for_line(MultiBufferRow(selection.start.row))
13264 .len;
13265 let language = if let Some(language) =
13266 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13267 {
13268 language
13269 } else {
13270 continue;
13271 };
13272
13273 selection_edit_ranges.clear();
13274
13275 // If multiple selections contain a given row, avoid processing that
13276 // row more than once.
13277 let mut start_row = MultiBufferRow(selection.start.row);
13278 if last_toggled_row == Some(start_row) {
13279 start_row = start_row.next_row();
13280 }
13281 let end_row =
13282 if selection.end.row > selection.start.row && selection.end.column == 0 {
13283 MultiBufferRow(selection.end.row - 1)
13284 } else {
13285 MultiBufferRow(selection.end.row)
13286 };
13287 last_toggled_row = Some(end_row);
13288
13289 if start_row > end_row {
13290 continue;
13291 }
13292
13293 // If the language has line comments, toggle those.
13294 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13295
13296 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13297 if ignore_indent {
13298 full_comment_prefixes = full_comment_prefixes
13299 .into_iter()
13300 .map(|s| Arc::from(s.trim_end()))
13301 .collect();
13302 }
13303
13304 if !full_comment_prefixes.is_empty() {
13305 let first_prefix = full_comment_prefixes
13306 .first()
13307 .expect("prefixes is non-empty");
13308 let prefix_trimmed_lengths = full_comment_prefixes
13309 .iter()
13310 .map(|p| p.trim_end_matches(' ').len())
13311 .collect::<SmallVec<[usize; 4]>>();
13312
13313 let mut all_selection_lines_are_comments = true;
13314
13315 for row in start_row.0..=end_row.0 {
13316 let row = MultiBufferRow(row);
13317 if start_row < end_row && snapshot.is_line_blank(row) {
13318 continue;
13319 }
13320
13321 let prefix_range = full_comment_prefixes
13322 .iter()
13323 .zip(prefix_trimmed_lengths.iter().copied())
13324 .map(|(prefix, trimmed_prefix_len)| {
13325 comment_prefix_range(
13326 snapshot.deref(),
13327 row,
13328 &prefix[..trimmed_prefix_len],
13329 &prefix[trimmed_prefix_len..],
13330 ignore_indent,
13331 )
13332 })
13333 .max_by_key(|range| range.end.column - range.start.column)
13334 .expect("prefixes is non-empty");
13335
13336 if prefix_range.is_empty() {
13337 all_selection_lines_are_comments = false;
13338 }
13339
13340 selection_edit_ranges.push(prefix_range);
13341 }
13342
13343 if all_selection_lines_are_comments {
13344 edits.extend(
13345 selection_edit_ranges
13346 .iter()
13347 .cloned()
13348 .map(|range| (range, empty_str.clone())),
13349 );
13350 } else {
13351 let min_column = selection_edit_ranges
13352 .iter()
13353 .map(|range| range.start.column)
13354 .min()
13355 .unwrap_or(0);
13356 edits.extend(selection_edit_ranges.iter().map(|range| {
13357 let position = Point::new(range.start.row, min_column);
13358 (position..position, first_prefix.clone())
13359 }));
13360 }
13361 } else if let Some((full_comment_prefix, comment_suffix)) =
13362 language.block_comment_delimiters()
13363 {
13364 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13365 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13366 let prefix_range = comment_prefix_range(
13367 snapshot.deref(),
13368 start_row,
13369 comment_prefix,
13370 comment_prefix_whitespace,
13371 ignore_indent,
13372 );
13373 let suffix_range = comment_suffix_range(
13374 snapshot.deref(),
13375 end_row,
13376 comment_suffix.trim_start_matches(' '),
13377 comment_suffix.starts_with(' '),
13378 );
13379
13380 if prefix_range.is_empty() || suffix_range.is_empty() {
13381 edits.push((
13382 prefix_range.start..prefix_range.start,
13383 full_comment_prefix.clone(),
13384 ));
13385 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13386 suffixes_inserted.push((end_row, comment_suffix.len()));
13387 } else {
13388 edits.push((prefix_range, empty_str.clone()));
13389 edits.push((suffix_range, empty_str.clone()));
13390 }
13391 } else {
13392 continue;
13393 }
13394 }
13395
13396 drop(snapshot);
13397 this.buffer.update(cx, |buffer, cx| {
13398 buffer.edit(edits, None, cx);
13399 });
13400
13401 // Adjust selections so that they end before any comment suffixes that
13402 // were inserted.
13403 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13404 let mut selections = this.selections.all::<Point>(cx);
13405 let snapshot = this.buffer.read(cx).read(cx);
13406 for selection in &mut selections {
13407 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13408 match row.cmp(&MultiBufferRow(selection.end.row)) {
13409 Ordering::Less => {
13410 suffixes_inserted.next();
13411 continue;
13412 }
13413 Ordering::Greater => break,
13414 Ordering::Equal => {
13415 if selection.end.column == snapshot.line_len(row) {
13416 if selection.is_empty() {
13417 selection.start.column -= suffix_len as u32;
13418 }
13419 selection.end.column -= suffix_len as u32;
13420 }
13421 break;
13422 }
13423 }
13424 }
13425 }
13426
13427 drop(snapshot);
13428 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13429 s.select(selections)
13430 });
13431
13432 let selections = this.selections.all::<Point>(cx);
13433 let selections_on_single_row = selections.windows(2).all(|selections| {
13434 selections[0].start.row == selections[1].start.row
13435 && selections[0].end.row == selections[1].end.row
13436 && selections[0].start.row == selections[0].end.row
13437 });
13438 let selections_selecting = selections
13439 .iter()
13440 .any(|selection| selection.start != selection.end);
13441 let advance_downwards = action.advance_downwards
13442 && selections_on_single_row
13443 && !selections_selecting
13444 && !matches!(this.mode, EditorMode::SingleLine { .. });
13445
13446 if advance_downwards {
13447 let snapshot = this.buffer.read(cx).snapshot(cx);
13448
13449 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13450 s.move_cursors_with(|display_snapshot, display_point, _| {
13451 let mut point = display_point.to_point(display_snapshot);
13452 point.row += 1;
13453 point = snapshot.clip_point(point, Bias::Left);
13454 let display_point = point.to_display_point(display_snapshot);
13455 let goal = SelectionGoal::HorizontalPosition(
13456 display_snapshot
13457 .x_for_display_point(display_point, text_layout_details)
13458 .into(),
13459 );
13460 (display_point, goal)
13461 })
13462 });
13463 }
13464 });
13465 }
13466
13467 pub fn select_enclosing_symbol(
13468 &mut self,
13469 _: &SelectEnclosingSymbol,
13470 window: &mut Window,
13471 cx: &mut Context<Self>,
13472 ) {
13473 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13474
13475 let buffer = self.buffer.read(cx).snapshot(cx);
13476 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13477
13478 fn update_selection(
13479 selection: &Selection<usize>,
13480 buffer_snap: &MultiBufferSnapshot,
13481 ) -> Option<Selection<usize>> {
13482 let cursor = selection.head();
13483 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13484 for symbol in symbols.iter().rev() {
13485 let start = symbol.range.start.to_offset(buffer_snap);
13486 let end = symbol.range.end.to_offset(buffer_snap);
13487 let new_range = start..end;
13488 if start < selection.start || end > selection.end {
13489 return Some(Selection {
13490 id: selection.id,
13491 start: new_range.start,
13492 end: new_range.end,
13493 goal: SelectionGoal::None,
13494 reversed: selection.reversed,
13495 });
13496 }
13497 }
13498 None
13499 }
13500
13501 let mut selected_larger_symbol = false;
13502 let new_selections = old_selections
13503 .iter()
13504 .map(|selection| match update_selection(selection, &buffer) {
13505 Some(new_selection) => {
13506 if new_selection.range() != selection.range() {
13507 selected_larger_symbol = true;
13508 }
13509 new_selection
13510 }
13511 None => selection.clone(),
13512 })
13513 .collect::<Vec<_>>();
13514
13515 if selected_larger_symbol {
13516 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13517 s.select(new_selections);
13518 });
13519 }
13520 }
13521
13522 pub fn select_larger_syntax_node(
13523 &mut self,
13524 _: &SelectLargerSyntaxNode,
13525 window: &mut Window,
13526 cx: &mut Context<Self>,
13527 ) {
13528 let Some(visible_row_count) = self.visible_row_count() else {
13529 return;
13530 };
13531 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13532 if old_selections.is_empty() {
13533 return;
13534 }
13535
13536 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13537
13538 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13539 let buffer = self.buffer.read(cx).snapshot(cx);
13540
13541 let mut selected_larger_node = false;
13542 let mut new_selections = old_selections
13543 .iter()
13544 .map(|selection| {
13545 let old_range = selection.start..selection.end;
13546
13547 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13548 // manually select word at selection
13549 if ["string_content", "inline"].contains(&node.kind()) {
13550 let word_range = {
13551 let display_point = buffer
13552 .offset_to_point(old_range.start)
13553 .to_display_point(&display_map);
13554 let Range { start, end } =
13555 movement::surrounding_word(&display_map, display_point);
13556 start.to_point(&display_map).to_offset(&buffer)
13557 ..end.to_point(&display_map).to_offset(&buffer)
13558 };
13559 // ignore if word is already selected
13560 if !word_range.is_empty() && old_range != word_range {
13561 let last_word_range = {
13562 let display_point = buffer
13563 .offset_to_point(old_range.end)
13564 .to_display_point(&display_map);
13565 let Range { start, end } =
13566 movement::surrounding_word(&display_map, display_point);
13567 start.to_point(&display_map).to_offset(&buffer)
13568 ..end.to_point(&display_map).to_offset(&buffer)
13569 };
13570 // only select word if start and end point belongs to same word
13571 if word_range == last_word_range {
13572 selected_larger_node = true;
13573 return Selection {
13574 id: selection.id,
13575 start: word_range.start,
13576 end: word_range.end,
13577 goal: SelectionGoal::None,
13578 reversed: selection.reversed,
13579 };
13580 }
13581 }
13582 }
13583 }
13584
13585 let mut new_range = old_range.clone();
13586 while let Some((_node, containing_range)) =
13587 buffer.syntax_ancestor(new_range.clone())
13588 {
13589 new_range = match containing_range {
13590 MultiOrSingleBufferOffsetRange::Single(_) => break,
13591 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13592 };
13593 if !display_map.intersects_fold(new_range.start)
13594 && !display_map.intersects_fold(new_range.end)
13595 {
13596 break;
13597 }
13598 }
13599
13600 selected_larger_node |= new_range != old_range;
13601 Selection {
13602 id: selection.id,
13603 start: new_range.start,
13604 end: new_range.end,
13605 goal: SelectionGoal::None,
13606 reversed: selection.reversed,
13607 }
13608 })
13609 .collect::<Vec<_>>();
13610
13611 if !selected_larger_node {
13612 return; // don't put this call in the history
13613 }
13614
13615 // scroll based on transformation done to the last selection created by the user
13616 let (last_old, last_new) = old_selections
13617 .last()
13618 .zip(new_selections.last().cloned())
13619 .expect("old_selections isn't empty");
13620
13621 // revert selection
13622 let is_selection_reversed = {
13623 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13624 new_selections.last_mut().expect("checked above").reversed =
13625 should_newest_selection_be_reversed;
13626 should_newest_selection_be_reversed
13627 };
13628
13629 if selected_larger_node {
13630 self.select_syntax_node_history.disable_clearing = true;
13631 self.change_selections(None, window, cx, |s| {
13632 s.select(new_selections.clone());
13633 });
13634 self.select_syntax_node_history.disable_clearing = false;
13635 }
13636
13637 let start_row = last_new.start.to_display_point(&display_map).row().0;
13638 let end_row = last_new.end.to_display_point(&display_map).row().0;
13639 let selection_height = end_row - start_row + 1;
13640 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13641
13642 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13643 let scroll_behavior = if fits_on_the_screen {
13644 self.request_autoscroll(Autoscroll::fit(), cx);
13645 SelectSyntaxNodeScrollBehavior::FitSelection
13646 } else if is_selection_reversed {
13647 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13648 SelectSyntaxNodeScrollBehavior::CursorTop
13649 } else {
13650 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13651 SelectSyntaxNodeScrollBehavior::CursorBottom
13652 };
13653
13654 self.select_syntax_node_history.push((
13655 old_selections,
13656 scroll_behavior,
13657 is_selection_reversed,
13658 ));
13659 }
13660
13661 pub fn select_smaller_syntax_node(
13662 &mut self,
13663 _: &SelectSmallerSyntaxNode,
13664 window: &mut Window,
13665 cx: &mut Context<Self>,
13666 ) {
13667 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13668
13669 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13670 self.select_syntax_node_history.pop()
13671 {
13672 if let Some(selection) = selections.last_mut() {
13673 selection.reversed = is_selection_reversed;
13674 }
13675
13676 self.select_syntax_node_history.disable_clearing = true;
13677 self.change_selections(None, window, cx, |s| {
13678 s.select(selections.to_vec());
13679 });
13680 self.select_syntax_node_history.disable_clearing = false;
13681
13682 match scroll_behavior {
13683 SelectSyntaxNodeScrollBehavior::CursorTop => {
13684 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13685 }
13686 SelectSyntaxNodeScrollBehavior::FitSelection => {
13687 self.request_autoscroll(Autoscroll::fit(), cx);
13688 }
13689 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13690 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13691 }
13692 }
13693 }
13694 }
13695
13696 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13697 if !EditorSettings::get_global(cx).gutter.runnables {
13698 self.clear_tasks();
13699 return Task::ready(());
13700 }
13701 let project = self.project.as_ref().map(Entity::downgrade);
13702 let task_sources = self.lsp_task_sources(cx);
13703 let multi_buffer = self.buffer.downgrade();
13704 cx.spawn_in(window, async move |editor, cx| {
13705 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13706 let Some(project) = project.and_then(|p| p.upgrade()) else {
13707 return;
13708 };
13709 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13710 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13711 }) else {
13712 return;
13713 };
13714
13715 let hide_runnables = project
13716 .update(cx, |project, cx| {
13717 // Do not display any test indicators in non-dev server remote projects.
13718 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13719 })
13720 .unwrap_or(true);
13721 if hide_runnables {
13722 return;
13723 }
13724 let new_rows =
13725 cx.background_spawn({
13726 let snapshot = display_snapshot.clone();
13727 async move {
13728 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13729 }
13730 })
13731 .await;
13732 let Ok(lsp_tasks) =
13733 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13734 else {
13735 return;
13736 };
13737 let lsp_tasks = lsp_tasks.await;
13738
13739 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13740 lsp_tasks
13741 .into_iter()
13742 .flat_map(|(kind, tasks)| {
13743 tasks.into_iter().filter_map(move |(location, task)| {
13744 Some((kind.clone(), location?, task))
13745 })
13746 })
13747 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13748 let buffer = location.target.buffer;
13749 let buffer_snapshot = buffer.read(cx).snapshot();
13750 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13751 |(excerpt_id, snapshot, _)| {
13752 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13753 display_snapshot
13754 .buffer_snapshot
13755 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13756 } else {
13757 None
13758 }
13759 },
13760 );
13761 if let Some(offset) = offset {
13762 let task_buffer_range =
13763 location.target.range.to_point(&buffer_snapshot);
13764 let context_buffer_range =
13765 task_buffer_range.to_offset(&buffer_snapshot);
13766 let context_range = BufferOffset(context_buffer_range.start)
13767 ..BufferOffset(context_buffer_range.end);
13768
13769 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13770 .or_insert_with(|| RunnableTasks {
13771 templates: Vec::new(),
13772 offset,
13773 column: task_buffer_range.start.column,
13774 extra_variables: HashMap::default(),
13775 context_range,
13776 })
13777 .templates
13778 .push((kind, task.original_task().clone()));
13779 }
13780
13781 acc
13782 })
13783 }) else {
13784 return;
13785 };
13786
13787 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
13788 buffer.language_settings(cx).tasks.prefer_lsp
13789 }) else {
13790 return;
13791 };
13792
13793 let rows = Self::runnable_rows(
13794 project,
13795 display_snapshot,
13796 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
13797 new_rows,
13798 cx.clone(),
13799 );
13800 editor
13801 .update(cx, |editor, _| {
13802 editor.clear_tasks();
13803 for (key, mut value) in rows {
13804 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13805 value.templates.extend(lsp_tasks.templates);
13806 }
13807
13808 editor.insert_tasks(key, value);
13809 }
13810 for (key, value) in lsp_tasks_by_rows {
13811 editor.insert_tasks(key, value);
13812 }
13813 })
13814 .ok();
13815 })
13816 }
13817 fn fetch_runnable_ranges(
13818 snapshot: &DisplaySnapshot,
13819 range: Range<Anchor>,
13820 ) -> Vec<language::RunnableRange> {
13821 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13822 }
13823
13824 fn runnable_rows(
13825 project: Entity<Project>,
13826 snapshot: DisplaySnapshot,
13827 prefer_lsp: bool,
13828 runnable_ranges: Vec<RunnableRange>,
13829 mut cx: AsyncWindowContext,
13830 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13831 runnable_ranges
13832 .into_iter()
13833 .filter_map(|mut runnable| {
13834 let mut tasks = cx
13835 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13836 .ok()?;
13837 if prefer_lsp {
13838 tasks.retain(|(task_kind, _)| {
13839 !matches!(task_kind, TaskSourceKind::Language { .. })
13840 });
13841 }
13842 if tasks.is_empty() {
13843 return None;
13844 }
13845
13846 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13847
13848 let row = snapshot
13849 .buffer_snapshot
13850 .buffer_line_for_row(MultiBufferRow(point.row))?
13851 .1
13852 .start
13853 .row;
13854
13855 let context_range =
13856 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13857 Some((
13858 (runnable.buffer_id, row),
13859 RunnableTasks {
13860 templates: tasks,
13861 offset: snapshot
13862 .buffer_snapshot
13863 .anchor_before(runnable.run_range.start),
13864 context_range,
13865 column: point.column,
13866 extra_variables: runnable.extra_captures,
13867 },
13868 ))
13869 })
13870 .collect()
13871 }
13872
13873 fn templates_with_tags(
13874 project: &Entity<Project>,
13875 runnable: &mut Runnable,
13876 cx: &mut App,
13877 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13878 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13879 let (worktree_id, file) = project
13880 .buffer_for_id(runnable.buffer, cx)
13881 .and_then(|buffer| buffer.read(cx).file())
13882 .map(|file| (file.worktree_id(cx), file.clone()))
13883 .unzip();
13884
13885 (
13886 project.task_store().read(cx).task_inventory().cloned(),
13887 worktree_id,
13888 file,
13889 )
13890 });
13891
13892 let mut templates_with_tags = mem::take(&mut runnable.tags)
13893 .into_iter()
13894 .flat_map(|RunnableTag(tag)| {
13895 inventory
13896 .as_ref()
13897 .into_iter()
13898 .flat_map(|inventory| {
13899 inventory.read(cx).list_tasks(
13900 file.clone(),
13901 Some(runnable.language.clone()),
13902 worktree_id,
13903 cx,
13904 )
13905 })
13906 .filter(move |(_, template)| {
13907 template.tags.iter().any(|source_tag| source_tag == &tag)
13908 })
13909 })
13910 .sorted_by_key(|(kind, _)| kind.to_owned())
13911 .collect::<Vec<_>>();
13912 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13913 // Strongest source wins; if we have worktree tag binding, prefer that to
13914 // global and language bindings;
13915 // if we have a global binding, prefer that to language binding.
13916 let first_mismatch = templates_with_tags
13917 .iter()
13918 .position(|(tag_source, _)| tag_source != leading_tag_source);
13919 if let Some(index) = first_mismatch {
13920 templates_with_tags.truncate(index);
13921 }
13922 }
13923
13924 templates_with_tags
13925 }
13926
13927 pub fn move_to_enclosing_bracket(
13928 &mut self,
13929 _: &MoveToEnclosingBracket,
13930 window: &mut Window,
13931 cx: &mut Context<Self>,
13932 ) {
13933 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13934 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13935 s.move_offsets_with(|snapshot, selection| {
13936 let Some(enclosing_bracket_ranges) =
13937 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13938 else {
13939 return;
13940 };
13941
13942 let mut best_length = usize::MAX;
13943 let mut best_inside = false;
13944 let mut best_in_bracket_range = false;
13945 let mut best_destination = None;
13946 for (open, close) in enclosing_bracket_ranges {
13947 let close = close.to_inclusive();
13948 let length = close.end() - open.start;
13949 let inside = selection.start >= open.end && selection.end <= *close.start();
13950 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13951 || close.contains(&selection.head());
13952
13953 // If best is next to a bracket and current isn't, skip
13954 if !in_bracket_range && best_in_bracket_range {
13955 continue;
13956 }
13957
13958 // Prefer smaller lengths unless best is inside and current isn't
13959 if length > best_length && (best_inside || !inside) {
13960 continue;
13961 }
13962
13963 best_length = length;
13964 best_inside = inside;
13965 best_in_bracket_range = in_bracket_range;
13966 best_destination = Some(
13967 if close.contains(&selection.start) && close.contains(&selection.end) {
13968 if inside { open.end } else { open.start }
13969 } else if inside {
13970 *close.start()
13971 } else {
13972 *close.end()
13973 },
13974 );
13975 }
13976
13977 if let Some(destination) = best_destination {
13978 selection.collapse_to(destination, SelectionGoal::None);
13979 }
13980 })
13981 });
13982 }
13983
13984 pub fn undo_selection(
13985 &mut self,
13986 _: &UndoSelection,
13987 window: &mut Window,
13988 cx: &mut Context<Self>,
13989 ) {
13990 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13991 self.end_selection(window, cx);
13992 self.selection_history.mode = SelectionHistoryMode::Undoing;
13993 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13994 self.change_selections(None, window, cx, |s| {
13995 s.select_anchors(entry.selections.to_vec())
13996 });
13997 self.select_next_state = entry.select_next_state;
13998 self.select_prev_state = entry.select_prev_state;
13999 self.add_selections_state = entry.add_selections_state;
14000 self.request_autoscroll(Autoscroll::newest(), cx);
14001 }
14002 self.selection_history.mode = SelectionHistoryMode::Normal;
14003 }
14004
14005 pub fn redo_selection(
14006 &mut self,
14007 _: &RedoSelection,
14008 window: &mut Window,
14009 cx: &mut Context<Self>,
14010 ) {
14011 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14012 self.end_selection(window, cx);
14013 self.selection_history.mode = SelectionHistoryMode::Redoing;
14014 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14015 self.change_selections(None, window, cx, |s| {
14016 s.select_anchors(entry.selections.to_vec())
14017 });
14018 self.select_next_state = entry.select_next_state;
14019 self.select_prev_state = entry.select_prev_state;
14020 self.add_selections_state = entry.add_selections_state;
14021 self.request_autoscroll(Autoscroll::newest(), cx);
14022 }
14023 self.selection_history.mode = SelectionHistoryMode::Normal;
14024 }
14025
14026 pub fn expand_excerpts(
14027 &mut self,
14028 action: &ExpandExcerpts,
14029 _: &mut Window,
14030 cx: &mut Context<Self>,
14031 ) {
14032 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14033 }
14034
14035 pub fn expand_excerpts_down(
14036 &mut self,
14037 action: &ExpandExcerptsDown,
14038 _: &mut Window,
14039 cx: &mut Context<Self>,
14040 ) {
14041 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14042 }
14043
14044 pub fn expand_excerpts_up(
14045 &mut self,
14046 action: &ExpandExcerptsUp,
14047 _: &mut Window,
14048 cx: &mut Context<Self>,
14049 ) {
14050 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14051 }
14052
14053 pub fn expand_excerpts_for_direction(
14054 &mut self,
14055 lines: u32,
14056 direction: ExpandExcerptDirection,
14057
14058 cx: &mut Context<Self>,
14059 ) {
14060 let selections = self.selections.disjoint_anchors();
14061
14062 let lines = if lines == 0 {
14063 EditorSettings::get_global(cx).expand_excerpt_lines
14064 } else {
14065 lines
14066 };
14067
14068 self.buffer.update(cx, |buffer, cx| {
14069 let snapshot = buffer.snapshot(cx);
14070 let mut excerpt_ids = selections
14071 .iter()
14072 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14073 .collect::<Vec<_>>();
14074 excerpt_ids.sort();
14075 excerpt_ids.dedup();
14076 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14077 })
14078 }
14079
14080 pub fn expand_excerpt(
14081 &mut self,
14082 excerpt: ExcerptId,
14083 direction: ExpandExcerptDirection,
14084 window: &mut Window,
14085 cx: &mut Context<Self>,
14086 ) {
14087 let current_scroll_position = self.scroll_position(cx);
14088 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14089 let mut should_scroll_up = false;
14090
14091 if direction == ExpandExcerptDirection::Down {
14092 let multi_buffer = self.buffer.read(cx);
14093 let snapshot = multi_buffer.snapshot(cx);
14094 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14095 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14096 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14097 let buffer_snapshot = buffer.read(cx).snapshot();
14098 let excerpt_end_row =
14099 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14100 let last_row = buffer_snapshot.max_point().row;
14101 let lines_below = last_row.saturating_sub(excerpt_end_row);
14102 should_scroll_up = lines_below >= lines_to_expand;
14103 }
14104 }
14105 }
14106 }
14107
14108 self.buffer.update(cx, |buffer, cx| {
14109 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14110 });
14111
14112 if should_scroll_up {
14113 let new_scroll_position =
14114 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14115 self.set_scroll_position(new_scroll_position, window, cx);
14116 }
14117 }
14118
14119 pub fn go_to_singleton_buffer_point(
14120 &mut self,
14121 point: Point,
14122 window: &mut Window,
14123 cx: &mut Context<Self>,
14124 ) {
14125 self.go_to_singleton_buffer_range(point..point, window, cx);
14126 }
14127
14128 pub fn go_to_singleton_buffer_range(
14129 &mut self,
14130 range: Range<Point>,
14131 window: &mut Window,
14132 cx: &mut Context<Self>,
14133 ) {
14134 let multibuffer = self.buffer().read(cx);
14135 let Some(buffer) = multibuffer.as_singleton() else {
14136 return;
14137 };
14138 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14139 return;
14140 };
14141 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14142 return;
14143 };
14144 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14145 s.select_anchor_ranges([start..end])
14146 });
14147 }
14148
14149 pub fn go_to_diagnostic(
14150 &mut self,
14151 _: &GoToDiagnostic,
14152 window: &mut Window,
14153 cx: &mut Context<Self>,
14154 ) {
14155 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14156 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14157 }
14158
14159 pub fn go_to_prev_diagnostic(
14160 &mut self,
14161 _: &GoToPreviousDiagnostic,
14162 window: &mut Window,
14163 cx: &mut Context<Self>,
14164 ) {
14165 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14166 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14167 }
14168
14169 pub fn go_to_diagnostic_impl(
14170 &mut self,
14171 direction: Direction,
14172 window: &mut Window,
14173 cx: &mut Context<Self>,
14174 ) {
14175 let buffer = self.buffer.read(cx).snapshot(cx);
14176 let selection = self.selections.newest::<usize>(cx);
14177
14178 let mut active_group_id = None;
14179 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14180 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14181 active_group_id = Some(active_group.group_id);
14182 }
14183 }
14184
14185 fn filtered(
14186 snapshot: EditorSnapshot,
14187 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14188 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14189 diagnostics
14190 .filter(|entry| entry.range.start != entry.range.end)
14191 .filter(|entry| !entry.diagnostic.is_unnecessary)
14192 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14193 }
14194
14195 let snapshot = self.snapshot(window, cx);
14196 let before = filtered(
14197 snapshot.clone(),
14198 buffer
14199 .diagnostics_in_range(0..selection.start)
14200 .filter(|entry| entry.range.start <= selection.start),
14201 );
14202 let after = filtered(
14203 snapshot,
14204 buffer
14205 .diagnostics_in_range(selection.start..buffer.len())
14206 .filter(|entry| entry.range.start >= selection.start),
14207 );
14208
14209 let mut found: Option<DiagnosticEntry<usize>> = None;
14210 if direction == Direction::Prev {
14211 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14212 {
14213 for diagnostic in prev_diagnostics.into_iter().rev() {
14214 if diagnostic.range.start != selection.start
14215 || active_group_id
14216 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14217 {
14218 found = Some(diagnostic);
14219 break 'outer;
14220 }
14221 }
14222 }
14223 } else {
14224 for diagnostic in after.chain(before) {
14225 if diagnostic.range.start != selection.start
14226 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14227 {
14228 found = Some(diagnostic);
14229 break;
14230 }
14231 }
14232 }
14233 let Some(next_diagnostic) = found else {
14234 return;
14235 };
14236
14237 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14238 return;
14239 };
14240 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14241 s.select_ranges(vec![
14242 next_diagnostic.range.start..next_diagnostic.range.start,
14243 ])
14244 });
14245 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14246 self.refresh_inline_completion(false, true, window, cx);
14247 }
14248
14249 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14250 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14251 let snapshot = self.snapshot(window, cx);
14252 let selection = self.selections.newest::<Point>(cx);
14253 self.go_to_hunk_before_or_after_position(
14254 &snapshot,
14255 selection.head(),
14256 Direction::Next,
14257 window,
14258 cx,
14259 );
14260 }
14261
14262 pub fn go_to_hunk_before_or_after_position(
14263 &mut self,
14264 snapshot: &EditorSnapshot,
14265 position: Point,
14266 direction: Direction,
14267 window: &mut Window,
14268 cx: &mut Context<Editor>,
14269 ) {
14270 let row = if direction == Direction::Next {
14271 self.hunk_after_position(snapshot, position)
14272 .map(|hunk| hunk.row_range.start)
14273 } else {
14274 self.hunk_before_position(snapshot, position)
14275 };
14276
14277 if let Some(row) = row {
14278 let destination = Point::new(row.0, 0);
14279 let autoscroll = Autoscroll::center();
14280
14281 self.unfold_ranges(&[destination..destination], false, false, cx);
14282 self.change_selections(Some(autoscroll), window, cx, |s| {
14283 s.select_ranges([destination..destination]);
14284 });
14285 }
14286 }
14287
14288 fn hunk_after_position(
14289 &mut self,
14290 snapshot: &EditorSnapshot,
14291 position: Point,
14292 ) -> Option<MultiBufferDiffHunk> {
14293 snapshot
14294 .buffer_snapshot
14295 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14296 .find(|hunk| hunk.row_range.start.0 > position.row)
14297 .or_else(|| {
14298 snapshot
14299 .buffer_snapshot
14300 .diff_hunks_in_range(Point::zero()..position)
14301 .find(|hunk| hunk.row_range.end.0 < position.row)
14302 })
14303 }
14304
14305 fn go_to_prev_hunk(
14306 &mut self,
14307 _: &GoToPreviousHunk,
14308 window: &mut Window,
14309 cx: &mut Context<Self>,
14310 ) {
14311 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14312 let snapshot = self.snapshot(window, cx);
14313 let selection = self.selections.newest::<Point>(cx);
14314 self.go_to_hunk_before_or_after_position(
14315 &snapshot,
14316 selection.head(),
14317 Direction::Prev,
14318 window,
14319 cx,
14320 );
14321 }
14322
14323 fn hunk_before_position(
14324 &mut self,
14325 snapshot: &EditorSnapshot,
14326 position: Point,
14327 ) -> Option<MultiBufferRow> {
14328 snapshot
14329 .buffer_snapshot
14330 .diff_hunk_before(position)
14331 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14332 }
14333
14334 fn go_to_next_change(
14335 &mut self,
14336 _: &GoToNextChange,
14337 window: &mut Window,
14338 cx: &mut Context<Self>,
14339 ) {
14340 if let Some(selections) = self
14341 .change_list
14342 .next_change(1, Direction::Next)
14343 .map(|s| s.to_vec())
14344 {
14345 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14346 let map = s.display_map();
14347 s.select_display_ranges(selections.iter().map(|a| {
14348 let point = a.to_display_point(&map);
14349 point..point
14350 }))
14351 })
14352 }
14353 }
14354
14355 fn go_to_previous_change(
14356 &mut self,
14357 _: &GoToPreviousChange,
14358 window: &mut Window,
14359 cx: &mut Context<Self>,
14360 ) {
14361 if let Some(selections) = self
14362 .change_list
14363 .next_change(1, Direction::Prev)
14364 .map(|s| s.to_vec())
14365 {
14366 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14367 let map = s.display_map();
14368 s.select_display_ranges(selections.iter().map(|a| {
14369 let point = a.to_display_point(&map);
14370 point..point
14371 }))
14372 })
14373 }
14374 }
14375
14376 fn go_to_line<T: 'static>(
14377 &mut self,
14378 position: Anchor,
14379 highlight_color: Option<Hsla>,
14380 window: &mut Window,
14381 cx: &mut Context<Self>,
14382 ) {
14383 let snapshot = self.snapshot(window, cx).display_snapshot;
14384 let position = position.to_point(&snapshot.buffer_snapshot);
14385 let start = snapshot
14386 .buffer_snapshot
14387 .clip_point(Point::new(position.row, 0), Bias::Left);
14388 let end = start + Point::new(1, 0);
14389 let start = snapshot.buffer_snapshot.anchor_before(start);
14390 let end = snapshot.buffer_snapshot.anchor_before(end);
14391
14392 self.highlight_rows::<T>(
14393 start..end,
14394 highlight_color
14395 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14396 Default::default(),
14397 cx,
14398 );
14399
14400 if self.buffer.read(cx).is_singleton() {
14401 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14402 }
14403 }
14404
14405 pub fn go_to_definition(
14406 &mut self,
14407 _: &GoToDefinition,
14408 window: &mut Window,
14409 cx: &mut Context<Self>,
14410 ) -> Task<Result<Navigated>> {
14411 let definition =
14412 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14413 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14414 cx.spawn_in(window, async move |editor, cx| {
14415 if definition.await? == Navigated::Yes {
14416 return Ok(Navigated::Yes);
14417 }
14418 match fallback_strategy {
14419 GoToDefinitionFallback::None => Ok(Navigated::No),
14420 GoToDefinitionFallback::FindAllReferences => {
14421 match editor.update_in(cx, |editor, window, cx| {
14422 editor.find_all_references(&FindAllReferences, window, cx)
14423 })? {
14424 Some(references) => references.await,
14425 None => Ok(Navigated::No),
14426 }
14427 }
14428 }
14429 })
14430 }
14431
14432 pub fn go_to_declaration(
14433 &mut self,
14434 _: &GoToDeclaration,
14435 window: &mut Window,
14436 cx: &mut Context<Self>,
14437 ) -> Task<Result<Navigated>> {
14438 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14439 }
14440
14441 pub fn go_to_declaration_split(
14442 &mut self,
14443 _: &GoToDeclaration,
14444 window: &mut Window,
14445 cx: &mut Context<Self>,
14446 ) -> Task<Result<Navigated>> {
14447 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14448 }
14449
14450 pub fn go_to_implementation(
14451 &mut self,
14452 _: &GoToImplementation,
14453 window: &mut Window,
14454 cx: &mut Context<Self>,
14455 ) -> Task<Result<Navigated>> {
14456 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14457 }
14458
14459 pub fn go_to_implementation_split(
14460 &mut self,
14461 _: &GoToImplementationSplit,
14462 window: &mut Window,
14463 cx: &mut Context<Self>,
14464 ) -> Task<Result<Navigated>> {
14465 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14466 }
14467
14468 pub fn go_to_type_definition(
14469 &mut self,
14470 _: &GoToTypeDefinition,
14471 window: &mut Window,
14472 cx: &mut Context<Self>,
14473 ) -> Task<Result<Navigated>> {
14474 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14475 }
14476
14477 pub fn go_to_definition_split(
14478 &mut self,
14479 _: &GoToDefinitionSplit,
14480 window: &mut Window,
14481 cx: &mut Context<Self>,
14482 ) -> Task<Result<Navigated>> {
14483 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14484 }
14485
14486 pub fn go_to_type_definition_split(
14487 &mut self,
14488 _: &GoToTypeDefinitionSplit,
14489 window: &mut Window,
14490 cx: &mut Context<Self>,
14491 ) -> Task<Result<Navigated>> {
14492 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14493 }
14494
14495 fn go_to_definition_of_kind(
14496 &mut self,
14497 kind: GotoDefinitionKind,
14498 split: bool,
14499 window: &mut Window,
14500 cx: &mut Context<Self>,
14501 ) -> Task<Result<Navigated>> {
14502 let Some(provider) = self.semantics_provider.clone() else {
14503 return Task::ready(Ok(Navigated::No));
14504 };
14505 let head = self.selections.newest::<usize>(cx).head();
14506 let buffer = self.buffer.read(cx);
14507 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14508 text_anchor
14509 } else {
14510 return Task::ready(Ok(Navigated::No));
14511 };
14512
14513 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14514 return Task::ready(Ok(Navigated::No));
14515 };
14516
14517 cx.spawn_in(window, async move |editor, cx| {
14518 let definitions = definitions.await?;
14519 let navigated = editor
14520 .update_in(cx, |editor, window, cx| {
14521 editor.navigate_to_hover_links(
14522 Some(kind),
14523 definitions
14524 .into_iter()
14525 .filter(|location| {
14526 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14527 })
14528 .map(HoverLink::Text)
14529 .collect::<Vec<_>>(),
14530 split,
14531 window,
14532 cx,
14533 )
14534 })?
14535 .await?;
14536 anyhow::Ok(navigated)
14537 })
14538 }
14539
14540 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14541 let selection = self.selections.newest_anchor();
14542 let head = selection.head();
14543 let tail = selection.tail();
14544
14545 let Some((buffer, start_position)) =
14546 self.buffer.read(cx).text_anchor_for_position(head, cx)
14547 else {
14548 return;
14549 };
14550
14551 let end_position = if head != tail {
14552 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14553 return;
14554 };
14555 Some(pos)
14556 } else {
14557 None
14558 };
14559
14560 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14561 let url = if let Some(end_pos) = end_position {
14562 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14563 } else {
14564 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14565 };
14566
14567 if let Some(url) = url {
14568 editor.update(cx, |_, cx| {
14569 cx.open_url(&url);
14570 })
14571 } else {
14572 Ok(())
14573 }
14574 });
14575
14576 url_finder.detach();
14577 }
14578
14579 pub fn open_selected_filename(
14580 &mut self,
14581 _: &OpenSelectedFilename,
14582 window: &mut Window,
14583 cx: &mut Context<Self>,
14584 ) {
14585 let Some(workspace) = self.workspace() else {
14586 return;
14587 };
14588
14589 let position = self.selections.newest_anchor().head();
14590
14591 let Some((buffer, buffer_position)) =
14592 self.buffer.read(cx).text_anchor_for_position(position, cx)
14593 else {
14594 return;
14595 };
14596
14597 let project = self.project.clone();
14598
14599 cx.spawn_in(window, async move |_, cx| {
14600 let result = find_file(&buffer, project, buffer_position, cx).await;
14601
14602 if let Some((_, path)) = result {
14603 workspace
14604 .update_in(cx, |workspace, window, cx| {
14605 workspace.open_resolved_path(path, window, cx)
14606 })?
14607 .await?;
14608 }
14609 anyhow::Ok(())
14610 })
14611 .detach();
14612 }
14613
14614 pub(crate) fn navigate_to_hover_links(
14615 &mut self,
14616 kind: Option<GotoDefinitionKind>,
14617 mut definitions: Vec<HoverLink>,
14618 split: bool,
14619 window: &mut Window,
14620 cx: &mut Context<Editor>,
14621 ) -> Task<Result<Navigated>> {
14622 // If there is one definition, just open it directly
14623 if definitions.len() == 1 {
14624 let definition = definitions.pop().unwrap();
14625
14626 enum TargetTaskResult {
14627 Location(Option<Location>),
14628 AlreadyNavigated,
14629 }
14630
14631 let target_task = match definition {
14632 HoverLink::Text(link) => {
14633 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14634 }
14635 HoverLink::InlayHint(lsp_location, server_id) => {
14636 let computation =
14637 self.compute_target_location(lsp_location, server_id, window, cx);
14638 cx.background_spawn(async move {
14639 let location = computation.await?;
14640 Ok(TargetTaskResult::Location(location))
14641 })
14642 }
14643 HoverLink::Url(url) => {
14644 cx.open_url(&url);
14645 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14646 }
14647 HoverLink::File(path) => {
14648 if let Some(workspace) = self.workspace() {
14649 cx.spawn_in(window, async move |_, cx| {
14650 workspace
14651 .update_in(cx, |workspace, window, cx| {
14652 workspace.open_resolved_path(path, window, cx)
14653 })?
14654 .await
14655 .map(|_| TargetTaskResult::AlreadyNavigated)
14656 })
14657 } else {
14658 Task::ready(Ok(TargetTaskResult::Location(None)))
14659 }
14660 }
14661 };
14662 cx.spawn_in(window, async move |editor, cx| {
14663 let target = match target_task.await.context("target resolution task")? {
14664 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14665 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14666 TargetTaskResult::Location(Some(target)) => target,
14667 };
14668
14669 editor.update_in(cx, |editor, window, cx| {
14670 let Some(workspace) = editor.workspace() else {
14671 return Navigated::No;
14672 };
14673 let pane = workspace.read(cx).active_pane().clone();
14674
14675 let range = target.range.to_point(target.buffer.read(cx));
14676 let range = editor.range_for_match(&range);
14677 let range = collapse_multiline_range(range);
14678
14679 if !split
14680 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14681 {
14682 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14683 } else {
14684 window.defer(cx, move |window, cx| {
14685 let target_editor: Entity<Self> =
14686 workspace.update(cx, |workspace, cx| {
14687 let pane = if split {
14688 workspace.adjacent_pane(window, cx)
14689 } else {
14690 workspace.active_pane().clone()
14691 };
14692
14693 workspace.open_project_item(
14694 pane,
14695 target.buffer.clone(),
14696 true,
14697 true,
14698 window,
14699 cx,
14700 )
14701 });
14702 target_editor.update(cx, |target_editor, cx| {
14703 // When selecting a definition in a different buffer, disable the nav history
14704 // to avoid creating a history entry at the previous cursor location.
14705 pane.update(cx, |pane, _| pane.disable_history());
14706 target_editor.go_to_singleton_buffer_range(range, window, cx);
14707 pane.update(cx, |pane, _| pane.enable_history());
14708 });
14709 });
14710 }
14711 Navigated::Yes
14712 })
14713 })
14714 } else if !definitions.is_empty() {
14715 cx.spawn_in(window, async move |editor, cx| {
14716 let (title, location_tasks, workspace) = editor
14717 .update_in(cx, |editor, window, cx| {
14718 let tab_kind = match kind {
14719 Some(GotoDefinitionKind::Implementation) => "Implementations",
14720 _ => "Definitions",
14721 };
14722 let title = definitions
14723 .iter()
14724 .find_map(|definition| match definition {
14725 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14726 let buffer = origin.buffer.read(cx);
14727 format!(
14728 "{} for {}",
14729 tab_kind,
14730 buffer
14731 .text_for_range(origin.range.clone())
14732 .collect::<String>()
14733 )
14734 }),
14735 HoverLink::InlayHint(_, _) => None,
14736 HoverLink::Url(_) => None,
14737 HoverLink::File(_) => None,
14738 })
14739 .unwrap_or(tab_kind.to_string());
14740 let location_tasks = definitions
14741 .into_iter()
14742 .map(|definition| match definition {
14743 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14744 HoverLink::InlayHint(lsp_location, server_id) => editor
14745 .compute_target_location(lsp_location, server_id, window, cx),
14746 HoverLink::Url(_) => Task::ready(Ok(None)),
14747 HoverLink::File(_) => Task::ready(Ok(None)),
14748 })
14749 .collect::<Vec<_>>();
14750 (title, location_tasks, editor.workspace().clone())
14751 })
14752 .context("location tasks preparation")?;
14753
14754 let locations = future::join_all(location_tasks)
14755 .await
14756 .into_iter()
14757 .filter_map(|location| location.transpose())
14758 .collect::<Result<_>>()
14759 .context("location tasks")?;
14760
14761 let Some(workspace) = workspace else {
14762 return Ok(Navigated::No);
14763 };
14764 let opened = workspace
14765 .update_in(cx, |workspace, window, cx| {
14766 Self::open_locations_in_multibuffer(
14767 workspace,
14768 locations,
14769 title,
14770 split,
14771 MultibufferSelectionMode::First,
14772 window,
14773 cx,
14774 )
14775 })
14776 .ok();
14777
14778 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14779 })
14780 } else {
14781 Task::ready(Ok(Navigated::No))
14782 }
14783 }
14784
14785 fn compute_target_location(
14786 &self,
14787 lsp_location: lsp::Location,
14788 server_id: LanguageServerId,
14789 window: &mut Window,
14790 cx: &mut Context<Self>,
14791 ) -> Task<anyhow::Result<Option<Location>>> {
14792 let Some(project) = self.project.clone() else {
14793 return Task::ready(Ok(None));
14794 };
14795
14796 cx.spawn_in(window, async move |editor, cx| {
14797 let location_task = editor.update(cx, |_, cx| {
14798 project.update(cx, |project, cx| {
14799 let language_server_name = project
14800 .language_server_statuses(cx)
14801 .find(|(id, _)| server_id == *id)
14802 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14803 language_server_name.map(|language_server_name| {
14804 project.open_local_buffer_via_lsp(
14805 lsp_location.uri.clone(),
14806 server_id,
14807 language_server_name,
14808 cx,
14809 )
14810 })
14811 })
14812 })?;
14813 let location = match location_task {
14814 Some(task) => Some({
14815 let target_buffer_handle = task.await.context("open local buffer")?;
14816 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
14817 let target_start = target_buffer
14818 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14819 let target_end = target_buffer
14820 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14821 target_buffer.anchor_after(target_start)
14822 ..target_buffer.anchor_before(target_end)
14823 })?;
14824 Location {
14825 buffer: target_buffer_handle,
14826 range,
14827 }
14828 }),
14829 None => None,
14830 };
14831 Ok(location)
14832 })
14833 }
14834
14835 pub fn find_all_references(
14836 &mut self,
14837 _: &FindAllReferences,
14838 window: &mut Window,
14839 cx: &mut Context<Self>,
14840 ) -> Option<Task<Result<Navigated>>> {
14841 let selection = self.selections.newest::<usize>(cx);
14842 let multi_buffer = self.buffer.read(cx);
14843 let head = selection.head();
14844
14845 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14846 let head_anchor = multi_buffer_snapshot.anchor_at(
14847 head,
14848 if head < selection.tail() {
14849 Bias::Right
14850 } else {
14851 Bias::Left
14852 },
14853 );
14854
14855 match self
14856 .find_all_references_task_sources
14857 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14858 {
14859 Ok(_) => {
14860 log::info!(
14861 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14862 );
14863 return None;
14864 }
14865 Err(i) => {
14866 self.find_all_references_task_sources.insert(i, head_anchor);
14867 }
14868 }
14869
14870 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14871 let workspace = self.workspace()?;
14872 let project = workspace.read(cx).project().clone();
14873 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14874 Some(cx.spawn_in(window, async move |editor, cx| {
14875 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14876 if let Ok(i) = editor
14877 .find_all_references_task_sources
14878 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14879 {
14880 editor.find_all_references_task_sources.remove(i);
14881 }
14882 });
14883
14884 let locations = references.await?;
14885 if locations.is_empty() {
14886 return anyhow::Ok(Navigated::No);
14887 }
14888
14889 workspace.update_in(cx, |workspace, window, cx| {
14890 let title = locations
14891 .first()
14892 .as_ref()
14893 .map(|location| {
14894 let buffer = location.buffer.read(cx);
14895 format!(
14896 "References to `{}`",
14897 buffer
14898 .text_for_range(location.range.clone())
14899 .collect::<String>()
14900 )
14901 })
14902 .unwrap();
14903 Self::open_locations_in_multibuffer(
14904 workspace,
14905 locations,
14906 title,
14907 false,
14908 MultibufferSelectionMode::First,
14909 window,
14910 cx,
14911 );
14912 Navigated::Yes
14913 })
14914 }))
14915 }
14916
14917 /// Opens a multibuffer with the given project locations in it
14918 pub fn open_locations_in_multibuffer(
14919 workspace: &mut Workspace,
14920 mut locations: Vec<Location>,
14921 title: String,
14922 split: bool,
14923 multibuffer_selection_mode: MultibufferSelectionMode,
14924 window: &mut Window,
14925 cx: &mut Context<Workspace>,
14926 ) {
14927 // If there are multiple definitions, open them in a multibuffer
14928 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14929 let mut locations = locations.into_iter().peekable();
14930 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14931 let capability = workspace.project().read(cx).capability();
14932
14933 let excerpt_buffer = cx.new(|cx| {
14934 let mut multibuffer = MultiBuffer::new(capability);
14935 while let Some(location) = locations.next() {
14936 let buffer = location.buffer.read(cx);
14937 let mut ranges_for_buffer = Vec::new();
14938 let range = location.range.to_point(buffer);
14939 ranges_for_buffer.push(range.clone());
14940
14941 while let Some(next_location) = locations.peek() {
14942 if next_location.buffer == location.buffer {
14943 ranges_for_buffer.push(next_location.range.to_point(buffer));
14944 locations.next();
14945 } else {
14946 break;
14947 }
14948 }
14949
14950 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14951 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14952 PathKey::for_buffer(&location.buffer, cx),
14953 location.buffer.clone(),
14954 ranges_for_buffer,
14955 DEFAULT_MULTIBUFFER_CONTEXT,
14956 cx,
14957 );
14958 ranges.extend(new_ranges)
14959 }
14960
14961 multibuffer.with_title(title)
14962 });
14963
14964 let editor = cx.new(|cx| {
14965 Editor::for_multibuffer(
14966 excerpt_buffer,
14967 Some(workspace.project().clone()),
14968 window,
14969 cx,
14970 )
14971 });
14972 editor.update(cx, |editor, cx| {
14973 match multibuffer_selection_mode {
14974 MultibufferSelectionMode::First => {
14975 if let Some(first_range) = ranges.first() {
14976 editor.change_selections(None, window, cx, |selections| {
14977 selections.clear_disjoint();
14978 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14979 });
14980 }
14981 editor.highlight_background::<Self>(
14982 &ranges,
14983 |theme| theme.editor_highlighted_line_background,
14984 cx,
14985 );
14986 }
14987 MultibufferSelectionMode::All => {
14988 editor.change_selections(None, window, cx, |selections| {
14989 selections.clear_disjoint();
14990 selections.select_anchor_ranges(ranges);
14991 });
14992 }
14993 }
14994 editor.register_buffers_with_language_servers(cx);
14995 });
14996
14997 let item = Box::new(editor);
14998 let item_id = item.item_id();
14999
15000 if split {
15001 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15002 } else {
15003 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15004 let (preview_item_id, preview_item_idx) =
15005 workspace.active_pane().read_with(cx, |pane, _| {
15006 (pane.preview_item_id(), pane.preview_item_idx())
15007 });
15008
15009 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15010
15011 if let Some(preview_item_id) = preview_item_id {
15012 workspace.active_pane().update(cx, |pane, cx| {
15013 pane.remove_item(preview_item_id, false, false, window, cx);
15014 });
15015 }
15016 } else {
15017 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15018 }
15019 }
15020 workspace.active_pane().update(cx, |pane, cx| {
15021 pane.set_preview_item_id(Some(item_id), cx);
15022 });
15023 }
15024
15025 pub fn rename(
15026 &mut self,
15027 _: &Rename,
15028 window: &mut Window,
15029 cx: &mut Context<Self>,
15030 ) -> Option<Task<Result<()>>> {
15031 use language::ToOffset as _;
15032
15033 let provider = self.semantics_provider.clone()?;
15034 let selection = self.selections.newest_anchor().clone();
15035 let (cursor_buffer, cursor_buffer_position) = self
15036 .buffer
15037 .read(cx)
15038 .text_anchor_for_position(selection.head(), cx)?;
15039 let (tail_buffer, cursor_buffer_position_end) = self
15040 .buffer
15041 .read(cx)
15042 .text_anchor_for_position(selection.tail(), cx)?;
15043 if tail_buffer != cursor_buffer {
15044 return None;
15045 }
15046
15047 let snapshot = cursor_buffer.read(cx).snapshot();
15048 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15049 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15050 let prepare_rename = provider
15051 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15052 .unwrap_or_else(|| Task::ready(Ok(None)));
15053 drop(snapshot);
15054
15055 Some(cx.spawn_in(window, async move |this, cx| {
15056 let rename_range = if let Some(range) = prepare_rename.await? {
15057 Some(range)
15058 } else {
15059 this.update(cx, |this, cx| {
15060 let buffer = this.buffer.read(cx).snapshot(cx);
15061 let mut buffer_highlights = this
15062 .document_highlights_for_position(selection.head(), &buffer)
15063 .filter(|highlight| {
15064 highlight.start.excerpt_id == selection.head().excerpt_id
15065 && highlight.end.excerpt_id == selection.head().excerpt_id
15066 });
15067 buffer_highlights
15068 .next()
15069 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15070 })?
15071 };
15072 if let Some(rename_range) = rename_range {
15073 this.update_in(cx, |this, window, cx| {
15074 let snapshot = cursor_buffer.read(cx).snapshot();
15075 let rename_buffer_range = rename_range.to_offset(&snapshot);
15076 let cursor_offset_in_rename_range =
15077 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15078 let cursor_offset_in_rename_range_end =
15079 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15080
15081 this.take_rename(false, window, cx);
15082 let buffer = this.buffer.read(cx).read(cx);
15083 let cursor_offset = selection.head().to_offset(&buffer);
15084 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15085 let rename_end = rename_start + rename_buffer_range.len();
15086 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15087 let mut old_highlight_id = None;
15088 let old_name: Arc<str> = buffer
15089 .chunks(rename_start..rename_end, true)
15090 .map(|chunk| {
15091 if old_highlight_id.is_none() {
15092 old_highlight_id = chunk.syntax_highlight_id;
15093 }
15094 chunk.text
15095 })
15096 .collect::<String>()
15097 .into();
15098
15099 drop(buffer);
15100
15101 // Position the selection in the rename editor so that it matches the current selection.
15102 this.show_local_selections = false;
15103 let rename_editor = cx.new(|cx| {
15104 let mut editor = Editor::single_line(window, cx);
15105 editor.buffer.update(cx, |buffer, cx| {
15106 buffer.edit([(0..0, old_name.clone())], None, cx)
15107 });
15108 let rename_selection_range = match cursor_offset_in_rename_range
15109 .cmp(&cursor_offset_in_rename_range_end)
15110 {
15111 Ordering::Equal => {
15112 editor.select_all(&SelectAll, window, cx);
15113 return editor;
15114 }
15115 Ordering::Less => {
15116 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15117 }
15118 Ordering::Greater => {
15119 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15120 }
15121 };
15122 if rename_selection_range.end > old_name.len() {
15123 editor.select_all(&SelectAll, window, cx);
15124 } else {
15125 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15126 s.select_ranges([rename_selection_range]);
15127 });
15128 }
15129 editor
15130 });
15131 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15132 if e == &EditorEvent::Focused {
15133 cx.emit(EditorEvent::FocusedIn)
15134 }
15135 })
15136 .detach();
15137
15138 let write_highlights =
15139 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15140 let read_highlights =
15141 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15142 let ranges = write_highlights
15143 .iter()
15144 .flat_map(|(_, ranges)| ranges.iter())
15145 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15146 .cloned()
15147 .collect();
15148
15149 this.highlight_text::<Rename>(
15150 ranges,
15151 HighlightStyle {
15152 fade_out: Some(0.6),
15153 ..Default::default()
15154 },
15155 cx,
15156 );
15157 let rename_focus_handle = rename_editor.focus_handle(cx);
15158 window.focus(&rename_focus_handle);
15159 let block_id = this.insert_blocks(
15160 [BlockProperties {
15161 style: BlockStyle::Flex,
15162 placement: BlockPlacement::Below(range.start),
15163 height: Some(1),
15164 render: Arc::new({
15165 let rename_editor = rename_editor.clone();
15166 move |cx: &mut BlockContext| {
15167 let mut text_style = cx.editor_style.text.clone();
15168 if let Some(highlight_style) = old_highlight_id
15169 .and_then(|h| h.style(&cx.editor_style.syntax))
15170 {
15171 text_style = text_style.highlight(highlight_style);
15172 }
15173 div()
15174 .block_mouse_except_scroll()
15175 .pl(cx.anchor_x)
15176 .child(EditorElement::new(
15177 &rename_editor,
15178 EditorStyle {
15179 background: cx.theme().system().transparent,
15180 local_player: cx.editor_style.local_player,
15181 text: text_style,
15182 scrollbar_width: cx.editor_style.scrollbar_width,
15183 syntax: cx.editor_style.syntax.clone(),
15184 status: cx.editor_style.status.clone(),
15185 inlay_hints_style: HighlightStyle {
15186 font_weight: Some(FontWeight::BOLD),
15187 ..make_inlay_hints_style(cx.app)
15188 },
15189 inline_completion_styles: make_suggestion_styles(
15190 cx.app,
15191 ),
15192 ..EditorStyle::default()
15193 },
15194 ))
15195 .into_any_element()
15196 }
15197 }),
15198 priority: 0,
15199 render_in_minimap: true,
15200 }],
15201 Some(Autoscroll::fit()),
15202 cx,
15203 )[0];
15204 this.pending_rename = Some(RenameState {
15205 range,
15206 old_name,
15207 editor: rename_editor,
15208 block_id,
15209 });
15210 })?;
15211 }
15212
15213 Ok(())
15214 }))
15215 }
15216
15217 pub fn confirm_rename(
15218 &mut self,
15219 _: &ConfirmRename,
15220 window: &mut Window,
15221 cx: &mut Context<Self>,
15222 ) -> Option<Task<Result<()>>> {
15223 let rename = self.take_rename(false, window, cx)?;
15224 let workspace = self.workspace()?.downgrade();
15225 let (buffer, start) = self
15226 .buffer
15227 .read(cx)
15228 .text_anchor_for_position(rename.range.start, cx)?;
15229 let (end_buffer, _) = self
15230 .buffer
15231 .read(cx)
15232 .text_anchor_for_position(rename.range.end, cx)?;
15233 if buffer != end_buffer {
15234 return None;
15235 }
15236
15237 let old_name = rename.old_name;
15238 let new_name = rename.editor.read(cx).text(cx);
15239
15240 let rename = self.semantics_provider.as_ref()?.perform_rename(
15241 &buffer,
15242 start,
15243 new_name.clone(),
15244 cx,
15245 )?;
15246
15247 Some(cx.spawn_in(window, async move |editor, cx| {
15248 let project_transaction = rename.await?;
15249 Self::open_project_transaction(
15250 &editor,
15251 workspace,
15252 project_transaction,
15253 format!("Rename: {} → {}", old_name, new_name),
15254 cx,
15255 )
15256 .await?;
15257
15258 editor.update(cx, |editor, cx| {
15259 editor.refresh_document_highlights(cx);
15260 })?;
15261 Ok(())
15262 }))
15263 }
15264
15265 fn take_rename(
15266 &mut self,
15267 moving_cursor: bool,
15268 window: &mut Window,
15269 cx: &mut Context<Self>,
15270 ) -> Option<RenameState> {
15271 let rename = self.pending_rename.take()?;
15272 if rename.editor.focus_handle(cx).is_focused(window) {
15273 window.focus(&self.focus_handle);
15274 }
15275
15276 self.remove_blocks(
15277 [rename.block_id].into_iter().collect(),
15278 Some(Autoscroll::fit()),
15279 cx,
15280 );
15281 self.clear_highlights::<Rename>(cx);
15282 self.show_local_selections = true;
15283
15284 if moving_cursor {
15285 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15286 editor.selections.newest::<usize>(cx).head()
15287 });
15288
15289 // Update the selection to match the position of the selection inside
15290 // the rename editor.
15291 let snapshot = self.buffer.read(cx).read(cx);
15292 let rename_range = rename.range.to_offset(&snapshot);
15293 let cursor_in_editor = snapshot
15294 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15295 .min(rename_range.end);
15296 drop(snapshot);
15297
15298 self.change_selections(None, window, cx, |s| {
15299 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15300 });
15301 } else {
15302 self.refresh_document_highlights(cx);
15303 }
15304
15305 Some(rename)
15306 }
15307
15308 pub fn pending_rename(&self) -> Option<&RenameState> {
15309 self.pending_rename.as_ref()
15310 }
15311
15312 fn format(
15313 &mut self,
15314 _: &Format,
15315 window: &mut Window,
15316 cx: &mut Context<Self>,
15317 ) -> Option<Task<Result<()>>> {
15318 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15319
15320 let project = match &self.project {
15321 Some(project) => project.clone(),
15322 None => return None,
15323 };
15324
15325 Some(self.perform_format(
15326 project,
15327 FormatTrigger::Manual,
15328 FormatTarget::Buffers,
15329 window,
15330 cx,
15331 ))
15332 }
15333
15334 fn format_selections(
15335 &mut self,
15336 _: &FormatSelections,
15337 window: &mut Window,
15338 cx: &mut Context<Self>,
15339 ) -> Option<Task<Result<()>>> {
15340 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15341
15342 let project = match &self.project {
15343 Some(project) => project.clone(),
15344 None => return None,
15345 };
15346
15347 let ranges = self
15348 .selections
15349 .all_adjusted(cx)
15350 .into_iter()
15351 .map(|selection| selection.range())
15352 .collect_vec();
15353
15354 Some(self.perform_format(
15355 project,
15356 FormatTrigger::Manual,
15357 FormatTarget::Ranges(ranges),
15358 window,
15359 cx,
15360 ))
15361 }
15362
15363 fn perform_format(
15364 &mut self,
15365 project: Entity<Project>,
15366 trigger: FormatTrigger,
15367 target: FormatTarget,
15368 window: &mut Window,
15369 cx: &mut Context<Self>,
15370 ) -> Task<Result<()>> {
15371 let buffer = self.buffer.clone();
15372 let (buffers, target) = match target {
15373 FormatTarget::Buffers => {
15374 let mut buffers = buffer.read(cx).all_buffers();
15375 if trigger == FormatTrigger::Save {
15376 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15377 }
15378 (buffers, LspFormatTarget::Buffers)
15379 }
15380 FormatTarget::Ranges(selection_ranges) => {
15381 let multi_buffer = buffer.read(cx);
15382 let snapshot = multi_buffer.read(cx);
15383 let mut buffers = HashSet::default();
15384 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15385 BTreeMap::new();
15386 for selection_range in selection_ranges {
15387 for (buffer, buffer_range, _) in
15388 snapshot.range_to_buffer_ranges(selection_range)
15389 {
15390 let buffer_id = buffer.remote_id();
15391 let start = buffer.anchor_before(buffer_range.start);
15392 let end = buffer.anchor_after(buffer_range.end);
15393 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15394 buffer_id_to_ranges
15395 .entry(buffer_id)
15396 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15397 .or_insert_with(|| vec![start..end]);
15398 }
15399 }
15400 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15401 }
15402 };
15403
15404 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15405 let selections_prev = transaction_id_prev
15406 .and_then(|transaction_id_prev| {
15407 // default to selections as they were after the last edit, if we have them,
15408 // instead of how they are now.
15409 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15410 // will take you back to where you made the last edit, instead of staying where you scrolled
15411 self.selection_history
15412 .transaction(transaction_id_prev)
15413 .map(|t| t.0.clone())
15414 })
15415 .unwrap_or_else(|| {
15416 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15417 self.selections.disjoint_anchors()
15418 });
15419
15420 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15421 let format = project.update(cx, |project, cx| {
15422 project.format(buffers, target, true, trigger, cx)
15423 });
15424
15425 cx.spawn_in(window, async move |editor, cx| {
15426 let transaction = futures::select_biased! {
15427 transaction = format.log_err().fuse() => transaction,
15428 () = timeout => {
15429 log::warn!("timed out waiting for formatting");
15430 None
15431 }
15432 };
15433
15434 buffer
15435 .update(cx, |buffer, cx| {
15436 if let Some(transaction) = transaction {
15437 if !buffer.is_singleton() {
15438 buffer.push_transaction(&transaction.0, cx);
15439 }
15440 }
15441 cx.notify();
15442 })
15443 .ok();
15444
15445 if let Some(transaction_id_now) =
15446 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15447 {
15448 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15449 if has_new_transaction {
15450 _ = editor.update(cx, |editor, _| {
15451 editor
15452 .selection_history
15453 .insert_transaction(transaction_id_now, selections_prev);
15454 });
15455 }
15456 }
15457
15458 Ok(())
15459 })
15460 }
15461
15462 fn organize_imports(
15463 &mut self,
15464 _: &OrganizeImports,
15465 window: &mut Window,
15466 cx: &mut Context<Self>,
15467 ) -> Option<Task<Result<()>>> {
15468 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15469 let project = match &self.project {
15470 Some(project) => project.clone(),
15471 None => return None,
15472 };
15473 Some(self.perform_code_action_kind(
15474 project,
15475 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15476 window,
15477 cx,
15478 ))
15479 }
15480
15481 fn perform_code_action_kind(
15482 &mut self,
15483 project: Entity<Project>,
15484 kind: CodeActionKind,
15485 window: &mut Window,
15486 cx: &mut Context<Self>,
15487 ) -> Task<Result<()>> {
15488 let buffer = self.buffer.clone();
15489 let buffers = buffer.read(cx).all_buffers();
15490 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15491 let apply_action = project.update(cx, |project, cx| {
15492 project.apply_code_action_kind(buffers, kind, true, cx)
15493 });
15494 cx.spawn_in(window, async move |_, cx| {
15495 let transaction = futures::select_biased! {
15496 () = timeout => {
15497 log::warn!("timed out waiting for executing code action");
15498 None
15499 }
15500 transaction = apply_action.log_err().fuse() => transaction,
15501 };
15502 buffer
15503 .update(cx, |buffer, cx| {
15504 // check if we need this
15505 if let Some(transaction) = transaction {
15506 if !buffer.is_singleton() {
15507 buffer.push_transaction(&transaction.0, cx);
15508 }
15509 }
15510 cx.notify();
15511 })
15512 .ok();
15513 Ok(())
15514 })
15515 }
15516
15517 fn restart_language_server(
15518 &mut self,
15519 _: &RestartLanguageServer,
15520 _: &mut Window,
15521 cx: &mut Context<Self>,
15522 ) {
15523 if let Some(project) = self.project.clone() {
15524 self.buffer.update(cx, |multi_buffer, cx| {
15525 project.update(cx, |project, cx| {
15526 project.restart_language_servers_for_buffers(
15527 multi_buffer.all_buffers().into_iter().collect(),
15528 cx,
15529 );
15530 });
15531 })
15532 }
15533 }
15534
15535 fn stop_language_server(
15536 &mut self,
15537 _: &StopLanguageServer,
15538 _: &mut Window,
15539 cx: &mut Context<Self>,
15540 ) {
15541 if let Some(project) = self.project.clone() {
15542 self.buffer.update(cx, |multi_buffer, cx| {
15543 project.update(cx, |project, cx| {
15544 project.stop_language_servers_for_buffers(
15545 multi_buffer.all_buffers().into_iter().collect(),
15546 cx,
15547 );
15548 cx.emit(project::Event::RefreshInlayHints);
15549 });
15550 });
15551 }
15552 }
15553
15554 fn cancel_language_server_work(
15555 workspace: &mut Workspace,
15556 _: &actions::CancelLanguageServerWork,
15557 _: &mut Window,
15558 cx: &mut Context<Workspace>,
15559 ) {
15560 let project = workspace.project();
15561 let buffers = workspace
15562 .active_item(cx)
15563 .and_then(|item| item.act_as::<Editor>(cx))
15564 .map_or(HashSet::default(), |editor| {
15565 editor.read(cx).buffer.read(cx).all_buffers()
15566 });
15567 project.update(cx, |project, cx| {
15568 project.cancel_language_server_work_for_buffers(buffers, cx);
15569 });
15570 }
15571
15572 fn show_character_palette(
15573 &mut self,
15574 _: &ShowCharacterPalette,
15575 window: &mut Window,
15576 _: &mut Context<Self>,
15577 ) {
15578 window.show_character_palette();
15579 }
15580
15581 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15582 if self.mode.is_minimap() {
15583 return;
15584 }
15585
15586 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15587 let buffer = self.buffer.read(cx).snapshot(cx);
15588 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15589 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15590 let is_valid = buffer
15591 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15592 .any(|entry| {
15593 entry.diagnostic.is_primary
15594 && !entry.range.is_empty()
15595 && entry.range.start == primary_range_start
15596 && entry.diagnostic.message == active_diagnostics.active_message
15597 });
15598
15599 if !is_valid {
15600 self.dismiss_diagnostics(cx);
15601 }
15602 }
15603 }
15604
15605 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15606 match &self.active_diagnostics {
15607 ActiveDiagnostic::Group(group) => Some(group),
15608 _ => None,
15609 }
15610 }
15611
15612 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15613 self.dismiss_diagnostics(cx);
15614 self.active_diagnostics = ActiveDiagnostic::All;
15615 }
15616
15617 fn activate_diagnostics(
15618 &mut self,
15619 buffer_id: BufferId,
15620 diagnostic: DiagnosticEntry<usize>,
15621 window: &mut Window,
15622 cx: &mut Context<Self>,
15623 ) {
15624 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15625 return;
15626 }
15627 self.dismiss_diagnostics(cx);
15628 let snapshot = self.snapshot(window, cx);
15629 let buffer = self.buffer.read(cx).snapshot(cx);
15630 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15631 return;
15632 };
15633
15634 let diagnostic_group = buffer
15635 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15636 .collect::<Vec<_>>();
15637
15638 let blocks =
15639 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15640
15641 let blocks = self.display_map.update(cx, |display_map, cx| {
15642 display_map.insert_blocks(blocks, cx).into_iter().collect()
15643 });
15644 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15645 active_range: buffer.anchor_before(diagnostic.range.start)
15646 ..buffer.anchor_after(diagnostic.range.end),
15647 active_message: diagnostic.diagnostic.message.clone(),
15648 group_id: diagnostic.diagnostic.group_id,
15649 blocks,
15650 });
15651 cx.notify();
15652 }
15653
15654 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15655 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15656 return;
15657 };
15658
15659 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15660 if let ActiveDiagnostic::Group(group) = prev {
15661 self.display_map.update(cx, |display_map, cx| {
15662 display_map.remove_blocks(group.blocks, cx);
15663 });
15664 cx.notify();
15665 }
15666 }
15667
15668 /// Disable inline diagnostics rendering for this editor.
15669 pub fn disable_inline_diagnostics(&mut self) {
15670 self.inline_diagnostics_enabled = false;
15671 self.inline_diagnostics_update = Task::ready(());
15672 self.inline_diagnostics.clear();
15673 }
15674
15675 pub fn diagnostics_enabled(&self) -> bool {
15676 self.mode.is_full()
15677 }
15678
15679 pub fn inline_diagnostics_enabled(&self) -> bool {
15680 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15681 }
15682
15683 pub fn show_inline_diagnostics(&self) -> bool {
15684 self.show_inline_diagnostics
15685 }
15686
15687 pub fn toggle_inline_diagnostics(
15688 &mut self,
15689 _: &ToggleInlineDiagnostics,
15690 window: &mut Window,
15691 cx: &mut Context<Editor>,
15692 ) {
15693 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15694 self.refresh_inline_diagnostics(false, window, cx);
15695 }
15696
15697 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15698 self.diagnostics_max_severity = severity;
15699 self.display_map.update(cx, |display_map, _| {
15700 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15701 });
15702 }
15703
15704 pub fn toggle_diagnostics(
15705 &mut self,
15706 _: &ToggleDiagnostics,
15707 window: &mut Window,
15708 cx: &mut Context<Editor>,
15709 ) {
15710 if !self.diagnostics_enabled() {
15711 return;
15712 }
15713
15714 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15715 EditorSettings::get_global(cx)
15716 .diagnostics_max_severity
15717 .filter(|severity| severity != &DiagnosticSeverity::Off)
15718 .unwrap_or(DiagnosticSeverity::Hint)
15719 } else {
15720 DiagnosticSeverity::Off
15721 };
15722 self.set_max_diagnostics_severity(new_severity, cx);
15723 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15724 self.active_diagnostics = ActiveDiagnostic::None;
15725 self.inline_diagnostics_update = Task::ready(());
15726 self.inline_diagnostics.clear();
15727 } else {
15728 self.refresh_inline_diagnostics(false, window, cx);
15729 }
15730
15731 cx.notify();
15732 }
15733
15734 pub fn toggle_minimap(
15735 &mut self,
15736 _: &ToggleMinimap,
15737 window: &mut Window,
15738 cx: &mut Context<Editor>,
15739 ) {
15740 if self.supports_minimap(cx) {
15741 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15742 }
15743 }
15744
15745 fn refresh_inline_diagnostics(
15746 &mut self,
15747 debounce: bool,
15748 window: &mut Window,
15749 cx: &mut Context<Self>,
15750 ) {
15751 let max_severity = ProjectSettings::get_global(cx)
15752 .diagnostics
15753 .inline
15754 .max_severity
15755 .unwrap_or(self.diagnostics_max_severity);
15756
15757 if !self.inline_diagnostics_enabled()
15758 || !self.show_inline_diagnostics
15759 || max_severity == DiagnosticSeverity::Off
15760 {
15761 self.inline_diagnostics_update = Task::ready(());
15762 self.inline_diagnostics.clear();
15763 return;
15764 }
15765
15766 let debounce_ms = ProjectSettings::get_global(cx)
15767 .diagnostics
15768 .inline
15769 .update_debounce_ms;
15770 let debounce = if debounce && debounce_ms > 0 {
15771 Some(Duration::from_millis(debounce_ms))
15772 } else {
15773 None
15774 };
15775 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15776 if let Some(debounce) = debounce {
15777 cx.background_executor().timer(debounce).await;
15778 }
15779 let Some(snapshot) = editor.upgrade().and_then(|editor| {
15780 editor
15781 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15782 .ok()
15783 }) else {
15784 return;
15785 };
15786
15787 let new_inline_diagnostics = cx
15788 .background_spawn(async move {
15789 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15790 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15791 let message = diagnostic_entry
15792 .diagnostic
15793 .message
15794 .split_once('\n')
15795 .map(|(line, _)| line)
15796 .map(SharedString::new)
15797 .unwrap_or_else(|| {
15798 SharedString::from(diagnostic_entry.diagnostic.message)
15799 });
15800 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15801 let (Ok(i) | Err(i)) = inline_diagnostics
15802 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15803 inline_diagnostics.insert(
15804 i,
15805 (
15806 start_anchor,
15807 InlineDiagnostic {
15808 message,
15809 group_id: diagnostic_entry.diagnostic.group_id,
15810 start: diagnostic_entry.range.start.to_point(&snapshot),
15811 is_primary: diagnostic_entry.diagnostic.is_primary,
15812 severity: diagnostic_entry.diagnostic.severity,
15813 },
15814 ),
15815 );
15816 }
15817 inline_diagnostics
15818 })
15819 .await;
15820
15821 editor
15822 .update(cx, |editor, cx| {
15823 editor.inline_diagnostics = new_inline_diagnostics;
15824 cx.notify();
15825 })
15826 .ok();
15827 });
15828 }
15829
15830 pub fn set_selections_from_remote(
15831 &mut self,
15832 selections: Vec<Selection<Anchor>>,
15833 pending_selection: Option<Selection<Anchor>>,
15834 window: &mut Window,
15835 cx: &mut Context<Self>,
15836 ) {
15837 let old_cursor_position = self.selections.newest_anchor().head();
15838 self.selections.change_with(cx, |s| {
15839 s.select_anchors(selections);
15840 if let Some(pending_selection) = pending_selection {
15841 s.set_pending(pending_selection, SelectMode::Character);
15842 } else {
15843 s.clear_pending();
15844 }
15845 });
15846 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15847 }
15848
15849 pub fn transact(
15850 &mut self,
15851 window: &mut Window,
15852 cx: &mut Context<Self>,
15853 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15854 ) -> Option<TransactionId> {
15855 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15856 this.start_transaction_at(Instant::now(), window, cx);
15857 update(this, window, cx);
15858 this.end_transaction_at(Instant::now(), cx)
15859 })
15860 }
15861
15862 pub fn start_transaction_at(
15863 &mut self,
15864 now: Instant,
15865 window: &mut Window,
15866 cx: &mut Context<Self>,
15867 ) {
15868 self.end_selection(window, cx);
15869 if let Some(tx_id) = self
15870 .buffer
15871 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15872 {
15873 self.selection_history
15874 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15875 cx.emit(EditorEvent::TransactionBegun {
15876 transaction_id: tx_id,
15877 })
15878 }
15879 }
15880
15881 pub fn end_transaction_at(
15882 &mut self,
15883 now: Instant,
15884 cx: &mut Context<Self>,
15885 ) -> Option<TransactionId> {
15886 if let Some(transaction_id) = self
15887 .buffer
15888 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15889 {
15890 if let Some((_, end_selections)) =
15891 self.selection_history.transaction_mut(transaction_id)
15892 {
15893 *end_selections = Some(self.selections.disjoint_anchors());
15894 } else {
15895 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15896 }
15897
15898 cx.emit(EditorEvent::Edited { transaction_id });
15899 Some(transaction_id)
15900 } else {
15901 None
15902 }
15903 }
15904
15905 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15906 if self.selection_mark_mode {
15907 self.change_selections(None, window, cx, |s| {
15908 s.move_with(|_, sel| {
15909 sel.collapse_to(sel.head(), SelectionGoal::None);
15910 });
15911 })
15912 }
15913 self.selection_mark_mode = true;
15914 cx.notify();
15915 }
15916
15917 pub fn swap_selection_ends(
15918 &mut self,
15919 _: &actions::SwapSelectionEnds,
15920 window: &mut Window,
15921 cx: &mut Context<Self>,
15922 ) {
15923 self.change_selections(None, window, cx, |s| {
15924 s.move_with(|_, sel| {
15925 if sel.start != sel.end {
15926 sel.reversed = !sel.reversed
15927 }
15928 });
15929 });
15930 self.request_autoscroll(Autoscroll::newest(), cx);
15931 cx.notify();
15932 }
15933
15934 pub fn toggle_fold(
15935 &mut self,
15936 _: &actions::ToggleFold,
15937 window: &mut Window,
15938 cx: &mut Context<Self>,
15939 ) {
15940 if self.is_singleton(cx) {
15941 let selection = self.selections.newest::<Point>(cx);
15942
15943 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15944 let range = if selection.is_empty() {
15945 let point = selection.head().to_display_point(&display_map);
15946 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15947 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15948 .to_point(&display_map);
15949 start..end
15950 } else {
15951 selection.range()
15952 };
15953 if display_map.folds_in_range(range).next().is_some() {
15954 self.unfold_lines(&Default::default(), window, cx)
15955 } else {
15956 self.fold(&Default::default(), window, cx)
15957 }
15958 } else {
15959 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15960 let buffer_ids: HashSet<_> = self
15961 .selections
15962 .disjoint_anchor_ranges()
15963 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15964 .collect();
15965
15966 let should_unfold = buffer_ids
15967 .iter()
15968 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15969
15970 for buffer_id in buffer_ids {
15971 if should_unfold {
15972 self.unfold_buffer(buffer_id, cx);
15973 } else {
15974 self.fold_buffer(buffer_id, cx);
15975 }
15976 }
15977 }
15978 }
15979
15980 pub fn toggle_fold_recursive(
15981 &mut self,
15982 _: &actions::ToggleFoldRecursive,
15983 window: &mut Window,
15984 cx: &mut Context<Self>,
15985 ) {
15986 let selection = self.selections.newest::<Point>(cx);
15987
15988 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15989 let range = if selection.is_empty() {
15990 let point = selection.head().to_display_point(&display_map);
15991 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15992 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15993 .to_point(&display_map);
15994 start..end
15995 } else {
15996 selection.range()
15997 };
15998 if display_map.folds_in_range(range).next().is_some() {
15999 self.unfold_recursive(&Default::default(), window, cx)
16000 } else {
16001 self.fold_recursive(&Default::default(), window, cx)
16002 }
16003 }
16004
16005 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16006 if self.is_singleton(cx) {
16007 let mut to_fold = Vec::new();
16008 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16009 let selections = self.selections.all_adjusted(cx);
16010
16011 for selection in selections {
16012 let range = selection.range().sorted();
16013 let buffer_start_row = range.start.row;
16014
16015 if range.start.row != range.end.row {
16016 let mut found = false;
16017 let mut row = range.start.row;
16018 while row <= range.end.row {
16019 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16020 {
16021 found = true;
16022 row = crease.range().end.row + 1;
16023 to_fold.push(crease);
16024 } else {
16025 row += 1
16026 }
16027 }
16028 if found {
16029 continue;
16030 }
16031 }
16032
16033 for row in (0..=range.start.row).rev() {
16034 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16035 if crease.range().end.row >= buffer_start_row {
16036 to_fold.push(crease);
16037 if row <= range.start.row {
16038 break;
16039 }
16040 }
16041 }
16042 }
16043 }
16044
16045 self.fold_creases(to_fold, true, window, cx);
16046 } else {
16047 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16048 let buffer_ids = self
16049 .selections
16050 .disjoint_anchor_ranges()
16051 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16052 .collect::<HashSet<_>>();
16053 for buffer_id in buffer_ids {
16054 self.fold_buffer(buffer_id, cx);
16055 }
16056 }
16057 }
16058
16059 fn fold_at_level(
16060 &mut self,
16061 fold_at: &FoldAtLevel,
16062 window: &mut Window,
16063 cx: &mut Context<Self>,
16064 ) {
16065 if !self.buffer.read(cx).is_singleton() {
16066 return;
16067 }
16068
16069 let fold_at_level = fold_at.0;
16070 let snapshot = self.buffer.read(cx).snapshot(cx);
16071 let mut to_fold = Vec::new();
16072 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16073
16074 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16075 while start_row < end_row {
16076 match self
16077 .snapshot(window, cx)
16078 .crease_for_buffer_row(MultiBufferRow(start_row))
16079 {
16080 Some(crease) => {
16081 let nested_start_row = crease.range().start.row + 1;
16082 let nested_end_row = crease.range().end.row;
16083
16084 if current_level < fold_at_level {
16085 stack.push((nested_start_row, nested_end_row, current_level + 1));
16086 } else if current_level == fold_at_level {
16087 to_fold.push(crease);
16088 }
16089
16090 start_row = nested_end_row + 1;
16091 }
16092 None => start_row += 1,
16093 }
16094 }
16095 }
16096
16097 self.fold_creases(to_fold, true, window, cx);
16098 }
16099
16100 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16101 if self.buffer.read(cx).is_singleton() {
16102 let mut fold_ranges = Vec::new();
16103 let snapshot = self.buffer.read(cx).snapshot(cx);
16104
16105 for row in 0..snapshot.max_row().0 {
16106 if let Some(foldable_range) = self
16107 .snapshot(window, cx)
16108 .crease_for_buffer_row(MultiBufferRow(row))
16109 {
16110 fold_ranges.push(foldable_range);
16111 }
16112 }
16113
16114 self.fold_creases(fold_ranges, true, window, cx);
16115 } else {
16116 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16117 editor
16118 .update_in(cx, |editor, _, cx| {
16119 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16120 editor.fold_buffer(buffer_id, cx);
16121 }
16122 })
16123 .ok();
16124 });
16125 }
16126 }
16127
16128 pub fn fold_function_bodies(
16129 &mut self,
16130 _: &actions::FoldFunctionBodies,
16131 window: &mut Window,
16132 cx: &mut Context<Self>,
16133 ) {
16134 let snapshot = self.buffer.read(cx).snapshot(cx);
16135
16136 let ranges = snapshot
16137 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16138 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16139 .collect::<Vec<_>>();
16140
16141 let creases = ranges
16142 .into_iter()
16143 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16144 .collect();
16145
16146 self.fold_creases(creases, true, window, cx);
16147 }
16148
16149 pub fn fold_recursive(
16150 &mut self,
16151 _: &actions::FoldRecursive,
16152 window: &mut Window,
16153 cx: &mut Context<Self>,
16154 ) {
16155 let mut to_fold = Vec::new();
16156 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16157 let selections = self.selections.all_adjusted(cx);
16158
16159 for selection in selections {
16160 let range = selection.range().sorted();
16161 let buffer_start_row = range.start.row;
16162
16163 if range.start.row != range.end.row {
16164 let mut found = false;
16165 for row in range.start.row..=range.end.row {
16166 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16167 found = true;
16168 to_fold.push(crease);
16169 }
16170 }
16171 if found {
16172 continue;
16173 }
16174 }
16175
16176 for row in (0..=range.start.row).rev() {
16177 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16178 if crease.range().end.row >= buffer_start_row {
16179 to_fold.push(crease);
16180 } else {
16181 break;
16182 }
16183 }
16184 }
16185 }
16186
16187 self.fold_creases(to_fold, true, window, cx);
16188 }
16189
16190 pub fn fold_at(
16191 &mut self,
16192 buffer_row: MultiBufferRow,
16193 window: &mut Window,
16194 cx: &mut Context<Self>,
16195 ) {
16196 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16197
16198 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16199 let autoscroll = self
16200 .selections
16201 .all::<Point>(cx)
16202 .iter()
16203 .any(|selection| crease.range().overlaps(&selection.range()));
16204
16205 self.fold_creases(vec![crease], autoscroll, window, cx);
16206 }
16207 }
16208
16209 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16210 if self.is_singleton(cx) {
16211 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16212 let buffer = &display_map.buffer_snapshot;
16213 let selections = self.selections.all::<Point>(cx);
16214 let ranges = selections
16215 .iter()
16216 .map(|s| {
16217 let range = s.display_range(&display_map).sorted();
16218 let mut start = range.start.to_point(&display_map);
16219 let mut end = range.end.to_point(&display_map);
16220 start.column = 0;
16221 end.column = buffer.line_len(MultiBufferRow(end.row));
16222 start..end
16223 })
16224 .collect::<Vec<_>>();
16225
16226 self.unfold_ranges(&ranges, true, true, cx);
16227 } else {
16228 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16229 let buffer_ids = self
16230 .selections
16231 .disjoint_anchor_ranges()
16232 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16233 .collect::<HashSet<_>>();
16234 for buffer_id in buffer_ids {
16235 self.unfold_buffer(buffer_id, cx);
16236 }
16237 }
16238 }
16239
16240 pub fn unfold_recursive(
16241 &mut self,
16242 _: &UnfoldRecursive,
16243 _window: &mut Window,
16244 cx: &mut Context<Self>,
16245 ) {
16246 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16247 let selections = self.selections.all::<Point>(cx);
16248 let ranges = selections
16249 .iter()
16250 .map(|s| {
16251 let mut range = s.display_range(&display_map).sorted();
16252 *range.start.column_mut() = 0;
16253 *range.end.column_mut() = display_map.line_len(range.end.row());
16254 let start = range.start.to_point(&display_map);
16255 let end = range.end.to_point(&display_map);
16256 start..end
16257 })
16258 .collect::<Vec<_>>();
16259
16260 self.unfold_ranges(&ranges, true, true, cx);
16261 }
16262
16263 pub fn unfold_at(
16264 &mut self,
16265 buffer_row: MultiBufferRow,
16266 _window: &mut Window,
16267 cx: &mut Context<Self>,
16268 ) {
16269 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16270
16271 let intersection_range = Point::new(buffer_row.0, 0)
16272 ..Point::new(
16273 buffer_row.0,
16274 display_map.buffer_snapshot.line_len(buffer_row),
16275 );
16276
16277 let autoscroll = self
16278 .selections
16279 .all::<Point>(cx)
16280 .iter()
16281 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16282
16283 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16284 }
16285
16286 pub fn unfold_all(
16287 &mut self,
16288 _: &actions::UnfoldAll,
16289 _window: &mut Window,
16290 cx: &mut Context<Self>,
16291 ) {
16292 if self.buffer.read(cx).is_singleton() {
16293 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16294 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16295 } else {
16296 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16297 editor
16298 .update(cx, |editor, cx| {
16299 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16300 editor.unfold_buffer(buffer_id, cx);
16301 }
16302 })
16303 .ok();
16304 });
16305 }
16306 }
16307
16308 pub fn fold_selected_ranges(
16309 &mut self,
16310 _: &FoldSelectedRanges,
16311 window: &mut Window,
16312 cx: &mut Context<Self>,
16313 ) {
16314 let selections = self.selections.all_adjusted(cx);
16315 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16316 let ranges = selections
16317 .into_iter()
16318 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16319 .collect::<Vec<_>>();
16320 self.fold_creases(ranges, true, window, cx);
16321 }
16322
16323 pub fn fold_ranges<T: ToOffset + Clone>(
16324 &mut self,
16325 ranges: Vec<Range<T>>,
16326 auto_scroll: bool,
16327 window: &mut Window,
16328 cx: &mut Context<Self>,
16329 ) {
16330 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16331 let ranges = ranges
16332 .into_iter()
16333 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16334 .collect::<Vec<_>>();
16335 self.fold_creases(ranges, auto_scroll, window, cx);
16336 }
16337
16338 pub fn fold_creases<T: ToOffset + Clone>(
16339 &mut self,
16340 creases: Vec<Crease<T>>,
16341 auto_scroll: bool,
16342 _window: &mut Window,
16343 cx: &mut Context<Self>,
16344 ) {
16345 if creases.is_empty() {
16346 return;
16347 }
16348
16349 let mut buffers_affected = HashSet::default();
16350 let multi_buffer = self.buffer().read(cx);
16351 for crease in &creases {
16352 if let Some((_, buffer, _)) =
16353 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16354 {
16355 buffers_affected.insert(buffer.read(cx).remote_id());
16356 };
16357 }
16358
16359 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16360
16361 if auto_scroll {
16362 self.request_autoscroll(Autoscroll::fit(), cx);
16363 }
16364
16365 cx.notify();
16366
16367 self.scrollbar_marker_state.dirty = true;
16368 self.folds_did_change(cx);
16369 }
16370
16371 /// Removes any folds whose ranges intersect any of the given ranges.
16372 pub fn unfold_ranges<T: ToOffset + Clone>(
16373 &mut self,
16374 ranges: &[Range<T>],
16375 inclusive: bool,
16376 auto_scroll: bool,
16377 cx: &mut Context<Self>,
16378 ) {
16379 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16380 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16381 });
16382 self.folds_did_change(cx);
16383 }
16384
16385 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16386 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16387 return;
16388 }
16389 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16390 self.display_map.update(cx, |display_map, cx| {
16391 display_map.fold_buffers([buffer_id], cx)
16392 });
16393 cx.emit(EditorEvent::BufferFoldToggled {
16394 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16395 folded: true,
16396 });
16397 cx.notify();
16398 }
16399
16400 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16401 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16402 return;
16403 }
16404 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16405 self.display_map.update(cx, |display_map, cx| {
16406 display_map.unfold_buffers([buffer_id], cx);
16407 });
16408 cx.emit(EditorEvent::BufferFoldToggled {
16409 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16410 folded: false,
16411 });
16412 cx.notify();
16413 }
16414
16415 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16416 self.display_map.read(cx).is_buffer_folded(buffer)
16417 }
16418
16419 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16420 self.display_map.read(cx).folded_buffers()
16421 }
16422
16423 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16424 self.display_map.update(cx, |display_map, cx| {
16425 display_map.disable_header_for_buffer(buffer_id, cx);
16426 });
16427 cx.notify();
16428 }
16429
16430 /// Removes any folds with the given ranges.
16431 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16432 &mut self,
16433 ranges: &[Range<T>],
16434 type_id: TypeId,
16435 auto_scroll: bool,
16436 cx: &mut Context<Self>,
16437 ) {
16438 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16439 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16440 });
16441 self.folds_did_change(cx);
16442 }
16443
16444 fn remove_folds_with<T: ToOffset + Clone>(
16445 &mut self,
16446 ranges: &[Range<T>],
16447 auto_scroll: bool,
16448 cx: &mut Context<Self>,
16449 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16450 ) {
16451 if ranges.is_empty() {
16452 return;
16453 }
16454
16455 let mut buffers_affected = HashSet::default();
16456 let multi_buffer = self.buffer().read(cx);
16457 for range in ranges {
16458 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16459 buffers_affected.insert(buffer.read(cx).remote_id());
16460 };
16461 }
16462
16463 self.display_map.update(cx, update);
16464
16465 if auto_scroll {
16466 self.request_autoscroll(Autoscroll::fit(), cx);
16467 }
16468
16469 cx.notify();
16470 self.scrollbar_marker_state.dirty = true;
16471 self.active_indent_guides_state.dirty = true;
16472 }
16473
16474 pub fn update_fold_widths(
16475 &mut self,
16476 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16477 cx: &mut Context<Self>,
16478 ) -> bool {
16479 self.display_map
16480 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16481 }
16482
16483 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16484 self.display_map.read(cx).fold_placeholder.clone()
16485 }
16486
16487 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16488 self.buffer.update(cx, |buffer, cx| {
16489 buffer.set_all_diff_hunks_expanded(cx);
16490 });
16491 }
16492
16493 pub fn expand_all_diff_hunks(
16494 &mut self,
16495 _: &ExpandAllDiffHunks,
16496 _window: &mut Window,
16497 cx: &mut Context<Self>,
16498 ) {
16499 self.buffer.update(cx, |buffer, cx| {
16500 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16501 });
16502 }
16503
16504 pub fn toggle_selected_diff_hunks(
16505 &mut self,
16506 _: &ToggleSelectedDiffHunks,
16507 _window: &mut Window,
16508 cx: &mut Context<Self>,
16509 ) {
16510 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16511 self.toggle_diff_hunks_in_ranges(ranges, cx);
16512 }
16513
16514 pub fn diff_hunks_in_ranges<'a>(
16515 &'a self,
16516 ranges: &'a [Range<Anchor>],
16517 buffer: &'a MultiBufferSnapshot,
16518 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16519 ranges.iter().flat_map(move |range| {
16520 let end_excerpt_id = range.end.excerpt_id;
16521 let range = range.to_point(buffer);
16522 let mut peek_end = range.end;
16523 if range.end.row < buffer.max_row().0 {
16524 peek_end = Point::new(range.end.row + 1, 0);
16525 }
16526 buffer
16527 .diff_hunks_in_range(range.start..peek_end)
16528 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16529 })
16530 }
16531
16532 pub fn has_stageable_diff_hunks_in_ranges(
16533 &self,
16534 ranges: &[Range<Anchor>],
16535 snapshot: &MultiBufferSnapshot,
16536 ) -> bool {
16537 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16538 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16539 }
16540
16541 pub fn toggle_staged_selected_diff_hunks(
16542 &mut self,
16543 _: &::git::ToggleStaged,
16544 _: &mut Window,
16545 cx: &mut Context<Self>,
16546 ) {
16547 let snapshot = self.buffer.read(cx).snapshot(cx);
16548 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16549 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16550 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16551 }
16552
16553 pub fn set_render_diff_hunk_controls(
16554 &mut self,
16555 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16556 cx: &mut Context<Self>,
16557 ) {
16558 self.render_diff_hunk_controls = render_diff_hunk_controls;
16559 cx.notify();
16560 }
16561
16562 pub fn stage_and_next(
16563 &mut self,
16564 _: &::git::StageAndNext,
16565 window: &mut Window,
16566 cx: &mut Context<Self>,
16567 ) {
16568 self.do_stage_or_unstage_and_next(true, window, cx);
16569 }
16570
16571 pub fn unstage_and_next(
16572 &mut self,
16573 _: &::git::UnstageAndNext,
16574 window: &mut Window,
16575 cx: &mut Context<Self>,
16576 ) {
16577 self.do_stage_or_unstage_and_next(false, window, cx);
16578 }
16579
16580 pub fn stage_or_unstage_diff_hunks(
16581 &mut self,
16582 stage: bool,
16583 ranges: Vec<Range<Anchor>>,
16584 cx: &mut Context<Self>,
16585 ) {
16586 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16587 cx.spawn(async move |this, cx| {
16588 task.await?;
16589 this.update(cx, |this, cx| {
16590 let snapshot = this.buffer.read(cx).snapshot(cx);
16591 let chunk_by = this
16592 .diff_hunks_in_ranges(&ranges, &snapshot)
16593 .chunk_by(|hunk| hunk.buffer_id);
16594 for (buffer_id, hunks) in &chunk_by {
16595 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16596 }
16597 })
16598 })
16599 .detach_and_log_err(cx);
16600 }
16601
16602 fn save_buffers_for_ranges_if_needed(
16603 &mut self,
16604 ranges: &[Range<Anchor>],
16605 cx: &mut Context<Editor>,
16606 ) -> Task<Result<()>> {
16607 let multibuffer = self.buffer.read(cx);
16608 let snapshot = multibuffer.read(cx);
16609 let buffer_ids: HashSet<_> = ranges
16610 .iter()
16611 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16612 .collect();
16613 drop(snapshot);
16614
16615 let mut buffers = HashSet::default();
16616 for buffer_id in buffer_ids {
16617 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16618 let buffer = buffer_entity.read(cx);
16619 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16620 {
16621 buffers.insert(buffer_entity);
16622 }
16623 }
16624 }
16625
16626 if let Some(project) = &self.project {
16627 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16628 } else {
16629 Task::ready(Ok(()))
16630 }
16631 }
16632
16633 fn do_stage_or_unstage_and_next(
16634 &mut self,
16635 stage: bool,
16636 window: &mut Window,
16637 cx: &mut Context<Self>,
16638 ) {
16639 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16640
16641 if ranges.iter().any(|range| range.start != range.end) {
16642 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16643 return;
16644 }
16645
16646 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16647 let snapshot = self.snapshot(window, cx);
16648 let position = self.selections.newest::<Point>(cx).head();
16649 let mut row = snapshot
16650 .buffer_snapshot
16651 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16652 .find(|hunk| hunk.row_range.start.0 > position.row)
16653 .map(|hunk| hunk.row_range.start);
16654
16655 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16656 // Outside of the project diff editor, wrap around to the beginning.
16657 if !all_diff_hunks_expanded {
16658 row = row.or_else(|| {
16659 snapshot
16660 .buffer_snapshot
16661 .diff_hunks_in_range(Point::zero()..position)
16662 .find(|hunk| hunk.row_range.end.0 < position.row)
16663 .map(|hunk| hunk.row_range.start)
16664 });
16665 }
16666
16667 if let Some(row) = row {
16668 let destination = Point::new(row.0, 0);
16669 let autoscroll = Autoscroll::center();
16670
16671 self.unfold_ranges(&[destination..destination], false, false, cx);
16672 self.change_selections(Some(autoscroll), window, cx, |s| {
16673 s.select_ranges([destination..destination]);
16674 });
16675 }
16676 }
16677
16678 fn do_stage_or_unstage(
16679 &self,
16680 stage: bool,
16681 buffer_id: BufferId,
16682 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16683 cx: &mut App,
16684 ) -> Option<()> {
16685 let project = self.project.as_ref()?;
16686 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16687 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16688 let buffer_snapshot = buffer.read(cx).snapshot();
16689 let file_exists = buffer_snapshot
16690 .file()
16691 .is_some_and(|file| file.disk_state().exists());
16692 diff.update(cx, |diff, cx| {
16693 diff.stage_or_unstage_hunks(
16694 stage,
16695 &hunks
16696 .map(|hunk| buffer_diff::DiffHunk {
16697 buffer_range: hunk.buffer_range,
16698 diff_base_byte_range: hunk.diff_base_byte_range,
16699 secondary_status: hunk.secondary_status,
16700 range: Point::zero()..Point::zero(), // unused
16701 })
16702 .collect::<Vec<_>>(),
16703 &buffer_snapshot,
16704 file_exists,
16705 cx,
16706 )
16707 });
16708 None
16709 }
16710
16711 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16712 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16713 self.buffer
16714 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16715 }
16716
16717 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16718 self.buffer.update(cx, |buffer, cx| {
16719 let ranges = vec![Anchor::min()..Anchor::max()];
16720 if !buffer.all_diff_hunks_expanded()
16721 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16722 {
16723 buffer.collapse_diff_hunks(ranges, cx);
16724 true
16725 } else {
16726 false
16727 }
16728 })
16729 }
16730
16731 fn toggle_diff_hunks_in_ranges(
16732 &mut self,
16733 ranges: Vec<Range<Anchor>>,
16734 cx: &mut Context<Editor>,
16735 ) {
16736 self.buffer.update(cx, |buffer, cx| {
16737 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16738 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16739 })
16740 }
16741
16742 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16743 self.buffer.update(cx, |buffer, cx| {
16744 let snapshot = buffer.snapshot(cx);
16745 let excerpt_id = range.end.excerpt_id;
16746 let point_range = range.to_point(&snapshot);
16747 let expand = !buffer.single_hunk_is_expanded(range, cx);
16748 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16749 })
16750 }
16751
16752 pub(crate) fn apply_all_diff_hunks(
16753 &mut self,
16754 _: &ApplyAllDiffHunks,
16755 window: &mut Window,
16756 cx: &mut Context<Self>,
16757 ) {
16758 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16759
16760 let buffers = self.buffer.read(cx).all_buffers();
16761 for branch_buffer in buffers {
16762 branch_buffer.update(cx, |branch_buffer, cx| {
16763 branch_buffer.merge_into_base(Vec::new(), cx);
16764 });
16765 }
16766
16767 if let Some(project) = self.project.clone() {
16768 self.save(true, project, window, cx).detach_and_log_err(cx);
16769 }
16770 }
16771
16772 pub(crate) fn apply_selected_diff_hunks(
16773 &mut self,
16774 _: &ApplyDiffHunk,
16775 window: &mut Window,
16776 cx: &mut Context<Self>,
16777 ) {
16778 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16779 let snapshot = self.snapshot(window, cx);
16780 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16781 let mut ranges_by_buffer = HashMap::default();
16782 self.transact(window, cx, |editor, _window, cx| {
16783 for hunk in hunks {
16784 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16785 ranges_by_buffer
16786 .entry(buffer.clone())
16787 .or_insert_with(Vec::new)
16788 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16789 }
16790 }
16791
16792 for (buffer, ranges) in ranges_by_buffer {
16793 buffer.update(cx, |buffer, cx| {
16794 buffer.merge_into_base(ranges, cx);
16795 });
16796 }
16797 });
16798
16799 if let Some(project) = self.project.clone() {
16800 self.save(true, project, window, cx).detach_and_log_err(cx);
16801 }
16802 }
16803
16804 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16805 if hovered != self.gutter_hovered {
16806 self.gutter_hovered = hovered;
16807 cx.notify();
16808 }
16809 }
16810
16811 pub fn insert_blocks(
16812 &mut self,
16813 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16814 autoscroll: Option<Autoscroll>,
16815 cx: &mut Context<Self>,
16816 ) -> Vec<CustomBlockId> {
16817 let blocks = self
16818 .display_map
16819 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16820 if let Some(autoscroll) = autoscroll {
16821 self.request_autoscroll(autoscroll, cx);
16822 }
16823 cx.notify();
16824 blocks
16825 }
16826
16827 pub fn resize_blocks(
16828 &mut self,
16829 heights: HashMap<CustomBlockId, u32>,
16830 autoscroll: Option<Autoscroll>,
16831 cx: &mut Context<Self>,
16832 ) {
16833 self.display_map
16834 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16835 if let Some(autoscroll) = autoscroll {
16836 self.request_autoscroll(autoscroll, cx);
16837 }
16838 cx.notify();
16839 }
16840
16841 pub fn replace_blocks(
16842 &mut self,
16843 renderers: HashMap<CustomBlockId, RenderBlock>,
16844 autoscroll: Option<Autoscroll>,
16845 cx: &mut Context<Self>,
16846 ) {
16847 self.display_map
16848 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16849 if let Some(autoscroll) = autoscroll {
16850 self.request_autoscroll(autoscroll, cx);
16851 }
16852 cx.notify();
16853 }
16854
16855 pub fn remove_blocks(
16856 &mut self,
16857 block_ids: HashSet<CustomBlockId>,
16858 autoscroll: Option<Autoscroll>,
16859 cx: &mut Context<Self>,
16860 ) {
16861 self.display_map.update(cx, |display_map, cx| {
16862 display_map.remove_blocks(block_ids, cx)
16863 });
16864 if let Some(autoscroll) = autoscroll {
16865 self.request_autoscroll(autoscroll, cx);
16866 }
16867 cx.notify();
16868 }
16869
16870 pub fn row_for_block(
16871 &self,
16872 block_id: CustomBlockId,
16873 cx: &mut Context<Self>,
16874 ) -> Option<DisplayRow> {
16875 self.display_map
16876 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16877 }
16878
16879 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16880 self.focused_block = Some(focused_block);
16881 }
16882
16883 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16884 self.focused_block.take()
16885 }
16886
16887 pub fn insert_creases(
16888 &mut self,
16889 creases: impl IntoIterator<Item = Crease<Anchor>>,
16890 cx: &mut Context<Self>,
16891 ) -> Vec<CreaseId> {
16892 self.display_map
16893 .update(cx, |map, cx| map.insert_creases(creases, cx))
16894 }
16895
16896 pub fn remove_creases(
16897 &mut self,
16898 ids: impl IntoIterator<Item = CreaseId>,
16899 cx: &mut Context<Self>,
16900 ) -> Vec<(CreaseId, Range<Anchor>)> {
16901 self.display_map
16902 .update(cx, |map, cx| map.remove_creases(ids, cx))
16903 }
16904
16905 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16906 self.display_map
16907 .update(cx, |map, cx| map.snapshot(cx))
16908 .longest_row()
16909 }
16910
16911 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16912 self.display_map
16913 .update(cx, |map, cx| map.snapshot(cx))
16914 .max_point()
16915 }
16916
16917 pub fn text(&self, cx: &App) -> String {
16918 self.buffer.read(cx).read(cx).text()
16919 }
16920
16921 pub fn is_empty(&self, cx: &App) -> bool {
16922 self.buffer.read(cx).read(cx).is_empty()
16923 }
16924
16925 pub fn text_option(&self, cx: &App) -> Option<String> {
16926 let text = self.text(cx);
16927 let text = text.trim();
16928
16929 if text.is_empty() {
16930 return None;
16931 }
16932
16933 Some(text.to_string())
16934 }
16935
16936 pub fn set_text(
16937 &mut self,
16938 text: impl Into<Arc<str>>,
16939 window: &mut Window,
16940 cx: &mut Context<Self>,
16941 ) {
16942 self.transact(window, cx, |this, _, cx| {
16943 this.buffer
16944 .read(cx)
16945 .as_singleton()
16946 .expect("you can only call set_text on editors for singleton buffers")
16947 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16948 });
16949 }
16950
16951 pub fn display_text(&self, cx: &mut App) -> String {
16952 self.display_map
16953 .update(cx, |map, cx| map.snapshot(cx))
16954 .text()
16955 }
16956
16957 fn create_minimap(
16958 &self,
16959 minimap_settings: MinimapSettings,
16960 window: &mut Window,
16961 cx: &mut Context<Self>,
16962 ) -> Option<Entity<Self>> {
16963 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
16964 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
16965 }
16966
16967 fn initialize_new_minimap(
16968 &self,
16969 minimap_settings: MinimapSettings,
16970 window: &mut Window,
16971 cx: &mut Context<Self>,
16972 ) -> Entity<Self> {
16973 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
16974
16975 let mut minimap = Editor::new_internal(
16976 EditorMode::Minimap {
16977 parent: cx.weak_entity(),
16978 },
16979 self.buffer.clone(),
16980 self.project.clone(),
16981 Some(self.display_map.clone()),
16982 window,
16983 cx,
16984 );
16985 minimap.scroll_manager.clone_state(&self.scroll_manager);
16986 minimap.set_text_style_refinement(TextStyleRefinement {
16987 font_size: Some(MINIMAP_FONT_SIZE),
16988 font_weight: Some(MINIMAP_FONT_WEIGHT),
16989 ..Default::default()
16990 });
16991 minimap.update_minimap_configuration(minimap_settings, cx);
16992 cx.new(|_| minimap)
16993 }
16994
16995 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
16996 let current_line_highlight = minimap_settings
16997 .current_line_highlight
16998 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
16999 self.set_current_line_highlight(Some(current_line_highlight));
17000 }
17001
17002 pub fn minimap(&self) -> Option<&Entity<Self>> {
17003 self.minimap
17004 .as_ref()
17005 .filter(|_| self.minimap_visibility.visible())
17006 }
17007
17008 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17009 let mut wrap_guides = smallvec![];
17010
17011 if self.show_wrap_guides == Some(false) {
17012 return wrap_guides;
17013 }
17014
17015 let settings = self.buffer.read(cx).language_settings(cx);
17016 if settings.show_wrap_guides {
17017 match self.soft_wrap_mode(cx) {
17018 SoftWrap::Column(soft_wrap) => {
17019 wrap_guides.push((soft_wrap as usize, true));
17020 }
17021 SoftWrap::Bounded(soft_wrap) => {
17022 wrap_guides.push((soft_wrap as usize, true));
17023 }
17024 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17025 }
17026 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17027 }
17028
17029 wrap_guides
17030 }
17031
17032 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17033 let settings = self.buffer.read(cx).language_settings(cx);
17034 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17035 match mode {
17036 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17037 SoftWrap::None
17038 }
17039 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17040 language_settings::SoftWrap::PreferredLineLength => {
17041 SoftWrap::Column(settings.preferred_line_length)
17042 }
17043 language_settings::SoftWrap::Bounded => {
17044 SoftWrap::Bounded(settings.preferred_line_length)
17045 }
17046 }
17047 }
17048
17049 pub fn set_soft_wrap_mode(
17050 &mut self,
17051 mode: language_settings::SoftWrap,
17052
17053 cx: &mut Context<Self>,
17054 ) {
17055 self.soft_wrap_mode_override = Some(mode);
17056 cx.notify();
17057 }
17058
17059 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17060 self.hard_wrap = hard_wrap;
17061 cx.notify();
17062 }
17063
17064 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17065 self.text_style_refinement = Some(style);
17066 }
17067
17068 /// called by the Element so we know what style we were most recently rendered with.
17069 pub(crate) fn set_style(
17070 &mut self,
17071 style: EditorStyle,
17072 window: &mut Window,
17073 cx: &mut Context<Self>,
17074 ) {
17075 // We intentionally do not inform the display map about the minimap style
17076 // so that wrapping is not recalculated and stays consistent for the editor
17077 // and its linked minimap.
17078 if !self.mode.is_minimap() {
17079 let rem_size = window.rem_size();
17080 self.display_map.update(cx, |map, cx| {
17081 map.set_font(
17082 style.text.font(),
17083 style.text.font_size.to_pixels(rem_size),
17084 cx,
17085 )
17086 });
17087 }
17088 self.style = Some(style);
17089 }
17090
17091 pub fn style(&self) -> Option<&EditorStyle> {
17092 self.style.as_ref()
17093 }
17094
17095 // Called by the element. This method is not designed to be called outside of the editor
17096 // element's layout code because it does not notify when rewrapping is computed synchronously.
17097 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17098 self.display_map
17099 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17100 }
17101
17102 pub fn set_soft_wrap(&mut self) {
17103 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17104 }
17105
17106 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17107 if self.soft_wrap_mode_override.is_some() {
17108 self.soft_wrap_mode_override.take();
17109 } else {
17110 let soft_wrap = match self.soft_wrap_mode(cx) {
17111 SoftWrap::GitDiff => return,
17112 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17113 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17114 language_settings::SoftWrap::None
17115 }
17116 };
17117 self.soft_wrap_mode_override = Some(soft_wrap);
17118 }
17119 cx.notify();
17120 }
17121
17122 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17123 let Some(workspace) = self.workspace() else {
17124 return;
17125 };
17126 let fs = workspace.read(cx).app_state().fs.clone();
17127 let current_show = TabBarSettings::get_global(cx).show;
17128 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17129 setting.show = Some(!current_show);
17130 });
17131 }
17132
17133 pub fn toggle_indent_guides(
17134 &mut self,
17135 _: &ToggleIndentGuides,
17136 _: &mut Window,
17137 cx: &mut Context<Self>,
17138 ) {
17139 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17140 self.buffer
17141 .read(cx)
17142 .language_settings(cx)
17143 .indent_guides
17144 .enabled
17145 });
17146 self.show_indent_guides = Some(!currently_enabled);
17147 cx.notify();
17148 }
17149
17150 fn should_show_indent_guides(&self) -> Option<bool> {
17151 self.show_indent_guides
17152 }
17153
17154 pub fn toggle_line_numbers(
17155 &mut self,
17156 _: &ToggleLineNumbers,
17157 _: &mut Window,
17158 cx: &mut Context<Self>,
17159 ) {
17160 let mut editor_settings = EditorSettings::get_global(cx).clone();
17161 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17162 EditorSettings::override_global(editor_settings, cx);
17163 }
17164
17165 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17166 if let Some(show_line_numbers) = self.show_line_numbers {
17167 return show_line_numbers;
17168 }
17169 EditorSettings::get_global(cx).gutter.line_numbers
17170 }
17171
17172 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17173 self.use_relative_line_numbers
17174 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17175 }
17176
17177 pub fn toggle_relative_line_numbers(
17178 &mut self,
17179 _: &ToggleRelativeLineNumbers,
17180 _: &mut Window,
17181 cx: &mut Context<Self>,
17182 ) {
17183 let is_relative = self.should_use_relative_line_numbers(cx);
17184 self.set_relative_line_number(Some(!is_relative), cx)
17185 }
17186
17187 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17188 self.use_relative_line_numbers = is_relative;
17189 cx.notify();
17190 }
17191
17192 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17193 self.show_gutter = show_gutter;
17194 cx.notify();
17195 }
17196
17197 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17198 self.show_scrollbars = ScrollbarAxes {
17199 horizontal: show,
17200 vertical: show,
17201 };
17202 cx.notify();
17203 }
17204
17205 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17206 self.show_scrollbars.vertical = show;
17207 cx.notify();
17208 }
17209
17210 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17211 self.show_scrollbars.horizontal = show;
17212 cx.notify();
17213 }
17214
17215 pub fn set_minimap_visibility(
17216 &mut self,
17217 minimap_visibility: MinimapVisibility,
17218 window: &mut Window,
17219 cx: &mut Context<Self>,
17220 ) {
17221 if self.minimap_visibility != minimap_visibility {
17222 if minimap_visibility.visible() && self.minimap.is_none() {
17223 let minimap_settings = EditorSettings::get_global(cx).minimap;
17224 self.minimap =
17225 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17226 }
17227 self.minimap_visibility = minimap_visibility;
17228 cx.notify();
17229 }
17230 }
17231
17232 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17233 self.set_show_scrollbars(false, cx);
17234 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17235 }
17236
17237 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17238 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17239 }
17240
17241 /// Normally the text in full mode and auto height editors is padded on the
17242 /// left side by roughly half a character width for improved hit testing.
17243 ///
17244 /// Use this method to disable this for cases where this is not wanted (e.g.
17245 /// if you want to align the editor text with some other text above or below)
17246 /// or if you want to add this padding to single-line editors.
17247 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17248 self.offset_content = offset_content;
17249 cx.notify();
17250 }
17251
17252 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17253 self.show_line_numbers = Some(show_line_numbers);
17254 cx.notify();
17255 }
17256
17257 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17258 self.disable_expand_excerpt_buttons = true;
17259 cx.notify();
17260 }
17261
17262 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17263 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17264 cx.notify();
17265 }
17266
17267 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17268 self.show_code_actions = Some(show_code_actions);
17269 cx.notify();
17270 }
17271
17272 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17273 self.show_runnables = Some(show_runnables);
17274 cx.notify();
17275 }
17276
17277 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17278 self.show_breakpoints = Some(show_breakpoints);
17279 cx.notify();
17280 }
17281
17282 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17283 if self.display_map.read(cx).masked != masked {
17284 self.display_map.update(cx, |map, _| map.masked = masked);
17285 }
17286 cx.notify()
17287 }
17288
17289 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17290 self.show_wrap_guides = Some(show_wrap_guides);
17291 cx.notify();
17292 }
17293
17294 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17295 self.show_indent_guides = Some(show_indent_guides);
17296 cx.notify();
17297 }
17298
17299 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17300 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17301 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17302 if let Some(dir) = file.abs_path(cx).parent() {
17303 return Some(dir.to_owned());
17304 }
17305 }
17306
17307 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17308 return Some(project_path.path.to_path_buf());
17309 }
17310 }
17311
17312 None
17313 }
17314
17315 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17316 self.active_excerpt(cx)?
17317 .1
17318 .read(cx)
17319 .file()
17320 .and_then(|f| f.as_local())
17321 }
17322
17323 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17324 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17325 let buffer = buffer.read(cx);
17326 if let Some(project_path) = buffer.project_path(cx) {
17327 let project = self.project.as_ref()?.read(cx);
17328 project.absolute_path(&project_path, cx)
17329 } else {
17330 buffer
17331 .file()
17332 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17333 }
17334 })
17335 }
17336
17337 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17338 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17339 let project_path = buffer.read(cx).project_path(cx)?;
17340 let project = self.project.as_ref()?.read(cx);
17341 let entry = project.entry_for_path(&project_path, cx)?;
17342 let path = entry.path.to_path_buf();
17343 Some(path)
17344 })
17345 }
17346
17347 pub fn reveal_in_finder(
17348 &mut self,
17349 _: &RevealInFileManager,
17350 _window: &mut Window,
17351 cx: &mut Context<Self>,
17352 ) {
17353 if let Some(target) = self.target_file(cx) {
17354 cx.reveal_path(&target.abs_path(cx));
17355 }
17356 }
17357
17358 pub fn copy_path(
17359 &mut self,
17360 _: &zed_actions::workspace::CopyPath,
17361 _window: &mut Window,
17362 cx: &mut Context<Self>,
17363 ) {
17364 if let Some(path) = self.target_file_abs_path(cx) {
17365 if let Some(path) = path.to_str() {
17366 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17367 }
17368 }
17369 }
17370
17371 pub fn copy_relative_path(
17372 &mut self,
17373 _: &zed_actions::workspace::CopyRelativePath,
17374 _window: &mut Window,
17375 cx: &mut Context<Self>,
17376 ) {
17377 if let Some(path) = self.target_file_path(cx) {
17378 if let Some(path) = path.to_str() {
17379 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17380 }
17381 }
17382 }
17383
17384 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17385 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17386 buffer.read(cx).project_path(cx)
17387 } else {
17388 None
17389 }
17390 }
17391
17392 // Returns true if the editor handled a go-to-line request
17393 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17394 maybe!({
17395 let breakpoint_store = self.breakpoint_store.as_ref()?;
17396
17397 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17398 else {
17399 self.clear_row_highlights::<ActiveDebugLine>();
17400 return None;
17401 };
17402
17403 let position = active_stack_frame.position;
17404 let buffer_id = position.buffer_id?;
17405 let snapshot = self
17406 .project
17407 .as_ref()?
17408 .read(cx)
17409 .buffer_for_id(buffer_id, cx)?
17410 .read(cx)
17411 .snapshot();
17412
17413 let mut handled = false;
17414 for (id, ExcerptRange { context, .. }) in
17415 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17416 {
17417 if context.start.cmp(&position, &snapshot).is_ge()
17418 || context.end.cmp(&position, &snapshot).is_lt()
17419 {
17420 continue;
17421 }
17422 let snapshot = self.buffer.read(cx).snapshot(cx);
17423 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17424
17425 handled = true;
17426 self.clear_row_highlights::<ActiveDebugLine>();
17427
17428 self.go_to_line::<ActiveDebugLine>(
17429 multibuffer_anchor,
17430 Some(cx.theme().colors().editor_debugger_active_line_background),
17431 window,
17432 cx,
17433 );
17434
17435 cx.notify();
17436 }
17437
17438 handled.then_some(())
17439 })
17440 .is_some()
17441 }
17442
17443 pub fn copy_file_name_without_extension(
17444 &mut self,
17445 _: &CopyFileNameWithoutExtension,
17446 _: &mut Window,
17447 cx: &mut Context<Self>,
17448 ) {
17449 if let Some(file) = self.target_file(cx) {
17450 if let Some(file_stem) = file.path().file_stem() {
17451 if let Some(name) = file_stem.to_str() {
17452 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17453 }
17454 }
17455 }
17456 }
17457
17458 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17459 if let Some(file) = self.target_file(cx) {
17460 if let Some(file_name) = file.path().file_name() {
17461 if let Some(name) = file_name.to_str() {
17462 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17463 }
17464 }
17465 }
17466 }
17467
17468 pub fn toggle_git_blame(
17469 &mut self,
17470 _: &::git::Blame,
17471 window: &mut Window,
17472 cx: &mut Context<Self>,
17473 ) {
17474 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17475
17476 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17477 self.start_git_blame(true, window, cx);
17478 }
17479
17480 cx.notify();
17481 }
17482
17483 pub fn toggle_git_blame_inline(
17484 &mut self,
17485 _: &ToggleGitBlameInline,
17486 window: &mut Window,
17487 cx: &mut Context<Self>,
17488 ) {
17489 self.toggle_git_blame_inline_internal(true, window, cx);
17490 cx.notify();
17491 }
17492
17493 pub fn open_git_blame_commit(
17494 &mut self,
17495 _: &OpenGitBlameCommit,
17496 window: &mut Window,
17497 cx: &mut Context<Self>,
17498 ) {
17499 self.open_git_blame_commit_internal(window, cx);
17500 }
17501
17502 fn open_git_blame_commit_internal(
17503 &mut self,
17504 window: &mut Window,
17505 cx: &mut Context<Self>,
17506 ) -> Option<()> {
17507 let blame = self.blame.as_ref()?;
17508 let snapshot = self.snapshot(window, cx);
17509 let cursor = self.selections.newest::<Point>(cx).head();
17510 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17511 let blame_entry = blame
17512 .update(cx, |blame, cx| {
17513 blame
17514 .blame_for_rows(
17515 &[RowInfo {
17516 buffer_id: Some(buffer.remote_id()),
17517 buffer_row: Some(point.row),
17518 ..Default::default()
17519 }],
17520 cx,
17521 )
17522 .next()
17523 })
17524 .flatten()?;
17525 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17526 let repo = blame.read(cx).repository(cx)?;
17527 let workspace = self.workspace()?.downgrade();
17528 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17529 None
17530 }
17531
17532 pub fn git_blame_inline_enabled(&self) -> bool {
17533 self.git_blame_inline_enabled
17534 }
17535
17536 pub fn toggle_selection_menu(
17537 &mut self,
17538 _: &ToggleSelectionMenu,
17539 _: &mut Window,
17540 cx: &mut Context<Self>,
17541 ) {
17542 self.show_selection_menu = self
17543 .show_selection_menu
17544 .map(|show_selections_menu| !show_selections_menu)
17545 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17546
17547 cx.notify();
17548 }
17549
17550 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17551 self.show_selection_menu
17552 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17553 }
17554
17555 fn start_git_blame(
17556 &mut self,
17557 user_triggered: bool,
17558 window: &mut Window,
17559 cx: &mut Context<Self>,
17560 ) {
17561 if let Some(project) = self.project.as_ref() {
17562 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17563 return;
17564 };
17565
17566 if buffer.read(cx).file().is_none() {
17567 return;
17568 }
17569
17570 let focused = self.focus_handle(cx).contains_focused(window, cx);
17571
17572 let project = project.clone();
17573 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17574 self.blame_subscription =
17575 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17576 self.blame = Some(blame);
17577 }
17578 }
17579
17580 fn toggle_git_blame_inline_internal(
17581 &mut self,
17582 user_triggered: bool,
17583 window: &mut Window,
17584 cx: &mut Context<Self>,
17585 ) {
17586 if self.git_blame_inline_enabled {
17587 self.git_blame_inline_enabled = false;
17588 self.show_git_blame_inline = false;
17589 self.show_git_blame_inline_delay_task.take();
17590 } else {
17591 self.git_blame_inline_enabled = true;
17592 self.start_git_blame_inline(user_triggered, window, cx);
17593 }
17594
17595 cx.notify();
17596 }
17597
17598 fn start_git_blame_inline(
17599 &mut self,
17600 user_triggered: bool,
17601 window: &mut Window,
17602 cx: &mut Context<Self>,
17603 ) {
17604 self.start_git_blame(user_triggered, window, cx);
17605
17606 if ProjectSettings::get_global(cx)
17607 .git
17608 .inline_blame_delay()
17609 .is_some()
17610 {
17611 self.start_inline_blame_timer(window, cx);
17612 } else {
17613 self.show_git_blame_inline = true
17614 }
17615 }
17616
17617 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17618 self.blame.as_ref()
17619 }
17620
17621 pub fn show_git_blame_gutter(&self) -> bool {
17622 self.show_git_blame_gutter
17623 }
17624
17625 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17626 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17627 }
17628
17629 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17630 self.show_git_blame_inline
17631 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17632 && !self.newest_selection_head_on_empty_line(cx)
17633 && self.has_blame_entries(cx)
17634 }
17635
17636 fn has_blame_entries(&self, cx: &App) -> bool {
17637 self.blame()
17638 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17639 }
17640
17641 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17642 let cursor_anchor = self.selections.newest_anchor().head();
17643
17644 let snapshot = self.buffer.read(cx).snapshot(cx);
17645 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17646
17647 snapshot.line_len(buffer_row) == 0
17648 }
17649
17650 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17651 let buffer_and_selection = maybe!({
17652 let selection = self.selections.newest::<Point>(cx);
17653 let selection_range = selection.range();
17654
17655 let multi_buffer = self.buffer().read(cx);
17656 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17657 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17658
17659 let (buffer, range, _) = if selection.reversed {
17660 buffer_ranges.first()
17661 } else {
17662 buffer_ranges.last()
17663 }?;
17664
17665 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17666 ..text::ToPoint::to_point(&range.end, &buffer).row;
17667 Some((
17668 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17669 selection,
17670 ))
17671 });
17672
17673 let Some((buffer, selection)) = buffer_and_selection else {
17674 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17675 };
17676
17677 let Some(project) = self.project.as_ref() else {
17678 return Task::ready(Err(anyhow!("editor does not have project")));
17679 };
17680
17681 project.update(cx, |project, cx| {
17682 project.get_permalink_to_line(&buffer, selection, cx)
17683 })
17684 }
17685
17686 pub fn copy_permalink_to_line(
17687 &mut self,
17688 _: &CopyPermalinkToLine,
17689 window: &mut Window,
17690 cx: &mut Context<Self>,
17691 ) {
17692 let permalink_task = self.get_permalink_to_line(cx);
17693 let workspace = self.workspace();
17694
17695 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17696 Ok(permalink) => {
17697 cx.update(|_, cx| {
17698 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17699 })
17700 .ok();
17701 }
17702 Err(err) => {
17703 let message = format!("Failed to copy permalink: {err}");
17704
17705 anyhow::Result::<()>::Err(err).log_err();
17706
17707 if let Some(workspace) = workspace {
17708 workspace
17709 .update_in(cx, |workspace, _, cx| {
17710 struct CopyPermalinkToLine;
17711
17712 workspace.show_toast(
17713 Toast::new(
17714 NotificationId::unique::<CopyPermalinkToLine>(),
17715 message,
17716 ),
17717 cx,
17718 )
17719 })
17720 .ok();
17721 }
17722 }
17723 })
17724 .detach();
17725 }
17726
17727 pub fn copy_file_location(
17728 &mut self,
17729 _: &CopyFileLocation,
17730 _: &mut Window,
17731 cx: &mut Context<Self>,
17732 ) {
17733 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17734 if let Some(file) = self.target_file(cx) {
17735 if let Some(path) = file.path().to_str() {
17736 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17737 }
17738 }
17739 }
17740
17741 pub fn open_permalink_to_line(
17742 &mut self,
17743 _: &OpenPermalinkToLine,
17744 window: &mut Window,
17745 cx: &mut Context<Self>,
17746 ) {
17747 let permalink_task = self.get_permalink_to_line(cx);
17748 let workspace = self.workspace();
17749
17750 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17751 Ok(permalink) => {
17752 cx.update(|_, cx| {
17753 cx.open_url(permalink.as_ref());
17754 })
17755 .ok();
17756 }
17757 Err(err) => {
17758 let message = format!("Failed to open permalink: {err}");
17759
17760 anyhow::Result::<()>::Err(err).log_err();
17761
17762 if let Some(workspace) = workspace {
17763 workspace
17764 .update(cx, |workspace, cx| {
17765 struct OpenPermalinkToLine;
17766
17767 workspace.show_toast(
17768 Toast::new(
17769 NotificationId::unique::<OpenPermalinkToLine>(),
17770 message,
17771 ),
17772 cx,
17773 )
17774 })
17775 .ok();
17776 }
17777 }
17778 })
17779 .detach();
17780 }
17781
17782 pub fn insert_uuid_v4(
17783 &mut self,
17784 _: &InsertUuidV4,
17785 window: &mut Window,
17786 cx: &mut Context<Self>,
17787 ) {
17788 self.insert_uuid(UuidVersion::V4, window, cx);
17789 }
17790
17791 pub fn insert_uuid_v7(
17792 &mut self,
17793 _: &InsertUuidV7,
17794 window: &mut Window,
17795 cx: &mut Context<Self>,
17796 ) {
17797 self.insert_uuid(UuidVersion::V7, window, cx);
17798 }
17799
17800 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17801 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17802 self.transact(window, cx, |this, window, cx| {
17803 let edits = this
17804 .selections
17805 .all::<Point>(cx)
17806 .into_iter()
17807 .map(|selection| {
17808 let uuid = match version {
17809 UuidVersion::V4 => uuid::Uuid::new_v4(),
17810 UuidVersion::V7 => uuid::Uuid::now_v7(),
17811 };
17812
17813 (selection.range(), uuid.to_string())
17814 });
17815 this.edit(edits, cx);
17816 this.refresh_inline_completion(true, false, window, cx);
17817 });
17818 }
17819
17820 pub fn open_selections_in_multibuffer(
17821 &mut self,
17822 _: &OpenSelectionsInMultibuffer,
17823 window: &mut Window,
17824 cx: &mut Context<Self>,
17825 ) {
17826 let multibuffer = self.buffer.read(cx);
17827
17828 let Some(buffer) = multibuffer.as_singleton() else {
17829 return;
17830 };
17831
17832 let Some(workspace) = self.workspace() else {
17833 return;
17834 };
17835
17836 let locations = self
17837 .selections
17838 .disjoint_anchors()
17839 .iter()
17840 .map(|selection| {
17841 let range = if selection.reversed {
17842 selection.end.text_anchor..selection.start.text_anchor
17843 } else {
17844 selection.start.text_anchor..selection.end.text_anchor
17845 };
17846 Location {
17847 buffer: buffer.clone(),
17848 range,
17849 }
17850 })
17851 .collect::<Vec<_>>();
17852
17853 let title = multibuffer.title(cx).to_string();
17854
17855 cx.spawn_in(window, async move |_, cx| {
17856 workspace.update_in(cx, |workspace, window, cx| {
17857 Self::open_locations_in_multibuffer(
17858 workspace,
17859 locations,
17860 format!("Selections for '{title}'"),
17861 false,
17862 MultibufferSelectionMode::All,
17863 window,
17864 cx,
17865 );
17866 })
17867 })
17868 .detach();
17869 }
17870
17871 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17872 /// last highlight added will be used.
17873 ///
17874 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17875 pub fn highlight_rows<T: 'static>(
17876 &mut self,
17877 range: Range<Anchor>,
17878 color: Hsla,
17879 options: RowHighlightOptions,
17880 cx: &mut Context<Self>,
17881 ) {
17882 let snapshot = self.buffer().read(cx).snapshot(cx);
17883 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17884 let ix = row_highlights.binary_search_by(|highlight| {
17885 Ordering::Equal
17886 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17887 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17888 });
17889
17890 if let Err(mut ix) = ix {
17891 let index = post_inc(&mut self.highlight_order);
17892
17893 // If this range intersects with the preceding highlight, then merge it with
17894 // the preceding highlight. Otherwise insert a new highlight.
17895 let mut merged = false;
17896 if ix > 0 {
17897 let prev_highlight = &mut row_highlights[ix - 1];
17898 if prev_highlight
17899 .range
17900 .end
17901 .cmp(&range.start, &snapshot)
17902 .is_ge()
17903 {
17904 ix -= 1;
17905 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17906 prev_highlight.range.end = range.end;
17907 }
17908 merged = true;
17909 prev_highlight.index = index;
17910 prev_highlight.color = color;
17911 prev_highlight.options = options;
17912 }
17913 }
17914
17915 if !merged {
17916 row_highlights.insert(
17917 ix,
17918 RowHighlight {
17919 range: range.clone(),
17920 index,
17921 color,
17922 options,
17923 type_id: TypeId::of::<T>(),
17924 },
17925 );
17926 }
17927
17928 // If any of the following highlights intersect with this one, merge them.
17929 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17930 let highlight = &row_highlights[ix];
17931 if next_highlight
17932 .range
17933 .start
17934 .cmp(&highlight.range.end, &snapshot)
17935 .is_le()
17936 {
17937 if next_highlight
17938 .range
17939 .end
17940 .cmp(&highlight.range.end, &snapshot)
17941 .is_gt()
17942 {
17943 row_highlights[ix].range.end = next_highlight.range.end;
17944 }
17945 row_highlights.remove(ix + 1);
17946 } else {
17947 break;
17948 }
17949 }
17950 }
17951 }
17952
17953 /// Remove any highlighted row ranges of the given type that intersect the
17954 /// given ranges.
17955 pub fn remove_highlighted_rows<T: 'static>(
17956 &mut self,
17957 ranges_to_remove: Vec<Range<Anchor>>,
17958 cx: &mut Context<Self>,
17959 ) {
17960 let snapshot = self.buffer().read(cx).snapshot(cx);
17961 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17962 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17963 row_highlights.retain(|highlight| {
17964 while let Some(range_to_remove) = ranges_to_remove.peek() {
17965 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17966 Ordering::Less | Ordering::Equal => {
17967 ranges_to_remove.next();
17968 }
17969 Ordering::Greater => {
17970 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17971 Ordering::Less | Ordering::Equal => {
17972 return false;
17973 }
17974 Ordering::Greater => break,
17975 }
17976 }
17977 }
17978 }
17979
17980 true
17981 })
17982 }
17983
17984 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17985 pub fn clear_row_highlights<T: 'static>(&mut self) {
17986 self.highlighted_rows.remove(&TypeId::of::<T>());
17987 }
17988
17989 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17990 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17991 self.highlighted_rows
17992 .get(&TypeId::of::<T>())
17993 .map_or(&[] as &[_], |vec| vec.as_slice())
17994 .iter()
17995 .map(|highlight| (highlight.range.clone(), highlight.color))
17996 }
17997
17998 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17999 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18000 /// Allows to ignore certain kinds of highlights.
18001 pub fn highlighted_display_rows(
18002 &self,
18003 window: &mut Window,
18004 cx: &mut App,
18005 ) -> BTreeMap<DisplayRow, LineHighlight> {
18006 let snapshot = self.snapshot(window, cx);
18007 let mut used_highlight_orders = HashMap::default();
18008 self.highlighted_rows
18009 .iter()
18010 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18011 .fold(
18012 BTreeMap::<DisplayRow, LineHighlight>::new(),
18013 |mut unique_rows, highlight| {
18014 let start = highlight.range.start.to_display_point(&snapshot);
18015 let end = highlight.range.end.to_display_point(&snapshot);
18016 let start_row = start.row().0;
18017 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18018 && end.column() == 0
18019 {
18020 end.row().0.saturating_sub(1)
18021 } else {
18022 end.row().0
18023 };
18024 for row in start_row..=end_row {
18025 let used_index =
18026 used_highlight_orders.entry(row).or_insert(highlight.index);
18027 if highlight.index >= *used_index {
18028 *used_index = highlight.index;
18029 unique_rows.insert(
18030 DisplayRow(row),
18031 LineHighlight {
18032 include_gutter: highlight.options.include_gutter,
18033 border: None,
18034 background: highlight.color.into(),
18035 type_id: Some(highlight.type_id),
18036 },
18037 );
18038 }
18039 }
18040 unique_rows
18041 },
18042 )
18043 }
18044
18045 pub fn highlighted_display_row_for_autoscroll(
18046 &self,
18047 snapshot: &DisplaySnapshot,
18048 ) -> Option<DisplayRow> {
18049 self.highlighted_rows
18050 .values()
18051 .flat_map(|highlighted_rows| highlighted_rows.iter())
18052 .filter_map(|highlight| {
18053 if highlight.options.autoscroll {
18054 Some(highlight.range.start.to_display_point(snapshot).row())
18055 } else {
18056 None
18057 }
18058 })
18059 .min()
18060 }
18061
18062 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18063 self.highlight_background::<SearchWithinRange>(
18064 ranges,
18065 |colors| colors.editor_document_highlight_read_background,
18066 cx,
18067 )
18068 }
18069
18070 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18071 self.breadcrumb_header = Some(new_header);
18072 }
18073
18074 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18075 self.clear_background_highlights::<SearchWithinRange>(cx);
18076 }
18077
18078 pub fn highlight_background<T: 'static>(
18079 &mut self,
18080 ranges: &[Range<Anchor>],
18081 color_fetcher: fn(&ThemeColors) -> Hsla,
18082 cx: &mut Context<Self>,
18083 ) {
18084 self.background_highlights
18085 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18086 self.scrollbar_marker_state.dirty = true;
18087 cx.notify();
18088 }
18089
18090 pub fn clear_background_highlights<T: 'static>(
18091 &mut self,
18092 cx: &mut Context<Self>,
18093 ) -> Option<BackgroundHighlight> {
18094 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
18095 if !text_highlights.1.is_empty() {
18096 self.scrollbar_marker_state.dirty = true;
18097 cx.notify();
18098 }
18099 Some(text_highlights)
18100 }
18101
18102 pub fn highlight_gutter<T: 'static>(
18103 &mut self,
18104 ranges: &[Range<Anchor>],
18105 color_fetcher: fn(&App) -> Hsla,
18106 cx: &mut Context<Self>,
18107 ) {
18108 self.gutter_highlights
18109 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18110 cx.notify();
18111 }
18112
18113 pub fn clear_gutter_highlights<T: 'static>(
18114 &mut self,
18115 cx: &mut Context<Self>,
18116 ) -> Option<GutterHighlight> {
18117 cx.notify();
18118 self.gutter_highlights.remove(&TypeId::of::<T>())
18119 }
18120
18121 #[cfg(feature = "test-support")]
18122 pub fn all_text_background_highlights(
18123 &self,
18124 window: &mut Window,
18125 cx: &mut Context<Self>,
18126 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18127 let snapshot = self.snapshot(window, cx);
18128 let buffer = &snapshot.buffer_snapshot;
18129 let start = buffer.anchor_before(0);
18130 let end = buffer.anchor_after(buffer.len());
18131 let theme = cx.theme().colors();
18132 self.background_highlights_in_range(start..end, &snapshot, theme)
18133 }
18134
18135 #[cfg(feature = "test-support")]
18136 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18137 let snapshot = self.buffer().read(cx).snapshot(cx);
18138
18139 let highlights = self
18140 .background_highlights
18141 .get(&TypeId::of::<items::BufferSearchHighlights>());
18142
18143 if let Some((_color, ranges)) = highlights {
18144 ranges
18145 .iter()
18146 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18147 .collect_vec()
18148 } else {
18149 vec![]
18150 }
18151 }
18152
18153 fn document_highlights_for_position<'a>(
18154 &'a self,
18155 position: Anchor,
18156 buffer: &'a MultiBufferSnapshot,
18157 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18158 let read_highlights = self
18159 .background_highlights
18160 .get(&TypeId::of::<DocumentHighlightRead>())
18161 .map(|h| &h.1);
18162 let write_highlights = self
18163 .background_highlights
18164 .get(&TypeId::of::<DocumentHighlightWrite>())
18165 .map(|h| &h.1);
18166 let left_position = position.bias_left(buffer);
18167 let right_position = position.bias_right(buffer);
18168 read_highlights
18169 .into_iter()
18170 .chain(write_highlights)
18171 .flat_map(move |ranges| {
18172 let start_ix = match ranges.binary_search_by(|probe| {
18173 let cmp = probe.end.cmp(&left_position, buffer);
18174 if cmp.is_ge() {
18175 Ordering::Greater
18176 } else {
18177 Ordering::Less
18178 }
18179 }) {
18180 Ok(i) | Err(i) => i,
18181 };
18182
18183 ranges[start_ix..]
18184 .iter()
18185 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
18186 })
18187 }
18188
18189 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18190 self.background_highlights
18191 .get(&TypeId::of::<T>())
18192 .map_or(false, |(_, highlights)| !highlights.is_empty())
18193 }
18194
18195 pub fn background_highlights_in_range(
18196 &self,
18197 search_range: Range<Anchor>,
18198 display_snapshot: &DisplaySnapshot,
18199 theme: &ThemeColors,
18200 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18201 let mut results = Vec::new();
18202 for (color_fetcher, ranges) in self.background_highlights.values() {
18203 let color = color_fetcher(theme);
18204 let start_ix = match ranges.binary_search_by(|probe| {
18205 let cmp = probe
18206 .end
18207 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18208 if cmp.is_gt() {
18209 Ordering::Greater
18210 } else {
18211 Ordering::Less
18212 }
18213 }) {
18214 Ok(i) | Err(i) => i,
18215 };
18216 for range in &ranges[start_ix..] {
18217 if range
18218 .start
18219 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18220 .is_ge()
18221 {
18222 break;
18223 }
18224
18225 let start = range.start.to_display_point(display_snapshot);
18226 let end = range.end.to_display_point(display_snapshot);
18227 results.push((start..end, color))
18228 }
18229 }
18230 results
18231 }
18232
18233 pub fn background_highlight_row_ranges<T: 'static>(
18234 &self,
18235 search_range: Range<Anchor>,
18236 display_snapshot: &DisplaySnapshot,
18237 count: usize,
18238 ) -> Vec<RangeInclusive<DisplayPoint>> {
18239 let mut results = Vec::new();
18240 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
18241 return vec![];
18242 };
18243
18244 let start_ix = match ranges.binary_search_by(|probe| {
18245 let cmp = probe
18246 .end
18247 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18248 if cmp.is_gt() {
18249 Ordering::Greater
18250 } else {
18251 Ordering::Less
18252 }
18253 }) {
18254 Ok(i) | Err(i) => i,
18255 };
18256 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18257 if let (Some(start_display), Some(end_display)) = (start, end) {
18258 results.push(
18259 start_display.to_display_point(display_snapshot)
18260 ..=end_display.to_display_point(display_snapshot),
18261 );
18262 }
18263 };
18264 let mut start_row: Option<Point> = None;
18265 let mut end_row: Option<Point> = None;
18266 if ranges.len() > count {
18267 return Vec::new();
18268 }
18269 for range in &ranges[start_ix..] {
18270 if range
18271 .start
18272 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18273 .is_ge()
18274 {
18275 break;
18276 }
18277 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18278 if let Some(current_row) = &end_row {
18279 if end.row == current_row.row {
18280 continue;
18281 }
18282 }
18283 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18284 if start_row.is_none() {
18285 assert_eq!(end_row, None);
18286 start_row = Some(start);
18287 end_row = Some(end);
18288 continue;
18289 }
18290 if let Some(current_end) = end_row.as_mut() {
18291 if start.row > current_end.row + 1 {
18292 push_region(start_row, end_row);
18293 start_row = Some(start);
18294 end_row = Some(end);
18295 } else {
18296 // Merge two hunks.
18297 *current_end = end;
18298 }
18299 } else {
18300 unreachable!();
18301 }
18302 }
18303 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18304 push_region(start_row, end_row);
18305 results
18306 }
18307
18308 pub fn gutter_highlights_in_range(
18309 &self,
18310 search_range: Range<Anchor>,
18311 display_snapshot: &DisplaySnapshot,
18312 cx: &App,
18313 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18314 let mut results = Vec::new();
18315 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18316 let color = color_fetcher(cx);
18317 let start_ix = match ranges.binary_search_by(|probe| {
18318 let cmp = probe
18319 .end
18320 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18321 if cmp.is_gt() {
18322 Ordering::Greater
18323 } else {
18324 Ordering::Less
18325 }
18326 }) {
18327 Ok(i) | Err(i) => i,
18328 };
18329 for range in &ranges[start_ix..] {
18330 if range
18331 .start
18332 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18333 .is_ge()
18334 {
18335 break;
18336 }
18337
18338 let start = range.start.to_display_point(display_snapshot);
18339 let end = range.end.to_display_point(display_snapshot);
18340 results.push((start..end, color))
18341 }
18342 }
18343 results
18344 }
18345
18346 /// Get the text ranges corresponding to the redaction query
18347 pub fn redacted_ranges(
18348 &self,
18349 search_range: Range<Anchor>,
18350 display_snapshot: &DisplaySnapshot,
18351 cx: &App,
18352 ) -> Vec<Range<DisplayPoint>> {
18353 display_snapshot
18354 .buffer_snapshot
18355 .redacted_ranges(search_range, |file| {
18356 if let Some(file) = file {
18357 file.is_private()
18358 && EditorSettings::get(
18359 Some(SettingsLocation {
18360 worktree_id: file.worktree_id(cx),
18361 path: file.path().as_ref(),
18362 }),
18363 cx,
18364 )
18365 .redact_private_values
18366 } else {
18367 false
18368 }
18369 })
18370 .map(|range| {
18371 range.start.to_display_point(display_snapshot)
18372 ..range.end.to_display_point(display_snapshot)
18373 })
18374 .collect()
18375 }
18376
18377 pub fn highlight_text<T: 'static>(
18378 &mut self,
18379 ranges: Vec<Range<Anchor>>,
18380 style: HighlightStyle,
18381 cx: &mut Context<Self>,
18382 ) {
18383 self.display_map.update(cx, |map, _| {
18384 map.highlight_text(TypeId::of::<T>(), ranges, style)
18385 });
18386 cx.notify();
18387 }
18388
18389 pub(crate) fn highlight_inlays<T: 'static>(
18390 &mut self,
18391 highlights: Vec<InlayHighlight>,
18392 style: HighlightStyle,
18393 cx: &mut Context<Self>,
18394 ) {
18395 self.display_map.update(cx, |map, _| {
18396 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18397 });
18398 cx.notify();
18399 }
18400
18401 pub fn text_highlights<'a, T: 'static>(
18402 &'a self,
18403 cx: &'a App,
18404 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18405 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18406 }
18407
18408 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18409 let cleared = self
18410 .display_map
18411 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18412 if cleared {
18413 cx.notify();
18414 }
18415 }
18416
18417 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18418 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18419 && self.focus_handle.is_focused(window)
18420 }
18421
18422 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18423 self.show_cursor_when_unfocused = is_enabled;
18424 cx.notify();
18425 }
18426
18427 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18428 cx.notify();
18429 }
18430
18431 fn on_debug_session_event(
18432 &mut self,
18433 _session: Entity<Session>,
18434 event: &SessionEvent,
18435 cx: &mut Context<Self>,
18436 ) {
18437 match event {
18438 SessionEvent::InvalidateInlineValue => {
18439 self.refresh_inline_values(cx);
18440 }
18441 _ => {}
18442 }
18443 }
18444
18445 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18446 let Some(project) = self.project.clone() else {
18447 return;
18448 };
18449
18450 if !self.inline_value_cache.enabled {
18451 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18452 self.splice_inlays(&inlays, Vec::new(), cx);
18453 return;
18454 }
18455
18456 let current_execution_position = self
18457 .highlighted_rows
18458 .get(&TypeId::of::<ActiveDebugLine>())
18459 .and_then(|lines| lines.last().map(|line| line.range.start));
18460
18461 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18462 let inline_values = editor
18463 .update(cx, |editor, cx| {
18464 let Some(current_execution_position) = current_execution_position else {
18465 return Some(Task::ready(Ok(Vec::new())));
18466 };
18467
18468 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18469 let snapshot = buffer.snapshot(cx);
18470
18471 let excerpt = snapshot.excerpt_containing(
18472 current_execution_position..current_execution_position,
18473 )?;
18474
18475 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18476 })?;
18477
18478 let range =
18479 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18480
18481 project.inline_values(buffer, range, cx)
18482 })
18483 .ok()
18484 .flatten()?
18485 .await
18486 .context("refreshing debugger inlays")
18487 .log_err()?;
18488
18489 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18490
18491 for (buffer_id, inline_value) in inline_values
18492 .into_iter()
18493 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18494 {
18495 buffer_inline_values
18496 .entry(buffer_id)
18497 .or_default()
18498 .push(inline_value);
18499 }
18500
18501 editor
18502 .update(cx, |editor, cx| {
18503 let snapshot = editor.buffer.read(cx).snapshot(cx);
18504 let mut new_inlays = Vec::default();
18505
18506 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18507 let buffer_id = buffer_snapshot.remote_id();
18508 buffer_inline_values
18509 .get(&buffer_id)
18510 .into_iter()
18511 .flatten()
18512 .for_each(|hint| {
18513 let inlay = Inlay::debugger_hint(
18514 post_inc(&mut editor.next_inlay_id),
18515 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18516 hint.text(),
18517 );
18518
18519 new_inlays.push(inlay);
18520 });
18521 }
18522
18523 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18524 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18525
18526 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18527 })
18528 .ok()?;
18529 Some(())
18530 });
18531 }
18532
18533 fn on_buffer_event(
18534 &mut self,
18535 multibuffer: &Entity<MultiBuffer>,
18536 event: &multi_buffer::Event,
18537 window: &mut Window,
18538 cx: &mut Context<Self>,
18539 ) {
18540 match event {
18541 multi_buffer::Event::Edited {
18542 singleton_buffer_edited,
18543 edited_buffer: buffer_edited,
18544 } => {
18545 self.scrollbar_marker_state.dirty = true;
18546 self.active_indent_guides_state.dirty = true;
18547 self.refresh_active_diagnostics(cx);
18548 self.refresh_code_actions(window, cx);
18549 self.refresh_selected_text_highlights(true, window, cx);
18550 refresh_matching_bracket_highlights(self, window, cx);
18551 if self.has_active_inline_completion() {
18552 self.update_visible_inline_completion(window, cx);
18553 }
18554 if let Some(buffer) = buffer_edited {
18555 let buffer_id = buffer.read(cx).remote_id();
18556 if !self.registered_buffers.contains_key(&buffer_id) {
18557 if let Some(project) = self.project.as_ref() {
18558 project.update(cx, |project, cx| {
18559 self.registered_buffers.insert(
18560 buffer_id,
18561 project.register_buffer_with_language_servers(&buffer, cx),
18562 );
18563 })
18564 }
18565 }
18566 }
18567 cx.emit(EditorEvent::BufferEdited);
18568 cx.emit(SearchEvent::MatchesInvalidated);
18569 if *singleton_buffer_edited {
18570 if let Some(project) = &self.project {
18571 #[allow(clippy::mutable_key_type)]
18572 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18573 multibuffer
18574 .all_buffers()
18575 .into_iter()
18576 .filter_map(|buffer| {
18577 buffer.update(cx, |buffer, cx| {
18578 let language = buffer.language()?;
18579 let should_discard = project.update(cx, |project, cx| {
18580 project.is_local()
18581 && !project.has_language_servers_for(buffer, cx)
18582 });
18583 should_discard.not().then_some(language.clone())
18584 })
18585 })
18586 .collect::<HashSet<_>>()
18587 });
18588 if !languages_affected.is_empty() {
18589 self.refresh_inlay_hints(
18590 InlayHintRefreshReason::BufferEdited(languages_affected),
18591 cx,
18592 );
18593 }
18594 }
18595 }
18596
18597 let Some(project) = &self.project else { return };
18598 let (telemetry, is_via_ssh) = {
18599 let project = project.read(cx);
18600 let telemetry = project.client().telemetry().clone();
18601 let is_via_ssh = project.is_via_ssh();
18602 (telemetry, is_via_ssh)
18603 };
18604 refresh_linked_ranges(self, window, cx);
18605 telemetry.log_edit_event("editor", is_via_ssh);
18606 }
18607 multi_buffer::Event::ExcerptsAdded {
18608 buffer,
18609 predecessor,
18610 excerpts,
18611 } => {
18612 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18613 let buffer_id = buffer.read(cx).remote_id();
18614 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18615 if let Some(project) = &self.project {
18616 update_uncommitted_diff_for_buffer(
18617 cx.entity(),
18618 project,
18619 [buffer.clone()],
18620 self.buffer.clone(),
18621 cx,
18622 )
18623 .detach();
18624 }
18625 }
18626 cx.emit(EditorEvent::ExcerptsAdded {
18627 buffer: buffer.clone(),
18628 predecessor: *predecessor,
18629 excerpts: excerpts.clone(),
18630 });
18631 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18632 }
18633 multi_buffer::Event::ExcerptsRemoved {
18634 ids,
18635 removed_buffer_ids,
18636 } => {
18637 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18638 let buffer = self.buffer.read(cx);
18639 self.registered_buffers
18640 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18641 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18642 cx.emit(EditorEvent::ExcerptsRemoved {
18643 ids: ids.clone(),
18644 removed_buffer_ids: removed_buffer_ids.clone(),
18645 })
18646 }
18647 multi_buffer::Event::ExcerptsEdited {
18648 excerpt_ids,
18649 buffer_ids,
18650 } => {
18651 self.display_map.update(cx, |map, cx| {
18652 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18653 });
18654 cx.emit(EditorEvent::ExcerptsEdited {
18655 ids: excerpt_ids.clone(),
18656 })
18657 }
18658 multi_buffer::Event::ExcerptsExpanded { ids } => {
18659 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18660 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18661 }
18662 multi_buffer::Event::Reparsed(buffer_id) => {
18663 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18664 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18665
18666 cx.emit(EditorEvent::Reparsed(*buffer_id));
18667 }
18668 multi_buffer::Event::DiffHunksToggled => {
18669 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18670 }
18671 multi_buffer::Event::LanguageChanged(buffer_id) => {
18672 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18673 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18674 cx.emit(EditorEvent::Reparsed(*buffer_id));
18675 cx.notify();
18676 }
18677 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18678 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18679 multi_buffer::Event::FileHandleChanged
18680 | multi_buffer::Event::Reloaded
18681 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18682 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18683 multi_buffer::Event::DiagnosticsUpdated => {
18684 self.refresh_active_diagnostics(cx);
18685 self.refresh_inline_diagnostics(true, window, cx);
18686 self.scrollbar_marker_state.dirty = true;
18687 cx.notify();
18688 }
18689 _ => {}
18690 };
18691 }
18692
18693 pub fn start_temporary_diff_override(&mut self) {
18694 self.load_diff_task.take();
18695 self.temporary_diff_override = true;
18696 }
18697
18698 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18699 self.temporary_diff_override = false;
18700 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18701 self.buffer.update(cx, |buffer, cx| {
18702 buffer.set_all_diff_hunks_collapsed(cx);
18703 });
18704
18705 if let Some(project) = self.project.clone() {
18706 self.load_diff_task = Some(
18707 update_uncommitted_diff_for_buffer(
18708 cx.entity(),
18709 &project,
18710 self.buffer.read(cx).all_buffers(),
18711 self.buffer.clone(),
18712 cx,
18713 )
18714 .shared(),
18715 );
18716 }
18717 }
18718
18719 fn on_display_map_changed(
18720 &mut self,
18721 _: Entity<DisplayMap>,
18722 _: &mut Window,
18723 cx: &mut Context<Self>,
18724 ) {
18725 cx.notify();
18726 }
18727
18728 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18729 let new_severity = if self.diagnostics_enabled() {
18730 EditorSettings::get_global(cx)
18731 .diagnostics_max_severity
18732 .unwrap_or(DiagnosticSeverity::Hint)
18733 } else {
18734 DiagnosticSeverity::Off
18735 };
18736 self.set_max_diagnostics_severity(new_severity, cx);
18737 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18738 self.update_edit_prediction_settings(cx);
18739 self.refresh_inline_completion(true, false, window, cx);
18740 self.refresh_inlay_hints(
18741 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18742 self.selections.newest_anchor().head(),
18743 &self.buffer.read(cx).snapshot(cx),
18744 cx,
18745 )),
18746 cx,
18747 );
18748
18749 let old_cursor_shape = self.cursor_shape;
18750
18751 {
18752 let editor_settings = EditorSettings::get_global(cx);
18753 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18754 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18755 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18756 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18757 }
18758
18759 if old_cursor_shape != self.cursor_shape {
18760 cx.emit(EditorEvent::CursorShapeChanged);
18761 }
18762
18763 let project_settings = ProjectSettings::get_global(cx);
18764 self.serialize_dirty_buffers =
18765 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18766
18767 if self.mode.is_full() {
18768 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18769 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18770 if self.show_inline_diagnostics != show_inline_diagnostics {
18771 self.show_inline_diagnostics = show_inline_diagnostics;
18772 self.refresh_inline_diagnostics(false, window, cx);
18773 }
18774
18775 if self.git_blame_inline_enabled != inline_blame_enabled {
18776 self.toggle_git_blame_inline_internal(false, window, cx);
18777 }
18778
18779 let minimap_settings = EditorSettings::get_global(cx).minimap;
18780 if self.minimap_visibility != MinimapVisibility::Disabled {
18781 if self.minimap_visibility.settings_visibility()
18782 != minimap_settings.minimap_enabled()
18783 {
18784 self.set_minimap_visibility(
18785 MinimapVisibility::for_mode(self.mode(), cx),
18786 window,
18787 cx,
18788 );
18789 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18790 minimap_entity.update(cx, |minimap_editor, cx| {
18791 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18792 })
18793 }
18794 }
18795 }
18796
18797 cx.notify();
18798 }
18799
18800 pub fn set_searchable(&mut self, searchable: bool) {
18801 self.searchable = searchable;
18802 }
18803
18804 pub fn searchable(&self) -> bool {
18805 self.searchable
18806 }
18807
18808 fn open_proposed_changes_editor(
18809 &mut self,
18810 _: &OpenProposedChangesEditor,
18811 window: &mut Window,
18812 cx: &mut Context<Self>,
18813 ) {
18814 let Some(workspace) = self.workspace() else {
18815 cx.propagate();
18816 return;
18817 };
18818
18819 let selections = self.selections.all::<usize>(cx);
18820 let multi_buffer = self.buffer.read(cx);
18821 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18822 let mut new_selections_by_buffer = HashMap::default();
18823 for selection in selections {
18824 for (buffer, range, _) in
18825 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18826 {
18827 let mut range = range.to_point(buffer);
18828 range.start.column = 0;
18829 range.end.column = buffer.line_len(range.end.row);
18830 new_selections_by_buffer
18831 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18832 .or_insert(Vec::new())
18833 .push(range)
18834 }
18835 }
18836
18837 let proposed_changes_buffers = new_selections_by_buffer
18838 .into_iter()
18839 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18840 .collect::<Vec<_>>();
18841 let proposed_changes_editor = cx.new(|cx| {
18842 ProposedChangesEditor::new(
18843 "Proposed changes",
18844 proposed_changes_buffers,
18845 self.project.clone(),
18846 window,
18847 cx,
18848 )
18849 });
18850
18851 window.defer(cx, move |window, cx| {
18852 workspace.update(cx, |workspace, cx| {
18853 workspace.active_pane().update(cx, |pane, cx| {
18854 pane.add_item(
18855 Box::new(proposed_changes_editor),
18856 true,
18857 true,
18858 None,
18859 window,
18860 cx,
18861 );
18862 });
18863 });
18864 });
18865 }
18866
18867 pub fn open_excerpts_in_split(
18868 &mut self,
18869 _: &OpenExcerptsSplit,
18870 window: &mut Window,
18871 cx: &mut Context<Self>,
18872 ) {
18873 self.open_excerpts_common(None, true, window, cx)
18874 }
18875
18876 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18877 self.open_excerpts_common(None, false, window, cx)
18878 }
18879
18880 fn open_excerpts_common(
18881 &mut self,
18882 jump_data: Option<JumpData>,
18883 split: bool,
18884 window: &mut Window,
18885 cx: &mut Context<Self>,
18886 ) {
18887 let Some(workspace) = self.workspace() else {
18888 cx.propagate();
18889 return;
18890 };
18891
18892 if self.buffer.read(cx).is_singleton() {
18893 cx.propagate();
18894 return;
18895 }
18896
18897 let mut new_selections_by_buffer = HashMap::default();
18898 match &jump_data {
18899 Some(JumpData::MultiBufferPoint {
18900 excerpt_id,
18901 position,
18902 anchor,
18903 line_offset_from_top,
18904 }) => {
18905 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18906 if let Some(buffer) = multi_buffer_snapshot
18907 .buffer_id_for_excerpt(*excerpt_id)
18908 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18909 {
18910 let buffer_snapshot = buffer.read(cx).snapshot();
18911 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18912 language::ToPoint::to_point(anchor, &buffer_snapshot)
18913 } else {
18914 buffer_snapshot.clip_point(*position, Bias::Left)
18915 };
18916 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18917 new_selections_by_buffer.insert(
18918 buffer,
18919 (
18920 vec![jump_to_offset..jump_to_offset],
18921 Some(*line_offset_from_top),
18922 ),
18923 );
18924 }
18925 }
18926 Some(JumpData::MultiBufferRow {
18927 row,
18928 line_offset_from_top,
18929 }) => {
18930 let point = MultiBufferPoint::new(row.0, 0);
18931 if let Some((buffer, buffer_point, _)) =
18932 self.buffer.read(cx).point_to_buffer_point(point, cx)
18933 {
18934 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18935 new_selections_by_buffer
18936 .entry(buffer)
18937 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18938 .0
18939 .push(buffer_offset..buffer_offset)
18940 }
18941 }
18942 None => {
18943 let selections = self.selections.all::<usize>(cx);
18944 let multi_buffer = self.buffer.read(cx);
18945 for selection in selections {
18946 for (snapshot, range, _, anchor) in multi_buffer
18947 .snapshot(cx)
18948 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18949 {
18950 if let Some(anchor) = anchor {
18951 // selection is in a deleted hunk
18952 let Some(buffer_id) = anchor.buffer_id else {
18953 continue;
18954 };
18955 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18956 continue;
18957 };
18958 let offset = text::ToOffset::to_offset(
18959 &anchor.text_anchor,
18960 &buffer_handle.read(cx).snapshot(),
18961 );
18962 let range = offset..offset;
18963 new_selections_by_buffer
18964 .entry(buffer_handle)
18965 .or_insert((Vec::new(), None))
18966 .0
18967 .push(range)
18968 } else {
18969 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18970 else {
18971 continue;
18972 };
18973 new_selections_by_buffer
18974 .entry(buffer_handle)
18975 .or_insert((Vec::new(), None))
18976 .0
18977 .push(range)
18978 }
18979 }
18980 }
18981 }
18982 }
18983
18984 new_selections_by_buffer
18985 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18986
18987 if new_selections_by_buffer.is_empty() {
18988 return;
18989 }
18990
18991 // We defer the pane interaction because we ourselves are a workspace item
18992 // and activating a new item causes the pane to call a method on us reentrantly,
18993 // which panics if we're on the stack.
18994 window.defer(cx, move |window, cx| {
18995 workspace.update(cx, |workspace, cx| {
18996 let pane = if split {
18997 workspace.adjacent_pane(window, cx)
18998 } else {
18999 workspace.active_pane().clone()
19000 };
19001
19002 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19003 let editor = buffer
19004 .read(cx)
19005 .file()
19006 .is_none()
19007 .then(|| {
19008 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19009 // so `workspace.open_project_item` will never find them, always opening a new editor.
19010 // Instead, we try to activate the existing editor in the pane first.
19011 let (editor, pane_item_index) =
19012 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19013 let editor = item.downcast::<Editor>()?;
19014 let singleton_buffer =
19015 editor.read(cx).buffer().read(cx).as_singleton()?;
19016 if singleton_buffer == buffer {
19017 Some((editor, i))
19018 } else {
19019 None
19020 }
19021 })?;
19022 pane.update(cx, |pane, cx| {
19023 pane.activate_item(pane_item_index, true, true, window, cx)
19024 });
19025 Some(editor)
19026 })
19027 .flatten()
19028 .unwrap_or_else(|| {
19029 workspace.open_project_item::<Self>(
19030 pane.clone(),
19031 buffer,
19032 true,
19033 true,
19034 window,
19035 cx,
19036 )
19037 });
19038
19039 editor.update(cx, |editor, cx| {
19040 let autoscroll = match scroll_offset {
19041 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19042 None => Autoscroll::newest(),
19043 };
19044 let nav_history = editor.nav_history.take();
19045 editor.change_selections(Some(autoscroll), window, cx, |s| {
19046 s.select_ranges(ranges);
19047 });
19048 editor.nav_history = nav_history;
19049 });
19050 }
19051 })
19052 });
19053 }
19054
19055 // For now, don't allow opening excerpts in buffers that aren't backed by
19056 // regular project files.
19057 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19058 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19059 }
19060
19061 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19062 let snapshot = self.buffer.read(cx).read(cx);
19063 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
19064 Some(
19065 ranges
19066 .iter()
19067 .map(move |range| {
19068 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19069 })
19070 .collect(),
19071 )
19072 }
19073
19074 fn selection_replacement_ranges(
19075 &self,
19076 range: Range<OffsetUtf16>,
19077 cx: &mut App,
19078 ) -> Vec<Range<OffsetUtf16>> {
19079 let selections = self.selections.all::<OffsetUtf16>(cx);
19080 let newest_selection = selections
19081 .iter()
19082 .max_by_key(|selection| selection.id)
19083 .unwrap();
19084 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19085 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19086 let snapshot = self.buffer.read(cx).read(cx);
19087 selections
19088 .into_iter()
19089 .map(|mut selection| {
19090 selection.start.0 =
19091 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19092 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19093 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19094 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19095 })
19096 .collect()
19097 }
19098
19099 fn report_editor_event(
19100 &self,
19101 event_type: &'static str,
19102 file_extension: Option<String>,
19103 cx: &App,
19104 ) {
19105 if cfg!(any(test, feature = "test-support")) {
19106 return;
19107 }
19108
19109 let Some(project) = &self.project else { return };
19110
19111 // If None, we are in a file without an extension
19112 let file = self
19113 .buffer
19114 .read(cx)
19115 .as_singleton()
19116 .and_then(|b| b.read(cx).file());
19117 let file_extension = file_extension.or(file
19118 .as_ref()
19119 .and_then(|file| Path::new(file.file_name(cx)).extension())
19120 .and_then(|e| e.to_str())
19121 .map(|a| a.to_string()));
19122
19123 let vim_mode = vim_enabled(cx);
19124
19125 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19126 let copilot_enabled = edit_predictions_provider
19127 == language::language_settings::EditPredictionProvider::Copilot;
19128 let copilot_enabled_for_language = self
19129 .buffer
19130 .read(cx)
19131 .language_settings(cx)
19132 .show_edit_predictions;
19133
19134 let project = project.read(cx);
19135 telemetry::event!(
19136 event_type,
19137 file_extension,
19138 vim_mode,
19139 copilot_enabled,
19140 copilot_enabled_for_language,
19141 edit_predictions_provider,
19142 is_via_ssh = project.is_via_ssh(),
19143 );
19144 }
19145
19146 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19147 /// with each line being an array of {text, highlight} objects.
19148 fn copy_highlight_json(
19149 &mut self,
19150 _: &CopyHighlightJson,
19151 window: &mut Window,
19152 cx: &mut Context<Self>,
19153 ) {
19154 #[derive(Serialize)]
19155 struct Chunk<'a> {
19156 text: String,
19157 highlight: Option<&'a str>,
19158 }
19159
19160 let snapshot = self.buffer.read(cx).snapshot(cx);
19161 let range = self
19162 .selected_text_range(false, window, cx)
19163 .and_then(|selection| {
19164 if selection.range.is_empty() {
19165 None
19166 } else {
19167 Some(selection.range)
19168 }
19169 })
19170 .unwrap_or_else(|| 0..snapshot.len());
19171
19172 let chunks = snapshot.chunks(range, true);
19173 let mut lines = Vec::new();
19174 let mut line: VecDeque<Chunk> = VecDeque::new();
19175
19176 let Some(style) = self.style.as_ref() else {
19177 return;
19178 };
19179
19180 for chunk in chunks {
19181 let highlight = chunk
19182 .syntax_highlight_id
19183 .and_then(|id| id.name(&style.syntax));
19184 let mut chunk_lines = chunk.text.split('\n').peekable();
19185 while let Some(text) = chunk_lines.next() {
19186 let mut merged_with_last_token = false;
19187 if let Some(last_token) = line.back_mut() {
19188 if last_token.highlight == highlight {
19189 last_token.text.push_str(text);
19190 merged_with_last_token = true;
19191 }
19192 }
19193
19194 if !merged_with_last_token {
19195 line.push_back(Chunk {
19196 text: text.into(),
19197 highlight,
19198 });
19199 }
19200
19201 if chunk_lines.peek().is_some() {
19202 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19203 line.pop_front();
19204 }
19205 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19206 line.pop_back();
19207 }
19208
19209 lines.push(mem::take(&mut line));
19210 }
19211 }
19212 }
19213
19214 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19215 return;
19216 };
19217 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19218 }
19219
19220 pub fn open_context_menu(
19221 &mut self,
19222 _: &OpenContextMenu,
19223 window: &mut Window,
19224 cx: &mut Context<Self>,
19225 ) {
19226 self.request_autoscroll(Autoscroll::newest(), cx);
19227 let position = self.selections.newest_display(cx).start;
19228 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19229 }
19230
19231 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19232 &self.inlay_hint_cache
19233 }
19234
19235 pub fn replay_insert_event(
19236 &mut self,
19237 text: &str,
19238 relative_utf16_range: Option<Range<isize>>,
19239 window: &mut Window,
19240 cx: &mut Context<Self>,
19241 ) {
19242 if !self.input_enabled {
19243 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19244 return;
19245 }
19246 if let Some(relative_utf16_range) = relative_utf16_range {
19247 let selections = self.selections.all::<OffsetUtf16>(cx);
19248 self.change_selections(None, window, cx, |s| {
19249 let new_ranges = selections.into_iter().map(|range| {
19250 let start = OffsetUtf16(
19251 range
19252 .head()
19253 .0
19254 .saturating_add_signed(relative_utf16_range.start),
19255 );
19256 let end = OffsetUtf16(
19257 range
19258 .head()
19259 .0
19260 .saturating_add_signed(relative_utf16_range.end),
19261 );
19262 start..end
19263 });
19264 s.select_ranges(new_ranges);
19265 });
19266 }
19267
19268 self.handle_input(text, window, cx);
19269 }
19270
19271 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19272 let Some(provider) = self.semantics_provider.as_ref() else {
19273 return false;
19274 };
19275
19276 let mut supports = false;
19277 self.buffer().update(cx, |this, cx| {
19278 this.for_each_buffer(|buffer| {
19279 supports |= provider.supports_inlay_hints(buffer, cx);
19280 });
19281 });
19282
19283 supports
19284 }
19285
19286 pub fn is_focused(&self, window: &Window) -> bool {
19287 self.focus_handle.is_focused(window)
19288 }
19289
19290 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19291 cx.emit(EditorEvent::Focused);
19292
19293 if let Some(descendant) = self
19294 .last_focused_descendant
19295 .take()
19296 .and_then(|descendant| descendant.upgrade())
19297 {
19298 window.focus(&descendant);
19299 } else {
19300 if let Some(blame) = self.blame.as_ref() {
19301 blame.update(cx, GitBlame::focus)
19302 }
19303
19304 self.blink_manager.update(cx, BlinkManager::enable);
19305 self.show_cursor_names(window, cx);
19306 self.buffer.update(cx, |buffer, cx| {
19307 buffer.finalize_last_transaction(cx);
19308 if self.leader_id.is_none() {
19309 buffer.set_active_selections(
19310 &self.selections.disjoint_anchors(),
19311 self.selections.line_mode,
19312 self.cursor_shape,
19313 cx,
19314 );
19315 }
19316 });
19317 }
19318 }
19319
19320 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19321 cx.emit(EditorEvent::FocusedIn)
19322 }
19323
19324 fn handle_focus_out(
19325 &mut self,
19326 event: FocusOutEvent,
19327 _window: &mut Window,
19328 cx: &mut Context<Self>,
19329 ) {
19330 if event.blurred != self.focus_handle {
19331 self.last_focused_descendant = Some(event.blurred);
19332 }
19333 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19334 }
19335
19336 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19337 self.blink_manager.update(cx, BlinkManager::disable);
19338 self.buffer
19339 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19340
19341 if let Some(blame) = self.blame.as_ref() {
19342 blame.update(cx, GitBlame::blur)
19343 }
19344 if !self.hover_state.focused(window, cx) {
19345 hide_hover(self, cx);
19346 }
19347 if !self
19348 .context_menu
19349 .borrow()
19350 .as_ref()
19351 .is_some_and(|context_menu| context_menu.focused(window, cx))
19352 {
19353 self.hide_context_menu(window, cx);
19354 }
19355 self.discard_inline_completion(false, cx);
19356 cx.emit(EditorEvent::Blurred);
19357 cx.notify();
19358 }
19359
19360 pub fn register_action<A: Action>(
19361 &mut self,
19362 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19363 ) -> Subscription {
19364 let id = self.next_editor_action_id.post_inc();
19365 let listener = Arc::new(listener);
19366 self.editor_actions.borrow_mut().insert(
19367 id,
19368 Box::new(move |window, _| {
19369 let listener = listener.clone();
19370 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19371 let action = action.downcast_ref().unwrap();
19372 if phase == DispatchPhase::Bubble {
19373 listener(action, window, cx)
19374 }
19375 })
19376 }),
19377 );
19378
19379 let editor_actions = self.editor_actions.clone();
19380 Subscription::new(move || {
19381 editor_actions.borrow_mut().remove(&id);
19382 })
19383 }
19384
19385 pub fn file_header_size(&self) -> u32 {
19386 FILE_HEADER_HEIGHT
19387 }
19388
19389 pub fn restore(
19390 &mut self,
19391 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19392 window: &mut Window,
19393 cx: &mut Context<Self>,
19394 ) {
19395 let workspace = self.workspace();
19396 let project = self.project.as_ref();
19397 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19398 let mut tasks = Vec::new();
19399 for (buffer_id, changes) in revert_changes {
19400 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19401 buffer.update(cx, |buffer, cx| {
19402 buffer.edit(
19403 changes
19404 .into_iter()
19405 .map(|(range, text)| (range, text.to_string())),
19406 None,
19407 cx,
19408 );
19409 });
19410
19411 if let Some(project) =
19412 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19413 {
19414 project.update(cx, |project, cx| {
19415 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19416 })
19417 }
19418 }
19419 }
19420 tasks
19421 });
19422 cx.spawn_in(window, async move |_, cx| {
19423 for (buffer, task) in save_tasks {
19424 let result = task.await;
19425 if result.is_err() {
19426 let Some(path) = buffer
19427 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19428 .ok()
19429 else {
19430 continue;
19431 };
19432 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19433 let Some(task) = cx
19434 .update_window_entity(&workspace, |workspace, window, cx| {
19435 workspace
19436 .open_path_preview(path, None, false, false, false, window, cx)
19437 })
19438 .ok()
19439 else {
19440 continue;
19441 };
19442 task.await.log_err();
19443 }
19444 }
19445 }
19446 })
19447 .detach();
19448 self.change_selections(None, window, cx, |selections| selections.refresh());
19449 }
19450
19451 pub fn to_pixel_point(
19452 &self,
19453 source: multi_buffer::Anchor,
19454 editor_snapshot: &EditorSnapshot,
19455 window: &mut Window,
19456 ) -> Option<gpui::Point<Pixels>> {
19457 let source_point = source.to_display_point(editor_snapshot);
19458 self.display_to_pixel_point(source_point, editor_snapshot, window)
19459 }
19460
19461 pub fn display_to_pixel_point(
19462 &self,
19463 source: DisplayPoint,
19464 editor_snapshot: &EditorSnapshot,
19465 window: &mut Window,
19466 ) -> Option<gpui::Point<Pixels>> {
19467 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19468 let text_layout_details = self.text_layout_details(window);
19469 let scroll_top = text_layout_details
19470 .scroll_anchor
19471 .scroll_position(editor_snapshot)
19472 .y;
19473
19474 if source.row().as_f32() < scroll_top.floor() {
19475 return None;
19476 }
19477 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19478 let source_y = line_height * (source.row().as_f32() - scroll_top);
19479 Some(gpui::Point::new(source_x, source_y))
19480 }
19481
19482 pub fn has_visible_completions_menu(&self) -> bool {
19483 !self.edit_prediction_preview_is_active()
19484 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19485 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19486 })
19487 }
19488
19489 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19490 if self.mode.is_minimap() {
19491 return;
19492 }
19493 self.addons
19494 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19495 }
19496
19497 pub fn unregister_addon<T: Addon>(&mut self) {
19498 self.addons.remove(&std::any::TypeId::of::<T>());
19499 }
19500
19501 pub fn addon<T: Addon>(&self) -> Option<&T> {
19502 let type_id = std::any::TypeId::of::<T>();
19503 self.addons
19504 .get(&type_id)
19505 .and_then(|item| item.to_any().downcast_ref::<T>())
19506 }
19507
19508 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19509 let type_id = std::any::TypeId::of::<T>();
19510 self.addons
19511 .get_mut(&type_id)
19512 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19513 }
19514
19515 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19516 let text_layout_details = self.text_layout_details(window);
19517 let style = &text_layout_details.editor_style;
19518 let font_id = window.text_system().resolve_font(&style.text.font());
19519 let font_size = style.text.font_size.to_pixels(window.rem_size());
19520 let line_height = style.text.line_height_in_pixels(window.rem_size());
19521 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19522
19523 gpui::Size::new(em_width, line_height)
19524 }
19525
19526 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
19527 self.load_diff_task.clone()
19528 }
19529
19530 fn read_metadata_from_db(
19531 &mut self,
19532 item_id: u64,
19533 workspace_id: WorkspaceId,
19534 window: &mut Window,
19535 cx: &mut Context<Editor>,
19536 ) {
19537 if self.is_singleton(cx)
19538 && !self.mode.is_minimap()
19539 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
19540 {
19541 let buffer_snapshot = OnceCell::new();
19542
19543 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
19544 if !folds.is_empty() {
19545 let snapshot =
19546 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19547 self.fold_ranges(
19548 folds
19549 .into_iter()
19550 .map(|(start, end)| {
19551 snapshot.clip_offset(start, Bias::Left)
19552 ..snapshot.clip_offset(end, Bias::Right)
19553 })
19554 .collect(),
19555 false,
19556 window,
19557 cx,
19558 );
19559 }
19560 }
19561
19562 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
19563 if !selections.is_empty() {
19564 let snapshot =
19565 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19566 self.change_selections(None, window, cx, |s| {
19567 s.select_ranges(selections.into_iter().map(|(start, end)| {
19568 snapshot.clip_offset(start, Bias::Left)
19569 ..snapshot.clip_offset(end, Bias::Right)
19570 }));
19571 });
19572 }
19573 };
19574 }
19575
19576 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
19577 }
19578}
19579
19580fn vim_enabled(cx: &App) -> bool {
19581 cx.global::<SettingsStore>()
19582 .raw_user_settings()
19583 .get("vim_mode")
19584 == Some(&serde_json::Value::Bool(true))
19585}
19586
19587fn process_completion_for_edit(
19588 completion: &Completion,
19589 intent: CompletionIntent,
19590 buffer: &Entity<Buffer>,
19591 cursor_position: &text::Anchor,
19592 cx: &mut Context<Editor>,
19593) -> CompletionEdit {
19594 let buffer = buffer.read(cx);
19595 let buffer_snapshot = buffer.snapshot();
19596 let (snippet, new_text) = if completion.is_snippet() {
19597 let mut snippet_source = completion.new_text.clone();
19598 if let Some(scope) = buffer_snapshot.language_scope_at(cursor_position) {
19599 if scope.prefers_label_for_snippet_in_completion() {
19600 if let Some(label) = completion.label() {
19601 if matches!(
19602 completion.kind(),
19603 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
19604 ) {
19605 snippet_source = label;
19606 }
19607 }
19608 }
19609 }
19610 match Snippet::parse(&snippet_source).log_err() {
19611 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
19612 None => (None, completion.new_text.clone()),
19613 }
19614 } else {
19615 (None, completion.new_text.clone())
19616 };
19617
19618 let mut range_to_replace = {
19619 let replace_range = &completion.replace_range;
19620 if let CompletionSource::Lsp {
19621 insert_range: Some(insert_range),
19622 ..
19623 } = &completion.source
19624 {
19625 debug_assert_eq!(
19626 insert_range.start, replace_range.start,
19627 "insert_range and replace_range should start at the same position"
19628 );
19629 debug_assert!(
19630 insert_range
19631 .start
19632 .cmp(&cursor_position, &buffer_snapshot)
19633 .is_le(),
19634 "insert_range should start before or at cursor position"
19635 );
19636 debug_assert!(
19637 replace_range
19638 .start
19639 .cmp(&cursor_position, &buffer_snapshot)
19640 .is_le(),
19641 "replace_range should start before or at cursor position"
19642 );
19643 debug_assert!(
19644 insert_range
19645 .end
19646 .cmp(&cursor_position, &buffer_snapshot)
19647 .is_le(),
19648 "insert_range should end before or at cursor position"
19649 );
19650
19651 let should_replace = match intent {
19652 CompletionIntent::CompleteWithInsert => false,
19653 CompletionIntent::CompleteWithReplace => true,
19654 CompletionIntent::Complete | CompletionIntent::Compose => {
19655 let insert_mode =
19656 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
19657 .completions
19658 .lsp_insert_mode;
19659 match insert_mode {
19660 LspInsertMode::Insert => false,
19661 LspInsertMode::Replace => true,
19662 LspInsertMode::ReplaceSubsequence => {
19663 let mut text_to_replace = buffer.chars_for_range(
19664 buffer.anchor_before(replace_range.start)
19665 ..buffer.anchor_after(replace_range.end),
19666 );
19667 let mut current_needle = text_to_replace.next();
19668 for haystack_ch in completion.label.text.chars() {
19669 if let Some(needle_ch) = current_needle {
19670 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
19671 current_needle = text_to_replace.next();
19672 }
19673 }
19674 }
19675 current_needle.is_none()
19676 }
19677 LspInsertMode::ReplaceSuffix => {
19678 if replace_range
19679 .end
19680 .cmp(&cursor_position, &buffer_snapshot)
19681 .is_gt()
19682 {
19683 let range_after_cursor = *cursor_position..replace_range.end;
19684 let text_after_cursor = buffer
19685 .text_for_range(
19686 buffer.anchor_before(range_after_cursor.start)
19687 ..buffer.anchor_after(range_after_cursor.end),
19688 )
19689 .collect::<String>()
19690 .to_ascii_lowercase();
19691 completion
19692 .label
19693 .text
19694 .to_ascii_lowercase()
19695 .ends_with(&text_after_cursor)
19696 } else {
19697 true
19698 }
19699 }
19700 }
19701 }
19702 };
19703
19704 if should_replace {
19705 replace_range.clone()
19706 } else {
19707 insert_range.clone()
19708 }
19709 } else {
19710 replace_range.clone()
19711 }
19712 };
19713
19714 if range_to_replace
19715 .end
19716 .cmp(&cursor_position, &buffer_snapshot)
19717 .is_lt()
19718 {
19719 range_to_replace.end = *cursor_position;
19720 }
19721
19722 CompletionEdit {
19723 new_text,
19724 replace_range: range_to_replace.to_offset(&buffer),
19725 snippet,
19726 }
19727}
19728
19729struct CompletionEdit {
19730 new_text: String,
19731 replace_range: Range<usize>,
19732 snippet: Option<Snippet>,
19733}
19734
19735fn insert_extra_newline_brackets(
19736 buffer: &MultiBufferSnapshot,
19737 range: Range<usize>,
19738 language: &language::LanguageScope,
19739) -> bool {
19740 let leading_whitespace_len = buffer
19741 .reversed_chars_at(range.start)
19742 .take_while(|c| c.is_whitespace() && *c != '\n')
19743 .map(|c| c.len_utf8())
19744 .sum::<usize>();
19745 let trailing_whitespace_len = buffer
19746 .chars_at(range.end)
19747 .take_while(|c| c.is_whitespace() && *c != '\n')
19748 .map(|c| c.len_utf8())
19749 .sum::<usize>();
19750 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
19751
19752 language.brackets().any(|(pair, enabled)| {
19753 let pair_start = pair.start.trim_end();
19754 let pair_end = pair.end.trim_start();
19755
19756 enabled
19757 && pair.newline
19758 && buffer.contains_str_at(range.end, pair_end)
19759 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
19760 })
19761}
19762
19763fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
19764 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
19765 [(buffer, range, _)] => (*buffer, range.clone()),
19766 _ => return false,
19767 };
19768 let pair = {
19769 let mut result: Option<BracketMatch> = None;
19770
19771 for pair in buffer
19772 .all_bracket_ranges(range.clone())
19773 .filter(move |pair| {
19774 pair.open_range.start <= range.start && pair.close_range.end >= range.end
19775 })
19776 {
19777 let len = pair.close_range.end - pair.open_range.start;
19778
19779 if let Some(existing) = &result {
19780 let existing_len = existing.close_range.end - existing.open_range.start;
19781 if len > existing_len {
19782 continue;
19783 }
19784 }
19785
19786 result = Some(pair);
19787 }
19788
19789 result
19790 };
19791 let Some(pair) = pair else {
19792 return false;
19793 };
19794 pair.newline_only
19795 && buffer
19796 .chars_for_range(pair.open_range.end..range.start)
19797 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
19798 .all(|c| c.is_whitespace() && c != '\n')
19799}
19800
19801fn update_uncommitted_diff_for_buffer(
19802 editor: Entity<Editor>,
19803 project: &Entity<Project>,
19804 buffers: impl IntoIterator<Item = Entity<Buffer>>,
19805 buffer: Entity<MultiBuffer>,
19806 cx: &mut App,
19807) -> Task<()> {
19808 let mut tasks = Vec::new();
19809 project.update(cx, |project, cx| {
19810 for buffer in buffers {
19811 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
19812 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
19813 }
19814 }
19815 });
19816 cx.spawn(async move |cx| {
19817 let diffs = future::join_all(tasks).await;
19818 if editor
19819 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19820 .unwrap_or(false)
19821 {
19822 return;
19823 }
19824
19825 buffer
19826 .update(cx, |buffer, cx| {
19827 for diff in diffs.into_iter().flatten() {
19828 buffer.add_diff(diff, cx);
19829 }
19830 })
19831 .ok();
19832 })
19833}
19834
19835fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
19836 let tab_size = tab_size.get() as usize;
19837 let mut width = offset;
19838
19839 for ch in text.chars() {
19840 width += if ch == '\t' {
19841 tab_size - (width % tab_size)
19842 } else {
19843 1
19844 };
19845 }
19846
19847 width - offset
19848}
19849
19850#[cfg(test)]
19851mod tests {
19852 use super::*;
19853
19854 #[test]
19855 fn test_string_size_with_expanded_tabs() {
19856 let nz = |val| NonZeroU32::new(val).unwrap();
19857 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
19858 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
19859 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
19860 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
19861 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
19862 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
19863 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
19864 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
19865 }
19866}
19867
19868/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
19869struct WordBreakingTokenizer<'a> {
19870 input: &'a str,
19871}
19872
19873impl<'a> WordBreakingTokenizer<'a> {
19874 fn new(input: &'a str) -> Self {
19875 Self { input }
19876 }
19877}
19878
19879fn is_char_ideographic(ch: char) -> bool {
19880 use unicode_script::Script::*;
19881 use unicode_script::UnicodeScript;
19882 matches!(ch.script(), Han | Tangut | Yi)
19883}
19884
19885fn is_grapheme_ideographic(text: &str) -> bool {
19886 text.chars().any(is_char_ideographic)
19887}
19888
19889fn is_grapheme_whitespace(text: &str) -> bool {
19890 text.chars().any(|x| x.is_whitespace())
19891}
19892
19893fn should_stay_with_preceding_ideograph(text: &str) -> bool {
19894 text.chars().next().map_or(false, |ch| {
19895 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
19896 })
19897}
19898
19899#[derive(PartialEq, Eq, Debug, Clone, Copy)]
19900enum WordBreakToken<'a> {
19901 Word { token: &'a str, grapheme_len: usize },
19902 InlineWhitespace { token: &'a str, grapheme_len: usize },
19903 Newline,
19904}
19905
19906impl<'a> Iterator for WordBreakingTokenizer<'a> {
19907 /// Yields a span, the count of graphemes in the token, and whether it was
19908 /// whitespace. Note that it also breaks at word boundaries.
19909 type Item = WordBreakToken<'a>;
19910
19911 fn next(&mut self) -> Option<Self::Item> {
19912 use unicode_segmentation::UnicodeSegmentation;
19913 if self.input.is_empty() {
19914 return None;
19915 }
19916
19917 let mut iter = self.input.graphemes(true).peekable();
19918 let mut offset = 0;
19919 let mut grapheme_len = 0;
19920 if let Some(first_grapheme) = iter.next() {
19921 let is_newline = first_grapheme == "\n";
19922 let is_whitespace = is_grapheme_whitespace(first_grapheme);
19923 offset += first_grapheme.len();
19924 grapheme_len += 1;
19925 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
19926 if let Some(grapheme) = iter.peek().copied() {
19927 if should_stay_with_preceding_ideograph(grapheme) {
19928 offset += grapheme.len();
19929 grapheme_len += 1;
19930 }
19931 }
19932 } else {
19933 let mut words = self.input[offset..].split_word_bound_indices().peekable();
19934 let mut next_word_bound = words.peek().copied();
19935 if next_word_bound.map_or(false, |(i, _)| i == 0) {
19936 next_word_bound = words.next();
19937 }
19938 while let Some(grapheme) = iter.peek().copied() {
19939 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19940 break;
19941 };
19942 if is_grapheme_whitespace(grapheme) != is_whitespace
19943 || (grapheme == "\n") != is_newline
19944 {
19945 break;
19946 };
19947 offset += grapheme.len();
19948 grapheme_len += 1;
19949 iter.next();
19950 }
19951 }
19952 let token = &self.input[..offset];
19953 self.input = &self.input[offset..];
19954 if token == "\n" {
19955 Some(WordBreakToken::Newline)
19956 } else if is_whitespace {
19957 Some(WordBreakToken::InlineWhitespace {
19958 token,
19959 grapheme_len,
19960 })
19961 } else {
19962 Some(WordBreakToken::Word {
19963 token,
19964 grapheme_len,
19965 })
19966 }
19967 } else {
19968 None
19969 }
19970 }
19971}
19972
19973#[test]
19974fn test_word_breaking_tokenizer() {
19975 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
19976 ("", &[]),
19977 (" ", &[whitespace(" ", 2)]),
19978 ("Ʒ", &[word("Ʒ", 1)]),
19979 ("Ǽ", &[word("Ǽ", 1)]),
19980 ("⋑", &[word("⋑", 1)]),
19981 ("⋑⋑", &[word("⋑⋑", 2)]),
19982 (
19983 "原理,进而",
19984 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
19985 ),
19986 (
19987 "hello world",
19988 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
19989 ),
19990 (
19991 "hello, world",
19992 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
19993 ),
19994 (
19995 " hello world",
19996 &[
19997 whitespace(" ", 2),
19998 word("hello", 5),
19999 whitespace(" ", 1),
20000 word("world", 5),
20001 ],
20002 ),
20003 (
20004 "这是什么 \n 钢笔",
20005 &[
20006 word("这", 1),
20007 word("是", 1),
20008 word("什", 1),
20009 word("么", 1),
20010 whitespace(" ", 1),
20011 newline(),
20012 whitespace(" ", 1),
20013 word("钢", 1),
20014 word("笔", 1),
20015 ],
20016 ),
20017 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
20018 ];
20019
20020 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20021 WordBreakToken::Word {
20022 token,
20023 grapheme_len,
20024 }
20025 }
20026
20027 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20028 WordBreakToken::InlineWhitespace {
20029 token,
20030 grapheme_len,
20031 }
20032 }
20033
20034 fn newline() -> WordBreakToken<'static> {
20035 WordBreakToken::Newline
20036 }
20037
20038 for (input, result) in tests {
20039 assert_eq!(
20040 WordBreakingTokenizer::new(input)
20041 .collect::<Vec<_>>()
20042 .as_slice(),
20043 *result,
20044 );
20045 }
20046}
20047
20048fn wrap_with_prefix(
20049 line_prefix: String,
20050 unwrapped_text: String,
20051 wrap_column: usize,
20052 tab_size: NonZeroU32,
20053 preserve_existing_whitespace: bool,
20054) -> String {
20055 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
20056 let mut wrapped_text = String::new();
20057 let mut current_line = line_prefix.clone();
20058
20059 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
20060 let mut current_line_len = line_prefix_len;
20061 let mut in_whitespace = false;
20062 for token in tokenizer {
20063 let have_preceding_whitespace = in_whitespace;
20064 match token {
20065 WordBreakToken::Word {
20066 token,
20067 grapheme_len,
20068 } => {
20069 in_whitespace = false;
20070 if current_line_len + grapheme_len > wrap_column
20071 && current_line_len != line_prefix_len
20072 {
20073 wrapped_text.push_str(current_line.trim_end());
20074 wrapped_text.push('\n');
20075 current_line.truncate(line_prefix.len());
20076 current_line_len = line_prefix_len;
20077 }
20078 current_line.push_str(token);
20079 current_line_len += grapheme_len;
20080 }
20081 WordBreakToken::InlineWhitespace {
20082 mut token,
20083 mut grapheme_len,
20084 } => {
20085 in_whitespace = true;
20086 if have_preceding_whitespace && !preserve_existing_whitespace {
20087 continue;
20088 }
20089 if !preserve_existing_whitespace {
20090 token = " ";
20091 grapheme_len = 1;
20092 }
20093 if current_line_len + grapheme_len > wrap_column {
20094 wrapped_text.push_str(current_line.trim_end());
20095 wrapped_text.push('\n');
20096 current_line.truncate(line_prefix.len());
20097 current_line_len = line_prefix_len;
20098 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
20099 current_line.push_str(token);
20100 current_line_len += grapheme_len;
20101 }
20102 }
20103 WordBreakToken::Newline => {
20104 in_whitespace = true;
20105 if preserve_existing_whitespace {
20106 wrapped_text.push_str(current_line.trim_end());
20107 wrapped_text.push('\n');
20108 current_line.truncate(line_prefix.len());
20109 current_line_len = line_prefix_len;
20110 } else if have_preceding_whitespace {
20111 continue;
20112 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
20113 {
20114 wrapped_text.push_str(current_line.trim_end());
20115 wrapped_text.push('\n');
20116 current_line.truncate(line_prefix.len());
20117 current_line_len = line_prefix_len;
20118 } else if current_line_len != line_prefix_len {
20119 current_line.push(' ');
20120 current_line_len += 1;
20121 }
20122 }
20123 }
20124 }
20125
20126 if !current_line.is_empty() {
20127 wrapped_text.push_str(¤t_line);
20128 }
20129 wrapped_text
20130}
20131
20132#[test]
20133fn test_wrap_with_prefix() {
20134 assert_eq!(
20135 wrap_with_prefix(
20136 "# ".to_string(),
20137 "abcdefg".to_string(),
20138 4,
20139 NonZeroU32::new(4).unwrap(),
20140 false,
20141 ),
20142 "# abcdefg"
20143 );
20144 assert_eq!(
20145 wrap_with_prefix(
20146 "".to_string(),
20147 "\thello world".to_string(),
20148 8,
20149 NonZeroU32::new(4).unwrap(),
20150 false,
20151 ),
20152 "hello\nworld"
20153 );
20154 assert_eq!(
20155 wrap_with_prefix(
20156 "// ".to_string(),
20157 "xx \nyy zz aa bb cc".to_string(),
20158 12,
20159 NonZeroU32::new(4).unwrap(),
20160 false,
20161 ),
20162 "// xx yy zz\n// aa bb cc"
20163 );
20164 assert_eq!(
20165 wrap_with_prefix(
20166 String::new(),
20167 "这是什么 \n 钢笔".to_string(),
20168 3,
20169 NonZeroU32::new(4).unwrap(),
20170 false,
20171 ),
20172 "这是什\n么 钢\n笔"
20173 );
20174}
20175
20176pub trait CollaborationHub {
20177 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20178 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20179 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20180}
20181
20182impl CollaborationHub for Entity<Project> {
20183 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20184 self.read(cx).collaborators()
20185 }
20186
20187 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
20188 self.read(cx).user_store().read(cx).participant_indices()
20189 }
20190
20191 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
20192 let this = self.read(cx);
20193 let user_ids = this.collaborators().values().map(|c| c.user_id);
20194 this.user_store().read(cx).participant_names(user_ids, cx)
20195 }
20196}
20197
20198pub trait SemanticsProvider {
20199 fn hover(
20200 &self,
20201 buffer: &Entity<Buffer>,
20202 position: text::Anchor,
20203 cx: &mut App,
20204 ) -> Option<Task<Vec<project::Hover>>>;
20205
20206 fn inline_values(
20207 &self,
20208 buffer_handle: Entity<Buffer>,
20209 range: Range<text::Anchor>,
20210 cx: &mut App,
20211 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20212
20213 fn inlay_hints(
20214 &self,
20215 buffer_handle: Entity<Buffer>,
20216 range: Range<text::Anchor>,
20217 cx: &mut App,
20218 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20219
20220 fn resolve_inlay_hint(
20221 &self,
20222 hint: InlayHint,
20223 buffer_handle: Entity<Buffer>,
20224 server_id: LanguageServerId,
20225 cx: &mut App,
20226 ) -> Option<Task<anyhow::Result<InlayHint>>>;
20227
20228 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
20229
20230 fn document_highlights(
20231 &self,
20232 buffer: &Entity<Buffer>,
20233 position: text::Anchor,
20234 cx: &mut App,
20235 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
20236
20237 fn definitions(
20238 &self,
20239 buffer: &Entity<Buffer>,
20240 position: text::Anchor,
20241 kind: GotoDefinitionKind,
20242 cx: &mut App,
20243 ) -> Option<Task<Result<Vec<LocationLink>>>>;
20244
20245 fn range_for_rename(
20246 &self,
20247 buffer: &Entity<Buffer>,
20248 position: text::Anchor,
20249 cx: &mut App,
20250 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
20251
20252 fn perform_rename(
20253 &self,
20254 buffer: &Entity<Buffer>,
20255 position: text::Anchor,
20256 new_name: String,
20257 cx: &mut App,
20258 ) -> Option<Task<Result<ProjectTransaction>>>;
20259}
20260
20261pub trait CompletionProvider {
20262 fn completions(
20263 &self,
20264 excerpt_id: ExcerptId,
20265 buffer: &Entity<Buffer>,
20266 buffer_position: text::Anchor,
20267 trigger: CompletionContext,
20268 window: &mut Window,
20269 cx: &mut Context<Editor>,
20270 ) -> Task<Result<Vec<CompletionResponse>>>;
20271
20272 fn resolve_completions(
20273 &self,
20274 buffer: Entity<Buffer>,
20275 completion_indices: Vec<usize>,
20276 completions: Rc<RefCell<Box<[Completion]>>>,
20277 cx: &mut Context<Editor>,
20278 ) -> Task<Result<bool>>;
20279
20280 fn apply_additional_edits_for_completion(
20281 &self,
20282 _buffer: Entity<Buffer>,
20283 _completions: Rc<RefCell<Box<[Completion]>>>,
20284 _completion_index: usize,
20285 _push_to_history: bool,
20286 _cx: &mut Context<Editor>,
20287 ) -> Task<Result<Option<language::Transaction>>> {
20288 Task::ready(Ok(None))
20289 }
20290
20291 fn is_completion_trigger(
20292 &self,
20293 buffer: &Entity<Buffer>,
20294 position: language::Anchor,
20295 text: &str,
20296 trigger_in_words: bool,
20297 cx: &mut Context<Editor>,
20298 ) -> bool;
20299
20300 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
20301
20302 fn sort_completions(&self) -> bool {
20303 true
20304 }
20305
20306 fn filter_completions(&self) -> bool {
20307 true
20308 }
20309}
20310
20311pub trait CodeActionProvider {
20312 fn id(&self) -> Arc<str>;
20313
20314 fn code_actions(
20315 &self,
20316 buffer: &Entity<Buffer>,
20317 range: Range<text::Anchor>,
20318 window: &mut Window,
20319 cx: &mut App,
20320 ) -> Task<Result<Vec<CodeAction>>>;
20321
20322 fn apply_code_action(
20323 &self,
20324 buffer_handle: Entity<Buffer>,
20325 action: CodeAction,
20326 excerpt_id: ExcerptId,
20327 push_to_history: bool,
20328 window: &mut Window,
20329 cx: &mut App,
20330 ) -> Task<Result<ProjectTransaction>>;
20331}
20332
20333impl CodeActionProvider for Entity<Project> {
20334 fn id(&self) -> Arc<str> {
20335 "project".into()
20336 }
20337
20338 fn code_actions(
20339 &self,
20340 buffer: &Entity<Buffer>,
20341 range: Range<text::Anchor>,
20342 _window: &mut Window,
20343 cx: &mut App,
20344 ) -> Task<Result<Vec<CodeAction>>> {
20345 self.update(cx, |project, cx| {
20346 let code_lens = project.code_lens(buffer, range.clone(), cx);
20347 let code_actions = project.code_actions(buffer, range, None, cx);
20348 cx.background_spawn(async move {
20349 let (code_lens, code_actions) = join(code_lens, code_actions).await;
20350 Ok(code_lens
20351 .context("code lens fetch")?
20352 .into_iter()
20353 .chain(code_actions.context("code action fetch")?)
20354 .collect())
20355 })
20356 })
20357 }
20358
20359 fn apply_code_action(
20360 &self,
20361 buffer_handle: Entity<Buffer>,
20362 action: CodeAction,
20363 _excerpt_id: ExcerptId,
20364 push_to_history: bool,
20365 _window: &mut Window,
20366 cx: &mut App,
20367 ) -> Task<Result<ProjectTransaction>> {
20368 self.update(cx, |project, cx| {
20369 project.apply_code_action(buffer_handle, action, push_to_history, cx)
20370 })
20371 }
20372}
20373
20374fn snippet_completions(
20375 project: &Project,
20376 buffer: &Entity<Buffer>,
20377 buffer_position: text::Anchor,
20378 cx: &mut App,
20379) -> Task<Result<CompletionResponse>> {
20380 let languages = buffer.read(cx).languages_at(buffer_position);
20381 let snippet_store = project.snippets().read(cx);
20382
20383 let scopes: Vec<_> = languages
20384 .iter()
20385 .filter_map(|language| {
20386 let language_name = language.lsp_id();
20387 let snippets = snippet_store.snippets_for(Some(language_name), cx);
20388
20389 if snippets.is_empty() {
20390 None
20391 } else {
20392 Some((language.default_scope(), snippets))
20393 }
20394 })
20395 .collect();
20396
20397 if scopes.is_empty() {
20398 return Task::ready(Ok(CompletionResponse {
20399 completions: vec![],
20400 is_incomplete: false,
20401 }));
20402 }
20403
20404 let snapshot = buffer.read(cx).text_snapshot();
20405 let chars: String = snapshot
20406 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
20407 .collect();
20408 let executor = cx.background_executor().clone();
20409
20410 cx.background_spawn(async move {
20411 let mut is_incomplete = false;
20412 let mut completions: Vec<Completion> = Vec::new();
20413 for (scope, snippets) in scopes.into_iter() {
20414 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
20415 let mut last_word = chars
20416 .chars()
20417 .take_while(|c| classifier.is_word(*c))
20418 .collect::<String>();
20419 last_word = last_word.chars().rev().collect();
20420
20421 if last_word.is_empty() {
20422 return Ok(CompletionResponse {
20423 completions: vec![],
20424 is_incomplete: true,
20425 });
20426 }
20427
20428 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
20429 let to_lsp = |point: &text::Anchor| {
20430 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
20431 point_to_lsp(end)
20432 };
20433 let lsp_end = to_lsp(&buffer_position);
20434
20435 let candidates = snippets
20436 .iter()
20437 .enumerate()
20438 .flat_map(|(ix, snippet)| {
20439 snippet
20440 .prefix
20441 .iter()
20442 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
20443 })
20444 .collect::<Vec<StringMatchCandidate>>();
20445
20446 const MAX_RESULTS: usize = 100;
20447 let mut matches = fuzzy::match_strings(
20448 &candidates,
20449 &last_word,
20450 last_word.chars().any(|c| c.is_uppercase()),
20451 MAX_RESULTS,
20452 &Default::default(),
20453 executor.clone(),
20454 )
20455 .await;
20456
20457 if matches.len() >= MAX_RESULTS {
20458 is_incomplete = true;
20459 }
20460
20461 // Remove all candidates where the query's start does not match the start of any word in the candidate
20462 if let Some(query_start) = last_word.chars().next() {
20463 matches.retain(|string_match| {
20464 split_words(&string_match.string).any(|word| {
20465 // Check that the first codepoint of the word as lowercase matches the first
20466 // codepoint of the query as lowercase
20467 word.chars()
20468 .flat_map(|codepoint| codepoint.to_lowercase())
20469 .zip(query_start.to_lowercase())
20470 .all(|(word_cp, query_cp)| word_cp == query_cp)
20471 })
20472 });
20473 }
20474
20475 let matched_strings = matches
20476 .into_iter()
20477 .map(|m| m.string)
20478 .collect::<HashSet<_>>();
20479
20480 completions.extend(snippets.iter().filter_map(|snippet| {
20481 let matching_prefix = snippet
20482 .prefix
20483 .iter()
20484 .find(|prefix| matched_strings.contains(*prefix))?;
20485 let start = as_offset - last_word.len();
20486 let start = snapshot.anchor_before(start);
20487 let range = start..buffer_position;
20488 let lsp_start = to_lsp(&start);
20489 let lsp_range = lsp::Range {
20490 start: lsp_start,
20491 end: lsp_end,
20492 };
20493 Some(Completion {
20494 replace_range: range,
20495 new_text: snippet.body.clone(),
20496 source: CompletionSource::Lsp {
20497 insert_range: None,
20498 server_id: LanguageServerId(usize::MAX),
20499 resolved: true,
20500 lsp_completion: Box::new(lsp::CompletionItem {
20501 label: snippet.prefix.first().unwrap().clone(),
20502 kind: Some(CompletionItemKind::SNIPPET),
20503 label_details: snippet.description.as_ref().map(|description| {
20504 lsp::CompletionItemLabelDetails {
20505 detail: Some(description.clone()),
20506 description: None,
20507 }
20508 }),
20509 insert_text_format: Some(InsertTextFormat::SNIPPET),
20510 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20511 lsp::InsertReplaceEdit {
20512 new_text: snippet.body.clone(),
20513 insert: lsp_range,
20514 replace: lsp_range,
20515 },
20516 )),
20517 filter_text: Some(snippet.body.clone()),
20518 sort_text: Some(char::MAX.to_string()),
20519 ..lsp::CompletionItem::default()
20520 }),
20521 lsp_defaults: None,
20522 },
20523 label: CodeLabel {
20524 text: matching_prefix.clone(),
20525 runs: Vec::new(),
20526 filter_range: 0..matching_prefix.len(),
20527 },
20528 icon_path: None,
20529 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
20530 single_line: snippet.name.clone().into(),
20531 plain_text: snippet
20532 .description
20533 .clone()
20534 .map(|description| description.into()),
20535 }),
20536 insert_text_mode: None,
20537 confirm: None,
20538 })
20539 }))
20540 }
20541
20542 Ok(CompletionResponse {
20543 completions,
20544 is_incomplete,
20545 })
20546 })
20547}
20548
20549impl CompletionProvider for Entity<Project> {
20550 fn completions(
20551 &self,
20552 _excerpt_id: ExcerptId,
20553 buffer: &Entity<Buffer>,
20554 buffer_position: text::Anchor,
20555 options: CompletionContext,
20556 _window: &mut Window,
20557 cx: &mut Context<Editor>,
20558 ) -> Task<Result<Vec<CompletionResponse>>> {
20559 self.update(cx, |project, cx| {
20560 let snippets = snippet_completions(project, buffer, buffer_position, cx);
20561 let project_completions = project.completions(buffer, buffer_position, options, cx);
20562 cx.background_spawn(async move {
20563 let mut responses = project_completions.await?;
20564 let snippets = snippets.await?;
20565 if !snippets.completions.is_empty() {
20566 responses.push(snippets);
20567 }
20568 Ok(responses)
20569 })
20570 })
20571 }
20572
20573 fn resolve_completions(
20574 &self,
20575 buffer: Entity<Buffer>,
20576 completion_indices: Vec<usize>,
20577 completions: Rc<RefCell<Box<[Completion]>>>,
20578 cx: &mut Context<Editor>,
20579 ) -> Task<Result<bool>> {
20580 self.update(cx, |project, cx| {
20581 project.lsp_store().update(cx, |lsp_store, cx| {
20582 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
20583 })
20584 })
20585 }
20586
20587 fn apply_additional_edits_for_completion(
20588 &self,
20589 buffer: Entity<Buffer>,
20590 completions: Rc<RefCell<Box<[Completion]>>>,
20591 completion_index: usize,
20592 push_to_history: bool,
20593 cx: &mut Context<Editor>,
20594 ) -> Task<Result<Option<language::Transaction>>> {
20595 self.update(cx, |project, cx| {
20596 project.lsp_store().update(cx, |lsp_store, cx| {
20597 lsp_store.apply_additional_edits_for_completion(
20598 buffer,
20599 completions,
20600 completion_index,
20601 push_to_history,
20602 cx,
20603 )
20604 })
20605 })
20606 }
20607
20608 fn is_completion_trigger(
20609 &self,
20610 buffer: &Entity<Buffer>,
20611 position: language::Anchor,
20612 text: &str,
20613 trigger_in_words: bool,
20614 cx: &mut Context<Editor>,
20615 ) -> bool {
20616 let mut chars = text.chars();
20617 let char = if let Some(char) = chars.next() {
20618 char
20619 } else {
20620 return false;
20621 };
20622 if chars.next().is_some() {
20623 return false;
20624 }
20625
20626 let buffer = buffer.read(cx);
20627 let snapshot = buffer.snapshot();
20628 if !snapshot.settings_at(position, cx).show_completions_on_input {
20629 return false;
20630 }
20631 let classifier = snapshot.char_classifier_at(position).for_completion(true);
20632 if trigger_in_words && classifier.is_word(char) {
20633 return true;
20634 }
20635
20636 buffer.completion_triggers().contains(text)
20637 }
20638}
20639
20640impl SemanticsProvider for Entity<Project> {
20641 fn hover(
20642 &self,
20643 buffer: &Entity<Buffer>,
20644 position: text::Anchor,
20645 cx: &mut App,
20646 ) -> Option<Task<Vec<project::Hover>>> {
20647 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
20648 }
20649
20650 fn document_highlights(
20651 &self,
20652 buffer: &Entity<Buffer>,
20653 position: text::Anchor,
20654 cx: &mut App,
20655 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
20656 Some(self.update(cx, |project, cx| {
20657 project.document_highlights(buffer, position, cx)
20658 }))
20659 }
20660
20661 fn definitions(
20662 &self,
20663 buffer: &Entity<Buffer>,
20664 position: text::Anchor,
20665 kind: GotoDefinitionKind,
20666 cx: &mut App,
20667 ) -> Option<Task<Result<Vec<LocationLink>>>> {
20668 Some(self.update(cx, |project, cx| match kind {
20669 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
20670 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
20671 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
20672 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
20673 }))
20674 }
20675
20676 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
20677 // TODO: make this work for remote projects
20678 self.update(cx, |project, cx| {
20679 if project
20680 .active_debug_session(cx)
20681 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
20682 {
20683 return true;
20684 }
20685
20686 buffer.update(cx, |buffer, cx| {
20687 project.any_language_server_supports_inlay_hints(buffer, cx)
20688 })
20689 })
20690 }
20691
20692 fn inline_values(
20693 &self,
20694 buffer_handle: Entity<Buffer>,
20695
20696 range: Range<text::Anchor>,
20697 cx: &mut App,
20698 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20699 self.update(cx, |project, cx| {
20700 let (session, active_stack_frame) = project.active_debug_session(cx)?;
20701
20702 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
20703 })
20704 }
20705
20706 fn inlay_hints(
20707 &self,
20708 buffer_handle: Entity<Buffer>,
20709 range: Range<text::Anchor>,
20710 cx: &mut App,
20711 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20712 Some(self.update(cx, |project, cx| {
20713 project.inlay_hints(buffer_handle, range, cx)
20714 }))
20715 }
20716
20717 fn resolve_inlay_hint(
20718 &self,
20719 hint: InlayHint,
20720 buffer_handle: Entity<Buffer>,
20721 server_id: LanguageServerId,
20722 cx: &mut App,
20723 ) -> Option<Task<anyhow::Result<InlayHint>>> {
20724 Some(self.update(cx, |project, cx| {
20725 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
20726 }))
20727 }
20728
20729 fn range_for_rename(
20730 &self,
20731 buffer: &Entity<Buffer>,
20732 position: text::Anchor,
20733 cx: &mut App,
20734 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
20735 Some(self.update(cx, |project, cx| {
20736 let buffer = buffer.clone();
20737 let task = project.prepare_rename(buffer.clone(), position, cx);
20738 cx.spawn(async move |_, cx| {
20739 Ok(match task.await? {
20740 PrepareRenameResponse::Success(range) => Some(range),
20741 PrepareRenameResponse::InvalidPosition => None,
20742 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
20743 // Fallback on using TreeSitter info to determine identifier range
20744 buffer.read_with(cx, |buffer, _| {
20745 let snapshot = buffer.snapshot();
20746 let (range, kind) = snapshot.surrounding_word(position);
20747 if kind != Some(CharKind::Word) {
20748 return None;
20749 }
20750 Some(
20751 snapshot.anchor_before(range.start)
20752 ..snapshot.anchor_after(range.end),
20753 )
20754 })?
20755 }
20756 })
20757 })
20758 }))
20759 }
20760
20761 fn perform_rename(
20762 &self,
20763 buffer: &Entity<Buffer>,
20764 position: text::Anchor,
20765 new_name: String,
20766 cx: &mut App,
20767 ) -> Option<Task<Result<ProjectTransaction>>> {
20768 Some(self.update(cx, |project, cx| {
20769 project.perform_rename(buffer.clone(), position, new_name, cx)
20770 }))
20771 }
20772}
20773
20774fn inlay_hint_settings(
20775 location: Anchor,
20776 snapshot: &MultiBufferSnapshot,
20777 cx: &mut Context<Editor>,
20778) -> InlayHintSettings {
20779 let file = snapshot.file_at(location);
20780 let language = snapshot.language_at(location).map(|l| l.name());
20781 language_settings(language, file, cx).inlay_hints
20782}
20783
20784fn consume_contiguous_rows(
20785 contiguous_row_selections: &mut Vec<Selection<Point>>,
20786 selection: &Selection<Point>,
20787 display_map: &DisplaySnapshot,
20788 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
20789) -> (MultiBufferRow, MultiBufferRow) {
20790 contiguous_row_selections.push(selection.clone());
20791 let start_row = MultiBufferRow(selection.start.row);
20792 let mut end_row = ending_row(selection, display_map);
20793
20794 while let Some(next_selection) = selections.peek() {
20795 if next_selection.start.row <= end_row.0 {
20796 end_row = ending_row(next_selection, display_map);
20797 contiguous_row_selections.push(selections.next().unwrap().clone());
20798 } else {
20799 break;
20800 }
20801 }
20802 (start_row, end_row)
20803}
20804
20805fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
20806 if next_selection.end.column > 0 || next_selection.is_empty() {
20807 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
20808 } else {
20809 MultiBufferRow(next_selection.end.row)
20810 }
20811}
20812
20813impl EditorSnapshot {
20814 pub fn remote_selections_in_range<'a>(
20815 &'a self,
20816 range: &'a Range<Anchor>,
20817 collaboration_hub: &dyn CollaborationHub,
20818 cx: &'a App,
20819 ) -> impl 'a + Iterator<Item = RemoteSelection> {
20820 let participant_names = collaboration_hub.user_names(cx);
20821 let participant_indices = collaboration_hub.user_participant_indices(cx);
20822 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20823 let collaborators_by_replica_id = collaborators_by_peer_id
20824 .values()
20825 .map(|collaborator| (collaborator.replica_id, collaborator))
20826 .collect::<HashMap<_, _>>();
20827 self.buffer_snapshot
20828 .selections_in_range(range, false)
20829 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20830 if replica_id == AGENT_REPLICA_ID {
20831 Some(RemoteSelection {
20832 replica_id,
20833 selection,
20834 cursor_shape,
20835 line_mode,
20836 collaborator_id: CollaboratorId::Agent,
20837 user_name: Some("Agent".into()),
20838 color: cx.theme().players().agent(),
20839 })
20840 } else {
20841 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20842 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20843 let user_name = participant_names.get(&collaborator.user_id).cloned();
20844 Some(RemoteSelection {
20845 replica_id,
20846 selection,
20847 cursor_shape,
20848 line_mode,
20849 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20850 user_name,
20851 color: if let Some(index) = participant_index {
20852 cx.theme().players().color_for_participant(index.0)
20853 } else {
20854 cx.theme().players().absent()
20855 },
20856 })
20857 }
20858 })
20859 }
20860
20861 pub fn hunks_for_ranges(
20862 &self,
20863 ranges: impl IntoIterator<Item = Range<Point>>,
20864 ) -> Vec<MultiBufferDiffHunk> {
20865 let mut hunks = Vec::new();
20866 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20867 HashMap::default();
20868 for query_range in ranges {
20869 let query_rows =
20870 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20871 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20872 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20873 ) {
20874 // Include deleted hunks that are adjacent to the query range, because
20875 // otherwise they would be missed.
20876 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20877 if hunk.status().is_deleted() {
20878 intersects_range |= hunk.row_range.start == query_rows.end;
20879 intersects_range |= hunk.row_range.end == query_rows.start;
20880 }
20881 if intersects_range {
20882 if !processed_buffer_rows
20883 .entry(hunk.buffer_id)
20884 .or_default()
20885 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20886 {
20887 continue;
20888 }
20889 hunks.push(hunk);
20890 }
20891 }
20892 }
20893
20894 hunks
20895 }
20896
20897 fn display_diff_hunks_for_rows<'a>(
20898 &'a self,
20899 display_rows: Range<DisplayRow>,
20900 folded_buffers: &'a HashSet<BufferId>,
20901 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20902 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20903 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20904
20905 self.buffer_snapshot
20906 .diff_hunks_in_range(buffer_start..buffer_end)
20907 .filter_map(|hunk| {
20908 if folded_buffers.contains(&hunk.buffer_id) {
20909 return None;
20910 }
20911
20912 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20913 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20914
20915 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20916 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20917
20918 let display_hunk = if hunk_display_start.column() != 0 {
20919 DisplayDiffHunk::Folded {
20920 display_row: hunk_display_start.row(),
20921 }
20922 } else {
20923 let mut end_row = hunk_display_end.row();
20924 if hunk_display_end.column() > 0 {
20925 end_row.0 += 1;
20926 }
20927 let is_created_file = hunk.is_created_file();
20928 DisplayDiffHunk::Unfolded {
20929 status: hunk.status(),
20930 diff_base_byte_range: hunk.diff_base_byte_range,
20931 display_row_range: hunk_display_start.row()..end_row,
20932 multi_buffer_range: Anchor::range_in_buffer(
20933 hunk.excerpt_id,
20934 hunk.buffer_id,
20935 hunk.buffer_range,
20936 ),
20937 is_created_file,
20938 }
20939 };
20940
20941 Some(display_hunk)
20942 })
20943 }
20944
20945 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20946 self.display_snapshot.buffer_snapshot.language_at(position)
20947 }
20948
20949 pub fn is_focused(&self) -> bool {
20950 self.is_focused
20951 }
20952
20953 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20954 self.placeholder_text.as_ref()
20955 }
20956
20957 pub fn scroll_position(&self) -> gpui::Point<f32> {
20958 self.scroll_anchor.scroll_position(&self.display_snapshot)
20959 }
20960
20961 fn gutter_dimensions(
20962 &self,
20963 font_id: FontId,
20964 font_size: Pixels,
20965 max_line_number_width: Pixels,
20966 cx: &App,
20967 ) -> Option<GutterDimensions> {
20968 if !self.show_gutter {
20969 return None;
20970 }
20971
20972 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20973 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20974
20975 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20976 matches!(
20977 ProjectSettings::get_global(cx).git.git_gutter,
20978 Some(GitGutterSetting::TrackedFiles)
20979 )
20980 });
20981 let gutter_settings = EditorSettings::get_global(cx).gutter;
20982 let show_line_numbers = self
20983 .show_line_numbers
20984 .unwrap_or(gutter_settings.line_numbers);
20985 let line_gutter_width = if show_line_numbers {
20986 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20987 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20988 max_line_number_width.max(min_width_for_number_on_gutter)
20989 } else {
20990 0.0.into()
20991 };
20992
20993 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20994 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20995
20996 let git_blame_entries_width =
20997 self.git_blame_gutter_max_author_length
20998 .map(|max_author_length| {
20999 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21000 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
21001
21002 /// The number of characters to dedicate to gaps and margins.
21003 const SPACING_WIDTH: usize = 4;
21004
21005 let max_char_count = max_author_length.min(renderer.max_author_length())
21006 + ::git::SHORT_SHA_LENGTH
21007 + MAX_RELATIVE_TIMESTAMP.len()
21008 + SPACING_WIDTH;
21009
21010 em_advance * max_char_count
21011 });
21012
21013 let is_singleton = self.buffer_snapshot.is_singleton();
21014
21015 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
21016 left_padding += if !is_singleton {
21017 em_width * 4.0
21018 } else if show_runnables || show_breakpoints {
21019 em_width * 3.0
21020 } else if show_git_gutter && show_line_numbers {
21021 em_width * 2.0
21022 } else if show_git_gutter || show_line_numbers {
21023 em_width
21024 } else {
21025 px(0.)
21026 };
21027
21028 let shows_folds = is_singleton && gutter_settings.folds;
21029
21030 let right_padding = if shows_folds && show_line_numbers {
21031 em_width * 4.0
21032 } else if shows_folds || (!is_singleton && show_line_numbers) {
21033 em_width * 3.0
21034 } else if show_line_numbers {
21035 em_width
21036 } else {
21037 px(0.)
21038 };
21039
21040 Some(GutterDimensions {
21041 left_padding,
21042 right_padding,
21043 width: line_gutter_width + left_padding + right_padding,
21044 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
21045 git_blame_entries_width,
21046 })
21047 }
21048
21049 pub fn render_crease_toggle(
21050 &self,
21051 buffer_row: MultiBufferRow,
21052 row_contains_cursor: bool,
21053 editor: Entity<Editor>,
21054 window: &mut Window,
21055 cx: &mut App,
21056 ) -> Option<AnyElement> {
21057 let folded = self.is_line_folded(buffer_row);
21058 let mut is_foldable = false;
21059
21060 if let Some(crease) = self
21061 .crease_snapshot
21062 .query_row(buffer_row, &self.buffer_snapshot)
21063 {
21064 is_foldable = true;
21065 match crease {
21066 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
21067 if let Some(render_toggle) = render_toggle {
21068 let toggle_callback =
21069 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
21070 if folded {
21071 editor.update(cx, |editor, cx| {
21072 editor.fold_at(buffer_row, window, cx)
21073 });
21074 } else {
21075 editor.update(cx, |editor, cx| {
21076 editor.unfold_at(buffer_row, window, cx)
21077 });
21078 }
21079 });
21080 return Some((render_toggle)(
21081 buffer_row,
21082 folded,
21083 toggle_callback,
21084 window,
21085 cx,
21086 ));
21087 }
21088 }
21089 }
21090 }
21091
21092 is_foldable |= self.starts_indent(buffer_row);
21093
21094 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
21095 Some(
21096 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
21097 .toggle_state(folded)
21098 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
21099 if folded {
21100 this.unfold_at(buffer_row, window, cx);
21101 } else {
21102 this.fold_at(buffer_row, window, cx);
21103 }
21104 }))
21105 .into_any_element(),
21106 )
21107 } else {
21108 None
21109 }
21110 }
21111
21112 pub fn render_crease_trailer(
21113 &self,
21114 buffer_row: MultiBufferRow,
21115 window: &mut Window,
21116 cx: &mut App,
21117 ) -> Option<AnyElement> {
21118 let folded = self.is_line_folded(buffer_row);
21119 if let Crease::Inline { render_trailer, .. } = self
21120 .crease_snapshot
21121 .query_row(buffer_row, &self.buffer_snapshot)?
21122 {
21123 let render_trailer = render_trailer.as_ref()?;
21124 Some(render_trailer(buffer_row, folded, window, cx))
21125 } else {
21126 None
21127 }
21128 }
21129}
21130
21131impl Deref for EditorSnapshot {
21132 type Target = DisplaySnapshot;
21133
21134 fn deref(&self) -> &Self::Target {
21135 &self.display_snapshot
21136 }
21137}
21138
21139#[derive(Clone, Debug, PartialEq, Eq)]
21140pub enum EditorEvent {
21141 InputIgnored {
21142 text: Arc<str>,
21143 },
21144 InputHandled {
21145 utf16_range_to_replace: Option<Range<isize>>,
21146 text: Arc<str>,
21147 },
21148 ExcerptsAdded {
21149 buffer: Entity<Buffer>,
21150 predecessor: ExcerptId,
21151 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21152 },
21153 ExcerptsRemoved {
21154 ids: Vec<ExcerptId>,
21155 removed_buffer_ids: Vec<BufferId>,
21156 },
21157 BufferFoldToggled {
21158 ids: Vec<ExcerptId>,
21159 folded: bool,
21160 },
21161 ExcerptsEdited {
21162 ids: Vec<ExcerptId>,
21163 },
21164 ExcerptsExpanded {
21165 ids: Vec<ExcerptId>,
21166 },
21167 BufferEdited,
21168 Edited {
21169 transaction_id: clock::Lamport,
21170 },
21171 Reparsed(BufferId),
21172 Focused,
21173 FocusedIn,
21174 Blurred,
21175 DirtyChanged,
21176 Saved,
21177 TitleChanged,
21178 DiffBaseChanged,
21179 SelectionsChanged {
21180 local: bool,
21181 },
21182 ScrollPositionChanged {
21183 local: bool,
21184 autoscroll: bool,
21185 },
21186 Closed,
21187 TransactionUndone {
21188 transaction_id: clock::Lamport,
21189 },
21190 TransactionBegun {
21191 transaction_id: clock::Lamport,
21192 },
21193 Reloaded,
21194 CursorShapeChanged,
21195 PushedToNavHistory {
21196 anchor: Anchor,
21197 is_deactivate: bool,
21198 },
21199}
21200
21201impl EventEmitter<EditorEvent> for Editor {}
21202
21203impl Focusable for Editor {
21204 fn focus_handle(&self, _cx: &App) -> FocusHandle {
21205 self.focus_handle.clone()
21206 }
21207}
21208
21209impl Render for Editor {
21210 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21211 let settings = ThemeSettings::get_global(cx);
21212
21213 let mut text_style = match self.mode {
21214 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21215 color: cx.theme().colors().editor_foreground,
21216 font_family: settings.ui_font.family.clone(),
21217 font_features: settings.ui_font.features.clone(),
21218 font_fallbacks: settings.ui_font.fallbacks.clone(),
21219 font_size: rems(0.875).into(),
21220 font_weight: settings.ui_font.weight,
21221 line_height: relative(settings.buffer_line_height.value()),
21222 ..Default::default()
21223 },
21224 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
21225 color: cx.theme().colors().editor_foreground,
21226 font_family: settings.buffer_font.family.clone(),
21227 font_features: settings.buffer_font.features.clone(),
21228 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21229 font_size: settings.buffer_font_size(cx).into(),
21230 font_weight: settings.buffer_font.weight,
21231 line_height: relative(settings.buffer_line_height.value()),
21232 ..Default::default()
21233 },
21234 };
21235 if let Some(text_style_refinement) = &self.text_style_refinement {
21236 text_style.refine(text_style_refinement)
21237 }
21238
21239 let background = match self.mode {
21240 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
21241 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
21242 EditorMode::Full { .. } => cx.theme().colors().editor_background,
21243 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
21244 };
21245
21246 EditorElement::new(
21247 &cx.entity(),
21248 EditorStyle {
21249 background,
21250 local_player: cx.theme().players().local(),
21251 text: text_style,
21252 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
21253 syntax: cx.theme().syntax().clone(),
21254 status: cx.theme().status().clone(),
21255 inlay_hints_style: make_inlay_hints_style(cx),
21256 inline_completion_styles: make_suggestion_styles(cx),
21257 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
21258 show_underlines: !self.mode.is_minimap(),
21259 },
21260 )
21261 }
21262}
21263
21264impl EntityInputHandler for Editor {
21265 fn text_for_range(
21266 &mut self,
21267 range_utf16: Range<usize>,
21268 adjusted_range: &mut Option<Range<usize>>,
21269 _: &mut Window,
21270 cx: &mut Context<Self>,
21271 ) -> Option<String> {
21272 let snapshot = self.buffer.read(cx).read(cx);
21273 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
21274 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
21275 if (start.0..end.0) != range_utf16 {
21276 adjusted_range.replace(start.0..end.0);
21277 }
21278 Some(snapshot.text_for_range(start..end).collect())
21279 }
21280
21281 fn selected_text_range(
21282 &mut self,
21283 ignore_disabled_input: bool,
21284 _: &mut Window,
21285 cx: &mut Context<Self>,
21286 ) -> Option<UTF16Selection> {
21287 // Prevent the IME menu from appearing when holding down an alphabetic key
21288 // while input is disabled.
21289 if !ignore_disabled_input && !self.input_enabled {
21290 return None;
21291 }
21292
21293 let selection = self.selections.newest::<OffsetUtf16>(cx);
21294 let range = selection.range();
21295
21296 Some(UTF16Selection {
21297 range: range.start.0..range.end.0,
21298 reversed: selection.reversed,
21299 })
21300 }
21301
21302 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
21303 let snapshot = self.buffer.read(cx).read(cx);
21304 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
21305 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
21306 }
21307
21308 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21309 self.clear_highlights::<InputComposition>(cx);
21310 self.ime_transaction.take();
21311 }
21312
21313 fn replace_text_in_range(
21314 &mut self,
21315 range_utf16: Option<Range<usize>>,
21316 text: &str,
21317 window: &mut Window,
21318 cx: &mut Context<Self>,
21319 ) {
21320 if !self.input_enabled {
21321 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21322 return;
21323 }
21324
21325 self.transact(window, cx, |this, window, cx| {
21326 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
21327 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21328 Some(this.selection_replacement_ranges(range_utf16, cx))
21329 } else {
21330 this.marked_text_ranges(cx)
21331 };
21332
21333 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
21334 let newest_selection_id = this.selections.newest_anchor().id;
21335 this.selections
21336 .all::<OffsetUtf16>(cx)
21337 .iter()
21338 .zip(ranges_to_replace.iter())
21339 .find_map(|(selection, range)| {
21340 if selection.id == newest_selection_id {
21341 Some(
21342 (range.start.0 as isize - selection.head().0 as isize)
21343 ..(range.end.0 as isize - selection.head().0 as isize),
21344 )
21345 } else {
21346 None
21347 }
21348 })
21349 });
21350
21351 cx.emit(EditorEvent::InputHandled {
21352 utf16_range_to_replace: range_to_replace,
21353 text: text.into(),
21354 });
21355
21356 if let Some(new_selected_ranges) = new_selected_ranges {
21357 this.change_selections(None, window, cx, |selections| {
21358 selections.select_ranges(new_selected_ranges)
21359 });
21360 this.backspace(&Default::default(), window, cx);
21361 }
21362
21363 this.handle_input(text, window, cx);
21364 });
21365
21366 if let Some(transaction) = self.ime_transaction {
21367 self.buffer.update(cx, |buffer, cx| {
21368 buffer.group_until_transaction(transaction, cx);
21369 });
21370 }
21371
21372 self.unmark_text(window, cx);
21373 }
21374
21375 fn replace_and_mark_text_in_range(
21376 &mut self,
21377 range_utf16: Option<Range<usize>>,
21378 text: &str,
21379 new_selected_range_utf16: Option<Range<usize>>,
21380 window: &mut Window,
21381 cx: &mut Context<Self>,
21382 ) {
21383 if !self.input_enabled {
21384 return;
21385 }
21386
21387 let transaction = self.transact(window, cx, |this, window, cx| {
21388 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
21389 let snapshot = this.buffer.read(cx).read(cx);
21390 if let Some(relative_range_utf16) = range_utf16.as_ref() {
21391 for marked_range in &mut marked_ranges {
21392 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
21393 marked_range.start.0 += relative_range_utf16.start;
21394 marked_range.start =
21395 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
21396 marked_range.end =
21397 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
21398 }
21399 }
21400 Some(marked_ranges)
21401 } else if let Some(range_utf16) = range_utf16 {
21402 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21403 Some(this.selection_replacement_ranges(range_utf16, cx))
21404 } else {
21405 None
21406 };
21407
21408 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
21409 let newest_selection_id = this.selections.newest_anchor().id;
21410 this.selections
21411 .all::<OffsetUtf16>(cx)
21412 .iter()
21413 .zip(ranges_to_replace.iter())
21414 .find_map(|(selection, range)| {
21415 if selection.id == newest_selection_id {
21416 Some(
21417 (range.start.0 as isize - selection.head().0 as isize)
21418 ..(range.end.0 as isize - selection.head().0 as isize),
21419 )
21420 } else {
21421 None
21422 }
21423 })
21424 });
21425
21426 cx.emit(EditorEvent::InputHandled {
21427 utf16_range_to_replace: range_to_replace,
21428 text: text.into(),
21429 });
21430
21431 if let Some(ranges) = ranges_to_replace {
21432 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
21433 }
21434
21435 let marked_ranges = {
21436 let snapshot = this.buffer.read(cx).read(cx);
21437 this.selections
21438 .disjoint_anchors()
21439 .iter()
21440 .map(|selection| {
21441 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
21442 })
21443 .collect::<Vec<_>>()
21444 };
21445
21446 if text.is_empty() {
21447 this.unmark_text(window, cx);
21448 } else {
21449 this.highlight_text::<InputComposition>(
21450 marked_ranges.clone(),
21451 HighlightStyle {
21452 underline: Some(UnderlineStyle {
21453 thickness: px(1.),
21454 color: None,
21455 wavy: false,
21456 }),
21457 ..Default::default()
21458 },
21459 cx,
21460 );
21461 }
21462
21463 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
21464 let use_autoclose = this.use_autoclose;
21465 let use_auto_surround = this.use_auto_surround;
21466 this.set_use_autoclose(false);
21467 this.set_use_auto_surround(false);
21468 this.handle_input(text, window, cx);
21469 this.set_use_autoclose(use_autoclose);
21470 this.set_use_auto_surround(use_auto_surround);
21471
21472 if let Some(new_selected_range) = new_selected_range_utf16 {
21473 let snapshot = this.buffer.read(cx).read(cx);
21474 let new_selected_ranges = marked_ranges
21475 .into_iter()
21476 .map(|marked_range| {
21477 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
21478 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
21479 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
21480 snapshot.clip_offset_utf16(new_start, Bias::Left)
21481 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
21482 })
21483 .collect::<Vec<_>>();
21484
21485 drop(snapshot);
21486 this.change_selections(None, window, cx, |selections| {
21487 selections.select_ranges(new_selected_ranges)
21488 });
21489 }
21490 });
21491
21492 self.ime_transaction = self.ime_transaction.or(transaction);
21493 if let Some(transaction) = self.ime_transaction {
21494 self.buffer.update(cx, |buffer, cx| {
21495 buffer.group_until_transaction(transaction, cx);
21496 });
21497 }
21498
21499 if self.text_highlights::<InputComposition>(cx).is_none() {
21500 self.ime_transaction.take();
21501 }
21502 }
21503
21504 fn bounds_for_range(
21505 &mut self,
21506 range_utf16: Range<usize>,
21507 element_bounds: gpui::Bounds<Pixels>,
21508 window: &mut Window,
21509 cx: &mut Context<Self>,
21510 ) -> Option<gpui::Bounds<Pixels>> {
21511 let text_layout_details = self.text_layout_details(window);
21512 let gpui::Size {
21513 width: em_width,
21514 height: line_height,
21515 } = self.character_size(window);
21516
21517 let snapshot = self.snapshot(window, cx);
21518 let scroll_position = snapshot.scroll_position();
21519 let scroll_left = scroll_position.x * em_width;
21520
21521 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
21522 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
21523 + self.gutter_dimensions.width
21524 + self.gutter_dimensions.margin;
21525 let y = line_height * (start.row().as_f32() - scroll_position.y);
21526
21527 Some(Bounds {
21528 origin: element_bounds.origin + point(x, y),
21529 size: size(em_width, line_height),
21530 })
21531 }
21532
21533 fn character_index_for_point(
21534 &mut self,
21535 point: gpui::Point<Pixels>,
21536 _window: &mut Window,
21537 _cx: &mut Context<Self>,
21538 ) -> Option<usize> {
21539 let position_map = self.last_position_map.as_ref()?;
21540 if !position_map.text_hitbox.contains(&point) {
21541 return None;
21542 }
21543 let display_point = position_map.point_for_position(point).previous_valid;
21544 let anchor = position_map
21545 .snapshot
21546 .display_point_to_anchor(display_point, Bias::Left);
21547 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
21548 Some(utf16_offset.0)
21549 }
21550}
21551
21552trait SelectionExt {
21553 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
21554 fn spanned_rows(
21555 &self,
21556 include_end_if_at_line_start: bool,
21557 map: &DisplaySnapshot,
21558 ) -> Range<MultiBufferRow>;
21559}
21560
21561impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
21562 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
21563 let start = self
21564 .start
21565 .to_point(&map.buffer_snapshot)
21566 .to_display_point(map);
21567 let end = self
21568 .end
21569 .to_point(&map.buffer_snapshot)
21570 .to_display_point(map);
21571 if self.reversed {
21572 end..start
21573 } else {
21574 start..end
21575 }
21576 }
21577
21578 fn spanned_rows(
21579 &self,
21580 include_end_if_at_line_start: bool,
21581 map: &DisplaySnapshot,
21582 ) -> Range<MultiBufferRow> {
21583 let start = self.start.to_point(&map.buffer_snapshot);
21584 let mut end = self.end.to_point(&map.buffer_snapshot);
21585 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
21586 end.row -= 1;
21587 }
21588
21589 let buffer_start = map.prev_line_boundary(start).0;
21590 let buffer_end = map.next_line_boundary(end).0;
21591 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
21592 }
21593}
21594
21595impl<T: InvalidationRegion> InvalidationStack<T> {
21596 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
21597 where
21598 S: Clone + ToOffset,
21599 {
21600 while let Some(region) = self.last() {
21601 let all_selections_inside_invalidation_ranges =
21602 if selections.len() == region.ranges().len() {
21603 selections
21604 .iter()
21605 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
21606 .all(|(selection, invalidation_range)| {
21607 let head = selection.head().to_offset(buffer);
21608 invalidation_range.start <= head && invalidation_range.end >= head
21609 })
21610 } else {
21611 false
21612 };
21613
21614 if all_selections_inside_invalidation_ranges {
21615 break;
21616 } else {
21617 self.pop();
21618 }
21619 }
21620 }
21621}
21622
21623impl<T> Default for InvalidationStack<T> {
21624 fn default() -> Self {
21625 Self(Default::default())
21626 }
21627}
21628
21629impl<T> Deref for InvalidationStack<T> {
21630 type Target = Vec<T>;
21631
21632 fn deref(&self) -> &Self::Target {
21633 &self.0
21634 }
21635}
21636
21637impl<T> DerefMut for InvalidationStack<T> {
21638 fn deref_mut(&mut self) -> &mut Self::Target {
21639 &mut self.0
21640 }
21641}
21642
21643impl InvalidationRegion for SnippetState {
21644 fn ranges(&self) -> &[Range<Anchor>] {
21645 &self.ranges[self.active_index]
21646 }
21647}
21648
21649fn inline_completion_edit_text(
21650 current_snapshot: &BufferSnapshot,
21651 edits: &[(Range<Anchor>, String)],
21652 edit_preview: &EditPreview,
21653 include_deletions: bool,
21654 cx: &App,
21655) -> HighlightedText {
21656 let edits = edits
21657 .iter()
21658 .map(|(anchor, text)| {
21659 (
21660 anchor.start.text_anchor..anchor.end.text_anchor,
21661 text.clone(),
21662 )
21663 })
21664 .collect::<Vec<_>>();
21665
21666 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
21667}
21668
21669pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
21670 match severity {
21671 lsp::DiagnosticSeverity::ERROR => colors.error,
21672 lsp::DiagnosticSeverity::WARNING => colors.warning,
21673 lsp::DiagnosticSeverity::INFORMATION => colors.info,
21674 lsp::DiagnosticSeverity::HINT => colors.info,
21675 _ => colors.ignored,
21676 }
21677}
21678
21679pub fn styled_runs_for_code_label<'a>(
21680 label: &'a CodeLabel,
21681 syntax_theme: &'a theme::SyntaxTheme,
21682) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
21683 let fade_out = HighlightStyle {
21684 fade_out: Some(0.35),
21685 ..Default::default()
21686 };
21687
21688 let mut prev_end = label.filter_range.end;
21689 label
21690 .runs
21691 .iter()
21692 .enumerate()
21693 .flat_map(move |(ix, (range, highlight_id))| {
21694 let style = if let Some(style) = highlight_id.style(syntax_theme) {
21695 style
21696 } else {
21697 return Default::default();
21698 };
21699 let mut muted_style = style;
21700 muted_style.highlight(fade_out);
21701
21702 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
21703 if range.start >= label.filter_range.end {
21704 if range.start > prev_end {
21705 runs.push((prev_end..range.start, fade_out));
21706 }
21707 runs.push((range.clone(), muted_style));
21708 } else if range.end <= label.filter_range.end {
21709 runs.push((range.clone(), style));
21710 } else {
21711 runs.push((range.start..label.filter_range.end, style));
21712 runs.push((label.filter_range.end..range.end, muted_style));
21713 }
21714 prev_end = cmp::max(prev_end, range.end);
21715
21716 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
21717 runs.push((prev_end..label.text.len(), fade_out));
21718 }
21719
21720 runs
21721 })
21722}
21723
21724pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
21725 let mut prev_index = 0;
21726 let mut prev_codepoint: Option<char> = None;
21727 text.char_indices()
21728 .chain([(text.len(), '\0')])
21729 .filter_map(move |(index, codepoint)| {
21730 let prev_codepoint = prev_codepoint.replace(codepoint)?;
21731 let is_boundary = index == text.len()
21732 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
21733 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
21734 if is_boundary {
21735 let chunk = &text[prev_index..index];
21736 prev_index = index;
21737 Some(chunk)
21738 } else {
21739 None
21740 }
21741 })
21742}
21743
21744pub trait RangeToAnchorExt: Sized {
21745 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
21746
21747 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
21748 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
21749 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
21750 }
21751}
21752
21753impl<T: ToOffset> RangeToAnchorExt for Range<T> {
21754 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
21755 let start_offset = self.start.to_offset(snapshot);
21756 let end_offset = self.end.to_offset(snapshot);
21757 if start_offset == end_offset {
21758 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
21759 } else {
21760 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
21761 }
21762 }
21763}
21764
21765pub trait RowExt {
21766 fn as_f32(&self) -> f32;
21767
21768 fn next_row(&self) -> Self;
21769
21770 fn previous_row(&self) -> Self;
21771
21772 fn minus(&self, other: Self) -> u32;
21773}
21774
21775impl RowExt for DisplayRow {
21776 fn as_f32(&self) -> f32 {
21777 self.0 as f32
21778 }
21779
21780 fn next_row(&self) -> Self {
21781 Self(self.0 + 1)
21782 }
21783
21784 fn previous_row(&self) -> Self {
21785 Self(self.0.saturating_sub(1))
21786 }
21787
21788 fn minus(&self, other: Self) -> u32 {
21789 self.0 - other.0
21790 }
21791}
21792
21793impl RowExt for MultiBufferRow {
21794 fn as_f32(&self) -> f32 {
21795 self.0 as f32
21796 }
21797
21798 fn next_row(&self) -> Self {
21799 Self(self.0 + 1)
21800 }
21801
21802 fn previous_row(&self) -> Self {
21803 Self(self.0.saturating_sub(1))
21804 }
21805
21806 fn minus(&self, other: Self) -> u32 {
21807 self.0 - other.0
21808 }
21809}
21810
21811trait RowRangeExt {
21812 type Row;
21813
21814 fn len(&self) -> usize;
21815
21816 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
21817}
21818
21819impl RowRangeExt for Range<MultiBufferRow> {
21820 type Row = MultiBufferRow;
21821
21822 fn len(&self) -> usize {
21823 (self.end.0 - self.start.0) as usize
21824 }
21825
21826 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21827 (self.start.0..self.end.0).map(MultiBufferRow)
21828 }
21829}
21830
21831impl RowRangeExt for Range<DisplayRow> {
21832 type Row = DisplayRow;
21833
21834 fn len(&self) -> usize {
21835 (self.end.0 - self.start.0) as usize
21836 }
21837
21838 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21839 (self.start.0..self.end.0).map(DisplayRow)
21840 }
21841}
21842
21843/// If select range has more than one line, we
21844/// just point the cursor to range.start.
21845fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21846 if range.start.row == range.end.row {
21847 range
21848 } else {
21849 range.start..range.start
21850 }
21851}
21852pub struct KillRing(ClipboardItem);
21853impl Global for KillRing {}
21854
21855const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21856
21857enum BreakpointPromptEditAction {
21858 Log,
21859 Condition,
21860 HitCondition,
21861}
21862
21863struct BreakpointPromptEditor {
21864 pub(crate) prompt: Entity<Editor>,
21865 editor: WeakEntity<Editor>,
21866 breakpoint_anchor: Anchor,
21867 breakpoint: Breakpoint,
21868 edit_action: BreakpointPromptEditAction,
21869 block_ids: HashSet<CustomBlockId>,
21870 editor_margins: Arc<Mutex<EditorMargins>>,
21871 _subscriptions: Vec<Subscription>,
21872}
21873
21874impl BreakpointPromptEditor {
21875 const MAX_LINES: u8 = 4;
21876
21877 fn new(
21878 editor: WeakEntity<Editor>,
21879 breakpoint_anchor: Anchor,
21880 breakpoint: Breakpoint,
21881 edit_action: BreakpointPromptEditAction,
21882 window: &mut Window,
21883 cx: &mut Context<Self>,
21884 ) -> Self {
21885 let base_text = match edit_action {
21886 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21887 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21888 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21889 }
21890 .map(|msg| msg.to_string())
21891 .unwrap_or_default();
21892
21893 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21894 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21895
21896 let prompt = cx.new(|cx| {
21897 let mut prompt = Editor::new(
21898 EditorMode::AutoHeight {
21899 max_lines: Self::MAX_LINES as usize,
21900 },
21901 buffer,
21902 None,
21903 window,
21904 cx,
21905 );
21906 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21907 prompt.set_show_cursor_when_unfocused(false, cx);
21908 prompt.set_placeholder_text(
21909 match edit_action {
21910 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21911 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21912 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21913 },
21914 cx,
21915 );
21916
21917 prompt
21918 });
21919
21920 Self {
21921 prompt,
21922 editor,
21923 breakpoint_anchor,
21924 breakpoint,
21925 edit_action,
21926 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21927 block_ids: Default::default(),
21928 _subscriptions: vec![],
21929 }
21930 }
21931
21932 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21933 self.block_ids.extend(block_ids)
21934 }
21935
21936 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21937 if let Some(editor) = self.editor.upgrade() {
21938 let message = self
21939 .prompt
21940 .read(cx)
21941 .buffer
21942 .read(cx)
21943 .as_singleton()
21944 .expect("A multi buffer in breakpoint prompt isn't possible")
21945 .read(cx)
21946 .as_rope()
21947 .to_string();
21948
21949 editor.update(cx, |editor, cx| {
21950 editor.edit_breakpoint_at_anchor(
21951 self.breakpoint_anchor,
21952 self.breakpoint.clone(),
21953 match self.edit_action {
21954 BreakpointPromptEditAction::Log => {
21955 BreakpointEditAction::EditLogMessage(message.into())
21956 }
21957 BreakpointPromptEditAction::Condition => {
21958 BreakpointEditAction::EditCondition(message.into())
21959 }
21960 BreakpointPromptEditAction::HitCondition => {
21961 BreakpointEditAction::EditHitCondition(message.into())
21962 }
21963 },
21964 cx,
21965 );
21966
21967 editor.remove_blocks(self.block_ids.clone(), None, cx);
21968 cx.focus_self(window);
21969 });
21970 }
21971 }
21972
21973 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21974 self.editor
21975 .update(cx, |editor, cx| {
21976 editor.remove_blocks(self.block_ids.clone(), None, cx);
21977 window.focus(&editor.focus_handle);
21978 })
21979 .log_err();
21980 }
21981
21982 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21983 let settings = ThemeSettings::get_global(cx);
21984 let text_style = TextStyle {
21985 color: if self.prompt.read(cx).read_only(cx) {
21986 cx.theme().colors().text_disabled
21987 } else {
21988 cx.theme().colors().text
21989 },
21990 font_family: settings.buffer_font.family.clone(),
21991 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21992 font_size: settings.buffer_font_size(cx).into(),
21993 font_weight: settings.buffer_font.weight,
21994 line_height: relative(settings.buffer_line_height.value()),
21995 ..Default::default()
21996 };
21997 EditorElement::new(
21998 &self.prompt,
21999 EditorStyle {
22000 background: cx.theme().colors().editor_background,
22001 local_player: cx.theme().players().local(),
22002 text: text_style,
22003 ..Default::default()
22004 },
22005 )
22006 }
22007}
22008
22009impl Render for BreakpointPromptEditor {
22010 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22011 let editor_margins = *self.editor_margins.lock();
22012 let gutter_dimensions = editor_margins.gutter;
22013 h_flex()
22014 .key_context("Editor")
22015 .bg(cx.theme().colors().editor_background)
22016 .border_y_1()
22017 .border_color(cx.theme().status().info_border)
22018 .size_full()
22019 .py(window.line_height() / 2.5)
22020 .on_action(cx.listener(Self::confirm))
22021 .on_action(cx.listener(Self::cancel))
22022 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
22023 .child(div().flex_1().child(self.render_prompt_editor(cx)))
22024 }
22025}
22026
22027impl Focusable for BreakpointPromptEditor {
22028 fn focus_handle(&self, cx: &App) -> FocusHandle {
22029 self.prompt.focus_handle(cx)
22030 }
22031}
22032
22033fn all_edits_insertions_or_deletions(
22034 edits: &Vec<(Range<Anchor>, String)>,
22035 snapshot: &MultiBufferSnapshot,
22036) -> bool {
22037 let mut all_insertions = true;
22038 let mut all_deletions = true;
22039
22040 for (range, new_text) in edits.iter() {
22041 let range_is_empty = range.to_offset(&snapshot).is_empty();
22042 let text_is_empty = new_text.is_empty();
22043
22044 if range_is_empty != text_is_empty {
22045 if range_is_empty {
22046 all_deletions = false;
22047 } else {
22048 all_insertions = false;
22049 }
22050 } else {
22051 return false;
22052 }
22053
22054 if !all_insertions && !all_deletions {
22055 return false;
22056 }
22057 }
22058 all_insertions || all_deletions
22059}
22060
22061struct MissingEditPredictionKeybindingTooltip;
22062
22063impl Render for MissingEditPredictionKeybindingTooltip {
22064 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22065 ui::tooltip_container(window, cx, |container, _, cx| {
22066 container
22067 .flex_shrink_0()
22068 .max_w_80()
22069 .min_h(rems_from_px(124.))
22070 .justify_between()
22071 .child(
22072 v_flex()
22073 .flex_1()
22074 .text_ui_sm(cx)
22075 .child(Label::new("Conflict with Accept Keybinding"))
22076 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
22077 )
22078 .child(
22079 h_flex()
22080 .pb_1()
22081 .gap_1()
22082 .items_end()
22083 .w_full()
22084 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
22085 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
22086 }))
22087 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
22088 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
22089 })),
22090 )
22091 })
22092 }
22093}
22094
22095#[derive(Debug, Clone, Copy, PartialEq)]
22096pub struct LineHighlight {
22097 pub background: Background,
22098 pub border: Option<gpui::Hsla>,
22099 pub include_gutter: bool,
22100 pub type_id: Option<TypeId>,
22101}
22102
22103fn render_diff_hunk_controls(
22104 row: u32,
22105 status: &DiffHunkStatus,
22106 hunk_range: Range<Anchor>,
22107 is_created_file: bool,
22108 line_height: Pixels,
22109 editor: &Entity<Editor>,
22110 _window: &mut Window,
22111 cx: &mut App,
22112) -> AnyElement {
22113 h_flex()
22114 .h(line_height)
22115 .mr_1()
22116 .gap_1()
22117 .px_0p5()
22118 .pb_1()
22119 .border_x_1()
22120 .border_b_1()
22121 .border_color(cx.theme().colors().border_variant)
22122 .rounded_b_lg()
22123 .bg(cx.theme().colors().editor_background)
22124 .gap_1()
22125 .block_mouse_except_scroll()
22126 .shadow_md()
22127 .child(if status.has_secondary_hunk() {
22128 Button::new(("stage", row as u64), "Stage")
22129 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22130 .tooltip({
22131 let focus_handle = editor.focus_handle(cx);
22132 move |window, cx| {
22133 Tooltip::for_action_in(
22134 "Stage Hunk",
22135 &::git::ToggleStaged,
22136 &focus_handle,
22137 window,
22138 cx,
22139 )
22140 }
22141 })
22142 .on_click({
22143 let editor = editor.clone();
22144 move |_event, _window, cx| {
22145 editor.update(cx, |editor, cx| {
22146 editor.stage_or_unstage_diff_hunks(
22147 true,
22148 vec![hunk_range.start..hunk_range.start],
22149 cx,
22150 );
22151 });
22152 }
22153 })
22154 } else {
22155 Button::new(("unstage", row as u64), "Unstage")
22156 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22157 .tooltip({
22158 let focus_handle = editor.focus_handle(cx);
22159 move |window, cx| {
22160 Tooltip::for_action_in(
22161 "Unstage Hunk",
22162 &::git::ToggleStaged,
22163 &focus_handle,
22164 window,
22165 cx,
22166 )
22167 }
22168 })
22169 .on_click({
22170 let editor = editor.clone();
22171 move |_event, _window, cx| {
22172 editor.update(cx, |editor, cx| {
22173 editor.stage_or_unstage_diff_hunks(
22174 false,
22175 vec![hunk_range.start..hunk_range.start],
22176 cx,
22177 );
22178 });
22179 }
22180 })
22181 })
22182 .child(
22183 Button::new(("restore", row as u64), "Restore")
22184 .tooltip({
22185 let focus_handle = editor.focus_handle(cx);
22186 move |window, cx| {
22187 Tooltip::for_action_in(
22188 "Restore Hunk",
22189 &::git::Restore,
22190 &focus_handle,
22191 window,
22192 cx,
22193 )
22194 }
22195 })
22196 .on_click({
22197 let editor = editor.clone();
22198 move |_event, window, cx| {
22199 editor.update(cx, |editor, cx| {
22200 let snapshot = editor.snapshot(window, cx);
22201 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
22202 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
22203 });
22204 }
22205 })
22206 .disabled(is_created_file),
22207 )
22208 .when(
22209 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
22210 |el| {
22211 el.child(
22212 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
22213 .shape(IconButtonShape::Square)
22214 .icon_size(IconSize::Small)
22215 // .disabled(!has_multiple_hunks)
22216 .tooltip({
22217 let focus_handle = editor.focus_handle(cx);
22218 move |window, cx| {
22219 Tooltip::for_action_in(
22220 "Next Hunk",
22221 &GoToHunk,
22222 &focus_handle,
22223 window,
22224 cx,
22225 )
22226 }
22227 })
22228 .on_click({
22229 let editor = editor.clone();
22230 move |_event, window, cx| {
22231 editor.update(cx, |editor, cx| {
22232 let snapshot = editor.snapshot(window, cx);
22233 let position =
22234 hunk_range.end.to_point(&snapshot.buffer_snapshot);
22235 editor.go_to_hunk_before_or_after_position(
22236 &snapshot,
22237 position,
22238 Direction::Next,
22239 window,
22240 cx,
22241 );
22242 editor.expand_selected_diff_hunks(cx);
22243 });
22244 }
22245 }),
22246 )
22247 .child(
22248 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
22249 .shape(IconButtonShape::Square)
22250 .icon_size(IconSize::Small)
22251 // .disabled(!has_multiple_hunks)
22252 .tooltip({
22253 let focus_handle = editor.focus_handle(cx);
22254 move |window, cx| {
22255 Tooltip::for_action_in(
22256 "Previous Hunk",
22257 &GoToPreviousHunk,
22258 &focus_handle,
22259 window,
22260 cx,
22261 )
22262 }
22263 })
22264 .on_click({
22265 let editor = editor.clone();
22266 move |_event, window, cx| {
22267 editor.update(cx, |editor, cx| {
22268 let snapshot = editor.snapshot(window, cx);
22269 let point =
22270 hunk_range.start.to_point(&snapshot.buffer_snapshot);
22271 editor.go_to_hunk_before_or_after_position(
22272 &snapshot,
22273 point,
22274 Direction::Prev,
22275 window,
22276 cx,
22277 );
22278 editor.expand_selected_diff_hunks(cx);
22279 });
22280 }
22281 }),
22282 )
22283 },
22284 )
22285 .into_any_element()
22286}