1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod editor_tests;
46#[cfg(test)]
47mod inline_completion_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
54use aho_corasick::AhoCorasick;
55use anyhow::{Context as _, Result, anyhow};
56use blink_manager::BlinkManager;
57use buffer_diff::DiffHunkStatus;
58use client::{Collaborator, ParticipantIndex};
59use clock::{AGENT_REPLICA_ID, ReplicaId};
60use collections::{BTreeMap, HashMap, HashSet, VecDeque};
61use convert_case::{Case, Casing};
62use dap::TelemetrySpawnLocation;
63use display_map::*;
64pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
65pub use editor_settings::{
66 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes,
67 SearchSettings, ShowScrollbar,
68};
69use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
70pub use editor_settings_controls::*;
71use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
72pub use element::{
73 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
74};
75use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
76use futures::{
77 FutureExt,
78 future::{self, Shared, join},
79};
80use fuzzy::{StringMatch, StringMatchCandidate};
81
82use ::git::blame::BlameEntry;
83use ::git::{Restore, blame::ParsedCommitMessage};
84use code_context_menus::{
85 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
86 CompletionsMenu, ContextMenuOrigin,
87};
88use git::blame::{GitBlame, GlobalBlameRenderer};
89use gpui::{
90 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
91 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
92 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
93 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
94 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
95 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
96 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
97 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
98};
99use highlight_matching_bracket::refresh_matching_bracket_highlights;
100use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
101pub use hover_popover::hover_markdown_style;
102use hover_popover::{HoverState, hide_hover};
103use indent_guides::ActiveIndentGuidesState;
104use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
105pub use inline_completion::Direction;
106use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
107pub use items::MAX_TAB_TITLE_LEN;
108use itertools::Itertools;
109use language::{
110 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
111 CursorShape, DiagnosticEntry, DiffOptions, DocumentationConfig, EditPredictionsMode,
112 EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point,
113 Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
114 language_settings::{
115 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
116 all_language_settings, language_settings,
117 },
118 point_from_lsp, text_diff_with_options,
119};
120use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
121use linked_editing_ranges::refresh_linked_ranges;
122use markdown::Markdown;
123use mouse_context_menu::MouseContextMenu;
124use persistence::DB;
125use project::{
126 BreakpointWithPosition, ProjectPath,
127 debugger::{
128 breakpoint_store::{
129 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
130 BreakpointStoreEvent,
131 },
132 session::{Session, SessionEvent},
133 },
134 project_settings::DiagnosticSeverity,
135};
136
137pub use git::blame::BlameRenderer;
138pub use proposed_changes_editor::{
139 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
140};
141use std::{cell::OnceCell, iter::Peekable, ops::Not};
142use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
143
144pub use lsp::CompletionContext;
145use lsp::{
146 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
147 LanguageServerId, LanguageServerName,
148};
149
150use language::BufferSnapshot;
151pub use lsp_ext::lsp_tasks;
152use movement::TextLayoutDetails;
153pub use multi_buffer::{
154 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
155 RowInfo, ToOffset, ToPoint,
156};
157use multi_buffer::{
158 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
159 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
160};
161use parking_lot::Mutex;
162use project::{
163 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
164 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
165 TaskSourceKind,
166 debugger::breakpoint_store::Breakpoint,
167 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
168 project_settings::{GitGutterSetting, ProjectSettings},
169};
170use rand::prelude::*;
171use rpc::{ErrorExt, proto::*};
172use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
173use selections_collection::{
174 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
175};
176use serde::{Deserialize, Serialize};
177use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
178use smallvec::{SmallVec, smallvec};
179use snippet::Snippet;
180use std::sync::Arc;
181use std::{
182 any::TypeId,
183 borrow::Cow,
184 cell::RefCell,
185 cmp::{self, Ordering, Reverse},
186 mem,
187 num::NonZeroU32,
188 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
189 path::{Path, PathBuf},
190 rc::Rc,
191 time::{Duration, Instant},
192};
193pub use sum_tree::Bias;
194use sum_tree::TreeMap;
195use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
196use theme::{
197 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
198 observe_buffer_font_size_adjustment,
199};
200use ui::{
201 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
202 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
203};
204use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
205use workspace::{
206 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
207 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
208 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
209 item::{ItemHandle, PreviewTabsSettings},
210 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
211 searchable::SearchEvent,
212};
213
214use crate::hover_links::{find_url, find_url_from_range};
215use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
216
217pub const FILE_HEADER_HEIGHT: u32 = 2;
218pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
219pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
220const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
221const MAX_LINE_LEN: usize = 1024;
222const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
223const MAX_SELECTION_HISTORY_LEN: usize = 1024;
224pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
225#[doc(hidden)]
226pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
227const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
228
229pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
230pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
231pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
232
233pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
234pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
235pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
236pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
237
238pub type RenderDiffHunkControlsFn = Arc<
239 dyn Fn(
240 u32,
241 &DiffHunkStatus,
242 Range<Anchor>,
243 bool,
244 Pixels,
245 &Entity<Editor>,
246 &mut Window,
247 &mut App,
248 ) -> AnyElement,
249>;
250
251const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
252 alt: true,
253 shift: true,
254 control: false,
255 platform: false,
256 function: false,
257};
258
259struct InlineValueCache {
260 enabled: bool,
261 inlays: Vec<InlayId>,
262 refresh_task: Task<Option<()>>,
263}
264
265impl InlineValueCache {
266 fn new(enabled: bool) -> Self {
267 Self {
268 enabled,
269 inlays: Vec::new(),
270 refresh_task: Task::ready(None),
271 }
272 }
273}
274
275#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
276pub enum InlayId {
277 InlineCompletion(usize),
278 Hint(usize),
279 DebuggerValue(usize),
280}
281
282impl InlayId {
283 fn id(&self) -> usize {
284 match self {
285 Self::InlineCompletion(id) => *id,
286 Self::Hint(id) => *id,
287 Self::DebuggerValue(id) => *id,
288 }
289 }
290}
291
292pub enum ActiveDebugLine {}
293pub enum DebugStackFrameLine {}
294enum DocumentHighlightRead {}
295enum DocumentHighlightWrite {}
296enum InputComposition {}
297enum SelectedTextHighlight {}
298
299pub enum ConflictsOuter {}
300pub enum ConflictsOurs {}
301pub enum ConflictsTheirs {}
302pub enum ConflictsOursMarker {}
303pub enum ConflictsTheirsMarker {}
304
305#[derive(Debug, Copy, Clone, PartialEq, Eq)]
306pub enum Navigated {
307 Yes,
308 No,
309}
310
311impl Navigated {
312 pub fn from_bool(yes: bool) -> Navigated {
313 if yes { Navigated::Yes } else { Navigated::No }
314 }
315}
316
317#[derive(Debug, Clone, PartialEq, Eq)]
318enum DisplayDiffHunk {
319 Folded {
320 display_row: DisplayRow,
321 },
322 Unfolded {
323 is_created_file: bool,
324 diff_base_byte_range: Range<usize>,
325 display_row_range: Range<DisplayRow>,
326 multi_buffer_range: Range<Anchor>,
327 status: DiffHunkStatus,
328 },
329}
330
331pub enum HideMouseCursorOrigin {
332 TypingAction,
333 MovementAction,
334}
335
336pub fn init_settings(cx: &mut App) {
337 EditorSettings::register(cx);
338}
339
340pub fn init(cx: &mut App) {
341 init_settings(cx);
342
343 cx.set_global(GlobalBlameRenderer(Arc::new(())));
344
345 workspace::register_project_item::<Editor>(cx);
346 workspace::FollowableViewRegistry::register::<Editor>(cx);
347 workspace::register_serializable_item::<Editor>(cx);
348
349 cx.observe_new(
350 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
351 workspace.register_action(Editor::new_file);
352 workspace.register_action(Editor::new_file_vertical);
353 workspace.register_action(Editor::new_file_horizontal);
354 workspace.register_action(Editor::cancel_language_server_work);
355 },
356 )
357 .detach();
358
359 cx.on_action(move |_: &workspace::NewFile, cx| {
360 let app_state = workspace::AppState::global(cx);
361 if let Some(app_state) = app_state.upgrade() {
362 workspace::open_new(
363 Default::default(),
364 app_state,
365 cx,
366 |workspace, window, cx| {
367 Editor::new_file(workspace, &Default::default(), window, cx)
368 },
369 )
370 .detach();
371 }
372 });
373 cx.on_action(move |_: &workspace::NewWindow, cx| {
374 let app_state = workspace::AppState::global(cx);
375 if let Some(app_state) = app_state.upgrade() {
376 workspace::open_new(
377 Default::default(),
378 app_state,
379 cx,
380 |workspace, window, cx| {
381 cx.activate(true);
382 Editor::new_file(workspace, &Default::default(), window, cx)
383 },
384 )
385 .detach();
386 }
387 });
388}
389
390pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
391 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
392}
393
394pub trait DiagnosticRenderer {
395 fn render_group(
396 &self,
397 diagnostic_group: Vec<DiagnosticEntry<Point>>,
398 buffer_id: BufferId,
399 snapshot: EditorSnapshot,
400 editor: WeakEntity<Editor>,
401 cx: &mut App,
402 ) -> Vec<BlockProperties<Anchor>>;
403
404 fn render_hover(
405 &self,
406 diagnostic_group: Vec<DiagnosticEntry<Point>>,
407 range: Range<Point>,
408 buffer_id: BufferId,
409 cx: &mut App,
410 ) -> Option<Entity<markdown::Markdown>>;
411
412 fn open_link(
413 &self,
414 editor: &mut Editor,
415 link: SharedString,
416 window: &mut Window,
417 cx: &mut Context<Editor>,
418 );
419}
420
421pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
422
423impl GlobalDiagnosticRenderer {
424 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
425 cx.try_global::<Self>().map(|g| g.0.clone())
426 }
427}
428
429impl gpui::Global for GlobalDiagnosticRenderer {}
430pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
431 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
432}
433
434pub struct SearchWithinRange;
435
436trait InvalidationRegion {
437 fn ranges(&self) -> &[Range<Anchor>];
438}
439
440#[derive(Clone, Debug, PartialEq)]
441pub enum SelectPhase {
442 Begin {
443 position: DisplayPoint,
444 add: bool,
445 click_count: usize,
446 },
447 BeginColumnar {
448 position: DisplayPoint,
449 reset: bool,
450 goal_column: u32,
451 },
452 Extend {
453 position: DisplayPoint,
454 click_count: usize,
455 },
456 Update {
457 position: DisplayPoint,
458 goal_column: u32,
459 scroll_delta: gpui::Point<f32>,
460 },
461 End,
462}
463
464#[derive(Clone, Debug)]
465pub enum SelectMode {
466 Character,
467 Word(Range<Anchor>),
468 Line(Range<Anchor>),
469 All,
470}
471
472#[derive(Clone, PartialEq, Eq, Debug)]
473pub enum EditorMode {
474 SingleLine {
475 auto_width: bool,
476 },
477 AutoHeight {
478 max_lines: usize,
479 },
480 Full {
481 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
482 scale_ui_elements_with_buffer_font_size: bool,
483 /// When set to `true`, the editor will render a background for the active line.
484 show_active_line_background: bool,
485 /// When set to `true`, the editor's height will be determined by its content.
486 sized_by_content: bool,
487 },
488 Minimap {
489 parent: WeakEntity<Editor>,
490 },
491}
492
493impl EditorMode {
494 pub fn full() -> Self {
495 Self::Full {
496 scale_ui_elements_with_buffer_font_size: true,
497 show_active_line_background: true,
498 sized_by_content: false,
499 }
500 }
501
502 pub fn is_full(&self) -> bool {
503 matches!(self, Self::Full { .. })
504 }
505
506 fn is_minimap(&self) -> bool {
507 matches!(self, Self::Minimap { .. })
508 }
509}
510
511#[derive(Copy, Clone, Debug)]
512pub enum SoftWrap {
513 /// Prefer not to wrap at all.
514 ///
515 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
516 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
517 GitDiff,
518 /// Prefer a single line generally, unless an overly long line is encountered.
519 None,
520 /// Soft wrap lines that exceed the editor width.
521 EditorWidth,
522 /// Soft wrap lines at the preferred line length.
523 Column(u32),
524 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
525 Bounded(u32),
526}
527
528#[derive(Clone)]
529pub struct EditorStyle {
530 pub background: Hsla,
531 pub local_player: PlayerColor,
532 pub text: TextStyle,
533 pub scrollbar_width: Pixels,
534 pub syntax: Arc<SyntaxTheme>,
535 pub status: StatusColors,
536 pub inlay_hints_style: HighlightStyle,
537 pub inline_completion_styles: InlineCompletionStyles,
538 pub unnecessary_code_fade: f32,
539 pub show_underlines: bool,
540}
541
542impl Default for EditorStyle {
543 fn default() -> Self {
544 Self {
545 background: Hsla::default(),
546 local_player: PlayerColor::default(),
547 text: TextStyle::default(),
548 scrollbar_width: Pixels::default(),
549 syntax: Default::default(),
550 // HACK: Status colors don't have a real default.
551 // We should look into removing the status colors from the editor
552 // style and retrieve them directly from the theme.
553 status: StatusColors::dark(),
554 inlay_hints_style: HighlightStyle::default(),
555 inline_completion_styles: InlineCompletionStyles {
556 insertion: HighlightStyle::default(),
557 whitespace: HighlightStyle::default(),
558 },
559 unnecessary_code_fade: Default::default(),
560 show_underlines: true,
561 }
562 }
563}
564
565pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
566 let show_background = language_settings::language_settings(None, None, cx)
567 .inlay_hints
568 .show_background;
569
570 HighlightStyle {
571 color: Some(cx.theme().status().hint),
572 background_color: show_background.then(|| cx.theme().status().hint_background),
573 ..HighlightStyle::default()
574 }
575}
576
577pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
578 InlineCompletionStyles {
579 insertion: HighlightStyle {
580 color: Some(cx.theme().status().predictive),
581 ..HighlightStyle::default()
582 },
583 whitespace: HighlightStyle {
584 background_color: Some(cx.theme().status().created_background),
585 ..HighlightStyle::default()
586 },
587 }
588}
589
590type CompletionId = usize;
591
592pub(crate) enum EditDisplayMode {
593 TabAccept,
594 DiffPopover,
595 Inline,
596}
597
598enum InlineCompletion {
599 Edit {
600 edits: Vec<(Range<Anchor>, String)>,
601 edit_preview: Option<EditPreview>,
602 display_mode: EditDisplayMode,
603 snapshot: BufferSnapshot,
604 },
605 Move {
606 target: Anchor,
607 snapshot: BufferSnapshot,
608 },
609}
610
611struct InlineCompletionState {
612 inlay_ids: Vec<InlayId>,
613 completion: InlineCompletion,
614 completion_id: Option<SharedString>,
615 invalidation_range: Range<Anchor>,
616}
617
618enum EditPredictionSettings {
619 Disabled,
620 Enabled {
621 show_in_menu: bool,
622 preview_requires_modifier: bool,
623 },
624}
625
626enum InlineCompletionHighlight {}
627
628#[derive(Debug, Clone)]
629struct InlineDiagnostic {
630 message: SharedString,
631 group_id: usize,
632 is_primary: bool,
633 start: Point,
634 severity: lsp::DiagnosticSeverity,
635}
636
637pub enum MenuInlineCompletionsPolicy {
638 Never,
639 ByProvider,
640}
641
642pub enum EditPredictionPreview {
643 /// Modifier is not pressed
644 Inactive { released_too_fast: bool },
645 /// Modifier pressed
646 Active {
647 since: Instant,
648 previous_scroll_position: Option<ScrollAnchor>,
649 },
650}
651
652impl EditPredictionPreview {
653 pub fn released_too_fast(&self) -> bool {
654 match self {
655 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
656 EditPredictionPreview::Active { .. } => false,
657 }
658 }
659
660 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
661 if let EditPredictionPreview::Active {
662 previous_scroll_position,
663 ..
664 } = self
665 {
666 *previous_scroll_position = scroll_position;
667 }
668 }
669}
670
671pub struct ContextMenuOptions {
672 pub min_entries_visible: usize,
673 pub max_entries_visible: usize,
674 pub placement: Option<ContextMenuPlacement>,
675}
676
677#[derive(Debug, Clone, PartialEq, Eq)]
678pub enum ContextMenuPlacement {
679 Above,
680 Below,
681}
682
683#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
684struct EditorActionId(usize);
685
686impl EditorActionId {
687 pub fn post_inc(&mut self) -> Self {
688 let answer = self.0;
689
690 *self = Self(answer + 1);
691
692 Self(answer)
693 }
694}
695
696// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
697// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
698
699type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
700type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
701
702#[derive(Default)]
703struct ScrollbarMarkerState {
704 scrollbar_size: Size<Pixels>,
705 dirty: bool,
706 markers: Arc<[PaintQuad]>,
707 pending_refresh: Option<Task<Result<()>>>,
708}
709
710impl ScrollbarMarkerState {
711 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
712 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
713 }
714}
715
716#[derive(Clone, Copy, PartialEq, Eq)]
717pub enum MinimapVisibility {
718 Disabled,
719 Enabled {
720 /// The configuration currently present in the users settings.
721 setting_configuration: bool,
722 /// Whether to override the currently set visibility from the users setting.
723 toggle_override: bool,
724 },
725}
726
727impl MinimapVisibility {
728 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
729 if mode.is_full() {
730 Self::Enabled {
731 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
732 toggle_override: false,
733 }
734 } else {
735 Self::Disabled
736 }
737 }
738
739 fn hidden(&self) -> Self {
740 match *self {
741 Self::Enabled {
742 setting_configuration,
743 ..
744 } => Self::Enabled {
745 setting_configuration,
746 toggle_override: setting_configuration,
747 },
748 Self::Disabled => Self::Disabled,
749 }
750 }
751
752 fn disabled(&self) -> bool {
753 match *self {
754 Self::Disabled => true,
755 _ => false,
756 }
757 }
758
759 fn settings_visibility(&self) -> bool {
760 match *self {
761 Self::Enabled {
762 setting_configuration,
763 ..
764 } => setting_configuration,
765 _ => false,
766 }
767 }
768
769 fn visible(&self) -> bool {
770 match *self {
771 Self::Enabled {
772 setting_configuration,
773 toggle_override,
774 } => setting_configuration ^ toggle_override,
775 _ => false,
776 }
777 }
778
779 fn toggle_visibility(&self) -> Self {
780 match *self {
781 Self::Enabled {
782 toggle_override,
783 setting_configuration,
784 } => Self::Enabled {
785 setting_configuration,
786 toggle_override: !toggle_override,
787 },
788 Self::Disabled => Self::Disabled,
789 }
790 }
791}
792
793#[derive(Clone, Debug)]
794struct RunnableTasks {
795 templates: Vec<(TaskSourceKind, TaskTemplate)>,
796 offset: multi_buffer::Anchor,
797 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
798 column: u32,
799 // Values of all named captures, including those starting with '_'
800 extra_variables: HashMap<String, String>,
801 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
802 context_range: Range<BufferOffset>,
803}
804
805impl RunnableTasks {
806 fn resolve<'a>(
807 &'a self,
808 cx: &'a task::TaskContext,
809 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
810 self.templates.iter().filter_map(|(kind, template)| {
811 template
812 .resolve_task(&kind.to_id_base(), cx)
813 .map(|task| (kind.clone(), task))
814 })
815 }
816}
817
818#[derive(Clone)]
819pub struct ResolvedTasks {
820 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
821 position: Anchor,
822}
823
824#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
825struct BufferOffset(usize);
826
827// Addons allow storing per-editor state in other crates (e.g. Vim)
828pub trait Addon: 'static {
829 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
830
831 fn render_buffer_header_controls(
832 &self,
833 _: &ExcerptInfo,
834 _: &Window,
835 _: &App,
836 ) -> Option<AnyElement> {
837 None
838 }
839
840 fn to_any(&self) -> &dyn std::any::Any;
841
842 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
843 None
844 }
845}
846
847/// A set of caret positions, registered when the editor was edited.
848pub struct ChangeList {
849 changes: Vec<Vec<Anchor>>,
850 /// Currently "selected" change.
851 position: Option<usize>,
852}
853
854impl ChangeList {
855 pub fn new() -> Self {
856 Self {
857 changes: Vec::new(),
858 position: None,
859 }
860 }
861
862 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
863 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
864 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
865 if self.changes.is_empty() {
866 return None;
867 }
868
869 let prev = self.position.unwrap_or(self.changes.len());
870 let next = if direction == Direction::Prev {
871 prev.saturating_sub(count)
872 } else {
873 (prev + count).min(self.changes.len() - 1)
874 };
875 self.position = Some(next);
876 self.changes.get(next).map(|anchors| anchors.as_slice())
877 }
878
879 /// Adds a new change to the list, resetting the change list position.
880 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
881 self.position.take();
882 if pop_state {
883 self.changes.pop();
884 }
885 self.changes.push(new_positions.clone());
886 }
887
888 pub fn last(&self) -> Option<&[Anchor]> {
889 self.changes.last().map(|anchors| anchors.as_slice())
890 }
891}
892
893#[derive(Clone)]
894struct InlineBlamePopoverState {
895 scroll_handle: ScrollHandle,
896 commit_message: Option<ParsedCommitMessage>,
897 markdown: Entity<Markdown>,
898}
899
900struct InlineBlamePopover {
901 position: gpui::Point<Pixels>,
902 show_task: Option<Task<()>>,
903 hide_task: Option<Task<()>>,
904 popover_bounds: Option<Bounds<Pixels>>,
905 popover_state: InlineBlamePopoverState,
906}
907
908/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
909/// a breakpoint on them.
910#[derive(Clone, Copy, Debug)]
911struct PhantomBreakpointIndicator {
912 display_row: DisplayRow,
913 /// There's a small debounce between hovering over the line and showing the indicator.
914 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
915 is_active: bool,
916 collides_with_existing_breakpoint: bool,
917}
918/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
919///
920/// See the [module level documentation](self) for more information.
921pub struct Editor {
922 focus_handle: FocusHandle,
923 last_focused_descendant: Option<WeakFocusHandle>,
924 /// The text buffer being edited
925 buffer: Entity<MultiBuffer>,
926 /// Map of how text in the buffer should be displayed.
927 /// Handles soft wraps, folds, fake inlay text insertions, etc.
928 pub display_map: Entity<DisplayMap>,
929 pub selections: SelectionsCollection,
930 pub scroll_manager: ScrollManager,
931 /// When inline assist editors are linked, they all render cursors because
932 /// typing enters text into each of them, even the ones that aren't focused.
933 pub(crate) show_cursor_when_unfocused: bool,
934 columnar_selection_tail: Option<Anchor>,
935 add_selections_state: Option<AddSelectionsState>,
936 select_next_state: Option<SelectNextState>,
937 select_prev_state: Option<SelectNextState>,
938 selection_history: SelectionHistory,
939 autoclose_regions: Vec<AutocloseRegion>,
940 snippet_stack: InvalidationStack<SnippetState>,
941 select_syntax_node_history: SelectSyntaxNodeHistory,
942 ime_transaction: Option<TransactionId>,
943 pub diagnostics_max_severity: DiagnosticSeverity,
944 active_diagnostics: ActiveDiagnostic,
945 show_inline_diagnostics: bool,
946 inline_diagnostics_update: Task<()>,
947 inline_diagnostics_enabled: bool,
948 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
949 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
950 hard_wrap: Option<usize>,
951
952 // TODO: make this a access method
953 pub project: Option<Entity<Project>>,
954 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
955 completion_provider: Option<Rc<dyn CompletionProvider>>,
956 collaboration_hub: Option<Box<dyn CollaborationHub>>,
957 blink_manager: Entity<BlinkManager>,
958 show_cursor_names: bool,
959 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
960 pub show_local_selections: bool,
961 mode: EditorMode,
962 show_breadcrumbs: bool,
963 show_gutter: bool,
964 show_scrollbars: ScrollbarAxes,
965 minimap_visibility: MinimapVisibility,
966 offset_content: bool,
967 disable_expand_excerpt_buttons: bool,
968 show_line_numbers: Option<bool>,
969 use_relative_line_numbers: Option<bool>,
970 show_git_diff_gutter: Option<bool>,
971 show_code_actions: Option<bool>,
972 show_runnables: Option<bool>,
973 show_breakpoints: Option<bool>,
974 show_wrap_guides: Option<bool>,
975 show_indent_guides: Option<bool>,
976 placeholder_text: Option<Arc<str>>,
977 highlight_order: usize,
978 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
979 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
980 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
981 scrollbar_marker_state: ScrollbarMarkerState,
982 active_indent_guides_state: ActiveIndentGuidesState,
983 nav_history: Option<ItemNavHistory>,
984 context_menu: RefCell<Option<CodeContextMenu>>,
985 context_menu_options: Option<ContextMenuOptions>,
986 mouse_context_menu: Option<MouseContextMenu>,
987 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
988 inline_blame_popover: Option<InlineBlamePopover>,
989 signature_help_state: SignatureHelpState,
990 auto_signature_help: Option<bool>,
991 find_all_references_task_sources: Vec<Anchor>,
992 next_completion_id: CompletionId,
993 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
994 code_actions_task: Option<Task<Result<()>>>,
995 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
996 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
997 document_highlights_task: Option<Task<()>>,
998 linked_editing_range_task: Option<Task<Option<()>>>,
999 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1000 pending_rename: Option<RenameState>,
1001 searchable: bool,
1002 cursor_shape: CursorShape,
1003 current_line_highlight: Option<CurrentLineHighlight>,
1004 collapse_matches: bool,
1005 autoindent_mode: Option<AutoindentMode>,
1006 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1007 input_enabled: bool,
1008 use_modal_editing: bool,
1009 read_only: bool,
1010 leader_id: Option<CollaboratorId>,
1011 remote_id: Option<ViewId>,
1012 pub hover_state: HoverState,
1013 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1014 gutter_hovered: bool,
1015 hovered_link_state: Option<HoveredLinkState>,
1016 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1017 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1018 active_inline_completion: Option<InlineCompletionState>,
1019 /// Used to prevent flickering as the user types while the menu is open
1020 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1021 edit_prediction_settings: EditPredictionSettings,
1022 inline_completions_hidden_for_vim_mode: bool,
1023 show_inline_completions_override: Option<bool>,
1024 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1025 edit_prediction_preview: EditPredictionPreview,
1026 edit_prediction_indent_conflict: bool,
1027 edit_prediction_requires_modifier_in_indent_conflict: bool,
1028 inlay_hint_cache: InlayHintCache,
1029 next_inlay_id: usize,
1030 _subscriptions: Vec<Subscription>,
1031 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1032 gutter_dimensions: GutterDimensions,
1033 style: Option<EditorStyle>,
1034 text_style_refinement: Option<TextStyleRefinement>,
1035 next_editor_action_id: EditorActionId,
1036 editor_actions:
1037 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
1038 use_autoclose: bool,
1039 use_auto_surround: bool,
1040 auto_replace_emoji_shortcode: bool,
1041 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1042 show_git_blame_gutter: bool,
1043 show_git_blame_inline: bool,
1044 show_git_blame_inline_delay_task: Option<Task<()>>,
1045 git_blame_inline_enabled: bool,
1046 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1047 serialize_dirty_buffers: bool,
1048 show_selection_menu: Option<bool>,
1049 blame: Option<Entity<GitBlame>>,
1050 blame_subscription: Option<Subscription>,
1051 custom_context_menu: Option<
1052 Box<
1053 dyn 'static
1054 + Fn(
1055 &mut Self,
1056 DisplayPoint,
1057 &mut Window,
1058 &mut Context<Self>,
1059 ) -> Option<Entity<ui::ContextMenu>>,
1060 >,
1061 >,
1062 last_bounds: Option<Bounds<Pixels>>,
1063 last_position_map: Option<Rc<PositionMap>>,
1064 expect_bounds_change: Option<Bounds<Pixels>>,
1065 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1066 tasks_update_task: Option<Task<()>>,
1067 breakpoint_store: Option<Entity<BreakpointStore>>,
1068 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1069 in_project_search: bool,
1070 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1071 breadcrumb_header: Option<String>,
1072 focused_block: Option<FocusedBlock>,
1073 next_scroll_position: NextScrollCursorCenterTopBottom,
1074 addons: HashMap<TypeId, Box<dyn Addon>>,
1075 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1076 load_diff_task: Option<Shared<Task<()>>>,
1077 /// Whether we are temporarily displaying a diff other than git's
1078 temporary_diff_override: bool,
1079 selection_mark_mode: bool,
1080 toggle_fold_multiple_buffers: Task<()>,
1081 _scroll_cursor_center_top_bottom_task: Task<()>,
1082 serialize_selections: Task<()>,
1083 serialize_folds: Task<()>,
1084 mouse_cursor_hidden: bool,
1085 minimap: Option<Entity<Self>>,
1086 hide_mouse_mode: HideMouseMode,
1087 pub change_list: ChangeList,
1088 inline_value_cache: InlineValueCache,
1089}
1090
1091#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1092enum NextScrollCursorCenterTopBottom {
1093 #[default]
1094 Center,
1095 Top,
1096 Bottom,
1097}
1098
1099impl NextScrollCursorCenterTopBottom {
1100 fn next(&self) -> Self {
1101 match self {
1102 Self::Center => Self::Top,
1103 Self::Top => Self::Bottom,
1104 Self::Bottom => Self::Center,
1105 }
1106 }
1107}
1108
1109#[derive(Clone)]
1110pub struct EditorSnapshot {
1111 pub mode: EditorMode,
1112 show_gutter: bool,
1113 show_line_numbers: Option<bool>,
1114 show_git_diff_gutter: Option<bool>,
1115 show_code_actions: Option<bool>,
1116 show_runnables: Option<bool>,
1117 show_breakpoints: Option<bool>,
1118 git_blame_gutter_max_author_length: Option<usize>,
1119 pub display_snapshot: DisplaySnapshot,
1120 pub placeholder_text: Option<Arc<str>>,
1121 is_focused: bool,
1122 scroll_anchor: ScrollAnchor,
1123 ongoing_scroll: OngoingScroll,
1124 current_line_highlight: CurrentLineHighlight,
1125 gutter_hovered: bool,
1126}
1127
1128#[derive(Default, Debug, Clone, Copy)]
1129pub struct GutterDimensions {
1130 pub left_padding: Pixels,
1131 pub right_padding: Pixels,
1132 pub width: Pixels,
1133 pub margin: Pixels,
1134 pub git_blame_entries_width: Option<Pixels>,
1135}
1136
1137impl GutterDimensions {
1138 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1139 Self {
1140 margin: Self::default_gutter_margin(font_id, font_size, cx),
1141 ..Default::default()
1142 }
1143 }
1144
1145 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1146 -cx.text_system().descent(font_id, font_size)
1147 }
1148 /// The full width of the space taken up by the gutter.
1149 pub fn full_width(&self) -> Pixels {
1150 self.margin + self.width
1151 }
1152
1153 /// The width of the space reserved for the fold indicators,
1154 /// use alongside 'justify_end' and `gutter_width` to
1155 /// right align content with the line numbers
1156 pub fn fold_area_width(&self) -> Pixels {
1157 self.margin + self.right_padding
1158 }
1159}
1160
1161#[derive(Debug)]
1162pub struct RemoteSelection {
1163 pub replica_id: ReplicaId,
1164 pub selection: Selection<Anchor>,
1165 pub cursor_shape: CursorShape,
1166 pub collaborator_id: CollaboratorId,
1167 pub line_mode: bool,
1168 pub user_name: Option<SharedString>,
1169 pub color: PlayerColor,
1170}
1171
1172#[derive(Clone, Debug)]
1173struct SelectionHistoryEntry {
1174 selections: Arc<[Selection<Anchor>]>,
1175 select_next_state: Option<SelectNextState>,
1176 select_prev_state: Option<SelectNextState>,
1177 add_selections_state: Option<AddSelectionsState>,
1178}
1179
1180enum SelectionHistoryMode {
1181 Normal,
1182 Undoing,
1183 Redoing,
1184}
1185
1186#[derive(Clone, PartialEq, Eq, Hash)]
1187struct HoveredCursor {
1188 replica_id: u16,
1189 selection_id: usize,
1190}
1191
1192impl Default for SelectionHistoryMode {
1193 fn default() -> Self {
1194 Self::Normal
1195 }
1196}
1197
1198#[derive(Default)]
1199struct SelectionHistory {
1200 #[allow(clippy::type_complexity)]
1201 selections_by_transaction:
1202 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1203 mode: SelectionHistoryMode,
1204 undo_stack: VecDeque<SelectionHistoryEntry>,
1205 redo_stack: VecDeque<SelectionHistoryEntry>,
1206}
1207
1208impl SelectionHistory {
1209 fn insert_transaction(
1210 &mut self,
1211 transaction_id: TransactionId,
1212 selections: Arc<[Selection<Anchor>]>,
1213 ) {
1214 self.selections_by_transaction
1215 .insert(transaction_id, (selections, None));
1216 }
1217
1218 #[allow(clippy::type_complexity)]
1219 fn transaction(
1220 &self,
1221 transaction_id: TransactionId,
1222 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1223 self.selections_by_transaction.get(&transaction_id)
1224 }
1225
1226 #[allow(clippy::type_complexity)]
1227 fn transaction_mut(
1228 &mut self,
1229 transaction_id: TransactionId,
1230 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1231 self.selections_by_transaction.get_mut(&transaction_id)
1232 }
1233
1234 fn push(&mut self, entry: SelectionHistoryEntry) {
1235 if !entry.selections.is_empty() {
1236 match self.mode {
1237 SelectionHistoryMode::Normal => {
1238 self.push_undo(entry);
1239 self.redo_stack.clear();
1240 }
1241 SelectionHistoryMode::Undoing => self.push_redo(entry),
1242 SelectionHistoryMode::Redoing => self.push_undo(entry),
1243 }
1244 }
1245 }
1246
1247 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1248 if self
1249 .undo_stack
1250 .back()
1251 .map_or(true, |e| e.selections != entry.selections)
1252 {
1253 self.undo_stack.push_back(entry);
1254 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1255 self.undo_stack.pop_front();
1256 }
1257 }
1258 }
1259
1260 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1261 if self
1262 .redo_stack
1263 .back()
1264 .map_or(true, |e| e.selections != entry.selections)
1265 {
1266 self.redo_stack.push_back(entry);
1267 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1268 self.redo_stack.pop_front();
1269 }
1270 }
1271 }
1272}
1273
1274#[derive(Clone, Copy)]
1275pub struct RowHighlightOptions {
1276 pub autoscroll: bool,
1277 pub include_gutter: bool,
1278}
1279
1280impl Default for RowHighlightOptions {
1281 fn default() -> Self {
1282 Self {
1283 autoscroll: Default::default(),
1284 include_gutter: true,
1285 }
1286 }
1287}
1288
1289struct RowHighlight {
1290 index: usize,
1291 range: Range<Anchor>,
1292 color: Hsla,
1293 options: RowHighlightOptions,
1294 type_id: TypeId,
1295}
1296
1297#[derive(Clone, Debug)]
1298struct AddSelectionsState {
1299 above: bool,
1300 stack: Vec<usize>,
1301}
1302
1303#[derive(Clone)]
1304struct SelectNextState {
1305 query: AhoCorasick,
1306 wordwise: bool,
1307 done: bool,
1308}
1309
1310impl std::fmt::Debug for SelectNextState {
1311 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1312 f.debug_struct(std::any::type_name::<Self>())
1313 .field("wordwise", &self.wordwise)
1314 .field("done", &self.done)
1315 .finish()
1316 }
1317}
1318
1319#[derive(Debug)]
1320struct AutocloseRegion {
1321 selection_id: usize,
1322 range: Range<Anchor>,
1323 pair: BracketPair,
1324}
1325
1326#[derive(Debug)]
1327struct SnippetState {
1328 ranges: Vec<Vec<Range<Anchor>>>,
1329 active_index: usize,
1330 choices: Vec<Option<Vec<String>>>,
1331}
1332
1333#[doc(hidden)]
1334pub struct RenameState {
1335 pub range: Range<Anchor>,
1336 pub old_name: Arc<str>,
1337 pub editor: Entity<Editor>,
1338 block_id: CustomBlockId,
1339}
1340
1341struct InvalidationStack<T>(Vec<T>);
1342
1343struct RegisteredInlineCompletionProvider {
1344 provider: Arc<dyn InlineCompletionProviderHandle>,
1345 _subscription: Subscription,
1346}
1347
1348#[derive(Debug, PartialEq, Eq)]
1349pub struct ActiveDiagnosticGroup {
1350 pub active_range: Range<Anchor>,
1351 pub active_message: String,
1352 pub group_id: usize,
1353 pub blocks: HashSet<CustomBlockId>,
1354}
1355
1356#[derive(Debug, PartialEq, Eq)]
1357
1358pub(crate) enum ActiveDiagnostic {
1359 None,
1360 All,
1361 Group(ActiveDiagnosticGroup),
1362}
1363
1364#[derive(Serialize, Deserialize, Clone, Debug)]
1365pub struct ClipboardSelection {
1366 /// The number of bytes in this selection.
1367 pub len: usize,
1368 /// Whether this was a full-line selection.
1369 pub is_entire_line: bool,
1370 /// The indentation of the first line when this content was originally copied.
1371 pub first_line_indent: u32,
1372}
1373
1374// selections, scroll behavior, was newest selection reversed
1375type SelectSyntaxNodeHistoryState = (
1376 Box<[Selection<usize>]>,
1377 SelectSyntaxNodeScrollBehavior,
1378 bool,
1379);
1380
1381#[derive(Default)]
1382struct SelectSyntaxNodeHistory {
1383 stack: Vec<SelectSyntaxNodeHistoryState>,
1384 // disable temporarily to allow changing selections without losing the stack
1385 pub disable_clearing: bool,
1386}
1387
1388impl SelectSyntaxNodeHistory {
1389 pub fn try_clear(&mut self) {
1390 if !self.disable_clearing {
1391 self.stack.clear();
1392 }
1393 }
1394
1395 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1396 self.stack.push(selection);
1397 }
1398
1399 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1400 self.stack.pop()
1401 }
1402}
1403
1404enum SelectSyntaxNodeScrollBehavior {
1405 CursorTop,
1406 FitSelection,
1407 CursorBottom,
1408}
1409
1410#[derive(Debug)]
1411pub(crate) struct NavigationData {
1412 cursor_anchor: Anchor,
1413 cursor_position: Point,
1414 scroll_anchor: ScrollAnchor,
1415 scroll_top_row: u32,
1416}
1417
1418#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1419pub enum GotoDefinitionKind {
1420 Symbol,
1421 Declaration,
1422 Type,
1423 Implementation,
1424}
1425
1426#[derive(Debug, Clone)]
1427enum InlayHintRefreshReason {
1428 ModifiersChanged(bool),
1429 Toggle(bool),
1430 SettingsChange(InlayHintSettings),
1431 NewLinesShown,
1432 BufferEdited(HashSet<Arc<Language>>),
1433 RefreshRequested,
1434 ExcerptsRemoved(Vec<ExcerptId>),
1435}
1436
1437impl InlayHintRefreshReason {
1438 fn description(&self) -> &'static str {
1439 match self {
1440 Self::ModifiersChanged(_) => "modifiers changed",
1441 Self::Toggle(_) => "toggle",
1442 Self::SettingsChange(_) => "settings change",
1443 Self::NewLinesShown => "new lines shown",
1444 Self::BufferEdited(_) => "buffer edited",
1445 Self::RefreshRequested => "refresh requested",
1446 Self::ExcerptsRemoved(_) => "excerpts removed",
1447 }
1448 }
1449}
1450
1451pub enum FormatTarget {
1452 Buffers,
1453 Ranges(Vec<Range<MultiBufferPoint>>),
1454}
1455
1456pub(crate) struct FocusedBlock {
1457 id: BlockId,
1458 focus_handle: WeakFocusHandle,
1459}
1460
1461#[derive(Clone)]
1462enum JumpData {
1463 MultiBufferRow {
1464 row: MultiBufferRow,
1465 line_offset_from_top: u32,
1466 },
1467 MultiBufferPoint {
1468 excerpt_id: ExcerptId,
1469 position: Point,
1470 anchor: text::Anchor,
1471 line_offset_from_top: u32,
1472 },
1473}
1474
1475pub enum MultibufferSelectionMode {
1476 First,
1477 All,
1478}
1479
1480#[derive(Clone, Copy, Debug, Default)]
1481pub struct RewrapOptions {
1482 pub override_language_settings: bool,
1483 pub preserve_existing_whitespace: bool,
1484}
1485
1486impl Editor {
1487 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1488 let buffer = cx.new(|cx| Buffer::local("", cx));
1489 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1490 Self::new(
1491 EditorMode::SingleLine { auto_width: false },
1492 buffer,
1493 None,
1494 window,
1495 cx,
1496 )
1497 }
1498
1499 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1500 let buffer = cx.new(|cx| Buffer::local("", cx));
1501 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1502 Self::new(EditorMode::full(), buffer, None, window, cx)
1503 }
1504
1505 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1506 let buffer = cx.new(|cx| Buffer::local("", cx));
1507 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1508 Self::new(
1509 EditorMode::SingleLine { auto_width: true },
1510 buffer,
1511 None,
1512 window,
1513 cx,
1514 )
1515 }
1516
1517 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1518 let buffer = cx.new(|cx| Buffer::local("", cx));
1519 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1520 Self::new(
1521 EditorMode::AutoHeight { max_lines },
1522 buffer,
1523 None,
1524 window,
1525 cx,
1526 )
1527 }
1528
1529 pub fn for_buffer(
1530 buffer: Entity<Buffer>,
1531 project: Option<Entity<Project>>,
1532 window: &mut Window,
1533 cx: &mut Context<Self>,
1534 ) -> Self {
1535 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1536 Self::new(EditorMode::full(), buffer, project, window, cx)
1537 }
1538
1539 pub fn for_multibuffer(
1540 buffer: Entity<MultiBuffer>,
1541 project: Option<Entity<Project>>,
1542 window: &mut Window,
1543 cx: &mut Context<Self>,
1544 ) -> Self {
1545 Self::new(EditorMode::full(), buffer, project, window, cx)
1546 }
1547
1548 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1549 let mut clone = Self::new(
1550 self.mode.clone(),
1551 self.buffer.clone(),
1552 self.project.clone(),
1553 window,
1554 cx,
1555 );
1556 self.display_map.update(cx, |display_map, cx| {
1557 let snapshot = display_map.snapshot(cx);
1558 clone.display_map.update(cx, |display_map, cx| {
1559 display_map.set_state(&snapshot, cx);
1560 });
1561 });
1562 clone.folds_did_change(cx);
1563 clone.selections.clone_state(&self.selections);
1564 clone.scroll_manager.clone_state(&self.scroll_manager);
1565 clone.searchable = self.searchable;
1566 clone.read_only = self.read_only;
1567 clone
1568 }
1569
1570 pub fn new(
1571 mode: EditorMode,
1572 buffer: Entity<MultiBuffer>,
1573 project: Option<Entity<Project>>,
1574 window: &mut Window,
1575 cx: &mut Context<Self>,
1576 ) -> Self {
1577 Editor::new_internal(mode, buffer, project, None, window, cx)
1578 }
1579
1580 fn new_internal(
1581 mode: EditorMode,
1582 buffer: Entity<MultiBuffer>,
1583 project: Option<Entity<Project>>,
1584 display_map: Option<Entity<DisplayMap>>,
1585 window: &mut Window,
1586 cx: &mut Context<Self>,
1587 ) -> Self {
1588 debug_assert!(
1589 display_map.is_none() || mode.is_minimap(),
1590 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1591 );
1592
1593 let full_mode = mode.is_full();
1594 let diagnostics_max_severity = if full_mode {
1595 EditorSettings::get_global(cx)
1596 .diagnostics_max_severity
1597 .unwrap_or(DiagnosticSeverity::Hint)
1598 } else {
1599 DiagnosticSeverity::Off
1600 };
1601 let style = window.text_style();
1602 let font_size = style.font_size.to_pixels(window.rem_size());
1603 let editor = cx.entity().downgrade();
1604 let fold_placeholder = FoldPlaceholder {
1605 constrain_width: true,
1606 render: Arc::new(move |fold_id, fold_range, cx| {
1607 let editor = editor.clone();
1608 div()
1609 .id(fold_id)
1610 .bg(cx.theme().colors().ghost_element_background)
1611 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1612 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1613 .rounded_xs()
1614 .size_full()
1615 .cursor_pointer()
1616 .child("⋯")
1617 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1618 .on_click(move |_, _window, cx| {
1619 editor
1620 .update(cx, |editor, cx| {
1621 editor.unfold_ranges(
1622 &[fold_range.start..fold_range.end],
1623 true,
1624 false,
1625 cx,
1626 );
1627 cx.stop_propagation();
1628 })
1629 .ok();
1630 })
1631 .into_any()
1632 }),
1633 merge_adjacent: true,
1634 ..FoldPlaceholder::default()
1635 };
1636 let display_map = display_map.unwrap_or_else(|| {
1637 cx.new(|cx| {
1638 DisplayMap::new(
1639 buffer.clone(),
1640 style.font(),
1641 font_size,
1642 None,
1643 FILE_HEADER_HEIGHT,
1644 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1645 fold_placeholder,
1646 diagnostics_max_severity,
1647 cx,
1648 )
1649 })
1650 });
1651
1652 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1653
1654 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1655
1656 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1657 .then(|| language_settings::SoftWrap::None);
1658
1659 let mut project_subscriptions = Vec::new();
1660 if mode.is_full() {
1661 if let Some(project) = project.as_ref() {
1662 project_subscriptions.push(cx.subscribe_in(
1663 project,
1664 window,
1665 |editor, _, event, window, cx| match event {
1666 project::Event::RefreshCodeLens => {
1667 // we always query lens with actions, without storing them, always refreshing them
1668 }
1669 project::Event::RefreshInlayHints => {
1670 editor
1671 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1672 }
1673 project::Event::LanguageServerAdded(..)
1674 | project::Event::LanguageServerRemoved(..) => {
1675 if editor.tasks_update_task.is_none() {
1676 editor.tasks_update_task =
1677 Some(editor.refresh_runnables(window, cx));
1678 }
1679 }
1680 project::Event::SnippetEdit(id, snippet_edits) => {
1681 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1682 let focus_handle = editor.focus_handle(cx);
1683 if focus_handle.is_focused(window) {
1684 let snapshot = buffer.read(cx).snapshot();
1685 for (range, snippet) in snippet_edits {
1686 let editor_range =
1687 language::range_from_lsp(*range).to_offset(&snapshot);
1688 editor
1689 .insert_snippet(
1690 &[editor_range],
1691 snippet.clone(),
1692 window,
1693 cx,
1694 )
1695 .ok();
1696 }
1697 }
1698 }
1699 }
1700 _ => {}
1701 },
1702 ));
1703 if let Some(task_inventory) = project
1704 .read(cx)
1705 .task_store()
1706 .read(cx)
1707 .task_inventory()
1708 .cloned()
1709 {
1710 project_subscriptions.push(cx.observe_in(
1711 &task_inventory,
1712 window,
1713 |editor, _, window, cx| {
1714 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1715 },
1716 ));
1717 };
1718
1719 project_subscriptions.push(cx.subscribe_in(
1720 &project.read(cx).breakpoint_store(),
1721 window,
1722 |editor, _, event, window, cx| match event {
1723 BreakpointStoreEvent::ClearDebugLines => {
1724 editor.clear_row_highlights::<ActiveDebugLine>();
1725 editor.refresh_inline_values(cx);
1726 }
1727 BreakpointStoreEvent::SetDebugLine => {
1728 if editor.go_to_active_debug_line(window, cx) {
1729 cx.stop_propagation();
1730 }
1731
1732 editor.refresh_inline_values(cx);
1733 }
1734 _ => {}
1735 },
1736 ));
1737 }
1738 }
1739
1740 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1741
1742 let inlay_hint_settings =
1743 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1744 let focus_handle = cx.focus_handle();
1745 cx.on_focus(&focus_handle, window, Self::handle_focus)
1746 .detach();
1747 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1748 .detach();
1749 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1750 .detach();
1751 cx.on_blur(&focus_handle, window, Self::handle_blur)
1752 .detach();
1753
1754 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1755 Some(false)
1756 } else {
1757 None
1758 };
1759
1760 let breakpoint_store = match (&mode, project.as_ref()) {
1761 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1762 _ => None,
1763 };
1764
1765 let mut code_action_providers = Vec::new();
1766 let mut load_uncommitted_diff = None;
1767 if let Some(project) = project.clone() {
1768 load_uncommitted_diff = Some(
1769 update_uncommitted_diff_for_buffer(
1770 cx.entity(),
1771 &project,
1772 buffer.read(cx).all_buffers(),
1773 buffer.clone(),
1774 cx,
1775 )
1776 .shared(),
1777 );
1778 code_action_providers.push(Rc::new(project) as Rc<_>);
1779 }
1780
1781 let mut this = Self {
1782 focus_handle,
1783 show_cursor_when_unfocused: false,
1784 last_focused_descendant: None,
1785 buffer: buffer.clone(),
1786 display_map: display_map.clone(),
1787 selections,
1788 scroll_manager: ScrollManager::new(cx),
1789 columnar_selection_tail: None,
1790 add_selections_state: None,
1791 select_next_state: None,
1792 select_prev_state: None,
1793 selection_history: SelectionHistory::default(),
1794 autoclose_regions: Vec::new(),
1795 snippet_stack: InvalidationStack::default(),
1796 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1797 ime_transaction: None,
1798 active_diagnostics: ActiveDiagnostic::None,
1799 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1800 inline_diagnostics_update: Task::ready(()),
1801 inline_diagnostics: Vec::new(),
1802 soft_wrap_mode_override,
1803 diagnostics_max_severity,
1804 hard_wrap: None,
1805 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1806 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1807 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1808 project,
1809 blink_manager: blink_manager.clone(),
1810 show_local_selections: true,
1811 show_scrollbars: ScrollbarAxes {
1812 horizontal: full_mode,
1813 vertical: full_mode,
1814 },
1815 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1816 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1817 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1818 show_gutter: mode.is_full(),
1819 show_line_numbers: None,
1820 use_relative_line_numbers: None,
1821 disable_expand_excerpt_buttons: false,
1822 show_git_diff_gutter: None,
1823 show_code_actions: None,
1824 show_runnables: None,
1825 show_breakpoints: None,
1826 show_wrap_guides: None,
1827 show_indent_guides,
1828 placeholder_text: None,
1829 highlight_order: 0,
1830 highlighted_rows: HashMap::default(),
1831 background_highlights: TreeMap::default(),
1832 gutter_highlights: TreeMap::default(),
1833 scrollbar_marker_state: ScrollbarMarkerState::default(),
1834 active_indent_guides_state: ActiveIndentGuidesState::default(),
1835 nav_history: None,
1836 context_menu: RefCell::new(None),
1837 context_menu_options: None,
1838 mouse_context_menu: None,
1839 completion_tasks: Vec::new(),
1840 inline_blame_popover: None,
1841 signature_help_state: SignatureHelpState::default(),
1842 auto_signature_help: None,
1843 find_all_references_task_sources: Vec::new(),
1844 next_completion_id: 0,
1845 next_inlay_id: 0,
1846 code_action_providers,
1847 available_code_actions: None,
1848 code_actions_task: None,
1849 quick_selection_highlight_task: None,
1850 debounced_selection_highlight_task: None,
1851 document_highlights_task: None,
1852 linked_editing_range_task: None,
1853 pending_rename: None,
1854 searchable: true,
1855 cursor_shape: EditorSettings::get_global(cx)
1856 .cursor_shape
1857 .unwrap_or_default(),
1858 current_line_highlight: None,
1859 autoindent_mode: Some(AutoindentMode::EachLine),
1860 collapse_matches: false,
1861 workspace: None,
1862 input_enabled: true,
1863 use_modal_editing: mode.is_full(),
1864 read_only: mode.is_minimap(),
1865 use_autoclose: true,
1866 use_auto_surround: true,
1867 auto_replace_emoji_shortcode: false,
1868 jsx_tag_auto_close_enabled_in_any_buffer: false,
1869 leader_id: None,
1870 remote_id: None,
1871 hover_state: HoverState::default(),
1872 pending_mouse_down: None,
1873 hovered_link_state: None,
1874 edit_prediction_provider: None,
1875 active_inline_completion: None,
1876 stale_inline_completion_in_menu: None,
1877 edit_prediction_preview: EditPredictionPreview::Inactive {
1878 released_too_fast: false,
1879 },
1880 inline_diagnostics_enabled: mode.is_full(),
1881 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1882 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1883
1884 gutter_hovered: false,
1885 pixel_position_of_newest_cursor: None,
1886 last_bounds: None,
1887 last_position_map: None,
1888 expect_bounds_change: None,
1889 gutter_dimensions: GutterDimensions::default(),
1890 style: None,
1891 show_cursor_names: false,
1892 hovered_cursors: HashMap::default(),
1893 next_editor_action_id: EditorActionId::default(),
1894 editor_actions: Rc::default(),
1895 inline_completions_hidden_for_vim_mode: false,
1896 show_inline_completions_override: None,
1897 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1898 edit_prediction_settings: EditPredictionSettings::Disabled,
1899 edit_prediction_indent_conflict: false,
1900 edit_prediction_requires_modifier_in_indent_conflict: true,
1901 custom_context_menu: None,
1902 show_git_blame_gutter: false,
1903 show_git_blame_inline: false,
1904 show_selection_menu: None,
1905 show_git_blame_inline_delay_task: None,
1906 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1907 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1908 serialize_dirty_buffers: !mode.is_minimap()
1909 && ProjectSettings::get_global(cx)
1910 .session
1911 .restore_unsaved_buffers,
1912 blame: None,
1913 blame_subscription: None,
1914 tasks: BTreeMap::default(),
1915
1916 breakpoint_store,
1917 gutter_breakpoint_indicator: (None, None),
1918 _subscriptions: vec![
1919 cx.observe(&buffer, Self::on_buffer_changed),
1920 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1921 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1922 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1923 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1924 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1925 cx.observe_window_activation(window, |editor, window, cx| {
1926 let active = window.is_window_active();
1927 editor.blink_manager.update(cx, |blink_manager, cx| {
1928 if active {
1929 blink_manager.enable(cx);
1930 } else {
1931 blink_manager.disable(cx);
1932 }
1933 });
1934 if active {
1935 editor.show_mouse_cursor();
1936 }
1937 }),
1938 ],
1939 tasks_update_task: None,
1940 linked_edit_ranges: Default::default(),
1941 in_project_search: false,
1942 previous_search_ranges: None,
1943 breadcrumb_header: None,
1944 focused_block: None,
1945 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1946 addons: HashMap::default(),
1947 registered_buffers: HashMap::default(),
1948 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1949 selection_mark_mode: false,
1950 toggle_fold_multiple_buffers: Task::ready(()),
1951 serialize_selections: Task::ready(()),
1952 serialize_folds: Task::ready(()),
1953 text_style_refinement: None,
1954 load_diff_task: load_uncommitted_diff,
1955 temporary_diff_override: false,
1956 mouse_cursor_hidden: false,
1957 minimap: None,
1958 hide_mouse_mode: EditorSettings::get_global(cx)
1959 .hide_mouse
1960 .unwrap_or_default(),
1961 change_list: ChangeList::new(),
1962 mode,
1963 };
1964 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1965 this._subscriptions
1966 .push(cx.observe(breakpoints, |_, _, cx| {
1967 cx.notify();
1968 }));
1969 }
1970 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1971 this._subscriptions.extend(project_subscriptions);
1972
1973 this._subscriptions.push(cx.subscribe_in(
1974 &cx.entity(),
1975 window,
1976 |editor, _, e: &EditorEvent, window, cx| match e {
1977 EditorEvent::ScrollPositionChanged { local, .. } => {
1978 if *local {
1979 let new_anchor = editor.scroll_manager.anchor();
1980 let snapshot = editor.snapshot(window, cx);
1981 editor.update_restoration_data(cx, move |data| {
1982 data.scroll_position = (
1983 new_anchor.top_row(&snapshot.buffer_snapshot),
1984 new_anchor.offset,
1985 );
1986 });
1987 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
1988 editor.inline_blame_popover.take();
1989 }
1990 }
1991 EditorEvent::Edited { .. } => {
1992 if !vim_enabled(cx) {
1993 let (map, selections) = editor.selections.all_adjusted_display(cx);
1994 let pop_state = editor
1995 .change_list
1996 .last()
1997 .map(|previous| {
1998 previous.len() == selections.len()
1999 && previous.iter().enumerate().all(|(ix, p)| {
2000 p.to_display_point(&map).row()
2001 == selections[ix].head().row()
2002 })
2003 })
2004 .unwrap_or(false);
2005 let new_positions = selections
2006 .into_iter()
2007 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2008 .collect();
2009 editor
2010 .change_list
2011 .push_to_change_list(pop_state, new_positions);
2012 }
2013 }
2014 _ => (),
2015 },
2016 ));
2017
2018 if let Some(dap_store) = this
2019 .project
2020 .as_ref()
2021 .map(|project| project.read(cx).dap_store())
2022 {
2023 let weak_editor = cx.weak_entity();
2024
2025 this._subscriptions
2026 .push(
2027 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2028 let session_entity = cx.entity();
2029 weak_editor
2030 .update(cx, |editor, cx| {
2031 editor._subscriptions.push(
2032 cx.subscribe(&session_entity, Self::on_debug_session_event),
2033 );
2034 })
2035 .ok();
2036 }),
2037 );
2038
2039 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2040 this._subscriptions
2041 .push(cx.subscribe(&session, Self::on_debug_session_event));
2042 }
2043 }
2044
2045 this.end_selection(window, cx);
2046 this.scroll_manager.show_scrollbars(window, cx);
2047 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
2048
2049 if full_mode {
2050 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2051 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2052
2053 if this.git_blame_inline_enabled {
2054 this.start_git_blame_inline(false, window, cx);
2055 }
2056
2057 this.go_to_active_debug_line(window, cx);
2058
2059 if let Some(buffer) = buffer.read(cx).as_singleton() {
2060 if let Some(project) = this.project.as_ref() {
2061 let handle = project.update(cx, |project, cx| {
2062 project.register_buffer_with_language_servers(&buffer, cx)
2063 });
2064 this.registered_buffers
2065 .insert(buffer.read(cx).remote_id(), handle);
2066 }
2067 }
2068
2069 this.minimap = this.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2070 }
2071
2072 this.report_editor_event("Editor Opened", None, cx);
2073 this
2074 }
2075
2076 pub fn deploy_mouse_context_menu(
2077 &mut self,
2078 position: gpui::Point<Pixels>,
2079 context_menu: Entity<ContextMenu>,
2080 window: &mut Window,
2081 cx: &mut Context<Self>,
2082 ) {
2083 self.mouse_context_menu = Some(MouseContextMenu::new(
2084 self,
2085 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2086 context_menu,
2087 window,
2088 cx,
2089 ));
2090 }
2091
2092 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2093 self.mouse_context_menu
2094 .as_ref()
2095 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2096 }
2097
2098 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2099 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2100 }
2101
2102 fn key_context_internal(
2103 &self,
2104 has_active_edit_prediction: bool,
2105 window: &Window,
2106 cx: &App,
2107 ) -> KeyContext {
2108 let mut key_context = KeyContext::new_with_defaults();
2109 key_context.add("Editor");
2110 let mode = match self.mode {
2111 EditorMode::SingleLine { .. } => "single_line",
2112 EditorMode::AutoHeight { .. } => "auto_height",
2113 EditorMode::Minimap { .. } => "minimap",
2114 EditorMode::Full { .. } => "full",
2115 };
2116
2117 if EditorSettings::jupyter_enabled(cx) {
2118 key_context.add("jupyter");
2119 }
2120
2121 key_context.set("mode", mode);
2122 if self.pending_rename.is_some() {
2123 key_context.add("renaming");
2124 }
2125
2126 match self.context_menu.borrow().as_ref() {
2127 Some(CodeContextMenu::Completions(_)) => {
2128 key_context.add("menu");
2129 key_context.add("showing_completions");
2130 }
2131 Some(CodeContextMenu::CodeActions(_)) => {
2132 key_context.add("menu");
2133 key_context.add("showing_code_actions")
2134 }
2135 None => {}
2136 }
2137
2138 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2139 if !self.focus_handle(cx).contains_focused(window, cx)
2140 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2141 {
2142 for addon in self.addons.values() {
2143 addon.extend_key_context(&mut key_context, cx)
2144 }
2145 }
2146
2147 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2148 if let Some(extension) = singleton_buffer
2149 .read(cx)
2150 .file()
2151 .and_then(|file| file.path().extension()?.to_str())
2152 {
2153 key_context.set("extension", extension.to_string());
2154 }
2155 } else {
2156 key_context.add("multibuffer");
2157 }
2158
2159 if has_active_edit_prediction {
2160 if self.edit_prediction_in_conflict() {
2161 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2162 } else {
2163 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2164 key_context.add("copilot_suggestion");
2165 }
2166 }
2167
2168 if self.selection_mark_mode {
2169 key_context.add("selection_mode");
2170 }
2171
2172 key_context
2173 }
2174
2175 fn show_mouse_cursor(&mut self) {
2176 self.mouse_cursor_hidden = false;
2177 }
2178
2179 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2180 self.mouse_cursor_hidden = match origin {
2181 HideMouseCursorOrigin::TypingAction => {
2182 matches!(
2183 self.hide_mouse_mode,
2184 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2185 )
2186 }
2187 HideMouseCursorOrigin::MovementAction => {
2188 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2189 }
2190 };
2191 }
2192
2193 pub fn edit_prediction_in_conflict(&self) -> bool {
2194 if !self.show_edit_predictions_in_menu() {
2195 return false;
2196 }
2197
2198 let showing_completions = self
2199 .context_menu
2200 .borrow()
2201 .as_ref()
2202 .map_or(false, |context| {
2203 matches!(context, CodeContextMenu::Completions(_))
2204 });
2205
2206 showing_completions
2207 || self.edit_prediction_requires_modifier()
2208 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2209 // bindings to insert tab characters.
2210 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2211 }
2212
2213 pub fn accept_edit_prediction_keybind(
2214 &self,
2215 window: &Window,
2216 cx: &App,
2217 ) -> AcceptEditPredictionBinding {
2218 let key_context = self.key_context_internal(true, window, cx);
2219 let in_conflict = self.edit_prediction_in_conflict();
2220
2221 AcceptEditPredictionBinding(
2222 window
2223 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2224 .into_iter()
2225 .filter(|binding| {
2226 !in_conflict
2227 || binding
2228 .keystrokes()
2229 .first()
2230 .map_or(false, |keystroke| keystroke.modifiers.modified())
2231 })
2232 .rev()
2233 .min_by_key(|binding| {
2234 binding
2235 .keystrokes()
2236 .first()
2237 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
2238 }),
2239 )
2240 }
2241
2242 pub fn new_file(
2243 workspace: &mut Workspace,
2244 _: &workspace::NewFile,
2245 window: &mut Window,
2246 cx: &mut Context<Workspace>,
2247 ) {
2248 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2249 "Failed to create buffer",
2250 window,
2251 cx,
2252 |e, _, _| match e.error_code() {
2253 ErrorCode::RemoteUpgradeRequired => Some(format!(
2254 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2255 e.error_tag("required").unwrap_or("the latest version")
2256 )),
2257 _ => None,
2258 },
2259 );
2260 }
2261
2262 pub fn new_in_workspace(
2263 workspace: &mut Workspace,
2264 window: &mut Window,
2265 cx: &mut Context<Workspace>,
2266 ) -> Task<Result<Entity<Editor>>> {
2267 let project = workspace.project().clone();
2268 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2269
2270 cx.spawn_in(window, async move |workspace, cx| {
2271 let buffer = create.await?;
2272 workspace.update_in(cx, |workspace, window, cx| {
2273 let editor =
2274 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2275 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2276 editor
2277 })
2278 })
2279 }
2280
2281 fn new_file_vertical(
2282 workspace: &mut Workspace,
2283 _: &workspace::NewFileSplitVertical,
2284 window: &mut Window,
2285 cx: &mut Context<Workspace>,
2286 ) {
2287 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2288 }
2289
2290 fn new_file_horizontal(
2291 workspace: &mut Workspace,
2292 _: &workspace::NewFileSplitHorizontal,
2293 window: &mut Window,
2294 cx: &mut Context<Workspace>,
2295 ) {
2296 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2297 }
2298
2299 fn new_file_in_direction(
2300 workspace: &mut Workspace,
2301 direction: SplitDirection,
2302 window: &mut Window,
2303 cx: &mut Context<Workspace>,
2304 ) {
2305 let project = workspace.project().clone();
2306 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2307
2308 cx.spawn_in(window, async move |workspace, cx| {
2309 let buffer = create.await?;
2310 workspace.update_in(cx, move |workspace, window, cx| {
2311 workspace.split_item(
2312 direction,
2313 Box::new(
2314 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2315 ),
2316 window,
2317 cx,
2318 )
2319 })?;
2320 anyhow::Ok(())
2321 })
2322 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2323 match e.error_code() {
2324 ErrorCode::RemoteUpgradeRequired => Some(format!(
2325 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2326 e.error_tag("required").unwrap_or("the latest version")
2327 )),
2328 _ => None,
2329 }
2330 });
2331 }
2332
2333 pub fn leader_id(&self) -> Option<CollaboratorId> {
2334 self.leader_id
2335 }
2336
2337 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2338 &self.buffer
2339 }
2340
2341 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2342 self.workspace.as_ref()?.0.upgrade()
2343 }
2344
2345 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2346 self.buffer().read(cx).title(cx)
2347 }
2348
2349 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2350 let git_blame_gutter_max_author_length = self
2351 .render_git_blame_gutter(cx)
2352 .then(|| {
2353 if let Some(blame) = self.blame.as_ref() {
2354 let max_author_length =
2355 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2356 Some(max_author_length)
2357 } else {
2358 None
2359 }
2360 })
2361 .flatten();
2362
2363 EditorSnapshot {
2364 mode: self.mode.clone(),
2365 show_gutter: self.show_gutter,
2366 show_line_numbers: self.show_line_numbers,
2367 show_git_diff_gutter: self.show_git_diff_gutter,
2368 show_code_actions: self.show_code_actions,
2369 show_runnables: self.show_runnables,
2370 show_breakpoints: self.show_breakpoints,
2371 git_blame_gutter_max_author_length,
2372 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2373 scroll_anchor: self.scroll_manager.anchor(),
2374 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2375 placeholder_text: self.placeholder_text.clone(),
2376 is_focused: self.focus_handle.is_focused(window),
2377 current_line_highlight: self
2378 .current_line_highlight
2379 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2380 gutter_hovered: self.gutter_hovered,
2381 }
2382 }
2383
2384 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2385 self.buffer.read(cx).language_at(point, cx)
2386 }
2387
2388 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2389 self.buffer.read(cx).read(cx).file_at(point).cloned()
2390 }
2391
2392 pub fn active_excerpt(
2393 &self,
2394 cx: &App,
2395 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2396 self.buffer
2397 .read(cx)
2398 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2399 }
2400
2401 pub fn mode(&self) -> &EditorMode {
2402 &self.mode
2403 }
2404
2405 pub fn set_mode(&mut self, mode: EditorMode) {
2406 self.mode = mode;
2407 }
2408
2409 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2410 self.collaboration_hub.as_deref()
2411 }
2412
2413 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2414 self.collaboration_hub = Some(hub);
2415 }
2416
2417 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2418 self.in_project_search = in_project_search;
2419 }
2420
2421 pub fn set_custom_context_menu(
2422 &mut self,
2423 f: impl 'static
2424 + Fn(
2425 &mut Self,
2426 DisplayPoint,
2427 &mut Window,
2428 &mut Context<Self>,
2429 ) -> Option<Entity<ui::ContextMenu>>,
2430 ) {
2431 self.custom_context_menu = Some(Box::new(f))
2432 }
2433
2434 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2435 self.completion_provider = provider;
2436 }
2437
2438 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2439 self.semantics_provider.clone()
2440 }
2441
2442 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2443 self.semantics_provider = provider;
2444 }
2445
2446 pub fn set_edit_prediction_provider<T>(
2447 &mut self,
2448 provider: Option<Entity<T>>,
2449 window: &mut Window,
2450 cx: &mut Context<Self>,
2451 ) where
2452 T: EditPredictionProvider,
2453 {
2454 self.edit_prediction_provider =
2455 provider.map(|provider| RegisteredInlineCompletionProvider {
2456 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2457 if this.focus_handle.is_focused(window) {
2458 this.update_visible_inline_completion(window, cx);
2459 }
2460 }),
2461 provider: Arc::new(provider),
2462 });
2463 self.update_edit_prediction_settings(cx);
2464 self.refresh_inline_completion(false, false, window, cx);
2465 }
2466
2467 pub fn placeholder_text(&self) -> Option<&str> {
2468 self.placeholder_text.as_deref()
2469 }
2470
2471 pub fn set_placeholder_text(
2472 &mut self,
2473 placeholder_text: impl Into<Arc<str>>,
2474 cx: &mut Context<Self>,
2475 ) {
2476 let placeholder_text = Some(placeholder_text.into());
2477 if self.placeholder_text != placeholder_text {
2478 self.placeholder_text = placeholder_text;
2479 cx.notify();
2480 }
2481 }
2482
2483 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2484 self.cursor_shape = cursor_shape;
2485
2486 // Disrupt blink for immediate user feedback that the cursor shape has changed
2487 self.blink_manager.update(cx, BlinkManager::show_cursor);
2488
2489 cx.notify();
2490 }
2491
2492 pub fn set_current_line_highlight(
2493 &mut self,
2494 current_line_highlight: Option<CurrentLineHighlight>,
2495 ) {
2496 self.current_line_highlight = current_line_highlight;
2497 }
2498
2499 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2500 self.collapse_matches = collapse_matches;
2501 }
2502
2503 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2504 let buffers = self.buffer.read(cx).all_buffers();
2505 let Some(project) = self.project.as_ref() else {
2506 return;
2507 };
2508 project.update(cx, |project, cx| {
2509 for buffer in buffers {
2510 self.registered_buffers
2511 .entry(buffer.read(cx).remote_id())
2512 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2513 }
2514 })
2515 }
2516
2517 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2518 if self.collapse_matches {
2519 return range.start..range.start;
2520 }
2521 range.clone()
2522 }
2523
2524 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2525 if self.display_map.read(cx).clip_at_line_ends != clip {
2526 self.display_map
2527 .update(cx, |map, _| map.clip_at_line_ends = clip);
2528 }
2529 }
2530
2531 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2532 self.input_enabled = input_enabled;
2533 }
2534
2535 pub fn set_inline_completions_hidden_for_vim_mode(
2536 &mut self,
2537 hidden: bool,
2538 window: &mut Window,
2539 cx: &mut Context<Self>,
2540 ) {
2541 if hidden != self.inline_completions_hidden_for_vim_mode {
2542 self.inline_completions_hidden_for_vim_mode = hidden;
2543 if hidden {
2544 self.update_visible_inline_completion(window, cx);
2545 } else {
2546 self.refresh_inline_completion(true, false, window, cx);
2547 }
2548 }
2549 }
2550
2551 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2552 self.menu_inline_completions_policy = value;
2553 }
2554
2555 pub fn set_autoindent(&mut self, autoindent: bool) {
2556 if autoindent {
2557 self.autoindent_mode = Some(AutoindentMode::EachLine);
2558 } else {
2559 self.autoindent_mode = None;
2560 }
2561 }
2562
2563 pub fn read_only(&self, cx: &App) -> bool {
2564 self.read_only || self.buffer.read(cx).read_only()
2565 }
2566
2567 pub fn set_read_only(&mut self, read_only: bool) {
2568 self.read_only = read_only;
2569 }
2570
2571 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2572 self.use_autoclose = autoclose;
2573 }
2574
2575 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2576 self.use_auto_surround = auto_surround;
2577 }
2578
2579 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2580 self.auto_replace_emoji_shortcode = auto_replace;
2581 }
2582
2583 pub fn toggle_edit_predictions(
2584 &mut self,
2585 _: &ToggleEditPrediction,
2586 window: &mut Window,
2587 cx: &mut Context<Self>,
2588 ) {
2589 if self.show_inline_completions_override.is_some() {
2590 self.set_show_edit_predictions(None, window, cx);
2591 } else {
2592 let show_edit_predictions = !self.edit_predictions_enabled();
2593 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2594 }
2595 }
2596
2597 pub fn set_show_edit_predictions(
2598 &mut self,
2599 show_edit_predictions: Option<bool>,
2600 window: &mut Window,
2601 cx: &mut Context<Self>,
2602 ) {
2603 self.show_inline_completions_override = show_edit_predictions;
2604 self.update_edit_prediction_settings(cx);
2605
2606 if let Some(false) = show_edit_predictions {
2607 self.discard_inline_completion(false, cx);
2608 } else {
2609 self.refresh_inline_completion(false, true, window, cx);
2610 }
2611 }
2612
2613 fn inline_completions_disabled_in_scope(
2614 &self,
2615 buffer: &Entity<Buffer>,
2616 buffer_position: language::Anchor,
2617 cx: &App,
2618 ) -> bool {
2619 let snapshot = buffer.read(cx).snapshot();
2620 let settings = snapshot.settings_at(buffer_position, cx);
2621
2622 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2623 return false;
2624 };
2625
2626 scope.override_name().map_or(false, |scope_name| {
2627 settings
2628 .edit_predictions_disabled_in
2629 .iter()
2630 .any(|s| s == scope_name)
2631 })
2632 }
2633
2634 pub fn set_use_modal_editing(&mut self, to: bool) {
2635 self.use_modal_editing = to;
2636 }
2637
2638 pub fn use_modal_editing(&self) -> bool {
2639 self.use_modal_editing
2640 }
2641
2642 fn selections_did_change(
2643 &mut self,
2644 local: bool,
2645 old_cursor_position: &Anchor,
2646 show_completions: bool,
2647 window: &mut Window,
2648 cx: &mut Context<Self>,
2649 ) {
2650 window.invalidate_character_coordinates();
2651
2652 // Copy selections to primary selection buffer
2653 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2654 if local {
2655 let selections = self.selections.all::<usize>(cx);
2656 let buffer_handle = self.buffer.read(cx).read(cx);
2657
2658 let mut text = String::new();
2659 for (index, selection) in selections.iter().enumerate() {
2660 let text_for_selection = buffer_handle
2661 .text_for_range(selection.start..selection.end)
2662 .collect::<String>();
2663
2664 text.push_str(&text_for_selection);
2665 if index != selections.len() - 1 {
2666 text.push('\n');
2667 }
2668 }
2669
2670 if !text.is_empty() {
2671 cx.write_to_primary(ClipboardItem::new_string(text));
2672 }
2673 }
2674
2675 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2676 self.buffer.update(cx, |buffer, cx| {
2677 buffer.set_active_selections(
2678 &self.selections.disjoint_anchors(),
2679 self.selections.line_mode,
2680 self.cursor_shape,
2681 cx,
2682 )
2683 });
2684 }
2685 let display_map = self
2686 .display_map
2687 .update(cx, |display_map, cx| display_map.snapshot(cx));
2688 let buffer = &display_map.buffer_snapshot;
2689 self.add_selections_state = None;
2690 self.select_next_state = None;
2691 self.select_prev_state = None;
2692 self.select_syntax_node_history.try_clear();
2693 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2694 self.snippet_stack
2695 .invalidate(&self.selections.disjoint_anchors(), buffer);
2696 self.take_rename(false, window, cx);
2697
2698 let new_cursor_position = self.selections.newest_anchor().head();
2699
2700 self.push_to_nav_history(
2701 *old_cursor_position,
2702 Some(new_cursor_position.to_point(buffer)),
2703 false,
2704 cx,
2705 );
2706
2707 if local {
2708 let new_cursor_position = self.selections.newest_anchor().head();
2709 let mut context_menu = self.context_menu.borrow_mut();
2710 let completion_menu = match context_menu.as_ref() {
2711 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2712 _ => {
2713 *context_menu = None;
2714 None
2715 }
2716 };
2717 if let Some(buffer_id) = new_cursor_position.buffer_id {
2718 if !self.registered_buffers.contains_key(&buffer_id) {
2719 if let Some(project) = self.project.as_ref() {
2720 project.update(cx, |project, cx| {
2721 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2722 return;
2723 };
2724 self.registered_buffers.insert(
2725 buffer_id,
2726 project.register_buffer_with_language_servers(&buffer, cx),
2727 );
2728 })
2729 }
2730 }
2731 }
2732
2733 if let Some(completion_menu) = completion_menu {
2734 let cursor_position = new_cursor_position.to_offset(buffer);
2735 let (word_range, kind) =
2736 buffer.surrounding_word(completion_menu.initial_position, true);
2737 if kind == Some(CharKind::Word)
2738 && word_range.to_inclusive().contains(&cursor_position)
2739 {
2740 let mut completion_menu = completion_menu.clone();
2741 drop(context_menu);
2742
2743 let query = Self::completion_query(buffer, cursor_position);
2744 let completion_provider = self.completion_provider.clone();
2745 cx.spawn_in(window, async move |this, cx| {
2746 completion_menu
2747 .filter(query.as_deref(), completion_provider, this.clone(), cx)
2748 .await;
2749
2750 this.update(cx, |this, cx| {
2751 let mut context_menu = this.context_menu.borrow_mut();
2752 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2753 else {
2754 return;
2755 };
2756
2757 if menu.id > completion_menu.id {
2758 return;
2759 }
2760
2761 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2762 drop(context_menu);
2763 cx.notify();
2764 })
2765 })
2766 .detach();
2767
2768 if show_completions {
2769 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2770 }
2771 } else {
2772 drop(context_menu);
2773 self.hide_context_menu(window, cx);
2774 }
2775 } else {
2776 drop(context_menu);
2777 }
2778
2779 hide_hover(self, cx);
2780
2781 if old_cursor_position.to_display_point(&display_map).row()
2782 != new_cursor_position.to_display_point(&display_map).row()
2783 {
2784 self.available_code_actions.take();
2785 }
2786 self.refresh_code_actions(window, cx);
2787 self.refresh_document_highlights(cx);
2788 self.refresh_selected_text_highlights(false, window, cx);
2789 refresh_matching_bracket_highlights(self, window, cx);
2790 self.update_visible_inline_completion(window, cx);
2791 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2792 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2793 self.inline_blame_popover.take();
2794 if self.git_blame_inline_enabled {
2795 self.start_inline_blame_timer(window, cx);
2796 }
2797 }
2798
2799 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2800 cx.emit(EditorEvent::SelectionsChanged { local });
2801
2802 let selections = &self.selections.disjoint;
2803 if selections.len() == 1 {
2804 cx.emit(SearchEvent::ActiveMatchChanged)
2805 }
2806 if local {
2807 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2808 let inmemory_selections = selections
2809 .iter()
2810 .map(|s| {
2811 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2812 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2813 })
2814 .collect();
2815 self.update_restoration_data(cx, |data| {
2816 data.selections = inmemory_selections;
2817 });
2818
2819 if WorkspaceSettings::get(None, cx).restore_on_startup
2820 != RestoreOnStartupBehavior::None
2821 {
2822 if let Some(workspace_id) =
2823 self.workspace.as_ref().and_then(|workspace| workspace.1)
2824 {
2825 let snapshot = self.buffer().read(cx).snapshot(cx);
2826 let selections = selections.clone();
2827 let background_executor = cx.background_executor().clone();
2828 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2829 self.serialize_selections = cx.background_spawn(async move {
2830 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2831 let db_selections = selections
2832 .iter()
2833 .map(|selection| {
2834 (
2835 selection.start.to_offset(&snapshot),
2836 selection.end.to_offset(&snapshot),
2837 )
2838 })
2839 .collect();
2840
2841 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2842 .await
2843 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2844 .log_err();
2845 });
2846 }
2847 }
2848 }
2849 }
2850
2851 cx.notify();
2852 }
2853
2854 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2855 use text::ToOffset as _;
2856 use text::ToPoint as _;
2857
2858 if self.mode.is_minimap()
2859 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2860 {
2861 return;
2862 }
2863
2864 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2865 return;
2866 };
2867
2868 let snapshot = singleton.read(cx).snapshot();
2869 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2870 let display_snapshot = display_map.snapshot(cx);
2871
2872 display_snapshot
2873 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2874 .map(|fold| {
2875 fold.range.start.text_anchor.to_point(&snapshot)
2876 ..fold.range.end.text_anchor.to_point(&snapshot)
2877 })
2878 .collect()
2879 });
2880 self.update_restoration_data(cx, |data| {
2881 data.folds = inmemory_folds;
2882 });
2883
2884 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2885 return;
2886 };
2887 let background_executor = cx.background_executor().clone();
2888 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2889 let db_folds = self.display_map.update(cx, |display_map, cx| {
2890 display_map
2891 .snapshot(cx)
2892 .folds_in_range(0..snapshot.len())
2893 .map(|fold| {
2894 (
2895 fold.range.start.text_anchor.to_offset(&snapshot),
2896 fold.range.end.text_anchor.to_offset(&snapshot),
2897 )
2898 })
2899 .collect()
2900 });
2901 self.serialize_folds = cx.background_spawn(async move {
2902 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2903 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2904 .await
2905 .with_context(|| {
2906 format!(
2907 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2908 )
2909 })
2910 .log_err();
2911 });
2912 }
2913
2914 pub fn sync_selections(
2915 &mut self,
2916 other: Entity<Editor>,
2917 cx: &mut Context<Self>,
2918 ) -> gpui::Subscription {
2919 let other_selections = other.read(cx).selections.disjoint.to_vec();
2920 self.selections.change_with(cx, |selections| {
2921 selections.select_anchors(other_selections);
2922 });
2923
2924 let other_subscription =
2925 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2926 EditorEvent::SelectionsChanged { local: true } => {
2927 let other_selections = other.read(cx).selections.disjoint.to_vec();
2928 if other_selections.is_empty() {
2929 return;
2930 }
2931 this.selections.change_with(cx, |selections| {
2932 selections.select_anchors(other_selections);
2933 });
2934 }
2935 _ => {}
2936 });
2937
2938 let this_subscription =
2939 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2940 EditorEvent::SelectionsChanged { local: true } => {
2941 let these_selections = this.selections.disjoint.to_vec();
2942 if these_selections.is_empty() {
2943 return;
2944 }
2945 other.update(cx, |other_editor, cx| {
2946 other_editor.selections.change_with(cx, |selections| {
2947 selections.select_anchors(these_selections);
2948 })
2949 });
2950 }
2951 _ => {}
2952 });
2953
2954 Subscription::join(other_subscription, this_subscription)
2955 }
2956
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(autoscroll, true, window, cx, change)
2965 }
2966
2967 fn change_selections_inner<R>(
2968 &mut self,
2969 autoscroll: Option<Autoscroll>,
2970 request_completions: bool,
2971 window: &mut Window,
2972 cx: &mut Context<Self>,
2973 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2974 ) -> R {
2975 let old_cursor_position = self.selections.newest_anchor().head();
2976 self.push_to_selection_history();
2977
2978 let (changed, result) = self.selections.change_with(cx, change);
2979
2980 if changed {
2981 if let Some(autoscroll) = autoscroll {
2982 self.request_autoscroll(autoscroll, cx);
2983 }
2984 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2985
2986 if self.should_open_signature_help_automatically(
2987 &old_cursor_position,
2988 self.signature_help_state.backspace_pressed(),
2989 cx,
2990 ) {
2991 self.show_signature_help(&ShowSignatureHelp, window, cx);
2992 }
2993 self.signature_help_state.set_backspace_pressed(false);
2994 }
2995
2996 result
2997 }
2998
2999 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3000 where
3001 I: IntoIterator<Item = (Range<S>, T)>,
3002 S: ToOffset,
3003 T: Into<Arc<str>>,
3004 {
3005 if self.read_only(cx) {
3006 return;
3007 }
3008
3009 self.buffer
3010 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3011 }
3012
3013 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3014 where
3015 I: IntoIterator<Item = (Range<S>, T)>,
3016 S: ToOffset,
3017 T: Into<Arc<str>>,
3018 {
3019 if self.read_only(cx) {
3020 return;
3021 }
3022
3023 self.buffer.update(cx, |buffer, cx| {
3024 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3025 });
3026 }
3027
3028 pub fn edit_with_block_indent<I, S, T>(
3029 &mut self,
3030 edits: I,
3031 original_indent_columns: Vec<Option<u32>>,
3032 cx: &mut Context<Self>,
3033 ) where
3034 I: IntoIterator<Item = (Range<S>, T)>,
3035 S: ToOffset,
3036 T: Into<Arc<str>>,
3037 {
3038 if self.read_only(cx) {
3039 return;
3040 }
3041
3042 self.buffer.update(cx, |buffer, cx| {
3043 buffer.edit(
3044 edits,
3045 Some(AutoindentMode::Block {
3046 original_indent_columns,
3047 }),
3048 cx,
3049 )
3050 });
3051 }
3052
3053 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3054 self.hide_context_menu(window, cx);
3055
3056 match phase {
3057 SelectPhase::Begin {
3058 position,
3059 add,
3060 click_count,
3061 } => self.begin_selection(position, add, click_count, window, cx),
3062 SelectPhase::BeginColumnar {
3063 position,
3064 goal_column,
3065 reset,
3066 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3067 SelectPhase::Extend {
3068 position,
3069 click_count,
3070 } => self.extend_selection(position, click_count, window, cx),
3071 SelectPhase::Update {
3072 position,
3073 goal_column,
3074 scroll_delta,
3075 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3076 SelectPhase::End => self.end_selection(window, cx),
3077 }
3078 }
3079
3080 fn extend_selection(
3081 &mut self,
3082 position: DisplayPoint,
3083 click_count: usize,
3084 window: &mut Window,
3085 cx: &mut Context<Self>,
3086 ) {
3087 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3088 let tail = self.selections.newest::<usize>(cx).tail();
3089 self.begin_selection(position, false, click_count, window, cx);
3090
3091 let position = position.to_offset(&display_map, Bias::Left);
3092 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3093
3094 let mut pending_selection = self
3095 .selections
3096 .pending_anchor()
3097 .expect("extend_selection not called with pending selection");
3098 if position >= tail {
3099 pending_selection.start = tail_anchor;
3100 } else {
3101 pending_selection.end = tail_anchor;
3102 pending_selection.reversed = true;
3103 }
3104
3105 let mut pending_mode = self.selections.pending_mode().unwrap();
3106 match &mut pending_mode {
3107 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3108 _ => {}
3109 }
3110
3111 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3112
3113 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3114 s.set_pending(pending_selection, pending_mode)
3115 });
3116 }
3117
3118 fn begin_selection(
3119 &mut self,
3120 position: DisplayPoint,
3121 add: bool,
3122 click_count: usize,
3123 window: &mut Window,
3124 cx: &mut Context<Self>,
3125 ) {
3126 if !self.focus_handle.is_focused(window) {
3127 self.last_focused_descendant = None;
3128 window.focus(&self.focus_handle);
3129 }
3130
3131 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3132 let buffer = &display_map.buffer_snapshot;
3133 let position = display_map.clip_point(position, Bias::Left);
3134
3135 let start;
3136 let end;
3137 let mode;
3138 let mut auto_scroll;
3139 match click_count {
3140 1 => {
3141 start = buffer.anchor_before(position.to_point(&display_map));
3142 end = start;
3143 mode = SelectMode::Character;
3144 auto_scroll = true;
3145 }
3146 2 => {
3147 let range = movement::surrounding_word(&display_map, position);
3148 start = buffer.anchor_before(range.start.to_point(&display_map));
3149 end = buffer.anchor_before(range.end.to_point(&display_map));
3150 mode = SelectMode::Word(start..end);
3151 auto_scroll = true;
3152 }
3153 3 => {
3154 let position = display_map
3155 .clip_point(position, Bias::Left)
3156 .to_point(&display_map);
3157 let line_start = display_map.prev_line_boundary(position).0;
3158 let next_line_start = buffer.clip_point(
3159 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3160 Bias::Left,
3161 );
3162 start = buffer.anchor_before(line_start);
3163 end = buffer.anchor_before(next_line_start);
3164 mode = SelectMode::Line(start..end);
3165 auto_scroll = true;
3166 }
3167 _ => {
3168 start = buffer.anchor_before(0);
3169 end = buffer.anchor_before(buffer.len());
3170 mode = SelectMode::All;
3171 auto_scroll = false;
3172 }
3173 }
3174 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3175
3176 let point_to_delete: Option<usize> = {
3177 let selected_points: Vec<Selection<Point>> =
3178 self.selections.disjoint_in_range(start..end, cx);
3179
3180 if !add || click_count > 1 {
3181 None
3182 } else if !selected_points.is_empty() {
3183 Some(selected_points[0].id)
3184 } else {
3185 let clicked_point_already_selected =
3186 self.selections.disjoint.iter().find(|selection| {
3187 selection.start.to_point(buffer) == start.to_point(buffer)
3188 || selection.end.to_point(buffer) == end.to_point(buffer)
3189 });
3190
3191 clicked_point_already_selected.map(|selection| selection.id)
3192 }
3193 };
3194
3195 let selections_count = self.selections.count();
3196
3197 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3198 if let Some(point_to_delete) = point_to_delete {
3199 s.delete(point_to_delete);
3200
3201 if selections_count == 1 {
3202 s.set_pending_anchor_range(start..end, mode);
3203 }
3204 } else {
3205 if !add {
3206 s.clear_disjoint();
3207 }
3208
3209 s.set_pending_anchor_range(start..end, mode);
3210 }
3211 });
3212 }
3213
3214 fn begin_columnar_selection(
3215 &mut self,
3216 position: DisplayPoint,
3217 goal_column: u32,
3218 reset: bool,
3219 window: &mut Window,
3220 cx: &mut Context<Self>,
3221 ) {
3222 if !self.focus_handle.is_focused(window) {
3223 self.last_focused_descendant = None;
3224 window.focus(&self.focus_handle);
3225 }
3226
3227 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3228
3229 if reset {
3230 let pointer_position = display_map
3231 .buffer_snapshot
3232 .anchor_before(position.to_point(&display_map));
3233
3234 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3235 s.clear_disjoint();
3236 s.set_pending_anchor_range(
3237 pointer_position..pointer_position,
3238 SelectMode::Character,
3239 );
3240 });
3241 }
3242
3243 let tail = self.selections.newest::<Point>(cx).tail();
3244 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3245
3246 if !reset {
3247 self.select_columns(
3248 tail.to_display_point(&display_map),
3249 position,
3250 goal_column,
3251 &display_map,
3252 window,
3253 cx,
3254 );
3255 }
3256 }
3257
3258 fn update_selection(
3259 &mut self,
3260 position: DisplayPoint,
3261 goal_column: u32,
3262 scroll_delta: gpui::Point<f32>,
3263 window: &mut Window,
3264 cx: &mut Context<Self>,
3265 ) {
3266 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3267
3268 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3269 let tail = tail.to_display_point(&display_map);
3270 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3271 } else if let Some(mut pending) = self.selections.pending_anchor() {
3272 let buffer = self.buffer.read(cx).snapshot(cx);
3273 let head;
3274 let tail;
3275 let mode = self.selections.pending_mode().unwrap();
3276 match &mode {
3277 SelectMode::Character => {
3278 head = position.to_point(&display_map);
3279 tail = pending.tail().to_point(&buffer);
3280 }
3281 SelectMode::Word(original_range) => {
3282 let original_display_range = original_range.start.to_display_point(&display_map)
3283 ..original_range.end.to_display_point(&display_map);
3284 let original_buffer_range = original_display_range.start.to_point(&display_map)
3285 ..original_display_range.end.to_point(&display_map);
3286 if movement::is_inside_word(&display_map, position)
3287 || original_display_range.contains(&position)
3288 {
3289 let word_range = movement::surrounding_word(&display_map, position);
3290 if word_range.start < original_display_range.start {
3291 head = word_range.start.to_point(&display_map);
3292 } else {
3293 head = word_range.end.to_point(&display_map);
3294 }
3295 } else {
3296 head = position.to_point(&display_map);
3297 }
3298
3299 if head <= original_buffer_range.start {
3300 tail = original_buffer_range.end;
3301 } else {
3302 tail = original_buffer_range.start;
3303 }
3304 }
3305 SelectMode::Line(original_range) => {
3306 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3307
3308 let position = display_map
3309 .clip_point(position, Bias::Left)
3310 .to_point(&display_map);
3311 let line_start = display_map.prev_line_boundary(position).0;
3312 let next_line_start = buffer.clip_point(
3313 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3314 Bias::Left,
3315 );
3316
3317 if line_start < original_range.start {
3318 head = line_start
3319 } else {
3320 head = next_line_start
3321 }
3322
3323 if head <= original_range.start {
3324 tail = original_range.end;
3325 } else {
3326 tail = original_range.start;
3327 }
3328 }
3329 SelectMode::All => {
3330 return;
3331 }
3332 };
3333
3334 if head < tail {
3335 pending.start = buffer.anchor_before(head);
3336 pending.end = buffer.anchor_before(tail);
3337 pending.reversed = true;
3338 } else {
3339 pending.start = buffer.anchor_before(tail);
3340 pending.end = buffer.anchor_before(head);
3341 pending.reversed = false;
3342 }
3343
3344 self.change_selections(None, window, cx, |s| {
3345 s.set_pending(pending, mode);
3346 });
3347 } else {
3348 log::error!("update_selection dispatched with no pending selection");
3349 return;
3350 }
3351
3352 self.apply_scroll_delta(scroll_delta, window, cx);
3353 cx.notify();
3354 }
3355
3356 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3357 self.columnar_selection_tail.take();
3358 if self.selections.pending_anchor().is_some() {
3359 let selections = self.selections.all::<usize>(cx);
3360 self.change_selections(None, window, cx, |s| {
3361 s.select(selections);
3362 s.clear_pending();
3363 });
3364 }
3365 }
3366
3367 fn select_columns(
3368 &mut self,
3369 tail: DisplayPoint,
3370 head: DisplayPoint,
3371 goal_column: u32,
3372 display_map: &DisplaySnapshot,
3373 window: &mut Window,
3374 cx: &mut Context<Self>,
3375 ) {
3376 let start_row = cmp::min(tail.row(), head.row());
3377 let end_row = cmp::max(tail.row(), head.row());
3378 let start_column = cmp::min(tail.column(), goal_column);
3379 let end_column = cmp::max(tail.column(), goal_column);
3380 let reversed = start_column < tail.column();
3381
3382 let selection_ranges = (start_row.0..=end_row.0)
3383 .map(DisplayRow)
3384 .filter_map(|row| {
3385 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
3386 let start = display_map
3387 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3388 .to_point(display_map);
3389 let end = display_map
3390 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3391 .to_point(display_map);
3392 if reversed {
3393 Some(end..start)
3394 } else {
3395 Some(start..end)
3396 }
3397 } else {
3398 None
3399 }
3400 })
3401 .collect::<Vec<_>>();
3402
3403 self.change_selections(None, window, cx, |s| {
3404 s.select_ranges(selection_ranges);
3405 });
3406 cx.notify();
3407 }
3408
3409 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3410 self.selections
3411 .all_adjusted(cx)
3412 .iter()
3413 .any(|selection| !selection.is_empty())
3414 }
3415
3416 pub fn has_pending_nonempty_selection(&self) -> bool {
3417 let pending_nonempty_selection = match self.selections.pending_anchor() {
3418 Some(Selection { start, end, .. }) => start != end,
3419 None => false,
3420 };
3421
3422 pending_nonempty_selection
3423 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3424 }
3425
3426 pub fn has_pending_selection(&self) -> bool {
3427 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3428 }
3429
3430 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3431 self.selection_mark_mode = false;
3432
3433 if self.clear_expanded_diff_hunks(cx) {
3434 cx.notify();
3435 return;
3436 }
3437 if self.dismiss_menus_and_popups(true, window, cx) {
3438 return;
3439 }
3440
3441 if self.mode.is_full()
3442 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3443 {
3444 return;
3445 }
3446
3447 cx.propagate();
3448 }
3449
3450 pub fn dismiss_menus_and_popups(
3451 &mut self,
3452 is_user_requested: bool,
3453 window: &mut Window,
3454 cx: &mut Context<Self>,
3455 ) -> bool {
3456 if self.take_rename(false, window, cx).is_some() {
3457 return true;
3458 }
3459
3460 if hide_hover(self, cx) {
3461 return true;
3462 }
3463
3464 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3465 return true;
3466 }
3467
3468 if self.hide_context_menu(window, cx).is_some() {
3469 return true;
3470 }
3471
3472 if self.mouse_context_menu.take().is_some() {
3473 return true;
3474 }
3475
3476 if is_user_requested && self.discard_inline_completion(true, cx) {
3477 return true;
3478 }
3479
3480 if self.snippet_stack.pop().is_some() {
3481 return true;
3482 }
3483
3484 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3485 self.dismiss_diagnostics(cx);
3486 return true;
3487 }
3488
3489 false
3490 }
3491
3492 fn linked_editing_ranges_for(
3493 &self,
3494 selection: Range<text::Anchor>,
3495 cx: &App,
3496 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3497 if self.linked_edit_ranges.is_empty() {
3498 return None;
3499 }
3500 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3501 selection.end.buffer_id.and_then(|end_buffer_id| {
3502 if selection.start.buffer_id != Some(end_buffer_id) {
3503 return None;
3504 }
3505 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3506 let snapshot = buffer.read(cx).snapshot();
3507 self.linked_edit_ranges
3508 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3509 .map(|ranges| (ranges, snapshot, buffer))
3510 })?;
3511 use text::ToOffset as TO;
3512 // find offset from the start of current range to current cursor position
3513 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3514
3515 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3516 let start_difference = start_offset - start_byte_offset;
3517 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3518 let end_difference = end_offset - start_byte_offset;
3519 // Current range has associated linked ranges.
3520 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3521 for range in linked_ranges.iter() {
3522 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3523 let end_offset = start_offset + end_difference;
3524 let start_offset = start_offset + start_difference;
3525 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3526 continue;
3527 }
3528 if self.selections.disjoint_anchor_ranges().any(|s| {
3529 if s.start.buffer_id != selection.start.buffer_id
3530 || s.end.buffer_id != selection.end.buffer_id
3531 {
3532 return false;
3533 }
3534 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3535 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3536 }) {
3537 continue;
3538 }
3539 let start = buffer_snapshot.anchor_after(start_offset);
3540 let end = buffer_snapshot.anchor_after(end_offset);
3541 linked_edits
3542 .entry(buffer.clone())
3543 .or_default()
3544 .push(start..end);
3545 }
3546 Some(linked_edits)
3547 }
3548
3549 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3550 let text: Arc<str> = text.into();
3551
3552 if self.read_only(cx) {
3553 return;
3554 }
3555
3556 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3557
3558 let selections = self.selections.all_adjusted(cx);
3559 let mut bracket_inserted = false;
3560 let mut edits = Vec::new();
3561 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3562 let mut new_selections = Vec::with_capacity(selections.len());
3563 let mut new_autoclose_regions = Vec::new();
3564 let snapshot = self.buffer.read(cx).read(cx);
3565 let mut clear_linked_edit_ranges = false;
3566
3567 for (selection, autoclose_region) in
3568 self.selections_with_autoclose_regions(selections, &snapshot)
3569 {
3570 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3571 // Determine if the inserted text matches the opening or closing
3572 // bracket of any of this language's bracket pairs.
3573 let mut bracket_pair = None;
3574 let mut is_bracket_pair_start = false;
3575 let mut is_bracket_pair_end = false;
3576 if !text.is_empty() {
3577 let mut bracket_pair_matching_end = None;
3578 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3579 // and they are removing the character that triggered IME popup.
3580 for (pair, enabled) in scope.brackets() {
3581 if !pair.close && !pair.surround {
3582 continue;
3583 }
3584
3585 if enabled && pair.start.ends_with(text.as_ref()) {
3586 let prefix_len = pair.start.len() - text.len();
3587 let preceding_text_matches_prefix = prefix_len == 0
3588 || (selection.start.column >= (prefix_len as u32)
3589 && snapshot.contains_str_at(
3590 Point::new(
3591 selection.start.row,
3592 selection.start.column - (prefix_len as u32),
3593 ),
3594 &pair.start[..prefix_len],
3595 ));
3596 if preceding_text_matches_prefix {
3597 bracket_pair = Some(pair.clone());
3598 is_bracket_pair_start = true;
3599 break;
3600 }
3601 }
3602 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3603 {
3604 // take first bracket pair matching end, but don't break in case a later bracket
3605 // pair matches start
3606 bracket_pair_matching_end = Some(pair.clone());
3607 }
3608 }
3609 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3610 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3611 is_bracket_pair_end = true;
3612 }
3613 }
3614
3615 if let Some(bracket_pair) = bracket_pair {
3616 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3617 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3618 let auto_surround =
3619 self.use_auto_surround && snapshot_settings.use_auto_surround;
3620 if selection.is_empty() {
3621 if is_bracket_pair_start {
3622 // If the inserted text is a suffix of an opening bracket and the
3623 // selection is preceded by the rest of the opening bracket, then
3624 // insert the closing bracket.
3625 let following_text_allows_autoclose = snapshot
3626 .chars_at(selection.start)
3627 .next()
3628 .map_or(true, |c| scope.should_autoclose_before(c));
3629
3630 let preceding_text_allows_autoclose = selection.start.column == 0
3631 || snapshot.reversed_chars_at(selection.start).next().map_or(
3632 true,
3633 |c| {
3634 bracket_pair.start != bracket_pair.end
3635 || !snapshot
3636 .char_classifier_at(selection.start)
3637 .is_word(c)
3638 },
3639 );
3640
3641 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3642 && bracket_pair.start.len() == 1
3643 {
3644 let target = bracket_pair.start.chars().next().unwrap();
3645 let current_line_count = snapshot
3646 .reversed_chars_at(selection.start)
3647 .take_while(|&c| c != '\n')
3648 .filter(|&c| c == target)
3649 .count();
3650 current_line_count % 2 == 1
3651 } else {
3652 false
3653 };
3654
3655 if autoclose
3656 && bracket_pair.close
3657 && following_text_allows_autoclose
3658 && preceding_text_allows_autoclose
3659 && !is_closing_quote
3660 {
3661 let anchor = snapshot.anchor_before(selection.end);
3662 new_selections.push((selection.map(|_| anchor), text.len()));
3663 new_autoclose_regions.push((
3664 anchor,
3665 text.len(),
3666 selection.id,
3667 bracket_pair.clone(),
3668 ));
3669 edits.push((
3670 selection.range(),
3671 format!("{}{}", text, bracket_pair.end).into(),
3672 ));
3673 bracket_inserted = true;
3674 continue;
3675 }
3676 }
3677
3678 if let Some(region) = autoclose_region {
3679 // If the selection is followed by an auto-inserted closing bracket,
3680 // then don't insert that closing bracket again; just move the selection
3681 // past the closing bracket.
3682 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3683 && text.as_ref() == region.pair.end.as_str();
3684 if should_skip {
3685 let anchor = snapshot.anchor_after(selection.end);
3686 new_selections
3687 .push((selection.map(|_| anchor), region.pair.end.len()));
3688 continue;
3689 }
3690 }
3691
3692 let always_treat_brackets_as_autoclosed = snapshot
3693 .language_settings_at(selection.start, cx)
3694 .always_treat_brackets_as_autoclosed;
3695 if always_treat_brackets_as_autoclosed
3696 && is_bracket_pair_end
3697 && snapshot.contains_str_at(selection.end, text.as_ref())
3698 {
3699 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3700 // and the inserted text is a closing bracket and the selection is followed
3701 // by the closing bracket then move the selection past the closing bracket.
3702 let anchor = snapshot.anchor_after(selection.end);
3703 new_selections.push((selection.map(|_| anchor), text.len()));
3704 continue;
3705 }
3706 }
3707 // If an opening bracket is 1 character long and is typed while
3708 // text is selected, then surround that text with the bracket pair.
3709 else if auto_surround
3710 && bracket_pair.surround
3711 && is_bracket_pair_start
3712 && bracket_pair.start.chars().count() == 1
3713 {
3714 edits.push((selection.start..selection.start, text.clone()));
3715 edits.push((
3716 selection.end..selection.end,
3717 bracket_pair.end.as_str().into(),
3718 ));
3719 bracket_inserted = true;
3720 new_selections.push((
3721 Selection {
3722 id: selection.id,
3723 start: snapshot.anchor_after(selection.start),
3724 end: snapshot.anchor_before(selection.end),
3725 reversed: selection.reversed,
3726 goal: selection.goal,
3727 },
3728 0,
3729 ));
3730 continue;
3731 }
3732 }
3733 }
3734
3735 if self.auto_replace_emoji_shortcode
3736 && selection.is_empty()
3737 && text.as_ref().ends_with(':')
3738 {
3739 if let Some(possible_emoji_short_code) =
3740 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3741 {
3742 if !possible_emoji_short_code.is_empty() {
3743 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3744 let emoji_shortcode_start = Point::new(
3745 selection.start.row,
3746 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3747 );
3748
3749 // Remove shortcode from buffer
3750 edits.push((
3751 emoji_shortcode_start..selection.start,
3752 "".to_string().into(),
3753 ));
3754 new_selections.push((
3755 Selection {
3756 id: selection.id,
3757 start: snapshot.anchor_after(emoji_shortcode_start),
3758 end: snapshot.anchor_before(selection.start),
3759 reversed: selection.reversed,
3760 goal: selection.goal,
3761 },
3762 0,
3763 ));
3764
3765 // Insert emoji
3766 let selection_start_anchor = snapshot.anchor_after(selection.start);
3767 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3768 edits.push((selection.start..selection.end, emoji.to_string().into()));
3769
3770 continue;
3771 }
3772 }
3773 }
3774 }
3775
3776 // If not handling any auto-close operation, then just replace the selected
3777 // text with the given input and move the selection to the end of the
3778 // newly inserted text.
3779 let anchor = snapshot.anchor_after(selection.end);
3780 if !self.linked_edit_ranges.is_empty() {
3781 let start_anchor = snapshot.anchor_before(selection.start);
3782
3783 let is_word_char = text.chars().next().map_or(true, |char| {
3784 let classifier = snapshot
3785 .char_classifier_at(start_anchor.to_offset(&snapshot))
3786 .ignore_punctuation(true);
3787 classifier.is_word(char)
3788 });
3789
3790 if is_word_char {
3791 if let Some(ranges) = self
3792 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3793 {
3794 for (buffer, edits) in ranges {
3795 linked_edits
3796 .entry(buffer.clone())
3797 .or_default()
3798 .extend(edits.into_iter().map(|range| (range, text.clone())));
3799 }
3800 }
3801 } else {
3802 clear_linked_edit_ranges = true;
3803 }
3804 }
3805
3806 new_selections.push((selection.map(|_| anchor), 0));
3807 edits.push((selection.start..selection.end, text.clone()));
3808 }
3809
3810 drop(snapshot);
3811
3812 self.transact(window, cx, |this, window, cx| {
3813 if clear_linked_edit_ranges {
3814 this.linked_edit_ranges.clear();
3815 }
3816 let initial_buffer_versions =
3817 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3818
3819 this.buffer.update(cx, |buffer, cx| {
3820 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3821 });
3822 for (buffer, edits) in linked_edits {
3823 buffer.update(cx, |buffer, cx| {
3824 let snapshot = buffer.snapshot();
3825 let edits = edits
3826 .into_iter()
3827 .map(|(range, text)| {
3828 use text::ToPoint as TP;
3829 let end_point = TP::to_point(&range.end, &snapshot);
3830 let start_point = TP::to_point(&range.start, &snapshot);
3831 (start_point..end_point, text)
3832 })
3833 .sorted_by_key(|(range, _)| range.start);
3834 buffer.edit(edits, None, cx);
3835 })
3836 }
3837 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3838 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3839 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3840 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3841 .zip(new_selection_deltas)
3842 .map(|(selection, delta)| Selection {
3843 id: selection.id,
3844 start: selection.start + delta,
3845 end: selection.end + delta,
3846 reversed: selection.reversed,
3847 goal: SelectionGoal::None,
3848 })
3849 .collect::<Vec<_>>();
3850
3851 let mut i = 0;
3852 for (position, delta, selection_id, pair) in new_autoclose_regions {
3853 let position = position.to_offset(&map.buffer_snapshot) + delta;
3854 let start = map.buffer_snapshot.anchor_before(position);
3855 let end = map.buffer_snapshot.anchor_after(position);
3856 while let Some(existing_state) = this.autoclose_regions.get(i) {
3857 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3858 Ordering::Less => i += 1,
3859 Ordering::Greater => break,
3860 Ordering::Equal => {
3861 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3862 Ordering::Less => i += 1,
3863 Ordering::Equal => break,
3864 Ordering::Greater => break,
3865 }
3866 }
3867 }
3868 }
3869 this.autoclose_regions.insert(
3870 i,
3871 AutocloseRegion {
3872 selection_id,
3873 range: start..end,
3874 pair,
3875 },
3876 );
3877 }
3878
3879 let had_active_inline_completion = this.has_active_inline_completion();
3880 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3881 s.select(new_selections)
3882 });
3883
3884 if !bracket_inserted {
3885 if let Some(on_type_format_task) =
3886 this.trigger_on_type_formatting(text.to_string(), window, cx)
3887 {
3888 on_type_format_task.detach_and_log_err(cx);
3889 }
3890 }
3891
3892 let editor_settings = EditorSettings::get_global(cx);
3893 if bracket_inserted
3894 && (editor_settings.auto_signature_help
3895 || editor_settings.show_signature_help_after_edits)
3896 {
3897 this.show_signature_help(&ShowSignatureHelp, window, cx);
3898 }
3899
3900 let trigger_in_words =
3901 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3902 if this.hard_wrap.is_some() {
3903 let latest: Range<Point> = this.selections.newest(cx).range();
3904 if latest.is_empty()
3905 && this
3906 .buffer()
3907 .read(cx)
3908 .snapshot(cx)
3909 .line_len(MultiBufferRow(latest.start.row))
3910 == latest.start.column
3911 {
3912 this.rewrap_impl(
3913 RewrapOptions {
3914 override_language_settings: true,
3915 preserve_existing_whitespace: true,
3916 },
3917 cx,
3918 )
3919 }
3920 }
3921 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3922 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3923 this.refresh_inline_completion(true, false, window, cx);
3924 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3925 });
3926 }
3927
3928 fn find_possible_emoji_shortcode_at_position(
3929 snapshot: &MultiBufferSnapshot,
3930 position: Point,
3931 ) -> Option<String> {
3932 let mut chars = Vec::new();
3933 let mut found_colon = false;
3934 for char in snapshot.reversed_chars_at(position).take(100) {
3935 // Found a possible emoji shortcode in the middle of the buffer
3936 if found_colon {
3937 if char.is_whitespace() {
3938 chars.reverse();
3939 return Some(chars.iter().collect());
3940 }
3941 // If the previous character is not a whitespace, we are in the middle of a word
3942 // and we only want to complete the shortcode if the word is made up of other emojis
3943 let mut containing_word = String::new();
3944 for ch in snapshot
3945 .reversed_chars_at(position)
3946 .skip(chars.len() + 1)
3947 .take(100)
3948 {
3949 if ch.is_whitespace() {
3950 break;
3951 }
3952 containing_word.push(ch);
3953 }
3954 let containing_word = containing_word.chars().rev().collect::<String>();
3955 if util::word_consists_of_emojis(containing_word.as_str()) {
3956 chars.reverse();
3957 return Some(chars.iter().collect());
3958 }
3959 }
3960
3961 if char.is_whitespace() || !char.is_ascii() {
3962 return None;
3963 }
3964 if char == ':' {
3965 found_colon = true;
3966 } else {
3967 chars.push(char);
3968 }
3969 }
3970 // Found a possible emoji shortcode at the beginning of the buffer
3971 chars.reverse();
3972 Some(chars.iter().collect())
3973 }
3974
3975 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3976 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3977 self.transact(window, cx, |this, window, cx| {
3978 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
3979 let selections = this.selections.all::<usize>(cx);
3980 let multi_buffer = this.buffer.read(cx);
3981 let buffer = multi_buffer.snapshot(cx);
3982 selections
3983 .iter()
3984 .map(|selection| {
3985 let start_point = selection.start.to_point(&buffer);
3986 let mut existing_indent =
3987 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3988 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
3989 let start = selection.start;
3990 let end = selection.end;
3991 let selection_is_empty = start == end;
3992 let language_scope = buffer.language_scope_at(start);
3993 let (
3994 comment_delimiter,
3995 doc_delimiter,
3996 insert_extra_newline,
3997 indent_on_newline,
3998 indent_on_extra_newline,
3999 ) = if let Some(language) = &language_scope {
4000 let mut insert_extra_newline =
4001 insert_extra_newline_brackets(&buffer, start..end, language)
4002 || insert_extra_newline_tree_sitter(&buffer, start..end);
4003
4004 // Comment extension on newline is allowed only for cursor selections
4005 let comment_delimiter = maybe!({
4006 if !selection_is_empty {
4007 return None;
4008 }
4009
4010 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4011 return None;
4012 }
4013
4014 let delimiters = language.line_comment_prefixes();
4015 let max_len_of_delimiter =
4016 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4017 let (snapshot, range) =
4018 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4019
4020 let num_of_whitespaces = snapshot
4021 .chars_for_range(range.clone())
4022 .take_while(|c| c.is_whitespace())
4023 .count();
4024 let comment_candidate = snapshot
4025 .chars_for_range(range)
4026 .skip(num_of_whitespaces)
4027 .take(max_len_of_delimiter)
4028 .collect::<String>();
4029 let (delimiter, trimmed_len) = delimiters
4030 .iter()
4031 .filter_map(|delimiter| {
4032 let prefix = delimiter.trim_end();
4033 if comment_candidate.starts_with(prefix) {
4034 Some((delimiter, prefix.len()))
4035 } else {
4036 None
4037 }
4038 })
4039 .max_by_key(|(_, len)| *len)?;
4040
4041 let cursor_is_placed_after_comment_marker =
4042 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4043 if cursor_is_placed_after_comment_marker {
4044 Some(delimiter.clone())
4045 } else {
4046 None
4047 }
4048 });
4049
4050 let mut indent_on_newline = IndentSize::spaces(0);
4051 let mut indent_on_extra_newline = IndentSize::spaces(0);
4052
4053 let doc_delimiter = maybe!({
4054 if !selection_is_empty {
4055 return None;
4056 }
4057
4058 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4059 return None;
4060 }
4061
4062 let DocumentationConfig {
4063 start: start_tag,
4064 end: end_tag,
4065 prefix: delimiter,
4066 tab_size: len,
4067 } = language.documentation()?;
4068
4069 let is_within_block_comment = buffer
4070 .language_scope_at(start_point)
4071 .is_some_and(|scope| scope.override_name() == Some("comment"));
4072 if !is_within_block_comment {
4073 return None;
4074 }
4075
4076 let (snapshot, range) =
4077 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4078
4079 let num_of_whitespaces = snapshot
4080 .chars_for_range(range.clone())
4081 .take_while(|c| c.is_whitespace())
4082 .count();
4083
4084 // 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.
4085 let column = start_point.column;
4086 let cursor_is_after_start_tag = {
4087 let start_tag_len = start_tag.len();
4088 let start_tag_line = snapshot
4089 .chars_for_range(range.clone())
4090 .skip(num_of_whitespaces)
4091 .take(start_tag_len)
4092 .collect::<String>();
4093 if start_tag_line.starts_with(start_tag.as_ref()) {
4094 num_of_whitespaces + start_tag_len <= column as usize
4095 } else {
4096 false
4097 }
4098 };
4099
4100 let cursor_is_after_delimiter = {
4101 let delimiter_trim = delimiter.trim_end();
4102 let delimiter_line = snapshot
4103 .chars_for_range(range.clone())
4104 .skip(num_of_whitespaces)
4105 .take(delimiter_trim.len())
4106 .collect::<String>();
4107 if delimiter_line.starts_with(delimiter_trim) {
4108 num_of_whitespaces + delimiter_trim.len() <= column as usize
4109 } else {
4110 false
4111 }
4112 };
4113
4114 let cursor_is_before_end_tag_if_exists = {
4115 let mut char_position = 0u32;
4116 let mut end_tag_offset = None;
4117
4118 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4119 if let Some(byte_pos) = chunk.find(&**end_tag) {
4120 let chars_before_match =
4121 chunk[..byte_pos].chars().count() as u32;
4122 end_tag_offset =
4123 Some(char_position + chars_before_match);
4124 break 'outer;
4125 }
4126 char_position += chunk.chars().count() as u32;
4127 }
4128
4129 if let Some(end_tag_offset) = end_tag_offset {
4130 let cursor_is_before_end_tag = column <= end_tag_offset;
4131 if cursor_is_after_start_tag {
4132 if cursor_is_before_end_tag {
4133 insert_extra_newline = true;
4134 }
4135 let cursor_is_at_start_of_end_tag =
4136 column == end_tag_offset;
4137 if cursor_is_at_start_of_end_tag {
4138 indent_on_extra_newline.len = (*len).into();
4139 }
4140 }
4141 cursor_is_before_end_tag
4142 } else {
4143 true
4144 }
4145 };
4146
4147 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4148 && cursor_is_before_end_tag_if_exists
4149 {
4150 if cursor_is_after_start_tag {
4151 indent_on_newline.len = (*len).into();
4152 }
4153 Some(delimiter.clone())
4154 } else {
4155 None
4156 }
4157 });
4158
4159 (
4160 comment_delimiter,
4161 doc_delimiter,
4162 insert_extra_newline,
4163 indent_on_newline,
4164 indent_on_extra_newline,
4165 )
4166 } else {
4167 (
4168 None,
4169 None,
4170 false,
4171 IndentSize::default(),
4172 IndentSize::default(),
4173 )
4174 };
4175
4176 let prevent_auto_indent = doc_delimiter.is_some();
4177 let delimiter = comment_delimiter.or(doc_delimiter);
4178
4179 let capacity_for_delimiter =
4180 delimiter.as_deref().map(str::len).unwrap_or_default();
4181 let mut new_text = String::with_capacity(
4182 1 + capacity_for_delimiter
4183 + existing_indent.len as usize
4184 + indent_on_newline.len as usize
4185 + indent_on_extra_newline.len as usize,
4186 );
4187 new_text.push('\n');
4188 new_text.extend(existing_indent.chars());
4189 new_text.extend(indent_on_newline.chars());
4190
4191 if let Some(delimiter) = &delimiter {
4192 new_text.push_str(delimiter);
4193 }
4194
4195 if insert_extra_newline {
4196 new_text.push('\n');
4197 new_text.extend(existing_indent.chars());
4198 new_text.extend(indent_on_extra_newline.chars());
4199 }
4200
4201 let anchor = buffer.anchor_after(end);
4202 let new_selection = selection.map(|_| anchor);
4203 (
4204 ((start..end, new_text), prevent_auto_indent),
4205 (insert_extra_newline, new_selection),
4206 )
4207 })
4208 .unzip()
4209 };
4210
4211 let mut auto_indent_edits = Vec::new();
4212 let mut edits = Vec::new();
4213 for (edit, prevent_auto_indent) in edits_with_flags {
4214 if prevent_auto_indent {
4215 edits.push(edit);
4216 } else {
4217 auto_indent_edits.push(edit);
4218 }
4219 }
4220 if !edits.is_empty() {
4221 this.edit(edits, cx);
4222 }
4223 if !auto_indent_edits.is_empty() {
4224 this.edit_with_autoindent(auto_indent_edits, cx);
4225 }
4226
4227 let buffer = this.buffer.read(cx).snapshot(cx);
4228 let new_selections = selection_info
4229 .into_iter()
4230 .map(|(extra_newline_inserted, new_selection)| {
4231 let mut cursor = new_selection.end.to_point(&buffer);
4232 if extra_newline_inserted {
4233 cursor.row -= 1;
4234 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4235 }
4236 new_selection.map(|_| cursor)
4237 })
4238 .collect();
4239
4240 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4241 s.select(new_selections)
4242 });
4243 this.refresh_inline_completion(true, false, window, cx);
4244 });
4245 }
4246
4247 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4248 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4249
4250 let buffer = self.buffer.read(cx);
4251 let snapshot = buffer.snapshot(cx);
4252
4253 let mut edits = Vec::new();
4254 let mut rows = Vec::new();
4255
4256 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4257 let cursor = selection.head();
4258 let row = cursor.row;
4259
4260 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4261
4262 let newline = "\n".to_string();
4263 edits.push((start_of_line..start_of_line, newline));
4264
4265 rows.push(row + rows_inserted as u32);
4266 }
4267
4268 self.transact(window, cx, |editor, window, cx| {
4269 editor.edit(edits, cx);
4270
4271 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4272 let mut index = 0;
4273 s.move_cursors_with(|map, _, _| {
4274 let row = rows[index];
4275 index += 1;
4276
4277 let point = Point::new(row, 0);
4278 let boundary = map.next_line_boundary(point).1;
4279 let clipped = map.clip_point(boundary, Bias::Left);
4280
4281 (clipped, SelectionGoal::None)
4282 });
4283 });
4284
4285 let mut indent_edits = Vec::new();
4286 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4287 for row in rows {
4288 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4289 for (row, indent) in indents {
4290 if indent.len == 0 {
4291 continue;
4292 }
4293
4294 let text = match indent.kind {
4295 IndentKind::Space => " ".repeat(indent.len as usize),
4296 IndentKind::Tab => "\t".repeat(indent.len as usize),
4297 };
4298 let point = Point::new(row.0, 0);
4299 indent_edits.push((point..point, text));
4300 }
4301 }
4302 editor.edit(indent_edits, cx);
4303 });
4304 }
4305
4306 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4307 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4308
4309 let buffer = self.buffer.read(cx);
4310 let snapshot = buffer.snapshot(cx);
4311
4312 let mut edits = Vec::new();
4313 let mut rows = Vec::new();
4314 let mut rows_inserted = 0;
4315
4316 for selection in self.selections.all_adjusted(cx) {
4317 let cursor = selection.head();
4318 let row = cursor.row;
4319
4320 let point = Point::new(row + 1, 0);
4321 let start_of_line = snapshot.clip_point(point, Bias::Left);
4322
4323 let newline = "\n".to_string();
4324 edits.push((start_of_line..start_of_line, newline));
4325
4326 rows_inserted += 1;
4327 rows.push(row + rows_inserted);
4328 }
4329
4330 self.transact(window, cx, |editor, window, cx| {
4331 editor.edit(edits, cx);
4332
4333 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4334 let mut index = 0;
4335 s.move_cursors_with(|map, _, _| {
4336 let row = rows[index];
4337 index += 1;
4338
4339 let point = Point::new(row, 0);
4340 let boundary = map.next_line_boundary(point).1;
4341 let clipped = map.clip_point(boundary, Bias::Left);
4342
4343 (clipped, SelectionGoal::None)
4344 });
4345 });
4346
4347 let mut indent_edits = Vec::new();
4348 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4349 for row in rows {
4350 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4351 for (row, indent) in indents {
4352 if indent.len == 0 {
4353 continue;
4354 }
4355
4356 let text = match indent.kind {
4357 IndentKind::Space => " ".repeat(indent.len as usize),
4358 IndentKind::Tab => "\t".repeat(indent.len as usize),
4359 };
4360 let point = Point::new(row.0, 0);
4361 indent_edits.push((point..point, text));
4362 }
4363 }
4364 editor.edit(indent_edits, cx);
4365 });
4366 }
4367
4368 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4369 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4370 original_indent_columns: Vec::new(),
4371 });
4372 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4373 }
4374
4375 fn insert_with_autoindent_mode(
4376 &mut self,
4377 text: &str,
4378 autoindent_mode: Option<AutoindentMode>,
4379 window: &mut Window,
4380 cx: &mut Context<Self>,
4381 ) {
4382 if self.read_only(cx) {
4383 return;
4384 }
4385
4386 let text: Arc<str> = text.into();
4387 self.transact(window, cx, |this, window, cx| {
4388 let old_selections = this.selections.all_adjusted(cx);
4389 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4390 let anchors = {
4391 let snapshot = buffer.read(cx);
4392 old_selections
4393 .iter()
4394 .map(|s| {
4395 let anchor = snapshot.anchor_after(s.head());
4396 s.map(|_| anchor)
4397 })
4398 .collect::<Vec<_>>()
4399 };
4400 buffer.edit(
4401 old_selections
4402 .iter()
4403 .map(|s| (s.start..s.end, text.clone())),
4404 autoindent_mode,
4405 cx,
4406 );
4407 anchors
4408 });
4409
4410 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4411 s.select_anchors(selection_anchors);
4412 });
4413
4414 cx.notify();
4415 });
4416 }
4417
4418 fn trigger_completion_on_input(
4419 &mut self,
4420 text: &str,
4421 trigger_in_words: bool,
4422 window: &mut Window,
4423 cx: &mut Context<Self>,
4424 ) {
4425 let ignore_completion_provider = self
4426 .context_menu
4427 .borrow()
4428 .as_ref()
4429 .map(|menu| match menu {
4430 CodeContextMenu::Completions(completions_menu) => {
4431 completions_menu.ignore_completion_provider
4432 }
4433 CodeContextMenu::CodeActions(_) => false,
4434 })
4435 .unwrap_or(false);
4436
4437 if ignore_completion_provider {
4438 self.show_word_completions(&ShowWordCompletions, window, cx);
4439 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
4440 self.show_completions(
4441 &ShowCompletions {
4442 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4443 },
4444 window,
4445 cx,
4446 );
4447 } else {
4448 self.hide_context_menu(window, cx);
4449 }
4450 }
4451
4452 fn is_completion_trigger(
4453 &self,
4454 text: &str,
4455 trigger_in_words: bool,
4456 cx: &mut Context<Self>,
4457 ) -> bool {
4458 let position = self.selections.newest_anchor().head();
4459 let multibuffer = self.buffer.read(cx);
4460 let Some(buffer) = position
4461 .buffer_id
4462 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4463 else {
4464 return false;
4465 };
4466
4467 if let Some(completion_provider) = &self.completion_provider {
4468 completion_provider.is_completion_trigger(
4469 &buffer,
4470 position.text_anchor,
4471 text,
4472 trigger_in_words,
4473 cx,
4474 )
4475 } else {
4476 false
4477 }
4478 }
4479
4480 /// If any empty selections is touching the start of its innermost containing autoclose
4481 /// region, expand it to select the brackets.
4482 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4483 let selections = self.selections.all::<usize>(cx);
4484 let buffer = self.buffer.read(cx).read(cx);
4485 let new_selections = self
4486 .selections_with_autoclose_regions(selections, &buffer)
4487 .map(|(mut selection, region)| {
4488 if !selection.is_empty() {
4489 return selection;
4490 }
4491
4492 if let Some(region) = region {
4493 let mut range = region.range.to_offset(&buffer);
4494 if selection.start == range.start && range.start >= region.pair.start.len() {
4495 range.start -= region.pair.start.len();
4496 if buffer.contains_str_at(range.start, ®ion.pair.start)
4497 && buffer.contains_str_at(range.end, ®ion.pair.end)
4498 {
4499 range.end += region.pair.end.len();
4500 selection.start = range.start;
4501 selection.end = range.end;
4502
4503 return selection;
4504 }
4505 }
4506 }
4507
4508 let always_treat_brackets_as_autoclosed = buffer
4509 .language_settings_at(selection.start, cx)
4510 .always_treat_brackets_as_autoclosed;
4511
4512 if !always_treat_brackets_as_autoclosed {
4513 return selection;
4514 }
4515
4516 if let Some(scope) = buffer.language_scope_at(selection.start) {
4517 for (pair, enabled) in scope.brackets() {
4518 if !enabled || !pair.close {
4519 continue;
4520 }
4521
4522 if buffer.contains_str_at(selection.start, &pair.end) {
4523 let pair_start_len = pair.start.len();
4524 if buffer.contains_str_at(
4525 selection.start.saturating_sub(pair_start_len),
4526 &pair.start,
4527 ) {
4528 selection.start -= pair_start_len;
4529 selection.end += pair.end.len();
4530
4531 return selection;
4532 }
4533 }
4534 }
4535 }
4536
4537 selection
4538 })
4539 .collect();
4540
4541 drop(buffer);
4542 self.change_selections(None, window, cx, |selections| {
4543 selections.select(new_selections)
4544 });
4545 }
4546
4547 /// Iterate the given selections, and for each one, find the smallest surrounding
4548 /// autoclose region. This uses the ordering of the selections and the autoclose
4549 /// regions to avoid repeated comparisons.
4550 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4551 &'a self,
4552 selections: impl IntoIterator<Item = Selection<D>>,
4553 buffer: &'a MultiBufferSnapshot,
4554 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4555 let mut i = 0;
4556 let mut regions = self.autoclose_regions.as_slice();
4557 selections.into_iter().map(move |selection| {
4558 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4559
4560 let mut enclosing = None;
4561 while let Some(pair_state) = regions.get(i) {
4562 if pair_state.range.end.to_offset(buffer) < range.start {
4563 regions = ®ions[i + 1..];
4564 i = 0;
4565 } else if pair_state.range.start.to_offset(buffer) > range.end {
4566 break;
4567 } else {
4568 if pair_state.selection_id == selection.id {
4569 enclosing = Some(pair_state);
4570 }
4571 i += 1;
4572 }
4573 }
4574
4575 (selection, enclosing)
4576 })
4577 }
4578
4579 /// Remove any autoclose regions that no longer contain their selection.
4580 fn invalidate_autoclose_regions(
4581 &mut self,
4582 mut selections: &[Selection<Anchor>],
4583 buffer: &MultiBufferSnapshot,
4584 ) {
4585 self.autoclose_regions.retain(|state| {
4586 let mut i = 0;
4587 while let Some(selection) = selections.get(i) {
4588 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4589 selections = &selections[1..];
4590 continue;
4591 }
4592 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4593 break;
4594 }
4595 if selection.id == state.selection_id {
4596 return true;
4597 } else {
4598 i += 1;
4599 }
4600 }
4601 false
4602 });
4603 }
4604
4605 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4606 let offset = position.to_offset(buffer);
4607 let (word_range, kind) = buffer.surrounding_word(offset, true);
4608 if offset > word_range.start && kind == Some(CharKind::Word) {
4609 Some(
4610 buffer
4611 .text_for_range(word_range.start..offset)
4612 .collect::<String>(),
4613 )
4614 } else {
4615 None
4616 }
4617 }
4618
4619 pub fn toggle_inline_values(
4620 &mut self,
4621 _: &ToggleInlineValues,
4622 _: &mut Window,
4623 cx: &mut Context<Self>,
4624 ) {
4625 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4626
4627 self.refresh_inline_values(cx);
4628 }
4629
4630 pub fn toggle_inlay_hints(
4631 &mut self,
4632 _: &ToggleInlayHints,
4633 _: &mut Window,
4634 cx: &mut Context<Self>,
4635 ) {
4636 self.refresh_inlay_hints(
4637 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4638 cx,
4639 );
4640 }
4641
4642 pub fn inlay_hints_enabled(&self) -> bool {
4643 self.inlay_hint_cache.enabled
4644 }
4645
4646 pub fn inline_values_enabled(&self) -> bool {
4647 self.inline_value_cache.enabled
4648 }
4649
4650 #[cfg(any(test, feature = "test-support"))]
4651 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4652 self.display_map
4653 .read(cx)
4654 .current_inlays()
4655 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4656 .cloned()
4657 .collect()
4658 }
4659
4660 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4661 if self.semantics_provider.is_none() || !self.mode.is_full() {
4662 return;
4663 }
4664
4665 let reason_description = reason.description();
4666 let ignore_debounce = matches!(
4667 reason,
4668 InlayHintRefreshReason::SettingsChange(_)
4669 | InlayHintRefreshReason::Toggle(_)
4670 | InlayHintRefreshReason::ExcerptsRemoved(_)
4671 | InlayHintRefreshReason::ModifiersChanged(_)
4672 );
4673 let (invalidate_cache, required_languages) = match reason {
4674 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4675 match self.inlay_hint_cache.modifiers_override(enabled) {
4676 Some(enabled) => {
4677 if enabled {
4678 (InvalidationStrategy::RefreshRequested, None)
4679 } else {
4680 self.splice_inlays(
4681 &self
4682 .visible_inlay_hints(cx)
4683 .iter()
4684 .map(|inlay| inlay.id)
4685 .collect::<Vec<InlayId>>(),
4686 Vec::new(),
4687 cx,
4688 );
4689 return;
4690 }
4691 }
4692 None => return,
4693 }
4694 }
4695 InlayHintRefreshReason::Toggle(enabled) => {
4696 if self.inlay_hint_cache.toggle(enabled) {
4697 if enabled {
4698 (InvalidationStrategy::RefreshRequested, None)
4699 } else {
4700 self.splice_inlays(
4701 &self
4702 .visible_inlay_hints(cx)
4703 .iter()
4704 .map(|inlay| inlay.id)
4705 .collect::<Vec<InlayId>>(),
4706 Vec::new(),
4707 cx,
4708 );
4709 return;
4710 }
4711 } else {
4712 return;
4713 }
4714 }
4715 InlayHintRefreshReason::SettingsChange(new_settings) => {
4716 match self.inlay_hint_cache.update_settings(
4717 &self.buffer,
4718 new_settings,
4719 self.visible_inlay_hints(cx),
4720 cx,
4721 ) {
4722 ControlFlow::Break(Some(InlaySplice {
4723 to_remove,
4724 to_insert,
4725 })) => {
4726 self.splice_inlays(&to_remove, to_insert, cx);
4727 return;
4728 }
4729 ControlFlow::Break(None) => return,
4730 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4731 }
4732 }
4733 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4734 if let Some(InlaySplice {
4735 to_remove,
4736 to_insert,
4737 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4738 {
4739 self.splice_inlays(&to_remove, to_insert, cx);
4740 }
4741 self.display_map.update(cx, |display_map, _| {
4742 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4743 });
4744 return;
4745 }
4746 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4747 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4748 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4749 }
4750 InlayHintRefreshReason::RefreshRequested => {
4751 (InvalidationStrategy::RefreshRequested, None)
4752 }
4753 };
4754
4755 if let Some(InlaySplice {
4756 to_remove,
4757 to_insert,
4758 }) = self.inlay_hint_cache.spawn_hint_refresh(
4759 reason_description,
4760 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4761 invalidate_cache,
4762 ignore_debounce,
4763 cx,
4764 ) {
4765 self.splice_inlays(&to_remove, to_insert, cx);
4766 }
4767 }
4768
4769 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4770 self.display_map
4771 .read(cx)
4772 .current_inlays()
4773 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4774 .cloned()
4775 .collect()
4776 }
4777
4778 pub fn excerpts_for_inlay_hints_query(
4779 &self,
4780 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4781 cx: &mut Context<Editor>,
4782 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4783 let Some(project) = self.project.as_ref() else {
4784 return HashMap::default();
4785 };
4786 let project = project.read(cx);
4787 let multi_buffer = self.buffer().read(cx);
4788 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4789 let multi_buffer_visible_start = self
4790 .scroll_manager
4791 .anchor()
4792 .anchor
4793 .to_point(&multi_buffer_snapshot);
4794 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4795 multi_buffer_visible_start
4796 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4797 Bias::Left,
4798 );
4799 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4800 multi_buffer_snapshot
4801 .range_to_buffer_ranges(multi_buffer_visible_range)
4802 .into_iter()
4803 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4804 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4805 let buffer_file = project::File::from_dyn(buffer.file())?;
4806 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4807 let worktree_entry = buffer_worktree
4808 .read(cx)
4809 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4810 if worktree_entry.is_ignored {
4811 return None;
4812 }
4813
4814 let language = buffer.language()?;
4815 if let Some(restrict_to_languages) = restrict_to_languages {
4816 if !restrict_to_languages.contains(language) {
4817 return None;
4818 }
4819 }
4820 Some((
4821 excerpt_id,
4822 (
4823 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4824 buffer.version().clone(),
4825 excerpt_visible_range,
4826 ),
4827 ))
4828 })
4829 .collect()
4830 }
4831
4832 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4833 TextLayoutDetails {
4834 text_system: window.text_system().clone(),
4835 editor_style: self.style.clone().unwrap(),
4836 rem_size: window.rem_size(),
4837 scroll_anchor: self.scroll_manager.anchor(),
4838 visible_rows: self.visible_line_count(),
4839 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4840 }
4841 }
4842
4843 pub fn splice_inlays(
4844 &self,
4845 to_remove: &[InlayId],
4846 to_insert: Vec<Inlay>,
4847 cx: &mut Context<Self>,
4848 ) {
4849 self.display_map.update(cx, |display_map, cx| {
4850 display_map.splice_inlays(to_remove, to_insert, cx)
4851 });
4852 cx.notify();
4853 }
4854
4855 fn trigger_on_type_formatting(
4856 &self,
4857 input: String,
4858 window: &mut Window,
4859 cx: &mut Context<Self>,
4860 ) -> Option<Task<Result<()>>> {
4861 if input.len() != 1 {
4862 return None;
4863 }
4864
4865 let project = self.project.as_ref()?;
4866 let position = self.selections.newest_anchor().head();
4867 let (buffer, buffer_position) = self
4868 .buffer
4869 .read(cx)
4870 .text_anchor_for_position(position, cx)?;
4871
4872 let settings = language_settings::language_settings(
4873 buffer
4874 .read(cx)
4875 .language_at(buffer_position)
4876 .map(|l| l.name()),
4877 buffer.read(cx).file(),
4878 cx,
4879 );
4880 if !settings.use_on_type_format {
4881 return None;
4882 }
4883
4884 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4885 // hence we do LSP request & edit on host side only — add formats to host's history.
4886 let push_to_lsp_host_history = true;
4887 // If this is not the host, append its history with new edits.
4888 let push_to_client_history = project.read(cx).is_via_collab();
4889
4890 let on_type_formatting = project.update(cx, |project, cx| {
4891 project.on_type_format(
4892 buffer.clone(),
4893 buffer_position,
4894 input,
4895 push_to_lsp_host_history,
4896 cx,
4897 )
4898 });
4899 Some(cx.spawn_in(window, async move |editor, cx| {
4900 if let Some(transaction) = on_type_formatting.await? {
4901 if push_to_client_history {
4902 buffer
4903 .update(cx, |buffer, _| {
4904 buffer.push_transaction(transaction, Instant::now());
4905 buffer.finalize_last_transaction();
4906 })
4907 .ok();
4908 }
4909 editor.update(cx, |editor, cx| {
4910 editor.refresh_document_highlights(cx);
4911 })?;
4912 }
4913 Ok(())
4914 }))
4915 }
4916
4917 pub fn show_word_completions(
4918 &mut self,
4919 _: &ShowWordCompletions,
4920 window: &mut Window,
4921 cx: &mut Context<Self>,
4922 ) {
4923 self.open_completions_menu(true, None, window, cx);
4924 }
4925
4926 pub fn show_completions(
4927 &mut self,
4928 options: &ShowCompletions,
4929 window: &mut Window,
4930 cx: &mut Context<Self>,
4931 ) {
4932 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4933 }
4934
4935 fn open_completions_menu(
4936 &mut self,
4937 ignore_completion_provider: bool,
4938 trigger: Option<&str>,
4939 window: &mut Window,
4940 cx: &mut Context<Self>,
4941 ) {
4942 if self.pending_rename.is_some() {
4943 return;
4944 }
4945 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4946 return;
4947 }
4948
4949 let position = self.selections.newest_anchor().head();
4950 if position.diff_base_anchor.is_some() {
4951 return;
4952 }
4953 let (buffer, buffer_position) =
4954 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4955 output
4956 } else {
4957 return;
4958 };
4959 let buffer_snapshot = buffer.read(cx).snapshot();
4960 let show_completion_documentation = buffer_snapshot
4961 .settings_at(buffer_position, cx)
4962 .show_completion_documentation;
4963
4964 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4965
4966 let trigger_kind = match trigger {
4967 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4968 CompletionTriggerKind::TRIGGER_CHARACTER
4969 }
4970 _ => CompletionTriggerKind::INVOKED,
4971 };
4972 let completion_context = CompletionContext {
4973 trigger_character: trigger.and_then(|trigger| {
4974 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4975 Some(String::from(trigger))
4976 } else {
4977 None
4978 }
4979 }),
4980 trigger_kind,
4981 };
4982
4983 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4984 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4985 let word_to_exclude = buffer_snapshot
4986 .text_for_range(old_range.clone())
4987 .collect::<String>();
4988 (
4989 buffer_snapshot.anchor_before(old_range.start)
4990 ..buffer_snapshot.anchor_after(old_range.end),
4991 Some(word_to_exclude),
4992 )
4993 } else {
4994 (buffer_position..buffer_position, None)
4995 };
4996
4997 let language = buffer_snapshot
4998 .language_at(buffer_position)
4999 .map(|language| language.name());
5000
5001 let completion_settings =
5002 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5003
5004 // The document can be large, so stay in reasonable bounds when searching for words,
5005 // otherwise completion pop-up might be slow to appear.
5006 const WORD_LOOKUP_ROWS: u32 = 5_000;
5007 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5008 let min_word_search = buffer_snapshot.clip_point(
5009 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5010 Bias::Left,
5011 );
5012 let max_word_search = buffer_snapshot.clip_point(
5013 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5014 Bias::Right,
5015 );
5016 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5017 ..buffer_snapshot.point_to_offset(max_word_search);
5018
5019 let provider = if ignore_completion_provider {
5020 None
5021 } else {
5022 self.completion_provider.clone()
5023 };
5024 let skip_digits = query
5025 .as_ref()
5026 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5027
5028 let (mut words, provided_completions) = match &provider {
5029 Some(provider) => {
5030 let completions = provider.completions(
5031 position.excerpt_id,
5032 &buffer,
5033 buffer_position,
5034 completion_context,
5035 window,
5036 cx,
5037 );
5038
5039 let words = match completion_settings.words {
5040 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5041 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5042 .background_spawn(async move {
5043 buffer_snapshot.words_in_range(WordsQuery {
5044 fuzzy_contents: None,
5045 range: word_search_range,
5046 skip_digits,
5047 })
5048 }),
5049 };
5050
5051 (words, completions)
5052 }
5053 None => (
5054 cx.background_spawn(async move {
5055 buffer_snapshot.words_in_range(WordsQuery {
5056 fuzzy_contents: None,
5057 range: word_search_range,
5058 skip_digits,
5059 })
5060 }),
5061 Task::ready(Ok(None)),
5062 ),
5063 };
5064
5065 let sort_completions = provider
5066 .as_ref()
5067 .map_or(false, |provider| provider.sort_completions());
5068
5069 let filter_completions = provider
5070 .as_ref()
5071 .map_or(true, |provider| provider.filter_completions());
5072
5073 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5074
5075 let id = post_inc(&mut self.next_completion_id);
5076 let task = cx.spawn_in(window, async move |editor, cx| {
5077 async move {
5078 editor.update(cx, |this, _| {
5079 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5080 })?;
5081
5082 let mut completions = Vec::new();
5083 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
5084 completions.extend(provided_completions);
5085 if completion_settings.words == WordsCompletionMode::Fallback {
5086 words = Task::ready(BTreeMap::default());
5087 }
5088 }
5089
5090 let mut words = words.await;
5091 if let Some(word_to_exclude) = &word_to_exclude {
5092 words.remove(word_to_exclude);
5093 }
5094 for lsp_completion in &completions {
5095 words.remove(&lsp_completion.new_text);
5096 }
5097 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5098 replace_range: old_range.clone(),
5099 new_text: word.clone(),
5100 label: CodeLabel::plain(word, None),
5101 icon_path: None,
5102 documentation: None,
5103 source: CompletionSource::BufferWord {
5104 word_range,
5105 resolved: false,
5106 },
5107 insert_text_mode: Some(InsertTextMode::AS_IS),
5108 confirm: None,
5109 }));
5110
5111 let menu = if completions.is_empty() {
5112 None
5113 } else {
5114 let mut menu = editor.update(cx, |editor, cx| {
5115 let languages = editor
5116 .workspace
5117 .as_ref()
5118 .and_then(|(workspace, _)| workspace.upgrade())
5119 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5120 CompletionsMenu::new(
5121 id,
5122 sort_completions,
5123 show_completion_documentation,
5124 ignore_completion_provider,
5125 position,
5126 buffer.clone(),
5127 completions.into(),
5128 snippet_sort_order,
5129 languages,
5130 language,
5131 cx,
5132 )
5133 })?;
5134
5135 menu.filter(
5136 if filter_completions {
5137 query.as_deref()
5138 } else {
5139 None
5140 },
5141 provider,
5142 editor.clone(),
5143 cx,
5144 )
5145 .await;
5146
5147 menu.visible().then_some(menu)
5148 };
5149
5150 editor.update_in(cx, |editor, window, cx| {
5151 match editor.context_menu.borrow().as_ref() {
5152 None => {}
5153 Some(CodeContextMenu::Completions(prev_menu)) => {
5154 if prev_menu.id > id {
5155 return;
5156 }
5157 }
5158 _ => return,
5159 }
5160
5161 if editor.focus_handle.is_focused(window) && menu.is_some() {
5162 let mut menu = menu.unwrap();
5163 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
5164 crate::hover_popover::hide_hover(editor, cx);
5165 *editor.context_menu.borrow_mut() =
5166 Some(CodeContextMenu::Completions(menu));
5167
5168 if editor.show_edit_predictions_in_menu() {
5169 editor.update_visible_inline_completion(window, cx);
5170 } else {
5171 editor.discard_inline_completion(false, cx);
5172 }
5173
5174 cx.notify();
5175 } else if editor.completion_tasks.len() <= 1 {
5176 // If there are no more completion tasks and the last menu was
5177 // empty, we should hide it.
5178 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5179 // If it was already hidden and we don't show inline
5180 // completions in the menu, we should also show the
5181 // inline-completion when available.
5182 if was_hidden && editor.show_edit_predictions_in_menu() {
5183 editor.update_visible_inline_completion(window, cx);
5184 }
5185 }
5186 })?;
5187
5188 anyhow::Ok(())
5189 }
5190 .log_err()
5191 .await
5192 });
5193
5194 self.completion_tasks.push((id, task));
5195 }
5196
5197 #[cfg(feature = "test-support")]
5198 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5199 let menu = self.context_menu.borrow();
5200 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5201 let completions = menu.completions.borrow();
5202 Some(completions.to_vec())
5203 } else {
5204 None
5205 }
5206 }
5207
5208 pub fn with_completions_menu_matching_id<R>(
5209 &self,
5210 id: CompletionId,
5211 on_absent: impl FnOnce() -> R,
5212 on_match: impl FnOnce(&mut CompletionsMenu) -> R,
5213 ) -> R {
5214 let mut context_menu = self.context_menu.borrow_mut();
5215 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5216 return on_absent();
5217 };
5218 if completions_menu.id != id {
5219 return on_absent();
5220 }
5221 on_match(completions_menu)
5222 }
5223
5224 pub fn confirm_completion(
5225 &mut self,
5226 action: &ConfirmCompletion,
5227 window: &mut Window,
5228 cx: &mut Context<Self>,
5229 ) -> Option<Task<Result<()>>> {
5230 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5231 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5232 }
5233
5234 pub fn confirm_completion_insert(
5235 &mut self,
5236 _: &ConfirmCompletionInsert,
5237 window: &mut Window,
5238 cx: &mut Context<Self>,
5239 ) -> Option<Task<Result<()>>> {
5240 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5241 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5242 }
5243
5244 pub fn confirm_completion_replace(
5245 &mut self,
5246 _: &ConfirmCompletionReplace,
5247 window: &mut Window,
5248 cx: &mut Context<Self>,
5249 ) -> Option<Task<Result<()>>> {
5250 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5251 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5252 }
5253
5254 pub fn compose_completion(
5255 &mut self,
5256 action: &ComposeCompletion,
5257 window: &mut Window,
5258 cx: &mut Context<Self>,
5259 ) -> Option<Task<Result<()>>> {
5260 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5261 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5262 }
5263
5264 fn do_completion(
5265 &mut self,
5266 item_ix: Option<usize>,
5267 intent: CompletionIntent,
5268 window: &mut Window,
5269 cx: &mut Context<Editor>,
5270 ) -> Option<Task<Result<()>>> {
5271 use language::ToOffset as _;
5272
5273 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5274 else {
5275 return None;
5276 };
5277
5278 let candidate_id = {
5279 let entries = completions_menu.entries.borrow();
5280 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5281 if self.show_edit_predictions_in_menu() {
5282 self.discard_inline_completion(true, cx);
5283 }
5284 mat.candidate_id
5285 };
5286
5287 let buffer_handle = completions_menu.buffer;
5288 let completion = completions_menu
5289 .completions
5290 .borrow()
5291 .get(candidate_id)?
5292 .clone();
5293 cx.stop_propagation();
5294
5295 let snapshot = self.buffer.read(cx).snapshot(cx);
5296 let newest_anchor = self.selections.newest_anchor();
5297
5298 let snippet;
5299 let new_text;
5300 if completion.is_snippet() {
5301 let mut snippet_source = completion.new_text.clone();
5302 if let Some(scope) = snapshot.language_scope_at(newest_anchor.head()) {
5303 if scope.prefers_label_for_snippet_in_completion() {
5304 if let Some(label) = completion.label() {
5305 if matches!(
5306 completion.kind(),
5307 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
5308 ) {
5309 snippet_source = label;
5310 }
5311 }
5312 }
5313 }
5314 snippet = Some(Snippet::parse(&snippet_source).log_err()?);
5315 new_text = snippet.as_ref().unwrap().text.clone();
5316 } else {
5317 snippet = None;
5318 new_text = completion.new_text.clone();
5319 };
5320
5321 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
5322 let buffer = buffer_handle.read(cx);
5323 let replace_range_multibuffer = {
5324 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5325 let multibuffer_anchor = snapshot
5326 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5327 .unwrap()
5328 ..snapshot
5329 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5330 .unwrap();
5331 multibuffer_anchor.start.to_offset(&snapshot)
5332 ..multibuffer_anchor.end.to_offset(&snapshot)
5333 };
5334 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5335 return None;
5336 }
5337
5338 let old_text = buffer
5339 .text_for_range(replace_range.clone())
5340 .collect::<String>();
5341 let lookbehind = newest_anchor
5342 .start
5343 .text_anchor
5344 .to_offset(buffer)
5345 .saturating_sub(replace_range.start);
5346 let lookahead = replace_range
5347 .end
5348 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5349 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5350 let suffix = &old_text[lookbehind.min(old_text.len())..];
5351
5352 let selections = self.selections.all::<usize>(cx);
5353 let mut ranges = Vec::new();
5354 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5355
5356 for selection in &selections {
5357 let range = if selection.id == newest_anchor.id {
5358 replace_range_multibuffer.clone()
5359 } else {
5360 let mut range = selection.range();
5361
5362 // if prefix is present, don't duplicate it
5363 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5364 range.start = range.start.saturating_sub(lookbehind);
5365
5366 // if suffix is also present, mimic the newest cursor and replace it
5367 if selection.id != newest_anchor.id
5368 && snapshot.contains_str_at(range.end, suffix)
5369 {
5370 range.end += lookahead;
5371 }
5372 }
5373 range
5374 };
5375
5376 ranges.push(range.clone());
5377
5378 if !self.linked_edit_ranges.is_empty() {
5379 let start_anchor = snapshot.anchor_before(range.start);
5380 let end_anchor = snapshot.anchor_after(range.end);
5381 if let Some(ranges) = self
5382 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5383 {
5384 for (buffer, edits) in ranges {
5385 linked_edits
5386 .entry(buffer.clone())
5387 .or_default()
5388 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5389 }
5390 }
5391 }
5392 }
5393
5394 cx.emit(EditorEvent::InputHandled {
5395 utf16_range_to_replace: None,
5396 text: new_text.clone().into(),
5397 });
5398
5399 self.transact(window, cx, |this, window, cx| {
5400 if let Some(mut snippet) = snippet {
5401 snippet.text = new_text.to_string();
5402 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5403 } else {
5404 this.buffer.update(cx, |buffer, cx| {
5405 let auto_indent = match completion.insert_text_mode {
5406 Some(InsertTextMode::AS_IS) => None,
5407 _ => this.autoindent_mode.clone(),
5408 };
5409 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5410 buffer.edit(edits, auto_indent, cx);
5411 });
5412 }
5413 for (buffer, edits) in linked_edits {
5414 buffer.update(cx, |buffer, cx| {
5415 let snapshot = buffer.snapshot();
5416 let edits = edits
5417 .into_iter()
5418 .map(|(range, text)| {
5419 use text::ToPoint as TP;
5420 let end_point = TP::to_point(&range.end, &snapshot);
5421 let start_point = TP::to_point(&range.start, &snapshot);
5422 (start_point..end_point, text)
5423 })
5424 .sorted_by_key(|(range, _)| range.start);
5425 buffer.edit(edits, None, cx);
5426 })
5427 }
5428
5429 this.refresh_inline_completion(true, false, window, cx);
5430 });
5431
5432 let show_new_completions_on_confirm = completion
5433 .confirm
5434 .as_ref()
5435 .map_or(false, |confirm| confirm(intent, window, cx));
5436 if show_new_completions_on_confirm {
5437 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5438 }
5439
5440 let provider = self.completion_provider.as_ref()?;
5441 drop(completion);
5442 let apply_edits = provider.apply_additional_edits_for_completion(
5443 buffer_handle,
5444 completions_menu.completions.clone(),
5445 candidate_id,
5446 true,
5447 cx,
5448 );
5449
5450 let editor_settings = EditorSettings::get_global(cx);
5451 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5452 // After the code completion is finished, users often want to know what signatures are needed.
5453 // so we should automatically call signature_help
5454 self.show_signature_help(&ShowSignatureHelp, window, cx);
5455 }
5456
5457 Some(cx.foreground_executor().spawn(async move {
5458 apply_edits.await?;
5459 Ok(())
5460 }))
5461 }
5462
5463 pub fn toggle_code_actions(
5464 &mut self,
5465 action: &ToggleCodeActions,
5466 window: &mut Window,
5467 cx: &mut Context<Self>,
5468 ) {
5469 let quick_launch = action.quick_launch;
5470 let mut context_menu = self.context_menu.borrow_mut();
5471 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5472 if code_actions.deployed_from == action.deployed_from {
5473 // Toggle if we're selecting the same one
5474 *context_menu = None;
5475 cx.notify();
5476 return;
5477 } else {
5478 // Otherwise, clear it and start a new one
5479 *context_menu = None;
5480 cx.notify();
5481 }
5482 }
5483 drop(context_menu);
5484 let snapshot = self.snapshot(window, cx);
5485 let deployed_from = action.deployed_from.clone();
5486 let mut task = self.code_actions_task.take();
5487 let action = action.clone();
5488 cx.spawn_in(window, async move |editor, cx| {
5489 while let Some(prev_task) = task {
5490 prev_task.await.log_err();
5491 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5492 }
5493
5494 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5495 if editor.focus_handle.is_focused(window) {
5496 let multibuffer_point = match &action.deployed_from {
5497 Some(CodeActionSource::Indicator(row)) => {
5498 DisplayPoint::new(*row, 0).to_point(&snapshot)
5499 }
5500 _ => editor.selections.newest::<Point>(cx).head(),
5501 };
5502 let (buffer, buffer_row) = snapshot
5503 .buffer_snapshot
5504 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5505 .and_then(|(buffer_snapshot, range)| {
5506 editor
5507 .buffer
5508 .read(cx)
5509 .buffer(buffer_snapshot.remote_id())
5510 .map(|buffer| (buffer, range.start.row))
5511 })?;
5512 let (_, code_actions) = editor
5513 .available_code_actions
5514 .clone()
5515 .and_then(|(location, code_actions)| {
5516 let snapshot = location.buffer.read(cx).snapshot();
5517 let point_range = location.range.to_point(&snapshot);
5518 let point_range = point_range.start.row..=point_range.end.row;
5519 if point_range.contains(&buffer_row) {
5520 Some((location, code_actions))
5521 } else {
5522 None
5523 }
5524 })
5525 .unzip();
5526 let buffer_id = buffer.read(cx).remote_id();
5527 let tasks = editor
5528 .tasks
5529 .get(&(buffer_id, buffer_row))
5530 .map(|t| Arc::new(t.to_owned()));
5531 if tasks.is_none() && code_actions.is_none() {
5532 return None;
5533 }
5534
5535 editor.completion_tasks.clear();
5536 editor.discard_inline_completion(false, cx);
5537 let task_context =
5538 tasks
5539 .as_ref()
5540 .zip(editor.project.clone())
5541 .map(|(tasks, project)| {
5542 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5543 });
5544
5545 Some(cx.spawn_in(window, async move |editor, cx| {
5546 let task_context = match task_context {
5547 Some(task_context) => task_context.await,
5548 None => None,
5549 };
5550 let resolved_tasks =
5551 tasks
5552 .zip(task_context.clone())
5553 .map(|(tasks, task_context)| ResolvedTasks {
5554 templates: tasks.resolve(&task_context).collect(),
5555 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5556 multibuffer_point.row,
5557 tasks.column,
5558 )),
5559 });
5560 let debug_scenarios = editor.update(cx, |editor, cx| {
5561 if cx.has_flag::<DebuggerFeatureFlag>() {
5562 maybe!({
5563 let project = editor.project.as_ref()?;
5564 let dap_store = project.read(cx).dap_store();
5565 let mut scenarios = vec![];
5566 let resolved_tasks = resolved_tasks.as_ref()?;
5567 let buffer = buffer.read(cx);
5568 let language = buffer.language()?;
5569 let file = buffer.file();
5570 let debug_adapter =
5571 language_settings(language.name().into(), file, cx)
5572 .debuggers
5573 .first()
5574 .map(SharedString::from)
5575 .or_else(|| {
5576 language
5577 .config()
5578 .debuggers
5579 .first()
5580 .map(SharedString::from)
5581 })?;
5582
5583 dap_store.update(cx, |dap_store, cx| {
5584 for (_, task) in &resolved_tasks.templates {
5585 if let Some(scenario) = dap_store
5586 .debug_scenario_for_build_task(
5587 task.original_task().clone(),
5588 debug_adapter.clone().into(),
5589 task.display_label().to_owned().into(),
5590 cx,
5591 )
5592 {
5593 scenarios.push(scenario);
5594 }
5595 }
5596 });
5597 Some(scenarios)
5598 })
5599 .unwrap_or_default()
5600 } else {
5601 vec![]
5602 }
5603 })?;
5604 let spawn_straight_away = quick_launch
5605 && resolved_tasks
5606 .as_ref()
5607 .map_or(false, |tasks| tasks.templates.len() == 1)
5608 && code_actions
5609 .as_ref()
5610 .map_or(true, |actions| actions.is_empty())
5611 && debug_scenarios.is_empty();
5612 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5613 crate::hover_popover::hide_hover(editor, cx);
5614 *editor.context_menu.borrow_mut() =
5615 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5616 buffer,
5617 actions: CodeActionContents::new(
5618 resolved_tasks,
5619 code_actions,
5620 debug_scenarios,
5621 task_context.unwrap_or_default(),
5622 ),
5623 selected_item: Default::default(),
5624 scroll_handle: UniformListScrollHandle::default(),
5625 deployed_from,
5626 }));
5627 if spawn_straight_away {
5628 if let Some(task) = editor.confirm_code_action(
5629 &ConfirmCodeAction { item_ix: Some(0) },
5630 window,
5631 cx,
5632 ) {
5633 cx.notify();
5634 return task;
5635 }
5636 }
5637 cx.notify();
5638 Task::ready(Ok(()))
5639 }) {
5640 task.await
5641 } else {
5642 Ok(())
5643 }
5644 }))
5645 } else {
5646 Some(Task::ready(Ok(())))
5647 }
5648 })?;
5649 if let Some(task) = spawned_test_task {
5650 task.await?;
5651 }
5652
5653 anyhow::Ok(())
5654 })
5655 .detach_and_log_err(cx);
5656 }
5657
5658 pub fn confirm_code_action(
5659 &mut self,
5660 action: &ConfirmCodeAction,
5661 window: &mut Window,
5662 cx: &mut Context<Self>,
5663 ) -> Option<Task<Result<()>>> {
5664 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5665
5666 let actions_menu =
5667 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5668 menu
5669 } else {
5670 return None;
5671 };
5672
5673 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5674 let action = actions_menu.actions.get(action_ix)?;
5675 let title = action.label();
5676 let buffer = actions_menu.buffer;
5677 let workspace = self.workspace()?;
5678
5679 match action {
5680 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5681 workspace.update(cx, |workspace, cx| {
5682 workspace.schedule_resolved_task(
5683 task_source_kind,
5684 resolved_task,
5685 false,
5686 window,
5687 cx,
5688 );
5689
5690 Some(Task::ready(Ok(())))
5691 })
5692 }
5693 CodeActionsItem::CodeAction {
5694 excerpt_id,
5695 action,
5696 provider,
5697 } => {
5698 let apply_code_action =
5699 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5700 let workspace = workspace.downgrade();
5701 Some(cx.spawn_in(window, async move |editor, cx| {
5702 let project_transaction = apply_code_action.await?;
5703 Self::open_project_transaction(
5704 &editor,
5705 workspace,
5706 project_transaction,
5707 title,
5708 cx,
5709 )
5710 .await
5711 }))
5712 }
5713 CodeActionsItem::DebugScenario(scenario) => {
5714 let context = actions_menu.actions.context.clone();
5715
5716 workspace.update(cx, |workspace, cx| {
5717 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
5718 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5719 });
5720 Some(Task::ready(Ok(())))
5721 }
5722 }
5723 }
5724
5725 pub async fn open_project_transaction(
5726 this: &WeakEntity<Editor>,
5727 workspace: WeakEntity<Workspace>,
5728 transaction: ProjectTransaction,
5729 title: String,
5730 cx: &mut AsyncWindowContext,
5731 ) -> Result<()> {
5732 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5733 cx.update(|_, cx| {
5734 entries.sort_unstable_by_key(|(buffer, _)| {
5735 buffer.read(cx).file().map(|f| f.path().clone())
5736 });
5737 })?;
5738
5739 // If the project transaction's edits are all contained within this editor, then
5740 // avoid opening a new editor to display them.
5741
5742 if let Some((buffer, transaction)) = entries.first() {
5743 if entries.len() == 1 {
5744 let excerpt = this.update(cx, |editor, cx| {
5745 editor
5746 .buffer()
5747 .read(cx)
5748 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5749 })?;
5750 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5751 if excerpted_buffer == *buffer {
5752 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5753 let excerpt_range = excerpt_range.to_offset(buffer);
5754 buffer
5755 .edited_ranges_for_transaction::<usize>(transaction)
5756 .all(|range| {
5757 excerpt_range.start <= range.start
5758 && excerpt_range.end >= range.end
5759 })
5760 })?;
5761
5762 if all_edits_within_excerpt {
5763 return Ok(());
5764 }
5765 }
5766 }
5767 }
5768 } else {
5769 return Ok(());
5770 }
5771
5772 let mut ranges_to_highlight = Vec::new();
5773 let excerpt_buffer = cx.new(|cx| {
5774 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5775 for (buffer_handle, transaction) in &entries {
5776 let edited_ranges = buffer_handle
5777 .read(cx)
5778 .edited_ranges_for_transaction::<Point>(transaction)
5779 .collect::<Vec<_>>();
5780 let (ranges, _) = multibuffer.set_excerpts_for_path(
5781 PathKey::for_buffer(buffer_handle, cx),
5782 buffer_handle.clone(),
5783 edited_ranges,
5784 DEFAULT_MULTIBUFFER_CONTEXT,
5785 cx,
5786 );
5787
5788 ranges_to_highlight.extend(ranges);
5789 }
5790 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5791 multibuffer
5792 })?;
5793
5794 workspace.update_in(cx, |workspace, window, cx| {
5795 let project = workspace.project().clone();
5796 let editor =
5797 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5798 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5799 editor.update(cx, |editor, cx| {
5800 editor.highlight_background::<Self>(
5801 &ranges_to_highlight,
5802 |theme| theme.editor_highlighted_line_background,
5803 cx,
5804 );
5805 });
5806 })?;
5807
5808 Ok(())
5809 }
5810
5811 pub fn clear_code_action_providers(&mut self) {
5812 self.code_action_providers.clear();
5813 self.available_code_actions.take();
5814 }
5815
5816 pub fn add_code_action_provider(
5817 &mut self,
5818 provider: Rc<dyn CodeActionProvider>,
5819 window: &mut Window,
5820 cx: &mut Context<Self>,
5821 ) {
5822 if self
5823 .code_action_providers
5824 .iter()
5825 .any(|existing_provider| existing_provider.id() == provider.id())
5826 {
5827 return;
5828 }
5829
5830 self.code_action_providers.push(provider);
5831 self.refresh_code_actions(window, cx);
5832 }
5833
5834 pub fn remove_code_action_provider(
5835 &mut self,
5836 id: Arc<str>,
5837 window: &mut Window,
5838 cx: &mut Context<Self>,
5839 ) {
5840 self.code_action_providers
5841 .retain(|provider| provider.id() != id);
5842 self.refresh_code_actions(window, cx);
5843 }
5844
5845 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
5846 !self.code_action_providers.is_empty()
5847 && EditorSettings::get_global(cx).toolbar.code_actions
5848 }
5849
5850 pub fn has_available_code_actions(&self) -> bool {
5851 self.available_code_actions
5852 .as_ref()
5853 .is_some_and(|(_, actions)| !actions.is_empty())
5854 }
5855
5856 fn render_inline_code_actions(
5857 &self,
5858 icon_size: ui::IconSize,
5859 display_row: DisplayRow,
5860 is_active: bool,
5861 cx: &mut Context<Self>,
5862 ) -> AnyElement {
5863 let show_tooltip = !self.context_menu_visible();
5864 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
5865 .icon_size(icon_size)
5866 .shape(ui::IconButtonShape::Square)
5867 .style(ButtonStyle::Transparent)
5868 .icon_color(ui::Color::Hidden)
5869 .toggle_state(is_active)
5870 .when(show_tooltip, |this| {
5871 this.tooltip({
5872 let focus_handle = self.focus_handle.clone();
5873 move |window, cx| {
5874 Tooltip::for_action_in(
5875 "Toggle Code Actions",
5876 &ToggleCodeActions {
5877 deployed_from: None,
5878 quick_launch: false,
5879 },
5880 &focus_handle,
5881 window,
5882 cx,
5883 )
5884 }
5885 })
5886 })
5887 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
5888 window.focus(&editor.focus_handle(cx));
5889 editor.toggle_code_actions(
5890 &crate::actions::ToggleCodeActions {
5891 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
5892 display_row,
5893 )),
5894 quick_launch: false,
5895 },
5896 window,
5897 cx,
5898 );
5899 }))
5900 .into_any_element()
5901 }
5902
5903 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
5904 &self.context_menu
5905 }
5906
5907 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5908 let newest_selection = self.selections.newest_anchor().clone();
5909 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5910 let buffer = self.buffer.read(cx);
5911 if newest_selection.head().diff_base_anchor.is_some() {
5912 return None;
5913 }
5914 let (start_buffer, start) =
5915 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5916 let (end_buffer, end) =
5917 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5918 if start_buffer != end_buffer {
5919 return None;
5920 }
5921
5922 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5923 cx.background_executor()
5924 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5925 .await;
5926
5927 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5928 let providers = this.code_action_providers.clone();
5929 let tasks = this
5930 .code_action_providers
5931 .iter()
5932 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5933 .collect::<Vec<_>>();
5934 (providers, tasks)
5935 })?;
5936
5937 let mut actions = Vec::new();
5938 for (provider, provider_actions) in
5939 providers.into_iter().zip(future::join_all(tasks).await)
5940 {
5941 if let Some(provider_actions) = provider_actions.log_err() {
5942 actions.extend(provider_actions.into_iter().map(|action| {
5943 AvailableCodeAction {
5944 excerpt_id: newest_selection.start.excerpt_id,
5945 action,
5946 provider: provider.clone(),
5947 }
5948 }));
5949 }
5950 }
5951
5952 this.update(cx, |this, cx| {
5953 this.available_code_actions = if actions.is_empty() {
5954 None
5955 } else {
5956 Some((
5957 Location {
5958 buffer: start_buffer,
5959 range: start..end,
5960 },
5961 actions.into(),
5962 ))
5963 };
5964 cx.notify();
5965 })
5966 }));
5967 None
5968 }
5969
5970 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5971 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5972 self.show_git_blame_inline = false;
5973
5974 self.show_git_blame_inline_delay_task =
5975 Some(cx.spawn_in(window, async move |this, cx| {
5976 cx.background_executor().timer(delay).await;
5977
5978 this.update(cx, |this, cx| {
5979 this.show_git_blame_inline = true;
5980 cx.notify();
5981 })
5982 .log_err();
5983 }));
5984 }
5985 }
5986
5987 fn show_blame_popover(
5988 &mut self,
5989 blame_entry: &BlameEntry,
5990 position: gpui::Point<Pixels>,
5991 cx: &mut Context<Self>,
5992 ) {
5993 if let Some(state) = &mut self.inline_blame_popover {
5994 state.hide_task.take();
5995 cx.notify();
5996 } else {
5997 let delay = EditorSettings::get_global(cx).hover_popover_delay;
5998 let show_task = cx.spawn(async move |editor, cx| {
5999 cx.background_executor()
6000 .timer(std::time::Duration::from_millis(delay))
6001 .await;
6002 editor
6003 .update(cx, |editor, cx| {
6004 if let Some(state) = &mut editor.inline_blame_popover {
6005 state.show_task = None;
6006 cx.notify();
6007 }
6008 })
6009 .ok();
6010 });
6011 let Some(blame) = self.blame.as_ref() else {
6012 return;
6013 };
6014 let blame = blame.read(cx);
6015 let details = blame.details_for_entry(&blame_entry);
6016 let markdown = cx.new(|cx| {
6017 Markdown::new(
6018 details
6019 .as_ref()
6020 .map(|message| message.message.clone())
6021 .unwrap_or_default(),
6022 None,
6023 None,
6024 cx,
6025 )
6026 });
6027 self.inline_blame_popover = Some(InlineBlamePopover {
6028 position,
6029 show_task: Some(show_task),
6030 hide_task: None,
6031 popover_bounds: None,
6032 popover_state: InlineBlamePopoverState {
6033 scroll_handle: ScrollHandle::new(),
6034 commit_message: details,
6035 markdown,
6036 },
6037 });
6038 }
6039 }
6040
6041 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6042 if let Some(state) = &mut self.inline_blame_popover {
6043 if state.show_task.is_some() {
6044 self.inline_blame_popover.take();
6045 cx.notify();
6046 } else {
6047 let hide_task = cx.spawn(async move |editor, cx| {
6048 cx.background_executor()
6049 .timer(std::time::Duration::from_millis(100))
6050 .await;
6051 editor
6052 .update(cx, |editor, cx| {
6053 editor.inline_blame_popover.take();
6054 cx.notify();
6055 })
6056 .ok();
6057 });
6058 state.hide_task = Some(hide_task);
6059 }
6060 }
6061 }
6062
6063 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6064 if self.pending_rename.is_some() {
6065 return None;
6066 }
6067
6068 let provider = self.semantics_provider.clone()?;
6069 let buffer = self.buffer.read(cx);
6070 let newest_selection = self.selections.newest_anchor().clone();
6071 let cursor_position = newest_selection.head();
6072 let (cursor_buffer, cursor_buffer_position) =
6073 buffer.text_anchor_for_position(cursor_position, cx)?;
6074 let (tail_buffer, tail_buffer_position) =
6075 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6076 if cursor_buffer != tail_buffer {
6077 return None;
6078 }
6079
6080 let snapshot = cursor_buffer.read(cx).snapshot();
6081 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6082 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6083 if start_word_range != end_word_range {
6084 self.document_highlights_task.take();
6085 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6086 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6087 return None;
6088 }
6089
6090 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6091 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6092 cx.background_executor()
6093 .timer(Duration::from_millis(debounce))
6094 .await;
6095
6096 let highlights = if let Some(highlights) = cx
6097 .update(|cx| {
6098 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6099 })
6100 .ok()
6101 .flatten()
6102 {
6103 highlights.await.log_err()
6104 } else {
6105 None
6106 };
6107
6108 if let Some(highlights) = highlights {
6109 this.update(cx, |this, cx| {
6110 if this.pending_rename.is_some() {
6111 return;
6112 }
6113
6114 let buffer_id = cursor_position.buffer_id;
6115 let buffer = this.buffer.read(cx);
6116 if !buffer
6117 .text_anchor_for_position(cursor_position, cx)
6118 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6119 {
6120 return;
6121 }
6122
6123 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6124 let mut write_ranges = Vec::new();
6125 let mut read_ranges = Vec::new();
6126 for highlight in highlights {
6127 for (excerpt_id, excerpt_range) in
6128 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6129 {
6130 let start = highlight
6131 .range
6132 .start
6133 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6134 let end = highlight
6135 .range
6136 .end
6137 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6138 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6139 continue;
6140 }
6141
6142 let range = Anchor {
6143 buffer_id,
6144 excerpt_id,
6145 text_anchor: start,
6146 diff_base_anchor: None,
6147 }..Anchor {
6148 buffer_id,
6149 excerpt_id,
6150 text_anchor: end,
6151 diff_base_anchor: None,
6152 };
6153 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6154 write_ranges.push(range);
6155 } else {
6156 read_ranges.push(range);
6157 }
6158 }
6159 }
6160
6161 this.highlight_background::<DocumentHighlightRead>(
6162 &read_ranges,
6163 |theme| theme.editor_document_highlight_read_background,
6164 cx,
6165 );
6166 this.highlight_background::<DocumentHighlightWrite>(
6167 &write_ranges,
6168 |theme| theme.editor_document_highlight_write_background,
6169 cx,
6170 );
6171 cx.notify();
6172 })
6173 .log_err();
6174 }
6175 }));
6176 None
6177 }
6178
6179 fn prepare_highlight_query_from_selection(
6180 &mut self,
6181 cx: &mut Context<Editor>,
6182 ) -> Option<(String, Range<Anchor>)> {
6183 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6184 return None;
6185 }
6186 if !EditorSettings::get_global(cx).selection_highlight {
6187 return None;
6188 }
6189 if self.selections.count() != 1 || self.selections.line_mode {
6190 return None;
6191 }
6192 let selection = self.selections.newest::<Point>(cx);
6193 if selection.is_empty() || selection.start.row != selection.end.row {
6194 return None;
6195 }
6196 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6197 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6198 let query = multi_buffer_snapshot
6199 .text_for_range(selection_anchor_range.clone())
6200 .collect::<String>();
6201 if query.trim().is_empty() {
6202 return None;
6203 }
6204 Some((query, selection_anchor_range))
6205 }
6206
6207 fn update_selection_occurrence_highlights(
6208 &mut self,
6209 query_text: String,
6210 query_range: Range<Anchor>,
6211 multi_buffer_range_to_query: Range<Point>,
6212 use_debounce: bool,
6213 window: &mut Window,
6214 cx: &mut Context<Editor>,
6215 ) -> Task<()> {
6216 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6217 cx.spawn_in(window, async move |editor, cx| {
6218 if use_debounce {
6219 cx.background_executor()
6220 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6221 .await;
6222 }
6223 let match_task = cx.background_spawn(async move {
6224 let buffer_ranges = multi_buffer_snapshot
6225 .range_to_buffer_ranges(multi_buffer_range_to_query)
6226 .into_iter()
6227 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6228 let mut match_ranges = Vec::new();
6229 let Ok(regex) = project::search::SearchQuery::text(
6230 query_text.clone(),
6231 false,
6232 false,
6233 false,
6234 Default::default(),
6235 Default::default(),
6236 false,
6237 None,
6238 ) else {
6239 return Vec::default();
6240 };
6241 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6242 match_ranges.extend(
6243 regex
6244 .search(&buffer_snapshot, Some(search_range.clone()))
6245 .await
6246 .into_iter()
6247 .filter_map(|match_range| {
6248 let match_start = buffer_snapshot
6249 .anchor_after(search_range.start + match_range.start);
6250 let match_end = buffer_snapshot
6251 .anchor_before(search_range.start + match_range.end);
6252 let match_anchor_range = Anchor::range_in_buffer(
6253 excerpt_id,
6254 buffer_snapshot.remote_id(),
6255 match_start..match_end,
6256 );
6257 (match_anchor_range != query_range).then_some(match_anchor_range)
6258 }),
6259 );
6260 }
6261 match_ranges
6262 });
6263 let match_ranges = match_task.await;
6264 editor
6265 .update_in(cx, |editor, _, cx| {
6266 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6267 if !match_ranges.is_empty() {
6268 editor.highlight_background::<SelectedTextHighlight>(
6269 &match_ranges,
6270 |theme| theme.editor_document_highlight_bracket_background,
6271 cx,
6272 )
6273 }
6274 })
6275 .log_err();
6276 })
6277 }
6278
6279 fn refresh_selected_text_highlights(
6280 &mut self,
6281 on_buffer_edit: bool,
6282 window: &mut Window,
6283 cx: &mut Context<Editor>,
6284 ) {
6285 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6286 else {
6287 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6288 self.quick_selection_highlight_task.take();
6289 self.debounced_selection_highlight_task.take();
6290 return;
6291 };
6292 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6293 if on_buffer_edit
6294 || self
6295 .quick_selection_highlight_task
6296 .as_ref()
6297 .map_or(true, |(prev_anchor_range, _)| {
6298 prev_anchor_range != &query_range
6299 })
6300 {
6301 let multi_buffer_visible_start = self
6302 .scroll_manager
6303 .anchor()
6304 .anchor
6305 .to_point(&multi_buffer_snapshot);
6306 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6307 multi_buffer_visible_start
6308 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6309 Bias::Left,
6310 );
6311 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6312 self.quick_selection_highlight_task = Some((
6313 query_range.clone(),
6314 self.update_selection_occurrence_highlights(
6315 query_text.clone(),
6316 query_range.clone(),
6317 multi_buffer_visible_range,
6318 false,
6319 window,
6320 cx,
6321 ),
6322 ));
6323 }
6324 if on_buffer_edit
6325 || self
6326 .debounced_selection_highlight_task
6327 .as_ref()
6328 .map_or(true, |(prev_anchor_range, _)| {
6329 prev_anchor_range != &query_range
6330 })
6331 {
6332 let multi_buffer_start = multi_buffer_snapshot
6333 .anchor_before(0)
6334 .to_point(&multi_buffer_snapshot);
6335 let multi_buffer_end = multi_buffer_snapshot
6336 .anchor_after(multi_buffer_snapshot.len())
6337 .to_point(&multi_buffer_snapshot);
6338 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6339 self.debounced_selection_highlight_task = Some((
6340 query_range.clone(),
6341 self.update_selection_occurrence_highlights(
6342 query_text,
6343 query_range,
6344 multi_buffer_full_range,
6345 true,
6346 window,
6347 cx,
6348 ),
6349 ));
6350 }
6351 }
6352
6353 pub fn refresh_inline_completion(
6354 &mut self,
6355 debounce: bool,
6356 user_requested: bool,
6357 window: &mut Window,
6358 cx: &mut Context<Self>,
6359 ) -> Option<()> {
6360 let provider = self.edit_prediction_provider()?;
6361 let cursor = self.selections.newest_anchor().head();
6362 let (buffer, cursor_buffer_position) =
6363 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6364
6365 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6366 self.discard_inline_completion(false, cx);
6367 return None;
6368 }
6369
6370 if !user_requested
6371 && (!self.should_show_edit_predictions()
6372 || !self.is_focused(window)
6373 || buffer.read(cx).is_empty())
6374 {
6375 self.discard_inline_completion(false, cx);
6376 return None;
6377 }
6378
6379 self.update_visible_inline_completion(window, cx);
6380 provider.refresh(
6381 self.project.clone(),
6382 buffer,
6383 cursor_buffer_position,
6384 debounce,
6385 cx,
6386 );
6387 Some(())
6388 }
6389
6390 fn show_edit_predictions_in_menu(&self) -> bool {
6391 match self.edit_prediction_settings {
6392 EditPredictionSettings::Disabled => false,
6393 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6394 }
6395 }
6396
6397 pub fn edit_predictions_enabled(&self) -> bool {
6398 match self.edit_prediction_settings {
6399 EditPredictionSettings::Disabled => false,
6400 EditPredictionSettings::Enabled { .. } => true,
6401 }
6402 }
6403
6404 fn edit_prediction_requires_modifier(&self) -> bool {
6405 match self.edit_prediction_settings {
6406 EditPredictionSettings::Disabled => false,
6407 EditPredictionSettings::Enabled {
6408 preview_requires_modifier,
6409 ..
6410 } => preview_requires_modifier,
6411 }
6412 }
6413
6414 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6415 if self.edit_prediction_provider.is_none() {
6416 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6417 } else {
6418 let selection = self.selections.newest_anchor();
6419 let cursor = selection.head();
6420
6421 if let Some((buffer, cursor_buffer_position)) =
6422 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6423 {
6424 self.edit_prediction_settings =
6425 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6426 }
6427 }
6428 }
6429
6430 fn edit_prediction_settings_at_position(
6431 &self,
6432 buffer: &Entity<Buffer>,
6433 buffer_position: language::Anchor,
6434 cx: &App,
6435 ) -> EditPredictionSettings {
6436 if !self.mode.is_full()
6437 || !self.show_inline_completions_override.unwrap_or(true)
6438 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6439 {
6440 return EditPredictionSettings::Disabled;
6441 }
6442
6443 let buffer = buffer.read(cx);
6444
6445 let file = buffer.file();
6446
6447 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6448 return EditPredictionSettings::Disabled;
6449 };
6450
6451 let by_provider = matches!(
6452 self.menu_inline_completions_policy,
6453 MenuInlineCompletionsPolicy::ByProvider
6454 );
6455
6456 let show_in_menu = by_provider
6457 && self
6458 .edit_prediction_provider
6459 .as_ref()
6460 .map_or(false, |provider| {
6461 provider.provider.show_completions_in_menu()
6462 });
6463
6464 let preview_requires_modifier =
6465 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6466
6467 EditPredictionSettings::Enabled {
6468 show_in_menu,
6469 preview_requires_modifier,
6470 }
6471 }
6472
6473 fn should_show_edit_predictions(&self) -> bool {
6474 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6475 }
6476
6477 pub fn edit_prediction_preview_is_active(&self) -> bool {
6478 matches!(
6479 self.edit_prediction_preview,
6480 EditPredictionPreview::Active { .. }
6481 )
6482 }
6483
6484 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6485 let cursor = self.selections.newest_anchor().head();
6486 if let Some((buffer, cursor_position)) =
6487 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6488 {
6489 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6490 } else {
6491 false
6492 }
6493 }
6494
6495 pub fn supports_minimap(&self, cx: &App) -> bool {
6496 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6497 }
6498
6499 fn edit_predictions_enabled_in_buffer(
6500 &self,
6501 buffer: &Entity<Buffer>,
6502 buffer_position: language::Anchor,
6503 cx: &App,
6504 ) -> bool {
6505 maybe!({
6506 if self.read_only(cx) {
6507 return Some(false);
6508 }
6509 let provider = self.edit_prediction_provider()?;
6510 if !provider.is_enabled(&buffer, buffer_position, cx) {
6511 return Some(false);
6512 }
6513 let buffer = buffer.read(cx);
6514 let Some(file) = buffer.file() else {
6515 return Some(true);
6516 };
6517 let settings = all_language_settings(Some(file), cx);
6518 Some(settings.edit_predictions_enabled_for_file(file, cx))
6519 })
6520 .unwrap_or(false)
6521 }
6522
6523 fn cycle_inline_completion(
6524 &mut self,
6525 direction: Direction,
6526 window: &mut Window,
6527 cx: &mut Context<Self>,
6528 ) -> Option<()> {
6529 let provider = self.edit_prediction_provider()?;
6530 let cursor = self.selections.newest_anchor().head();
6531 let (buffer, cursor_buffer_position) =
6532 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6533 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6534 return None;
6535 }
6536
6537 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6538 self.update_visible_inline_completion(window, cx);
6539
6540 Some(())
6541 }
6542
6543 pub fn show_inline_completion(
6544 &mut self,
6545 _: &ShowEditPrediction,
6546 window: &mut Window,
6547 cx: &mut Context<Self>,
6548 ) {
6549 if !self.has_active_inline_completion() {
6550 self.refresh_inline_completion(false, true, window, cx);
6551 return;
6552 }
6553
6554 self.update_visible_inline_completion(window, cx);
6555 }
6556
6557 pub fn display_cursor_names(
6558 &mut self,
6559 _: &DisplayCursorNames,
6560 window: &mut Window,
6561 cx: &mut Context<Self>,
6562 ) {
6563 self.show_cursor_names(window, cx);
6564 }
6565
6566 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6567 self.show_cursor_names = true;
6568 cx.notify();
6569 cx.spawn_in(window, async move |this, cx| {
6570 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6571 this.update(cx, |this, cx| {
6572 this.show_cursor_names = false;
6573 cx.notify()
6574 })
6575 .ok()
6576 })
6577 .detach();
6578 }
6579
6580 pub fn next_edit_prediction(
6581 &mut self,
6582 _: &NextEditPrediction,
6583 window: &mut Window,
6584 cx: &mut Context<Self>,
6585 ) {
6586 if self.has_active_inline_completion() {
6587 self.cycle_inline_completion(Direction::Next, window, cx);
6588 } else {
6589 let is_copilot_disabled = self
6590 .refresh_inline_completion(false, true, window, cx)
6591 .is_none();
6592 if is_copilot_disabled {
6593 cx.propagate();
6594 }
6595 }
6596 }
6597
6598 pub fn previous_edit_prediction(
6599 &mut self,
6600 _: &PreviousEditPrediction,
6601 window: &mut Window,
6602 cx: &mut Context<Self>,
6603 ) {
6604 if self.has_active_inline_completion() {
6605 self.cycle_inline_completion(Direction::Prev, window, cx);
6606 } else {
6607 let is_copilot_disabled = self
6608 .refresh_inline_completion(false, true, window, cx)
6609 .is_none();
6610 if is_copilot_disabled {
6611 cx.propagate();
6612 }
6613 }
6614 }
6615
6616 pub fn accept_edit_prediction(
6617 &mut self,
6618 _: &AcceptEditPrediction,
6619 window: &mut Window,
6620 cx: &mut Context<Self>,
6621 ) {
6622 if self.show_edit_predictions_in_menu() {
6623 self.hide_context_menu(window, cx);
6624 }
6625
6626 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6627 return;
6628 };
6629
6630 self.report_inline_completion_event(
6631 active_inline_completion.completion_id.clone(),
6632 true,
6633 cx,
6634 );
6635
6636 match &active_inline_completion.completion {
6637 InlineCompletion::Move { target, .. } => {
6638 let target = *target;
6639
6640 if let Some(position_map) = &self.last_position_map {
6641 if position_map
6642 .visible_row_range
6643 .contains(&target.to_display_point(&position_map.snapshot).row())
6644 || !self.edit_prediction_requires_modifier()
6645 {
6646 self.unfold_ranges(&[target..target], true, false, cx);
6647 // Note that this is also done in vim's handler of the Tab action.
6648 self.change_selections(
6649 Some(Autoscroll::newest()),
6650 window,
6651 cx,
6652 |selections| {
6653 selections.select_anchor_ranges([target..target]);
6654 },
6655 );
6656 self.clear_row_highlights::<EditPredictionPreview>();
6657
6658 self.edit_prediction_preview
6659 .set_previous_scroll_position(None);
6660 } else {
6661 self.edit_prediction_preview
6662 .set_previous_scroll_position(Some(
6663 position_map.snapshot.scroll_anchor,
6664 ));
6665
6666 self.highlight_rows::<EditPredictionPreview>(
6667 target..target,
6668 cx.theme().colors().editor_highlighted_line_background,
6669 RowHighlightOptions {
6670 autoscroll: true,
6671 ..Default::default()
6672 },
6673 cx,
6674 );
6675 self.request_autoscroll(Autoscroll::fit(), cx);
6676 }
6677 }
6678 }
6679 InlineCompletion::Edit { edits, .. } => {
6680 if let Some(provider) = self.edit_prediction_provider() {
6681 provider.accept(cx);
6682 }
6683
6684 // Store the transaction ID and selections before applying the edit
6685 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
6686
6687 let snapshot = self.buffer.read(cx).snapshot(cx);
6688 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6689
6690 self.buffer.update(cx, |buffer, cx| {
6691 buffer.edit(edits.iter().cloned(), None, cx)
6692 });
6693
6694 self.change_selections(None, window, cx, |s| {
6695 s.select_anchor_ranges([last_edit_end..last_edit_end]);
6696 });
6697
6698 let selections = self.selections.disjoint_anchors();
6699 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
6700 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
6701 if has_new_transaction {
6702 self.selection_history
6703 .insert_transaction(transaction_id_now, selections);
6704 }
6705 }
6706
6707 self.update_visible_inline_completion(window, cx);
6708 if self.active_inline_completion.is_none() {
6709 self.refresh_inline_completion(true, true, window, cx);
6710 }
6711
6712 cx.notify();
6713 }
6714 }
6715
6716 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6717 }
6718
6719 pub fn accept_partial_inline_completion(
6720 &mut self,
6721 _: &AcceptPartialEditPrediction,
6722 window: &mut Window,
6723 cx: &mut Context<Self>,
6724 ) {
6725 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6726 return;
6727 };
6728 if self.selections.count() != 1 {
6729 return;
6730 }
6731
6732 self.report_inline_completion_event(
6733 active_inline_completion.completion_id.clone(),
6734 true,
6735 cx,
6736 );
6737
6738 match &active_inline_completion.completion {
6739 InlineCompletion::Move { target, .. } => {
6740 let target = *target;
6741 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6742 selections.select_anchor_ranges([target..target]);
6743 });
6744 }
6745 InlineCompletion::Edit { edits, .. } => {
6746 // Find an insertion that starts at the cursor position.
6747 let snapshot = self.buffer.read(cx).snapshot(cx);
6748 let cursor_offset = self.selections.newest::<usize>(cx).head();
6749 let insertion = edits.iter().find_map(|(range, text)| {
6750 let range = range.to_offset(&snapshot);
6751 if range.is_empty() && range.start == cursor_offset {
6752 Some(text)
6753 } else {
6754 None
6755 }
6756 });
6757
6758 if let Some(text) = insertion {
6759 let mut partial_completion = text
6760 .chars()
6761 .by_ref()
6762 .take_while(|c| c.is_alphabetic())
6763 .collect::<String>();
6764 if partial_completion.is_empty() {
6765 partial_completion = text
6766 .chars()
6767 .by_ref()
6768 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6769 .collect::<String>();
6770 }
6771
6772 cx.emit(EditorEvent::InputHandled {
6773 utf16_range_to_replace: None,
6774 text: partial_completion.clone().into(),
6775 });
6776
6777 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6778
6779 self.refresh_inline_completion(true, true, window, cx);
6780 cx.notify();
6781 } else {
6782 self.accept_edit_prediction(&Default::default(), window, cx);
6783 }
6784 }
6785 }
6786 }
6787
6788 fn discard_inline_completion(
6789 &mut self,
6790 should_report_inline_completion_event: bool,
6791 cx: &mut Context<Self>,
6792 ) -> bool {
6793 if should_report_inline_completion_event {
6794 let completion_id = self
6795 .active_inline_completion
6796 .as_ref()
6797 .and_then(|active_completion| active_completion.completion_id.clone());
6798
6799 self.report_inline_completion_event(completion_id, false, cx);
6800 }
6801
6802 if let Some(provider) = self.edit_prediction_provider() {
6803 provider.discard(cx);
6804 }
6805
6806 self.take_active_inline_completion(cx)
6807 }
6808
6809 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6810 let Some(provider) = self.edit_prediction_provider() else {
6811 return;
6812 };
6813
6814 let Some((_, buffer, _)) = self
6815 .buffer
6816 .read(cx)
6817 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6818 else {
6819 return;
6820 };
6821
6822 let extension = buffer
6823 .read(cx)
6824 .file()
6825 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6826
6827 let event_type = match accepted {
6828 true => "Edit Prediction Accepted",
6829 false => "Edit Prediction Discarded",
6830 };
6831 telemetry::event!(
6832 event_type,
6833 provider = provider.name(),
6834 prediction_id = id,
6835 suggestion_accepted = accepted,
6836 file_extension = extension,
6837 );
6838 }
6839
6840 pub fn has_active_inline_completion(&self) -> bool {
6841 self.active_inline_completion.is_some()
6842 }
6843
6844 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6845 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6846 return false;
6847 };
6848
6849 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6850 self.clear_highlights::<InlineCompletionHighlight>(cx);
6851 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6852 true
6853 }
6854
6855 /// Returns true when we're displaying the edit prediction popover below the cursor
6856 /// like we are not previewing and the LSP autocomplete menu is visible
6857 /// or we are in `when_holding_modifier` mode.
6858 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6859 if self.edit_prediction_preview_is_active()
6860 || !self.show_edit_predictions_in_menu()
6861 || !self.edit_predictions_enabled()
6862 {
6863 return false;
6864 }
6865
6866 if self.has_visible_completions_menu() {
6867 return true;
6868 }
6869
6870 has_completion && self.edit_prediction_requires_modifier()
6871 }
6872
6873 fn handle_modifiers_changed(
6874 &mut self,
6875 modifiers: Modifiers,
6876 position_map: &PositionMap,
6877 window: &mut Window,
6878 cx: &mut Context<Self>,
6879 ) {
6880 if self.show_edit_predictions_in_menu() {
6881 self.update_edit_prediction_preview(&modifiers, window, cx);
6882 }
6883
6884 self.update_selection_mode(&modifiers, position_map, window, cx);
6885
6886 let mouse_position = window.mouse_position();
6887 if !position_map.text_hitbox.is_hovered(window) {
6888 return;
6889 }
6890
6891 self.update_hovered_link(
6892 position_map.point_for_position(mouse_position),
6893 &position_map.snapshot,
6894 modifiers,
6895 window,
6896 cx,
6897 )
6898 }
6899
6900 fn update_selection_mode(
6901 &mut self,
6902 modifiers: &Modifiers,
6903 position_map: &PositionMap,
6904 window: &mut Window,
6905 cx: &mut Context<Self>,
6906 ) {
6907 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6908 return;
6909 }
6910
6911 let mouse_position = window.mouse_position();
6912 let point_for_position = position_map.point_for_position(mouse_position);
6913 let position = point_for_position.previous_valid;
6914
6915 self.select(
6916 SelectPhase::BeginColumnar {
6917 position,
6918 reset: false,
6919 goal_column: point_for_position.exact_unclipped.column(),
6920 },
6921 window,
6922 cx,
6923 );
6924 }
6925
6926 fn update_edit_prediction_preview(
6927 &mut self,
6928 modifiers: &Modifiers,
6929 window: &mut Window,
6930 cx: &mut Context<Self>,
6931 ) {
6932 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6933 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6934 return;
6935 };
6936
6937 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6938 if matches!(
6939 self.edit_prediction_preview,
6940 EditPredictionPreview::Inactive { .. }
6941 ) {
6942 self.edit_prediction_preview = EditPredictionPreview::Active {
6943 previous_scroll_position: None,
6944 since: Instant::now(),
6945 };
6946
6947 self.update_visible_inline_completion(window, cx);
6948 cx.notify();
6949 }
6950 } else if let EditPredictionPreview::Active {
6951 previous_scroll_position,
6952 since,
6953 } = self.edit_prediction_preview
6954 {
6955 if let (Some(previous_scroll_position), Some(position_map)) =
6956 (previous_scroll_position, self.last_position_map.as_ref())
6957 {
6958 self.set_scroll_position(
6959 previous_scroll_position
6960 .scroll_position(&position_map.snapshot.display_snapshot),
6961 window,
6962 cx,
6963 );
6964 }
6965
6966 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6967 released_too_fast: since.elapsed() < Duration::from_millis(200),
6968 };
6969 self.clear_row_highlights::<EditPredictionPreview>();
6970 self.update_visible_inline_completion(window, cx);
6971 cx.notify();
6972 }
6973 }
6974
6975 fn update_visible_inline_completion(
6976 &mut self,
6977 _window: &mut Window,
6978 cx: &mut Context<Self>,
6979 ) -> Option<()> {
6980 let selection = self.selections.newest_anchor();
6981 let cursor = selection.head();
6982 let multibuffer = self.buffer.read(cx).snapshot(cx);
6983 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6984 let excerpt_id = cursor.excerpt_id;
6985
6986 let show_in_menu = self.show_edit_predictions_in_menu();
6987 let completions_menu_has_precedence = !show_in_menu
6988 && (self.context_menu.borrow().is_some()
6989 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6990
6991 if completions_menu_has_precedence
6992 || !offset_selection.is_empty()
6993 || self
6994 .active_inline_completion
6995 .as_ref()
6996 .map_or(false, |completion| {
6997 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6998 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6999 !invalidation_range.contains(&offset_selection.head())
7000 })
7001 {
7002 self.discard_inline_completion(false, cx);
7003 return None;
7004 }
7005
7006 self.take_active_inline_completion(cx);
7007 let Some(provider) = self.edit_prediction_provider() else {
7008 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7009 return None;
7010 };
7011
7012 let (buffer, cursor_buffer_position) =
7013 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7014
7015 self.edit_prediction_settings =
7016 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7017
7018 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7019
7020 if self.edit_prediction_indent_conflict {
7021 let cursor_point = cursor.to_point(&multibuffer);
7022
7023 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7024
7025 if let Some((_, indent)) = indents.iter().next() {
7026 if indent.len == cursor_point.column {
7027 self.edit_prediction_indent_conflict = false;
7028 }
7029 }
7030 }
7031
7032 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7033 let edits = inline_completion
7034 .edits
7035 .into_iter()
7036 .flat_map(|(range, new_text)| {
7037 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7038 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7039 Some((start..end, new_text))
7040 })
7041 .collect::<Vec<_>>();
7042 if edits.is_empty() {
7043 return None;
7044 }
7045
7046 let first_edit_start = edits.first().unwrap().0.start;
7047 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7048 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7049
7050 let last_edit_end = edits.last().unwrap().0.end;
7051 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7052 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7053
7054 let cursor_row = cursor.to_point(&multibuffer).row;
7055
7056 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7057
7058 let mut inlay_ids = Vec::new();
7059 let invalidation_row_range;
7060 let move_invalidation_row_range = if cursor_row < edit_start_row {
7061 Some(cursor_row..edit_end_row)
7062 } else if cursor_row > edit_end_row {
7063 Some(edit_start_row..cursor_row)
7064 } else {
7065 None
7066 };
7067 let is_move =
7068 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7069 let completion = if is_move {
7070 invalidation_row_range =
7071 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7072 let target = first_edit_start;
7073 InlineCompletion::Move { target, snapshot }
7074 } else {
7075 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7076 && !self.inline_completions_hidden_for_vim_mode;
7077
7078 if show_completions_in_buffer {
7079 if edits
7080 .iter()
7081 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7082 {
7083 let mut inlays = Vec::new();
7084 for (range, new_text) in &edits {
7085 let inlay = Inlay::inline_completion(
7086 post_inc(&mut self.next_inlay_id),
7087 range.start,
7088 new_text.as_str(),
7089 );
7090 inlay_ids.push(inlay.id);
7091 inlays.push(inlay);
7092 }
7093
7094 self.splice_inlays(&[], inlays, cx);
7095 } else {
7096 let background_color = cx.theme().status().deleted_background;
7097 self.highlight_text::<InlineCompletionHighlight>(
7098 edits.iter().map(|(range, _)| range.clone()).collect(),
7099 HighlightStyle {
7100 background_color: Some(background_color),
7101 ..Default::default()
7102 },
7103 cx,
7104 );
7105 }
7106 }
7107
7108 invalidation_row_range = edit_start_row..edit_end_row;
7109
7110 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7111 if provider.show_tab_accept_marker() {
7112 EditDisplayMode::TabAccept
7113 } else {
7114 EditDisplayMode::Inline
7115 }
7116 } else {
7117 EditDisplayMode::DiffPopover
7118 };
7119
7120 InlineCompletion::Edit {
7121 edits,
7122 edit_preview: inline_completion.edit_preview,
7123 display_mode,
7124 snapshot,
7125 }
7126 };
7127
7128 let invalidation_range = multibuffer
7129 .anchor_before(Point::new(invalidation_row_range.start, 0))
7130 ..multibuffer.anchor_after(Point::new(
7131 invalidation_row_range.end,
7132 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7133 ));
7134
7135 self.stale_inline_completion_in_menu = None;
7136 self.active_inline_completion = Some(InlineCompletionState {
7137 inlay_ids,
7138 completion,
7139 completion_id: inline_completion.id,
7140 invalidation_range,
7141 });
7142
7143 cx.notify();
7144
7145 Some(())
7146 }
7147
7148 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7149 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7150 }
7151
7152 fn clear_tasks(&mut self) {
7153 self.tasks.clear()
7154 }
7155
7156 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7157 if self.tasks.insert(key, value).is_some() {
7158 // This case should hopefully be rare, but just in case...
7159 log::error!(
7160 "multiple different run targets found on a single line, only the last target will be rendered"
7161 )
7162 }
7163 }
7164
7165 /// Get all display points of breakpoints that will be rendered within editor
7166 ///
7167 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7168 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7169 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7170 fn active_breakpoints(
7171 &self,
7172 range: Range<DisplayRow>,
7173 window: &mut Window,
7174 cx: &mut Context<Self>,
7175 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7176 let mut breakpoint_display_points = HashMap::default();
7177
7178 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7179 return breakpoint_display_points;
7180 };
7181
7182 let snapshot = self.snapshot(window, cx);
7183
7184 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7185 let Some(project) = self.project.as_ref() else {
7186 return breakpoint_display_points;
7187 };
7188
7189 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7190 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7191
7192 for (buffer_snapshot, range, excerpt_id) in
7193 multi_buffer_snapshot.range_to_buffer_ranges(range)
7194 {
7195 let Some(buffer) = project
7196 .read(cx)
7197 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7198 else {
7199 continue;
7200 };
7201 let breakpoints = breakpoint_store.read(cx).breakpoints(
7202 &buffer,
7203 Some(
7204 buffer_snapshot.anchor_before(range.start)
7205 ..buffer_snapshot.anchor_after(range.end),
7206 ),
7207 buffer_snapshot,
7208 cx,
7209 );
7210 for (breakpoint, state) in breakpoints {
7211 let multi_buffer_anchor =
7212 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7213 let position = multi_buffer_anchor
7214 .to_point(&multi_buffer_snapshot)
7215 .to_display_point(&snapshot);
7216
7217 breakpoint_display_points.insert(
7218 position.row(),
7219 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7220 );
7221 }
7222 }
7223
7224 breakpoint_display_points
7225 }
7226
7227 fn breakpoint_context_menu(
7228 &self,
7229 anchor: Anchor,
7230 window: &mut Window,
7231 cx: &mut Context<Self>,
7232 ) -> Entity<ui::ContextMenu> {
7233 let weak_editor = cx.weak_entity();
7234 let focus_handle = self.focus_handle(cx);
7235
7236 let row = self
7237 .buffer
7238 .read(cx)
7239 .snapshot(cx)
7240 .summary_for_anchor::<Point>(&anchor)
7241 .row;
7242
7243 let breakpoint = self
7244 .breakpoint_at_row(row, window, cx)
7245 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7246
7247 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7248 "Edit Log Breakpoint"
7249 } else {
7250 "Set Log Breakpoint"
7251 };
7252
7253 let condition_breakpoint_msg = if breakpoint
7254 .as_ref()
7255 .is_some_and(|bp| bp.1.condition.is_some())
7256 {
7257 "Edit Condition Breakpoint"
7258 } else {
7259 "Set Condition Breakpoint"
7260 };
7261
7262 let hit_condition_breakpoint_msg = if breakpoint
7263 .as_ref()
7264 .is_some_and(|bp| bp.1.hit_condition.is_some())
7265 {
7266 "Edit Hit Condition Breakpoint"
7267 } else {
7268 "Set Hit Condition Breakpoint"
7269 };
7270
7271 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7272 "Unset Breakpoint"
7273 } else {
7274 "Set Breakpoint"
7275 };
7276
7277 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
7278 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
7279
7280 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7281 BreakpointState::Enabled => Some("Disable"),
7282 BreakpointState::Disabled => Some("Enable"),
7283 });
7284
7285 let (anchor, breakpoint) =
7286 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7287
7288 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7289 menu.on_blur_subscription(Subscription::new(|| {}))
7290 .context(focus_handle)
7291 .when(run_to_cursor, |this| {
7292 let weak_editor = weak_editor.clone();
7293 this.entry("Run to cursor", None, move |window, cx| {
7294 weak_editor
7295 .update(cx, |editor, cx| {
7296 editor.change_selections(None, window, cx, |s| {
7297 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7298 });
7299 })
7300 .ok();
7301
7302 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
7303 })
7304 .separator()
7305 })
7306 .when_some(toggle_state_msg, |this, msg| {
7307 this.entry(msg, None, {
7308 let weak_editor = weak_editor.clone();
7309 let breakpoint = breakpoint.clone();
7310 move |_window, cx| {
7311 weak_editor
7312 .update(cx, |this, cx| {
7313 this.edit_breakpoint_at_anchor(
7314 anchor,
7315 breakpoint.as_ref().clone(),
7316 BreakpointEditAction::InvertState,
7317 cx,
7318 );
7319 })
7320 .log_err();
7321 }
7322 })
7323 })
7324 .entry(set_breakpoint_msg, None, {
7325 let weak_editor = weak_editor.clone();
7326 let breakpoint = breakpoint.clone();
7327 move |_window, cx| {
7328 weak_editor
7329 .update(cx, |this, cx| {
7330 this.edit_breakpoint_at_anchor(
7331 anchor,
7332 breakpoint.as_ref().clone(),
7333 BreakpointEditAction::Toggle,
7334 cx,
7335 );
7336 })
7337 .log_err();
7338 }
7339 })
7340 .entry(log_breakpoint_msg, None, {
7341 let breakpoint = breakpoint.clone();
7342 let weak_editor = weak_editor.clone();
7343 move |window, cx| {
7344 weak_editor
7345 .update(cx, |this, cx| {
7346 this.add_edit_breakpoint_block(
7347 anchor,
7348 breakpoint.as_ref(),
7349 BreakpointPromptEditAction::Log,
7350 window,
7351 cx,
7352 );
7353 })
7354 .log_err();
7355 }
7356 })
7357 .entry(condition_breakpoint_msg, None, {
7358 let breakpoint = breakpoint.clone();
7359 let weak_editor = weak_editor.clone();
7360 move |window, cx| {
7361 weak_editor
7362 .update(cx, |this, cx| {
7363 this.add_edit_breakpoint_block(
7364 anchor,
7365 breakpoint.as_ref(),
7366 BreakpointPromptEditAction::Condition,
7367 window,
7368 cx,
7369 );
7370 })
7371 .log_err();
7372 }
7373 })
7374 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7375 weak_editor
7376 .update(cx, |this, cx| {
7377 this.add_edit_breakpoint_block(
7378 anchor,
7379 breakpoint.as_ref(),
7380 BreakpointPromptEditAction::HitCondition,
7381 window,
7382 cx,
7383 );
7384 })
7385 .log_err();
7386 })
7387 })
7388 }
7389
7390 fn render_breakpoint(
7391 &self,
7392 position: Anchor,
7393 row: DisplayRow,
7394 breakpoint: &Breakpoint,
7395 state: Option<BreakpointSessionState>,
7396 cx: &mut Context<Self>,
7397 ) -> IconButton {
7398 let is_rejected = state.is_some_and(|s| !s.verified);
7399 // Is it a breakpoint that shows up when hovering over gutter?
7400 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7401 (false, false),
7402 |PhantomBreakpointIndicator {
7403 is_active,
7404 display_row,
7405 collides_with_existing_breakpoint,
7406 }| {
7407 (
7408 is_active && display_row == row,
7409 collides_with_existing_breakpoint,
7410 )
7411 },
7412 );
7413
7414 let (color, icon) = {
7415 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7416 (false, false) => ui::IconName::DebugBreakpoint,
7417 (true, false) => ui::IconName::DebugLogBreakpoint,
7418 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7419 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7420 };
7421
7422 let color = if is_phantom {
7423 Color::Hint
7424 } else if is_rejected {
7425 Color::Disabled
7426 } else {
7427 Color::Debugger
7428 };
7429
7430 (color, icon)
7431 };
7432
7433 let breakpoint = Arc::from(breakpoint.clone());
7434
7435 let alt_as_text = gpui::Keystroke {
7436 modifiers: Modifiers::secondary_key(),
7437 ..Default::default()
7438 };
7439 let primary_action_text = if breakpoint.is_disabled() {
7440 "Enable breakpoint"
7441 } else if is_phantom && !collides_with_existing {
7442 "Set breakpoint"
7443 } else {
7444 "Unset breakpoint"
7445 };
7446 let focus_handle = self.focus_handle.clone();
7447
7448 let meta = if is_rejected {
7449 SharedString::from("No executable code is associated with this line.")
7450 } else if collides_with_existing && !breakpoint.is_disabled() {
7451 SharedString::from(format!(
7452 "{alt_as_text}-click to disable,\nright-click for more options."
7453 ))
7454 } else {
7455 SharedString::from("Right-click for more options.")
7456 };
7457 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7458 .icon_size(IconSize::XSmall)
7459 .size(ui::ButtonSize::None)
7460 .when(is_rejected, |this| {
7461 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7462 })
7463 .icon_color(color)
7464 .style(ButtonStyle::Transparent)
7465 .on_click(cx.listener({
7466 let breakpoint = breakpoint.clone();
7467
7468 move |editor, event: &ClickEvent, window, cx| {
7469 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7470 BreakpointEditAction::InvertState
7471 } else {
7472 BreakpointEditAction::Toggle
7473 };
7474
7475 window.focus(&editor.focus_handle(cx));
7476 editor.edit_breakpoint_at_anchor(
7477 position,
7478 breakpoint.as_ref().clone(),
7479 edit_action,
7480 cx,
7481 );
7482 }
7483 }))
7484 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7485 editor.set_breakpoint_context_menu(
7486 row,
7487 Some(position),
7488 event.down.position,
7489 window,
7490 cx,
7491 );
7492 }))
7493 .tooltip(move |window, cx| {
7494 Tooltip::with_meta_in(
7495 primary_action_text,
7496 Some(&ToggleBreakpoint),
7497 meta.clone(),
7498 &focus_handle,
7499 window,
7500 cx,
7501 )
7502 })
7503 }
7504
7505 fn build_tasks_context(
7506 project: &Entity<Project>,
7507 buffer: &Entity<Buffer>,
7508 buffer_row: u32,
7509 tasks: &Arc<RunnableTasks>,
7510 cx: &mut Context<Self>,
7511 ) -> Task<Option<task::TaskContext>> {
7512 let position = Point::new(buffer_row, tasks.column);
7513 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7514 let location = Location {
7515 buffer: buffer.clone(),
7516 range: range_start..range_start,
7517 };
7518 // Fill in the environmental variables from the tree-sitter captures
7519 let mut captured_task_variables = TaskVariables::default();
7520 for (capture_name, value) in tasks.extra_variables.clone() {
7521 captured_task_variables.insert(
7522 task::VariableName::Custom(capture_name.into()),
7523 value.clone(),
7524 );
7525 }
7526 project.update(cx, |project, cx| {
7527 project.task_store().update(cx, |task_store, cx| {
7528 task_store.task_context_for_location(captured_task_variables, location, cx)
7529 })
7530 })
7531 }
7532
7533 pub fn spawn_nearest_task(
7534 &mut self,
7535 action: &SpawnNearestTask,
7536 window: &mut Window,
7537 cx: &mut Context<Self>,
7538 ) {
7539 let Some((workspace, _)) = self.workspace.clone() else {
7540 return;
7541 };
7542 let Some(project) = self.project.clone() else {
7543 return;
7544 };
7545
7546 // Try to find a closest, enclosing node using tree-sitter that has a
7547 // task
7548 let Some((buffer, buffer_row, tasks)) = self
7549 .find_enclosing_node_task(cx)
7550 // Or find the task that's closest in row-distance.
7551 .or_else(|| self.find_closest_task(cx))
7552 else {
7553 return;
7554 };
7555
7556 let reveal_strategy = action.reveal;
7557 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7558 cx.spawn_in(window, async move |_, cx| {
7559 let context = task_context.await?;
7560 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7561
7562 let resolved = &mut resolved_task.resolved;
7563 resolved.reveal = reveal_strategy;
7564
7565 workspace
7566 .update_in(cx, |workspace, window, cx| {
7567 workspace.schedule_resolved_task(
7568 task_source_kind,
7569 resolved_task,
7570 false,
7571 window,
7572 cx,
7573 );
7574 })
7575 .ok()
7576 })
7577 .detach();
7578 }
7579
7580 fn find_closest_task(
7581 &mut self,
7582 cx: &mut Context<Self>,
7583 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7584 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7585
7586 let ((buffer_id, row), tasks) = self
7587 .tasks
7588 .iter()
7589 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7590
7591 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7592 let tasks = Arc::new(tasks.to_owned());
7593 Some((buffer, *row, tasks))
7594 }
7595
7596 fn find_enclosing_node_task(
7597 &mut self,
7598 cx: &mut Context<Self>,
7599 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7600 let snapshot = self.buffer.read(cx).snapshot(cx);
7601 let offset = self.selections.newest::<usize>(cx).head();
7602 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7603 let buffer_id = excerpt.buffer().remote_id();
7604
7605 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7606 let mut cursor = layer.node().walk();
7607
7608 while cursor.goto_first_child_for_byte(offset).is_some() {
7609 if cursor.node().end_byte() == offset {
7610 cursor.goto_next_sibling();
7611 }
7612 }
7613
7614 // Ascend to the smallest ancestor that contains the range and has a task.
7615 loop {
7616 let node = cursor.node();
7617 let node_range = node.byte_range();
7618 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7619
7620 // Check if this node contains our offset
7621 if node_range.start <= offset && node_range.end >= offset {
7622 // If it contains offset, check for task
7623 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7624 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7625 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7626 }
7627 }
7628
7629 if !cursor.goto_parent() {
7630 break;
7631 }
7632 }
7633 None
7634 }
7635
7636 fn render_run_indicator(
7637 &self,
7638 _style: &EditorStyle,
7639 is_active: bool,
7640 row: DisplayRow,
7641 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7642 cx: &mut Context<Self>,
7643 ) -> IconButton {
7644 let color = Color::Muted;
7645 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7646
7647 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7648 .shape(ui::IconButtonShape::Square)
7649 .icon_size(IconSize::XSmall)
7650 .icon_color(color)
7651 .toggle_state(is_active)
7652 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7653 let quick_launch = e.down.button == MouseButton::Left;
7654 window.focus(&editor.focus_handle(cx));
7655 editor.toggle_code_actions(
7656 &ToggleCodeActions {
7657 deployed_from: Some(CodeActionSource::Indicator(row)),
7658 quick_launch,
7659 },
7660 window,
7661 cx,
7662 );
7663 }))
7664 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7665 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7666 }))
7667 }
7668
7669 pub fn context_menu_visible(&self) -> bool {
7670 !self.edit_prediction_preview_is_active()
7671 && self
7672 .context_menu
7673 .borrow()
7674 .as_ref()
7675 .map_or(false, |menu| menu.visible())
7676 }
7677
7678 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7679 self.context_menu
7680 .borrow()
7681 .as_ref()
7682 .map(|menu| menu.origin())
7683 }
7684
7685 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7686 self.context_menu_options = Some(options);
7687 }
7688
7689 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7690 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7691
7692 fn render_edit_prediction_popover(
7693 &mut self,
7694 text_bounds: &Bounds<Pixels>,
7695 content_origin: gpui::Point<Pixels>,
7696 right_margin: Pixels,
7697 editor_snapshot: &EditorSnapshot,
7698 visible_row_range: Range<DisplayRow>,
7699 scroll_top: f32,
7700 scroll_bottom: f32,
7701 line_layouts: &[LineWithInvisibles],
7702 line_height: Pixels,
7703 scroll_pixel_position: gpui::Point<Pixels>,
7704 newest_selection_head: Option<DisplayPoint>,
7705 editor_width: Pixels,
7706 style: &EditorStyle,
7707 window: &mut Window,
7708 cx: &mut App,
7709 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7710 if self.mode().is_minimap() {
7711 return None;
7712 }
7713 let active_inline_completion = self.active_inline_completion.as_ref()?;
7714
7715 if self.edit_prediction_visible_in_cursor_popover(true) {
7716 return None;
7717 }
7718
7719 match &active_inline_completion.completion {
7720 InlineCompletion::Move { target, .. } => {
7721 let target_display_point = target.to_display_point(editor_snapshot);
7722
7723 if self.edit_prediction_requires_modifier() {
7724 if !self.edit_prediction_preview_is_active() {
7725 return None;
7726 }
7727
7728 self.render_edit_prediction_modifier_jump_popover(
7729 text_bounds,
7730 content_origin,
7731 visible_row_range,
7732 line_layouts,
7733 line_height,
7734 scroll_pixel_position,
7735 newest_selection_head,
7736 target_display_point,
7737 window,
7738 cx,
7739 )
7740 } else {
7741 self.render_edit_prediction_eager_jump_popover(
7742 text_bounds,
7743 content_origin,
7744 editor_snapshot,
7745 visible_row_range,
7746 scroll_top,
7747 scroll_bottom,
7748 line_height,
7749 scroll_pixel_position,
7750 target_display_point,
7751 editor_width,
7752 window,
7753 cx,
7754 )
7755 }
7756 }
7757 InlineCompletion::Edit {
7758 display_mode: EditDisplayMode::Inline,
7759 ..
7760 } => None,
7761 InlineCompletion::Edit {
7762 display_mode: EditDisplayMode::TabAccept,
7763 edits,
7764 ..
7765 } => {
7766 let range = &edits.first()?.0;
7767 let target_display_point = range.end.to_display_point(editor_snapshot);
7768
7769 self.render_edit_prediction_end_of_line_popover(
7770 "Accept",
7771 editor_snapshot,
7772 visible_row_range,
7773 target_display_point,
7774 line_height,
7775 scroll_pixel_position,
7776 content_origin,
7777 editor_width,
7778 window,
7779 cx,
7780 )
7781 }
7782 InlineCompletion::Edit {
7783 edits,
7784 edit_preview,
7785 display_mode: EditDisplayMode::DiffPopover,
7786 snapshot,
7787 } => self.render_edit_prediction_diff_popover(
7788 text_bounds,
7789 content_origin,
7790 right_margin,
7791 editor_snapshot,
7792 visible_row_range,
7793 line_layouts,
7794 line_height,
7795 scroll_pixel_position,
7796 newest_selection_head,
7797 editor_width,
7798 style,
7799 edits,
7800 edit_preview,
7801 snapshot,
7802 window,
7803 cx,
7804 ),
7805 }
7806 }
7807
7808 fn render_edit_prediction_modifier_jump_popover(
7809 &mut self,
7810 text_bounds: &Bounds<Pixels>,
7811 content_origin: gpui::Point<Pixels>,
7812 visible_row_range: Range<DisplayRow>,
7813 line_layouts: &[LineWithInvisibles],
7814 line_height: Pixels,
7815 scroll_pixel_position: gpui::Point<Pixels>,
7816 newest_selection_head: Option<DisplayPoint>,
7817 target_display_point: DisplayPoint,
7818 window: &mut Window,
7819 cx: &mut App,
7820 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7821 let scrolled_content_origin =
7822 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7823
7824 const SCROLL_PADDING_Y: Pixels = px(12.);
7825
7826 if target_display_point.row() < visible_row_range.start {
7827 return self.render_edit_prediction_scroll_popover(
7828 |_| SCROLL_PADDING_Y,
7829 IconName::ArrowUp,
7830 visible_row_range,
7831 line_layouts,
7832 newest_selection_head,
7833 scrolled_content_origin,
7834 window,
7835 cx,
7836 );
7837 } else if target_display_point.row() >= visible_row_range.end {
7838 return self.render_edit_prediction_scroll_popover(
7839 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7840 IconName::ArrowDown,
7841 visible_row_range,
7842 line_layouts,
7843 newest_selection_head,
7844 scrolled_content_origin,
7845 window,
7846 cx,
7847 );
7848 }
7849
7850 const POLE_WIDTH: Pixels = px(2.);
7851
7852 let line_layout =
7853 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7854 let target_column = target_display_point.column() as usize;
7855
7856 let target_x = line_layout.x_for_index(target_column);
7857 let target_y =
7858 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7859
7860 let flag_on_right = target_x < text_bounds.size.width / 2.;
7861
7862 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7863 border_color.l += 0.001;
7864
7865 let mut element = v_flex()
7866 .items_end()
7867 .when(flag_on_right, |el| el.items_start())
7868 .child(if flag_on_right {
7869 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7870 .rounded_bl(px(0.))
7871 .rounded_tl(px(0.))
7872 .border_l_2()
7873 .border_color(border_color)
7874 } else {
7875 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7876 .rounded_br(px(0.))
7877 .rounded_tr(px(0.))
7878 .border_r_2()
7879 .border_color(border_color)
7880 })
7881 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7882 .into_any();
7883
7884 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7885
7886 let mut origin = scrolled_content_origin + point(target_x, target_y)
7887 - point(
7888 if flag_on_right {
7889 POLE_WIDTH
7890 } else {
7891 size.width - POLE_WIDTH
7892 },
7893 size.height - line_height,
7894 );
7895
7896 origin.x = origin.x.max(content_origin.x);
7897
7898 element.prepaint_at(origin, window, cx);
7899
7900 Some((element, origin))
7901 }
7902
7903 fn render_edit_prediction_scroll_popover(
7904 &mut self,
7905 to_y: impl Fn(Size<Pixels>) -> Pixels,
7906 scroll_icon: IconName,
7907 visible_row_range: Range<DisplayRow>,
7908 line_layouts: &[LineWithInvisibles],
7909 newest_selection_head: Option<DisplayPoint>,
7910 scrolled_content_origin: gpui::Point<Pixels>,
7911 window: &mut Window,
7912 cx: &mut App,
7913 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7914 let mut element = self
7915 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7916 .into_any();
7917
7918 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7919
7920 let cursor = newest_selection_head?;
7921 let cursor_row_layout =
7922 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7923 let cursor_column = cursor.column() as usize;
7924
7925 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7926
7927 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7928
7929 element.prepaint_at(origin, window, cx);
7930 Some((element, origin))
7931 }
7932
7933 fn render_edit_prediction_eager_jump_popover(
7934 &mut self,
7935 text_bounds: &Bounds<Pixels>,
7936 content_origin: gpui::Point<Pixels>,
7937 editor_snapshot: &EditorSnapshot,
7938 visible_row_range: Range<DisplayRow>,
7939 scroll_top: f32,
7940 scroll_bottom: f32,
7941 line_height: Pixels,
7942 scroll_pixel_position: gpui::Point<Pixels>,
7943 target_display_point: DisplayPoint,
7944 editor_width: Pixels,
7945 window: &mut Window,
7946 cx: &mut App,
7947 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7948 if target_display_point.row().as_f32() < scroll_top {
7949 let mut element = self
7950 .render_edit_prediction_line_popover(
7951 "Jump to Edit",
7952 Some(IconName::ArrowUp),
7953 window,
7954 cx,
7955 )?
7956 .into_any();
7957
7958 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7959 let offset = point(
7960 (text_bounds.size.width - size.width) / 2.,
7961 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7962 );
7963
7964 let origin = text_bounds.origin + offset;
7965 element.prepaint_at(origin, window, cx);
7966 Some((element, origin))
7967 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7968 let mut element = self
7969 .render_edit_prediction_line_popover(
7970 "Jump to Edit",
7971 Some(IconName::ArrowDown),
7972 window,
7973 cx,
7974 )?
7975 .into_any();
7976
7977 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7978 let offset = point(
7979 (text_bounds.size.width - size.width) / 2.,
7980 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7981 );
7982
7983 let origin = text_bounds.origin + offset;
7984 element.prepaint_at(origin, window, cx);
7985 Some((element, origin))
7986 } else {
7987 self.render_edit_prediction_end_of_line_popover(
7988 "Jump to Edit",
7989 editor_snapshot,
7990 visible_row_range,
7991 target_display_point,
7992 line_height,
7993 scroll_pixel_position,
7994 content_origin,
7995 editor_width,
7996 window,
7997 cx,
7998 )
7999 }
8000 }
8001
8002 fn render_edit_prediction_end_of_line_popover(
8003 self: &mut Editor,
8004 label: &'static str,
8005 editor_snapshot: &EditorSnapshot,
8006 visible_row_range: Range<DisplayRow>,
8007 target_display_point: DisplayPoint,
8008 line_height: Pixels,
8009 scroll_pixel_position: gpui::Point<Pixels>,
8010 content_origin: gpui::Point<Pixels>,
8011 editor_width: Pixels,
8012 window: &mut Window,
8013 cx: &mut App,
8014 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8015 let target_line_end = DisplayPoint::new(
8016 target_display_point.row(),
8017 editor_snapshot.line_len(target_display_point.row()),
8018 );
8019
8020 let mut element = self
8021 .render_edit_prediction_line_popover(label, None, window, cx)?
8022 .into_any();
8023
8024 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8025
8026 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8027
8028 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8029 let mut origin = start_point
8030 + line_origin
8031 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8032 origin.x = origin.x.max(content_origin.x);
8033
8034 let max_x = content_origin.x + editor_width - size.width;
8035
8036 if origin.x > max_x {
8037 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8038
8039 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8040 origin.y += offset;
8041 IconName::ArrowUp
8042 } else {
8043 origin.y -= offset;
8044 IconName::ArrowDown
8045 };
8046
8047 element = self
8048 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8049 .into_any();
8050
8051 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8052
8053 origin.x = content_origin.x + editor_width - size.width - px(2.);
8054 }
8055
8056 element.prepaint_at(origin, window, cx);
8057 Some((element, origin))
8058 }
8059
8060 fn render_edit_prediction_diff_popover(
8061 self: &Editor,
8062 text_bounds: &Bounds<Pixels>,
8063 content_origin: gpui::Point<Pixels>,
8064 right_margin: Pixels,
8065 editor_snapshot: &EditorSnapshot,
8066 visible_row_range: Range<DisplayRow>,
8067 line_layouts: &[LineWithInvisibles],
8068 line_height: Pixels,
8069 scroll_pixel_position: gpui::Point<Pixels>,
8070 newest_selection_head: Option<DisplayPoint>,
8071 editor_width: Pixels,
8072 style: &EditorStyle,
8073 edits: &Vec<(Range<Anchor>, String)>,
8074 edit_preview: &Option<language::EditPreview>,
8075 snapshot: &language::BufferSnapshot,
8076 window: &mut Window,
8077 cx: &mut App,
8078 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8079 let edit_start = edits
8080 .first()
8081 .unwrap()
8082 .0
8083 .start
8084 .to_display_point(editor_snapshot);
8085 let edit_end = edits
8086 .last()
8087 .unwrap()
8088 .0
8089 .end
8090 .to_display_point(editor_snapshot);
8091
8092 let is_visible = visible_row_range.contains(&edit_start.row())
8093 || visible_row_range.contains(&edit_end.row());
8094 if !is_visible {
8095 return None;
8096 }
8097
8098 let highlighted_edits =
8099 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8100
8101 let styled_text = highlighted_edits.to_styled_text(&style.text);
8102 let line_count = highlighted_edits.text.lines().count();
8103
8104 const BORDER_WIDTH: Pixels = px(1.);
8105
8106 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8107 let has_keybind = keybind.is_some();
8108
8109 let mut element = h_flex()
8110 .items_start()
8111 .child(
8112 h_flex()
8113 .bg(cx.theme().colors().editor_background)
8114 .border(BORDER_WIDTH)
8115 .shadow_sm()
8116 .border_color(cx.theme().colors().border)
8117 .rounded_l_lg()
8118 .when(line_count > 1, |el| el.rounded_br_lg())
8119 .pr_1()
8120 .child(styled_text),
8121 )
8122 .child(
8123 h_flex()
8124 .h(line_height + BORDER_WIDTH * 2.)
8125 .px_1p5()
8126 .gap_1()
8127 // Workaround: For some reason, there's a gap if we don't do this
8128 .ml(-BORDER_WIDTH)
8129 .shadow(vec![gpui::BoxShadow {
8130 color: gpui::black().opacity(0.05),
8131 offset: point(px(1.), px(1.)),
8132 blur_radius: px(2.),
8133 spread_radius: px(0.),
8134 }])
8135 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8136 .border(BORDER_WIDTH)
8137 .border_color(cx.theme().colors().border)
8138 .rounded_r_lg()
8139 .id("edit_prediction_diff_popover_keybind")
8140 .when(!has_keybind, |el| {
8141 let status_colors = cx.theme().status();
8142
8143 el.bg(status_colors.error_background)
8144 .border_color(status_colors.error.opacity(0.6))
8145 .child(Icon::new(IconName::Info).color(Color::Error))
8146 .cursor_default()
8147 .hoverable_tooltip(move |_window, cx| {
8148 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8149 })
8150 })
8151 .children(keybind),
8152 )
8153 .into_any();
8154
8155 let longest_row =
8156 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8157 let longest_line_width = if visible_row_range.contains(&longest_row) {
8158 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8159 } else {
8160 layout_line(
8161 longest_row,
8162 editor_snapshot,
8163 style,
8164 editor_width,
8165 |_| false,
8166 window,
8167 cx,
8168 )
8169 .width
8170 };
8171
8172 let viewport_bounds =
8173 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8174 right: -right_margin,
8175 ..Default::default()
8176 });
8177
8178 let x_after_longest =
8179 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8180 - scroll_pixel_position.x;
8181
8182 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8183
8184 // Fully visible if it can be displayed within the window (allow overlapping other
8185 // panes). However, this is only allowed if the popover starts within text_bounds.
8186 let can_position_to_the_right = x_after_longest < text_bounds.right()
8187 && x_after_longest + element_bounds.width < viewport_bounds.right();
8188
8189 let mut origin = if can_position_to_the_right {
8190 point(
8191 x_after_longest,
8192 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8193 - scroll_pixel_position.y,
8194 )
8195 } else {
8196 let cursor_row = newest_selection_head.map(|head| head.row());
8197 let above_edit = edit_start
8198 .row()
8199 .0
8200 .checked_sub(line_count as u32)
8201 .map(DisplayRow);
8202 let below_edit = Some(edit_end.row() + 1);
8203 let above_cursor =
8204 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8205 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8206
8207 // Place the edit popover adjacent to the edit if there is a location
8208 // available that is onscreen and does not obscure the cursor. Otherwise,
8209 // place it adjacent to the cursor.
8210 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8211 .into_iter()
8212 .flatten()
8213 .find(|&start_row| {
8214 let end_row = start_row + line_count as u32;
8215 visible_row_range.contains(&start_row)
8216 && visible_row_range.contains(&end_row)
8217 && cursor_row.map_or(true, |cursor_row| {
8218 !((start_row..end_row).contains(&cursor_row))
8219 })
8220 })?;
8221
8222 content_origin
8223 + point(
8224 -scroll_pixel_position.x,
8225 row_target.as_f32() * line_height - scroll_pixel_position.y,
8226 )
8227 };
8228
8229 origin.x -= BORDER_WIDTH;
8230
8231 window.defer_draw(element, origin, 1);
8232
8233 // Do not return an element, since it will already be drawn due to defer_draw.
8234 None
8235 }
8236
8237 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8238 px(30.)
8239 }
8240
8241 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8242 if self.read_only(cx) {
8243 cx.theme().players().read_only()
8244 } else {
8245 self.style.as_ref().unwrap().local_player
8246 }
8247 }
8248
8249 fn render_edit_prediction_accept_keybind(
8250 &self,
8251 window: &mut Window,
8252 cx: &App,
8253 ) -> Option<AnyElement> {
8254 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
8255 let accept_keystroke = accept_binding.keystroke()?;
8256
8257 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8258
8259 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8260 Color::Accent
8261 } else {
8262 Color::Muted
8263 };
8264
8265 h_flex()
8266 .px_0p5()
8267 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8268 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8269 .text_size(TextSize::XSmall.rems(cx))
8270 .child(h_flex().children(ui::render_modifiers(
8271 &accept_keystroke.modifiers,
8272 PlatformStyle::platform(),
8273 Some(modifiers_color),
8274 Some(IconSize::XSmall.rems().into()),
8275 true,
8276 )))
8277 .when(is_platform_style_mac, |parent| {
8278 parent.child(accept_keystroke.key.clone())
8279 })
8280 .when(!is_platform_style_mac, |parent| {
8281 parent.child(
8282 Key::new(
8283 util::capitalize(&accept_keystroke.key),
8284 Some(Color::Default),
8285 )
8286 .size(Some(IconSize::XSmall.rems().into())),
8287 )
8288 })
8289 .into_any()
8290 .into()
8291 }
8292
8293 fn render_edit_prediction_line_popover(
8294 &self,
8295 label: impl Into<SharedString>,
8296 icon: Option<IconName>,
8297 window: &mut Window,
8298 cx: &App,
8299 ) -> Option<Stateful<Div>> {
8300 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8301
8302 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8303 let has_keybind = keybind.is_some();
8304
8305 let result = h_flex()
8306 .id("ep-line-popover")
8307 .py_0p5()
8308 .pl_1()
8309 .pr(padding_right)
8310 .gap_1()
8311 .rounded_md()
8312 .border_1()
8313 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8314 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8315 .shadow_sm()
8316 .when(!has_keybind, |el| {
8317 let status_colors = cx.theme().status();
8318
8319 el.bg(status_colors.error_background)
8320 .border_color(status_colors.error.opacity(0.6))
8321 .pl_2()
8322 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8323 .cursor_default()
8324 .hoverable_tooltip(move |_window, cx| {
8325 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8326 })
8327 })
8328 .children(keybind)
8329 .child(
8330 Label::new(label)
8331 .size(LabelSize::Small)
8332 .when(!has_keybind, |el| {
8333 el.color(cx.theme().status().error.into()).strikethrough()
8334 }),
8335 )
8336 .when(!has_keybind, |el| {
8337 el.child(
8338 h_flex().ml_1().child(
8339 Icon::new(IconName::Info)
8340 .size(IconSize::Small)
8341 .color(cx.theme().status().error.into()),
8342 ),
8343 )
8344 })
8345 .when_some(icon, |element, icon| {
8346 element.child(
8347 div()
8348 .mt(px(1.5))
8349 .child(Icon::new(icon).size(IconSize::Small)),
8350 )
8351 });
8352
8353 Some(result)
8354 }
8355
8356 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8357 let accent_color = cx.theme().colors().text_accent;
8358 let editor_bg_color = cx.theme().colors().editor_background;
8359 editor_bg_color.blend(accent_color.opacity(0.1))
8360 }
8361
8362 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8363 let accent_color = cx.theme().colors().text_accent;
8364 let editor_bg_color = cx.theme().colors().editor_background;
8365 editor_bg_color.blend(accent_color.opacity(0.6))
8366 }
8367
8368 fn render_edit_prediction_cursor_popover(
8369 &self,
8370 min_width: Pixels,
8371 max_width: Pixels,
8372 cursor_point: Point,
8373 style: &EditorStyle,
8374 accept_keystroke: Option<&gpui::Keystroke>,
8375 _window: &Window,
8376 cx: &mut Context<Editor>,
8377 ) -> Option<AnyElement> {
8378 let provider = self.edit_prediction_provider.as_ref()?;
8379
8380 if provider.provider.needs_terms_acceptance(cx) {
8381 return Some(
8382 h_flex()
8383 .min_w(min_width)
8384 .flex_1()
8385 .px_2()
8386 .py_1()
8387 .gap_3()
8388 .elevation_2(cx)
8389 .hover(|style| style.bg(cx.theme().colors().element_hover))
8390 .id("accept-terms")
8391 .cursor_pointer()
8392 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8393 .on_click(cx.listener(|this, _event, window, cx| {
8394 cx.stop_propagation();
8395 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8396 window.dispatch_action(
8397 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8398 cx,
8399 );
8400 }))
8401 .child(
8402 h_flex()
8403 .flex_1()
8404 .gap_2()
8405 .child(Icon::new(IconName::ZedPredict))
8406 .child(Label::new("Accept Terms of Service"))
8407 .child(div().w_full())
8408 .child(
8409 Icon::new(IconName::ArrowUpRight)
8410 .color(Color::Muted)
8411 .size(IconSize::Small),
8412 )
8413 .into_any_element(),
8414 )
8415 .into_any(),
8416 );
8417 }
8418
8419 let is_refreshing = provider.provider.is_refreshing(cx);
8420
8421 fn pending_completion_container() -> Div {
8422 h_flex()
8423 .h_full()
8424 .flex_1()
8425 .gap_2()
8426 .child(Icon::new(IconName::ZedPredict))
8427 }
8428
8429 let completion = match &self.active_inline_completion {
8430 Some(prediction) => {
8431 if !self.has_visible_completions_menu() {
8432 const RADIUS: Pixels = px(6.);
8433 const BORDER_WIDTH: Pixels = px(1.);
8434
8435 return Some(
8436 h_flex()
8437 .elevation_2(cx)
8438 .border(BORDER_WIDTH)
8439 .border_color(cx.theme().colors().border)
8440 .when(accept_keystroke.is_none(), |el| {
8441 el.border_color(cx.theme().status().error)
8442 })
8443 .rounded(RADIUS)
8444 .rounded_tl(px(0.))
8445 .overflow_hidden()
8446 .child(div().px_1p5().child(match &prediction.completion {
8447 InlineCompletion::Move { target, snapshot } => {
8448 use text::ToPoint as _;
8449 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8450 {
8451 Icon::new(IconName::ZedPredictDown)
8452 } else {
8453 Icon::new(IconName::ZedPredictUp)
8454 }
8455 }
8456 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8457 }))
8458 .child(
8459 h_flex()
8460 .gap_1()
8461 .py_1()
8462 .px_2()
8463 .rounded_r(RADIUS - BORDER_WIDTH)
8464 .border_l_1()
8465 .border_color(cx.theme().colors().border)
8466 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8467 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8468 el.child(
8469 Label::new("Hold")
8470 .size(LabelSize::Small)
8471 .when(accept_keystroke.is_none(), |el| {
8472 el.strikethrough()
8473 })
8474 .line_height_style(LineHeightStyle::UiLabel),
8475 )
8476 })
8477 .id("edit_prediction_cursor_popover_keybind")
8478 .when(accept_keystroke.is_none(), |el| {
8479 let status_colors = cx.theme().status();
8480
8481 el.bg(status_colors.error_background)
8482 .border_color(status_colors.error.opacity(0.6))
8483 .child(Icon::new(IconName::Info).color(Color::Error))
8484 .cursor_default()
8485 .hoverable_tooltip(move |_window, cx| {
8486 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8487 .into()
8488 })
8489 })
8490 .when_some(
8491 accept_keystroke.as_ref(),
8492 |el, accept_keystroke| {
8493 el.child(h_flex().children(ui::render_modifiers(
8494 &accept_keystroke.modifiers,
8495 PlatformStyle::platform(),
8496 Some(Color::Default),
8497 Some(IconSize::XSmall.rems().into()),
8498 false,
8499 )))
8500 },
8501 ),
8502 )
8503 .into_any(),
8504 );
8505 }
8506
8507 self.render_edit_prediction_cursor_popover_preview(
8508 prediction,
8509 cursor_point,
8510 style,
8511 cx,
8512 )?
8513 }
8514
8515 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8516 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8517 stale_completion,
8518 cursor_point,
8519 style,
8520 cx,
8521 )?,
8522
8523 None => {
8524 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8525 }
8526 },
8527
8528 None => pending_completion_container().child(Label::new("No Prediction")),
8529 };
8530
8531 let completion = if is_refreshing {
8532 completion
8533 .with_animation(
8534 "loading-completion",
8535 Animation::new(Duration::from_secs(2))
8536 .repeat()
8537 .with_easing(pulsating_between(0.4, 0.8)),
8538 |label, delta| label.opacity(delta),
8539 )
8540 .into_any_element()
8541 } else {
8542 completion.into_any_element()
8543 };
8544
8545 let has_completion = self.active_inline_completion.is_some();
8546
8547 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8548 Some(
8549 h_flex()
8550 .min_w(min_width)
8551 .max_w(max_width)
8552 .flex_1()
8553 .elevation_2(cx)
8554 .border_color(cx.theme().colors().border)
8555 .child(
8556 div()
8557 .flex_1()
8558 .py_1()
8559 .px_2()
8560 .overflow_hidden()
8561 .child(completion),
8562 )
8563 .when_some(accept_keystroke, |el, accept_keystroke| {
8564 if !accept_keystroke.modifiers.modified() {
8565 return el;
8566 }
8567
8568 el.child(
8569 h_flex()
8570 .h_full()
8571 .border_l_1()
8572 .rounded_r_lg()
8573 .border_color(cx.theme().colors().border)
8574 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8575 .gap_1()
8576 .py_1()
8577 .px_2()
8578 .child(
8579 h_flex()
8580 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8581 .when(is_platform_style_mac, |parent| parent.gap_1())
8582 .child(h_flex().children(ui::render_modifiers(
8583 &accept_keystroke.modifiers,
8584 PlatformStyle::platform(),
8585 Some(if !has_completion {
8586 Color::Muted
8587 } else {
8588 Color::Default
8589 }),
8590 None,
8591 false,
8592 ))),
8593 )
8594 .child(Label::new("Preview").into_any_element())
8595 .opacity(if has_completion { 1.0 } else { 0.4 }),
8596 )
8597 })
8598 .into_any(),
8599 )
8600 }
8601
8602 fn render_edit_prediction_cursor_popover_preview(
8603 &self,
8604 completion: &InlineCompletionState,
8605 cursor_point: Point,
8606 style: &EditorStyle,
8607 cx: &mut Context<Editor>,
8608 ) -> Option<Div> {
8609 use text::ToPoint as _;
8610
8611 fn render_relative_row_jump(
8612 prefix: impl Into<String>,
8613 current_row: u32,
8614 target_row: u32,
8615 ) -> Div {
8616 let (row_diff, arrow) = if target_row < current_row {
8617 (current_row - target_row, IconName::ArrowUp)
8618 } else {
8619 (target_row - current_row, IconName::ArrowDown)
8620 };
8621
8622 h_flex()
8623 .child(
8624 Label::new(format!("{}{}", prefix.into(), row_diff))
8625 .color(Color::Muted)
8626 .size(LabelSize::Small),
8627 )
8628 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8629 }
8630
8631 match &completion.completion {
8632 InlineCompletion::Move {
8633 target, snapshot, ..
8634 } => Some(
8635 h_flex()
8636 .px_2()
8637 .gap_2()
8638 .flex_1()
8639 .child(
8640 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8641 Icon::new(IconName::ZedPredictDown)
8642 } else {
8643 Icon::new(IconName::ZedPredictUp)
8644 },
8645 )
8646 .child(Label::new("Jump to Edit")),
8647 ),
8648
8649 InlineCompletion::Edit {
8650 edits,
8651 edit_preview,
8652 snapshot,
8653 display_mode: _,
8654 } => {
8655 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8656
8657 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8658 &snapshot,
8659 &edits,
8660 edit_preview.as_ref()?,
8661 true,
8662 cx,
8663 )
8664 .first_line_preview();
8665
8666 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8667 .with_default_highlights(&style.text, highlighted_edits.highlights);
8668
8669 let preview = h_flex()
8670 .gap_1()
8671 .min_w_16()
8672 .child(styled_text)
8673 .when(has_more_lines, |parent| parent.child("…"));
8674
8675 let left = if first_edit_row != cursor_point.row {
8676 render_relative_row_jump("", cursor_point.row, first_edit_row)
8677 .into_any_element()
8678 } else {
8679 Icon::new(IconName::ZedPredict).into_any_element()
8680 };
8681
8682 Some(
8683 h_flex()
8684 .h_full()
8685 .flex_1()
8686 .gap_2()
8687 .pr_1()
8688 .overflow_x_hidden()
8689 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8690 .child(left)
8691 .child(preview),
8692 )
8693 }
8694 }
8695 }
8696
8697 pub fn render_context_menu(
8698 &self,
8699 style: &EditorStyle,
8700 max_height_in_lines: u32,
8701 window: &mut Window,
8702 cx: &mut Context<Editor>,
8703 ) -> Option<AnyElement> {
8704 let menu = self.context_menu.borrow();
8705 let menu = menu.as_ref()?;
8706 if !menu.visible() {
8707 return None;
8708 };
8709 Some(menu.render(style, max_height_in_lines, window, cx))
8710 }
8711
8712 fn render_context_menu_aside(
8713 &mut self,
8714 max_size: Size<Pixels>,
8715 window: &mut Window,
8716 cx: &mut Context<Editor>,
8717 ) -> Option<AnyElement> {
8718 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8719 if menu.visible() {
8720 menu.render_aside(max_size, window, cx)
8721 } else {
8722 None
8723 }
8724 })
8725 }
8726
8727 fn hide_context_menu(
8728 &mut self,
8729 window: &mut Window,
8730 cx: &mut Context<Self>,
8731 ) -> Option<CodeContextMenu> {
8732 cx.notify();
8733 self.completion_tasks.clear();
8734 let context_menu = self.context_menu.borrow_mut().take();
8735 self.stale_inline_completion_in_menu.take();
8736 self.update_visible_inline_completion(window, cx);
8737 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
8738 if let Some(completion_provider) = &self.completion_provider {
8739 completion_provider.selection_changed(None, window, cx);
8740 }
8741 }
8742 context_menu
8743 }
8744
8745 fn show_snippet_choices(
8746 &mut self,
8747 choices: &Vec<String>,
8748 selection: Range<Anchor>,
8749 cx: &mut Context<Self>,
8750 ) {
8751 if selection.start.buffer_id.is_none() {
8752 return;
8753 }
8754 let buffer_id = selection.start.buffer_id.unwrap();
8755 let buffer = self.buffer().read(cx).buffer(buffer_id);
8756 let id = post_inc(&mut self.next_completion_id);
8757 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8758
8759 if let Some(buffer) = buffer {
8760 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8761 CompletionsMenu::new_snippet_choices(
8762 id,
8763 true,
8764 choices,
8765 selection,
8766 buffer,
8767 snippet_sort_order,
8768 ),
8769 ));
8770 }
8771 }
8772
8773 pub fn insert_snippet(
8774 &mut self,
8775 insertion_ranges: &[Range<usize>],
8776 snippet: Snippet,
8777 window: &mut Window,
8778 cx: &mut Context<Self>,
8779 ) -> Result<()> {
8780 struct Tabstop<T> {
8781 is_end_tabstop: bool,
8782 ranges: Vec<Range<T>>,
8783 choices: Option<Vec<String>>,
8784 }
8785
8786 let tabstops = self.buffer.update(cx, |buffer, cx| {
8787 let snippet_text: Arc<str> = snippet.text.clone().into();
8788 let edits = insertion_ranges
8789 .iter()
8790 .cloned()
8791 .map(|range| (range, snippet_text.clone()));
8792 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8793
8794 let snapshot = &*buffer.read(cx);
8795 let snippet = &snippet;
8796 snippet
8797 .tabstops
8798 .iter()
8799 .map(|tabstop| {
8800 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8801 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8802 });
8803 let mut tabstop_ranges = tabstop
8804 .ranges
8805 .iter()
8806 .flat_map(|tabstop_range| {
8807 let mut delta = 0_isize;
8808 insertion_ranges.iter().map(move |insertion_range| {
8809 let insertion_start = insertion_range.start as isize + delta;
8810 delta +=
8811 snippet.text.len() as isize - insertion_range.len() as isize;
8812
8813 let start = ((insertion_start + tabstop_range.start) as usize)
8814 .min(snapshot.len());
8815 let end = ((insertion_start + tabstop_range.end) as usize)
8816 .min(snapshot.len());
8817 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8818 })
8819 })
8820 .collect::<Vec<_>>();
8821 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8822
8823 Tabstop {
8824 is_end_tabstop,
8825 ranges: tabstop_ranges,
8826 choices: tabstop.choices.clone(),
8827 }
8828 })
8829 .collect::<Vec<_>>()
8830 });
8831 if let Some(tabstop) = tabstops.first() {
8832 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8833 s.select_ranges(tabstop.ranges.iter().cloned());
8834 });
8835
8836 if let Some(choices) = &tabstop.choices {
8837 if let Some(selection) = tabstop.ranges.first() {
8838 self.show_snippet_choices(choices, selection.clone(), cx)
8839 }
8840 }
8841
8842 // If we're already at the last tabstop and it's at the end of the snippet,
8843 // we're done, we don't need to keep the state around.
8844 if !tabstop.is_end_tabstop {
8845 let choices = tabstops
8846 .iter()
8847 .map(|tabstop| tabstop.choices.clone())
8848 .collect();
8849
8850 let ranges = tabstops
8851 .into_iter()
8852 .map(|tabstop| tabstop.ranges)
8853 .collect::<Vec<_>>();
8854
8855 self.snippet_stack.push(SnippetState {
8856 active_index: 0,
8857 ranges,
8858 choices,
8859 });
8860 }
8861
8862 // Check whether the just-entered snippet ends with an auto-closable bracket.
8863 if self.autoclose_regions.is_empty() {
8864 let snapshot = self.buffer.read(cx).snapshot(cx);
8865 for selection in &mut self.selections.all::<Point>(cx) {
8866 let selection_head = selection.head();
8867 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8868 continue;
8869 };
8870
8871 let mut bracket_pair = None;
8872 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8873 let prev_chars = snapshot
8874 .reversed_chars_at(selection_head)
8875 .collect::<String>();
8876 for (pair, enabled) in scope.brackets() {
8877 if enabled
8878 && pair.close
8879 && prev_chars.starts_with(pair.start.as_str())
8880 && next_chars.starts_with(pair.end.as_str())
8881 {
8882 bracket_pair = Some(pair.clone());
8883 break;
8884 }
8885 }
8886 if let Some(pair) = bracket_pair {
8887 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8888 let autoclose_enabled =
8889 self.use_autoclose && snapshot_settings.use_autoclose;
8890 if autoclose_enabled {
8891 let start = snapshot.anchor_after(selection_head);
8892 let end = snapshot.anchor_after(selection_head);
8893 self.autoclose_regions.push(AutocloseRegion {
8894 selection_id: selection.id,
8895 range: start..end,
8896 pair,
8897 });
8898 }
8899 }
8900 }
8901 }
8902 }
8903 Ok(())
8904 }
8905
8906 pub fn move_to_next_snippet_tabstop(
8907 &mut self,
8908 window: &mut Window,
8909 cx: &mut Context<Self>,
8910 ) -> bool {
8911 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8912 }
8913
8914 pub fn move_to_prev_snippet_tabstop(
8915 &mut self,
8916 window: &mut Window,
8917 cx: &mut Context<Self>,
8918 ) -> bool {
8919 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8920 }
8921
8922 pub fn move_to_snippet_tabstop(
8923 &mut self,
8924 bias: Bias,
8925 window: &mut Window,
8926 cx: &mut Context<Self>,
8927 ) -> bool {
8928 if let Some(mut snippet) = self.snippet_stack.pop() {
8929 match bias {
8930 Bias::Left => {
8931 if snippet.active_index > 0 {
8932 snippet.active_index -= 1;
8933 } else {
8934 self.snippet_stack.push(snippet);
8935 return false;
8936 }
8937 }
8938 Bias::Right => {
8939 if snippet.active_index + 1 < snippet.ranges.len() {
8940 snippet.active_index += 1;
8941 } else {
8942 self.snippet_stack.push(snippet);
8943 return false;
8944 }
8945 }
8946 }
8947 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8948 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8949 s.select_anchor_ranges(current_ranges.iter().cloned())
8950 });
8951
8952 if let Some(choices) = &snippet.choices[snippet.active_index] {
8953 if let Some(selection) = current_ranges.first() {
8954 self.show_snippet_choices(&choices, selection.clone(), cx);
8955 }
8956 }
8957
8958 // If snippet state is not at the last tabstop, push it back on the stack
8959 if snippet.active_index + 1 < snippet.ranges.len() {
8960 self.snippet_stack.push(snippet);
8961 }
8962 return true;
8963 }
8964 }
8965
8966 false
8967 }
8968
8969 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8970 self.transact(window, cx, |this, window, cx| {
8971 this.select_all(&SelectAll, window, cx);
8972 this.insert("", window, cx);
8973 });
8974 }
8975
8976 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8977 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8978 self.transact(window, cx, |this, window, cx| {
8979 this.select_autoclose_pair(window, cx);
8980 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8981 if !this.linked_edit_ranges.is_empty() {
8982 let selections = this.selections.all::<MultiBufferPoint>(cx);
8983 let snapshot = this.buffer.read(cx).snapshot(cx);
8984
8985 for selection in selections.iter() {
8986 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8987 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8988 if selection_start.buffer_id != selection_end.buffer_id {
8989 continue;
8990 }
8991 if let Some(ranges) =
8992 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8993 {
8994 for (buffer, entries) in ranges {
8995 linked_ranges.entry(buffer).or_default().extend(entries);
8996 }
8997 }
8998 }
8999 }
9000
9001 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9002 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9003 for selection in &mut selections {
9004 if selection.is_empty() {
9005 let old_head = selection.head();
9006 let mut new_head =
9007 movement::left(&display_map, old_head.to_display_point(&display_map))
9008 .to_point(&display_map);
9009 if let Some((buffer, line_buffer_range)) = display_map
9010 .buffer_snapshot
9011 .buffer_line_for_row(MultiBufferRow(old_head.row))
9012 {
9013 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9014 let indent_len = match indent_size.kind {
9015 IndentKind::Space => {
9016 buffer.settings_at(line_buffer_range.start, cx).tab_size
9017 }
9018 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9019 };
9020 if old_head.column <= indent_size.len && old_head.column > 0 {
9021 let indent_len = indent_len.get();
9022 new_head = cmp::min(
9023 new_head,
9024 MultiBufferPoint::new(
9025 old_head.row,
9026 ((old_head.column - 1) / indent_len) * indent_len,
9027 ),
9028 );
9029 }
9030 }
9031
9032 selection.set_head(new_head, SelectionGoal::None);
9033 }
9034 }
9035
9036 this.signature_help_state.set_backspace_pressed(true);
9037 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9038 s.select(selections)
9039 });
9040 this.insert("", window, cx);
9041 let empty_str: Arc<str> = Arc::from("");
9042 for (buffer, edits) in linked_ranges {
9043 let snapshot = buffer.read(cx).snapshot();
9044 use text::ToPoint as TP;
9045
9046 let edits = edits
9047 .into_iter()
9048 .map(|range| {
9049 let end_point = TP::to_point(&range.end, &snapshot);
9050 let mut start_point = TP::to_point(&range.start, &snapshot);
9051
9052 if end_point == start_point {
9053 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9054 .saturating_sub(1);
9055 start_point =
9056 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9057 };
9058
9059 (start_point..end_point, empty_str.clone())
9060 })
9061 .sorted_by_key(|(range, _)| range.start)
9062 .collect::<Vec<_>>();
9063 buffer.update(cx, |this, cx| {
9064 this.edit(edits, None, cx);
9065 })
9066 }
9067 this.refresh_inline_completion(true, false, window, cx);
9068 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9069 });
9070 }
9071
9072 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9073 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9074 self.transact(window, cx, |this, window, cx| {
9075 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9076 s.move_with(|map, selection| {
9077 if selection.is_empty() {
9078 let cursor = movement::right(map, selection.head());
9079 selection.end = cursor;
9080 selection.reversed = true;
9081 selection.goal = SelectionGoal::None;
9082 }
9083 })
9084 });
9085 this.insert("", window, cx);
9086 this.refresh_inline_completion(true, false, window, cx);
9087 });
9088 }
9089
9090 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9091 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9092 if self.move_to_prev_snippet_tabstop(window, cx) {
9093 return;
9094 }
9095 self.outdent(&Outdent, window, cx);
9096 }
9097
9098 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9099 if self.move_to_next_snippet_tabstop(window, cx) {
9100 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9101 return;
9102 }
9103 if self.read_only(cx) {
9104 return;
9105 }
9106 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9107 let mut selections = self.selections.all_adjusted(cx);
9108 let buffer = self.buffer.read(cx);
9109 let snapshot = buffer.snapshot(cx);
9110 let rows_iter = selections.iter().map(|s| s.head().row);
9111 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9112
9113 let has_some_cursor_in_whitespace = selections
9114 .iter()
9115 .filter(|selection| selection.is_empty())
9116 .any(|selection| {
9117 let cursor = selection.head();
9118 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9119 cursor.column < current_indent.len
9120 });
9121
9122 let mut edits = Vec::new();
9123 let mut prev_edited_row = 0;
9124 let mut row_delta = 0;
9125 for selection in &mut selections {
9126 if selection.start.row != prev_edited_row {
9127 row_delta = 0;
9128 }
9129 prev_edited_row = selection.end.row;
9130
9131 // If the selection is non-empty, then increase the indentation of the selected lines.
9132 if !selection.is_empty() {
9133 row_delta =
9134 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9135 continue;
9136 }
9137
9138 let cursor = selection.head();
9139 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9140 if let Some(suggested_indent) =
9141 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9142 {
9143 // Don't do anything if already at suggested indent
9144 // and there is any other cursor which is not
9145 if has_some_cursor_in_whitespace
9146 && cursor.column == current_indent.len
9147 && current_indent.len == suggested_indent.len
9148 {
9149 continue;
9150 }
9151
9152 // Adjust line and move cursor to suggested indent
9153 // if cursor is not at suggested indent
9154 if cursor.column < suggested_indent.len
9155 && cursor.column <= current_indent.len
9156 && current_indent.len <= suggested_indent.len
9157 {
9158 selection.start = Point::new(cursor.row, suggested_indent.len);
9159 selection.end = selection.start;
9160 if row_delta == 0 {
9161 edits.extend(Buffer::edit_for_indent_size_adjustment(
9162 cursor.row,
9163 current_indent,
9164 suggested_indent,
9165 ));
9166 row_delta = suggested_indent.len - current_indent.len;
9167 }
9168 continue;
9169 }
9170
9171 // If current indent is more than suggested indent
9172 // only move cursor to current indent and skip indent
9173 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9174 selection.start = Point::new(cursor.row, current_indent.len);
9175 selection.end = selection.start;
9176 continue;
9177 }
9178 }
9179
9180 // Otherwise, insert a hard or soft tab.
9181 let settings = buffer.language_settings_at(cursor, cx);
9182 let tab_size = if settings.hard_tabs {
9183 IndentSize::tab()
9184 } else {
9185 let tab_size = settings.tab_size.get();
9186 let indent_remainder = snapshot
9187 .text_for_range(Point::new(cursor.row, 0)..cursor)
9188 .flat_map(str::chars)
9189 .fold(row_delta % tab_size, |counter: u32, c| {
9190 if c == '\t' {
9191 0
9192 } else {
9193 (counter + 1) % tab_size
9194 }
9195 });
9196
9197 let chars_to_next_tab_stop = tab_size - indent_remainder;
9198 IndentSize::spaces(chars_to_next_tab_stop)
9199 };
9200 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9201 selection.end = selection.start;
9202 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9203 row_delta += tab_size.len;
9204 }
9205
9206 self.transact(window, cx, |this, window, cx| {
9207 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9208 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9209 s.select(selections)
9210 });
9211 this.refresh_inline_completion(true, false, window, cx);
9212 });
9213 }
9214
9215 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9216 if self.read_only(cx) {
9217 return;
9218 }
9219 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9220 let mut selections = self.selections.all::<Point>(cx);
9221 let mut prev_edited_row = 0;
9222 let mut row_delta = 0;
9223 let mut edits = Vec::new();
9224 let buffer = self.buffer.read(cx);
9225 let snapshot = buffer.snapshot(cx);
9226 for selection in &mut selections {
9227 if selection.start.row != prev_edited_row {
9228 row_delta = 0;
9229 }
9230 prev_edited_row = selection.end.row;
9231
9232 row_delta =
9233 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9234 }
9235
9236 self.transact(window, cx, |this, window, cx| {
9237 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9238 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9239 s.select(selections)
9240 });
9241 });
9242 }
9243
9244 fn indent_selection(
9245 buffer: &MultiBuffer,
9246 snapshot: &MultiBufferSnapshot,
9247 selection: &mut Selection<Point>,
9248 edits: &mut Vec<(Range<Point>, String)>,
9249 delta_for_start_row: u32,
9250 cx: &App,
9251 ) -> u32 {
9252 let settings = buffer.language_settings_at(selection.start, cx);
9253 let tab_size = settings.tab_size.get();
9254 let indent_kind = if settings.hard_tabs {
9255 IndentKind::Tab
9256 } else {
9257 IndentKind::Space
9258 };
9259 let mut start_row = selection.start.row;
9260 let mut end_row = selection.end.row + 1;
9261
9262 // If a selection ends at the beginning of a line, don't indent
9263 // that last line.
9264 if selection.end.column == 0 && selection.end.row > selection.start.row {
9265 end_row -= 1;
9266 }
9267
9268 // Avoid re-indenting a row that has already been indented by a
9269 // previous selection, but still update this selection's column
9270 // to reflect that indentation.
9271 if delta_for_start_row > 0 {
9272 start_row += 1;
9273 selection.start.column += delta_for_start_row;
9274 if selection.end.row == selection.start.row {
9275 selection.end.column += delta_for_start_row;
9276 }
9277 }
9278
9279 let mut delta_for_end_row = 0;
9280 let has_multiple_rows = start_row + 1 != end_row;
9281 for row in start_row..end_row {
9282 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9283 let indent_delta = match (current_indent.kind, indent_kind) {
9284 (IndentKind::Space, IndentKind::Space) => {
9285 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9286 IndentSize::spaces(columns_to_next_tab_stop)
9287 }
9288 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9289 (_, IndentKind::Tab) => IndentSize::tab(),
9290 };
9291
9292 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9293 0
9294 } else {
9295 selection.start.column
9296 };
9297 let row_start = Point::new(row, start);
9298 edits.push((
9299 row_start..row_start,
9300 indent_delta.chars().collect::<String>(),
9301 ));
9302
9303 // Update this selection's endpoints to reflect the indentation.
9304 if row == selection.start.row {
9305 selection.start.column += indent_delta.len;
9306 }
9307 if row == selection.end.row {
9308 selection.end.column += indent_delta.len;
9309 delta_for_end_row = indent_delta.len;
9310 }
9311 }
9312
9313 if selection.start.row == selection.end.row {
9314 delta_for_start_row + delta_for_end_row
9315 } else {
9316 delta_for_end_row
9317 }
9318 }
9319
9320 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9321 if self.read_only(cx) {
9322 return;
9323 }
9324 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9325 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9326 let selections = self.selections.all::<Point>(cx);
9327 let mut deletion_ranges = Vec::new();
9328 let mut last_outdent = None;
9329 {
9330 let buffer = self.buffer.read(cx);
9331 let snapshot = buffer.snapshot(cx);
9332 for selection in &selections {
9333 let settings = buffer.language_settings_at(selection.start, cx);
9334 let tab_size = settings.tab_size.get();
9335 let mut rows = selection.spanned_rows(false, &display_map);
9336
9337 // Avoid re-outdenting a row that has already been outdented by a
9338 // previous selection.
9339 if let Some(last_row) = last_outdent {
9340 if last_row == rows.start {
9341 rows.start = rows.start.next_row();
9342 }
9343 }
9344 let has_multiple_rows = rows.len() > 1;
9345 for row in rows.iter_rows() {
9346 let indent_size = snapshot.indent_size_for_line(row);
9347 if indent_size.len > 0 {
9348 let deletion_len = match indent_size.kind {
9349 IndentKind::Space => {
9350 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9351 if columns_to_prev_tab_stop == 0 {
9352 tab_size
9353 } else {
9354 columns_to_prev_tab_stop
9355 }
9356 }
9357 IndentKind::Tab => 1,
9358 };
9359 let start = if has_multiple_rows
9360 || deletion_len > selection.start.column
9361 || indent_size.len < selection.start.column
9362 {
9363 0
9364 } else {
9365 selection.start.column - deletion_len
9366 };
9367 deletion_ranges.push(
9368 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9369 );
9370 last_outdent = Some(row);
9371 }
9372 }
9373 }
9374 }
9375
9376 self.transact(window, cx, |this, window, cx| {
9377 this.buffer.update(cx, |buffer, cx| {
9378 let empty_str: Arc<str> = Arc::default();
9379 buffer.edit(
9380 deletion_ranges
9381 .into_iter()
9382 .map(|range| (range, empty_str.clone())),
9383 None,
9384 cx,
9385 );
9386 });
9387 let selections = this.selections.all::<usize>(cx);
9388 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9389 s.select(selections)
9390 });
9391 });
9392 }
9393
9394 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9395 if self.read_only(cx) {
9396 return;
9397 }
9398 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9399 let selections = self
9400 .selections
9401 .all::<usize>(cx)
9402 .into_iter()
9403 .map(|s| s.range());
9404
9405 self.transact(window, cx, |this, window, cx| {
9406 this.buffer.update(cx, |buffer, cx| {
9407 buffer.autoindent_ranges(selections, cx);
9408 });
9409 let selections = this.selections.all::<usize>(cx);
9410 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9411 s.select(selections)
9412 });
9413 });
9414 }
9415
9416 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9417 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9418 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9419 let selections = self.selections.all::<Point>(cx);
9420
9421 let mut new_cursors = Vec::new();
9422 let mut edit_ranges = Vec::new();
9423 let mut selections = selections.iter().peekable();
9424 while let Some(selection) = selections.next() {
9425 let mut rows = selection.spanned_rows(false, &display_map);
9426 let goal_display_column = selection.head().to_display_point(&display_map).column();
9427
9428 // Accumulate contiguous regions of rows that we want to delete.
9429 while let Some(next_selection) = selections.peek() {
9430 let next_rows = next_selection.spanned_rows(false, &display_map);
9431 if next_rows.start <= rows.end {
9432 rows.end = next_rows.end;
9433 selections.next().unwrap();
9434 } else {
9435 break;
9436 }
9437 }
9438
9439 let buffer = &display_map.buffer_snapshot;
9440 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9441 let edit_end;
9442 let cursor_buffer_row;
9443 if buffer.max_point().row >= rows.end.0 {
9444 // If there's a line after the range, delete the \n from the end of the row range
9445 // and position the cursor on the next line.
9446 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9447 cursor_buffer_row = rows.end;
9448 } else {
9449 // If there isn't a line after the range, delete the \n from the line before the
9450 // start of the row range and position the cursor there.
9451 edit_start = edit_start.saturating_sub(1);
9452 edit_end = buffer.len();
9453 cursor_buffer_row = rows.start.previous_row();
9454 }
9455
9456 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9457 *cursor.column_mut() =
9458 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9459
9460 new_cursors.push((
9461 selection.id,
9462 buffer.anchor_after(cursor.to_point(&display_map)),
9463 ));
9464 edit_ranges.push(edit_start..edit_end);
9465 }
9466
9467 self.transact(window, cx, |this, window, cx| {
9468 let buffer = this.buffer.update(cx, |buffer, cx| {
9469 let empty_str: Arc<str> = Arc::default();
9470 buffer.edit(
9471 edit_ranges
9472 .into_iter()
9473 .map(|range| (range, empty_str.clone())),
9474 None,
9475 cx,
9476 );
9477 buffer.snapshot(cx)
9478 });
9479 let new_selections = new_cursors
9480 .into_iter()
9481 .map(|(id, cursor)| {
9482 let cursor = cursor.to_point(&buffer);
9483 Selection {
9484 id,
9485 start: cursor,
9486 end: cursor,
9487 reversed: false,
9488 goal: SelectionGoal::None,
9489 }
9490 })
9491 .collect();
9492
9493 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9494 s.select(new_selections);
9495 });
9496 });
9497 }
9498
9499 pub fn join_lines_impl(
9500 &mut self,
9501 insert_whitespace: bool,
9502 window: &mut Window,
9503 cx: &mut Context<Self>,
9504 ) {
9505 if self.read_only(cx) {
9506 return;
9507 }
9508 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9509 for selection in self.selections.all::<Point>(cx) {
9510 let start = MultiBufferRow(selection.start.row);
9511 // Treat single line selections as if they include the next line. Otherwise this action
9512 // would do nothing for single line selections individual cursors.
9513 let end = if selection.start.row == selection.end.row {
9514 MultiBufferRow(selection.start.row + 1)
9515 } else {
9516 MultiBufferRow(selection.end.row)
9517 };
9518
9519 if let Some(last_row_range) = row_ranges.last_mut() {
9520 if start <= last_row_range.end {
9521 last_row_range.end = end;
9522 continue;
9523 }
9524 }
9525 row_ranges.push(start..end);
9526 }
9527
9528 let snapshot = self.buffer.read(cx).snapshot(cx);
9529 let mut cursor_positions = Vec::new();
9530 for row_range in &row_ranges {
9531 let anchor = snapshot.anchor_before(Point::new(
9532 row_range.end.previous_row().0,
9533 snapshot.line_len(row_range.end.previous_row()),
9534 ));
9535 cursor_positions.push(anchor..anchor);
9536 }
9537
9538 self.transact(window, cx, |this, window, cx| {
9539 for row_range in row_ranges.into_iter().rev() {
9540 for row in row_range.iter_rows().rev() {
9541 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9542 let next_line_row = row.next_row();
9543 let indent = snapshot.indent_size_for_line(next_line_row);
9544 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9545
9546 let replace =
9547 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9548 " "
9549 } else {
9550 ""
9551 };
9552
9553 this.buffer.update(cx, |buffer, cx| {
9554 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9555 });
9556 }
9557 }
9558
9559 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9560 s.select_anchor_ranges(cursor_positions)
9561 });
9562 });
9563 }
9564
9565 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9566 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9567 self.join_lines_impl(true, window, cx);
9568 }
9569
9570 pub fn sort_lines_case_sensitive(
9571 &mut self,
9572 _: &SortLinesCaseSensitive,
9573 window: &mut Window,
9574 cx: &mut Context<Self>,
9575 ) {
9576 self.manipulate_lines(window, cx, |lines| lines.sort())
9577 }
9578
9579 pub fn sort_lines_case_insensitive(
9580 &mut self,
9581 _: &SortLinesCaseInsensitive,
9582 window: &mut Window,
9583 cx: &mut Context<Self>,
9584 ) {
9585 self.manipulate_lines(window, cx, |lines| {
9586 lines.sort_by_key(|line| line.to_lowercase())
9587 })
9588 }
9589
9590 pub fn unique_lines_case_insensitive(
9591 &mut self,
9592 _: &UniqueLinesCaseInsensitive,
9593 window: &mut Window,
9594 cx: &mut Context<Self>,
9595 ) {
9596 self.manipulate_lines(window, cx, |lines| {
9597 let mut seen = HashSet::default();
9598 lines.retain(|line| seen.insert(line.to_lowercase()));
9599 })
9600 }
9601
9602 pub fn unique_lines_case_sensitive(
9603 &mut self,
9604 _: &UniqueLinesCaseSensitive,
9605 window: &mut Window,
9606 cx: &mut Context<Self>,
9607 ) {
9608 self.manipulate_lines(window, cx, |lines| {
9609 let mut seen = HashSet::default();
9610 lines.retain(|line| seen.insert(*line));
9611 })
9612 }
9613
9614 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9615 let Some(project) = self.project.clone() else {
9616 return;
9617 };
9618 self.reload(project, window, cx)
9619 .detach_and_notify_err(window, cx);
9620 }
9621
9622 pub fn restore_file(
9623 &mut self,
9624 _: &::git::RestoreFile,
9625 window: &mut Window,
9626 cx: &mut Context<Self>,
9627 ) {
9628 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9629 let mut buffer_ids = HashSet::default();
9630 let snapshot = self.buffer().read(cx).snapshot(cx);
9631 for selection in self.selections.all::<usize>(cx) {
9632 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9633 }
9634
9635 let buffer = self.buffer().read(cx);
9636 let ranges = buffer_ids
9637 .into_iter()
9638 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9639 .collect::<Vec<_>>();
9640
9641 self.restore_hunks_in_ranges(ranges, window, cx);
9642 }
9643
9644 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9645 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9646 let selections = self
9647 .selections
9648 .all(cx)
9649 .into_iter()
9650 .map(|s| s.range())
9651 .collect();
9652 self.restore_hunks_in_ranges(selections, window, cx);
9653 }
9654
9655 pub fn restore_hunks_in_ranges(
9656 &mut self,
9657 ranges: Vec<Range<Point>>,
9658 window: &mut Window,
9659 cx: &mut Context<Editor>,
9660 ) {
9661 let mut revert_changes = HashMap::default();
9662 let chunk_by = self
9663 .snapshot(window, cx)
9664 .hunks_for_ranges(ranges)
9665 .into_iter()
9666 .chunk_by(|hunk| hunk.buffer_id);
9667 for (buffer_id, hunks) in &chunk_by {
9668 let hunks = hunks.collect::<Vec<_>>();
9669 for hunk in &hunks {
9670 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9671 }
9672 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9673 }
9674 drop(chunk_by);
9675 if !revert_changes.is_empty() {
9676 self.transact(window, cx, |editor, window, cx| {
9677 editor.restore(revert_changes, window, cx);
9678 });
9679 }
9680 }
9681
9682 pub fn open_active_item_in_terminal(
9683 &mut self,
9684 _: &OpenInTerminal,
9685 window: &mut Window,
9686 cx: &mut Context<Self>,
9687 ) {
9688 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9689 let project_path = buffer.read(cx).project_path(cx)?;
9690 let project = self.project.as_ref()?.read(cx);
9691 let entry = project.entry_for_path(&project_path, cx)?;
9692 let parent = match &entry.canonical_path {
9693 Some(canonical_path) => canonical_path.to_path_buf(),
9694 None => project.absolute_path(&project_path, cx)?,
9695 }
9696 .parent()?
9697 .to_path_buf();
9698 Some(parent)
9699 }) {
9700 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9701 }
9702 }
9703
9704 fn set_breakpoint_context_menu(
9705 &mut self,
9706 display_row: DisplayRow,
9707 position: Option<Anchor>,
9708 clicked_point: gpui::Point<Pixels>,
9709 window: &mut Window,
9710 cx: &mut Context<Self>,
9711 ) {
9712 if !cx.has_flag::<DebuggerFeatureFlag>() {
9713 return;
9714 }
9715 let source = self
9716 .buffer
9717 .read(cx)
9718 .snapshot(cx)
9719 .anchor_before(Point::new(display_row.0, 0u32));
9720
9721 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9722
9723 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9724 self,
9725 source,
9726 clicked_point,
9727 context_menu,
9728 window,
9729 cx,
9730 );
9731 }
9732
9733 fn add_edit_breakpoint_block(
9734 &mut self,
9735 anchor: Anchor,
9736 breakpoint: &Breakpoint,
9737 edit_action: BreakpointPromptEditAction,
9738 window: &mut Window,
9739 cx: &mut Context<Self>,
9740 ) {
9741 let weak_editor = cx.weak_entity();
9742 let bp_prompt = cx.new(|cx| {
9743 BreakpointPromptEditor::new(
9744 weak_editor,
9745 anchor,
9746 breakpoint.clone(),
9747 edit_action,
9748 window,
9749 cx,
9750 )
9751 });
9752
9753 let height = bp_prompt.update(cx, |this, cx| {
9754 this.prompt
9755 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9756 });
9757 let cloned_prompt = bp_prompt.clone();
9758 let blocks = vec![BlockProperties {
9759 style: BlockStyle::Sticky,
9760 placement: BlockPlacement::Above(anchor),
9761 height: Some(height),
9762 render: Arc::new(move |cx| {
9763 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9764 cloned_prompt.clone().into_any_element()
9765 }),
9766 priority: 0,
9767 render_in_minimap: true,
9768 }];
9769
9770 let focus_handle = bp_prompt.focus_handle(cx);
9771 window.focus(&focus_handle);
9772
9773 let block_ids = self.insert_blocks(blocks, None, cx);
9774 bp_prompt.update(cx, |prompt, _| {
9775 prompt.add_block_ids(block_ids);
9776 });
9777 }
9778
9779 pub(crate) fn breakpoint_at_row(
9780 &self,
9781 row: u32,
9782 window: &mut Window,
9783 cx: &mut Context<Self>,
9784 ) -> Option<(Anchor, Breakpoint)> {
9785 let snapshot = self.snapshot(window, cx);
9786 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9787
9788 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9789 }
9790
9791 pub(crate) fn breakpoint_at_anchor(
9792 &self,
9793 breakpoint_position: Anchor,
9794 snapshot: &EditorSnapshot,
9795 cx: &mut Context<Self>,
9796 ) -> Option<(Anchor, Breakpoint)> {
9797 let project = self.project.clone()?;
9798
9799 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9800 snapshot
9801 .buffer_snapshot
9802 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9803 })?;
9804
9805 let enclosing_excerpt = breakpoint_position.excerpt_id;
9806 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
9807 let buffer_snapshot = buffer.read(cx).snapshot();
9808
9809 let row = buffer_snapshot
9810 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9811 .row;
9812
9813 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9814 let anchor_end = snapshot
9815 .buffer_snapshot
9816 .anchor_after(Point::new(row, line_len));
9817
9818 let bp = self
9819 .breakpoint_store
9820 .as_ref()?
9821 .read_with(cx, |breakpoint_store, cx| {
9822 breakpoint_store
9823 .breakpoints(
9824 &buffer,
9825 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9826 &buffer_snapshot,
9827 cx,
9828 )
9829 .next()
9830 .and_then(|(bp, _)| {
9831 let breakpoint_row = buffer_snapshot
9832 .summary_for_anchor::<text::PointUtf16>(&bp.position)
9833 .row;
9834
9835 if breakpoint_row == row {
9836 snapshot
9837 .buffer_snapshot
9838 .anchor_in_excerpt(enclosing_excerpt, bp.position)
9839 .map(|position| (position, bp.bp.clone()))
9840 } else {
9841 None
9842 }
9843 })
9844 });
9845 bp
9846 }
9847
9848 pub fn edit_log_breakpoint(
9849 &mut self,
9850 _: &EditLogBreakpoint,
9851 window: &mut Window,
9852 cx: &mut Context<Self>,
9853 ) {
9854 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9855 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9856 message: None,
9857 state: BreakpointState::Enabled,
9858 condition: None,
9859 hit_condition: None,
9860 });
9861
9862 self.add_edit_breakpoint_block(
9863 anchor,
9864 &breakpoint,
9865 BreakpointPromptEditAction::Log,
9866 window,
9867 cx,
9868 );
9869 }
9870 }
9871
9872 fn breakpoints_at_cursors(
9873 &self,
9874 window: &mut Window,
9875 cx: &mut Context<Self>,
9876 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9877 let snapshot = self.snapshot(window, cx);
9878 let cursors = self
9879 .selections
9880 .disjoint_anchors()
9881 .into_iter()
9882 .map(|selection| {
9883 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9884
9885 let breakpoint_position = self
9886 .breakpoint_at_row(cursor_position.row, window, cx)
9887 .map(|bp| bp.0)
9888 .unwrap_or_else(|| {
9889 snapshot
9890 .display_snapshot
9891 .buffer_snapshot
9892 .anchor_after(Point::new(cursor_position.row, 0))
9893 });
9894
9895 let breakpoint = self
9896 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9897 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9898
9899 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9900 })
9901 // 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.
9902 .collect::<HashMap<Anchor, _>>();
9903
9904 cursors.into_iter().collect()
9905 }
9906
9907 pub fn enable_breakpoint(
9908 &mut self,
9909 _: &crate::actions::EnableBreakpoint,
9910 window: &mut Window,
9911 cx: &mut Context<Self>,
9912 ) {
9913 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9914 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9915 continue;
9916 };
9917 self.edit_breakpoint_at_anchor(
9918 anchor,
9919 breakpoint,
9920 BreakpointEditAction::InvertState,
9921 cx,
9922 );
9923 }
9924 }
9925
9926 pub fn disable_breakpoint(
9927 &mut self,
9928 _: &crate::actions::DisableBreakpoint,
9929 window: &mut Window,
9930 cx: &mut Context<Self>,
9931 ) {
9932 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9933 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9934 continue;
9935 };
9936 self.edit_breakpoint_at_anchor(
9937 anchor,
9938 breakpoint,
9939 BreakpointEditAction::InvertState,
9940 cx,
9941 );
9942 }
9943 }
9944
9945 pub fn toggle_breakpoint(
9946 &mut self,
9947 _: &crate::actions::ToggleBreakpoint,
9948 window: &mut Window,
9949 cx: &mut Context<Self>,
9950 ) {
9951 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9952 if let Some(breakpoint) = breakpoint {
9953 self.edit_breakpoint_at_anchor(
9954 anchor,
9955 breakpoint,
9956 BreakpointEditAction::Toggle,
9957 cx,
9958 );
9959 } else {
9960 self.edit_breakpoint_at_anchor(
9961 anchor,
9962 Breakpoint::new_standard(),
9963 BreakpointEditAction::Toggle,
9964 cx,
9965 );
9966 }
9967 }
9968 }
9969
9970 pub fn edit_breakpoint_at_anchor(
9971 &mut self,
9972 breakpoint_position: Anchor,
9973 breakpoint: Breakpoint,
9974 edit_action: BreakpointEditAction,
9975 cx: &mut Context<Self>,
9976 ) {
9977 let Some(breakpoint_store) = &self.breakpoint_store else {
9978 return;
9979 };
9980
9981 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9982 if breakpoint_position == Anchor::min() {
9983 self.buffer()
9984 .read(cx)
9985 .excerpt_buffer_ids()
9986 .into_iter()
9987 .next()
9988 } else {
9989 None
9990 }
9991 }) else {
9992 return;
9993 };
9994
9995 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9996 return;
9997 };
9998
9999 breakpoint_store.update(cx, |breakpoint_store, cx| {
10000 breakpoint_store.toggle_breakpoint(
10001 buffer,
10002 BreakpointWithPosition {
10003 position: breakpoint_position.text_anchor,
10004 bp: breakpoint,
10005 },
10006 edit_action,
10007 cx,
10008 );
10009 });
10010
10011 cx.notify();
10012 }
10013
10014 #[cfg(any(test, feature = "test-support"))]
10015 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10016 self.breakpoint_store.clone()
10017 }
10018
10019 pub fn prepare_restore_change(
10020 &self,
10021 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10022 hunk: &MultiBufferDiffHunk,
10023 cx: &mut App,
10024 ) -> Option<()> {
10025 if hunk.is_created_file() {
10026 return None;
10027 }
10028 let buffer = self.buffer.read(cx);
10029 let diff = buffer.diff_for(hunk.buffer_id)?;
10030 let buffer = buffer.buffer(hunk.buffer_id)?;
10031 let buffer = buffer.read(cx);
10032 let original_text = diff
10033 .read(cx)
10034 .base_text()
10035 .as_rope()
10036 .slice(hunk.diff_base_byte_range.clone());
10037 let buffer_snapshot = buffer.snapshot();
10038 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10039 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10040 probe
10041 .0
10042 .start
10043 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10044 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10045 }) {
10046 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10047 Some(())
10048 } else {
10049 None
10050 }
10051 }
10052
10053 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10054 self.manipulate_lines(window, cx, |lines| lines.reverse())
10055 }
10056
10057 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10058 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10059 }
10060
10061 fn manipulate_lines<Fn>(
10062 &mut self,
10063 window: &mut Window,
10064 cx: &mut Context<Self>,
10065 mut callback: Fn,
10066 ) where
10067 Fn: FnMut(&mut Vec<&str>),
10068 {
10069 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10070
10071 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10072 let buffer = self.buffer.read(cx).snapshot(cx);
10073
10074 let mut edits = Vec::new();
10075
10076 let selections = self.selections.all::<Point>(cx);
10077 let mut selections = selections.iter().peekable();
10078 let mut contiguous_row_selections = Vec::new();
10079 let mut new_selections = Vec::new();
10080 let mut added_lines = 0;
10081 let mut removed_lines = 0;
10082
10083 while let Some(selection) = selections.next() {
10084 let (start_row, end_row) = consume_contiguous_rows(
10085 &mut contiguous_row_selections,
10086 selection,
10087 &display_map,
10088 &mut selections,
10089 );
10090
10091 let start_point = Point::new(start_row.0, 0);
10092 let end_point = Point::new(
10093 end_row.previous_row().0,
10094 buffer.line_len(end_row.previous_row()),
10095 );
10096 let text = buffer
10097 .text_for_range(start_point..end_point)
10098 .collect::<String>();
10099
10100 let mut lines = text.split('\n').collect_vec();
10101
10102 let lines_before = lines.len();
10103 callback(&mut lines);
10104 let lines_after = lines.len();
10105
10106 edits.push((start_point..end_point, lines.join("\n")));
10107
10108 // Selections must change based on added and removed line count
10109 let start_row =
10110 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10111 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10112 new_selections.push(Selection {
10113 id: selection.id,
10114 start: start_row,
10115 end: end_row,
10116 goal: SelectionGoal::None,
10117 reversed: selection.reversed,
10118 });
10119
10120 if lines_after > lines_before {
10121 added_lines += lines_after - lines_before;
10122 } else if lines_before > lines_after {
10123 removed_lines += lines_before - lines_after;
10124 }
10125 }
10126
10127 self.transact(window, cx, |this, window, cx| {
10128 let buffer = this.buffer.update(cx, |buffer, cx| {
10129 buffer.edit(edits, None, cx);
10130 buffer.snapshot(cx)
10131 });
10132
10133 // Recalculate offsets on newly edited buffer
10134 let new_selections = new_selections
10135 .iter()
10136 .map(|s| {
10137 let start_point = Point::new(s.start.0, 0);
10138 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10139 Selection {
10140 id: s.id,
10141 start: buffer.point_to_offset(start_point),
10142 end: buffer.point_to_offset(end_point),
10143 goal: s.goal,
10144 reversed: s.reversed,
10145 }
10146 })
10147 .collect();
10148
10149 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10150 s.select(new_selections);
10151 });
10152
10153 this.request_autoscroll(Autoscroll::fit(), cx);
10154 });
10155 }
10156
10157 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10158 self.manipulate_text(window, cx, |text| {
10159 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10160 if has_upper_case_characters {
10161 text.to_lowercase()
10162 } else {
10163 text.to_uppercase()
10164 }
10165 })
10166 }
10167
10168 pub fn convert_to_upper_case(
10169 &mut self,
10170 _: &ConvertToUpperCase,
10171 window: &mut Window,
10172 cx: &mut Context<Self>,
10173 ) {
10174 self.manipulate_text(window, cx, |text| text.to_uppercase())
10175 }
10176
10177 pub fn convert_to_lower_case(
10178 &mut self,
10179 _: &ConvertToLowerCase,
10180 window: &mut Window,
10181 cx: &mut Context<Self>,
10182 ) {
10183 self.manipulate_text(window, cx, |text| text.to_lowercase())
10184 }
10185
10186 pub fn convert_to_title_case(
10187 &mut self,
10188 _: &ConvertToTitleCase,
10189 window: &mut Window,
10190 cx: &mut Context<Self>,
10191 ) {
10192 self.manipulate_text(window, cx, |text| {
10193 text.split('\n')
10194 .map(|line| line.to_case(Case::Title))
10195 .join("\n")
10196 })
10197 }
10198
10199 pub fn convert_to_snake_case(
10200 &mut self,
10201 _: &ConvertToSnakeCase,
10202 window: &mut Window,
10203 cx: &mut Context<Self>,
10204 ) {
10205 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10206 }
10207
10208 pub fn convert_to_kebab_case(
10209 &mut self,
10210 _: &ConvertToKebabCase,
10211 window: &mut Window,
10212 cx: &mut Context<Self>,
10213 ) {
10214 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10215 }
10216
10217 pub fn convert_to_upper_camel_case(
10218 &mut self,
10219 _: &ConvertToUpperCamelCase,
10220 window: &mut Window,
10221 cx: &mut Context<Self>,
10222 ) {
10223 self.manipulate_text(window, cx, |text| {
10224 text.split('\n')
10225 .map(|line| line.to_case(Case::UpperCamel))
10226 .join("\n")
10227 })
10228 }
10229
10230 pub fn convert_to_lower_camel_case(
10231 &mut self,
10232 _: &ConvertToLowerCamelCase,
10233 window: &mut Window,
10234 cx: &mut Context<Self>,
10235 ) {
10236 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10237 }
10238
10239 pub fn convert_to_opposite_case(
10240 &mut self,
10241 _: &ConvertToOppositeCase,
10242 window: &mut Window,
10243 cx: &mut Context<Self>,
10244 ) {
10245 self.manipulate_text(window, cx, |text| {
10246 text.chars()
10247 .fold(String::with_capacity(text.len()), |mut t, c| {
10248 if c.is_uppercase() {
10249 t.extend(c.to_lowercase());
10250 } else {
10251 t.extend(c.to_uppercase());
10252 }
10253 t
10254 })
10255 })
10256 }
10257
10258 pub fn convert_to_rot13(
10259 &mut self,
10260 _: &ConvertToRot13,
10261 window: &mut Window,
10262 cx: &mut Context<Self>,
10263 ) {
10264 self.manipulate_text(window, cx, |text| {
10265 text.chars()
10266 .map(|c| match c {
10267 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10268 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10269 _ => c,
10270 })
10271 .collect()
10272 })
10273 }
10274
10275 pub fn convert_to_rot47(
10276 &mut self,
10277 _: &ConvertToRot47,
10278 window: &mut Window,
10279 cx: &mut Context<Self>,
10280 ) {
10281 self.manipulate_text(window, cx, |text| {
10282 text.chars()
10283 .map(|c| {
10284 let code_point = c as u32;
10285 if code_point >= 33 && code_point <= 126 {
10286 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10287 }
10288 c
10289 })
10290 .collect()
10291 })
10292 }
10293
10294 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10295 where
10296 Fn: FnMut(&str) -> String,
10297 {
10298 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10299 let buffer = self.buffer.read(cx).snapshot(cx);
10300
10301 let mut new_selections = Vec::new();
10302 let mut edits = Vec::new();
10303 let mut selection_adjustment = 0i32;
10304
10305 for selection in self.selections.all::<usize>(cx) {
10306 let selection_is_empty = selection.is_empty();
10307
10308 let (start, end) = if selection_is_empty {
10309 let word_range = movement::surrounding_word(
10310 &display_map,
10311 selection.start.to_display_point(&display_map),
10312 );
10313 let start = word_range.start.to_offset(&display_map, Bias::Left);
10314 let end = word_range.end.to_offset(&display_map, Bias::Left);
10315 (start, end)
10316 } else {
10317 (selection.start, selection.end)
10318 };
10319
10320 let text = buffer.text_for_range(start..end).collect::<String>();
10321 let old_length = text.len() as i32;
10322 let text = callback(&text);
10323
10324 new_selections.push(Selection {
10325 start: (start as i32 - selection_adjustment) as usize,
10326 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10327 goal: SelectionGoal::None,
10328 ..selection
10329 });
10330
10331 selection_adjustment += old_length - text.len() as i32;
10332
10333 edits.push((start..end, text));
10334 }
10335
10336 self.transact(window, cx, |this, window, cx| {
10337 this.buffer.update(cx, |buffer, cx| {
10338 buffer.edit(edits, None, cx);
10339 });
10340
10341 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10342 s.select(new_selections);
10343 });
10344
10345 this.request_autoscroll(Autoscroll::fit(), cx);
10346 });
10347 }
10348
10349 pub fn duplicate(
10350 &mut self,
10351 upwards: bool,
10352 whole_lines: bool,
10353 window: &mut Window,
10354 cx: &mut Context<Self>,
10355 ) {
10356 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10357
10358 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10359 let buffer = &display_map.buffer_snapshot;
10360 let selections = self.selections.all::<Point>(cx);
10361
10362 let mut edits = Vec::new();
10363 let mut selections_iter = selections.iter().peekable();
10364 while let Some(selection) = selections_iter.next() {
10365 let mut rows = selection.spanned_rows(false, &display_map);
10366 // duplicate line-wise
10367 if whole_lines || selection.start == selection.end {
10368 // Avoid duplicating the same lines twice.
10369 while let Some(next_selection) = selections_iter.peek() {
10370 let next_rows = next_selection.spanned_rows(false, &display_map);
10371 if next_rows.start < rows.end {
10372 rows.end = next_rows.end;
10373 selections_iter.next().unwrap();
10374 } else {
10375 break;
10376 }
10377 }
10378
10379 // Copy the text from the selected row region and splice it either at the start
10380 // or end of the region.
10381 let start = Point::new(rows.start.0, 0);
10382 let end = Point::new(
10383 rows.end.previous_row().0,
10384 buffer.line_len(rows.end.previous_row()),
10385 );
10386 let text = buffer
10387 .text_for_range(start..end)
10388 .chain(Some("\n"))
10389 .collect::<String>();
10390 let insert_location = if upwards {
10391 Point::new(rows.end.0, 0)
10392 } else {
10393 start
10394 };
10395 edits.push((insert_location..insert_location, text));
10396 } else {
10397 // duplicate character-wise
10398 let start = selection.start;
10399 let end = selection.end;
10400 let text = buffer.text_for_range(start..end).collect::<String>();
10401 edits.push((selection.end..selection.end, text));
10402 }
10403 }
10404
10405 self.transact(window, cx, |this, _, cx| {
10406 this.buffer.update(cx, |buffer, cx| {
10407 buffer.edit(edits, None, cx);
10408 });
10409
10410 this.request_autoscroll(Autoscroll::fit(), cx);
10411 });
10412 }
10413
10414 pub fn duplicate_line_up(
10415 &mut self,
10416 _: &DuplicateLineUp,
10417 window: &mut Window,
10418 cx: &mut Context<Self>,
10419 ) {
10420 self.duplicate(true, true, window, cx);
10421 }
10422
10423 pub fn duplicate_line_down(
10424 &mut self,
10425 _: &DuplicateLineDown,
10426 window: &mut Window,
10427 cx: &mut Context<Self>,
10428 ) {
10429 self.duplicate(false, true, window, cx);
10430 }
10431
10432 pub fn duplicate_selection(
10433 &mut self,
10434 _: &DuplicateSelection,
10435 window: &mut Window,
10436 cx: &mut Context<Self>,
10437 ) {
10438 self.duplicate(false, false, window, cx);
10439 }
10440
10441 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10442 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10443
10444 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10445 let buffer = self.buffer.read(cx).snapshot(cx);
10446
10447 let mut edits = Vec::new();
10448 let mut unfold_ranges = Vec::new();
10449 let mut refold_creases = Vec::new();
10450
10451 let selections = self.selections.all::<Point>(cx);
10452 let mut selections = selections.iter().peekable();
10453 let mut contiguous_row_selections = Vec::new();
10454 let mut new_selections = Vec::new();
10455
10456 while let Some(selection) = selections.next() {
10457 // Find all the selections that span a contiguous row range
10458 let (start_row, end_row) = consume_contiguous_rows(
10459 &mut contiguous_row_selections,
10460 selection,
10461 &display_map,
10462 &mut selections,
10463 );
10464
10465 // Move the text spanned by the row range to be before the line preceding the row range
10466 if start_row.0 > 0 {
10467 let range_to_move = Point::new(
10468 start_row.previous_row().0,
10469 buffer.line_len(start_row.previous_row()),
10470 )
10471 ..Point::new(
10472 end_row.previous_row().0,
10473 buffer.line_len(end_row.previous_row()),
10474 );
10475 let insertion_point = display_map
10476 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10477 .0;
10478
10479 // Don't move lines across excerpts
10480 if buffer
10481 .excerpt_containing(insertion_point..range_to_move.end)
10482 .is_some()
10483 {
10484 let text = buffer
10485 .text_for_range(range_to_move.clone())
10486 .flat_map(|s| s.chars())
10487 .skip(1)
10488 .chain(['\n'])
10489 .collect::<String>();
10490
10491 edits.push((
10492 buffer.anchor_after(range_to_move.start)
10493 ..buffer.anchor_before(range_to_move.end),
10494 String::new(),
10495 ));
10496 let insertion_anchor = buffer.anchor_after(insertion_point);
10497 edits.push((insertion_anchor..insertion_anchor, text));
10498
10499 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10500
10501 // Move selections up
10502 new_selections.extend(contiguous_row_selections.drain(..).map(
10503 |mut selection| {
10504 selection.start.row -= row_delta;
10505 selection.end.row -= row_delta;
10506 selection
10507 },
10508 ));
10509
10510 // Move folds up
10511 unfold_ranges.push(range_to_move.clone());
10512 for fold in display_map.folds_in_range(
10513 buffer.anchor_before(range_to_move.start)
10514 ..buffer.anchor_after(range_to_move.end),
10515 ) {
10516 let mut start = fold.range.start.to_point(&buffer);
10517 let mut end = fold.range.end.to_point(&buffer);
10518 start.row -= row_delta;
10519 end.row -= row_delta;
10520 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10521 }
10522 }
10523 }
10524
10525 // If we didn't move line(s), preserve the existing selections
10526 new_selections.append(&mut contiguous_row_selections);
10527 }
10528
10529 self.transact(window, cx, |this, window, cx| {
10530 this.unfold_ranges(&unfold_ranges, true, true, cx);
10531 this.buffer.update(cx, |buffer, cx| {
10532 for (range, text) in edits {
10533 buffer.edit([(range, text)], None, cx);
10534 }
10535 });
10536 this.fold_creases(refold_creases, true, window, cx);
10537 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10538 s.select(new_selections);
10539 })
10540 });
10541 }
10542
10543 pub fn move_line_down(
10544 &mut self,
10545 _: &MoveLineDown,
10546 window: &mut Window,
10547 cx: &mut Context<Self>,
10548 ) {
10549 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10550
10551 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10552 let buffer = self.buffer.read(cx).snapshot(cx);
10553
10554 let mut edits = Vec::new();
10555 let mut unfold_ranges = Vec::new();
10556 let mut refold_creases = Vec::new();
10557
10558 let selections = self.selections.all::<Point>(cx);
10559 let mut selections = selections.iter().peekable();
10560 let mut contiguous_row_selections = Vec::new();
10561 let mut new_selections = Vec::new();
10562
10563 while let Some(selection) = selections.next() {
10564 // Find all the selections that span a contiguous row range
10565 let (start_row, end_row) = consume_contiguous_rows(
10566 &mut contiguous_row_selections,
10567 selection,
10568 &display_map,
10569 &mut selections,
10570 );
10571
10572 // Move the text spanned by the row range to be after the last line of the row range
10573 if end_row.0 <= buffer.max_point().row {
10574 let range_to_move =
10575 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10576 let insertion_point = display_map
10577 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10578 .0;
10579
10580 // Don't move lines across excerpt boundaries
10581 if buffer
10582 .excerpt_containing(range_to_move.start..insertion_point)
10583 .is_some()
10584 {
10585 let mut text = String::from("\n");
10586 text.extend(buffer.text_for_range(range_to_move.clone()));
10587 text.pop(); // Drop trailing newline
10588 edits.push((
10589 buffer.anchor_after(range_to_move.start)
10590 ..buffer.anchor_before(range_to_move.end),
10591 String::new(),
10592 ));
10593 let insertion_anchor = buffer.anchor_after(insertion_point);
10594 edits.push((insertion_anchor..insertion_anchor, text));
10595
10596 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10597
10598 // Move selections down
10599 new_selections.extend(contiguous_row_selections.drain(..).map(
10600 |mut selection| {
10601 selection.start.row += row_delta;
10602 selection.end.row += row_delta;
10603 selection
10604 },
10605 ));
10606
10607 // Move folds down
10608 unfold_ranges.push(range_to_move.clone());
10609 for fold in display_map.folds_in_range(
10610 buffer.anchor_before(range_to_move.start)
10611 ..buffer.anchor_after(range_to_move.end),
10612 ) {
10613 let mut start = fold.range.start.to_point(&buffer);
10614 let mut end = fold.range.end.to_point(&buffer);
10615 start.row += row_delta;
10616 end.row += row_delta;
10617 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10618 }
10619 }
10620 }
10621
10622 // If we didn't move line(s), preserve the existing selections
10623 new_selections.append(&mut contiguous_row_selections);
10624 }
10625
10626 self.transact(window, cx, |this, window, cx| {
10627 this.unfold_ranges(&unfold_ranges, true, true, cx);
10628 this.buffer.update(cx, |buffer, cx| {
10629 for (range, text) in edits {
10630 buffer.edit([(range, text)], None, cx);
10631 }
10632 });
10633 this.fold_creases(refold_creases, true, window, cx);
10634 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10635 s.select(new_selections)
10636 });
10637 });
10638 }
10639
10640 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10641 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10642 let text_layout_details = &self.text_layout_details(window);
10643 self.transact(window, cx, |this, window, cx| {
10644 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10645 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10646 s.move_with(|display_map, selection| {
10647 if !selection.is_empty() {
10648 return;
10649 }
10650
10651 let mut head = selection.head();
10652 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10653 if head.column() == display_map.line_len(head.row()) {
10654 transpose_offset = display_map
10655 .buffer_snapshot
10656 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10657 }
10658
10659 if transpose_offset == 0 {
10660 return;
10661 }
10662
10663 *head.column_mut() += 1;
10664 head = display_map.clip_point(head, Bias::Right);
10665 let goal = SelectionGoal::HorizontalPosition(
10666 display_map
10667 .x_for_display_point(head, text_layout_details)
10668 .into(),
10669 );
10670 selection.collapse_to(head, goal);
10671
10672 let transpose_start = display_map
10673 .buffer_snapshot
10674 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10675 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10676 let transpose_end = display_map
10677 .buffer_snapshot
10678 .clip_offset(transpose_offset + 1, Bias::Right);
10679 if let Some(ch) =
10680 display_map.buffer_snapshot.chars_at(transpose_start).next()
10681 {
10682 edits.push((transpose_start..transpose_offset, String::new()));
10683 edits.push((transpose_end..transpose_end, ch.to_string()));
10684 }
10685 }
10686 });
10687 edits
10688 });
10689 this.buffer
10690 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10691 let selections = this.selections.all::<usize>(cx);
10692 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10693 s.select(selections);
10694 });
10695 });
10696 }
10697
10698 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10699 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10700 self.rewrap_impl(RewrapOptions::default(), cx)
10701 }
10702
10703 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10704 let buffer = self.buffer.read(cx).snapshot(cx);
10705 let selections = self.selections.all::<Point>(cx);
10706 let mut selections = selections.iter().peekable();
10707
10708 let mut edits = Vec::new();
10709 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10710
10711 while let Some(selection) = selections.next() {
10712 let mut start_row = selection.start.row;
10713 let mut end_row = selection.end.row;
10714
10715 // Skip selections that overlap with a range that has already been rewrapped.
10716 let selection_range = start_row..end_row;
10717 if rewrapped_row_ranges
10718 .iter()
10719 .any(|range| range.overlaps(&selection_range))
10720 {
10721 continue;
10722 }
10723
10724 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10725
10726 // Since not all lines in the selection may be at the same indent
10727 // level, choose the indent size that is the most common between all
10728 // of the lines.
10729 //
10730 // If there is a tie, we use the deepest indent.
10731 let (indent_size, indent_end) = {
10732 let mut indent_size_occurrences = HashMap::default();
10733 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10734
10735 for row in start_row..=end_row {
10736 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10737 rows_by_indent_size.entry(indent).or_default().push(row);
10738 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10739 }
10740
10741 let indent_size = indent_size_occurrences
10742 .into_iter()
10743 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10744 .map(|(indent, _)| indent)
10745 .unwrap_or_default();
10746 let row = rows_by_indent_size[&indent_size][0];
10747 let indent_end = Point::new(row, indent_size.len);
10748
10749 (indent_size, indent_end)
10750 };
10751
10752 let mut line_prefix = indent_size.chars().collect::<String>();
10753
10754 let mut inside_comment = false;
10755 if let Some(comment_prefix) =
10756 buffer
10757 .language_scope_at(selection.head())
10758 .and_then(|language| {
10759 language
10760 .line_comment_prefixes()
10761 .iter()
10762 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10763 .cloned()
10764 })
10765 {
10766 line_prefix.push_str(&comment_prefix);
10767 inside_comment = true;
10768 }
10769
10770 let language_settings = buffer.language_settings_at(selection.head(), cx);
10771 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10772 RewrapBehavior::InComments => inside_comment,
10773 RewrapBehavior::InSelections => !selection.is_empty(),
10774 RewrapBehavior::Anywhere => true,
10775 };
10776
10777 let should_rewrap = options.override_language_settings
10778 || allow_rewrap_based_on_language
10779 || self.hard_wrap.is_some();
10780 if !should_rewrap {
10781 continue;
10782 }
10783
10784 if selection.is_empty() {
10785 'expand_upwards: while start_row > 0 {
10786 let prev_row = start_row - 1;
10787 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10788 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10789 {
10790 start_row = prev_row;
10791 } else {
10792 break 'expand_upwards;
10793 }
10794 }
10795
10796 'expand_downwards: while end_row < buffer.max_point().row {
10797 let next_row = end_row + 1;
10798 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10799 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10800 {
10801 end_row = next_row;
10802 } else {
10803 break 'expand_downwards;
10804 }
10805 }
10806 }
10807
10808 let start = Point::new(start_row, 0);
10809 let start_offset = start.to_offset(&buffer);
10810 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10811 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10812 let Some(lines_without_prefixes) = selection_text
10813 .lines()
10814 .map(|line| {
10815 line.strip_prefix(&line_prefix)
10816 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10817 .with_context(|| {
10818 format!("line did not start with prefix {line_prefix:?}: {line:?}")
10819 })
10820 })
10821 .collect::<Result<Vec<_>, _>>()
10822 .log_err()
10823 else {
10824 continue;
10825 };
10826
10827 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10828 buffer
10829 .language_settings_at(Point::new(start_row, 0), cx)
10830 .preferred_line_length as usize
10831 });
10832 let wrapped_text = wrap_with_prefix(
10833 line_prefix,
10834 lines_without_prefixes.join("\n"),
10835 wrap_column,
10836 tab_size,
10837 options.preserve_existing_whitespace,
10838 );
10839
10840 // TODO: should always use char-based diff while still supporting cursor behavior that
10841 // matches vim.
10842 let mut diff_options = DiffOptions::default();
10843 if options.override_language_settings {
10844 diff_options.max_word_diff_len = 0;
10845 diff_options.max_word_diff_line_count = 0;
10846 } else {
10847 diff_options.max_word_diff_len = usize::MAX;
10848 diff_options.max_word_diff_line_count = usize::MAX;
10849 }
10850
10851 for (old_range, new_text) in
10852 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10853 {
10854 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10855 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10856 edits.push((edit_start..edit_end, new_text));
10857 }
10858
10859 rewrapped_row_ranges.push(start_row..=end_row);
10860 }
10861
10862 self.buffer
10863 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10864 }
10865
10866 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10867 let mut text = String::new();
10868 let buffer = self.buffer.read(cx).snapshot(cx);
10869 let mut selections = self.selections.all::<Point>(cx);
10870 let mut clipboard_selections = Vec::with_capacity(selections.len());
10871 {
10872 let max_point = buffer.max_point();
10873 let mut is_first = true;
10874 for selection in &mut selections {
10875 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10876 if is_entire_line {
10877 selection.start = Point::new(selection.start.row, 0);
10878 if !selection.is_empty() && selection.end.column == 0 {
10879 selection.end = cmp::min(max_point, selection.end);
10880 } else {
10881 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10882 }
10883 selection.goal = SelectionGoal::None;
10884 }
10885 if is_first {
10886 is_first = false;
10887 } else {
10888 text += "\n";
10889 }
10890 let mut len = 0;
10891 for chunk in buffer.text_for_range(selection.start..selection.end) {
10892 text.push_str(chunk);
10893 len += chunk.len();
10894 }
10895 clipboard_selections.push(ClipboardSelection {
10896 len,
10897 is_entire_line,
10898 first_line_indent: buffer
10899 .indent_size_for_line(MultiBufferRow(selection.start.row))
10900 .len,
10901 });
10902 }
10903 }
10904
10905 self.transact(window, cx, |this, window, cx| {
10906 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10907 s.select(selections);
10908 });
10909 this.insert("", window, cx);
10910 });
10911 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10912 }
10913
10914 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10915 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10916 let item = self.cut_common(window, cx);
10917 cx.write_to_clipboard(item);
10918 }
10919
10920 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10921 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10922 self.change_selections(None, window, cx, |s| {
10923 s.move_with(|snapshot, sel| {
10924 if sel.is_empty() {
10925 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10926 }
10927 });
10928 });
10929 let item = self.cut_common(window, cx);
10930 cx.set_global(KillRing(item))
10931 }
10932
10933 pub fn kill_ring_yank(
10934 &mut self,
10935 _: &KillRingYank,
10936 window: &mut Window,
10937 cx: &mut Context<Self>,
10938 ) {
10939 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10940 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10941 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10942 (kill_ring.text().to_string(), kill_ring.metadata_json())
10943 } else {
10944 return;
10945 }
10946 } else {
10947 return;
10948 };
10949 self.do_paste(&text, metadata, false, window, cx);
10950 }
10951
10952 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10953 self.do_copy(true, cx);
10954 }
10955
10956 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10957 self.do_copy(false, cx);
10958 }
10959
10960 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10961 let selections = self.selections.all::<Point>(cx);
10962 let buffer = self.buffer.read(cx).read(cx);
10963 let mut text = String::new();
10964
10965 let mut clipboard_selections = Vec::with_capacity(selections.len());
10966 {
10967 let max_point = buffer.max_point();
10968 let mut is_first = true;
10969 for selection in &selections {
10970 let mut start = selection.start;
10971 let mut end = selection.end;
10972 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10973 if is_entire_line {
10974 start = Point::new(start.row, 0);
10975 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10976 }
10977
10978 let mut trimmed_selections = Vec::new();
10979 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10980 let row = MultiBufferRow(start.row);
10981 let first_indent = buffer.indent_size_for_line(row);
10982 if first_indent.len == 0 || start.column > first_indent.len {
10983 trimmed_selections.push(start..end);
10984 } else {
10985 trimmed_selections.push(
10986 Point::new(row.0, first_indent.len)
10987 ..Point::new(row.0, buffer.line_len(row)),
10988 );
10989 for row in start.row + 1..=end.row {
10990 let mut line_len = buffer.line_len(MultiBufferRow(row));
10991 if row == end.row {
10992 line_len = end.column;
10993 }
10994 if line_len == 0 {
10995 trimmed_selections
10996 .push(Point::new(row, 0)..Point::new(row, line_len));
10997 continue;
10998 }
10999 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11000 if row_indent_size.len >= first_indent.len {
11001 trimmed_selections.push(
11002 Point::new(row, first_indent.len)..Point::new(row, line_len),
11003 );
11004 } else {
11005 trimmed_selections.clear();
11006 trimmed_selections.push(start..end);
11007 break;
11008 }
11009 }
11010 }
11011 } else {
11012 trimmed_selections.push(start..end);
11013 }
11014
11015 for trimmed_range in trimmed_selections {
11016 if is_first {
11017 is_first = false;
11018 } else {
11019 text += "\n";
11020 }
11021 let mut len = 0;
11022 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11023 text.push_str(chunk);
11024 len += chunk.len();
11025 }
11026 clipboard_selections.push(ClipboardSelection {
11027 len,
11028 is_entire_line,
11029 first_line_indent: buffer
11030 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11031 .len,
11032 });
11033 }
11034 }
11035 }
11036
11037 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11038 text,
11039 clipboard_selections,
11040 ));
11041 }
11042
11043 pub fn do_paste(
11044 &mut self,
11045 text: &String,
11046 clipboard_selections: Option<Vec<ClipboardSelection>>,
11047 handle_entire_lines: bool,
11048 window: &mut Window,
11049 cx: &mut Context<Self>,
11050 ) {
11051 if self.read_only(cx) {
11052 return;
11053 }
11054
11055 let clipboard_text = Cow::Borrowed(text);
11056
11057 self.transact(window, cx, |this, window, cx| {
11058 if let Some(mut clipboard_selections) = clipboard_selections {
11059 let old_selections = this.selections.all::<usize>(cx);
11060 let all_selections_were_entire_line =
11061 clipboard_selections.iter().all(|s| s.is_entire_line);
11062 let first_selection_indent_column =
11063 clipboard_selections.first().map(|s| s.first_line_indent);
11064 if clipboard_selections.len() != old_selections.len() {
11065 clipboard_selections.drain(..);
11066 }
11067 let cursor_offset = this.selections.last::<usize>(cx).head();
11068 let mut auto_indent_on_paste = true;
11069
11070 this.buffer.update(cx, |buffer, cx| {
11071 let snapshot = buffer.read(cx);
11072 auto_indent_on_paste = snapshot
11073 .language_settings_at(cursor_offset, cx)
11074 .auto_indent_on_paste;
11075
11076 let mut start_offset = 0;
11077 let mut edits = Vec::new();
11078 let mut original_indent_columns = Vec::new();
11079 for (ix, selection) in old_selections.iter().enumerate() {
11080 let to_insert;
11081 let entire_line;
11082 let original_indent_column;
11083 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11084 let end_offset = start_offset + clipboard_selection.len;
11085 to_insert = &clipboard_text[start_offset..end_offset];
11086 entire_line = clipboard_selection.is_entire_line;
11087 start_offset = end_offset + 1;
11088 original_indent_column = Some(clipboard_selection.first_line_indent);
11089 } else {
11090 to_insert = clipboard_text.as_str();
11091 entire_line = all_selections_were_entire_line;
11092 original_indent_column = first_selection_indent_column
11093 }
11094
11095 // If the corresponding selection was empty when this slice of the
11096 // clipboard text was written, then the entire line containing the
11097 // selection was copied. If this selection is also currently empty,
11098 // then paste the line before the current line of the buffer.
11099 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11100 let column = selection.start.to_point(&snapshot).column as usize;
11101 let line_start = selection.start - column;
11102 line_start..line_start
11103 } else {
11104 selection.range()
11105 };
11106
11107 edits.push((range, to_insert));
11108 original_indent_columns.push(original_indent_column);
11109 }
11110 drop(snapshot);
11111
11112 buffer.edit(
11113 edits,
11114 if auto_indent_on_paste {
11115 Some(AutoindentMode::Block {
11116 original_indent_columns,
11117 })
11118 } else {
11119 None
11120 },
11121 cx,
11122 );
11123 });
11124
11125 let selections = this.selections.all::<usize>(cx);
11126 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11127 s.select(selections)
11128 });
11129 } else {
11130 this.insert(&clipboard_text, window, cx);
11131 }
11132 });
11133 }
11134
11135 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11136 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11137 if let Some(item) = cx.read_from_clipboard() {
11138 let entries = item.entries();
11139
11140 match entries.first() {
11141 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11142 // of all the pasted entries.
11143 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11144 .do_paste(
11145 clipboard_string.text(),
11146 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11147 true,
11148 window,
11149 cx,
11150 ),
11151 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11152 }
11153 }
11154 }
11155
11156 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11157 if self.read_only(cx) {
11158 return;
11159 }
11160
11161 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11162
11163 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11164 if let Some((selections, _)) =
11165 self.selection_history.transaction(transaction_id).cloned()
11166 {
11167 self.change_selections(None, window, cx, |s| {
11168 s.select_anchors(selections.to_vec());
11169 });
11170 } else {
11171 log::error!(
11172 "No entry in selection_history found for undo. \
11173 This may correspond to a bug where undo does not update the selection. \
11174 If this is occurring, please add details to \
11175 https://github.com/zed-industries/zed/issues/22692"
11176 );
11177 }
11178 self.request_autoscroll(Autoscroll::fit(), cx);
11179 self.unmark_text(window, cx);
11180 self.refresh_inline_completion(true, false, window, cx);
11181 cx.emit(EditorEvent::Edited { transaction_id });
11182 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11183 }
11184 }
11185
11186 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11187 if self.read_only(cx) {
11188 return;
11189 }
11190
11191 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11192
11193 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11194 if let Some((_, Some(selections))) =
11195 self.selection_history.transaction(transaction_id).cloned()
11196 {
11197 self.change_selections(None, window, cx, |s| {
11198 s.select_anchors(selections.to_vec());
11199 });
11200 } else {
11201 log::error!(
11202 "No entry in selection_history found for redo. \
11203 This may correspond to a bug where undo does not update the selection. \
11204 If this is occurring, please add details to \
11205 https://github.com/zed-industries/zed/issues/22692"
11206 );
11207 }
11208 self.request_autoscroll(Autoscroll::fit(), cx);
11209 self.unmark_text(window, cx);
11210 self.refresh_inline_completion(true, false, window, cx);
11211 cx.emit(EditorEvent::Edited { transaction_id });
11212 }
11213 }
11214
11215 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11216 self.buffer
11217 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11218 }
11219
11220 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11221 self.buffer
11222 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11223 }
11224
11225 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11226 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11227 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11228 s.move_with(|map, selection| {
11229 let cursor = if selection.is_empty() {
11230 movement::left(map, selection.start)
11231 } else {
11232 selection.start
11233 };
11234 selection.collapse_to(cursor, SelectionGoal::None);
11235 });
11236 })
11237 }
11238
11239 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11240 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11241 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11242 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11243 })
11244 }
11245
11246 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11247 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11248 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11249 s.move_with(|map, selection| {
11250 let cursor = if selection.is_empty() {
11251 movement::right(map, selection.end)
11252 } else {
11253 selection.end
11254 };
11255 selection.collapse_to(cursor, SelectionGoal::None)
11256 });
11257 })
11258 }
11259
11260 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11261 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11262 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11263 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11264 })
11265 }
11266
11267 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11268 if self.take_rename(true, window, cx).is_some() {
11269 return;
11270 }
11271
11272 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11273 cx.propagate();
11274 return;
11275 }
11276
11277 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11278
11279 let text_layout_details = &self.text_layout_details(window);
11280 let selection_count = self.selections.count();
11281 let first_selection = self.selections.first_anchor();
11282
11283 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11284 s.move_with(|map, selection| {
11285 if !selection.is_empty() {
11286 selection.goal = SelectionGoal::None;
11287 }
11288 let (cursor, goal) = movement::up(
11289 map,
11290 selection.start,
11291 selection.goal,
11292 false,
11293 text_layout_details,
11294 );
11295 selection.collapse_to(cursor, goal);
11296 });
11297 });
11298
11299 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11300 {
11301 cx.propagate();
11302 }
11303 }
11304
11305 pub fn move_up_by_lines(
11306 &mut self,
11307 action: &MoveUpByLines,
11308 window: &mut Window,
11309 cx: &mut Context<Self>,
11310 ) {
11311 if self.take_rename(true, window, cx).is_some() {
11312 return;
11313 }
11314
11315 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11316 cx.propagate();
11317 return;
11318 }
11319
11320 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11321
11322 let text_layout_details = &self.text_layout_details(window);
11323
11324 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11325 s.move_with(|map, selection| {
11326 if !selection.is_empty() {
11327 selection.goal = SelectionGoal::None;
11328 }
11329 let (cursor, goal) = movement::up_by_rows(
11330 map,
11331 selection.start,
11332 action.lines,
11333 selection.goal,
11334 false,
11335 text_layout_details,
11336 );
11337 selection.collapse_to(cursor, goal);
11338 });
11339 })
11340 }
11341
11342 pub fn move_down_by_lines(
11343 &mut self,
11344 action: &MoveDownByLines,
11345 window: &mut Window,
11346 cx: &mut Context<Self>,
11347 ) {
11348 if self.take_rename(true, window, cx).is_some() {
11349 return;
11350 }
11351
11352 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11353 cx.propagate();
11354 return;
11355 }
11356
11357 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11358
11359 let text_layout_details = &self.text_layout_details(window);
11360
11361 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11362 s.move_with(|map, selection| {
11363 if !selection.is_empty() {
11364 selection.goal = SelectionGoal::None;
11365 }
11366 let (cursor, goal) = movement::down_by_rows(
11367 map,
11368 selection.start,
11369 action.lines,
11370 selection.goal,
11371 false,
11372 text_layout_details,
11373 );
11374 selection.collapse_to(cursor, goal);
11375 });
11376 })
11377 }
11378
11379 pub fn select_down_by_lines(
11380 &mut self,
11381 action: &SelectDownByLines,
11382 window: &mut Window,
11383 cx: &mut Context<Self>,
11384 ) {
11385 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11386 let text_layout_details = &self.text_layout_details(window);
11387 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11388 s.move_heads_with(|map, head, goal| {
11389 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11390 })
11391 })
11392 }
11393
11394 pub fn select_up_by_lines(
11395 &mut self,
11396 action: &SelectUpByLines,
11397 window: &mut Window,
11398 cx: &mut Context<Self>,
11399 ) {
11400 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11401 let text_layout_details = &self.text_layout_details(window);
11402 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11403 s.move_heads_with(|map, head, goal| {
11404 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11405 })
11406 })
11407 }
11408
11409 pub fn select_page_up(
11410 &mut self,
11411 _: &SelectPageUp,
11412 window: &mut Window,
11413 cx: &mut Context<Self>,
11414 ) {
11415 let Some(row_count) = self.visible_row_count() else {
11416 return;
11417 };
11418
11419 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11420
11421 let text_layout_details = &self.text_layout_details(window);
11422
11423 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11424 s.move_heads_with(|map, head, goal| {
11425 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11426 })
11427 })
11428 }
11429
11430 pub fn move_page_up(
11431 &mut self,
11432 action: &MovePageUp,
11433 window: &mut Window,
11434 cx: &mut Context<Self>,
11435 ) {
11436 if self.take_rename(true, window, cx).is_some() {
11437 return;
11438 }
11439
11440 if self
11441 .context_menu
11442 .borrow_mut()
11443 .as_mut()
11444 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11445 .unwrap_or(false)
11446 {
11447 return;
11448 }
11449
11450 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11451 cx.propagate();
11452 return;
11453 }
11454
11455 let Some(row_count) = self.visible_row_count() else {
11456 return;
11457 };
11458
11459 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11460
11461 let autoscroll = if action.center_cursor {
11462 Autoscroll::center()
11463 } else {
11464 Autoscroll::fit()
11465 };
11466
11467 let text_layout_details = &self.text_layout_details(window);
11468
11469 self.change_selections(Some(autoscroll), window, cx, |s| {
11470 s.move_with(|map, selection| {
11471 if !selection.is_empty() {
11472 selection.goal = SelectionGoal::None;
11473 }
11474 let (cursor, goal) = movement::up_by_rows(
11475 map,
11476 selection.end,
11477 row_count,
11478 selection.goal,
11479 false,
11480 text_layout_details,
11481 );
11482 selection.collapse_to(cursor, goal);
11483 });
11484 });
11485 }
11486
11487 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11488 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11489 let text_layout_details = &self.text_layout_details(window);
11490 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11491 s.move_heads_with(|map, head, goal| {
11492 movement::up(map, head, goal, false, text_layout_details)
11493 })
11494 })
11495 }
11496
11497 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11498 self.take_rename(true, window, cx);
11499
11500 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11501 cx.propagate();
11502 return;
11503 }
11504
11505 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11506
11507 let text_layout_details = &self.text_layout_details(window);
11508 let selection_count = self.selections.count();
11509 let first_selection = self.selections.first_anchor();
11510
11511 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11512 s.move_with(|map, selection| {
11513 if !selection.is_empty() {
11514 selection.goal = SelectionGoal::None;
11515 }
11516 let (cursor, goal) = movement::down(
11517 map,
11518 selection.end,
11519 selection.goal,
11520 false,
11521 text_layout_details,
11522 );
11523 selection.collapse_to(cursor, goal);
11524 });
11525 });
11526
11527 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11528 {
11529 cx.propagate();
11530 }
11531 }
11532
11533 pub fn select_page_down(
11534 &mut self,
11535 _: &SelectPageDown,
11536 window: &mut Window,
11537 cx: &mut Context<Self>,
11538 ) {
11539 let Some(row_count) = self.visible_row_count() else {
11540 return;
11541 };
11542
11543 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11544
11545 let text_layout_details = &self.text_layout_details(window);
11546
11547 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11548 s.move_heads_with(|map, head, goal| {
11549 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11550 })
11551 })
11552 }
11553
11554 pub fn move_page_down(
11555 &mut self,
11556 action: &MovePageDown,
11557 window: &mut Window,
11558 cx: &mut Context<Self>,
11559 ) {
11560 if self.take_rename(true, window, cx).is_some() {
11561 return;
11562 }
11563
11564 if self
11565 .context_menu
11566 .borrow_mut()
11567 .as_mut()
11568 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
11569 .unwrap_or(false)
11570 {
11571 return;
11572 }
11573
11574 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11575 cx.propagate();
11576 return;
11577 }
11578
11579 let Some(row_count) = self.visible_row_count() else {
11580 return;
11581 };
11582
11583 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11584
11585 let autoscroll = if action.center_cursor {
11586 Autoscroll::center()
11587 } else {
11588 Autoscroll::fit()
11589 };
11590
11591 let text_layout_details = &self.text_layout_details(window);
11592 self.change_selections(Some(autoscroll), window, cx, |s| {
11593 s.move_with(|map, selection| {
11594 if !selection.is_empty() {
11595 selection.goal = SelectionGoal::None;
11596 }
11597 let (cursor, goal) = movement::down_by_rows(
11598 map,
11599 selection.end,
11600 row_count,
11601 selection.goal,
11602 false,
11603 text_layout_details,
11604 );
11605 selection.collapse_to(cursor, goal);
11606 });
11607 });
11608 }
11609
11610 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11611 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11612 let text_layout_details = &self.text_layout_details(window);
11613 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11614 s.move_heads_with(|map, head, goal| {
11615 movement::down(map, head, goal, false, text_layout_details)
11616 })
11617 });
11618 }
11619
11620 pub fn context_menu_first(
11621 &mut self,
11622 _: &ContextMenuFirst,
11623 window: &mut Window,
11624 cx: &mut Context<Self>,
11625 ) {
11626 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11627 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
11628 }
11629 }
11630
11631 pub fn context_menu_prev(
11632 &mut self,
11633 _: &ContextMenuPrevious,
11634 window: &mut Window,
11635 cx: &mut Context<Self>,
11636 ) {
11637 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11638 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
11639 }
11640 }
11641
11642 pub fn context_menu_next(
11643 &mut self,
11644 _: &ContextMenuNext,
11645 window: &mut Window,
11646 cx: &mut Context<Self>,
11647 ) {
11648 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11649 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
11650 }
11651 }
11652
11653 pub fn context_menu_last(
11654 &mut self,
11655 _: &ContextMenuLast,
11656 window: &mut Window,
11657 cx: &mut Context<Self>,
11658 ) {
11659 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11660 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
11661 }
11662 }
11663
11664 pub fn move_to_previous_word_start(
11665 &mut self,
11666 _: &MoveToPreviousWordStart,
11667 window: &mut Window,
11668 cx: &mut Context<Self>,
11669 ) {
11670 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11671 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11672 s.move_cursors_with(|map, head, _| {
11673 (
11674 movement::previous_word_start(map, head),
11675 SelectionGoal::None,
11676 )
11677 });
11678 })
11679 }
11680
11681 pub fn move_to_previous_subword_start(
11682 &mut self,
11683 _: &MoveToPreviousSubwordStart,
11684 window: &mut Window,
11685 cx: &mut Context<Self>,
11686 ) {
11687 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11688 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11689 s.move_cursors_with(|map, head, _| {
11690 (
11691 movement::previous_subword_start(map, head),
11692 SelectionGoal::None,
11693 )
11694 });
11695 })
11696 }
11697
11698 pub fn select_to_previous_word_start(
11699 &mut self,
11700 _: &SelectToPreviousWordStart,
11701 window: &mut Window,
11702 cx: &mut Context<Self>,
11703 ) {
11704 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11705 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11706 s.move_heads_with(|map, head, _| {
11707 (
11708 movement::previous_word_start(map, head),
11709 SelectionGoal::None,
11710 )
11711 });
11712 })
11713 }
11714
11715 pub fn select_to_previous_subword_start(
11716 &mut self,
11717 _: &SelectToPreviousSubwordStart,
11718 window: &mut Window,
11719 cx: &mut Context<Self>,
11720 ) {
11721 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11722 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11723 s.move_heads_with(|map, head, _| {
11724 (
11725 movement::previous_subword_start(map, head),
11726 SelectionGoal::None,
11727 )
11728 });
11729 })
11730 }
11731
11732 pub fn delete_to_previous_word_start(
11733 &mut self,
11734 action: &DeleteToPreviousWordStart,
11735 window: &mut Window,
11736 cx: &mut Context<Self>,
11737 ) {
11738 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11739 self.transact(window, cx, |this, window, cx| {
11740 this.select_autoclose_pair(window, cx);
11741 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11742 s.move_with(|map, selection| {
11743 if selection.is_empty() {
11744 let cursor = if action.ignore_newlines {
11745 movement::previous_word_start(map, selection.head())
11746 } else {
11747 movement::previous_word_start_or_newline(map, selection.head())
11748 };
11749 selection.set_head(cursor, SelectionGoal::None);
11750 }
11751 });
11752 });
11753 this.insert("", window, cx);
11754 });
11755 }
11756
11757 pub fn delete_to_previous_subword_start(
11758 &mut self,
11759 _: &DeleteToPreviousSubwordStart,
11760 window: &mut Window,
11761 cx: &mut Context<Self>,
11762 ) {
11763 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11764 self.transact(window, cx, |this, window, cx| {
11765 this.select_autoclose_pair(window, cx);
11766 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11767 s.move_with(|map, selection| {
11768 if selection.is_empty() {
11769 let cursor = movement::previous_subword_start(map, selection.head());
11770 selection.set_head(cursor, SelectionGoal::None);
11771 }
11772 });
11773 });
11774 this.insert("", window, cx);
11775 });
11776 }
11777
11778 pub fn move_to_next_word_end(
11779 &mut self,
11780 _: &MoveToNextWordEnd,
11781 window: &mut Window,
11782 cx: &mut Context<Self>,
11783 ) {
11784 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11785 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11786 s.move_cursors_with(|map, head, _| {
11787 (movement::next_word_end(map, head), SelectionGoal::None)
11788 });
11789 })
11790 }
11791
11792 pub fn move_to_next_subword_end(
11793 &mut self,
11794 _: &MoveToNextSubwordEnd,
11795 window: &mut Window,
11796 cx: &mut Context<Self>,
11797 ) {
11798 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11799 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11800 s.move_cursors_with(|map, head, _| {
11801 (movement::next_subword_end(map, head), SelectionGoal::None)
11802 });
11803 })
11804 }
11805
11806 pub fn select_to_next_word_end(
11807 &mut self,
11808 _: &SelectToNextWordEnd,
11809 window: &mut Window,
11810 cx: &mut Context<Self>,
11811 ) {
11812 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11813 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11814 s.move_heads_with(|map, head, _| {
11815 (movement::next_word_end(map, head), SelectionGoal::None)
11816 });
11817 })
11818 }
11819
11820 pub fn select_to_next_subword_end(
11821 &mut self,
11822 _: &SelectToNextSubwordEnd,
11823 window: &mut Window,
11824 cx: &mut Context<Self>,
11825 ) {
11826 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11827 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11828 s.move_heads_with(|map, head, _| {
11829 (movement::next_subword_end(map, head), SelectionGoal::None)
11830 });
11831 })
11832 }
11833
11834 pub fn delete_to_next_word_end(
11835 &mut self,
11836 action: &DeleteToNextWordEnd,
11837 window: &mut Window,
11838 cx: &mut Context<Self>,
11839 ) {
11840 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11841 self.transact(window, cx, |this, window, cx| {
11842 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11843 s.move_with(|map, selection| {
11844 if selection.is_empty() {
11845 let cursor = if action.ignore_newlines {
11846 movement::next_word_end(map, selection.head())
11847 } else {
11848 movement::next_word_end_or_newline(map, selection.head())
11849 };
11850 selection.set_head(cursor, SelectionGoal::None);
11851 }
11852 });
11853 });
11854 this.insert("", window, cx);
11855 });
11856 }
11857
11858 pub fn delete_to_next_subword_end(
11859 &mut self,
11860 _: &DeleteToNextSubwordEnd,
11861 window: &mut Window,
11862 cx: &mut Context<Self>,
11863 ) {
11864 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11865 self.transact(window, cx, |this, window, cx| {
11866 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11867 s.move_with(|map, selection| {
11868 if selection.is_empty() {
11869 let cursor = movement::next_subword_end(map, selection.head());
11870 selection.set_head(cursor, SelectionGoal::None);
11871 }
11872 });
11873 });
11874 this.insert("", window, cx);
11875 });
11876 }
11877
11878 pub fn move_to_beginning_of_line(
11879 &mut self,
11880 action: &MoveToBeginningOfLine,
11881 window: &mut Window,
11882 cx: &mut Context<Self>,
11883 ) {
11884 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11885 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11886 s.move_cursors_with(|map, head, _| {
11887 (
11888 movement::indented_line_beginning(
11889 map,
11890 head,
11891 action.stop_at_soft_wraps,
11892 action.stop_at_indent,
11893 ),
11894 SelectionGoal::None,
11895 )
11896 });
11897 })
11898 }
11899
11900 pub fn select_to_beginning_of_line(
11901 &mut self,
11902 action: &SelectToBeginningOfLine,
11903 window: &mut Window,
11904 cx: &mut Context<Self>,
11905 ) {
11906 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11907 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11908 s.move_heads_with(|map, head, _| {
11909 (
11910 movement::indented_line_beginning(
11911 map,
11912 head,
11913 action.stop_at_soft_wraps,
11914 action.stop_at_indent,
11915 ),
11916 SelectionGoal::None,
11917 )
11918 });
11919 });
11920 }
11921
11922 pub fn delete_to_beginning_of_line(
11923 &mut self,
11924 action: &DeleteToBeginningOfLine,
11925 window: &mut Window,
11926 cx: &mut Context<Self>,
11927 ) {
11928 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11929 self.transact(window, cx, |this, window, cx| {
11930 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11931 s.move_with(|_, selection| {
11932 selection.reversed = true;
11933 });
11934 });
11935
11936 this.select_to_beginning_of_line(
11937 &SelectToBeginningOfLine {
11938 stop_at_soft_wraps: false,
11939 stop_at_indent: action.stop_at_indent,
11940 },
11941 window,
11942 cx,
11943 );
11944 this.backspace(&Backspace, window, cx);
11945 });
11946 }
11947
11948 pub fn move_to_end_of_line(
11949 &mut self,
11950 action: &MoveToEndOfLine,
11951 window: &mut Window,
11952 cx: &mut Context<Self>,
11953 ) {
11954 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11955 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11956 s.move_cursors_with(|map, head, _| {
11957 (
11958 movement::line_end(map, head, action.stop_at_soft_wraps),
11959 SelectionGoal::None,
11960 )
11961 });
11962 })
11963 }
11964
11965 pub fn select_to_end_of_line(
11966 &mut self,
11967 action: &SelectToEndOfLine,
11968 window: &mut Window,
11969 cx: &mut Context<Self>,
11970 ) {
11971 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11972 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11973 s.move_heads_with(|map, head, _| {
11974 (
11975 movement::line_end(map, head, action.stop_at_soft_wraps),
11976 SelectionGoal::None,
11977 )
11978 });
11979 })
11980 }
11981
11982 pub fn delete_to_end_of_line(
11983 &mut self,
11984 _: &DeleteToEndOfLine,
11985 window: &mut Window,
11986 cx: &mut Context<Self>,
11987 ) {
11988 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11989 self.transact(window, cx, |this, window, cx| {
11990 this.select_to_end_of_line(
11991 &SelectToEndOfLine {
11992 stop_at_soft_wraps: false,
11993 },
11994 window,
11995 cx,
11996 );
11997 this.delete(&Delete, window, cx);
11998 });
11999 }
12000
12001 pub fn cut_to_end_of_line(
12002 &mut self,
12003 _: &CutToEndOfLine,
12004 window: &mut Window,
12005 cx: &mut Context<Self>,
12006 ) {
12007 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12008 self.transact(window, cx, |this, window, cx| {
12009 this.select_to_end_of_line(
12010 &SelectToEndOfLine {
12011 stop_at_soft_wraps: false,
12012 },
12013 window,
12014 cx,
12015 );
12016 this.cut(&Cut, window, cx);
12017 });
12018 }
12019
12020 pub fn move_to_start_of_paragraph(
12021 &mut self,
12022 _: &MoveToStartOfParagraph,
12023 window: &mut Window,
12024 cx: &mut Context<Self>,
12025 ) {
12026 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12027 cx.propagate();
12028 return;
12029 }
12030 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12031 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12032 s.move_with(|map, selection| {
12033 selection.collapse_to(
12034 movement::start_of_paragraph(map, selection.head(), 1),
12035 SelectionGoal::None,
12036 )
12037 });
12038 })
12039 }
12040
12041 pub fn move_to_end_of_paragraph(
12042 &mut self,
12043 _: &MoveToEndOfParagraph,
12044 window: &mut Window,
12045 cx: &mut Context<Self>,
12046 ) {
12047 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12048 cx.propagate();
12049 return;
12050 }
12051 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12052 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12053 s.move_with(|map, selection| {
12054 selection.collapse_to(
12055 movement::end_of_paragraph(map, selection.head(), 1),
12056 SelectionGoal::None,
12057 )
12058 });
12059 })
12060 }
12061
12062 pub fn select_to_start_of_paragraph(
12063 &mut self,
12064 _: &SelectToStartOfParagraph,
12065 window: &mut Window,
12066 cx: &mut Context<Self>,
12067 ) {
12068 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12069 cx.propagate();
12070 return;
12071 }
12072 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12073 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12074 s.move_heads_with(|map, head, _| {
12075 (
12076 movement::start_of_paragraph(map, head, 1),
12077 SelectionGoal::None,
12078 )
12079 });
12080 })
12081 }
12082
12083 pub fn select_to_end_of_paragraph(
12084 &mut self,
12085 _: &SelectToEndOfParagraph,
12086 window: &mut Window,
12087 cx: &mut Context<Self>,
12088 ) {
12089 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12090 cx.propagate();
12091 return;
12092 }
12093 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12094 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12095 s.move_heads_with(|map, head, _| {
12096 (
12097 movement::end_of_paragraph(map, head, 1),
12098 SelectionGoal::None,
12099 )
12100 });
12101 })
12102 }
12103
12104 pub fn move_to_start_of_excerpt(
12105 &mut self,
12106 _: &MoveToStartOfExcerpt,
12107 window: &mut Window,
12108 cx: &mut Context<Self>,
12109 ) {
12110 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12111 cx.propagate();
12112 return;
12113 }
12114 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12115 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12116 s.move_with(|map, selection| {
12117 selection.collapse_to(
12118 movement::start_of_excerpt(
12119 map,
12120 selection.head(),
12121 workspace::searchable::Direction::Prev,
12122 ),
12123 SelectionGoal::None,
12124 )
12125 });
12126 })
12127 }
12128
12129 pub fn move_to_start_of_next_excerpt(
12130 &mut self,
12131 _: &MoveToStartOfNextExcerpt,
12132 window: &mut Window,
12133 cx: &mut Context<Self>,
12134 ) {
12135 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12136 cx.propagate();
12137 return;
12138 }
12139
12140 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12141 s.move_with(|map, selection| {
12142 selection.collapse_to(
12143 movement::start_of_excerpt(
12144 map,
12145 selection.head(),
12146 workspace::searchable::Direction::Next,
12147 ),
12148 SelectionGoal::None,
12149 )
12150 });
12151 })
12152 }
12153
12154 pub fn move_to_end_of_excerpt(
12155 &mut self,
12156 _: &MoveToEndOfExcerpt,
12157 window: &mut Window,
12158 cx: &mut Context<Self>,
12159 ) {
12160 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12161 cx.propagate();
12162 return;
12163 }
12164 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12165 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12166 s.move_with(|map, selection| {
12167 selection.collapse_to(
12168 movement::end_of_excerpt(
12169 map,
12170 selection.head(),
12171 workspace::searchable::Direction::Next,
12172 ),
12173 SelectionGoal::None,
12174 )
12175 });
12176 })
12177 }
12178
12179 pub fn move_to_end_of_previous_excerpt(
12180 &mut self,
12181 _: &MoveToEndOfPreviousExcerpt,
12182 window: &mut Window,
12183 cx: &mut Context<Self>,
12184 ) {
12185 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12186 cx.propagate();
12187 return;
12188 }
12189 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12190 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12191 s.move_with(|map, selection| {
12192 selection.collapse_to(
12193 movement::end_of_excerpt(
12194 map,
12195 selection.head(),
12196 workspace::searchable::Direction::Prev,
12197 ),
12198 SelectionGoal::None,
12199 )
12200 });
12201 })
12202 }
12203
12204 pub fn select_to_start_of_excerpt(
12205 &mut self,
12206 _: &SelectToStartOfExcerpt,
12207 window: &mut Window,
12208 cx: &mut Context<Self>,
12209 ) {
12210 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12211 cx.propagate();
12212 return;
12213 }
12214 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12215 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12216 s.move_heads_with(|map, head, _| {
12217 (
12218 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12219 SelectionGoal::None,
12220 )
12221 });
12222 })
12223 }
12224
12225 pub fn select_to_start_of_next_excerpt(
12226 &mut self,
12227 _: &SelectToStartOfNextExcerpt,
12228 window: &mut Window,
12229 cx: &mut Context<Self>,
12230 ) {
12231 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12232 cx.propagate();
12233 return;
12234 }
12235 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12236 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12237 s.move_heads_with(|map, head, _| {
12238 (
12239 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12240 SelectionGoal::None,
12241 )
12242 });
12243 })
12244 }
12245
12246 pub fn select_to_end_of_excerpt(
12247 &mut self,
12248 _: &SelectToEndOfExcerpt,
12249 window: &mut Window,
12250 cx: &mut Context<Self>,
12251 ) {
12252 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12253 cx.propagate();
12254 return;
12255 }
12256 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12257 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12258 s.move_heads_with(|map, head, _| {
12259 (
12260 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12261 SelectionGoal::None,
12262 )
12263 });
12264 })
12265 }
12266
12267 pub fn select_to_end_of_previous_excerpt(
12268 &mut self,
12269 _: &SelectToEndOfPreviousExcerpt,
12270 window: &mut Window,
12271 cx: &mut Context<Self>,
12272 ) {
12273 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12274 cx.propagate();
12275 return;
12276 }
12277 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12278 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12279 s.move_heads_with(|map, head, _| {
12280 (
12281 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12282 SelectionGoal::None,
12283 )
12284 });
12285 })
12286 }
12287
12288 pub fn move_to_beginning(
12289 &mut self,
12290 _: &MoveToBeginning,
12291 window: &mut Window,
12292 cx: &mut Context<Self>,
12293 ) {
12294 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12295 cx.propagate();
12296 return;
12297 }
12298 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12299 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12300 s.select_ranges(vec![0..0]);
12301 });
12302 }
12303
12304 pub fn select_to_beginning(
12305 &mut self,
12306 _: &SelectToBeginning,
12307 window: &mut Window,
12308 cx: &mut Context<Self>,
12309 ) {
12310 let mut selection = self.selections.last::<Point>(cx);
12311 selection.set_head(Point::zero(), SelectionGoal::None);
12312 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12313 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12314 s.select(vec![selection]);
12315 });
12316 }
12317
12318 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12319 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12320 cx.propagate();
12321 return;
12322 }
12323 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12324 let cursor = self.buffer.read(cx).read(cx).len();
12325 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12326 s.select_ranges(vec![cursor..cursor])
12327 });
12328 }
12329
12330 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12331 self.nav_history = nav_history;
12332 }
12333
12334 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12335 self.nav_history.as_ref()
12336 }
12337
12338 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12339 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12340 }
12341
12342 fn push_to_nav_history(
12343 &mut self,
12344 cursor_anchor: Anchor,
12345 new_position: Option<Point>,
12346 is_deactivate: bool,
12347 cx: &mut Context<Self>,
12348 ) {
12349 if let Some(nav_history) = self.nav_history.as_mut() {
12350 let buffer = self.buffer.read(cx).read(cx);
12351 let cursor_position = cursor_anchor.to_point(&buffer);
12352 let scroll_state = self.scroll_manager.anchor();
12353 let scroll_top_row = scroll_state.top_row(&buffer);
12354 drop(buffer);
12355
12356 if let Some(new_position) = new_position {
12357 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12358 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12359 return;
12360 }
12361 }
12362
12363 nav_history.push(
12364 Some(NavigationData {
12365 cursor_anchor,
12366 cursor_position,
12367 scroll_anchor: scroll_state,
12368 scroll_top_row,
12369 }),
12370 cx,
12371 );
12372 cx.emit(EditorEvent::PushedToNavHistory {
12373 anchor: cursor_anchor,
12374 is_deactivate,
12375 })
12376 }
12377 }
12378
12379 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12380 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12381 let buffer = self.buffer.read(cx).snapshot(cx);
12382 let mut selection = self.selections.first::<usize>(cx);
12383 selection.set_head(buffer.len(), SelectionGoal::None);
12384 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12385 s.select(vec![selection]);
12386 });
12387 }
12388
12389 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12390 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12391 let end = self.buffer.read(cx).read(cx).len();
12392 self.change_selections(None, window, cx, |s| {
12393 s.select_ranges(vec![0..end]);
12394 });
12395 }
12396
12397 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12398 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12399 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12400 let mut selections = self.selections.all::<Point>(cx);
12401 let max_point = display_map.buffer_snapshot.max_point();
12402 for selection in &mut selections {
12403 let rows = selection.spanned_rows(true, &display_map);
12404 selection.start = Point::new(rows.start.0, 0);
12405 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12406 selection.reversed = false;
12407 }
12408 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12409 s.select(selections);
12410 });
12411 }
12412
12413 pub fn split_selection_into_lines(
12414 &mut self,
12415 _: &SplitSelectionIntoLines,
12416 window: &mut Window,
12417 cx: &mut Context<Self>,
12418 ) {
12419 let selections = self
12420 .selections
12421 .all::<Point>(cx)
12422 .into_iter()
12423 .map(|selection| selection.start..selection.end)
12424 .collect::<Vec<_>>();
12425 self.unfold_ranges(&selections, true, true, cx);
12426
12427 let mut new_selection_ranges = Vec::new();
12428 {
12429 let buffer = self.buffer.read(cx).read(cx);
12430 for selection in selections {
12431 for row in selection.start.row..selection.end.row {
12432 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12433 new_selection_ranges.push(cursor..cursor);
12434 }
12435
12436 let is_multiline_selection = selection.start.row != selection.end.row;
12437 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12438 // so this action feels more ergonomic when paired with other selection operations
12439 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12440 if !should_skip_last {
12441 new_selection_ranges.push(selection.end..selection.end);
12442 }
12443 }
12444 }
12445 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12446 s.select_ranges(new_selection_ranges);
12447 });
12448 }
12449
12450 pub fn add_selection_above(
12451 &mut self,
12452 _: &AddSelectionAbove,
12453 window: &mut Window,
12454 cx: &mut Context<Self>,
12455 ) {
12456 self.add_selection(true, window, cx);
12457 }
12458
12459 pub fn add_selection_below(
12460 &mut self,
12461 _: &AddSelectionBelow,
12462 window: &mut Window,
12463 cx: &mut Context<Self>,
12464 ) {
12465 self.add_selection(false, window, cx);
12466 }
12467
12468 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12469 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12470
12471 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12472 let mut selections = self.selections.all::<Point>(cx);
12473 let text_layout_details = self.text_layout_details(window);
12474 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12475 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12476 let range = oldest_selection.display_range(&display_map).sorted();
12477
12478 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12479 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12480 let positions = start_x.min(end_x)..start_x.max(end_x);
12481
12482 selections.clear();
12483 let mut stack = Vec::new();
12484 for row in range.start.row().0..=range.end.row().0 {
12485 if let Some(selection) = self.selections.build_columnar_selection(
12486 &display_map,
12487 DisplayRow(row),
12488 &positions,
12489 oldest_selection.reversed,
12490 &text_layout_details,
12491 ) {
12492 stack.push(selection.id);
12493 selections.push(selection);
12494 }
12495 }
12496
12497 if above {
12498 stack.reverse();
12499 }
12500
12501 AddSelectionsState { above, stack }
12502 });
12503
12504 let last_added_selection = *state.stack.last().unwrap();
12505 let mut new_selections = Vec::new();
12506 if above == state.above {
12507 let end_row = if above {
12508 DisplayRow(0)
12509 } else {
12510 display_map.max_point().row()
12511 };
12512
12513 'outer: for selection in selections {
12514 if selection.id == last_added_selection {
12515 let range = selection.display_range(&display_map).sorted();
12516 debug_assert_eq!(range.start.row(), range.end.row());
12517 let mut row = range.start.row();
12518 let positions =
12519 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12520 px(start)..px(end)
12521 } else {
12522 let start_x =
12523 display_map.x_for_display_point(range.start, &text_layout_details);
12524 let end_x =
12525 display_map.x_for_display_point(range.end, &text_layout_details);
12526 start_x.min(end_x)..start_x.max(end_x)
12527 };
12528
12529 while row != end_row {
12530 if above {
12531 row.0 -= 1;
12532 } else {
12533 row.0 += 1;
12534 }
12535
12536 if let Some(new_selection) = self.selections.build_columnar_selection(
12537 &display_map,
12538 row,
12539 &positions,
12540 selection.reversed,
12541 &text_layout_details,
12542 ) {
12543 state.stack.push(new_selection.id);
12544 if above {
12545 new_selections.push(new_selection);
12546 new_selections.push(selection);
12547 } else {
12548 new_selections.push(selection);
12549 new_selections.push(new_selection);
12550 }
12551
12552 continue 'outer;
12553 }
12554 }
12555 }
12556
12557 new_selections.push(selection);
12558 }
12559 } else {
12560 new_selections = selections;
12561 new_selections.retain(|s| s.id != last_added_selection);
12562 state.stack.pop();
12563 }
12564
12565 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12566 s.select(new_selections);
12567 });
12568 if state.stack.len() > 1 {
12569 self.add_selections_state = Some(state);
12570 }
12571 }
12572
12573 fn select_match_ranges(
12574 &mut self,
12575 range: Range<usize>,
12576 reversed: bool,
12577 replace_newest: bool,
12578 auto_scroll: Option<Autoscroll>,
12579 window: &mut Window,
12580 cx: &mut Context<Editor>,
12581 ) {
12582 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12583 self.change_selections(auto_scroll, window, cx, |s| {
12584 if replace_newest {
12585 s.delete(s.newest_anchor().id);
12586 }
12587 if reversed {
12588 s.insert_range(range.end..range.start);
12589 } else {
12590 s.insert_range(range);
12591 }
12592 });
12593 }
12594
12595 pub fn select_next_match_internal(
12596 &mut self,
12597 display_map: &DisplaySnapshot,
12598 replace_newest: bool,
12599 autoscroll: Option<Autoscroll>,
12600 window: &mut Window,
12601 cx: &mut Context<Self>,
12602 ) -> Result<()> {
12603 let buffer = &display_map.buffer_snapshot;
12604 let mut selections = self.selections.all::<usize>(cx);
12605 if let Some(mut select_next_state) = self.select_next_state.take() {
12606 let query = &select_next_state.query;
12607 if !select_next_state.done {
12608 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12609 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12610 let mut next_selected_range = None;
12611
12612 let bytes_after_last_selection =
12613 buffer.bytes_in_range(last_selection.end..buffer.len());
12614 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12615 let query_matches = query
12616 .stream_find_iter(bytes_after_last_selection)
12617 .map(|result| (last_selection.end, result))
12618 .chain(
12619 query
12620 .stream_find_iter(bytes_before_first_selection)
12621 .map(|result| (0, result)),
12622 );
12623
12624 for (start_offset, query_match) in query_matches {
12625 let query_match = query_match.unwrap(); // can only fail due to I/O
12626 let offset_range =
12627 start_offset + query_match.start()..start_offset + query_match.end();
12628 let display_range = offset_range.start.to_display_point(display_map)
12629 ..offset_range.end.to_display_point(display_map);
12630
12631 if !select_next_state.wordwise
12632 || (!movement::is_inside_word(display_map, display_range.start)
12633 && !movement::is_inside_word(display_map, display_range.end))
12634 {
12635 // TODO: This is n^2, because we might check all the selections
12636 if !selections
12637 .iter()
12638 .any(|selection| selection.range().overlaps(&offset_range))
12639 {
12640 next_selected_range = Some(offset_range);
12641 break;
12642 }
12643 }
12644 }
12645
12646 if let Some(next_selected_range) = next_selected_range {
12647 self.select_match_ranges(
12648 next_selected_range,
12649 last_selection.reversed,
12650 replace_newest,
12651 autoscroll,
12652 window,
12653 cx,
12654 );
12655 } else {
12656 select_next_state.done = true;
12657 }
12658 }
12659
12660 self.select_next_state = Some(select_next_state);
12661 } else {
12662 let mut only_carets = true;
12663 let mut same_text_selected = true;
12664 let mut selected_text = None;
12665
12666 let mut selections_iter = selections.iter().peekable();
12667 while let Some(selection) = selections_iter.next() {
12668 if selection.start != selection.end {
12669 only_carets = false;
12670 }
12671
12672 if same_text_selected {
12673 if selected_text.is_none() {
12674 selected_text =
12675 Some(buffer.text_for_range(selection.range()).collect::<String>());
12676 }
12677
12678 if let Some(next_selection) = selections_iter.peek() {
12679 if next_selection.range().len() == selection.range().len() {
12680 let next_selected_text = buffer
12681 .text_for_range(next_selection.range())
12682 .collect::<String>();
12683 if Some(next_selected_text) != selected_text {
12684 same_text_selected = false;
12685 selected_text = None;
12686 }
12687 } else {
12688 same_text_selected = false;
12689 selected_text = None;
12690 }
12691 }
12692 }
12693 }
12694
12695 if only_carets {
12696 for selection in &mut selections {
12697 let word_range = movement::surrounding_word(
12698 display_map,
12699 selection.start.to_display_point(display_map),
12700 );
12701 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12702 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12703 selection.goal = SelectionGoal::None;
12704 selection.reversed = false;
12705 self.select_match_ranges(
12706 selection.start..selection.end,
12707 selection.reversed,
12708 replace_newest,
12709 autoscroll,
12710 window,
12711 cx,
12712 );
12713 }
12714
12715 if selections.len() == 1 {
12716 let selection = selections
12717 .last()
12718 .expect("ensured that there's only one selection");
12719 let query = buffer
12720 .text_for_range(selection.start..selection.end)
12721 .collect::<String>();
12722 let is_empty = query.is_empty();
12723 let select_state = SelectNextState {
12724 query: AhoCorasick::new(&[query])?,
12725 wordwise: true,
12726 done: is_empty,
12727 };
12728 self.select_next_state = Some(select_state);
12729 } else {
12730 self.select_next_state = None;
12731 }
12732 } else if let Some(selected_text) = selected_text {
12733 self.select_next_state = Some(SelectNextState {
12734 query: AhoCorasick::new(&[selected_text])?,
12735 wordwise: false,
12736 done: false,
12737 });
12738 self.select_next_match_internal(
12739 display_map,
12740 replace_newest,
12741 autoscroll,
12742 window,
12743 cx,
12744 )?;
12745 }
12746 }
12747 Ok(())
12748 }
12749
12750 pub fn select_all_matches(
12751 &mut self,
12752 _action: &SelectAllMatches,
12753 window: &mut Window,
12754 cx: &mut Context<Self>,
12755 ) -> Result<()> {
12756 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12757
12758 self.push_to_selection_history();
12759 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12760
12761 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12762 let Some(select_next_state) = self.select_next_state.as_mut() else {
12763 return Ok(());
12764 };
12765 if select_next_state.done {
12766 return Ok(());
12767 }
12768
12769 let mut new_selections = Vec::new();
12770
12771 let reversed = self.selections.oldest::<usize>(cx).reversed;
12772 let buffer = &display_map.buffer_snapshot;
12773 let query_matches = select_next_state
12774 .query
12775 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12776
12777 for query_match in query_matches.into_iter() {
12778 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12779 let offset_range = if reversed {
12780 query_match.end()..query_match.start()
12781 } else {
12782 query_match.start()..query_match.end()
12783 };
12784 let display_range = offset_range.start.to_display_point(&display_map)
12785 ..offset_range.end.to_display_point(&display_map);
12786
12787 if !select_next_state.wordwise
12788 || (!movement::is_inside_word(&display_map, display_range.start)
12789 && !movement::is_inside_word(&display_map, display_range.end))
12790 {
12791 new_selections.push(offset_range.start..offset_range.end);
12792 }
12793 }
12794
12795 select_next_state.done = true;
12796 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12797 self.change_selections(None, window, cx, |selections| {
12798 selections.select_ranges(new_selections)
12799 });
12800
12801 Ok(())
12802 }
12803
12804 pub fn select_next(
12805 &mut self,
12806 action: &SelectNext,
12807 window: &mut Window,
12808 cx: &mut Context<Self>,
12809 ) -> Result<()> {
12810 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12811 self.push_to_selection_history();
12812 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12813 self.select_next_match_internal(
12814 &display_map,
12815 action.replace_newest,
12816 Some(Autoscroll::newest()),
12817 window,
12818 cx,
12819 )?;
12820 Ok(())
12821 }
12822
12823 pub fn select_previous(
12824 &mut self,
12825 action: &SelectPrevious,
12826 window: &mut Window,
12827 cx: &mut Context<Self>,
12828 ) -> Result<()> {
12829 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12830 self.push_to_selection_history();
12831 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12832 let buffer = &display_map.buffer_snapshot;
12833 let mut selections = self.selections.all::<usize>(cx);
12834 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12835 let query = &select_prev_state.query;
12836 if !select_prev_state.done {
12837 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12838 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12839 let mut next_selected_range = None;
12840 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12841 let bytes_before_last_selection =
12842 buffer.reversed_bytes_in_range(0..last_selection.start);
12843 let bytes_after_first_selection =
12844 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12845 let query_matches = query
12846 .stream_find_iter(bytes_before_last_selection)
12847 .map(|result| (last_selection.start, result))
12848 .chain(
12849 query
12850 .stream_find_iter(bytes_after_first_selection)
12851 .map(|result| (buffer.len(), result)),
12852 );
12853 for (end_offset, query_match) in query_matches {
12854 let query_match = query_match.unwrap(); // can only fail due to I/O
12855 let offset_range =
12856 end_offset - query_match.end()..end_offset - query_match.start();
12857 let display_range = offset_range.start.to_display_point(&display_map)
12858 ..offset_range.end.to_display_point(&display_map);
12859
12860 if !select_prev_state.wordwise
12861 || (!movement::is_inside_word(&display_map, display_range.start)
12862 && !movement::is_inside_word(&display_map, display_range.end))
12863 {
12864 next_selected_range = Some(offset_range);
12865 break;
12866 }
12867 }
12868
12869 if let Some(next_selected_range) = next_selected_range {
12870 self.select_match_ranges(
12871 next_selected_range,
12872 last_selection.reversed,
12873 action.replace_newest,
12874 Some(Autoscroll::newest()),
12875 window,
12876 cx,
12877 );
12878 } else {
12879 select_prev_state.done = true;
12880 }
12881 }
12882
12883 self.select_prev_state = Some(select_prev_state);
12884 } else {
12885 let mut only_carets = true;
12886 let mut same_text_selected = true;
12887 let mut selected_text = None;
12888
12889 let mut selections_iter = selections.iter().peekable();
12890 while let Some(selection) = selections_iter.next() {
12891 if selection.start != selection.end {
12892 only_carets = false;
12893 }
12894
12895 if same_text_selected {
12896 if selected_text.is_none() {
12897 selected_text =
12898 Some(buffer.text_for_range(selection.range()).collect::<String>());
12899 }
12900
12901 if let Some(next_selection) = selections_iter.peek() {
12902 if next_selection.range().len() == selection.range().len() {
12903 let next_selected_text = buffer
12904 .text_for_range(next_selection.range())
12905 .collect::<String>();
12906 if Some(next_selected_text) != selected_text {
12907 same_text_selected = false;
12908 selected_text = None;
12909 }
12910 } else {
12911 same_text_selected = false;
12912 selected_text = None;
12913 }
12914 }
12915 }
12916 }
12917
12918 if only_carets {
12919 for selection in &mut selections {
12920 let word_range = movement::surrounding_word(
12921 &display_map,
12922 selection.start.to_display_point(&display_map),
12923 );
12924 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12925 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12926 selection.goal = SelectionGoal::None;
12927 selection.reversed = false;
12928 self.select_match_ranges(
12929 selection.start..selection.end,
12930 selection.reversed,
12931 action.replace_newest,
12932 Some(Autoscroll::newest()),
12933 window,
12934 cx,
12935 );
12936 }
12937 if selections.len() == 1 {
12938 let selection = selections
12939 .last()
12940 .expect("ensured that there's only one selection");
12941 let query = buffer
12942 .text_for_range(selection.start..selection.end)
12943 .collect::<String>();
12944 let is_empty = query.is_empty();
12945 let select_state = SelectNextState {
12946 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12947 wordwise: true,
12948 done: is_empty,
12949 };
12950 self.select_prev_state = Some(select_state);
12951 } else {
12952 self.select_prev_state = None;
12953 }
12954 } else if let Some(selected_text) = selected_text {
12955 self.select_prev_state = Some(SelectNextState {
12956 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12957 wordwise: false,
12958 done: false,
12959 });
12960 self.select_previous(action, window, cx)?;
12961 }
12962 }
12963 Ok(())
12964 }
12965
12966 pub fn find_next_match(
12967 &mut self,
12968 _: &FindNextMatch,
12969 window: &mut Window,
12970 cx: &mut Context<Self>,
12971 ) -> Result<()> {
12972 let selections = self.selections.disjoint_anchors();
12973 match selections.first() {
12974 Some(first) if selections.len() >= 2 => {
12975 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12976 s.select_ranges([first.range()]);
12977 });
12978 }
12979 _ => self.select_next(
12980 &SelectNext {
12981 replace_newest: true,
12982 },
12983 window,
12984 cx,
12985 )?,
12986 }
12987 Ok(())
12988 }
12989
12990 pub fn find_previous_match(
12991 &mut self,
12992 _: &FindPreviousMatch,
12993 window: &mut Window,
12994 cx: &mut Context<Self>,
12995 ) -> Result<()> {
12996 let selections = self.selections.disjoint_anchors();
12997 match selections.last() {
12998 Some(last) if selections.len() >= 2 => {
12999 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13000 s.select_ranges([last.range()]);
13001 });
13002 }
13003 _ => self.select_previous(
13004 &SelectPrevious {
13005 replace_newest: true,
13006 },
13007 window,
13008 cx,
13009 )?,
13010 }
13011 Ok(())
13012 }
13013
13014 pub fn toggle_comments(
13015 &mut self,
13016 action: &ToggleComments,
13017 window: &mut Window,
13018 cx: &mut Context<Self>,
13019 ) {
13020 if self.read_only(cx) {
13021 return;
13022 }
13023 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13024 let text_layout_details = &self.text_layout_details(window);
13025 self.transact(window, cx, |this, window, cx| {
13026 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13027 let mut edits = Vec::new();
13028 let mut selection_edit_ranges = Vec::new();
13029 let mut last_toggled_row = None;
13030 let snapshot = this.buffer.read(cx).read(cx);
13031 let empty_str: Arc<str> = Arc::default();
13032 let mut suffixes_inserted = Vec::new();
13033 let ignore_indent = action.ignore_indent;
13034
13035 fn comment_prefix_range(
13036 snapshot: &MultiBufferSnapshot,
13037 row: MultiBufferRow,
13038 comment_prefix: &str,
13039 comment_prefix_whitespace: &str,
13040 ignore_indent: bool,
13041 ) -> Range<Point> {
13042 let indent_size = if ignore_indent {
13043 0
13044 } else {
13045 snapshot.indent_size_for_line(row).len
13046 };
13047
13048 let start = Point::new(row.0, indent_size);
13049
13050 let mut line_bytes = snapshot
13051 .bytes_in_range(start..snapshot.max_point())
13052 .flatten()
13053 .copied();
13054
13055 // If this line currently begins with the line comment prefix, then record
13056 // the range containing the prefix.
13057 if line_bytes
13058 .by_ref()
13059 .take(comment_prefix.len())
13060 .eq(comment_prefix.bytes())
13061 {
13062 // Include any whitespace that matches the comment prefix.
13063 let matching_whitespace_len = line_bytes
13064 .zip(comment_prefix_whitespace.bytes())
13065 .take_while(|(a, b)| a == b)
13066 .count() as u32;
13067 let end = Point::new(
13068 start.row,
13069 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13070 );
13071 start..end
13072 } else {
13073 start..start
13074 }
13075 }
13076
13077 fn comment_suffix_range(
13078 snapshot: &MultiBufferSnapshot,
13079 row: MultiBufferRow,
13080 comment_suffix: &str,
13081 comment_suffix_has_leading_space: bool,
13082 ) -> Range<Point> {
13083 let end = Point::new(row.0, snapshot.line_len(row));
13084 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13085
13086 let mut line_end_bytes = snapshot
13087 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13088 .flatten()
13089 .copied();
13090
13091 let leading_space_len = if suffix_start_column > 0
13092 && line_end_bytes.next() == Some(b' ')
13093 && comment_suffix_has_leading_space
13094 {
13095 1
13096 } else {
13097 0
13098 };
13099
13100 // If this line currently begins with the line comment prefix, then record
13101 // the range containing the prefix.
13102 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13103 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13104 start..end
13105 } else {
13106 end..end
13107 }
13108 }
13109
13110 // TODO: Handle selections that cross excerpts
13111 for selection in &mut selections {
13112 let start_column = snapshot
13113 .indent_size_for_line(MultiBufferRow(selection.start.row))
13114 .len;
13115 let language = if let Some(language) =
13116 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13117 {
13118 language
13119 } else {
13120 continue;
13121 };
13122
13123 selection_edit_ranges.clear();
13124
13125 // If multiple selections contain a given row, avoid processing that
13126 // row more than once.
13127 let mut start_row = MultiBufferRow(selection.start.row);
13128 if last_toggled_row == Some(start_row) {
13129 start_row = start_row.next_row();
13130 }
13131 let end_row =
13132 if selection.end.row > selection.start.row && selection.end.column == 0 {
13133 MultiBufferRow(selection.end.row - 1)
13134 } else {
13135 MultiBufferRow(selection.end.row)
13136 };
13137 last_toggled_row = Some(end_row);
13138
13139 if start_row > end_row {
13140 continue;
13141 }
13142
13143 // If the language has line comments, toggle those.
13144 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13145
13146 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13147 if ignore_indent {
13148 full_comment_prefixes = full_comment_prefixes
13149 .into_iter()
13150 .map(|s| Arc::from(s.trim_end()))
13151 .collect();
13152 }
13153
13154 if !full_comment_prefixes.is_empty() {
13155 let first_prefix = full_comment_prefixes
13156 .first()
13157 .expect("prefixes is non-empty");
13158 let prefix_trimmed_lengths = full_comment_prefixes
13159 .iter()
13160 .map(|p| p.trim_end_matches(' ').len())
13161 .collect::<SmallVec<[usize; 4]>>();
13162
13163 let mut all_selection_lines_are_comments = true;
13164
13165 for row in start_row.0..=end_row.0 {
13166 let row = MultiBufferRow(row);
13167 if start_row < end_row && snapshot.is_line_blank(row) {
13168 continue;
13169 }
13170
13171 let prefix_range = full_comment_prefixes
13172 .iter()
13173 .zip(prefix_trimmed_lengths.iter().copied())
13174 .map(|(prefix, trimmed_prefix_len)| {
13175 comment_prefix_range(
13176 snapshot.deref(),
13177 row,
13178 &prefix[..trimmed_prefix_len],
13179 &prefix[trimmed_prefix_len..],
13180 ignore_indent,
13181 )
13182 })
13183 .max_by_key(|range| range.end.column - range.start.column)
13184 .expect("prefixes is non-empty");
13185
13186 if prefix_range.is_empty() {
13187 all_selection_lines_are_comments = false;
13188 }
13189
13190 selection_edit_ranges.push(prefix_range);
13191 }
13192
13193 if all_selection_lines_are_comments {
13194 edits.extend(
13195 selection_edit_ranges
13196 .iter()
13197 .cloned()
13198 .map(|range| (range, empty_str.clone())),
13199 );
13200 } else {
13201 let min_column = selection_edit_ranges
13202 .iter()
13203 .map(|range| range.start.column)
13204 .min()
13205 .unwrap_or(0);
13206 edits.extend(selection_edit_ranges.iter().map(|range| {
13207 let position = Point::new(range.start.row, min_column);
13208 (position..position, first_prefix.clone())
13209 }));
13210 }
13211 } else if let Some((full_comment_prefix, comment_suffix)) =
13212 language.block_comment_delimiters()
13213 {
13214 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13215 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13216 let prefix_range = comment_prefix_range(
13217 snapshot.deref(),
13218 start_row,
13219 comment_prefix,
13220 comment_prefix_whitespace,
13221 ignore_indent,
13222 );
13223 let suffix_range = comment_suffix_range(
13224 snapshot.deref(),
13225 end_row,
13226 comment_suffix.trim_start_matches(' '),
13227 comment_suffix.starts_with(' '),
13228 );
13229
13230 if prefix_range.is_empty() || suffix_range.is_empty() {
13231 edits.push((
13232 prefix_range.start..prefix_range.start,
13233 full_comment_prefix.clone(),
13234 ));
13235 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13236 suffixes_inserted.push((end_row, comment_suffix.len()));
13237 } else {
13238 edits.push((prefix_range, empty_str.clone()));
13239 edits.push((suffix_range, empty_str.clone()));
13240 }
13241 } else {
13242 continue;
13243 }
13244 }
13245
13246 drop(snapshot);
13247 this.buffer.update(cx, |buffer, cx| {
13248 buffer.edit(edits, None, cx);
13249 });
13250
13251 // Adjust selections so that they end before any comment suffixes that
13252 // were inserted.
13253 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13254 let mut selections = this.selections.all::<Point>(cx);
13255 let snapshot = this.buffer.read(cx).read(cx);
13256 for selection in &mut selections {
13257 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13258 match row.cmp(&MultiBufferRow(selection.end.row)) {
13259 Ordering::Less => {
13260 suffixes_inserted.next();
13261 continue;
13262 }
13263 Ordering::Greater => break,
13264 Ordering::Equal => {
13265 if selection.end.column == snapshot.line_len(row) {
13266 if selection.is_empty() {
13267 selection.start.column -= suffix_len as u32;
13268 }
13269 selection.end.column -= suffix_len as u32;
13270 }
13271 break;
13272 }
13273 }
13274 }
13275 }
13276
13277 drop(snapshot);
13278 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13279 s.select(selections)
13280 });
13281
13282 let selections = this.selections.all::<Point>(cx);
13283 let selections_on_single_row = selections.windows(2).all(|selections| {
13284 selections[0].start.row == selections[1].start.row
13285 && selections[0].end.row == selections[1].end.row
13286 && selections[0].start.row == selections[0].end.row
13287 });
13288 let selections_selecting = selections
13289 .iter()
13290 .any(|selection| selection.start != selection.end);
13291 let advance_downwards = action.advance_downwards
13292 && selections_on_single_row
13293 && !selections_selecting
13294 && !matches!(this.mode, EditorMode::SingleLine { .. });
13295
13296 if advance_downwards {
13297 let snapshot = this.buffer.read(cx).snapshot(cx);
13298
13299 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13300 s.move_cursors_with(|display_snapshot, display_point, _| {
13301 let mut point = display_point.to_point(display_snapshot);
13302 point.row += 1;
13303 point = snapshot.clip_point(point, Bias::Left);
13304 let display_point = point.to_display_point(display_snapshot);
13305 let goal = SelectionGoal::HorizontalPosition(
13306 display_snapshot
13307 .x_for_display_point(display_point, text_layout_details)
13308 .into(),
13309 );
13310 (display_point, goal)
13311 })
13312 });
13313 }
13314 });
13315 }
13316
13317 pub fn select_enclosing_symbol(
13318 &mut self,
13319 _: &SelectEnclosingSymbol,
13320 window: &mut Window,
13321 cx: &mut Context<Self>,
13322 ) {
13323 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13324
13325 let buffer = self.buffer.read(cx).snapshot(cx);
13326 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13327
13328 fn update_selection(
13329 selection: &Selection<usize>,
13330 buffer_snap: &MultiBufferSnapshot,
13331 ) -> Option<Selection<usize>> {
13332 let cursor = selection.head();
13333 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13334 for symbol in symbols.iter().rev() {
13335 let start = symbol.range.start.to_offset(buffer_snap);
13336 let end = symbol.range.end.to_offset(buffer_snap);
13337 let new_range = start..end;
13338 if start < selection.start || end > selection.end {
13339 return Some(Selection {
13340 id: selection.id,
13341 start: new_range.start,
13342 end: new_range.end,
13343 goal: SelectionGoal::None,
13344 reversed: selection.reversed,
13345 });
13346 }
13347 }
13348 None
13349 }
13350
13351 let mut selected_larger_symbol = false;
13352 let new_selections = old_selections
13353 .iter()
13354 .map(|selection| match update_selection(selection, &buffer) {
13355 Some(new_selection) => {
13356 if new_selection.range() != selection.range() {
13357 selected_larger_symbol = true;
13358 }
13359 new_selection
13360 }
13361 None => selection.clone(),
13362 })
13363 .collect::<Vec<_>>();
13364
13365 if selected_larger_symbol {
13366 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13367 s.select(new_selections);
13368 });
13369 }
13370 }
13371
13372 pub fn select_larger_syntax_node(
13373 &mut self,
13374 _: &SelectLargerSyntaxNode,
13375 window: &mut Window,
13376 cx: &mut Context<Self>,
13377 ) {
13378 let Some(visible_row_count) = self.visible_row_count() else {
13379 return;
13380 };
13381 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13382 if old_selections.is_empty() {
13383 return;
13384 }
13385
13386 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13387
13388 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13389 let buffer = self.buffer.read(cx).snapshot(cx);
13390
13391 let mut selected_larger_node = false;
13392 let mut new_selections = old_selections
13393 .iter()
13394 .map(|selection| {
13395 let old_range = selection.start..selection.end;
13396
13397 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13398 // manually select word at selection
13399 if ["string_content", "inline"].contains(&node.kind()) {
13400 let word_range = {
13401 let display_point = buffer
13402 .offset_to_point(old_range.start)
13403 .to_display_point(&display_map);
13404 let Range { start, end } =
13405 movement::surrounding_word(&display_map, display_point);
13406 start.to_point(&display_map).to_offset(&buffer)
13407 ..end.to_point(&display_map).to_offset(&buffer)
13408 };
13409 // ignore if word is already selected
13410 if !word_range.is_empty() && old_range != word_range {
13411 let last_word_range = {
13412 let display_point = buffer
13413 .offset_to_point(old_range.end)
13414 .to_display_point(&display_map);
13415 let Range { start, end } =
13416 movement::surrounding_word(&display_map, display_point);
13417 start.to_point(&display_map).to_offset(&buffer)
13418 ..end.to_point(&display_map).to_offset(&buffer)
13419 };
13420 // only select word if start and end point belongs to same word
13421 if word_range == last_word_range {
13422 selected_larger_node = true;
13423 return Selection {
13424 id: selection.id,
13425 start: word_range.start,
13426 end: word_range.end,
13427 goal: SelectionGoal::None,
13428 reversed: selection.reversed,
13429 };
13430 }
13431 }
13432 }
13433 }
13434
13435 let mut new_range = old_range.clone();
13436 while let Some((_node, containing_range)) =
13437 buffer.syntax_ancestor(new_range.clone())
13438 {
13439 new_range = match containing_range {
13440 MultiOrSingleBufferOffsetRange::Single(_) => break,
13441 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13442 };
13443 if !display_map.intersects_fold(new_range.start)
13444 && !display_map.intersects_fold(new_range.end)
13445 {
13446 break;
13447 }
13448 }
13449
13450 selected_larger_node |= new_range != old_range;
13451 Selection {
13452 id: selection.id,
13453 start: new_range.start,
13454 end: new_range.end,
13455 goal: SelectionGoal::None,
13456 reversed: selection.reversed,
13457 }
13458 })
13459 .collect::<Vec<_>>();
13460
13461 if !selected_larger_node {
13462 return; // don't put this call in the history
13463 }
13464
13465 // scroll based on transformation done to the last selection created by the user
13466 let (last_old, last_new) = old_selections
13467 .last()
13468 .zip(new_selections.last().cloned())
13469 .expect("old_selections isn't empty");
13470
13471 // revert selection
13472 let is_selection_reversed = {
13473 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13474 new_selections.last_mut().expect("checked above").reversed =
13475 should_newest_selection_be_reversed;
13476 should_newest_selection_be_reversed
13477 };
13478
13479 if selected_larger_node {
13480 self.select_syntax_node_history.disable_clearing = true;
13481 self.change_selections(None, window, cx, |s| {
13482 s.select(new_selections.clone());
13483 });
13484 self.select_syntax_node_history.disable_clearing = false;
13485 }
13486
13487 let start_row = last_new.start.to_display_point(&display_map).row().0;
13488 let end_row = last_new.end.to_display_point(&display_map).row().0;
13489 let selection_height = end_row - start_row + 1;
13490 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13491
13492 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13493 let scroll_behavior = if fits_on_the_screen {
13494 self.request_autoscroll(Autoscroll::fit(), cx);
13495 SelectSyntaxNodeScrollBehavior::FitSelection
13496 } else if is_selection_reversed {
13497 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13498 SelectSyntaxNodeScrollBehavior::CursorTop
13499 } else {
13500 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13501 SelectSyntaxNodeScrollBehavior::CursorBottom
13502 };
13503
13504 self.select_syntax_node_history.push((
13505 old_selections,
13506 scroll_behavior,
13507 is_selection_reversed,
13508 ));
13509 }
13510
13511 pub fn select_smaller_syntax_node(
13512 &mut self,
13513 _: &SelectSmallerSyntaxNode,
13514 window: &mut Window,
13515 cx: &mut Context<Self>,
13516 ) {
13517 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13518
13519 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13520 self.select_syntax_node_history.pop()
13521 {
13522 if let Some(selection) = selections.last_mut() {
13523 selection.reversed = is_selection_reversed;
13524 }
13525
13526 self.select_syntax_node_history.disable_clearing = true;
13527 self.change_selections(None, window, cx, |s| {
13528 s.select(selections.to_vec());
13529 });
13530 self.select_syntax_node_history.disable_clearing = false;
13531
13532 match scroll_behavior {
13533 SelectSyntaxNodeScrollBehavior::CursorTop => {
13534 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13535 }
13536 SelectSyntaxNodeScrollBehavior::FitSelection => {
13537 self.request_autoscroll(Autoscroll::fit(), cx);
13538 }
13539 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13540 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13541 }
13542 }
13543 }
13544 }
13545
13546 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13547 if !EditorSettings::get_global(cx).gutter.runnables {
13548 self.clear_tasks();
13549 return Task::ready(());
13550 }
13551 let project = self.project.as_ref().map(Entity::downgrade);
13552 let task_sources = self.lsp_task_sources(cx);
13553 let multi_buffer = self.buffer.downgrade();
13554 cx.spawn_in(window, async move |editor, cx| {
13555 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13556 let Some(project) = project.and_then(|p| p.upgrade()) else {
13557 return;
13558 };
13559 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13560 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13561 }) else {
13562 return;
13563 };
13564
13565 let hide_runnables = project
13566 .update(cx, |project, cx| {
13567 // Do not display any test indicators in non-dev server remote projects.
13568 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13569 })
13570 .unwrap_or(true);
13571 if hide_runnables {
13572 return;
13573 }
13574 let new_rows =
13575 cx.background_spawn({
13576 let snapshot = display_snapshot.clone();
13577 async move {
13578 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13579 }
13580 })
13581 .await;
13582 let Ok(lsp_tasks) =
13583 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13584 else {
13585 return;
13586 };
13587 let lsp_tasks = lsp_tasks.await;
13588
13589 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13590 lsp_tasks
13591 .into_iter()
13592 .flat_map(|(kind, tasks)| {
13593 tasks.into_iter().filter_map(move |(location, task)| {
13594 Some((kind.clone(), location?, task))
13595 })
13596 })
13597 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13598 let buffer = location.target.buffer;
13599 let buffer_snapshot = buffer.read(cx).snapshot();
13600 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13601 |(excerpt_id, snapshot, _)| {
13602 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13603 display_snapshot
13604 .buffer_snapshot
13605 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13606 } else {
13607 None
13608 }
13609 },
13610 );
13611 if let Some(offset) = offset {
13612 let task_buffer_range =
13613 location.target.range.to_point(&buffer_snapshot);
13614 let context_buffer_range =
13615 task_buffer_range.to_offset(&buffer_snapshot);
13616 let context_range = BufferOffset(context_buffer_range.start)
13617 ..BufferOffset(context_buffer_range.end);
13618
13619 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13620 .or_insert_with(|| RunnableTasks {
13621 templates: Vec::new(),
13622 offset,
13623 column: task_buffer_range.start.column,
13624 extra_variables: HashMap::default(),
13625 context_range,
13626 })
13627 .templates
13628 .push((kind, task.original_task().clone()));
13629 }
13630
13631 acc
13632 })
13633 }) else {
13634 return;
13635 };
13636
13637 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
13638 buffer.language_settings(cx).tasks.prefer_lsp
13639 }) else {
13640 return;
13641 };
13642
13643 let rows = Self::runnable_rows(
13644 project,
13645 display_snapshot,
13646 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
13647 new_rows,
13648 cx.clone(),
13649 );
13650 editor
13651 .update(cx, |editor, _| {
13652 editor.clear_tasks();
13653 for (key, mut value) in rows {
13654 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13655 value.templates.extend(lsp_tasks.templates);
13656 }
13657
13658 editor.insert_tasks(key, value);
13659 }
13660 for (key, value) in lsp_tasks_by_rows {
13661 editor.insert_tasks(key, value);
13662 }
13663 })
13664 .ok();
13665 })
13666 }
13667 fn fetch_runnable_ranges(
13668 snapshot: &DisplaySnapshot,
13669 range: Range<Anchor>,
13670 ) -> Vec<language::RunnableRange> {
13671 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13672 }
13673
13674 fn runnable_rows(
13675 project: Entity<Project>,
13676 snapshot: DisplaySnapshot,
13677 prefer_lsp: bool,
13678 runnable_ranges: Vec<RunnableRange>,
13679 mut cx: AsyncWindowContext,
13680 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13681 runnable_ranges
13682 .into_iter()
13683 .filter_map(|mut runnable| {
13684 let mut tasks = cx
13685 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13686 .ok()?;
13687 if prefer_lsp {
13688 tasks.retain(|(task_kind, _)| {
13689 !matches!(task_kind, TaskSourceKind::Language { .. })
13690 });
13691 }
13692 if tasks.is_empty() {
13693 return None;
13694 }
13695
13696 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13697
13698 let row = snapshot
13699 .buffer_snapshot
13700 .buffer_line_for_row(MultiBufferRow(point.row))?
13701 .1
13702 .start
13703 .row;
13704
13705 let context_range =
13706 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13707 Some((
13708 (runnable.buffer_id, row),
13709 RunnableTasks {
13710 templates: tasks,
13711 offset: snapshot
13712 .buffer_snapshot
13713 .anchor_before(runnable.run_range.start),
13714 context_range,
13715 column: point.column,
13716 extra_variables: runnable.extra_captures,
13717 },
13718 ))
13719 })
13720 .collect()
13721 }
13722
13723 fn templates_with_tags(
13724 project: &Entity<Project>,
13725 runnable: &mut Runnable,
13726 cx: &mut App,
13727 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13728 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13729 let (worktree_id, file) = project
13730 .buffer_for_id(runnable.buffer, cx)
13731 .and_then(|buffer| buffer.read(cx).file())
13732 .map(|file| (file.worktree_id(cx), file.clone()))
13733 .unzip();
13734
13735 (
13736 project.task_store().read(cx).task_inventory().cloned(),
13737 worktree_id,
13738 file,
13739 )
13740 });
13741
13742 let mut templates_with_tags = mem::take(&mut runnable.tags)
13743 .into_iter()
13744 .flat_map(|RunnableTag(tag)| {
13745 inventory
13746 .as_ref()
13747 .into_iter()
13748 .flat_map(|inventory| {
13749 inventory.read(cx).list_tasks(
13750 file.clone(),
13751 Some(runnable.language.clone()),
13752 worktree_id,
13753 cx,
13754 )
13755 })
13756 .filter(move |(_, template)| {
13757 template.tags.iter().any(|source_tag| source_tag == &tag)
13758 })
13759 })
13760 .sorted_by_key(|(kind, _)| kind.to_owned())
13761 .collect::<Vec<_>>();
13762 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13763 // Strongest source wins; if we have worktree tag binding, prefer that to
13764 // global and language bindings;
13765 // if we have a global binding, prefer that to language binding.
13766 let first_mismatch = templates_with_tags
13767 .iter()
13768 .position(|(tag_source, _)| tag_source != leading_tag_source);
13769 if let Some(index) = first_mismatch {
13770 templates_with_tags.truncate(index);
13771 }
13772 }
13773
13774 templates_with_tags
13775 }
13776
13777 pub fn move_to_enclosing_bracket(
13778 &mut self,
13779 _: &MoveToEnclosingBracket,
13780 window: &mut Window,
13781 cx: &mut Context<Self>,
13782 ) {
13783 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13784 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13785 s.move_offsets_with(|snapshot, selection| {
13786 let Some(enclosing_bracket_ranges) =
13787 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13788 else {
13789 return;
13790 };
13791
13792 let mut best_length = usize::MAX;
13793 let mut best_inside = false;
13794 let mut best_in_bracket_range = false;
13795 let mut best_destination = None;
13796 for (open, close) in enclosing_bracket_ranges {
13797 let close = close.to_inclusive();
13798 let length = close.end() - open.start;
13799 let inside = selection.start >= open.end && selection.end <= *close.start();
13800 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13801 || close.contains(&selection.head());
13802
13803 // If best is next to a bracket and current isn't, skip
13804 if !in_bracket_range && best_in_bracket_range {
13805 continue;
13806 }
13807
13808 // Prefer smaller lengths unless best is inside and current isn't
13809 if length > best_length && (best_inside || !inside) {
13810 continue;
13811 }
13812
13813 best_length = length;
13814 best_inside = inside;
13815 best_in_bracket_range = in_bracket_range;
13816 best_destination = Some(
13817 if close.contains(&selection.start) && close.contains(&selection.end) {
13818 if inside { open.end } else { open.start }
13819 } else if inside {
13820 *close.start()
13821 } else {
13822 *close.end()
13823 },
13824 );
13825 }
13826
13827 if let Some(destination) = best_destination {
13828 selection.collapse_to(destination, SelectionGoal::None);
13829 }
13830 })
13831 });
13832 }
13833
13834 pub fn undo_selection(
13835 &mut self,
13836 _: &UndoSelection,
13837 window: &mut Window,
13838 cx: &mut Context<Self>,
13839 ) {
13840 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13841 self.end_selection(window, cx);
13842 self.selection_history.mode = SelectionHistoryMode::Undoing;
13843 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13844 self.change_selections(None, window, cx, |s| {
13845 s.select_anchors(entry.selections.to_vec())
13846 });
13847 self.select_next_state = entry.select_next_state;
13848 self.select_prev_state = entry.select_prev_state;
13849 self.add_selections_state = entry.add_selections_state;
13850 self.request_autoscroll(Autoscroll::newest(), cx);
13851 }
13852 self.selection_history.mode = SelectionHistoryMode::Normal;
13853 }
13854
13855 pub fn redo_selection(
13856 &mut self,
13857 _: &RedoSelection,
13858 window: &mut Window,
13859 cx: &mut Context<Self>,
13860 ) {
13861 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13862 self.end_selection(window, cx);
13863 self.selection_history.mode = SelectionHistoryMode::Redoing;
13864 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13865 self.change_selections(None, window, cx, |s| {
13866 s.select_anchors(entry.selections.to_vec())
13867 });
13868 self.select_next_state = entry.select_next_state;
13869 self.select_prev_state = entry.select_prev_state;
13870 self.add_selections_state = entry.add_selections_state;
13871 self.request_autoscroll(Autoscroll::newest(), cx);
13872 }
13873 self.selection_history.mode = SelectionHistoryMode::Normal;
13874 }
13875
13876 pub fn expand_excerpts(
13877 &mut self,
13878 action: &ExpandExcerpts,
13879 _: &mut Window,
13880 cx: &mut Context<Self>,
13881 ) {
13882 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13883 }
13884
13885 pub fn expand_excerpts_down(
13886 &mut self,
13887 action: &ExpandExcerptsDown,
13888 _: &mut Window,
13889 cx: &mut Context<Self>,
13890 ) {
13891 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13892 }
13893
13894 pub fn expand_excerpts_up(
13895 &mut self,
13896 action: &ExpandExcerptsUp,
13897 _: &mut Window,
13898 cx: &mut Context<Self>,
13899 ) {
13900 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13901 }
13902
13903 pub fn expand_excerpts_for_direction(
13904 &mut self,
13905 lines: u32,
13906 direction: ExpandExcerptDirection,
13907
13908 cx: &mut Context<Self>,
13909 ) {
13910 let selections = self.selections.disjoint_anchors();
13911
13912 let lines = if lines == 0 {
13913 EditorSettings::get_global(cx).expand_excerpt_lines
13914 } else {
13915 lines
13916 };
13917
13918 self.buffer.update(cx, |buffer, cx| {
13919 let snapshot = buffer.snapshot(cx);
13920 let mut excerpt_ids = selections
13921 .iter()
13922 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13923 .collect::<Vec<_>>();
13924 excerpt_ids.sort();
13925 excerpt_ids.dedup();
13926 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13927 })
13928 }
13929
13930 pub fn expand_excerpt(
13931 &mut self,
13932 excerpt: ExcerptId,
13933 direction: ExpandExcerptDirection,
13934 window: &mut Window,
13935 cx: &mut Context<Self>,
13936 ) {
13937 let current_scroll_position = self.scroll_position(cx);
13938 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13939 let mut should_scroll_up = false;
13940
13941 if direction == ExpandExcerptDirection::Down {
13942 let multi_buffer = self.buffer.read(cx);
13943 let snapshot = multi_buffer.snapshot(cx);
13944 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13945 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13946 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13947 let buffer_snapshot = buffer.read(cx).snapshot();
13948 let excerpt_end_row =
13949 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13950 let last_row = buffer_snapshot.max_point().row;
13951 let lines_below = last_row.saturating_sub(excerpt_end_row);
13952 should_scroll_up = lines_below >= lines_to_expand;
13953 }
13954 }
13955 }
13956 }
13957
13958 self.buffer.update(cx, |buffer, cx| {
13959 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13960 });
13961
13962 if should_scroll_up {
13963 let new_scroll_position =
13964 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13965 self.set_scroll_position(new_scroll_position, window, cx);
13966 }
13967 }
13968
13969 pub fn go_to_singleton_buffer_point(
13970 &mut self,
13971 point: Point,
13972 window: &mut Window,
13973 cx: &mut Context<Self>,
13974 ) {
13975 self.go_to_singleton_buffer_range(point..point, window, cx);
13976 }
13977
13978 pub fn go_to_singleton_buffer_range(
13979 &mut self,
13980 range: Range<Point>,
13981 window: &mut Window,
13982 cx: &mut Context<Self>,
13983 ) {
13984 let multibuffer = self.buffer().read(cx);
13985 let Some(buffer) = multibuffer.as_singleton() else {
13986 return;
13987 };
13988 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13989 return;
13990 };
13991 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13992 return;
13993 };
13994 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13995 s.select_anchor_ranges([start..end])
13996 });
13997 }
13998
13999 pub fn go_to_diagnostic(
14000 &mut self,
14001 _: &GoToDiagnostic,
14002 window: &mut Window,
14003 cx: &mut Context<Self>,
14004 ) {
14005 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14006 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14007 }
14008
14009 pub fn go_to_prev_diagnostic(
14010 &mut self,
14011 _: &GoToPreviousDiagnostic,
14012 window: &mut Window,
14013 cx: &mut Context<Self>,
14014 ) {
14015 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14016 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14017 }
14018
14019 pub fn go_to_diagnostic_impl(
14020 &mut self,
14021 direction: Direction,
14022 window: &mut Window,
14023 cx: &mut Context<Self>,
14024 ) {
14025 let buffer = self.buffer.read(cx).snapshot(cx);
14026 let selection = self.selections.newest::<usize>(cx);
14027
14028 let mut active_group_id = None;
14029 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14030 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14031 active_group_id = Some(active_group.group_id);
14032 }
14033 }
14034
14035 fn filtered(
14036 snapshot: EditorSnapshot,
14037 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14038 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14039 diagnostics
14040 .filter(|entry| entry.range.start != entry.range.end)
14041 .filter(|entry| !entry.diagnostic.is_unnecessary)
14042 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14043 }
14044
14045 let snapshot = self.snapshot(window, cx);
14046 let before = filtered(
14047 snapshot.clone(),
14048 buffer
14049 .diagnostics_in_range(0..selection.start)
14050 .filter(|entry| entry.range.start <= selection.start),
14051 );
14052 let after = filtered(
14053 snapshot,
14054 buffer
14055 .diagnostics_in_range(selection.start..buffer.len())
14056 .filter(|entry| entry.range.start >= selection.start),
14057 );
14058
14059 let mut found: Option<DiagnosticEntry<usize>> = None;
14060 if direction == Direction::Prev {
14061 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14062 {
14063 for diagnostic in prev_diagnostics.into_iter().rev() {
14064 if diagnostic.range.start != selection.start
14065 || active_group_id
14066 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14067 {
14068 found = Some(diagnostic);
14069 break 'outer;
14070 }
14071 }
14072 }
14073 } else {
14074 for diagnostic in after.chain(before) {
14075 if diagnostic.range.start != selection.start
14076 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14077 {
14078 found = Some(diagnostic);
14079 break;
14080 }
14081 }
14082 }
14083 let Some(next_diagnostic) = found else {
14084 return;
14085 };
14086
14087 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14088 return;
14089 };
14090 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14091 s.select_ranges(vec![
14092 next_diagnostic.range.start..next_diagnostic.range.start,
14093 ])
14094 });
14095 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14096 self.refresh_inline_completion(false, true, window, cx);
14097 }
14098
14099 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14100 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14101 let snapshot = self.snapshot(window, cx);
14102 let selection = self.selections.newest::<Point>(cx);
14103 self.go_to_hunk_before_or_after_position(
14104 &snapshot,
14105 selection.head(),
14106 Direction::Next,
14107 window,
14108 cx,
14109 );
14110 }
14111
14112 pub fn go_to_hunk_before_or_after_position(
14113 &mut self,
14114 snapshot: &EditorSnapshot,
14115 position: Point,
14116 direction: Direction,
14117 window: &mut Window,
14118 cx: &mut Context<Editor>,
14119 ) {
14120 let row = if direction == Direction::Next {
14121 self.hunk_after_position(snapshot, position)
14122 .map(|hunk| hunk.row_range.start)
14123 } else {
14124 self.hunk_before_position(snapshot, position)
14125 };
14126
14127 if let Some(row) = row {
14128 let destination = Point::new(row.0, 0);
14129 let autoscroll = Autoscroll::center();
14130
14131 self.unfold_ranges(&[destination..destination], false, false, cx);
14132 self.change_selections(Some(autoscroll), window, cx, |s| {
14133 s.select_ranges([destination..destination]);
14134 });
14135 }
14136 }
14137
14138 fn hunk_after_position(
14139 &mut self,
14140 snapshot: &EditorSnapshot,
14141 position: Point,
14142 ) -> Option<MultiBufferDiffHunk> {
14143 snapshot
14144 .buffer_snapshot
14145 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14146 .find(|hunk| hunk.row_range.start.0 > position.row)
14147 .or_else(|| {
14148 snapshot
14149 .buffer_snapshot
14150 .diff_hunks_in_range(Point::zero()..position)
14151 .find(|hunk| hunk.row_range.end.0 < position.row)
14152 })
14153 }
14154
14155 fn go_to_prev_hunk(
14156 &mut self,
14157 _: &GoToPreviousHunk,
14158 window: &mut Window,
14159 cx: &mut Context<Self>,
14160 ) {
14161 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14162 let snapshot = self.snapshot(window, cx);
14163 let selection = self.selections.newest::<Point>(cx);
14164 self.go_to_hunk_before_or_after_position(
14165 &snapshot,
14166 selection.head(),
14167 Direction::Prev,
14168 window,
14169 cx,
14170 );
14171 }
14172
14173 fn hunk_before_position(
14174 &mut self,
14175 snapshot: &EditorSnapshot,
14176 position: Point,
14177 ) -> Option<MultiBufferRow> {
14178 snapshot
14179 .buffer_snapshot
14180 .diff_hunk_before(position)
14181 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14182 }
14183
14184 fn go_to_next_change(
14185 &mut self,
14186 _: &GoToNextChange,
14187 window: &mut Window,
14188 cx: &mut Context<Self>,
14189 ) {
14190 if let Some(selections) = self
14191 .change_list
14192 .next_change(1, Direction::Next)
14193 .map(|s| s.to_vec())
14194 {
14195 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14196 let map = s.display_map();
14197 s.select_display_ranges(selections.iter().map(|a| {
14198 let point = a.to_display_point(&map);
14199 point..point
14200 }))
14201 })
14202 }
14203 }
14204
14205 fn go_to_previous_change(
14206 &mut self,
14207 _: &GoToPreviousChange,
14208 window: &mut Window,
14209 cx: &mut Context<Self>,
14210 ) {
14211 if let Some(selections) = self
14212 .change_list
14213 .next_change(1, Direction::Prev)
14214 .map(|s| s.to_vec())
14215 {
14216 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14217 let map = s.display_map();
14218 s.select_display_ranges(selections.iter().map(|a| {
14219 let point = a.to_display_point(&map);
14220 point..point
14221 }))
14222 })
14223 }
14224 }
14225
14226 fn go_to_line<T: 'static>(
14227 &mut self,
14228 position: Anchor,
14229 highlight_color: Option<Hsla>,
14230 window: &mut Window,
14231 cx: &mut Context<Self>,
14232 ) {
14233 let snapshot = self.snapshot(window, cx).display_snapshot;
14234 let position = position.to_point(&snapshot.buffer_snapshot);
14235 let start = snapshot
14236 .buffer_snapshot
14237 .clip_point(Point::new(position.row, 0), Bias::Left);
14238 let end = start + Point::new(1, 0);
14239 let start = snapshot.buffer_snapshot.anchor_before(start);
14240 let end = snapshot.buffer_snapshot.anchor_before(end);
14241
14242 self.highlight_rows::<T>(
14243 start..end,
14244 highlight_color
14245 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14246 Default::default(),
14247 cx,
14248 );
14249
14250 if self.buffer.read(cx).is_singleton() {
14251 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14252 }
14253 }
14254
14255 pub fn go_to_definition(
14256 &mut self,
14257 _: &GoToDefinition,
14258 window: &mut Window,
14259 cx: &mut Context<Self>,
14260 ) -> Task<Result<Navigated>> {
14261 let definition =
14262 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14263 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14264 cx.spawn_in(window, async move |editor, cx| {
14265 if definition.await? == Navigated::Yes {
14266 return Ok(Navigated::Yes);
14267 }
14268 match fallback_strategy {
14269 GoToDefinitionFallback::None => Ok(Navigated::No),
14270 GoToDefinitionFallback::FindAllReferences => {
14271 match editor.update_in(cx, |editor, window, cx| {
14272 editor.find_all_references(&FindAllReferences, window, cx)
14273 })? {
14274 Some(references) => references.await,
14275 None => Ok(Navigated::No),
14276 }
14277 }
14278 }
14279 })
14280 }
14281
14282 pub fn go_to_declaration(
14283 &mut self,
14284 _: &GoToDeclaration,
14285 window: &mut Window,
14286 cx: &mut Context<Self>,
14287 ) -> Task<Result<Navigated>> {
14288 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14289 }
14290
14291 pub fn go_to_declaration_split(
14292 &mut self,
14293 _: &GoToDeclaration,
14294 window: &mut Window,
14295 cx: &mut Context<Self>,
14296 ) -> Task<Result<Navigated>> {
14297 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14298 }
14299
14300 pub fn go_to_implementation(
14301 &mut self,
14302 _: &GoToImplementation,
14303 window: &mut Window,
14304 cx: &mut Context<Self>,
14305 ) -> Task<Result<Navigated>> {
14306 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14307 }
14308
14309 pub fn go_to_implementation_split(
14310 &mut self,
14311 _: &GoToImplementationSplit,
14312 window: &mut Window,
14313 cx: &mut Context<Self>,
14314 ) -> Task<Result<Navigated>> {
14315 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14316 }
14317
14318 pub fn go_to_type_definition(
14319 &mut self,
14320 _: &GoToTypeDefinition,
14321 window: &mut Window,
14322 cx: &mut Context<Self>,
14323 ) -> Task<Result<Navigated>> {
14324 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14325 }
14326
14327 pub fn go_to_definition_split(
14328 &mut self,
14329 _: &GoToDefinitionSplit,
14330 window: &mut Window,
14331 cx: &mut Context<Self>,
14332 ) -> Task<Result<Navigated>> {
14333 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14334 }
14335
14336 pub fn go_to_type_definition_split(
14337 &mut self,
14338 _: &GoToTypeDefinitionSplit,
14339 window: &mut Window,
14340 cx: &mut Context<Self>,
14341 ) -> Task<Result<Navigated>> {
14342 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14343 }
14344
14345 fn go_to_definition_of_kind(
14346 &mut self,
14347 kind: GotoDefinitionKind,
14348 split: bool,
14349 window: &mut Window,
14350 cx: &mut Context<Self>,
14351 ) -> Task<Result<Navigated>> {
14352 let Some(provider) = self.semantics_provider.clone() else {
14353 return Task::ready(Ok(Navigated::No));
14354 };
14355 let head = self.selections.newest::<usize>(cx).head();
14356 let buffer = self.buffer.read(cx);
14357 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14358 text_anchor
14359 } else {
14360 return Task::ready(Ok(Navigated::No));
14361 };
14362
14363 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14364 return Task::ready(Ok(Navigated::No));
14365 };
14366
14367 cx.spawn_in(window, async move |editor, cx| {
14368 let definitions = definitions.await?;
14369 let navigated = editor
14370 .update_in(cx, |editor, window, cx| {
14371 editor.navigate_to_hover_links(
14372 Some(kind),
14373 definitions
14374 .into_iter()
14375 .filter(|location| {
14376 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14377 })
14378 .map(HoverLink::Text)
14379 .collect::<Vec<_>>(),
14380 split,
14381 window,
14382 cx,
14383 )
14384 })?
14385 .await?;
14386 anyhow::Ok(navigated)
14387 })
14388 }
14389
14390 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14391 let selection = self.selections.newest_anchor();
14392 let head = selection.head();
14393 let tail = selection.tail();
14394
14395 let Some((buffer, start_position)) =
14396 self.buffer.read(cx).text_anchor_for_position(head, cx)
14397 else {
14398 return;
14399 };
14400
14401 let end_position = if head != tail {
14402 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14403 return;
14404 };
14405 Some(pos)
14406 } else {
14407 None
14408 };
14409
14410 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14411 let url = if let Some(end_pos) = end_position {
14412 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14413 } else {
14414 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14415 };
14416
14417 if let Some(url) = url {
14418 editor.update(cx, |_, cx| {
14419 cx.open_url(&url);
14420 })
14421 } else {
14422 Ok(())
14423 }
14424 });
14425
14426 url_finder.detach();
14427 }
14428
14429 pub fn open_selected_filename(
14430 &mut self,
14431 _: &OpenSelectedFilename,
14432 window: &mut Window,
14433 cx: &mut Context<Self>,
14434 ) {
14435 let Some(workspace) = self.workspace() else {
14436 return;
14437 };
14438
14439 let position = self.selections.newest_anchor().head();
14440
14441 let Some((buffer, buffer_position)) =
14442 self.buffer.read(cx).text_anchor_for_position(position, cx)
14443 else {
14444 return;
14445 };
14446
14447 let project = self.project.clone();
14448
14449 cx.spawn_in(window, async move |_, cx| {
14450 let result = find_file(&buffer, project, buffer_position, cx).await;
14451
14452 if let Some((_, path)) = result {
14453 workspace
14454 .update_in(cx, |workspace, window, cx| {
14455 workspace.open_resolved_path(path, window, cx)
14456 })?
14457 .await?;
14458 }
14459 anyhow::Ok(())
14460 })
14461 .detach();
14462 }
14463
14464 pub(crate) fn navigate_to_hover_links(
14465 &mut self,
14466 kind: Option<GotoDefinitionKind>,
14467 mut definitions: Vec<HoverLink>,
14468 split: bool,
14469 window: &mut Window,
14470 cx: &mut Context<Editor>,
14471 ) -> Task<Result<Navigated>> {
14472 // If there is one definition, just open it directly
14473 if definitions.len() == 1 {
14474 let definition = definitions.pop().unwrap();
14475
14476 enum TargetTaskResult {
14477 Location(Option<Location>),
14478 AlreadyNavigated,
14479 }
14480
14481 let target_task = match definition {
14482 HoverLink::Text(link) => {
14483 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14484 }
14485 HoverLink::InlayHint(lsp_location, server_id) => {
14486 let computation =
14487 self.compute_target_location(lsp_location, server_id, window, cx);
14488 cx.background_spawn(async move {
14489 let location = computation.await?;
14490 Ok(TargetTaskResult::Location(location))
14491 })
14492 }
14493 HoverLink::Url(url) => {
14494 cx.open_url(&url);
14495 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14496 }
14497 HoverLink::File(path) => {
14498 if let Some(workspace) = self.workspace() {
14499 cx.spawn_in(window, async move |_, cx| {
14500 workspace
14501 .update_in(cx, |workspace, window, cx| {
14502 workspace.open_resolved_path(path, window, cx)
14503 })?
14504 .await
14505 .map(|_| TargetTaskResult::AlreadyNavigated)
14506 })
14507 } else {
14508 Task::ready(Ok(TargetTaskResult::Location(None)))
14509 }
14510 }
14511 };
14512 cx.spawn_in(window, async move |editor, cx| {
14513 let target = match target_task.await.context("target resolution task")? {
14514 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14515 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14516 TargetTaskResult::Location(Some(target)) => target,
14517 };
14518
14519 editor.update_in(cx, |editor, window, cx| {
14520 let Some(workspace) = editor.workspace() else {
14521 return Navigated::No;
14522 };
14523 let pane = workspace.read(cx).active_pane().clone();
14524
14525 let range = target.range.to_point(target.buffer.read(cx));
14526 let range = editor.range_for_match(&range);
14527 let range = collapse_multiline_range(range);
14528
14529 if !split
14530 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14531 {
14532 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14533 } else {
14534 window.defer(cx, move |window, cx| {
14535 let target_editor: Entity<Self> =
14536 workspace.update(cx, |workspace, cx| {
14537 let pane = if split {
14538 workspace.adjacent_pane(window, cx)
14539 } else {
14540 workspace.active_pane().clone()
14541 };
14542
14543 workspace.open_project_item(
14544 pane,
14545 target.buffer.clone(),
14546 true,
14547 true,
14548 window,
14549 cx,
14550 )
14551 });
14552 target_editor.update(cx, |target_editor, cx| {
14553 // When selecting a definition in a different buffer, disable the nav history
14554 // to avoid creating a history entry at the previous cursor location.
14555 pane.update(cx, |pane, _| pane.disable_history());
14556 target_editor.go_to_singleton_buffer_range(range, window, cx);
14557 pane.update(cx, |pane, _| pane.enable_history());
14558 });
14559 });
14560 }
14561 Navigated::Yes
14562 })
14563 })
14564 } else if !definitions.is_empty() {
14565 cx.spawn_in(window, async move |editor, cx| {
14566 let (title, location_tasks, workspace) = editor
14567 .update_in(cx, |editor, window, cx| {
14568 let tab_kind = match kind {
14569 Some(GotoDefinitionKind::Implementation) => "Implementations",
14570 _ => "Definitions",
14571 };
14572 let title = definitions
14573 .iter()
14574 .find_map(|definition| match definition {
14575 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14576 let buffer = origin.buffer.read(cx);
14577 format!(
14578 "{} for {}",
14579 tab_kind,
14580 buffer
14581 .text_for_range(origin.range.clone())
14582 .collect::<String>()
14583 )
14584 }),
14585 HoverLink::InlayHint(_, _) => None,
14586 HoverLink::Url(_) => None,
14587 HoverLink::File(_) => None,
14588 })
14589 .unwrap_or(tab_kind.to_string());
14590 let location_tasks = definitions
14591 .into_iter()
14592 .map(|definition| match definition {
14593 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14594 HoverLink::InlayHint(lsp_location, server_id) => editor
14595 .compute_target_location(lsp_location, server_id, window, cx),
14596 HoverLink::Url(_) => Task::ready(Ok(None)),
14597 HoverLink::File(_) => Task::ready(Ok(None)),
14598 })
14599 .collect::<Vec<_>>();
14600 (title, location_tasks, editor.workspace().clone())
14601 })
14602 .context("location tasks preparation")?;
14603
14604 let locations = future::join_all(location_tasks)
14605 .await
14606 .into_iter()
14607 .filter_map(|location| location.transpose())
14608 .collect::<Result<_>>()
14609 .context("location tasks")?;
14610
14611 let Some(workspace) = workspace else {
14612 return Ok(Navigated::No);
14613 };
14614 let opened = workspace
14615 .update_in(cx, |workspace, window, cx| {
14616 Self::open_locations_in_multibuffer(
14617 workspace,
14618 locations,
14619 title,
14620 split,
14621 MultibufferSelectionMode::First,
14622 window,
14623 cx,
14624 )
14625 })
14626 .ok();
14627
14628 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14629 })
14630 } else {
14631 Task::ready(Ok(Navigated::No))
14632 }
14633 }
14634
14635 fn compute_target_location(
14636 &self,
14637 lsp_location: lsp::Location,
14638 server_id: LanguageServerId,
14639 window: &mut Window,
14640 cx: &mut Context<Self>,
14641 ) -> Task<anyhow::Result<Option<Location>>> {
14642 let Some(project) = self.project.clone() else {
14643 return Task::ready(Ok(None));
14644 };
14645
14646 cx.spawn_in(window, async move |editor, cx| {
14647 let location_task = editor.update(cx, |_, cx| {
14648 project.update(cx, |project, cx| {
14649 let language_server_name = project
14650 .language_server_statuses(cx)
14651 .find(|(id, _)| server_id == *id)
14652 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14653 language_server_name.map(|language_server_name| {
14654 project.open_local_buffer_via_lsp(
14655 lsp_location.uri.clone(),
14656 server_id,
14657 language_server_name,
14658 cx,
14659 )
14660 })
14661 })
14662 })?;
14663 let location = match location_task {
14664 Some(task) => Some({
14665 let target_buffer_handle = task.await.context("open local buffer")?;
14666 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
14667 let target_start = target_buffer
14668 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14669 let target_end = target_buffer
14670 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14671 target_buffer.anchor_after(target_start)
14672 ..target_buffer.anchor_before(target_end)
14673 })?;
14674 Location {
14675 buffer: target_buffer_handle,
14676 range,
14677 }
14678 }),
14679 None => None,
14680 };
14681 Ok(location)
14682 })
14683 }
14684
14685 pub fn find_all_references(
14686 &mut self,
14687 _: &FindAllReferences,
14688 window: &mut Window,
14689 cx: &mut Context<Self>,
14690 ) -> Option<Task<Result<Navigated>>> {
14691 let selection = self.selections.newest::<usize>(cx);
14692 let multi_buffer = self.buffer.read(cx);
14693 let head = selection.head();
14694
14695 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14696 let head_anchor = multi_buffer_snapshot.anchor_at(
14697 head,
14698 if head < selection.tail() {
14699 Bias::Right
14700 } else {
14701 Bias::Left
14702 },
14703 );
14704
14705 match self
14706 .find_all_references_task_sources
14707 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14708 {
14709 Ok(_) => {
14710 log::info!(
14711 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14712 );
14713 return None;
14714 }
14715 Err(i) => {
14716 self.find_all_references_task_sources.insert(i, head_anchor);
14717 }
14718 }
14719
14720 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14721 let workspace = self.workspace()?;
14722 let project = workspace.read(cx).project().clone();
14723 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14724 Some(cx.spawn_in(window, async move |editor, cx| {
14725 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14726 if let Ok(i) = editor
14727 .find_all_references_task_sources
14728 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14729 {
14730 editor.find_all_references_task_sources.remove(i);
14731 }
14732 });
14733
14734 let locations = references.await?;
14735 if locations.is_empty() {
14736 return anyhow::Ok(Navigated::No);
14737 }
14738
14739 workspace.update_in(cx, |workspace, window, cx| {
14740 let title = locations
14741 .first()
14742 .as_ref()
14743 .map(|location| {
14744 let buffer = location.buffer.read(cx);
14745 format!(
14746 "References to `{}`",
14747 buffer
14748 .text_for_range(location.range.clone())
14749 .collect::<String>()
14750 )
14751 })
14752 .unwrap();
14753 Self::open_locations_in_multibuffer(
14754 workspace,
14755 locations,
14756 title,
14757 false,
14758 MultibufferSelectionMode::First,
14759 window,
14760 cx,
14761 );
14762 Navigated::Yes
14763 })
14764 }))
14765 }
14766
14767 /// Opens a multibuffer with the given project locations in it
14768 pub fn open_locations_in_multibuffer(
14769 workspace: &mut Workspace,
14770 mut locations: Vec<Location>,
14771 title: String,
14772 split: bool,
14773 multibuffer_selection_mode: MultibufferSelectionMode,
14774 window: &mut Window,
14775 cx: &mut Context<Workspace>,
14776 ) {
14777 // If there are multiple definitions, open them in a multibuffer
14778 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14779 let mut locations = locations.into_iter().peekable();
14780 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14781 let capability = workspace.project().read(cx).capability();
14782
14783 let excerpt_buffer = cx.new(|cx| {
14784 let mut multibuffer = MultiBuffer::new(capability);
14785 while let Some(location) = locations.next() {
14786 let buffer = location.buffer.read(cx);
14787 let mut ranges_for_buffer = Vec::new();
14788 let range = location.range.to_point(buffer);
14789 ranges_for_buffer.push(range.clone());
14790
14791 while let Some(next_location) = locations.peek() {
14792 if next_location.buffer == location.buffer {
14793 ranges_for_buffer.push(next_location.range.to_point(buffer));
14794 locations.next();
14795 } else {
14796 break;
14797 }
14798 }
14799
14800 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14801 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14802 PathKey::for_buffer(&location.buffer, cx),
14803 location.buffer.clone(),
14804 ranges_for_buffer,
14805 DEFAULT_MULTIBUFFER_CONTEXT,
14806 cx,
14807 );
14808 ranges.extend(new_ranges)
14809 }
14810
14811 multibuffer.with_title(title)
14812 });
14813
14814 let editor = cx.new(|cx| {
14815 Editor::for_multibuffer(
14816 excerpt_buffer,
14817 Some(workspace.project().clone()),
14818 window,
14819 cx,
14820 )
14821 });
14822 editor.update(cx, |editor, cx| {
14823 match multibuffer_selection_mode {
14824 MultibufferSelectionMode::First => {
14825 if let Some(first_range) = ranges.first() {
14826 editor.change_selections(None, window, cx, |selections| {
14827 selections.clear_disjoint();
14828 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14829 });
14830 }
14831 editor.highlight_background::<Self>(
14832 &ranges,
14833 |theme| theme.editor_highlighted_line_background,
14834 cx,
14835 );
14836 }
14837 MultibufferSelectionMode::All => {
14838 editor.change_selections(None, window, cx, |selections| {
14839 selections.clear_disjoint();
14840 selections.select_anchor_ranges(ranges);
14841 });
14842 }
14843 }
14844 editor.register_buffers_with_language_servers(cx);
14845 });
14846
14847 let item = Box::new(editor);
14848 let item_id = item.item_id();
14849
14850 if split {
14851 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14852 } else {
14853 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14854 let (preview_item_id, preview_item_idx) =
14855 workspace.active_pane().read_with(cx, |pane, _| {
14856 (pane.preview_item_id(), pane.preview_item_idx())
14857 });
14858
14859 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14860
14861 if let Some(preview_item_id) = preview_item_id {
14862 workspace.active_pane().update(cx, |pane, cx| {
14863 pane.remove_item(preview_item_id, false, false, window, cx);
14864 });
14865 }
14866 } else {
14867 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14868 }
14869 }
14870 workspace.active_pane().update(cx, |pane, cx| {
14871 pane.set_preview_item_id(Some(item_id), cx);
14872 });
14873 }
14874
14875 pub fn rename(
14876 &mut self,
14877 _: &Rename,
14878 window: &mut Window,
14879 cx: &mut Context<Self>,
14880 ) -> Option<Task<Result<()>>> {
14881 use language::ToOffset as _;
14882
14883 let provider = self.semantics_provider.clone()?;
14884 let selection = self.selections.newest_anchor().clone();
14885 let (cursor_buffer, cursor_buffer_position) = self
14886 .buffer
14887 .read(cx)
14888 .text_anchor_for_position(selection.head(), cx)?;
14889 let (tail_buffer, cursor_buffer_position_end) = self
14890 .buffer
14891 .read(cx)
14892 .text_anchor_for_position(selection.tail(), cx)?;
14893 if tail_buffer != cursor_buffer {
14894 return None;
14895 }
14896
14897 let snapshot = cursor_buffer.read(cx).snapshot();
14898 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14899 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14900 let prepare_rename = provider
14901 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14902 .unwrap_or_else(|| Task::ready(Ok(None)));
14903 drop(snapshot);
14904
14905 Some(cx.spawn_in(window, async move |this, cx| {
14906 let rename_range = if let Some(range) = prepare_rename.await? {
14907 Some(range)
14908 } else {
14909 this.update(cx, |this, cx| {
14910 let buffer = this.buffer.read(cx).snapshot(cx);
14911 let mut buffer_highlights = this
14912 .document_highlights_for_position(selection.head(), &buffer)
14913 .filter(|highlight| {
14914 highlight.start.excerpt_id == selection.head().excerpt_id
14915 && highlight.end.excerpt_id == selection.head().excerpt_id
14916 });
14917 buffer_highlights
14918 .next()
14919 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14920 })?
14921 };
14922 if let Some(rename_range) = rename_range {
14923 this.update_in(cx, |this, window, cx| {
14924 let snapshot = cursor_buffer.read(cx).snapshot();
14925 let rename_buffer_range = rename_range.to_offset(&snapshot);
14926 let cursor_offset_in_rename_range =
14927 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14928 let cursor_offset_in_rename_range_end =
14929 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14930
14931 this.take_rename(false, window, cx);
14932 let buffer = this.buffer.read(cx).read(cx);
14933 let cursor_offset = selection.head().to_offset(&buffer);
14934 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14935 let rename_end = rename_start + rename_buffer_range.len();
14936 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14937 let mut old_highlight_id = None;
14938 let old_name: Arc<str> = buffer
14939 .chunks(rename_start..rename_end, true)
14940 .map(|chunk| {
14941 if old_highlight_id.is_none() {
14942 old_highlight_id = chunk.syntax_highlight_id;
14943 }
14944 chunk.text
14945 })
14946 .collect::<String>()
14947 .into();
14948
14949 drop(buffer);
14950
14951 // Position the selection in the rename editor so that it matches the current selection.
14952 this.show_local_selections = false;
14953 let rename_editor = cx.new(|cx| {
14954 let mut editor = Editor::single_line(window, cx);
14955 editor.buffer.update(cx, |buffer, cx| {
14956 buffer.edit([(0..0, old_name.clone())], None, cx)
14957 });
14958 let rename_selection_range = match cursor_offset_in_rename_range
14959 .cmp(&cursor_offset_in_rename_range_end)
14960 {
14961 Ordering::Equal => {
14962 editor.select_all(&SelectAll, window, cx);
14963 return editor;
14964 }
14965 Ordering::Less => {
14966 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14967 }
14968 Ordering::Greater => {
14969 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14970 }
14971 };
14972 if rename_selection_range.end > old_name.len() {
14973 editor.select_all(&SelectAll, window, cx);
14974 } else {
14975 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14976 s.select_ranges([rename_selection_range]);
14977 });
14978 }
14979 editor
14980 });
14981 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14982 if e == &EditorEvent::Focused {
14983 cx.emit(EditorEvent::FocusedIn)
14984 }
14985 })
14986 .detach();
14987
14988 let write_highlights =
14989 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14990 let read_highlights =
14991 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14992 let ranges = write_highlights
14993 .iter()
14994 .flat_map(|(_, ranges)| ranges.iter())
14995 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14996 .cloned()
14997 .collect();
14998
14999 this.highlight_text::<Rename>(
15000 ranges,
15001 HighlightStyle {
15002 fade_out: Some(0.6),
15003 ..Default::default()
15004 },
15005 cx,
15006 );
15007 let rename_focus_handle = rename_editor.focus_handle(cx);
15008 window.focus(&rename_focus_handle);
15009 let block_id = this.insert_blocks(
15010 [BlockProperties {
15011 style: BlockStyle::Flex,
15012 placement: BlockPlacement::Below(range.start),
15013 height: Some(1),
15014 render: Arc::new({
15015 let rename_editor = rename_editor.clone();
15016 move |cx: &mut BlockContext| {
15017 let mut text_style = cx.editor_style.text.clone();
15018 if let Some(highlight_style) = old_highlight_id
15019 .and_then(|h| h.style(&cx.editor_style.syntax))
15020 {
15021 text_style = text_style.highlight(highlight_style);
15022 }
15023 div()
15024 .block_mouse_except_scroll()
15025 .pl(cx.anchor_x)
15026 .child(EditorElement::new(
15027 &rename_editor,
15028 EditorStyle {
15029 background: cx.theme().system().transparent,
15030 local_player: cx.editor_style.local_player,
15031 text: text_style,
15032 scrollbar_width: cx.editor_style.scrollbar_width,
15033 syntax: cx.editor_style.syntax.clone(),
15034 status: cx.editor_style.status.clone(),
15035 inlay_hints_style: HighlightStyle {
15036 font_weight: Some(FontWeight::BOLD),
15037 ..make_inlay_hints_style(cx.app)
15038 },
15039 inline_completion_styles: make_suggestion_styles(
15040 cx.app,
15041 ),
15042 ..EditorStyle::default()
15043 },
15044 ))
15045 .into_any_element()
15046 }
15047 }),
15048 priority: 0,
15049 render_in_minimap: true,
15050 }],
15051 Some(Autoscroll::fit()),
15052 cx,
15053 )[0];
15054 this.pending_rename = Some(RenameState {
15055 range,
15056 old_name,
15057 editor: rename_editor,
15058 block_id,
15059 });
15060 })?;
15061 }
15062
15063 Ok(())
15064 }))
15065 }
15066
15067 pub fn confirm_rename(
15068 &mut self,
15069 _: &ConfirmRename,
15070 window: &mut Window,
15071 cx: &mut Context<Self>,
15072 ) -> Option<Task<Result<()>>> {
15073 let rename = self.take_rename(false, window, cx)?;
15074 let workspace = self.workspace()?.downgrade();
15075 let (buffer, start) = self
15076 .buffer
15077 .read(cx)
15078 .text_anchor_for_position(rename.range.start, cx)?;
15079 let (end_buffer, _) = self
15080 .buffer
15081 .read(cx)
15082 .text_anchor_for_position(rename.range.end, cx)?;
15083 if buffer != end_buffer {
15084 return None;
15085 }
15086
15087 let old_name = rename.old_name;
15088 let new_name = rename.editor.read(cx).text(cx);
15089
15090 let rename = self.semantics_provider.as_ref()?.perform_rename(
15091 &buffer,
15092 start,
15093 new_name.clone(),
15094 cx,
15095 )?;
15096
15097 Some(cx.spawn_in(window, async move |editor, cx| {
15098 let project_transaction = rename.await?;
15099 Self::open_project_transaction(
15100 &editor,
15101 workspace,
15102 project_transaction,
15103 format!("Rename: {} → {}", old_name, new_name),
15104 cx,
15105 )
15106 .await?;
15107
15108 editor.update(cx, |editor, cx| {
15109 editor.refresh_document_highlights(cx);
15110 })?;
15111 Ok(())
15112 }))
15113 }
15114
15115 fn take_rename(
15116 &mut self,
15117 moving_cursor: bool,
15118 window: &mut Window,
15119 cx: &mut Context<Self>,
15120 ) -> Option<RenameState> {
15121 let rename = self.pending_rename.take()?;
15122 if rename.editor.focus_handle(cx).is_focused(window) {
15123 window.focus(&self.focus_handle);
15124 }
15125
15126 self.remove_blocks(
15127 [rename.block_id].into_iter().collect(),
15128 Some(Autoscroll::fit()),
15129 cx,
15130 );
15131 self.clear_highlights::<Rename>(cx);
15132 self.show_local_selections = true;
15133
15134 if moving_cursor {
15135 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15136 editor.selections.newest::<usize>(cx).head()
15137 });
15138
15139 // Update the selection to match the position of the selection inside
15140 // the rename editor.
15141 let snapshot = self.buffer.read(cx).read(cx);
15142 let rename_range = rename.range.to_offset(&snapshot);
15143 let cursor_in_editor = snapshot
15144 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15145 .min(rename_range.end);
15146 drop(snapshot);
15147
15148 self.change_selections(None, window, cx, |s| {
15149 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15150 });
15151 } else {
15152 self.refresh_document_highlights(cx);
15153 }
15154
15155 Some(rename)
15156 }
15157
15158 pub fn pending_rename(&self) -> Option<&RenameState> {
15159 self.pending_rename.as_ref()
15160 }
15161
15162 fn format(
15163 &mut self,
15164 _: &Format,
15165 window: &mut Window,
15166 cx: &mut Context<Self>,
15167 ) -> Option<Task<Result<()>>> {
15168 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15169
15170 let project = match &self.project {
15171 Some(project) => project.clone(),
15172 None => return None,
15173 };
15174
15175 Some(self.perform_format(
15176 project,
15177 FormatTrigger::Manual,
15178 FormatTarget::Buffers,
15179 window,
15180 cx,
15181 ))
15182 }
15183
15184 fn format_selections(
15185 &mut self,
15186 _: &FormatSelections,
15187 window: &mut Window,
15188 cx: &mut Context<Self>,
15189 ) -> Option<Task<Result<()>>> {
15190 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15191
15192 let project = match &self.project {
15193 Some(project) => project.clone(),
15194 None => return None,
15195 };
15196
15197 let ranges = self
15198 .selections
15199 .all_adjusted(cx)
15200 .into_iter()
15201 .map(|selection| selection.range())
15202 .collect_vec();
15203
15204 Some(self.perform_format(
15205 project,
15206 FormatTrigger::Manual,
15207 FormatTarget::Ranges(ranges),
15208 window,
15209 cx,
15210 ))
15211 }
15212
15213 fn perform_format(
15214 &mut self,
15215 project: Entity<Project>,
15216 trigger: FormatTrigger,
15217 target: FormatTarget,
15218 window: &mut Window,
15219 cx: &mut Context<Self>,
15220 ) -> Task<Result<()>> {
15221 let buffer = self.buffer.clone();
15222 let (buffers, target) = match target {
15223 FormatTarget::Buffers => {
15224 let mut buffers = buffer.read(cx).all_buffers();
15225 if trigger == FormatTrigger::Save {
15226 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15227 }
15228 (buffers, LspFormatTarget::Buffers)
15229 }
15230 FormatTarget::Ranges(selection_ranges) => {
15231 let multi_buffer = buffer.read(cx);
15232 let snapshot = multi_buffer.read(cx);
15233 let mut buffers = HashSet::default();
15234 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15235 BTreeMap::new();
15236 for selection_range in selection_ranges {
15237 for (buffer, buffer_range, _) in
15238 snapshot.range_to_buffer_ranges(selection_range)
15239 {
15240 let buffer_id = buffer.remote_id();
15241 let start = buffer.anchor_before(buffer_range.start);
15242 let end = buffer.anchor_after(buffer_range.end);
15243 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15244 buffer_id_to_ranges
15245 .entry(buffer_id)
15246 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15247 .or_insert_with(|| vec![start..end]);
15248 }
15249 }
15250 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15251 }
15252 };
15253
15254 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15255 let selections_prev = transaction_id_prev
15256 .and_then(|transaction_id_prev| {
15257 // default to selections as they were after the last edit, if we have them,
15258 // instead of how they are now.
15259 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15260 // will take you back to where you made the last edit, instead of staying where you scrolled
15261 self.selection_history
15262 .transaction(transaction_id_prev)
15263 .map(|t| t.0.clone())
15264 })
15265 .unwrap_or_else(|| {
15266 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15267 self.selections.disjoint_anchors()
15268 });
15269
15270 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15271 let format = project.update(cx, |project, cx| {
15272 project.format(buffers, target, true, trigger, cx)
15273 });
15274
15275 cx.spawn_in(window, async move |editor, cx| {
15276 let transaction = futures::select_biased! {
15277 transaction = format.log_err().fuse() => transaction,
15278 () = timeout => {
15279 log::warn!("timed out waiting for formatting");
15280 None
15281 }
15282 };
15283
15284 buffer
15285 .update(cx, |buffer, cx| {
15286 if let Some(transaction) = transaction {
15287 if !buffer.is_singleton() {
15288 buffer.push_transaction(&transaction.0, cx);
15289 }
15290 }
15291 cx.notify();
15292 })
15293 .ok();
15294
15295 if let Some(transaction_id_now) =
15296 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15297 {
15298 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15299 if has_new_transaction {
15300 _ = editor.update(cx, |editor, _| {
15301 editor
15302 .selection_history
15303 .insert_transaction(transaction_id_now, selections_prev);
15304 });
15305 }
15306 }
15307
15308 Ok(())
15309 })
15310 }
15311
15312 fn organize_imports(
15313 &mut self,
15314 _: &OrganizeImports,
15315 window: &mut Window,
15316 cx: &mut Context<Self>,
15317 ) -> Option<Task<Result<()>>> {
15318 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15319 let project = match &self.project {
15320 Some(project) => project.clone(),
15321 None => return None,
15322 };
15323 Some(self.perform_code_action_kind(
15324 project,
15325 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15326 window,
15327 cx,
15328 ))
15329 }
15330
15331 fn perform_code_action_kind(
15332 &mut self,
15333 project: Entity<Project>,
15334 kind: CodeActionKind,
15335 window: &mut Window,
15336 cx: &mut Context<Self>,
15337 ) -> Task<Result<()>> {
15338 let buffer = self.buffer.clone();
15339 let buffers = buffer.read(cx).all_buffers();
15340 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15341 let apply_action = project.update(cx, |project, cx| {
15342 project.apply_code_action_kind(buffers, kind, true, cx)
15343 });
15344 cx.spawn_in(window, async move |_, cx| {
15345 let transaction = futures::select_biased! {
15346 () = timeout => {
15347 log::warn!("timed out waiting for executing code action");
15348 None
15349 }
15350 transaction = apply_action.log_err().fuse() => transaction,
15351 };
15352 buffer
15353 .update(cx, |buffer, cx| {
15354 // check if we need this
15355 if let Some(transaction) = transaction {
15356 if !buffer.is_singleton() {
15357 buffer.push_transaction(&transaction.0, cx);
15358 }
15359 }
15360 cx.notify();
15361 })
15362 .ok();
15363 Ok(())
15364 })
15365 }
15366
15367 fn restart_language_server(
15368 &mut self,
15369 _: &RestartLanguageServer,
15370 _: &mut Window,
15371 cx: &mut Context<Self>,
15372 ) {
15373 if let Some(project) = self.project.clone() {
15374 self.buffer.update(cx, |multi_buffer, cx| {
15375 project.update(cx, |project, cx| {
15376 project.restart_language_servers_for_buffers(
15377 multi_buffer.all_buffers().into_iter().collect(),
15378 cx,
15379 );
15380 });
15381 })
15382 }
15383 }
15384
15385 fn stop_language_server(
15386 &mut self,
15387 _: &StopLanguageServer,
15388 _: &mut Window,
15389 cx: &mut Context<Self>,
15390 ) {
15391 if let Some(project) = self.project.clone() {
15392 self.buffer.update(cx, |multi_buffer, cx| {
15393 project.update(cx, |project, cx| {
15394 project.stop_language_servers_for_buffers(
15395 multi_buffer.all_buffers().into_iter().collect(),
15396 cx,
15397 );
15398 cx.emit(project::Event::RefreshInlayHints);
15399 });
15400 });
15401 }
15402 }
15403
15404 fn cancel_language_server_work(
15405 workspace: &mut Workspace,
15406 _: &actions::CancelLanguageServerWork,
15407 _: &mut Window,
15408 cx: &mut Context<Workspace>,
15409 ) {
15410 let project = workspace.project();
15411 let buffers = workspace
15412 .active_item(cx)
15413 .and_then(|item| item.act_as::<Editor>(cx))
15414 .map_or(HashSet::default(), |editor| {
15415 editor.read(cx).buffer.read(cx).all_buffers()
15416 });
15417 project.update(cx, |project, cx| {
15418 project.cancel_language_server_work_for_buffers(buffers, cx);
15419 });
15420 }
15421
15422 fn show_character_palette(
15423 &mut self,
15424 _: &ShowCharacterPalette,
15425 window: &mut Window,
15426 _: &mut Context<Self>,
15427 ) {
15428 window.show_character_palette();
15429 }
15430
15431 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15432 if self.mode.is_minimap() {
15433 return;
15434 }
15435
15436 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15437 let buffer = self.buffer.read(cx).snapshot(cx);
15438 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15439 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15440 let is_valid = buffer
15441 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15442 .any(|entry| {
15443 entry.diagnostic.is_primary
15444 && !entry.range.is_empty()
15445 && entry.range.start == primary_range_start
15446 && entry.diagnostic.message == active_diagnostics.active_message
15447 });
15448
15449 if !is_valid {
15450 self.dismiss_diagnostics(cx);
15451 }
15452 }
15453 }
15454
15455 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15456 match &self.active_diagnostics {
15457 ActiveDiagnostic::Group(group) => Some(group),
15458 _ => None,
15459 }
15460 }
15461
15462 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15463 self.dismiss_diagnostics(cx);
15464 self.active_diagnostics = ActiveDiagnostic::All;
15465 }
15466
15467 fn activate_diagnostics(
15468 &mut self,
15469 buffer_id: BufferId,
15470 diagnostic: DiagnosticEntry<usize>,
15471 window: &mut Window,
15472 cx: &mut Context<Self>,
15473 ) {
15474 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15475 return;
15476 }
15477 self.dismiss_diagnostics(cx);
15478 let snapshot = self.snapshot(window, cx);
15479 let buffer = self.buffer.read(cx).snapshot(cx);
15480 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15481 return;
15482 };
15483
15484 let diagnostic_group = buffer
15485 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15486 .collect::<Vec<_>>();
15487
15488 let blocks =
15489 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15490
15491 let blocks = self.display_map.update(cx, |display_map, cx| {
15492 display_map.insert_blocks(blocks, cx).into_iter().collect()
15493 });
15494 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15495 active_range: buffer.anchor_before(diagnostic.range.start)
15496 ..buffer.anchor_after(diagnostic.range.end),
15497 active_message: diagnostic.diagnostic.message.clone(),
15498 group_id: diagnostic.diagnostic.group_id,
15499 blocks,
15500 });
15501 cx.notify();
15502 }
15503
15504 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15505 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15506 return;
15507 };
15508
15509 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15510 if let ActiveDiagnostic::Group(group) = prev {
15511 self.display_map.update(cx, |display_map, cx| {
15512 display_map.remove_blocks(group.blocks, cx);
15513 });
15514 cx.notify();
15515 }
15516 }
15517
15518 /// Disable inline diagnostics rendering for this editor.
15519 pub fn disable_inline_diagnostics(&mut self) {
15520 self.inline_diagnostics_enabled = false;
15521 self.inline_diagnostics_update = Task::ready(());
15522 self.inline_diagnostics.clear();
15523 }
15524
15525 pub fn diagnostics_enabled(&self) -> bool {
15526 self.mode.is_full()
15527 }
15528
15529 pub fn inline_diagnostics_enabled(&self) -> bool {
15530 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15531 }
15532
15533 pub fn show_inline_diagnostics(&self) -> bool {
15534 self.show_inline_diagnostics
15535 }
15536
15537 pub fn toggle_inline_diagnostics(
15538 &mut self,
15539 _: &ToggleInlineDiagnostics,
15540 window: &mut Window,
15541 cx: &mut Context<Editor>,
15542 ) {
15543 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15544 self.refresh_inline_diagnostics(false, window, cx);
15545 }
15546
15547 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15548 self.diagnostics_max_severity = severity;
15549 self.display_map.update(cx, |display_map, _| {
15550 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15551 });
15552 }
15553
15554 pub fn toggle_diagnostics(
15555 &mut self,
15556 _: &ToggleDiagnostics,
15557 window: &mut Window,
15558 cx: &mut Context<Editor>,
15559 ) {
15560 if !self.diagnostics_enabled() {
15561 return;
15562 }
15563
15564 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15565 EditorSettings::get_global(cx)
15566 .diagnostics_max_severity
15567 .filter(|severity| severity != &DiagnosticSeverity::Off)
15568 .unwrap_or(DiagnosticSeverity::Hint)
15569 } else {
15570 DiagnosticSeverity::Off
15571 };
15572 self.set_max_diagnostics_severity(new_severity, cx);
15573 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15574 self.active_diagnostics = ActiveDiagnostic::None;
15575 self.inline_diagnostics_update = Task::ready(());
15576 self.inline_diagnostics.clear();
15577 } else {
15578 self.refresh_inline_diagnostics(false, window, cx);
15579 }
15580
15581 cx.notify();
15582 }
15583
15584 pub fn toggle_minimap(
15585 &mut self,
15586 _: &ToggleMinimap,
15587 window: &mut Window,
15588 cx: &mut Context<Editor>,
15589 ) {
15590 if self.supports_minimap(cx) {
15591 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15592 }
15593 }
15594
15595 fn refresh_inline_diagnostics(
15596 &mut self,
15597 debounce: bool,
15598 window: &mut Window,
15599 cx: &mut Context<Self>,
15600 ) {
15601 let max_severity = ProjectSettings::get_global(cx)
15602 .diagnostics
15603 .inline
15604 .max_severity
15605 .unwrap_or(self.diagnostics_max_severity);
15606
15607 if !self.inline_diagnostics_enabled()
15608 || !self.show_inline_diagnostics
15609 || max_severity == DiagnosticSeverity::Off
15610 {
15611 self.inline_diagnostics_update = Task::ready(());
15612 self.inline_diagnostics.clear();
15613 return;
15614 }
15615
15616 let debounce_ms = ProjectSettings::get_global(cx)
15617 .diagnostics
15618 .inline
15619 .update_debounce_ms;
15620 let debounce = if debounce && debounce_ms > 0 {
15621 Some(Duration::from_millis(debounce_ms))
15622 } else {
15623 None
15624 };
15625 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15626 let editor = editor.upgrade().unwrap();
15627
15628 if let Some(debounce) = debounce {
15629 cx.background_executor().timer(debounce).await;
15630 }
15631 let Some(snapshot) = editor
15632 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15633 .ok()
15634 else {
15635 return;
15636 };
15637
15638 let new_inline_diagnostics = cx
15639 .background_spawn(async move {
15640 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15641 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15642 let message = diagnostic_entry
15643 .diagnostic
15644 .message
15645 .split_once('\n')
15646 .map(|(line, _)| line)
15647 .map(SharedString::new)
15648 .unwrap_or_else(|| {
15649 SharedString::from(diagnostic_entry.diagnostic.message)
15650 });
15651 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15652 let (Ok(i) | Err(i)) = inline_diagnostics
15653 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15654 inline_diagnostics.insert(
15655 i,
15656 (
15657 start_anchor,
15658 InlineDiagnostic {
15659 message,
15660 group_id: diagnostic_entry.diagnostic.group_id,
15661 start: diagnostic_entry.range.start.to_point(&snapshot),
15662 is_primary: diagnostic_entry.diagnostic.is_primary,
15663 severity: diagnostic_entry.diagnostic.severity,
15664 },
15665 ),
15666 );
15667 }
15668 inline_diagnostics
15669 })
15670 .await;
15671
15672 editor
15673 .update(cx, |editor, cx| {
15674 editor.inline_diagnostics = new_inline_diagnostics;
15675 cx.notify();
15676 })
15677 .ok();
15678 });
15679 }
15680
15681 pub fn set_selections_from_remote(
15682 &mut self,
15683 selections: Vec<Selection<Anchor>>,
15684 pending_selection: Option<Selection<Anchor>>,
15685 window: &mut Window,
15686 cx: &mut Context<Self>,
15687 ) {
15688 let old_cursor_position = self.selections.newest_anchor().head();
15689 self.selections.change_with(cx, |s| {
15690 s.select_anchors(selections);
15691 if let Some(pending_selection) = pending_selection {
15692 s.set_pending(pending_selection, SelectMode::Character);
15693 } else {
15694 s.clear_pending();
15695 }
15696 });
15697 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15698 }
15699
15700 fn push_to_selection_history(&mut self) {
15701 self.selection_history.push(SelectionHistoryEntry {
15702 selections: self.selections.disjoint_anchors(),
15703 select_next_state: self.select_next_state.clone(),
15704 select_prev_state: self.select_prev_state.clone(),
15705 add_selections_state: self.add_selections_state.clone(),
15706 });
15707 }
15708
15709 pub fn transact(
15710 &mut self,
15711 window: &mut Window,
15712 cx: &mut Context<Self>,
15713 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15714 ) -> Option<TransactionId> {
15715 self.start_transaction_at(Instant::now(), window, cx);
15716 update(self, window, cx);
15717 self.end_transaction_at(Instant::now(), cx)
15718 }
15719
15720 pub fn start_transaction_at(
15721 &mut self,
15722 now: Instant,
15723 window: &mut Window,
15724 cx: &mut Context<Self>,
15725 ) {
15726 self.end_selection(window, cx);
15727 if let Some(tx_id) = self
15728 .buffer
15729 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15730 {
15731 self.selection_history
15732 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15733 cx.emit(EditorEvent::TransactionBegun {
15734 transaction_id: tx_id,
15735 })
15736 }
15737 }
15738
15739 pub fn end_transaction_at(
15740 &mut self,
15741 now: Instant,
15742 cx: &mut Context<Self>,
15743 ) -> Option<TransactionId> {
15744 if let Some(transaction_id) = self
15745 .buffer
15746 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15747 {
15748 if let Some((_, end_selections)) =
15749 self.selection_history.transaction_mut(transaction_id)
15750 {
15751 *end_selections = Some(self.selections.disjoint_anchors());
15752 } else {
15753 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15754 }
15755
15756 cx.emit(EditorEvent::Edited { transaction_id });
15757 Some(transaction_id)
15758 } else {
15759 None
15760 }
15761 }
15762
15763 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15764 if self.selection_mark_mode {
15765 self.change_selections(None, window, cx, |s| {
15766 s.move_with(|_, sel| {
15767 sel.collapse_to(sel.head(), SelectionGoal::None);
15768 });
15769 })
15770 }
15771 self.selection_mark_mode = true;
15772 cx.notify();
15773 }
15774
15775 pub fn swap_selection_ends(
15776 &mut self,
15777 _: &actions::SwapSelectionEnds,
15778 window: &mut Window,
15779 cx: &mut Context<Self>,
15780 ) {
15781 self.change_selections(None, window, cx, |s| {
15782 s.move_with(|_, sel| {
15783 if sel.start != sel.end {
15784 sel.reversed = !sel.reversed
15785 }
15786 });
15787 });
15788 self.request_autoscroll(Autoscroll::newest(), cx);
15789 cx.notify();
15790 }
15791
15792 pub fn toggle_fold(
15793 &mut self,
15794 _: &actions::ToggleFold,
15795 window: &mut Window,
15796 cx: &mut Context<Self>,
15797 ) {
15798 if self.is_singleton(cx) {
15799 let selection = self.selections.newest::<Point>(cx);
15800
15801 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15802 let range = if selection.is_empty() {
15803 let point = selection.head().to_display_point(&display_map);
15804 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15805 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15806 .to_point(&display_map);
15807 start..end
15808 } else {
15809 selection.range()
15810 };
15811 if display_map.folds_in_range(range).next().is_some() {
15812 self.unfold_lines(&Default::default(), window, cx)
15813 } else {
15814 self.fold(&Default::default(), window, cx)
15815 }
15816 } else {
15817 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15818 let buffer_ids: HashSet<_> = self
15819 .selections
15820 .disjoint_anchor_ranges()
15821 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15822 .collect();
15823
15824 let should_unfold = buffer_ids
15825 .iter()
15826 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15827
15828 for buffer_id in buffer_ids {
15829 if should_unfold {
15830 self.unfold_buffer(buffer_id, cx);
15831 } else {
15832 self.fold_buffer(buffer_id, cx);
15833 }
15834 }
15835 }
15836 }
15837
15838 pub fn toggle_fold_recursive(
15839 &mut self,
15840 _: &actions::ToggleFoldRecursive,
15841 window: &mut Window,
15842 cx: &mut Context<Self>,
15843 ) {
15844 let selection = self.selections.newest::<Point>(cx);
15845
15846 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15847 let range = if selection.is_empty() {
15848 let point = selection.head().to_display_point(&display_map);
15849 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15850 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15851 .to_point(&display_map);
15852 start..end
15853 } else {
15854 selection.range()
15855 };
15856 if display_map.folds_in_range(range).next().is_some() {
15857 self.unfold_recursive(&Default::default(), window, cx)
15858 } else {
15859 self.fold_recursive(&Default::default(), window, cx)
15860 }
15861 }
15862
15863 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15864 if self.is_singleton(cx) {
15865 let mut to_fold = Vec::new();
15866 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15867 let selections = self.selections.all_adjusted(cx);
15868
15869 for selection in selections {
15870 let range = selection.range().sorted();
15871 let buffer_start_row = range.start.row;
15872
15873 if range.start.row != range.end.row {
15874 let mut found = false;
15875 let mut row = range.start.row;
15876 while row <= range.end.row {
15877 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15878 {
15879 found = true;
15880 row = crease.range().end.row + 1;
15881 to_fold.push(crease);
15882 } else {
15883 row += 1
15884 }
15885 }
15886 if found {
15887 continue;
15888 }
15889 }
15890
15891 for row in (0..=range.start.row).rev() {
15892 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15893 if crease.range().end.row >= buffer_start_row {
15894 to_fold.push(crease);
15895 if row <= range.start.row {
15896 break;
15897 }
15898 }
15899 }
15900 }
15901 }
15902
15903 self.fold_creases(to_fold, true, window, cx);
15904 } else {
15905 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15906 let buffer_ids = self
15907 .selections
15908 .disjoint_anchor_ranges()
15909 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15910 .collect::<HashSet<_>>();
15911 for buffer_id in buffer_ids {
15912 self.fold_buffer(buffer_id, cx);
15913 }
15914 }
15915 }
15916
15917 fn fold_at_level(
15918 &mut self,
15919 fold_at: &FoldAtLevel,
15920 window: &mut Window,
15921 cx: &mut Context<Self>,
15922 ) {
15923 if !self.buffer.read(cx).is_singleton() {
15924 return;
15925 }
15926
15927 let fold_at_level = fold_at.0;
15928 let snapshot = self.buffer.read(cx).snapshot(cx);
15929 let mut to_fold = Vec::new();
15930 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15931
15932 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15933 while start_row < end_row {
15934 match self
15935 .snapshot(window, cx)
15936 .crease_for_buffer_row(MultiBufferRow(start_row))
15937 {
15938 Some(crease) => {
15939 let nested_start_row = crease.range().start.row + 1;
15940 let nested_end_row = crease.range().end.row;
15941
15942 if current_level < fold_at_level {
15943 stack.push((nested_start_row, nested_end_row, current_level + 1));
15944 } else if current_level == fold_at_level {
15945 to_fold.push(crease);
15946 }
15947
15948 start_row = nested_end_row + 1;
15949 }
15950 None => start_row += 1,
15951 }
15952 }
15953 }
15954
15955 self.fold_creases(to_fold, true, window, cx);
15956 }
15957
15958 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15959 if self.buffer.read(cx).is_singleton() {
15960 let mut fold_ranges = Vec::new();
15961 let snapshot = self.buffer.read(cx).snapshot(cx);
15962
15963 for row in 0..snapshot.max_row().0 {
15964 if let Some(foldable_range) = self
15965 .snapshot(window, cx)
15966 .crease_for_buffer_row(MultiBufferRow(row))
15967 {
15968 fold_ranges.push(foldable_range);
15969 }
15970 }
15971
15972 self.fold_creases(fold_ranges, true, window, cx);
15973 } else {
15974 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15975 editor
15976 .update_in(cx, |editor, _, cx| {
15977 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15978 editor.fold_buffer(buffer_id, cx);
15979 }
15980 })
15981 .ok();
15982 });
15983 }
15984 }
15985
15986 pub fn fold_function_bodies(
15987 &mut self,
15988 _: &actions::FoldFunctionBodies,
15989 window: &mut Window,
15990 cx: &mut Context<Self>,
15991 ) {
15992 let snapshot = self.buffer.read(cx).snapshot(cx);
15993
15994 let ranges = snapshot
15995 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15996 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15997 .collect::<Vec<_>>();
15998
15999 let creases = ranges
16000 .into_iter()
16001 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16002 .collect();
16003
16004 self.fold_creases(creases, true, window, cx);
16005 }
16006
16007 pub fn fold_recursive(
16008 &mut self,
16009 _: &actions::FoldRecursive,
16010 window: &mut Window,
16011 cx: &mut Context<Self>,
16012 ) {
16013 let mut to_fold = Vec::new();
16014 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16015 let selections = self.selections.all_adjusted(cx);
16016
16017 for selection in selections {
16018 let range = selection.range().sorted();
16019 let buffer_start_row = range.start.row;
16020
16021 if range.start.row != range.end.row {
16022 let mut found = false;
16023 for row in range.start.row..=range.end.row {
16024 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16025 found = true;
16026 to_fold.push(crease);
16027 }
16028 }
16029 if found {
16030 continue;
16031 }
16032 }
16033
16034 for row in (0..=range.start.row).rev() {
16035 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16036 if crease.range().end.row >= buffer_start_row {
16037 to_fold.push(crease);
16038 } else {
16039 break;
16040 }
16041 }
16042 }
16043 }
16044
16045 self.fold_creases(to_fold, true, window, cx);
16046 }
16047
16048 pub fn fold_at(
16049 &mut self,
16050 buffer_row: MultiBufferRow,
16051 window: &mut Window,
16052 cx: &mut Context<Self>,
16053 ) {
16054 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16055
16056 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16057 let autoscroll = self
16058 .selections
16059 .all::<Point>(cx)
16060 .iter()
16061 .any(|selection| crease.range().overlaps(&selection.range()));
16062
16063 self.fold_creases(vec![crease], autoscroll, window, cx);
16064 }
16065 }
16066
16067 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16068 if self.is_singleton(cx) {
16069 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16070 let buffer = &display_map.buffer_snapshot;
16071 let selections = self.selections.all::<Point>(cx);
16072 let ranges = selections
16073 .iter()
16074 .map(|s| {
16075 let range = s.display_range(&display_map).sorted();
16076 let mut start = range.start.to_point(&display_map);
16077 let mut end = range.end.to_point(&display_map);
16078 start.column = 0;
16079 end.column = buffer.line_len(MultiBufferRow(end.row));
16080 start..end
16081 })
16082 .collect::<Vec<_>>();
16083
16084 self.unfold_ranges(&ranges, true, true, cx);
16085 } else {
16086 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16087 let buffer_ids = self
16088 .selections
16089 .disjoint_anchor_ranges()
16090 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16091 .collect::<HashSet<_>>();
16092 for buffer_id in buffer_ids {
16093 self.unfold_buffer(buffer_id, cx);
16094 }
16095 }
16096 }
16097
16098 pub fn unfold_recursive(
16099 &mut self,
16100 _: &UnfoldRecursive,
16101 _window: &mut Window,
16102 cx: &mut Context<Self>,
16103 ) {
16104 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16105 let selections = self.selections.all::<Point>(cx);
16106 let ranges = selections
16107 .iter()
16108 .map(|s| {
16109 let mut range = s.display_range(&display_map).sorted();
16110 *range.start.column_mut() = 0;
16111 *range.end.column_mut() = display_map.line_len(range.end.row());
16112 let start = range.start.to_point(&display_map);
16113 let end = range.end.to_point(&display_map);
16114 start..end
16115 })
16116 .collect::<Vec<_>>();
16117
16118 self.unfold_ranges(&ranges, true, true, cx);
16119 }
16120
16121 pub fn unfold_at(
16122 &mut self,
16123 buffer_row: MultiBufferRow,
16124 _window: &mut Window,
16125 cx: &mut Context<Self>,
16126 ) {
16127 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16128
16129 let intersection_range = Point::new(buffer_row.0, 0)
16130 ..Point::new(
16131 buffer_row.0,
16132 display_map.buffer_snapshot.line_len(buffer_row),
16133 );
16134
16135 let autoscroll = self
16136 .selections
16137 .all::<Point>(cx)
16138 .iter()
16139 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16140
16141 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16142 }
16143
16144 pub fn unfold_all(
16145 &mut self,
16146 _: &actions::UnfoldAll,
16147 _window: &mut Window,
16148 cx: &mut Context<Self>,
16149 ) {
16150 if self.buffer.read(cx).is_singleton() {
16151 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16152 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16153 } else {
16154 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16155 editor
16156 .update(cx, |editor, cx| {
16157 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16158 editor.unfold_buffer(buffer_id, cx);
16159 }
16160 })
16161 .ok();
16162 });
16163 }
16164 }
16165
16166 pub fn fold_selected_ranges(
16167 &mut self,
16168 _: &FoldSelectedRanges,
16169 window: &mut Window,
16170 cx: &mut Context<Self>,
16171 ) {
16172 let selections = self.selections.all_adjusted(cx);
16173 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16174 let ranges = selections
16175 .into_iter()
16176 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16177 .collect::<Vec<_>>();
16178 self.fold_creases(ranges, true, window, cx);
16179 }
16180
16181 pub fn fold_ranges<T: ToOffset + Clone>(
16182 &mut self,
16183 ranges: Vec<Range<T>>,
16184 auto_scroll: bool,
16185 window: &mut Window,
16186 cx: &mut Context<Self>,
16187 ) {
16188 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16189 let ranges = ranges
16190 .into_iter()
16191 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16192 .collect::<Vec<_>>();
16193 self.fold_creases(ranges, auto_scroll, window, cx);
16194 }
16195
16196 pub fn fold_creases<T: ToOffset + Clone>(
16197 &mut self,
16198 creases: Vec<Crease<T>>,
16199 auto_scroll: bool,
16200 _window: &mut Window,
16201 cx: &mut Context<Self>,
16202 ) {
16203 if creases.is_empty() {
16204 return;
16205 }
16206
16207 let mut buffers_affected = HashSet::default();
16208 let multi_buffer = self.buffer().read(cx);
16209 for crease in &creases {
16210 if let Some((_, buffer, _)) =
16211 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16212 {
16213 buffers_affected.insert(buffer.read(cx).remote_id());
16214 };
16215 }
16216
16217 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16218
16219 if auto_scroll {
16220 self.request_autoscroll(Autoscroll::fit(), cx);
16221 }
16222
16223 cx.notify();
16224
16225 self.scrollbar_marker_state.dirty = true;
16226 self.folds_did_change(cx);
16227 }
16228
16229 /// Removes any folds whose ranges intersect any of the given ranges.
16230 pub fn unfold_ranges<T: ToOffset + Clone>(
16231 &mut self,
16232 ranges: &[Range<T>],
16233 inclusive: bool,
16234 auto_scroll: bool,
16235 cx: &mut Context<Self>,
16236 ) {
16237 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16238 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16239 });
16240 self.folds_did_change(cx);
16241 }
16242
16243 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16244 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16245 return;
16246 }
16247 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16248 self.display_map.update(cx, |display_map, cx| {
16249 display_map.fold_buffers([buffer_id], cx)
16250 });
16251 cx.emit(EditorEvent::BufferFoldToggled {
16252 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16253 folded: true,
16254 });
16255 cx.notify();
16256 }
16257
16258 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16259 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16260 return;
16261 }
16262 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16263 self.display_map.update(cx, |display_map, cx| {
16264 display_map.unfold_buffers([buffer_id], cx);
16265 });
16266 cx.emit(EditorEvent::BufferFoldToggled {
16267 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16268 folded: false,
16269 });
16270 cx.notify();
16271 }
16272
16273 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16274 self.display_map.read(cx).is_buffer_folded(buffer)
16275 }
16276
16277 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16278 self.display_map.read(cx).folded_buffers()
16279 }
16280
16281 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16282 self.display_map.update(cx, |display_map, cx| {
16283 display_map.disable_header_for_buffer(buffer_id, cx);
16284 });
16285 cx.notify();
16286 }
16287
16288 /// Removes any folds with the given ranges.
16289 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16290 &mut self,
16291 ranges: &[Range<T>],
16292 type_id: TypeId,
16293 auto_scroll: bool,
16294 cx: &mut Context<Self>,
16295 ) {
16296 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16297 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16298 });
16299 self.folds_did_change(cx);
16300 }
16301
16302 fn remove_folds_with<T: ToOffset + Clone>(
16303 &mut self,
16304 ranges: &[Range<T>],
16305 auto_scroll: bool,
16306 cx: &mut Context<Self>,
16307 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16308 ) {
16309 if ranges.is_empty() {
16310 return;
16311 }
16312
16313 let mut buffers_affected = HashSet::default();
16314 let multi_buffer = self.buffer().read(cx);
16315 for range in ranges {
16316 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16317 buffers_affected.insert(buffer.read(cx).remote_id());
16318 };
16319 }
16320
16321 self.display_map.update(cx, update);
16322
16323 if auto_scroll {
16324 self.request_autoscroll(Autoscroll::fit(), cx);
16325 }
16326
16327 cx.notify();
16328 self.scrollbar_marker_state.dirty = true;
16329 self.active_indent_guides_state.dirty = true;
16330 }
16331
16332 pub fn update_fold_widths(
16333 &mut self,
16334 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16335 cx: &mut Context<Self>,
16336 ) -> bool {
16337 self.display_map
16338 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16339 }
16340
16341 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16342 self.display_map.read(cx).fold_placeholder.clone()
16343 }
16344
16345 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16346 self.buffer.update(cx, |buffer, cx| {
16347 buffer.set_all_diff_hunks_expanded(cx);
16348 });
16349 }
16350
16351 pub fn expand_all_diff_hunks(
16352 &mut self,
16353 _: &ExpandAllDiffHunks,
16354 _window: &mut Window,
16355 cx: &mut Context<Self>,
16356 ) {
16357 self.buffer.update(cx, |buffer, cx| {
16358 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16359 });
16360 }
16361
16362 pub fn toggle_selected_diff_hunks(
16363 &mut self,
16364 _: &ToggleSelectedDiffHunks,
16365 _window: &mut Window,
16366 cx: &mut Context<Self>,
16367 ) {
16368 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16369 self.toggle_diff_hunks_in_ranges(ranges, cx);
16370 }
16371
16372 pub fn diff_hunks_in_ranges<'a>(
16373 &'a self,
16374 ranges: &'a [Range<Anchor>],
16375 buffer: &'a MultiBufferSnapshot,
16376 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16377 ranges.iter().flat_map(move |range| {
16378 let end_excerpt_id = range.end.excerpt_id;
16379 let range = range.to_point(buffer);
16380 let mut peek_end = range.end;
16381 if range.end.row < buffer.max_row().0 {
16382 peek_end = Point::new(range.end.row + 1, 0);
16383 }
16384 buffer
16385 .diff_hunks_in_range(range.start..peek_end)
16386 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16387 })
16388 }
16389
16390 pub fn has_stageable_diff_hunks_in_ranges(
16391 &self,
16392 ranges: &[Range<Anchor>],
16393 snapshot: &MultiBufferSnapshot,
16394 ) -> bool {
16395 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16396 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16397 }
16398
16399 pub fn toggle_staged_selected_diff_hunks(
16400 &mut self,
16401 _: &::git::ToggleStaged,
16402 _: &mut Window,
16403 cx: &mut Context<Self>,
16404 ) {
16405 let snapshot = self.buffer.read(cx).snapshot(cx);
16406 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16407 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16408 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16409 }
16410
16411 pub fn set_render_diff_hunk_controls(
16412 &mut self,
16413 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16414 cx: &mut Context<Self>,
16415 ) {
16416 self.render_diff_hunk_controls = render_diff_hunk_controls;
16417 cx.notify();
16418 }
16419
16420 pub fn stage_and_next(
16421 &mut self,
16422 _: &::git::StageAndNext,
16423 window: &mut Window,
16424 cx: &mut Context<Self>,
16425 ) {
16426 self.do_stage_or_unstage_and_next(true, window, cx);
16427 }
16428
16429 pub fn unstage_and_next(
16430 &mut self,
16431 _: &::git::UnstageAndNext,
16432 window: &mut Window,
16433 cx: &mut Context<Self>,
16434 ) {
16435 self.do_stage_or_unstage_and_next(false, window, cx);
16436 }
16437
16438 pub fn stage_or_unstage_diff_hunks(
16439 &mut self,
16440 stage: bool,
16441 ranges: Vec<Range<Anchor>>,
16442 cx: &mut Context<Self>,
16443 ) {
16444 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16445 cx.spawn(async move |this, cx| {
16446 task.await?;
16447 this.update(cx, |this, cx| {
16448 let snapshot = this.buffer.read(cx).snapshot(cx);
16449 let chunk_by = this
16450 .diff_hunks_in_ranges(&ranges, &snapshot)
16451 .chunk_by(|hunk| hunk.buffer_id);
16452 for (buffer_id, hunks) in &chunk_by {
16453 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16454 }
16455 })
16456 })
16457 .detach_and_log_err(cx);
16458 }
16459
16460 fn save_buffers_for_ranges_if_needed(
16461 &mut self,
16462 ranges: &[Range<Anchor>],
16463 cx: &mut Context<Editor>,
16464 ) -> Task<Result<()>> {
16465 let multibuffer = self.buffer.read(cx);
16466 let snapshot = multibuffer.read(cx);
16467 let buffer_ids: HashSet<_> = ranges
16468 .iter()
16469 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16470 .collect();
16471 drop(snapshot);
16472
16473 let mut buffers = HashSet::default();
16474 for buffer_id in buffer_ids {
16475 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16476 let buffer = buffer_entity.read(cx);
16477 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16478 {
16479 buffers.insert(buffer_entity);
16480 }
16481 }
16482 }
16483
16484 if let Some(project) = &self.project {
16485 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16486 } else {
16487 Task::ready(Ok(()))
16488 }
16489 }
16490
16491 fn do_stage_or_unstage_and_next(
16492 &mut self,
16493 stage: bool,
16494 window: &mut Window,
16495 cx: &mut Context<Self>,
16496 ) {
16497 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16498
16499 if ranges.iter().any(|range| range.start != range.end) {
16500 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16501 return;
16502 }
16503
16504 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16505 let snapshot = self.snapshot(window, cx);
16506 let position = self.selections.newest::<Point>(cx).head();
16507 let mut row = snapshot
16508 .buffer_snapshot
16509 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16510 .find(|hunk| hunk.row_range.start.0 > position.row)
16511 .map(|hunk| hunk.row_range.start);
16512
16513 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16514 // Outside of the project diff editor, wrap around to the beginning.
16515 if !all_diff_hunks_expanded {
16516 row = row.or_else(|| {
16517 snapshot
16518 .buffer_snapshot
16519 .diff_hunks_in_range(Point::zero()..position)
16520 .find(|hunk| hunk.row_range.end.0 < position.row)
16521 .map(|hunk| hunk.row_range.start)
16522 });
16523 }
16524
16525 if let Some(row) = row {
16526 let destination = Point::new(row.0, 0);
16527 let autoscroll = Autoscroll::center();
16528
16529 self.unfold_ranges(&[destination..destination], false, false, cx);
16530 self.change_selections(Some(autoscroll), window, cx, |s| {
16531 s.select_ranges([destination..destination]);
16532 });
16533 }
16534 }
16535
16536 fn do_stage_or_unstage(
16537 &self,
16538 stage: bool,
16539 buffer_id: BufferId,
16540 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16541 cx: &mut App,
16542 ) -> Option<()> {
16543 let project = self.project.as_ref()?;
16544 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16545 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16546 let buffer_snapshot = buffer.read(cx).snapshot();
16547 let file_exists = buffer_snapshot
16548 .file()
16549 .is_some_and(|file| file.disk_state().exists());
16550 diff.update(cx, |diff, cx| {
16551 diff.stage_or_unstage_hunks(
16552 stage,
16553 &hunks
16554 .map(|hunk| buffer_diff::DiffHunk {
16555 buffer_range: hunk.buffer_range,
16556 diff_base_byte_range: hunk.diff_base_byte_range,
16557 secondary_status: hunk.secondary_status,
16558 range: Point::zero()..Point::zero(), // unused
16559 })
16560 .collect::<Vec<_>>(),
16561 &buffer_snapshot,
16562 file_exists,
16563 cx,
16564 )
16565 });
16566 None
16567 }
16568
16569 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16570 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16571 self.buffer
16572 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16573 }
16574
16575 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16576 self.buffer.update(cx, |buffer, cx| {
16577 let ranges = vec![Anchor::min()..Anchor::max()];
16578 if !buffer.all_diff_hunks_expanded()
16579 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16580 {
16581 buffer.collapse_diff_hunks(ranges, cx);
16582 true
16583 } else {
16584 false
16585 }
16586 })
16587 }
16588
16589 fn toggle_diff_hunks_in_ranges(
16590 &mut self,
16591 ranges: Vec<Range<Anchor>>,
16592 cx: &mut Context<Editor>,
16593 ) {
16594 self.buffer.update(cx, |buffer, cx| {
16595 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16596 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16597 })
16598 }
16599
16600 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16601 self.buffer.update(cx, |buffer, cx| {
16602 let snapshot = buffer.snapshot(cx);
16603 let excerpt_id = range.end.excerpt_id;
16604 let point_range = range.to_point(&snapshot);
16605 let expand = !buffer.single_hunk_is_expanded(range, cx);
16606 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16607 })
16608 }
16609
16610 pub(crate) fn apply_all_diff_hunks(
16611 &mut self,
16612 _: &ApplyAllDiffHunks,
16613 window: &mut Window,
16614 cx: &mut Context<Self>,
16615 ) {
16616 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16617
16618 let buffers = self.buffer.read(cx).all_buffers();
16619 for branch_buffer in buffers {
16620 branch_buffer.update(cx, |branch_buffer, cx| {
16621 branch_buffer.merge_into_base(Vec::new(), cx);
16622 });
16623 }
16624
16625 if let Some(project) = self.project.clone() {
16626 self.save(true, project, window, cx).detach_and_log_err(cx);
16627 }
16628 }
16629
16630 pub(crate) fn apply_selected_diff_hunks(
16631 &mut self,
16632 _: &ApplyDiffHunk,
16633 window: &mut Window,
16634 cx: &mut Context<Self>,
16635 ) {
16636 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16637 let snapshot = self.snapshot(window, cx);
16638 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16639 let mut ranges_by_buffer = HashMap::default();
16640 self.transact(window, cx, |editor, _window, cx| {
16641 for hunk in hunks {
16642 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16643 ranges_by_buffer
16644 .entry(buffer.clone())
16645 .or_insert_with(Vec::new)
16646 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16647 }
16648 }
16649
16650 for (buffer, ranges) in ranges_by_buffer {
16651 buffer.update(cx, |buffer, cx| {
16652 buffer.merge_into_base(ranges, cx);
16653 });
16654 }
16655 });
16656
16657 if let Some(project) = self.project.clone() {
16658 self.save(true, project, window, cx).detach_and_log_err(cx);
16659 }
16660 }
16661
16662 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16663 if hovered != self.gutter_hovered {
16664 self.gutter_hovered = hovered;
16665 cx.notify();
16666 }
16667 }
16668
16669 pub fn insert_blocks(
16670 &mut self,
16671 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16672 autoscroll: Option<Autoscroll>,
16673 cx: &mut Context<Self>,
16674 ) -> Vec<CustomBlockId> {
16675 let blocks = self
16676 .display_map
16677 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16678 if let Some(autoscroll) = autoscroll {
16679 self.request_autoscroll(autoscroll, cx);
16680 }
16681 cx.notify();
16682 blocks
16683 }
16684
16685 pub fn resize_blocks(
16686 &mut self,
16687 heights: HashMap<CustomBlockId, u32>,
16688 autoscroll: Option<Autoscroll>,
16689 cx: &mut Context<Self>,
16690 ) {
16691 self.display_map
16692 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16693 if let Some(autoscroll) = autoscroll {
16694 self.request_autoscroll(autoscroll, cx);
16695 }
16696 cx.notify();
16697 }
16698
16699 pub fn replace_blocks(
16700 &mut self,
16701 renderers: HashMap<CustomBlockId, RenderBlock>,
16702 autoscroll: Option<Autoscroll>,
16703 cx: &mut Context<Self>,
16704 ) {
16705 self.display_map
16706 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16707 if let Some(autoscroll) = autoscroll {
16708 self.request_autoscroll(autoscroll, cx);
16709 }
16710 cx.notify();
16711 }
16712
16713 pub fn remove_blocks(
16714 &mut self,
16715 block_ids: HashSet<CustomBlockId>,
16716 autoscroll: Option<Autoscroll>,
16717 cx: &mut Context<Self>,
16718 ) {
16719 self.display_map.update(cx, |display_map, cx| {
16720 display_map.remove_blocks(block_ids, cx)
16721 });
16722 if let Some(autoscroll) = autoscroll {
16723 self.request_autoscroll(autoscroll, cx);
16724 }
16725 cx.notify();
16726 }
16727
16728 pub fn row_for_block(
16729 &self,
16730 block_id: CustomBlockId,
16731 cx: &mut Context<Self>,
16732 ) -> Option<DisplayRow> {
16733 self.display_map
16734 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16735 }
16736
16737 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16738 self.focused_block = Some(focused_block);
16739 }
16740
16741 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16742 self.focused_block.take()
16743 }
16744
16745 pub fn insert_creases(
16746 &mut self,
16747 creases: impl IntoIterator<Item = Crease<Anchor>>,
16748 cx: &mut Context<Self>,
16749 ) -> Vec<CreaseId> {
16750 self.display_map
16751 .update(cx, |map, cx| map.insert_creases(creases, cx))
16752 }
16753
16754 pub fn remove_creases(
16755 &mut self,
16756 ids: impl IntoIterator<Item = CreaseId>,
16757 cx: &mut Context<Self>,
16758 ) -> Vec<(CreaseId, Range<Anchor>)> {
16759 self.display_map
16760 .update(cx, |map, cx| map.remove_creases(ids, cx))
16761 }
16762
16763 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16764 self.display_map
16765 .update(cx, |map, cx| map.snapshot(cx))
16766 .longest_row()
16767 }
16768
16769 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16770 self.display_map
16771 .update(cx, |map, cx| map.snapshot(cx))
16772 .max_point()
16773 }
16774
16775 pub fn text(&self, cx: &App) -> String {
16776 self.buffer.read(cx).read(cx).text()
16777 }
16778
16779 pub fn is_empty(&self, cx: &App) -> bool {
16780 self.buffer.read(cx).read(cx).is_empty()
16781 }
16782
16783 pub fn text_option(&self, cx: &App) -> Option<String> {
16784 let text = self.text(cx);
16785 let text = text.trim();
16786
16787 if text.is_empty() {
16788 return None;
16789 }
16790
16791 Some(text.to_string())
16792 }
16793
16794 pub fn set_text(
16795 &mut self,
16796 text: impl Into<Arc<str>>,
16797 window: &mut Window,
16798 cx: &mut Context<Self>,
16799 ) {
16800 self.transact(window, cx, |this, _, cx| {
16801 this.buffer
16802 .read(cx)
16803 .as_singleton()
16804 .expect("you can only call set_text on editors for singleton buffers")
16805 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16806 });
16807 }
16808
16809 pub fn display_text(&self, cx: &mut App) -> String {
16810 self.display_map
16811 .update(cx, |map, cx| map.snapshot(cx))
16812 .text()
16813 }
16814
16815 fn create_minimap(
16816 &self,
16817 minimap_settings: MinimapSettings,
16818 window: &mut Window,
16819 cx: &mut Context<Self>,
16820 ) -> Option<Entity<Self>> {
16821 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
16822 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
16823 }
16824
16825 fn initialize_new_minimap(
16826 &self,
16827 minimap_settings: MinimapSettings,
16828 window: &mut Window,
16829 cx: &mut Context<Self>,
16830 ) -> Entity<Self> {
16831 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
16832
16833 let mut minimap = Editor::new_internal(
16834 EditorMode::Minimap {
16835 parent: cx.weak_entity(),
16836 },
16837 self.buffer.clone(),
16838 self.project.clone(),
16839 Some(self.display_map.clone()),
16840 window,
16841 cx,
16842 );
16843 minimap.scroll_manager.clone_state(&self.scroll_manager);
16844 minimap.set_text_style_refinement(TextStyleRefinement {
16845 font_size: Some(MINIMAP_FONT_SIZE),
16846 font_weight: Some(MINIMAP_FONT_WEIGHT),
16847 ..Default::default()
16848 });
16849 minimap.update_minimap_configuration(minimap_settings, cx);
16850 cx.new(|_| minimap)
16851 }
16852
16853 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
16854 let current_line_highlight = minimap_settings
16855 .current_line_highlight
16856 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
16857 self.set_current_line_highlight(Some(current_line_highlight));
16858 }
16859
16860 pub fn minimap(&self) -> Option<&Entity<Self>> {
16861 self.minimap
16862 .as_ref()
16863 .filter(|_| self.minimap_visibility.visible())
16864 }
16865
16866 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16867 let mut wrap_guides = smallvec![];
16868
16869 if self.show_wrap_guides == Some(false) {
16870 return wrap_guides;
16871 }
16872
16873 let settings = self.buffer.read(cx).language_settings(cx);
16874 if settings.show_wrap_guides {
16875 match self.soft_wrap_mode(cx) {
16876 SoftWrap::Column(soft_wrap) => {
16877 wrap_guides.push((soft_wrap as usize, true));
16878 }
16879 SoftWrap::Bounded(soft_wrap) => {
16880 wrap_guides.push((soft_wrap as usize, true));
16881 }
16882 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16883 }
16884 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16885 }
16886
16887 wrap_guides
16888 }
16889
16890 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16891 let settings = self.buffer.read(cx).language_settings(cx);
16892 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16893 match mode {
16894 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16895 SoftWrap::None
16896 }
16897 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16898 language_settings::SoftWrap::PreferredLineLength => {
16899 SoftWrap::Column(settings.preferred_line_length)
16900 }
16901 language_settings::SoftWrap::Bounded => {
16902 SoftWrap::Bounded(settings.preferred_line_length)
16903 }
16904 }
16905 }
16906
16907 pub fn set_soft_wrap_mode(
16908 &mut self,
16909 mode: language_settings::SoftWrap,
16910
16911 cx: &mut Context<Self>,
16912 ) {
16913 self.soft_wrap_mode_override = Some(mode);
16914 cx.notify();
16915 }
16916
16917 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16918 self.hard_wrap = hard_wrap;
16919 cx.notify();
16920 }
16921
16922 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16923 self.text_style_refinement = Some(style);
16924 }
16925
16926 /// called by the Element so we know what style we were most recently rendered with.
16927 pub(crate) fn set_style(
16928 &mut self,
16929 style: EditorStyle,
16930 window: &mut Window,
16931 cx: &mut Context<Self>,
16932 ) {
16933 // We intentionally do not inform the display map about the minimap style
16934 // so that wrapping is not recalculated and stays consistent for the editor
16935 // and its linked minimap.
16936 if !self.mode.is_minimap() {
16937 let rem_size = window.rem_size();
16938 self.display_map.update(cx, |map, cx| {
16939 map.set_font(
16940 style.text.font(),
16941 style.text.font_size.to_pixels(rem_size),
16942 cx,
16943 )
16944 });
16945 }
16946 self.style = Some(style);
16947 }
16948
16949 pub fn style(&self) -> Option<&EditorStyle> {
16950 self.style.as_ref()
16951 }
16952
16953 // Called by the element. This method is not designed to be called outside of the editor
16954 // element's layout code because it does not notify when rewrapping is computed synchronously.
16955 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16956 self.display_map
16957 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16958 }
16959
16960 pub fn set_soft_wrap(&mut self) {
16961 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16962 }
16963
16964 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16965 if self.soft_wrap_mode_override.is_some() {
16966 self.soft_wrap_mode_override.take();
16967 } else {
16968 let soft_wrap = match self.soft_wrap_mode(cx) {
16969 SoftWrap::GitDiff => return,
16970 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16971 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16972 language_settings::SoftWrap::None
16973 }
16974 };
16975 self.soft_wrap_mode_override = Some(soft_wrap);
16976 }
16977 cx.notify();
16978 }
16979
16980 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16981 let Some(workspace) = self.workspace() else {
16982 return;
16983 };
16984 let fs = workspace.read(cx).app_state().fs.clone();
16985 let current_show = TabBarSettings::get_global(cx).show;
16986 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16987 setting.show = Some(!current_show);
16988 });
16989 }
16990
16991 pub fn toggle_indent_guides(
16992 &mut self,
16993 _: &ToggleIndentGuides,
16994 _: &mut Window,
16995 cx: &mut Context<Self>,
16996 ) {
16997 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16998 self.buffer
16999 .read(cx)
17000 .language_settings(cx)
17001 .indent_guides
17002 .enabled
17003 });
17004 self.show_indent_guides = Some(!currently_enabled);
17005 cx.notify();
17006 }
17007
17008 fn should_show_indent_guides(&self) -> Option<bool> {
17009 self.show_indent_guides
17010 }
17011
17012 pub fn toggle_line_numbers(
17013 &mut self,
17014 _: &ToggleLineNumbers,
17015 _: &mut Window,
17016 cx: &mut Context<Self>,
17017 ) {
17018 let mut editor_settings = EditorSettings::get_global(cx).clone();
17019 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17020 EditorSettings::override_global(editor_settings, cx);
17021 }
17022
17023 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17024 if let Some(show_line_numbers) = self.show_line_numbers {
17025 return show_line_numbers;
17026 }
17027 EditorSettings::get_global(cx).gutter.line_numbers
17028 }
17029
17030 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17031 self.use_relative_line_numbers
17032 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17033 }
17034
17035 pub fn toggle_relative_line_numbers(
17036 &mut self,
17037 _: &ToggleRelativeLineNumbers,
17038 _: &mut Window,
17039 cx: &mut Context<Self>,
17040 ) {
17041 let is_relative = self.should_use_relative_line_numbers(cx);
17042 self.set_relative_line_number(Some(!is_relative), cx)
17043 }
17044
17045 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17046 self.use_relative_line_numbers = is_relative;
17047 cx.notify();
17048 }
17049
17050 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17051 self.show_gutter = show_gutter;
17052 cx.notify();
17053 }
17054
17055 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17056 self.show_scrollbars = ScrollbarAxes {
17057 horizontal: show,
17058 vertical: show,
17059 };
17060 cx.notify();
17061 }
17062
17063 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17064 self.show_scrollbars.vertical = show;
17065 cx.notify();
17066 }
17067
17068 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17069 self.show_scrollbars.horizontal = show;
17070 cx.notify();
17071 }
17072
17073 pub fn set_minimap_visibility(
17074 &mut self,
17075 minimap_visibility: MinimapVisibility,
17076 window: &mut Window,
17077 cx: &mut Context<Self>,
17078 ) {
17079 if self.minimap_visibility != minimap_visibility {
17080 if minimap_visibility.visible() && self.minimap.is_none() {
17081 let minimap_settings = EditorSettings::get_global(cx).minimap;
17082 self.minimap =
17083 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17084 }
17085 self.minimap_visibility = minimap_visibility;
17086 cx.notify();
17087 }
17088 }
17089
17090 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17091 self.set_show_scrollbars(false, cx);
17092 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17093 }
17094
17095 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17096 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17097 }
17098
17099 /// Normally the text in full mode and auto height editors is padded on the
17100 /// left side by roughly half a character width for improved hit testing.
17101 ///
17102 /// Use this method to disable this for cases where this is not wanted (e.g.
17103 /// if you want to align the editor text with some other text above or below)
17104 /// or if you want to add this padding to single-line editors.
17105 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17106 self.offset_content = offset_content;
17107 cx.notify();
17108 }
17109
17110 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17111 self.show_line_numbers = Some(show_line_numbers);
17112 cx.notify();
17113 }
17114
17115 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17116 self.disable_expand_excerpt_buttons = true;
17117 cx.notify();
17118 }
17119
17120 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17121 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17122 cx.notify();
17123 }
17124
17125 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17126 self.show_code_actions = Some(show_code_actions);
17127 cx.notify();
17128 }
17129
17130 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17131 self.show_runnables = Some(show_runnables);
17132 cx.notify();
17133 }
17134
17135 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17136 self.show_breakpoints = Some(show_breakpoints);
17137 cx.notify();
17138 }
17139
17140 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17141 if self.display_map.read(cx).masked != masked {
17142 self.display_map.update(cx, |map, _| map.masked = masked);
17143 }
17144 cx.notify()
17145 }
17146
17147 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17148 self.show_wrap_guides = Some(show_wrap_guides);
17149 cx.notify();
17150 }
17151
17152 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17153 self.show_indent_guides = Some(show_indent_guides);
17154 cx.notify();
17155 }
17156
17157 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17158 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17159 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17160 if let Some(dir) = file.abs_path(cx).parent() {
17161 return Some(dir.to_owned());
17162 }
17163 }
17164
17165 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17166 return Some(project_path.path.to_path_buf());
17167 }
17168 }
17169
17170 None
17171 }
17172
17173 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17174 self.active_excerpt(cx)?
17175 .1
17176 .read(cx)
17177 .file()
17178 .and_then(|f| f.as_local())
17179 }
17180
17181 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17182 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17183 let buffer = buffer.read(cx);
17184 if let Some(project_path) = buffer.project_path(cx) {
17185 let project = self.project.as_ref()?.read(cx);
17186 project.absolute_path(&project_path, cx)
17187 } else {
17188 buffer
17189 .file()
17190 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17191 }
17192 })
17193 }
17194
17195 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17196 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17197 let project_path = buffer.read(cx).project_path(cx)?;
17198 let project = self.project.as_ref()?.read(cx);
17199 let entry = project.entry_for_path(&project_path, cx)?;
17200 let path = entry.path.to_path_buf();
17201 Some(path)
17202 })
17203 }
17204
17205 pub fn reveal_in_finder(
17206 &mut self,
17207 _: &RevealInFileManager,
17208 _window: &mut Window,
17209 cx: &mut Context<Self>,
17210 ) {
17211 if let Some(target) = self.target_file(cx) {
17212 cx.reveal_path(&target.abs_path(cx));
17213 }
17214 }
17215
17216 pub fn copy_path(
17217 &mut self,
17218 _: &zed_actions::workspace::CopyPath,
17219 _window: &mut Window,
17220 cx: &mut Context<Self>,
17221 ) {
17222 if let Some(path) = self.target_file_abs_path(cx) {
17223 if let Some(path) = path.to_str() {
17224 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17225 }
17226 }
17227 }
17228
17229 pub fn copy_relative_path(
17230 &mut self,
17231 _: &zed_actions::workspace::CopyRelativePath,
17232 _window: &mut Window,
17233 cx: &mut Context<Self>,
17234 ) {
17235 if let Some(path) = self.target_file_path(cx) {
17236 if let Some(path) = path.to_str() {
17237 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17238 }
17239 }
17240 }
17241
17242 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17243 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17244 buffer.read(cx).project_path(cx)
17245 } else {
17246 None
17247 }
17248 }
17249
17250 // Returns true if the editor handled a go-to-line request
17251 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17252 maybe!({
17253 let breakpoint_store = self.breakpoint_store.as_ref()?;
17254
17255 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17256 else {
17257 self.clear_row_highlights::<ActiveDebugLine>();
17258 return None;
17259 };
17260
17261 let position = active_stack_frame.position;
17262 let buffer_id = position.buffer_id?;
17263 let snapshot = self
17264 .project
17265 .as_ref()?
17266 .read(cx)
17267 .buffer_for_id(buffer_id, cx)?
17268 .read(cx)
17269 .snapshot();
17270
17271 let mut handled = false;
17272 for (id, ExcerptRange { context, .. }) in
17273 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17274 {
17275 if context.start.cmp(&position, &snapshot).is_ge()
17276 || context.end.cmp(&position, &snapshot).is_lt()
17277 {
17278 continue;
17279 }
17280 let snapshot = self.buffer.read(cx).snapshot(cx);
17281 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17282
17283 handled = true;
17284 self.clear_row_highlights::<ActiveDebugLine>();
17285
17286 self.go_to_line::<ActiveDebugLine>(
17287 multibuffer_anchor,
17288 Some(cx.theme().colors().editor_debugger_active_line_background),
17289 window,
17290 cx,
17291 );
17292
17293 cx.notify();
17294 }
17295
17296 handled.then_some(())
17297 })
17298 .is_some()
17299 }
17300
17301 pub fn copy_file_name_without_extension(
17302 &mut self,
17303 _: &CopyFileNameWithoutExtension,
17304 _: &mut Window,
17305 cx: &mut Context<Self>,
17306 ) {
17307 if let Some(file) = self.target_file(cx) {
17308 if let Some(file_stem) = file.path().file_stem() {
17309 if let Some(name) = file_stem.to_str() {
17310 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17311 }
17312 }
17313 }
17314 }
17315
17316 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17317 if let Some(file) = self.target_file(cx) {
17318 if let Some(file_name) = file.path().file_name() {
17319 if let Some(name) = file_name.to_str() {
17320 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17321 }
17322 }
17323 }
17324 }
17325
17326 pub fn toggle_git_blame(
17327 &mut self,
17328 _: &::git::Blame,
17329 window: &mut Window,
17330 cx: &mut Context<Self>,
17331 ) {
17332 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17333
17334 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17335 self.start_git_blame(true, window, cx);
17336 }
17337
17338 cx.notify();
17339 }
17340
17341 pub fn toggle_git_blame_inline(
17342 &mut self,
17343 _: &ToggleGitBlameInline,
17344 window: &mut Window,
17345 cx: &mut Context<Self>,
17346 ) {
17347 self.toggle_git_blame_inline_internal(true, window, cx);
17348 cx.notify();
17349 }
17350
17351 pub fn open_git_blame_commit(
17352 &mut self,
17353 _: &OpenGitBlameCommit,
17354 window: &mut Window,
17355 cx: &mut Context<Self>,
17356 ) {
17357 self.open_git_blame_commit_internal(window, cx);
17358 }
17359
17360 fn open_git_blame_commit_internal(
17361 &mut self,
17362 window: &mut Window,
17363 cx: &mut Context<Self>,
17364 ) -> Option<()> {
17365 let blame = self.blame.as_ref()?;
17366 let snapshot = self.snapshot(window, cx);
17367 let cursor = self.selections.newest::<Point>(cx).head();
17368 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17369 let blame_entry = blame
17370 .update(cx, |blame, cx| {
17371 blame
17372 .blame_for_rows(
17373 &[RowInfo {
17374 buffer_id: Some(buffer.remote_id()),
17375 buffer_row: Some(point.row),
17376 ..Default::default()
17377 }],
17378 cx,
17379 )
17380 .next()
17381 })
17382 .flatten()?;
17383 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17384 let repo = blame.read(cx).repository(cx)?;
17385 let workspace = self.workspace()?.downgrade();
17386 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17387 None
17388 }
17389
17390 pub fn git_blame_inline_enabled(&self) -> bool {
17391 self.git_blame_inline_enabled
17392 }
17393
17394 pub fn toggle_selection_menu(
17395 &mut self,
17396 _: &ToggleSelectionMenu,
17397 _: &mut Window,
17398 cx: &mut Context<Self>,
17399 ) {
17400 self.show_selection_menu = self
17401 .show_selection_menu
17402 .map(|show_selections_menu| !show_selections_menu)
17403 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17404
17405 cx.notify();
17406 }
17407
17408 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17409 self.show_selection_menu
17410 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17411 }
17412
17413 fn start_git_blame(
17414 &mut self,
17415 user_triggered: bool,
17416 window: &mut Window,
17417 cx: &mut Context<Self>,
17418 ) {
17419 if let Some(project) = self.project.as_ref() {
17420 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17421 return;
17422 };
17423
17424 if buffer.read(cx).file().is_none() {
17425 return;
17426 }
17427
17428 let focused = self.focus_handle(cx).contains_focused(window, cx);
17429
17430 let project = project.clone();
17431 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17432 self.blame_subscription =
17433 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17434 self.blame = Some(blame);
17435 }
17436 }
17437
17438 fn toggle_git_blame_inline_internal(
17439 &mut self,
17440 user_triggered: bool,
17441 window: &mut Window,
17442 cx: &mut Context<Self>,
17443 ) {
17444 if self.git_blame_inline_enabled {
17445 self.git_blame_inline_enabled = false;
17446 self.show_git_blame_inline = false;
17447 self.show_git_blame_inline_delay_task.take();
17448 } else {
17449 self.git_blame_inline_enabled = true;
17450 self.start_git_blame_inline(user_triggered, window, cx);
17451 }
17452
17453 cx.notify();
17454 }
17455
17456 fn start_git_blame_inline(
17457 &mut self,
17458 user_triggered: bool,
17459 window: &mut Window,
17460 cx: &mut Context<Self>,
17461 ) {
17462 self.start_git_blame(user_triggered, window, cx);
17463
17464 if ProjectSettings::get_global(cx)
17465 .git
17466 .inline_blame_delay()
17467 .is_some()
17468 {
17469 self.start_inline_blame_timer(window, cx);
17470 } else {
17471 self.show_git_blame_inline = true
17472 }
17473 }
17474
17475 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17476 self.blame.as_ref()
17477 }
17478
17479 pub fn show_git_blame_gutter(&self) -> bool {
17480 self.show_git_blame_gutter
17481 }
17482
17483 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17484 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17485 }
17486
17487 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17488 self.show_git_blame_inline
17489 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17490 && !self.newest_selection_head_on_empty_line(cx)
17491 && self.has_blame_entries(cx)
17492 }
17493
17494 fn has_blame_entries(&self, cx: &App) -> bool {
17495 self.blame()
17496 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17497 }
17498
17499 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17500 let cursor_anchor = self.selections.newest_anchor().head();
17501
17502 let snapshot = self.buffer.read(cx).snapshot(cx);
17503 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17504
17505 snapshot.line_len(buffer_row) == 0
17506 }
17507
17508 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17509 let buffer_and_selection = maybe!({
17510 let selection = self.selections.newest::<Point>(cx);
17511 let selection_range = selection.range();
17512
17513 let multi_buffer = self.buffer().read(cx);
17514 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17515 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17516
17517 let (buffer, range, _) = if selection.reversed {
17518 buffer_ranges.first()
17519 } else {
17520 buffer_ranges.last()
17521 }?;
17522
17523 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17524 ..text::ToPoint::to_point(&range.end, &buffer).row;
17525 Some((
17526 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17527 selection,
17528 ))
17529 });
17530
17531 let Some((buffer, selection)) = buffer_and_selection else {
17532 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17533 };
17534
17535 let Some(project) = self.project.as_ref() else {
17536 return Task::ready(Err(anyhow!("editor does not have project")));
17537 };
17538
17539 project.update(cx, |project, cx| {
17540 project.get_permalink_to_line(&buffer, selection, cx)
17541 })
17542 }
17543
17544 pub fn copy_permalink_to_line(
17545 &mut self,
17546 _: &CopyPermalinkToLine,
17547 window: &mut Window,
17548 cx: &mut Context<Self>,
17549 ) {
17550 let permalink_task = self.get_permalink_to_line(cx);
17551 let workspace = self.workspace();
17552
17553 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17554 Ok(permalink) => {
17555 cx.update(|_, cx| {
17556 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17557 })
17558 .ok();
17559 }
17560 Err(err) => {
17561 let message = format!("Failed to copy permalink: {err}");
17562
17563 anyhow::Result::<()>::Err(err).log_err();
17564
17565 if let Some(workspace) = workspace {
17566 workspace
17567 .update_in(cx, |workspace, _, cx| {
17568 struct CopyPermalinkToLine;
17569
17570 workspace.show_toast(
17571 Toast::new(
17572 NotificationId::unique::<CopyPermalinkToLine>(),
17573 message,
17574 ),
17575 cx,
17576 )
17577 })
17578 .ok();
17579 }
17580 }
17581 })
17582 .detach();
17583 }
17584
17585 pub fn copy_file_location(
17586 &mut self,
17587 _: &CopyFileLocation,
17588 _: &mut Window,
17589 cx: &mut Context<Self>,
17590 ) {
17591 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17592 if let Some(file) = self.target_file(cx) {
17593 if let Some(path) = file.path().to_str() {
17594 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17595 }
17596 }
17597 }
17598
17599 pub fn open_permalink_to_line(
17600 &mut self,
17601 _: &OpenPermalinkToLine,
17602 window: &mut Window,
17603 cx: &mut Context<Self>,
17604 ) {
17605 let permalink_task = self.get_permalink_to_line(cx);
17606 let workspace = self.workspace();
17607
17608 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17609 Ok(permalink) => {
17610 cx.update(|_, cx| {
17611 cx.open_url(permalink.as_ref());
17612 })
17613 .ok();
17614 }
17615 Err(err) => {
17616 let message = format!("Failed to open permalink: {err}");
17617
17618 anyhow::Result::<()>::Err(err).log_err();
17619
17620 if let Some(workspace) = workspace {
17621 workspace
17622 .update(cx, |workspace, cx| {
17623 struct OpenPermalinkToLine;
17624
17625 workspace.show_toast(
17626 Toast::new(
17627 NotificationId::unique::<OpenPermalinkToLine>(),
17628 message,
17629 ),
17630 cx,
17631 )
17632 })
17633 .ok();
17634 }
17635 }
17636 })
17637 .detach();
17638 }
17639
17640 pub fn insert_uuid_v4(
17641 &mut self,
17642 _: &InsertUuidV4,
17643 window: &mut Window,
17644 cx: &mut Context<Self>,
17645 ) {
17646 self.insert_uuid(UuidVersion::V4, window, cx);
17647 }
17648
17649 pub fn insert_uuid_v7(
17650 &mut self,
17651 _: &InsertUuidV7,
17652 window: &mut Window,
17653 cx: &mut Context<Self>,
17654 ) {
17655 self.insert_uuid(UuidVersion::V7, window, cx);
17656 }
17657
17658 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17659 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17660 self.transact(window, cx, |this, window, cx| {
17661 let edits = this
17662 .selections
17663 .all::<Point>(cx)
17664 .into_iter()
17665 .map(|selection| {
17666 let uuid = match version {
17667 UuidVersion::V4 => uuid::Uuid::new_v4(),
17668 UuidVersion::V7 => uuid::Uuid::now_v7(),
17669 };
17670
17671 (selection.range(), uuid.to_string())
17672 });
17673 this.edit(edits, cx);
17674 this.refresh_inline_completion(true, false, window, cx);
17675 });
17676 }
17677
17678 pub fn open_selections_in_multibuffer(
17679 &mut self,
17680 _: &OpenSelectionsInMultibuffer,
17681 window: &mut Window,
17682 cx: &mut Context<Self>,
17683 ) {
17684 let multibuffer = self.buffer.read(cx);
17685
17686 let Some(buffer) = multibuffer.as_singleton() else {
17687 return;
17688 };
17689
17690 let Some(workspace) = self.workspace() else {
17691 return;
17692 };
17693
17694 let locations = self
17695 .selections
17696 .disjoint_anchors()
17697 .iter()
17698 .map(|selection| {
17699 let range = if selection.reversed {
17700 selection.end.text_anchor..selection.start.text_anchor
17701 } else {
17702 selection.start.text_anchor..selection.end.text_anchor
17703 };
17704 Location {
17705 buffer: buffer.clone(),
17706 range,
17707 }
17708 })
17709 .collect::<Vec<_>>();
17710
17711 let title = multibuffer.title(cx).to_string();
17712
17713 cx.spawn_in(window, async move |_, cx| {
17714 workspace.update_in(cx, |workspace, window, cx| {
17715 Self::open_locations_in_multibuffer(
17716 workspace,
17717 locations,
17718 format!("Selections for '{title}'"),
17719 false,
17720 MultibufferSelectionMode::All,
17721 window,
17722 cx,
17723 );
17724 })
17725 })
17726 .detach();
17727 }
17728
17729 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17730 /// last highlight added will be used.
17731 ///
17732 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17733 pub fn highlight_rows<T: 'static>(
17734 &mut self,
17735 range: Range<Anchor>,
17736 color: Hsla,
17737 options: RowHighlightOptions,
17738 cx: &mut Context<Self>,
17739 ) {
17740 let snapshot = self.buffer().read(cx).snapshot(cx);
17741 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17742 let ix = row_highlights.binary_search_by(|highlight| {
17743 Ordering::Equal
17744 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17745 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17746 });
17747
17748 if let Err(mut ix) = ix {
17749 let index = post_inc(&mut self.highlight_order);
17750
17751 // If this range intersects with the preceding highlight, then merge it with
17752 // the preceding highlight. Otherwise insert a new highlight.
17753 let mut merged = false;
17754 if ix > 0 {
17755 let prev_highlight = &mut row_highlights[ix - 1];
17756 if prev_highlight
17757 .range
17758 .end
17759 .cmp(&range.start, &snapshot)
17760 .is_ge()
17761 {
17762 ix -= 1;
17763 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17764 prev_highlight.range.end = range.end;
17765 }
17766 merged = true;
17767 prev_highlight.index = index;
17768 prev_highlight.color = color;
17769 prev_highlight.options = options;
17770 }
17771 }
17772
17773 if !merged {
17774 row_highlights.insert(
17775 ix,
17776 RowHighlight {
17777 range: range.clone(),
17778 index,
17779 color,
17780 options,
17781 type_id: TypeId::of::<T>(),
17782 },
17783 );
17784 }
17785
17786 // If any of the following highlights intersect with this one, merge them.
17787 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17788 let highlight = &row_highlights[ix];
17789 if next_highlight
17790 .range
17791 .start
17792 .cmp(&highlight.range.end, &snapshot)
17793 .is_le()
17794 {
17795 if next_highlight
17796 .range
17797 .end
17798 .cmp(&highlight.range.end, &snapshot)
17799 .is_gt()
17800 {
17801 row_highlights[ix].range.end = next_highlight.range.end;
17802 }
17803 row_highlights.remove(ix + 1);
17804 } else {
17805 break;
17806 }
17807 }
17808 }
17809 }
17810
17811 /// Remove any highlighted row ranges of the given type that intersect the
17812 /// given ranges.
17813 pub fn remove_highlighted_rows<T: 'static>(
17814 &mut self,
17815 ranges_to_remove: Vec<Range<Anchor>>,
17816 cx: &mut Context<Self>,
17817 ) {
17818 let snapshot = self.buffer().read(cx).snapshot(cx);
17819 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17820 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17821 row_highlights.retain(|highlight| {
17822 while let Some(range_to_remove) = ranges_to_remove.peek() {
17823 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17824 Ordering::Less | Ordering::Equal => {
17825 ranges_to_remove.next();
17826 }
17827 Ordering::Greater => {
17828 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17829 Ordering::Less | Ordering::Equal => {
17830 return false;
17831 }
17832 Ordering::Greater => break,
17833 }
17834 }
17835 }
17836 }
17837
17838 true
17839 })
17840 }
17841
17842 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17843 pub fn clear_row_highlights<T: 'static>(&mut self) {
17844 self.highlighted_rows.remove(&TypeId::of::<T>());
17845 }
17846
17847 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17848 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17849 self.highlighted_rows
17850 .get(&TypeId::of::<T>())
17851 .map_or(&[] as &[_], |vec| vec.as_slice())
17852 .iter()
17853 .map(|highlight| (highlight.range.clone(), highlight.color))
17854 }
17855
17856 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17857 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17858 /// Allows to ignore certain kinds of highlights.
17859 pub fn highlighted_display_rows(
17860 &self,
17861 window: &mut Window,
17862 cx: &mut App,
17863 ) -> BTreeMap<DisplayRow, LineHighlight> {
17864 let snapshot = self.snapshot(window, cx);
17865 let mut used_highlight_orders = HashMap::default();
17866 self.highlighted_rows
17867 .iter()
17868 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17869 .fold(
17870 BTreeMap::<DisplayRow, LineHighlight>::new(),
17871 |mut unique_rows, highlight| {
17872 let start = highlight.range.start.to_display_point(&snapshot);
17873 let end = highlight.range.end.to_display_point(&snapshot);
17874 let start_row = start.row().0;
17875 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17876 && end.column() == 0
17877 {
17878 end.row().0.saturating_sub(1)
17879 } else {
17880 end.row().0
17881 };
17882 for row in start_row..=end_row {
17883 let used_index =
17884 used_highlight_orders.entry(row).or_insert(highlight.index);
17885 if highlight.index >= *used_index {
17886 *used_index = highlight.index;
17887 unique_rows.insert(
17888 DisplayRow(row),
17889 LineHighlight {
17890 include_gutter: highlight.options.include_gutter,
17891 border: None,
17892 background: highlight.color.into(),
17893 type_id: Some(highlight.type_id),
17894 },
17895 );
17896 }
17897 }
17898 unique_rows
17899 },
17900 )
17901 }
17902
17903 pub fn highlighted_display_row_for_autoscroll(
17904 &self,
17905 snapshot: &DisplaySnapshot,
17906 ) -> Option<DisplayRow> {
17907 self.highlighted_rows
17908 .values()
17909 .flat_map(|highlighted_rows| highlighted_rows.iter())
17910 .filter_map(|highlight| {
17911 if highlight.options.autoscroll {
17912 Some(highlight.range.start.to_display_point(snapshot).row())
17913 } else {
17914 None
17915 }
17916 })
17917 .min()
17918 }
17919
17920 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17921 self.highlight_background::<SearchWithinRange>(
17922 ranges,
17923 |colors| colors.editor_document_highlight_read_background,
17924 cx,
17925 )
17926 }
17927
17928 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17929 self.breadcrumb_header = Some(new_header);
17930 }
17931
17932 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17933 self.clear_background_highlights::<SearchWithinRange>(cx);
17934 }
17935
17936 pub fn highlight_background<T: 'static>(
17937 &mut self,
17938 ranges: &[Range<Anchor>],
17939 color_fetcher: fn(&ThemeColors) -> Hsla,
17940 cx: &mut Context<Self>,
17941 ) {
17942 self.background_highlights
17943 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17944 self.scrollbar_marker_state.dirty = true;
17945 cx.notify();
17946 }
17947
17948 pub fn clear_background_highlights<T: 'static>(
17949 &mut self,
17950 cx: &mut Context<Self>,
17951 ) -> Option<BackgroundHighlight> {
17952 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17953 if !text_highlights.1.is_empty() {
17954 self.scrollbar_marker_state.dirty = true;
17955 cx.notify();
17956 }
17957 Some(text_highlights)
17958 }
17959
17960 pub fn highlight_gutter<T: 'static>(
17961 &mut self,
17962 ranges: &[Range<Anchor>],
17963 color_fetcher: fn(&App) -> Hsla,
17964 cx: &mut Context<Self>,
17965 ) {
17966 self.gutter_highlights
17967 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17968 cx.notify();
17969 }
17970
17971 pub fn clear_gutter_highlights<T: 'static>(
17972 &mut self,
17973 cx: &mut Context<Self>,
17974 ) -> Option<GutterHighlight> {
17975 cx.notify();
17976 self.gutter_highlights.remove(&TypeId::of::<T>())
17977 }
17978
17979 #[cfg(feature = "test-support")]
17980 pub fn all_text_background_highlights(
17981 &self,
17982 window: &mut Window,
17983 cx: &mut Context<Self>,
17984 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17985 let snapshot = self.snapshot(window, cx);
17986 let buffer = &snapshot.buffer_snapshot;
17987 let start = buffer.anchor_before(0);
17988 let end = buffer.anchor_after(buffer.len());
17989 let theme = cx.theme().colors();
17990 self.background_highlights_in_range(start..end, &snapshot, theme)
17991 }
17992
17993 #[cfg(feature = "test-support")]
17994 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17995 let snapshot = self.buffer().read(cx).snapshot(cx);
17996
17997 let highlights = self
17998 .background_highlights
17999 .get(&TypeId::of::<items::BufferSearchHighlights>());
18000
18001 if let Some((_color, ranges)) = highlights {
18002 ranges
18003 .iter()
18004 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18005 .collect_vec()
18006 } else {
18007 vec![]
18008 }
18009 }
18010
18011 fn document_highlights_for_position<'a>(
18012 &'a self,
18013 position: Anchor,
18014 buffer: &'a MultiBufferSnapshot,
18015 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18016 let read_highlights = self
18017 .background_highlights
18018 .get(&TypeId::of::<DocumentHighlightRead>())
18019 .map(|h| &h.1);
18020 let write_highlights = self
18021 .background_highlights
18022 .get(&TypeId::of::<DocumentHighlightWrite>())
18023 .map(|h| &h.1);
18024 let left_position = position.bias_left(buffer);
18025 let right_position = position.bias_right(buffer);
18026 read_highlights
18027 .into_iter()
18028 .chain(write_highlights)
18029 .flat_map(move |ranges| {
18030 let start_ix = match ranges.binary_search_by(|probe| {
18031 let cmp = probe.end.cmp(&left_position, buffer);
18032 if cmp.is_ge() {
18033 Ordering::Greater
18034 } else {
18035 Ordering::Less
18036 }
18037 }) {
18038 Ok(i) | Err(i) => i,
18039 };
18040
18041 ranges[start_ix..]
18042 .iter()
18043 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
18044 })
18045 }
18046
18047 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18048 self.background_highlights
18049 .get(&TypeId::of::<T>())
18050 .map_or(false, |(_, highlights)| !highlights.is_empty())
18051 }
18052
18053 pub fn background_highlights_in_range(
18054 &self,
18055 search_range: Range<Anchor>,
18056 display_snapshot: &DisplaySnapshot,
18057 theme: &ThemeColors,
18058 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18059 let mut results = Vec::new();
18060 for (color_fetcher, ranges) in self.background_highlights.values() {
18061 let color = color_fetcher(theme);
18062 let start_ix = match ranges.binary_search_by(|probe| {
18063 let cmp = probe
18064 .end
18065 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18066 if cmp.is_gt() {
18067 Ordering::Greater
18068 } else {
18069 Ordering::Less
18070 }
18071 }) {
18072 Ok(i) | Err(i) => i,
18073 };
18074 for range in &ranges[start_ix..] {
18075 if range
18076 .start
18077 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18078 .is_ge()
18079 {
18080 break;
18081 }
18082
18083 let start = range.start.to_display_point(display_snapshot);
18084 let end = range.end.to_display_point(display_snapshot);
18085 results.push((start..end, color))
18086 }
18087 }
18088 results
18089 }
18090
18091 pub fn background_highlight_row_ranges<T: 'static>(
18092 &self,
18093 search_range: Range<Anchor>,
18094 display_snapshot: &DisplaySnapshot,
18095 count: usize,
18096 ) -> Vec<RangeInclusive<DisplayPoint>> {
18097 let mut results = Vec::new();
18098 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
18099 return vec![];
18100 };
18101
18102 let start_ix = match ranges.binary_search_by(|probe| {
18103 let cmp = probe
18104 .end
18105 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18106 if cmp.is_gt() {
18107 Ordering::Greater
18108 } else {
18109 Ordering::Less
18110 }
18111 }) {
18112 Ok(i) | Err(i) => i,
18113 };
18114 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18115 if let (Some(start_display), Some(end_display)) = (start, end) {
18116 results.push(
18117 start_display.to_display_point(display_snapshot)
18118 ..=end_display.to_display_point(display_snapshot),
18119 );
18120 }
18121 };
18122 let mut start_row: Option<Point> = None;
18123 let mut end_row: Option<Point> = None;
18124 if ranges.len() > count {
18125 return Vec::new();
18126 }
18127 for range in &ranges[start_ix..] {
18128 if range
18129 .start
18130 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18131 .is_ge()
18132 {
18133 break;
18134 }
18135 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18136 if let Some(current_row) = &end_row {
18137 if end.row == current_row.row {
18138 continue;
18139 }
18140 }
18141 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18142 if start_row.is_none() {
18143 assert_eq!(end_row, None);
18144 start_row = Some(start);
18145 end_row = Some(end);
18146 continue;
18147 }
18148 if let Some(current_end) = end_row.as_mut() {
18149 if start.row > current_end.row + 1 {
18150 push_region(start_row, end_row);
18151 start_row = Some(start);
18152 end_row = Some(end);
18153 } else {
18154 // Merge two hunks.
18155 *current_end = end;
18156 }
18157 } else {
18158 unreachable!();
18159 }
18160 }
18161 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18162 push_region(start_row, end_row);
18163 results
18164 }
18165
18166 pub fn gutter_highlights_in_range(
18167 &self,
18168 search_range: Range<Anchor>,
18169 display_snapshot: &DisplaySnapshot,
18170 cx: &App,
18171 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18172 let mut results = Vec::new();
18173 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18174 let color = color_fetcher(cx);
18175 let start_ix = match ranges.binary_search_by(|probe| {
18176 let cmp = probe
18177 .end
18178 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18179 if cmp.is_gt() {
18180 Ordering::Greater
18181 } else {
18182 Ordering::Less
18183 }
18184 }) {
18185 Ok(i) | Err(i) => i,
18186 };
18187 for range in &ranges[start_ix..] {
18188 if range
18189 .start
18190 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18191 .is_ge()
18192 {
18193 break;
18194 }
18195
18196 let start = range.start.to_display_point(display_snapshot);
18197 let end = range.end.to_display_point(display_snapshot);
18198 results.push((start..end, color))
18199 }
18200 }
18201 results
18202 }
18203
18204 /// Get the text ranges corresponding to the redaction query
18205 pub fn redacted_ranges(
18206 &self,
18207 search_range: Range<Anchor>,
18208 display_snapshot: &DisplaySnapshot,
18209 cx: &App,
18210 ) -> Vec<Range<DisplayPoint>> {
18211 display_snapshot
18212 .buffer_snapshot
18213 .redacted_ranges(search_range, |file| {
18214 if let Some(file) = file {
18215 file.is_private()
18216 && EditorSettings::get(
18217 Some(SettingsLocation {
18218 worktree_id: file.worktree_id(cx),
18219 path: file.path().as_ref(),
18220 }),
18221 cx,
18222 )
18223 .redact_private_values
18224 } else {
18225 false
18226 }
18227 })
18228 .map(|range| {
18229 range.start.to_display_point(display_snapshot)
18230 ..range.end.to_display_point(display_snapshot)
18231 })
18232 .collect()
18233 }
18234
18235 pub fn highlight_text<T: 'static>(
18236 &mut self,
18237 ranges: Vec<Range<Anchor>>,
18238 style: HighlightStyle,
18239 cx: &mut Context<Self>,
18240 ) {
18241 self.display_map.update(cx, |map, _| {
18242 map.highlight_text(TypeId::of::<T>(), ranges, style)
18243 });
18244 cx.notify();
18245 }
18246
18247 pub(crate) fn highlight_inlays<T: 'static>(
18248 &mut self,
18249 highlights: Vec<InlayHighlight>,
18250 style: HighlightStyle,
18251 cx: &mut Context<Self>,
18252 ) {
18253 self.display_map.update(cx, |map, _| {
18254 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18255 });
18256 cx.notify();
18257 }
18258
18259 pub fn text_highlights<'a, T: 'static>(
18260 &'a self,
18261 cx: &'a App,
18262 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18263 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18264 }
18265
18266 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18267 let cleared = self
18268 .display_map
18269 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18270 if cleared {
18271 cx.notify();
18272 }
18273 }
18274
18275 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18276 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18277 && self.focus_handle.is_focused(window)
18278 }
18279
18280 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18281 self.show_cursor_when_unfocused = is_enabled;
18282 cx.notify();
18283 }
18284
18285 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18286 cx.notify();
18287 }
18288
18289 fn on_debug_session_event(
18290 &mut self,
18291 _session: Entity<Session>,
18292 event: &SessionEvent,
18293 cx: &mut Context<Self>,
18294 ) {
18295 match event {
18296 SessionEvent::InvalidateInlineValue => {
18297 self.refresh_inline_values(cx);
18298 }
18299 _ => {}
18300 }
18301 }
18302
18303 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18304 let Some(project) = self.project.clone() else {
18305 return;
18306 };
18307
18308 if !self.inline_value_cache.enabled {
18309 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18310 self.splice_inlays(&inlays, Vec::new(), cx);
18311 return;
18312 }
18313
18314 let current_execution_position = self
18315 .highlighted_rows
18316 .get(&TypeId::of::<ActiveDebugLine>())
18317 .and_then(|lines| lines.last().map(|line| line.range.start));
18318
18319 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18320 let inline_values = editor
18321 .update(cx, |editor, cx| {
18322 let Some(current_execution_position) = current_execution_position else {
18323 return Some(Task::ready(Ok(Vec::new())));
18324 };
18325
18326 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18327 let snapshot = buffer.snapshot(cx);
18328
18329 let excerpt = snapshot.excerpt_containing(
18330 current_execution_position..current_execution_position,
18331 )?;
18332
18333 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18334 })?;
18335
18336 let range =
18337 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18338
18339 project.inline_values(buffer, range, cx)
18340 })
18341 .ok()
18342 .flatten()?
18343 .await
18344 .context("refreshing debugger inlays")
18345 .log_err()?;
18346
18347 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18348
18349 for (buffer_id, inline_value) in inline_values
18350 .into_iter()
18351 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18352 {
18353 buffer_inline_values
18354 .entry(buffer_id)
18355 .or_default()
18356 .push(inline_value);
18357 }
18358
18359 editor
18360 .update(cx, |editor, cx| {
18361 let snapshot = editor.buffer.read(cx).snapshot(cx);
18362 let mut new_inlays = Vec::default();
18363
18364 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18365 let buffer_id = buffer_snapshot.remote_id();
18366 buffer_inline_values
18367 .get(&buffer_id)
18368 .into_iter()
18369 .flatten()
18370 .for_each(|hint| {
18371 let inlay = Inlay::debugger_hint(
18372 post_inc(&mut editor.next_inlay_id),
18373 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18374 hint.text(),
18375 );
18376
18377 new_inlays.push(inlay);
18378 });
18379 }
18380
18381 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18382 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18383
18384 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18385 })
18386 .ok()?;
18387 Some(())
18388 });
18389 }
18390
18391 fn on_buffer_event(
18392 &mut self,
18393 multibuffer: &Entity<MultiBuffer>,
18394 event: &multi_buffer::Event,
18395 window: &mut Window,
18396 cx: &mut Context<Self>,
18397 ) {
18398 match event {
18399 multi_buffer::Event::Edited {
18400 singleton_buffer_edited,
18401 edited_buffer: buffer_edited,
18402 } => {
18403 self.scrollbar_marker_state.dirty = true;
18404 self.active_indent_guides_state.dirty = true;
18405 self.refresh_active_diagnostics(cx);
18406 self.refresh_code_actions(window, cx);
18407 self.refresh_selected_text_highlights(true, window, cx);
18408 refresh_matching_bracket_highlights(self, window, cx);
18409 if self.has_active_inline_completion() {
18410 self.update_visible_inline_completion(window, cx);
18411 }
18412 if let Some(buffer) = buffer_edited {
18413 let buffer_id = buffer.read(cx).remote_id();
18414 if !self.registered_buffers.contains_key(&buffer_id) {
18415 if let Some(project) = self.project.as_ref() {
18416 project.update(cx, |project, cx| {
18417 self.registered_buffers.insert(
18418 buffer_id,
18419 project.register_buffer_with_language_servers(&buffer, cx),
18420 );
18421 })
18422 }
18423 }
18424 }
18425 cx.emit(EditorEvent::BufferEdited);
18426 cx.emit(SearchEvent::MatchesInvalidated);
18427 if *singleton_buffer_edited {
18428 if let Some(project) = &self.project {
18429 #[allow(clippy::mutable_key_type)]
18430 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18431 multibuffer
18432 .all_buffers()
18433 .into_iter()
18434 .filter_map(|buffer| {
18435 buffer.update(cx, |buffer, cx| {
18436 let language = buffer.language()?;
18437 let should_discard = project.update(cx, |project, cx| {
18438 project.is_local()
18439 && !project.has_language_servers_for(buffer, cx)
18440 });
18441 should_discard.not().then_some(language.clone())
18442 })
18443 })
18444 .collect::<HashSet<_>>()
18445 });
18446 if !languages_affected.is_empty() {
18447 self.refresh_inlay_hints(
18448 InlayHintRefreshReason::BufferEdited(languages_affected),
18449 cx,
18450 );
18451 }
18452 }
18453 }
18454
18455 let Some(project) = &self.project else { return };
18456 let (telemetry, is_via_ssh) = {
18457 let project = project.read(cx);
18458 let telemetry = project.client().telemetry().clone();
18459 let is_via_ssh = project.is_via_ssh();
18460 (telemetry, is_via_ssh)
18461 };
18462 refresh_linked_ranges(self, window, cx);
18463 telemetry.log_edit_event("editor", is_via_ssh);
18464 }
18465 multi_buffer::Event::ExcerptsAdded {
18466 buffer,
18467 predecessor,
18468 excerpts,
18469 } => {
18470 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18471 let buffer_id = buffer.read(cx).remote_id();
18472 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18473 if let Some(project) = &self.project {
18474 update_uncommitted_diff_for_buffer(
18475 cx.entity(),
18476 project,
18477 [buffer.clone()],
18478 self.buffer.clone(),
18479 cx,
18480 )
18481 .detach();
18482 }
18483 }
18484 cx.emit(EditorEvent::ExcerptsAdded {
18485 buffer: buffer.clone(),
18486 predecessor: *predecessor,
18487 excerpts: excerpts.clone(),
18488 });
18489 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18490 }
18491 multi_buffer::Event::ExcerptsRemoved {
18492 ids,
18493 removed_buffer_ids,
18494 } => {
18495 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18496 let buffer = self.buffer.read(cx);
18497 self.registered_buffers
18498 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18499 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18500 cx.emit(EditorEvent::ExcerptsRemoved {
18501 ids: ids.clone(),
18502 removed_buffer_ids: removed_buffer_ids.clone(),
18503 })
18504 }
18505 multi_buffer::Event::ExcerptsEdited {
18506 excerpt_ids,
18507 buffer_ids,
18508 } => {
18509 self.display_map.update(cx, |map, cx| {
18510 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18511 });
18512 cx.emit(EditorEvent::ExcerptsEdited {
18513 ids: excerpt_ids.clone(),
18514 })
18515 }
18516 multi_buffer::Event::ExcerptsExpanded { ids } => {
18517 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18518 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18519 }
18520 multi_buffer::Event::Reparsed(buffer_id) => {
18521 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18522 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18523
18524 cx.emit(EditorEvent::Reparsed(*buffer_id));
18525 }
18526 multi_buffer::Event::DiffHunksToggled => {
18527 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18528 }
18529 multi_buffer::Event::LanguageChanged(buffer_id) => {
18530 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18531 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18532 cx.emit(EditorEvent::Reparsed(*buffer_id));
18533 cx.notify();
18534 }
18535 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18536 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18537 multi_buffer::Event::FileHandleChanged
18538 | multi_buffer::Event::Reloaded
18539 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18540 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18541 multi_buffer::Event::DiagnosticsUpdated => {
18542 self.refresh_active_diagnostics(cx);
18543 self.refresh_inline_diagnostics(true, window, cx);
18544 self.scrollbar_marker_state.dirty = true;
18545 cx.notify();
18546 }
18547 _ => {}
18548 };
18549 }
18550
18551 pub fn start_temporary_diff_override(&mut self) {
18552 self.load_diff_task.take();
18553 self.temporary_diff_override = true;
18554 }
18555
18556 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18557 self.temporary_diff_override = false;
18558 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18559 self.buffer.update(cx, |buffer, cx| {
18560 buffer.set_all_diff_hunks_collapsed(cx);
18561 });
18562
18563 if let Some(project) = self.project.clone() {
18564 self.load_diff_task = Some(
18565 update_uncommitted_diff_for_buffer(
18566 cx.entity(),
18567 &project,
18568 self.buffer.read(cx).all_buffers(),
18569 self.buffer.clone(),
18570 cx,
18571 )
18572 .shared(),
18573 );
18574 }
18575 }
18576
18577 fn on_display_map_changed(
18578 &mut self,
18579 _: Entity<DisplayMap>,
18580 _: &mut Window,
18581 cx: &mut Context<Self>,
18582 ) {
18583 cx.notify();
18584 }
18585
18586 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18587 let new_severity = if self.diagnostics_enabled() {
18588 EditorSettings::get_global(cx)
18589 .diagnostics_max_severity
18590 .unwrap_or(DiagnosticSeverity::Hint)
18591 } else {
18592 DiagnosticSeverity::Off
18593 };
18594 self.set_max_diagnostics_severity(new_severity, cx);
18595 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18596 self.update_edit_prediction_settings(cx);
18597 self.refresh_inline_completion(true, false, window, cx);
18598 self.refresh_inlay_hints(
18599 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18600 self.selections.newest_anchor().head(),
18601 &self.buffer.read(cx).snapshot(cx),
18602 cx,
18603 )),
18604 cx,
18605 );
18606
18607 let old_cursor_shape = self.cursor_shape;
18608
18609 {
18610 let editor_settings = EditorSettings::get_global(cx);
18611 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18612 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18613 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18614 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18615 }
18616
18617 if old_cursor_shape != self.cursor_shape {
18618 cx.emit(EditorEvent::CursorShapeChanged);
18619 }
18620
18621 let project_settings = ProjectSettings::get_global(cx);
18622 self.serialize_dirty_buffers =
18623 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18624
18625 if self.mode.is_full() {
18626 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18627 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18628 if self.show_inline_diagnostics != show_inline_diagnostics {
18629 self.show_inline_diagnostics = show_inline_diagnostics;
18630 self.refresh_inline_diagnostics(false, window, cx);
18631 }
18632
18633 if self.git_blame_inline_enabled != inline_blame_enabled {
18634 self.toggle_git_blame_inline_internal(false, window, cx);
18635 }
18636
18637 let minimap_settings = EditorSettings::get_global(cx).minimap;
18638 if self.minimap_visibility != MinimapVisibility::Disabled {
18639 if self.minimap_visibility.settings_visibility()
18640 != minimap_settings.minimap_enabled()
18641 {
18642 self.set_minimap_visibility(
18643 MinimapVisibility::for_mode(self.mode(), cx),
18644 window,
18645 cx,
18646 );
18647 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18648 minimap_entity.update(cx, |minimap_editor, cx| {
18649 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18650 })
18651 }
18652 }
18653 }
18654
18655 cx.notify();
18656 }
18657
18658 pub fn set_searchable(&mut self, searchable: bool) {
18659 self.searchable = searchable;
18660 }
18661
18662 pub fn searchable(&self) -> bool {
18663 self.searchable
18664 }
18665
18666 fn open_proposed_changes_editor(
18667 &mut self,
18668 _: &OpenProposedChangesEditor,
18669 window: &mut Window,
18670 cx: &mut Context<Self>,
18671 ) {
18672 let Some(workspace) = self.workspace() else {
18673 cx.propagate();
18674 return;
18675 };
18676
18677 let selections = self.selections.all::<usize>(cx);
18678 let multi_buffer = self.buffer.read(cx);
18679 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18680 let mut new_selections_by_buffer = HashMap::default();
18681 for selection in selections {
18682 for (buffer, range, _) in
18683 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18684 {
18685 let mut range = range.to_point(buffer);
18686 range.start.column = 0;
18687 range.end.column = buffer.line_len(range.end.row);
18688 new_selections_by_buffer
18689 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18690 .or_insert(Vec::new())
18691 .push(range)
18692 }
18693 }
18694
18695 let proposed_changes_buffers = new_selections_by_buffer
18696 .into_iter()
18697 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18698 .collect::<Vec<_>>();
18699 let proposed_changes_editor = cx.new(|cx| {
18700 ProposedChangesEditor::new(
18701 "Proposed changes",
18702 proposed_changes_buffers,
18703 self.project.clone(),
18704 window,
18705 cx,
18706 )
18707 });
18708
18709 window.defer(cx, move |window, cx| {
18710 workspace.update(cx, |workspace, cx| {
18711 workspace.active_pane().update(cx, |pane, cx| {
18712 pane.add_item(
18713 Box::new(proposed_changes_editor),
18714 true,
18715 true,
18716 None,
18717 window,
18718 cx,
18719 );
18720 });
18721 });
18722 });
18723 }
18724
18725 pub fn open_excerpts_in_split(
18726 &mut self,
18727 _: &OpenExcerptsSplit,
18728 window: &mut Window,
18729 cx: &mut Context<Self>,
18730 ) {
18731 self.open_excerpts_common(None, true, window, cx)
18732 }
18733
18734 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18735 self.open_excerpts_common(None, false, window, cx)
18736 }
18737
18738 fn open_excerpts_common(
18739 &mut self,
18740 jump_data: Option<JumpData>,
18741 split: bool,
18742 window: &mut Window,
18743 cx: &mut Context<Self>,
18744 ) {
18745 let Some(workspace) = self.workspace() else {
18746 cx.propagate();
18747 return;
18748 };
18749
18750 if self.buffer.read(cx).is_singleton() {
18751 cx.propagate();
18752 return;
18753 }
18754
18755 let mut new_selections_by_buffer = HashMap::default();
18756 match &jump_data {
18757 Some(JumpData::MultiBufferPoint {
18758 excerpt_id,
18759 position,
18760 anchor,
18761 line_offset_from_top,
18762 }) => {
18763 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18764 if let Some(buffer) = multi_buffer_snapshot
18765 .buffer_id_for_excerpt(*excerpt_id)
18766 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18767 {
18768 let buffer_snapshot = buffer.read(cx).snapshot();
18769 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18770 language::ToPoint::to_point(anchor, &buffer_snapshot)
18771 } else {
18772 buffer_snapshot.clip_point(*position, Bias::Left)
18773 };
18774 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18775 new_selections_by_buffer.insert(
18776 buffer,
18777 (
18778 vec![jump_to_offset..jump_to_offset],
18779 Some(*line_offset_from_top),
18780 ),
18781 );
18782 }
18783 }
18784 Some(JumpData::MultiBufferRow {
18785 row,
18786 line_offset_from_top,
18787 }) => {
18788 let point = MultiBufferPoint::new(row.0, 0);
18789 if let Some((buffer, buffer_point, _)) =
18790 self.buffer.read(cx).point_to_buffer_point(point, cx)
18791 {
18792 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18793 new_selections_by_buffer
18794 .entry(buffer)
18795 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18796 .0
18797 .push(buffer_offset..buffer_offset)
18798 }
18799 }
18800 None => {
18801 let selections = self.selections.all::<usize>(cx);
18802 let multi_buffer = self.buffer.read(cx);
18803 for selection in selections {
18804 for (snapshot, range, _, anchor) in multi_buffer
18805 .snapshot(cx)
18806 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18807 {
18808 if let Some(anchor) = anchor {
18809 // selection is in a deleted hunk
18810 let Some(buffer_id) = anchor.buffer_id else {
18811 continue;
18812 };
18813 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18814 continue;
18815 };
18816 let offset = text::ToOffset::to_offset(
18817 &anchor.text_anchor,
18818 &buffer_handle.read(cx).snapshot(),
18819 );
18820 let range = offset..offset;
18821 new_selections_by_buffer
18822 .entry(buffer_handle)
18823 .or_insert((Vec::new(), None))
18824 .0
18825 .push(range)
18826 } else {
18827 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18828 else {
18829 continue;
18830 };
18831 new_selections_by_buffer
18832 .entry(buffer_handle)
18833 .or_insert((Vec::new(), None))
18834 .0
18835 .push(range)
18836 }
18837 }
18838 }
18839 }
18840 }
18841
18842 new_selections_by_buffer
18843 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18844
18845 if new_selections_by_buffer.is_empty() {
18846 return;
18847 }
18848
18849 // We defer the pane interaction because we ourselves are a workspace item
18850 // and activating a new item causes the pane to call a method on us reentrantly,
18851 // which panics if we're on the stack.
18852 window.defer(cx, move |window, cx| {
18853 workspace.update(cx, |workspace, cx| {
18854 let pane = if split {
18855 workspace.adjacent_pane(window, cx)
18856 } else {
18857 workspace.active_pane().clone()
18858 };
18859
18860 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18861 let editor = buffer
18862 .read(cx)
18863 .file()
18864 .is_none()
18865 .then(|| {
18866 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18867 // so `workspace.open_project_item` will never find them, always opening a new editor.
18868 // Instead, we try to activate the existing editor in the pane first.
18869 let (editor, pane_item_index) =
18870 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18871 let editor = item.downcast::<Editor>()?;
18872 let singleton_buffer =
18873 editor.read(cx).buffer().read(cx).as_singleton()?;
18874 if singleton_buffer == buffer {
18875 Some((editor, i))
18876 } else {
18877 None
18878 }
18879 })?;
18880 pane.update(cx, |pane, cx| {
18881 pane.activate_item(pane_item_index, true, true, window, cx)
18882 });
18883 Some(editor)
18884 })
18885 .flatten()
18886 .unwrap_or_else(|| {
18887 workspace.open_project_item::<Self>(
18888 pane.clone(),
18889 buffer,
18890 true,
18891 true,
18892 window,
18893 cx,
18894 )
18895 });
18896
18897 editor.update(cx, |editor, cx| {
18898 let autoscroll = match scroll_offset {
18899 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18900 None => Autoscroll::newest(),
18901 };
18902 let nav_history = editor.nav_history.take();
18903 editor.change_selections(Some(autoscroll), window, cx, |s| {
18904 s.select_ranges(ranges);
18905 });
18906 editor.nav_history = nav_history;
18907 });
18908 }
18909 })
18910 });
18911 }
18912
18913 // For now, don't allow opening excerpts in buffers that aren't backed by
18914 // regular project files.
18915 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18916 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18917 }
18918
18919 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18920 let snapshot = self.buffer.read(cx).read(cx);
18921 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18922 Some(
18923 ranges
18924 .iter()
18925 .map(move |range| {
18926 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18927 })
18928 .collect(),
18929 )
18930 }
18931
18932 fn selection_replacement_ranges(
18933 &self,
18934 range: Range<OffsetUtf16>,
18935 cx: &mut App,
18936 ) -> Vec<Range<OffsetUtf16>> {
18937 let selections = self.selections.all::<OffsetUtf16>(cx);
18938 let newest_selection = selections
18939 .iter()
18940 .max_by_key(|selection| selection.id)
18941 .unwrap();
18942 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
18943 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
18944 let snapshot = self.buffer.read(cx).read(cx);
18945 selections
18946 .into_iter()
18947 .map(|mut selection| {
18948 selection.start.0 =
18949 (selection.start.0 as isize).saturating_add(start_delta) as usize;
18950 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
18951 snapshot.clip_offset_utf16(selection.start, Bias::Left)
18952 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
18953 })
18954 .collect()
18955 }
18956
18957 fn report_editor_event(
18958 &self,
18959 event_type: &'static str,
18960 file_extension: Option<String>,
18961 cx: &App,
18962 ) {
18963 if cfg!(any(test, feature = "test-support")) {
18964 return;
18965 }
18966
18967 let Some(project) = &self.project else { return };
18968
18969 // If None, we are in a file without an extension
18970 let file = self
18971 .buffer
18972 .read(cx)
18973 .as_singleton()
18974 .and_then(|b| b.read(cx).file());
18975 let file_extension = file_extension.or(file
18976 .as_ref()
18977 .and_then(|file| Path::new(file.file_name(cx)).extension())
18978 .and_then(|e| e.to_str())
18979 .map(|a| a.to_string()));
18980
18981 let vim_mode = vim_enabled(cx);
18982
18983 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
18984 let copilot_enabled = edit_predictions_provider
18985 == language::language_settings::EditPredictionProvider::Copilot;
18986 let copilot_enabled_for_language = self
18987 .buffer
18988 .read(cx)
18989 .language_settings(cx)
18990 .show_edit_predictions;
18991
18992 let project = project.read(cx);
18993 telemetry::event!(
18994 event_type,
18995 file_extension,
18996 vim_mode,
18997 copilot_enabled,
18998 copilot_enabled_for_language,
18999 edit_predictions_provider,
19000 is_via_ssh = project.is_via_ssh(),
19001 );
19002 }
19003
19004 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19005 /// with each line being an array of {text, highlight} objects.
19006 fn copy_highlight_json(
19007 &mut self,
19008 _: &CopyHighlightJson,
19009 window: &mut Window,
19010 cx: &mut Context<Self>,
19011 ) {
19012 #[derive(Serialize)]
19013 struct Chunk<'a> {
19014 text: String,
19015 highlight: Option<&'a str>,
19016 }
19017
19018 let snapshot = self.buffer.read(cx).snapshot(cx);
19019 let range = self
19020 .selected_text_range(false, window, cx)
19021 .and_then(|selection| {
19022 if selection.range.is_empty() {
19023 None
19024 } else {
19025 Some(selection.range)
19026 }
19027 })
19028 .unwrap_or_else(|| 0..snapshot.len());
19029
19030 let chunks = snapshot.chunks(range, true);
19031 let mut lines = Vec::new();
19032 let mut line: VecDeque<Chunk> = VecDeque::new();
19033
19034 let Some(style) = self.style.as_ref() else {
19035 return;
19036 };
19037
19038 for chunk in chunks {
19039 let highlight = chunk
19040 .syntax_highlight_id
19041 .and_then(|id| id.name(&style.syntax));
19042 let mut chunk_lines = chunk.text.split('\n').peekable();
19043 while let Some(text) = chunk_lines.next() {
19044 let mut merged_with_last_token = false;
19045 if let Some(last_token) = line.back_mut() {
19046 if last_token.highlight == highlight {
19047 last_token.text.push_str(text);
19048 merged_with_last_token = true;
19049 }
19050 }
19051
19052 if !merged_with_last_token {
19053 line.push_back(Chunk {
19054 text: text.into(),
19055 highlight,
19056 });
19057 }
19058
19059 if chunk_lines.peek().is_some() {
19060 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19061 line.pop_front();
19062 }
19063 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19064 line.pop_back();
19065 }
19066
19067 lines.push(mem::take(&mut line));
19068 }
19069 }
19070 }
19071
19072 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19073 return;
19074 };
19075 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19076 }
19077
19078 pub fn open_context_menu(
19079 &mut self,
19080 _: &OpenContextMenu,
19081 window: &mut Window,
19082 cx: &mut Context<Self>,
19083 ) {
19084 self.request_autoscroll(Autoscroll::newest(), cx);
19085 let position = self.selections.newest_display(cx).start;
19086 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19087 }
19088
19089 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19090 &self.inlay_hint_cache
19091 }
19092
19093 pub fn replay_insert_event(
19094 &mut self,
19095 text: &str,
19096 relative_utf16_range: Option<Range<isize>>,
19097 window: &mut Window,
19098 cx: &mut Context<Self>,
19099 ) {
19100 if !self.input_enabled {
19101 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19102 return;
19103 }
19104 if let Some(relative_utf16_range) = relative_utf16_range {
19105 let selections = self.selections.all::<OffsetUtf16>(cx);
19106 self.change_selections(None, window, cx, |s| {
19107 let new_ranges = selections.into_iter().map(|range| {
19108 let start = OffsetUtf16(
19109 range
19110 .head()
19111 .0
19112 .saturating_add_signed(relative_utf16_range.start),
19113 );
19114 let end = OffsetUtf16(
19115 range
19116 .head()
19117 .0
19118 .saturating_add_signed(relative_utf16_range.end),
19119 );
19120 start..end
19121 });
19122 s.select_ranges(new_ranges);
19123 });
19124 }
19125
19126 self.handle_input(text, window, cx);
19127 }
19128
19129 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19130 let Some(provider) = self.semantics_provider.as_ref() else {
19131 return false;
19132 };
19133
19134 let mut supports = false;
19135 self.buffer().update(cx, |this, cx| {
19136 this.for_each_buffer(|buffer| {
19137 supports |= provider.supports_inlay_hints(buffer, cx);
19138 });
19139 });
19140
19141 supports
19142 }
19143
19144 pub fn is_focused(&self, window: &Window) -> bool {
19145 self.focus_handle.is_focused(window)
19146 }
19147
19148 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19149 cx.emit(EditorEvent::Focused);
19150
19151 if let Some(descendant) = self
19152 .last_focused_descendant
19153 .take()
19154 .and_then(|descendant| descendant.upgrade())
19155 {
19156 window.focus(&descendant);
19157 } else {
19158 if let Some(blame) = self.blame.as_ref() {
19159 blame.update(cx, GitBlame::focus)
19160 }
19161
19162 self.blink_manager.update(cx, BlinkManager::enable);
19163 self.show_cursor_names(window, cx);
19164 self.buffer.update(cx, |buffer, cx| {
19165 buffer.finalize_last_transaction(cx);
19166 if self.leader_id.is_none() {
19167 buffer.set_active_selections(
19168 &self.selections.disjoint_anchors(),
19169 self.selections.line_mode,
19170 self.cursor_shape,
19171 cx,
19172 );
19173 }
19174 });
19175 }
19176 }
19177
19178 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19179 cx.emit(EditorEvent::FocusedIn)
19180 }
19181
19182 fn handle_focus_out(
19183 &mut self,
19184 event: FocusOutEvent,
19185 _window: &mut Window,
19186 cx: &mut Context<Self>,
19187 ) {
19188 if event.blurred != self.focus_handle {
19189 self.last_focused_descendant = Some(event.blurred);
19190 }
19191 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19192 }
19193
19194 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19195 self.blink_manager.update(cx, BlinkManager::disable);
19196 self.buffer
19197 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19198
19199 if let Some(blame) = self.blame.as_ref() {
19200 blame.update(cx, GitBlame::blur)
19201 }
19202 if !self.hover_state.focused(window, cx) {
19203 hide_hover(self, cx);
19204 }
19205 if !self
19206 .context_menu
19207 .borrow()
19208 .as_ref()
19209 .is_some_and(|context_menu| context_menu.focused(window, cx))
19210 {
19211 self.hide_context_menu(window, cx);
19212 }
19213 self.discard_inline_completion(false, cx);
19214 cx.emit(EditorEvent::Blurred);
19215 cx.notify();
19216 }
19217
19218 pub fn register_action<A: Action>(
19219 &mut self,
19220 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19221 ) -> Subscription {
19222 let id = self.next_editor_action_id.post_inc();
19223 let listener = Arc::new(listener);
19224 self.editor_actions.borrow_mut().insert(
19225 id,
19226 Box::new(move |window, _| {
19227 let listener = listener.clone();
19228 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19229 let action = action.downcast_ref().unwrap();
19230 if phase == DispatchPhase::Bubble {
19231 listener(action, window, cx)
19232 }
19233 })
19234 }),
19235 );
19236
19237 let editor_actions = self.editor_actions.clone();
19238 Subscription::new(move || {
19239 editor_actions.borrow_mut().remove(&id);
19240 })
19241 }
19242
19243 pub fn file_header_size(&self) -> u32 {
19244 FILE_HEADER_HEIGHT
19245 }
19246
19247 pub fn restore(
19248 &mut self,
19249 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19250 window: &mut Window,
19251 cx: &mut Context<Self>,
19252 ) {
19253 let workspace = self.workspace();
19254 let project = self.project.as_ref();
19255 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19256 let mut tasks = Vec::new();
19257 for (buffer_id, changes) in revert_changes {
19258 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19259 buffer.update(cx, |buffer, cx| {
19260 buffer.edit(
19261 changes
19262 .into_iter()
19263 .map(|(range, text)| (range, text.to_string())),
19264 None,
19265 cx,
19266 );
19267 });
19268
19269 if let Some(project) =
19270 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19271 {
19272 project.update(cx, |project, cx| {
19273 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19274 })
19275 }
19276 }
19277 }
19278 tasks
19279 });
19280 cx.spawn_in(window, async move |_, cx| {
19281 for (buffer, task) in save_tasks {
19282 let result = task.await;
19283 if result.is_err() {
19284 let Some(path) = buffer
19285 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19286 .ok()
19287 else {
19288 continue;
19289 };
19290 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19291 let Some(task) = cx
19292 .update_window_entity(&workspace, |workspace, window, cx| {
19293 workspace
19294 .open_path_preview(path, None, false, false, false, window, cx)
19295 })
19296 .ok()
19297 else {
19298 continue;
19299 };
19300 task.await.log_err();
19301 }
19302 }
19303 }
19304 })
19305 .detach();
19306 self.change_selections(None, window, cx, |selections| selections.refresh());
19307 }
19308
19309 pub fn to_pixel_point(
19310 &self,
19311 source: multi_buffer::Anchor,
19312 editor_snapshot: &EditorSnapshot,
19313 window: &mut Window,
19314 ) -> Option<gpui::Point<Pixels>> {
19315 let source_point = source.to_display_point(editor_snapshot);
19316 self.display_to_pixel_point(source_point, editor_snapshot, window)
19317 }
19318
19319 pub fn display_to_pixel_point(
19320 &self,
19321 source: DisplayPoint,
19322 editor_snapshot: &EditorSnapshot,
19323 window: &mut Window,
19324 ) -> Option<gpui::Point<Pixels>> {
19325 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19326 let text_layout_details = self.text_layout_details(window);
19327 let scroll_top = text_layout_details
19328 .scroll_anchor
19329 .scroll_position(editor_snapshot)
19330 .y;
19331
19332 if source.row().as_f32() < scroll_top.floor() {
19333 return None;
19334 }
19335 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19336 let source_y = line_height * (source.row().as_f32() - scroll_top);
19337 Some(gpui::Point::new(source_x, source_y))
19338 }
19339
19340 pub fn has_visible_completions_menu(&self) -> bool {
19341 !self.edit_prediction_preview_is_active()
19342 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19343 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19344 })
19345 }
19346
19347 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19348 if self.mode.is_minimap() {
19349 return;
19350 }
19351 self.addons
19352 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19353 }
19354
19355 pub fn unregister_addon<T: Addon>(&mut self) {
19356 self.addons.remove(&std::any::TypeId::of::<T>());
19357 }
19358
19359 pub fn addon<T: Addon>(&self) -> Option<&T> {
19360 let type_id = std::any::TypeId::of::<T>();
19361 self.addons
19362 .get(&type_id)
19363 .and_then(|item| item.to_any().downcast_ref::<T>())
19364 }
19365
19366 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19367 let type_id = std::any::TypeId::of::<T>();
19368 self.addons
19369 .get_mut(&type_id)
19370 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19371 }
19372
19373 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19374 let text_layout_details = self.text_layout_details(window);
19375 let style = &text_layout_details.editor_style;
19376 let font_id = window.text_system().resolve_font(&style.text.font());
19377 let font_size = style.text.font_size.to_pixels(window.rem_size());
19378 let line_height = style.text.line_height_in_pixels(window.rem_size());
19379 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19380
19381 gpui::Size::new(em_width, line_height)
19382 }
19383
19384 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
19385 self.load_diff_task.clone()
19386 }
19387
19388 fn read_metadata_from_db(
19389 &mut self,
19390 item_id: u64,
19391 workspace_id: WorkspaceId,
19392 window: &mut Window,
19393 cx: &mut Context<Editor>,
19394 ) {
19395 if self.is_singleton(cx)
19396 && !self.mode.is_minimap()
19397 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
19398 {
19399 let buffer_snapshot = OnceCell::new();
19400
19401 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
19402 if !folds.is_empty() {
19403 let snapshot =
19404 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19405 self.fold_ranges(
19406 folds
19407 .into_iter()
19408 .map(|(start, end)| {
19409 snapshot.clip_offset(start, Bias::Left)
19410 ..snapshot.clip_offset(end, Bias::Right)
19411 })
19412 .collect(),
19413 false,
19414 window,
19415 cx,
19416 );
19417 }
19418 }
19419
19420 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
19421 if !selections.is_empty() {
19422 let snapshot =
19423 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19424 self.change_selections(None, window, cx, |s| {
19425 s.select_ranges(selections.into_iter().map(|(start, end)| {
19426 snapshot.clip_offset(start, Bias::Left)
19427 ..snapshot.clip_offset(end, Bias::Right)
19428 }));
19429 });
19430 }
19431 };
19432 }
19433
19434 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
19435 }
19436}
19437
19438fn vim_enabled(cx: &App) -> bool {
19439 cx.global::<SettingsStore>()
19440 .raw_user_settings()
19441 .get("vim_mode")
19442 == Some(&serde_json::Value::Bool(true))
19443}
19444
19445// Consider user intent and default settings
19446fn choose_completion_range(
19447 completion: &Completion,
19448 intent: CompletionIntent,
19449 buffer: &Entity<Buffer>,
19450 cx: &mut Context<Editor>,
19451) -> Range<usize> {
19452 fn should_replace(
19453 completion: &Completion,
19454 insert_range: &Range<text::Anchor>,
19455 intent: CompletionIntent,
19456 completion_mode_setting: LspInsertMode,
19457 buffer: &Buffer,
19458 ) -> bool {
19459 // specific actions take precedence over settings
19460 match intent {
19461 CompletionIntent::CompleteWithInsert => return false,
19462 CompletionIntent::CompleteWithReplace => return true,
19463 CompletionIntent::Complete | CompletionIntent::Compose => {}
19464 }
19465
19466 match completion_mode_setting {
19467 LspInsertMode::Insert => false,
19468 LspInsertMode::Replace => true,
19469 LspInsertMode::ReplaceSubsequence => {
19470 let mut text_to_replace = buffer.chars_for_range(
19471 buffer.anchor_before(completion.replace_range.start)
19472 ..buffer.anchor_after(completion.replace_range.end),
19473 );
19474 let mut completion_text = completion.new_text.chars();
19475
19476 // is `text_to_replace` a subsequence of `completion_text`
19477 text_to_replace
19478 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
19479 }
19480 LspInsertMode::ReplaceSuffix => {
19481 let range_after_cursor = insert_range.end..completion.replace_range.end;
19482
19483 let text_after_cursor = buffer
19484 .text_for_range(
19485 buffer.anchor_before(range_after_cursor.start)
19486 ..buffer.anchor_after(range_after_cursor.end),
19487 )
19488 .collect::<String>();
19489 completion.new_text.ends_with(&text_after_cursor)
19490 }
19491 }
19492 }
19493
19494 let buffer = buffer.read(cx);
19495
19496 if let CompletionSource::Lsp {
19497 insert_range: Some(insert_range),
19498 ..
19499 } = &completion.source
19500 {
19501 let completion_mode_setting =
19502 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
19503 .completions
19504 .lsp_insert_mode;
19505
19506 if !should_replace(
19507 completion,
19508 &insert_range,
19509 intent,
19510 completion_mode_setting,
19511 buffer,
19512 ) {
19513 return insert_range.to_offset(buffer);
19514 }
19515 }
19516
19517 completion.replace_range.to_offset(buffer)
19518}
19519
19520fn insert_extra_newline_brackets(
19521 buffer: &MultiBufferSnapshot,
19522 range: Range<usize>,
19523 language: &language::LanguageScope,
19524) -> bool {
19525 let leading_whitespace_len = buffer
19526 .reversed_chars_at(range.start)
19527 .take_while(|c| c.is_whitespace() && *c != '\n')
19528 .map(|c| c.len_utf8())
19529 .sum::<usize>();
19530 let trailing_whitespace_len = buffer
19531 .chars_at(range.end)
19532 .take_while(|c| c.is_whitespace() && *c != '\n')
19533 .map(|c| c.len_utf8())
19534 .sum::<usize>();
19535 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
19536
19537 language.brackets().any(|(pair, enabled)| {
19538 let pair_start = pair.start.trim_end();
19539 let pair_end = pair.end.trim_start();
19540
19541 enabled
19542 && pair.newline
19543 && buffer.contains_str_at(range.end, pair_end)
19544 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
19545 })
19546}
19547
19548fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
19549 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
19550 [(buffer, range, _)] => (*buffer, range.clone()),
19551 _ => return false,
19552 };
19553 let pair = {
19554 let mut result: Option<BracketMatch> = None;
19555
19556 for pair in buffer
19557 .all_bracket_ranges(range.clone())
19558 .filter(move |pair| {
19559 pair.open_range.start <= range.start && pair.close_range.end >= range.end
19560 })
19561 {
19562 let len = pair.close_range.end - pair.open_range.start;
19563
19564 if let Some(existing) = &result {
19565 let existing_len = existing.close_range.end - existing.open_range.start;
19566 if len > existing_len {
19567 continue;
19568 }
19569 }
19570
19571 result = Some(pair);
19572 }
19573
19574 result
19575 };
19576 let Some(pair) = pair else {
19577 return false;
19578 };
19579 pair.newline_only
19580 && buffer
19581 .chars_for_range(pair.open_range.end..range.start)
19582 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
19583 .all(|c| c.is_whitespace() && c != '\n')
19584}
19585
19586fn update_uncommitted_diff_for_buffer(
19587 editor: Entity<Editor>,
19588 project: &Entity<Project>,
19589 buffers: impl IntoIterator<Item = Entity<Buffer>>,
19590 buffer: Entity<MultiBuffer>,
19591 cx: &mut App,
19592) -> Task<()> {
19593 let mut tasks = Vec::new();
19594 project.update(cx, |project, cx| {
19595 for buffer in buffers {
19596 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
19597 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
19598 }
19599 }
19600 });
19601 cx.spawn(async move |cx| {
19602 let diffs = future::join_all(tasks).await;
19603 if editor
19604 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19605 .unwrap_or(false)
19606 {
19607 return;
19608 }
19609
19610 buffer
19611 .update(cx, |buffer, cx| {
19612 for diff in diffs.into_iter().flatten() {
19613 buffer.add_diff(diff, cx);
19614 }
19615 })
19616 .ok();
19617 })
19618}
19619
19620fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
19621 let tab_size = tab_size.get() as usize;
19622 let mut width = offset;
19623
19624 for ch in text.chars() {
19625 width += if ch == '\t' {
19626 tab_size - (width % tab_size)
19627 } else {
19628 1
19629 };
19630 }
19631
19632 width - offset
19633}
19634
19635#[cfg(test)]
19636mod tests {
19637 use super::*;
19638
19639 #[test]
19640 fn test_string_size_with_expanded_tabs() {
19641 let nz = |val| NonZeroU32::new(val).unwrap();
19642 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
19643 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
19644 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
19645 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
19646 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
19647 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
19648 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
19649 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
19650 }
19651}
19652
19653/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
19654struct WordBreakingTokenizer<'a> {
19655 input: &'a str,
19656}
19657
19658impl<'a> WordBreakingTokenizer<'a> {
19659 fn new(input: &'a str) -> Self {
19660 Self { input }
19661 }
19662}
19663
19664fn is_char_ideographic(ch: char) -> bool {
19665 use unicode_script::Script::*;
19666 use unicode_script::UnicodeScript;
19667 matches!(ch.script(), Han | Tangut | Yi)
19668}
19669
19670fn is_grapheme_ideographic(text: &str) -> bool {
19671 text.chars().any(is_char_ideographic)
19672}
19673
19674fn is_grapheme_whitespace(text: &str) -> bool {
19675 text.chars().any(|x| x.is_whitespace())
19676}
19677
19678fn should_stay_with_preceding_ideograph(text: &str) -> bool {
19679 text.chars().next().map_or(false, |ch| {
19680 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
19681 })
19682}
19683
19684#[derive(PartialEq, Eq, Debug, Clone, Copy)]
19685enum WordBreakToken<'a> {
19686 Word { token: &'a str, grapheme_len: usize },
19687 InlineWhitespace { token: &'a str, grapheme_len: usize },
19688 Newline,
19689}
19690
19691impl<'a> Iterator for WordBreakingTokenizer<'a> {
19692 /// Yields a span, the count of graphemes in the token, and whether it was
19693 /// whitespace. Note that it also breaks at word boundaries.
19694 type Item = WordBreakToken<'a>;
19695
19696 fn next(&mut self) -> Option<Self::Item> {
19697 use unicode_segmentation::UnicodeSegmentation;
19698 if self.input.is_empty() {
19699 return None;
19700 }
19701
19702 let mut iter = self.input.graphemes(true).peekable();
19703 let mut offset = 0;
19704 let mut grapheme_len = 0;
19705 if let Some(first_grapheme) = iter.next() {
19706 let is_newline = first_grapheme == "\n";
19707 let is_whitespace = is_grapheme_whitespace(first_grapheme);
19708 offset += first_grapheme.len();
19709 grapheme_len += 1;
19710 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
19711 if let Some(grapheme) = iter.peek().copied() {
19712 if should_stay_with_preceding_ideograph(grapheme) {
19713 offset += grapheme.len();
19714 grapheme_len += 1;
19715 }
19716 }
19717 } else {
19718 let mut words = self.input[offset..].split_word_bound_indices().peekable();
19719 let mut next_word_bound = words.peek().copied();
19720 if next_word_bound.map_or(false, |(i, _)| i == 0) {
19721 next_word_bound = words.next();
19722 }
19723 while let Some(grapheme) = iter.peek().copied() {
19724 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19725 break;
19726 };
19727 if is_grapheme_whitespace(grapheme) != is_whitespace
19728 || (grapheme == "\n") != is_newline
19729 {
19730 break;
19731 };
19732 offset += grapheme.len();
19733 grapheme_len += 1;
19734 iter.next();
19735 }
19736 }
19737 let token = &self.input[..offset];
19738 self.input = &self.input[offset..];
19739 if token == "\n" {
19740 Some(WordBreakToken::Newline)
19741 } else if is_whitespace {
19742 Some(WordBreakToken::InlineWhitespace {
19743 token,
19744 grapheme_len,
19745 })
19746 } else {
19747 Some(WordBreakToken::Word {
19748 token,
19749 grapheme_len,
19750 })
19751 }
19752 } else {
19753 None
19754 }
19755 }
19756}
19757
19758#[test]
19759fn test_word_breaking_tokenizer() {
19760 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
19761 ("", &[]),
19762 (" ", &[whitespace(" ", 2)]),
19763 ("Ʒ", &[word("Ʒ", 1)]),
19764 ("Ǽ", &[word("Ǽ", 1)]),
19765 ("⋑", &[word("⋑", 1)]),
19766 ("⋑⋑", &[word("⋑⋑", 2)]),
19767 (
19768 "原理,进而",
19769 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
19770 ),
19771 (
19772 "hello world",
19773 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
19774 ),
19775 (
19776 "hello, world",
19777 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
19778 ),
19779 (
19780 " hello world",
19781 &[
19782 whitespace(" ", 2),
19783 word("hello", 5),
19784 whitespace(" ", 1),
19785 word("world", 5),
19786 ],
19787 ),
19788 (
19789 "这是什么 \n 钢笔",
19790 &[
19791 word("这", 1),
19792 word("是", 1),
19793 word("什", 1),
19794 word("么", 1),
19795 whitespace(" ", 1),
19796 newline(),
19797 whitespace(" ", 1),
19798 word("钢", 1),
19799 word("笔", 1),
19800 ],
19801 ),
19802 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
19803 ];
19804
19805 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19806 WordBreakToken::Word {
19807 token,
19808 grapheme_len,
19809 }
19810 }
19811
19812 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19813 WordBreakToken::InlineWhitespace {
19814 token,
19815 grapheme_len,
19816 }
19817 }
19818
19819 fn newline() -> WordBreakToken<'static> {
19820 WordBreakToken::Newline
19821 }
19822
19823 for (input, result) in tests {
19824 assert_eq!(
19825 WordBreakingTokenizer::new(input)
19826 .collect::<Vec<_>>()
19827 .as_slice(),
19828 *result,
19829 );
19830 }
19831}
19832
19833fn wrap_with_prefix(
19834 line_prefix: String,
19835 unwrapped_text: String,
19836 wrap_column: usize,
19837 tab_size: NonZeroU32,
19838 preserve_existing_whitespace: bool,
19839) -> String {
19840 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
19841 let mut wrapped_text = String::new();
19842 let mut current_line = line_prefix.clone();
19843
19844 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
19845 let mut current_line_len = line_prefix_len;
19846 let mut in_whitespace = false;
19847 for token in tokenizer {
19848 let have_preceding_whitespace = in_whitespace;
19849 match token {
19850 WordBreakToken::Word {
19851 token,
19852 grapheme_len,
19853 } => {
19854 in_whitespace = false;
19855 if current_line_len + grapheme_len > wrap_column
19856 && current_line_len != line_prefix_len
19857 {
19858 wrapped_text.push_str(current_line.trim_end());
19859 wrapped_text.push('\n');
19860 current_line.truncate(line_prefix.len());
19861 current_line_len = line_prefix_len;
19862 }
19863 current_line.push_str(token);
19864 current_line_len += grapheme_len;
19865 }
19866 WordBreakToken::InlineWhitespace {
19867 mut token,
19868 mut grapheme_len,
19869 } => {
19870 in_whitespace = true;
19871 if have_preceding_whitespace && !preserve_existing_whitespace {
19872 continue;
19873 }
19874 if !preserve_existing_whitespace {
19875 token = " ";
19876 grapheme_len = 1;
19877 }
19878 if current_line_len + grapheme_len > wrap_column {
19879 wrapped_text.push_str(current_line.trim_end());
19880 wrapped_text.push('\n');
19881 current_line.truncate(line_prefix.len());
19882 current_line_len = line_prefix_len;
19883 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
19884 current_line.push_str(token);
19885 current_line_len += grapheme_len;
19886 }
19887 }
19888 WordBreakToken::Newline => {
19889 in_whitespace = true;
19890 if preserve_existing_whitespace {
19891 wrapped_text.push_str(current_line.trim_end());
19892 wrapped_text.push('\n');
19893 current_line.truncate(line_prefix.len());
19894 current_line_len = line_prefix_len;
19895 } else if have_preceding_whitespace {
19896 continue;
19897 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
19898 {
19899 wrapped_text.push_str(current_line.trim_end());
19900 wrapped_text.push('\n');
19901 current_line.truncate(line_prefix.len());
19902 current_line_len = line_prefix_len;
19903 } else if current_line_len != line_prefix_len {
19904 current_line.push(' ');
19905 current_line_len += 1;
19906 }
19907 }
19908 }
19909 }
19910
19911 if !current_line.is_empty() {
19912 wrapped_text.push_str(¤t_line);
19913 }
19914 wrapped_text
19915}
19916
19917#[test]
19918fn test_wrap_with_prefix() {
19919 assert_eq!(
19920 wrap_with_prefix(
19921 "# ".to_string(),
19922 "abcdefg".to_string(),
19923 4,
19924 NonZeroU32::new(4).unwrap(),
19925 false,
19926 ),
19927 "# abcdefg"
19928 );
19929 assert_eq!(
19930 wrap_with_prefix(
19931 "".to_string(),
19932 "\thello world".to_string(),
19933 8,
19934 NonZeroU32::new(4).unwrap(),
19935 false,
19936 ),
19937 "hello\nworld"
19938 );
19939 assert_eq!(
19940 wrap_with_prefix(
19941 "// ".to_string(),
19942 "xx \nyy zz aa bb cc".to_string(),
19943 12,
19944 NonZeroU32::new(4).unwrap(),
19945 false,
19946 ),
19947 "// xx yy zz\n// aa bb cc"
19948 );
19949 assert_eq!(
19950 wrap_with_prefix(
19951 String::new(),
19952 "这是什么 \n 钢笔".to_string(),
19953 3,
19954 NonZeroU32::new(4).unwrap(),
19955 false,
19956 ),
19957 "这是什\n么 钢\n笔"
19958 );
19959}
19960
19961pub trait CollaborationHub {
19962 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
19963 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
19964 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
19965}
19966
19967impl CollaborationHub for Entity<Project> {
19968 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
19969 self.read(cx).collaborators()
19970 }
19971
19972 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
19973 self.read(cx).user_store().read(cx).participant_indices()
19974 }
19975
19976 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
19977 let this = self.read(cx);
19978 let user_ids = this.collaborators().values().map(|c| c.user_id);
19979 this.user_store().read(cx).participant_names(user_ids, cx)
19980 }
19981}
19982
19983pub trait SemanticsProvider {
19984 fn hover(
19985 &self,
19986 buffer: &Entity<Buffer>,
19987 position: text::Anchor,
19988 cx: &mut App,
19989 ) -> Option<Task<Vec<project::Hover>>>;
19990
19991 fn inline_values(
19992 &self,
19993 buffer_handle: Entity<Buffer>,
19994 range: Range<text::Anchor>,
19995 cx: &mut App,
19996 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19997
19998 fn inlay_hints(
19999 &self,
20000 buffer_handle: Entity<Buffer>,
20001 range: Range<text::Anchor>,
20002 cx: &mut App,
20003 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20004
20005 fn resolve_inlay_hint(
20006 &self,
20007 hint: InlayHint,
20008 buffer_handle: Entity<Buffer>,
20009 server_id: LanguageServerId,
20010 cx: &mut App,
20011 ) -> Option<Task<anyhow::Result<InlayHint>>>;
20012
20013 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
20014
20015 fn document_highlights(
20016 &self,
20017 buffer: &Entity<Buffer>,
20018 position: text::Anchor,
20019 cx: &mut App,
20020 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
20021
20022 fn definitions(
20023 &self,
20024 buffer: &Entity<Buffer>,
20025 position: text::Anchor,
20026 kind: GotoDefinitionKind,
20027 cx: &mut App,
20028 ) -> Option<Task<Result<Vec<LocationLink>>>>;
20029
20030 fn range_for_rename(
20031 &self,
20032 buffer: &Entity<Buffer>,
20033 position: text::Anchor,
20034 cx: &mut App,
20035 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
20036
20037 fn perform_rename(
20038 &self,
20039 buffer: &Entity<Buffer>,
20040 position: text::Anchor,
20041 new_name: String,
20042 cx: &mut App,
20043 ) -> Option<Task<Result<ProjectTransaction>>>;
20044}
20045
20046pub trait CompletionProvider {
20047 fn completions(
20048 &self,
20049 excerpt_id: ExcerptId,
20050 buffer: &Entity<Buffer>,
20051 buffer_position: text::Anchor,
20052 trigger: CompletionContext,
20053 window: &mut Window,
20054 cx: &mut Context<Editor>,
20055 ) -> Task<Result<Option<Vec<Completion>>>>;
20056
20057 fn resolve_completions(
20058 &self,
20059 buffer: Entity<Buffer>,
20060 completion_indices: Vec<usize>,
20061 completions: Rc<RefCell<Box<[Completion]>>>,
20062 cx: &mut Context<Editor>,
20063 ) -> Task<Result<bool>>;
20064
20065 fn apply_additional_edits_for_completion(
20066 &self,
20067 _buffer: Entity<Buffer>,
20068 _completions: Rc<RefCell<Box<[Completion]>>>,
20069 _completion_index: usize,
20070 _push_to_history: bool,
20071 _cx: &mut Context<Editor>,
20072 ) -> Task<Result<Option<language::Transaction>>> {
20073 Task::ready(Ok(None))
20074 }
20075
20076 fn is_completion_trigger(
20077 &self,
20078 buffer: &Entity<Buffer>,
20079 position: language::Anchor,
20080 text: &str,
20081 trigger_in_words: bool,
20082 cx: &mut Context<Editor>,
20083 ) -> bool;
20084
20085 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
20086
20087 fn sort_completions(&self) -> bool {
20088 true
20089 }
20090
20091 fn filter_completions(&self) -> bool {
20092 true
20093 }
20094}
20095
20096pub trait CodeActionProvider {
20097 fn id(&self) -> Arc<str>;
20098
20099 fn code_actions(
20100 &self,
20101 buffer: &Entity<Buffer>,
20102 range: Range<text::Anchor>,
20103 window: &mut Window,
20104 cx: &mut App,
20105 ) -> Task<Result<Vec<CodeAction>>>;
20106
20107 fn apply_code_action(
20108 &self,
20109 buffer_handle: Entity<Buffer>,
20110 action: CodeAction,
20111 excerpt_id: ExcerptId,
20112 push_to_history: bool,
20113 window: &mut Window,
20114 cx: &mut App,
20115 ) -> Task<Result<ProjectTransaction>>;
20116}
20117
20118impl CodeActionProvider for Entity<Project> {
20119 fn id(&self) -> Arc<str> {
20120 "project".into()
20121 }
20122
20123 fn code_actions(
20124 &self,
20125 buffer: &Entity<Buffer>,
20126 range: Range<text::Anchor>,
20127 _window: &mut Window,
20128 cx: &mut App,
20129 ) -> Task<Result<Vec<CodeAction>>> {
20130 self.update(cx, |project, cx| {
20131 let code_lens = project.code_lens(buffer, range.clone(), cx);
20132 let code_actions = project.code_actions(buffer, range, None, cx);
20133 cx.background_spawn(async move {
20134 let (code_lens, code_actions) = join(code_lens, code_actions).await;
20135 Ok(code_lens
20136 .context("code lens fetch")?
20137 .into_iter()
20138 .chain(code_actions.context("code action fetch")?)
20139 .collect())
20140 })
20141 })
20142 }
20143
20144 fn apply_code_action(
20145 &self,
20146 buffer_handle: Entity<Buffer>,
20147 action: CodeAction,
20148 _excerpt_id: ExcerptId,
20149 push_to_history: bool,
20150 _window: &mut Window,
20151 cx: &mut App,
20152 ) -> Task<Result<ProjectTransaction>> {
20153 self.update(cx, |project, cx| {
20154 project.apply_code_action(buffer_handle, action, push_to_history, cx)
20155 })
20156 }
20157}
20158
20159fn snippet_completions(
20160 project: &Project,
20161 buffer: &Entity<Buffer>,
20162 buffer_position: text::Anchor,
20163 cx: &mut App,
20164) -> Task<Result<Vec<Completion>>> {
20165 let languages = buffer.read(cx).languages_at(buffer_position);
20166 let snippet_store = project.snippets().read(cx);
20167
20168 let scopes: Vec<_> = languages
20169 .iter()
20170 .filter_map(|language| {
20171 let language_name = language.lsp_id();
20172 let snippets = snippet_store.snippets_for(Some(language_name), cx);
20173
20174 if snippets.is_empty() {
20175 None
20176 } else {
20177 Some((language.default_scope(), snippets))
20178 }
20179 })
20180 .collect();
20181
20182 if scopes.is_empty() {
20183 return Task::ready(Ok(vec![]));
20184 }
20185
20186 let snapshot = buffer.read(cx).text_snapshot();
20187 let chars: String = snapshot
20188 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
20189 .collect();
20190 let executor = cx.background_executor().clone();
20191
20192 cx.background_spawn(async move {
20193 let mut all_results: Vec<Completion> = Vec::new();
20194 for (scope, snippets) in scopes.into_iter() {
20195 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
20196 let mut last_word = chars
20197 .chars()
20198 .take_while(|c| classifier.is_word(*c))
20199 .collect::<String>();
20200 last_word = last_word.chars().rev().collect();
20201
20202 if last_word.is_empty() {
20203 return Ok(vec![]);
20204 }
20205
20206 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
20207 let to_lsp = |point: &text::Anchor| {
20208 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
20209 point_to_lsp(end)
20210 };
20211 let lsp_end = to_lsp(&buffer_position);
20212
20213 let candidates = snippets
20214 .iter()
20215 .enumerate()
20216 .flat_map(|(ix, snippet)| {
20217 snippet
20218 .prefix
20219 .iter()
20220 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
20221 })
20222 .collect::<Vec<StringMatchCandidate>>();
20223
20224 let mut matches = fuzzy::match_strings(
20225 &candidates,
20226 &last_word,
20227 last_word.chars().any(|c| c.is_uppercase()),
20228 100,
20229 &Default::default(),
20230 executor.clone(),
20231 )
20232 .await;
20233
20234 // Remove all candidates where the query's start does not match the start of any word in the candidate
20235 if let Some(query_start) = last_word.chars().next() {
20236 matches.retain(|string_match| {
20237 split_words(&string_match.string).any(|word| {
20238 // Check that the first codepoint of the word as lowercase matches the first
20239 // codepoint of the query as lowercase
20240 word.chars()
20241 .flat_map(|codepoint| codepoint.to_lowercase())
20242 .zip(query_start.to_lowercase())
20243 .all(|(word_cp, query_cp)| word_cp == query_cp)
20244 })
20245 });
20246 }
20247
20248 let matched_strings = matches
20249 .into_iter()
20250 .map(|m| m.string)
20251 .collect::<HashSet<_>>();
20252
20253 let mut result: Vec<Completion> = snippets
20254 .iter()
20255 .filter_map(|snippet| {
20256 let matching_prefix = snippet
20257 .prefix
20258 .iter()
20259 .find(|prefix| matched_strings.contains(*prefix))?;
20260 let start = as_offset - last_word.len();
20261 let start = snapshot.anchor_before(start);
20262 let range = start..buffer_position;
20263 let lsp_start = to_lsp(&start);
20264 let lsp_range = lsp::Range {
20265 start: lsp_start,
20266 end: lsp_end,
20267 };
20268 Some(Completion {
20269 replace_range: range,
20270 new_text: snippet.body.clone(),
20271 source: CompletionSource::Lsp {
20272 insert_range: None,
20273 server_id: LanguageServerId(usize::MAX),
20274 resolved: true,
20275 lsp_completion: Box::new(lsp::CompletionItem {
20276 label: snippet.prefix.first().unwrap().clone(),
20277 kind: Some(CompletionItemKind::SNIPPET),
20278 label_details: snippet.description.as_ref().map(|description| {
20279 lsp::CompletionItemLabelDetails {
20280 detail: Some(description.clone()),
20281 description: None,
20282 }
20283 }),
20284 insert_text_format: Some(InsertTextFormat::SNIPPET),
20285 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20286 lsp::InsertReplaceEdit {
20287 new_text: snippet.body.clone(),
20288 insert: lsp_range,
20289 replace: lsp_range,
20290 },
20291 )),
20292 filter_text: Some(snippet.body.clone()),
20293 sort_text: Some(char::MAX.to_string()),
20294 ..lsp::CompletionItem::default()
20295 }),
20296 lsp_defaults: None,
20297 },
20298 label: CodeLabel {
20299 text: matching_prefix.clone(),
20300 runs: Vec::new(),
20301 filter_range: 0..matching_prefix.len(),
20302 },
20303 icon_path: None,
20304 documentation: Some(
20305 CompletionDocumentation::SingleLineAndMultiLinePlainText {
20306 single_line: snippet.name.clone().into(),
20307 plain_text: snippet
20308 .description
20309 .clone()
20310 .map(|description| description.into()),
20311 },
20312 ),
20313 insert_text_mode: None,
20314 confirm: None,
20315 })
20316 })
20317 .collect();
20318
20319 all_results.append(&mut result);
20320 }
20321
20322 Ok(all_results)
20323 })
20324}
20325
20326impl CompletionProvider for Entity<Project> {
20327 fn completions(
20328 &self,
20329 _excerpt_id: ExcerptId,
20330 buffer: &Entity<Buffer>,
20331 buffer_position: text::Anchor,
20332 options: CompletionContext,
20333 _window: &mut Window,
20334 cx: &mut Context<Editor>,
20335 ) -> Task<Result<Option<Vec<Completion>>>> {
20336 self.update(cx, |project, cx| {
20337 let snippets = snippet_completions(project, buffer, buffer_position, cx);
20338 let project_completions = project.completions(buffer, buffer_position, options, cx);
20339 cx.background_spawn(async move {
20340 let snippets_completions = snippets.await?;
20341 match project_completions.await? {
20342 Some(mut completions) => {
20343 completions.extend(snippets_completions);
20344 Ok(Some(completions))
20345 }
20346 None => {
20347 if snippets_completions.is_empty() {
20348 Ok(None)
20349 } else {
20350 Ok(Some(snippets_completions))
20351 }
20352 }
20353 }
20354 })
20355 })
20356 }
20357
20358 fn resolve_completions(
20359 &self,
20360 buffer: Entity<Buffer>,
20361 completion_indices: Vec<usize>,
20362 completions: Rc<RefCell<Box<[Completion]>>>,
20363 cx: &mut Context<Editor>,
20364 ) -> Task<Result<bool>> {
20365 self.update(cx, |project, cx| {
20366 project.lsp_store().update(cx, |lsp_store, cx| {
20367 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
20368 })
20369 })
20370 }
20371
20372 fn apply_additional_edits_for_completion(
20373 &self,
20374 buffer: Entity<Buffer>,
20375 completions: Rc<RefCell<Box<[Completion]>>>,
20376 completion_index: usize,
20377 push_to_history: bool,
20378 cx: &mut Context<Editor>,
20379 ) -> Task<Result<Option<language::Transaction>>> {
20380 self.update(cx, |project, cx| {
20381 project.lsp_store().update(cx, |lsp_store, cx| {
20382 lsp_store.apply_additional_edits_for_completion(
20383 buffer,
20384 completions,
20385 completion_index,
20386 push_to_history,
20387 cx,
20388 )
20389 })
20390 })
20391 }
20392
20393 fn is_completion_trigger(
20394 &self,
20395 buffer: &Entity<Buffer>,
20396 position: language::Anchor,
20397 text: &str,
20398 trigger_in_words: bool,
20399 cx: &mut Context<Editor>,
20400 ) -> bool {
20401 let mut chars = text.chars();
20402 let char = if let Some(char) = chars.next() {
20403 char
20404 } else {
20405 return false;
20406 };
20407 if chars.next().is_some() {
20408 return false;
20409 }
20410
20411 let buffer = buffer.read(cx);
20412 let snapshot = buffer.snapshot();
20413 if !snapshot.settings_at(position, cx).show_completions_on_input {
20414 return false;
20415 }
20416 let classifier = snapshot.char_classifier_at(position).for_completion(true);
20417 if trigger_in_words && classifier.is_word(char) {
20418 return true;
20419 }
20420
20421 buffer.completion_triggers().contains(text)
20422 }
20423}
20424
20425impl SemanticsProvider for Entity<Project> {
20426 fn hover(
20427 &self,
20428 buffer: &Entity<Buffer>,
20429 position: text::Anchor,
20430 cx: &mut App,
20431 ) -> Option<Task<Vec<project::Hover>>> {
20432 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
20433 }
20434
20435 fn document_highlights(
20436 &self,
20437 buffer: &Entity<Buffer>,
20438 position: text::Anchor,
20439 cx: &mut App,
20440 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
20441 Some(self.update(cx, |project, cx| {
20442 project.document_highlights(buffer, position, cx)
20443 }))
20444 }
20445
20446 fn definitions(
20447 &self,
20448 buffer: &Entity<Buffer>,
20449 position: text::Anchor,
20450 kind: GotoDefinitionKind,
20451 cx: &mut App,
20452 ) -> Option<Task<Result<Vec<LocationLink>>>> {
20453 Some(self.update(cx, |project, cx| match kind {
20454 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
20455 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
20456 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
20457 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
20458 }))
20459 }
20460
20461 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
20462 // TODO: make this work for remote projects
20463 self.update(cx, |project, cx| {
20464 if project
20465 .active_debug_session(cx)
20466 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
20467 {
20468 return true;
20469 }
20470
20471 buffer.update(cx, |buffer, cx| {
20472 project.any_language_server_supports_inlay_hints(buffer, cx)
20473 })
20474 })
20475 }
20476
20477 fn inline_values(
20478 &self,
20479 buffer_handle: Entity<Buffer>,
20480
20481 range: Range<text::Anchor>,
20482 cx: &mut App,
20483 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20484 self.update(cx, |project, cx| {
20485 let (session, active_stack_frame) = project.active_debug_session(cx)?;
20486
20487 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
20488 })
20489 }
20490
20491 fn inlay_hints(
20492 &self,
20493 buffer_handle: Entity<Buffer>,
20494 range: Range<text::Anchor>,
20495 cx: &mut App,
20496 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20497 Some(self.update(cx, |project, cx| {
20498 project.inlay_hints(buffer_handle, range, cx)
20499 }))
20500 }
20501
20502 fn resolve_inlay_hint(
20503 &self,
20504 hint: InlayHint,
20505 buffer_handle: Entity<Buffer>,
20506 server_id: LanguageServerId,
20507 cx: &mut App,
20508 ) -> Option<Task<anyhow::Result<InlayHint>>> {
20509 Some(self.update(cx, |project, cx| {
20510 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
20511 }))
20512 }
20513
20514 fn range_for_rename(
20515 &self,
20516 buffer: &Entity<Buffer>,
20517 position: text::Anchor,
20518 cx: &mut App,
20519 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
20520 Some(self.update(cx, |project, cx| {
20521 let buffer = buffer.clone();
20522 let task = project.prepare_rename(buffer.clone(), position, cx);
20523 cx.spawn(async move |_, cx| {
20524 Ok(match task.await? {
20525 PrepareRenameResponse::Success(range) => Some(range),
20526 PrepareRenameResponse::InvalidPosition => None,
20527 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
20528 // Fallback on using TreeSitter info to determine identifier range
20529 buffer.read_with(cx, |buffer, _| {
20530 let snapshot = buffer.snapshot();
20531 let (range, kind) = snapshot.surrounding_word(position);
20532 if kind != Some(CharKind::Word) {
20533 return None;
20534 }
20535 Some(
20536 snapshot.anchor_before(range.start)
20537 ..snapshot.anchor_after(range.end),
20538 )
20539 })?
20540 }
20541 })
20542 })
20543 }))
20544 }
20545
20546 fn perform_rename(
20547 &self,
20548 buffer: &Entity<Buffer>,
20549 position: text::Anchor,
20550 new_name: String,
20551 cx: &mut App,
20552 ) -> Option<Task<Result<ProjectTransaction>>> {
20553 Some(self.update(cx, |project, cx| {
20554 project.perform_rename(buffer.clone(), position, new_name, cx)
20555 }))
20556 }
20557}
20558
20559fn inlay_hint_settings(
20560 location: Anchor,
20561 snapshot: &MultiBufferSnapshot,
20562 cx: &mut Context<Editor>,
20563) -> InlayHintSettings {
20564 let file = snapshot.file_at(location);
20565 let language = snapshot.language_at(location).map(|l| l.name());
20566 language_settings(language, file, cx).inlay_hints
20567}
20568
20569fn consume_contiguous_rows(
20570 contiguous_row_selections: &mut Vec<Selection<Point>>,
20571 selection: &Selection<Point>,
20572 display_map: &DisplaySnapshot,
20573 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
20574) -> (MultiBufferRow, MultiBufferRow) {
20575 contiguous_row_selections.push(selection.clone());
20576 let start_row = MultiBufferRow(selection.start.row);
20577 let mut end_row = ending_row(selection, display_map);
20578
20579 while let Some(next_selection) = selections.peek() {
20580 if next_selection.start.row <= end_row.0 {
20581 end_row = ending_row(next_selection, display_map);
20582 contiguous_row_selections.push(selections.next().unwrap().clone());
20583 } else {
20584 break;
20585 }
20586 }
20587 (start_row, end_row)
20588}
20589
20590fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
20591 if next_selection.end.column > 0 || next_selection.is_empty() {
20592 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
20593 } else {
20594 MultiBufferRow(next_selection.end.row)
20595 }
20596}
20597
20598impl EditorSnapshot {
20599 pub fn remote_selections_in_range<'a>(
20600 &'a self,
20601 range: &'a Range<Anchor>,
20602 collaboration_hub: &dyn CollaborationHub,
20603 cx: &'a App,
20604 ) -> impl 'a + Iterator<Item = RemoteSelection> {
20605 let participant_names = collaboration_hub.user_names(cx);
20606 let participant_indices = collaboration_hub.user_participant_indices(cx);
20607 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20608 let collaborators_by_replica_id = collaborators_by_peer_id
20609 .values()
20610 .map(|collaborator| (collaborator.replica_id, collaborator))
20611 .collect::<HashMap<_, _>>();
20612 self.buffer_snapshot
20613 .selections_in_range(range, false)
20614 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20615 if replica_id == AGENT_REPLICA_ID {
20616 Some(RemoteSelection {
20617 replica_id,
20618 selection,
20619 cursor_shape,
20620 line_mode,
20621 collaborator_id: CollaboratorId::Agent,
20622 user_name: Some("Agent".into()),
20623 color: cx.theme().players().agent(),
20624 })
20625 } else {
20626 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20627 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20628 let user_name = participant_names.get(&collaborator.user_id).cloned();
20629 Some(RemoteSelection {
20630 replica_id,
20631 selection,
20632 cursor_shape,
20633 line_mode,
20634 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20635 user_name,
20636 color: if let Some(index) = participant_index {
20637 cx.theme().players().color_for_participant(index.0)
20638 } else {
20639 cx.theme().players().absent()
20640 },
20641 })
20642 }
20643 })
20644 }
20645
20646 pub fn hunks_for_ranges(
20647 &self,
20648 ranges: impl IntoIterator<Item = Range<Point>>,
20649 ) -> Vec<MultiBufferDiffHunk> {
20650 let mut hunks = Vec::new();
20651 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20652 HashMap::default();
20653 for query_range in ranges {
20654 let query_rows =
20655 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20656 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20657 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20658 ) {
20659 // Include deleted hunks that are adjacent to the query range, because
20660 // otherwise they would be missed.
20661 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20662 if hunk.status().is_deleted() {
20663 intersects_range |= hunk.row_range.start == query_rows.end;
20664 intersects_range |= hunk.row_range.end == query_rows.start;
20665 }
20666 if intersects_range {
20667 if !processed_buffer_rows
20668 .entry(hunk.buffer_id)
20669 .or_default()
20670 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20671 {
20672 continue;
20673 }
20674 hunks.push(hunk);
20675 }
20676 }
20677 }
20678
20679 hunks
20680 }
20681
20682 fn display_diff_hunks_for_rows<'a>(
20683 &'a self,
20684 display_rows: Range<DisplayRow>,
20685 folded_buffers: &'a HashSet<BufferId>,
20686 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20687 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20688 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20689
20690 self.buffer_snapshot
20691 .diff_hunks_in_range(buffer_start..buffer_end)
20692 .filter_map(|hunk| {
20693 if folded_buffers.contains(&hunk.buffer_id) {
20694 return None;
20695 }
20696
20697 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20698 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20699
20700 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20701 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20702
20703 let display_hunk = if hunk_display_start.column() != 0 {
20704 DisplayDiffHunk::Folded {
20705 display_row: hunk_display_start.row(),
20706 }
20707 } else {
20708 let mut end_row = hunk_display_end.row();
20709 if hunk_display_end.column() > 0 {
20710 end_row.0 += 1;
20711 }
20712 let is_created_file = hunk.is_created_file();
20713 DisplayDiffHunk::Unfolded {
20714 status: hunk.status(),
20715 diff_base_byte_range: hunk.diff_base_byte_range,
20716 display_row_range: hunk_display_start.row()..end_row,
20717 multi_buffer_range: Anchor::range_in_buffer(
20718 hunk.excerpt_id,
20719 hunk.buffer_id,
20720 hunk.buffer_range,
20721 ),
20722 is_created_file,
20723 }
20724 };
20725
20726 Some(display_hunk)
20727 })
20728 }
20729
20730 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20731 self.display_snapshot.buffer_snapshot.language_at(position)
20732 }
20733
20734 pub fn is_focused(&self) -> bool {
20735 self.is_focused
20736 }
20737
20738 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20739 self.placeholder_text.as_ref()
20740 }
20741
20742 pub fn scroll_position(&self) -> gpui::Point<f32> {
20743 self.scroll_anchor.scroll_position(&self.display_snapshot)
20744 }
20745
20746 fn gutter_dimensions(
20747 &self,
20748 font_id: FontId,
20749 font_size: Pixels,
20750 max_line_number_width: Pixels,
20751 cx: &App,
20752 ) -> Option<GutterDimensions> {
20753 if !self.show_gutter {
20754 return None;
20755 }
20756
20757 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20758 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20759
20760 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20761 matches!(
20762 ProjectSettings::get_global(cx).git.git_gutter,
20763 Some(GitGutterSetting::TrackedFiles)
20764 )
20765 });
20766 let gutter_settings = EditorSettings::get_global(cx).gutter;
20767 let show_line_numbers = self
20768 .show_line_numbers
20769 .unwrap_or(gutter_settings.line_numbers);
20770 let line_gutter_width = if show_line_numbers {
20771 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20772 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20773 max_line_number_width.max(min_width_for_number_on_gutter)
20774 } else {
20775 0.0.into()
20776 };
20777
20778 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20779 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20780
20781 let git_blame_entries_width =
20782 self.git_blame_gutter_max_author_length
20783 .map(|max_author_length| {
20784 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20785 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20786
20787 /// The number of characters to dedicate to gaps and margins.
20788 const SPACING_WIDTH: usize = 4;
20789
20790 let max_char_count = max_author_length.min(renderer.max_author_length())
20791 + ::git::SHORT_SHA_LENGTH
20792 + MAX_RELATIVE_TIMESTAMP.len()
20793 + SPACING_WIDTH;
20794
20795 em_advance * max_char_count
20796 });
20797
20798 let is_singleton = self.buffer_snapshot.is_singleton();
20799
20800 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
20801 left_padding += if !is_singleton {
20802 em_width * 4.0
20803 } else if show_runnables || show_breakpoints {
20804 em_width * 3.0
20805 } else if show_git_gutter && show_line_numbers {
20806 em_width * 2.0
20807 } else if show_git_gutter || show_line_numbers {
20808 em_width
20809 } else {
20810 px(0.)
20811 };
20812
20813 let shows_folds = is_singleton && gutter_settings.folds;
20814
20815 let right_padding = if shows_folds && show_line_numbers {
20816 em_width * 4.0
20817 } else if shows_folds || (!is_singleton && show_line_numbers) {
20818 em_width * 3.0
20819 } else if show_line_numbers {
20820 em_width
20821 } else {
20822 px(0.)
20823 };
20824
20825 Some(GutterDimensions {
20826 left_padding,
20827 right_padding,
20828 width: line_gutter_width + left_padding + right_padding,
20829 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
20830 git_blame_entries_width,
20831 })
20832 }
20833
20834 pub fn render_crease_toggle(
20835 &self,
20836 buffer_row: MultiBufferRow,
20837 row_contains_cursor: bool,
20838 editor: Entity<Editor>,
20839 window: &mut Window,
20840 cx: &mut App,
20841 ) -> Option<AnyElement> {
20842 let folded = self.is_line_folded(buffer_row);
20843 let mut is_foldable = false;
20844
20845 if let Some(crease) = self
20846 .crease_snapshot
20847 .query_row(buffer_row, &self.buffer_snapshot)
20848 {
20849 is_foldable = true;
20850 match crease {
20851 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20852 if let Some(render_toggle) = render_toggle {
20853 let toggle_callback =
20854 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20855 if folded {
20856 editor.update(cx, |editor, cx| {
20857 editor.fold_at(buffer_row, window, cx)
20858 });
20859 } else {
20860 editor.update(cx, |editor, cx| {
20861 editor.unfold_at(buffer_row, window, cx)
20862 });
20863 }
20864 });
20865 return Some((render_toggle)(
20866 buffer_row,
20867 folded,
20868 toggle_callback,
20869 window,
20870 cx,
20871 ));
20872 }
20873 }
20874 }
20875 }
20876
20877 is_foldable |= self.starts_indent(buffer_row);
20878
20879 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20880 Some(
20881 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20882 .toggle_state(folded)
20883 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20884 if folded {
20885 this.unfold_at(buffer_row, window, cx);
20886 } else {
20887 this.fold_at(buffer_row, window, cx);
20888 }
20889 }))
20890 .into_any_element(),
20891 )
20892 } else {
20893 None
20894 }
20895 }
20896
20897 pub fn render_crease_trailer(
20898 &self,
20899 buffer_row: MultiBufferRow,
20900 window: &mut Window,
20901 cx: &mut App,
20902 ) -> Option<AnyElement> {
20903 let folded = self.is_line_folded(buffer_row);
20904 if let Crease::Inline { render_trailer, .. } = self
20905 .crease_snapshot
20906 .query_row(buffer_row, &self.buffer_snapshot)?
20907 {
20908 let render_trailer = render_trailer.as_ref()?;
20909 Some(render_trailer(buffer_row, folded, window, cx))
20910 } else {
20911 None
20912 }
20913 }
20914}
20915
20916impl Deref for EditorSnapshot {
20917 type Target = DisplaySnapshot;
20918
20919 fn deref(&self) -> &Self::Target {
20920 &self.display_snapshot
20921 }
20922}
20923
20924#[derive(Clone, Debug, PartialEq, Eq)]
20925pub enum EditorEvent {
20926 InputIgnored {
20927 text: Arc<str>,
20928 },
20929 InputHandled {
20930 utf16_range_to_replace: Option<Range<isize>>,
20931 text: Arc<str>,
20932 },
20933 ExcerptsAdded {
20934 buffer: Entity<Buffer>,
20935 predecessor: ExcerptId,
20936 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
20937 },
20938 ExcerptsRemoved {
20939 ids: Vec<ExcerptId>,
20940 removed_buffer_ids: Vec<BufferId>,
20941 },
20942 BufferFoldToggled {
20943 ids: Vec<ExcerptId>,
20944 folded: bool,
20945 },
20946 ExcerptsEdited {
20947 ids: Vec<ExcerptId>,
20948 },
20949 ExcerptsExpanded {
20950 ids: Vec<ExcerptId>,
20951 },
20952 BufferEdited,
20953 Edited {
20954 transaction_id: clock::Lamport,
20955 },
20956 Reparsed(BufferId),
20957 Focused,
20958 FocusedIn,
20959 Blurred,
20960 DirtyChanged,
20961 Saved,
20962 TitleChanged,
20963 DiffBaseChanged,
20964 SelectionsChanged {
20965 local: bool,
20966 },
20967 ScrollPositionChanged {
20968 local: bool,
20969 autoscroll: bool,
20970 },
20971 Closed,
20972 TransactionUndone {
20973 transaction_id: clock::Lamport,
20974 },
20975 TransactionBegun {
20976 transaction_id: clock::Lamport,
20977 },
20978 Reloaded,
20979 CursorShapeChanged,
20980 PushedToNavHistory {
20981 anchor: Anchor,
20982 is_deactivate: bool,
20983 },
20984}
20985
20986impl EventEmitter<EditorEvent> for Editor {}
20987
20988impl Focusable for Editor {
20989 fn focus_handle(&self, _cx: &App) -> FocusHandle {
20990 self.focus_handle.clone()
20991 }
20992}
20993
20994impl Render for Editor {
20995 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20996 let settings = ThemeSettings::get_global(cx);
20997
20998 let mut text_style = match self.mode {
20999 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21000 color: cx.theme().colors().editor_foreground,
21001 font_family: settings.ui_font.family.clone(),
21002 font_features: settings.ui_font.features.clone(),
21003 font_fallbacks: settings.ui_font.fallbacks.clone(),
21004 font_size: rems(0.875).into(),
21005 font_weight: settings.ui_font.weight,
21006 line_height: relative(settings.buffer_line_height.value()),
21007 ..Default::default()
21008 },
21009 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
21010 color: cx.theme().colors().editor_foreground,
21011 font_family: settings.buffer_font.family.clone(),
21012 font_features: settings.buffer_font.features.clone(),
21013 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21014 font_size: settings.buffer_font_size(cx).into(),
21015 font_weight: settings.buffer_font.weight,
21016 line_height: relative(settings.buffer_line_height.value()),
21017 ..Default::default()
21018 },
21019 };
21020 if let Some(text_style_refinement) = &self.text_style_refinement {
21021 text_style.refine(text_style_refinement)
21022 }
21023
21024 let background = match self.mode {
21025 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
21026 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
21027 EditorMode::Full { .. } => cx.theme().colors().editor_background,
21028 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
21029 };
21030
21031 EditorElement::new(
21032 &cx.entity(),
21033 EditorStyle {
21034 background,
21035 local_player: cx.theme().players().local(),
21036 text: text_style,
21037 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
21038 syntax: cx.theme().syntax().clone(),
21039 status: cx.theme().status().clone(),
21040 inlay_hints_style: make_inlay_hints_style(cx),
21041 inline_completion_styles: make_suggestion_styles(cx),
21042 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
21043 show_underlines: !self.mode.is_minimap(),
21044 },
21045 )
21046 }
21047}
21048
21049impl EntityInputHandler for Editor {
21050 fn text_for_range(
21051 &mut self,
21052 range_utf16: Range<usize>,
21053 adjusted_range: &mut Option<Range<usize>>,
21054 _: &mut Window,
21055 cx: &mut Context<Self>,
21056 ) -> Option<String> {
21057 let snapshot = self.buffer.read(cx).read(cx);
21058 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
21059 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
21060 if (start.0..end.0) != range_utf16 {
21061 adjusted_range.replace(start.0..end.0);
21062 }
21063 Some(snapshot.text_for_range(start..end).collect())
21064 }
21065
21066 fn selected_text_range(
21067 &mut self,
21068 ignore_disabled_input: bool,
21069 _: &mut Window,
21070 cx: &mut Context<Self>,
21071 ) -> Option<UTF16Selection> {
21072 // Prevent the IME menu from appearing when holding down an alphabetic key
21073 // while input is disabled.
21074 if !ignore_disabled_input && !self.input_enabled {
21075 return None;
21076 }
21077
21078 let selection = self.selections.newest::<OffsetUtf16>(cx);
21079 let range = selection.range();
21080
21081 Some(UTF16Selection {
21082 range: range.start.0..range.end.0,
21083 reversed: selection.reversed,
21084 })
21085 }
21086
21087 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
21088 let snapshot = self.buffer.read(cx).read(cx);
21089 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
21090 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
21091 }
21092
21093 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21094 self.clear_highlights::<InputComposition>(cx);
21095 self.ime_transaction.take();
21096 }
21097
21098 fn replace_text_in_range(
21099 &mut self,
21100 range_utf16: Option<Range<usize>>,
21101 text: &str,
21102 window: &mut Window,
21103 cx: &mut Context<Self>,
21104 ) {
21105 if !self.input_enabled {
21106 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21107 return;
21108 }
21109
21110 self.transact(window, cx, |this, window, cx| {
21111 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
21112 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21113 Some(this.selection_replacement_ranges(range_utf16, cx))
21114 } else {
21115 this.marked_text_ranges(cx)
21116 };
21117
21118 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
21119 let newest_selection_id = this.selections.newest_anchor().id;
21120 this.selections
21121 .all::<OffsetUtf16>(cx)
21122 .iter()
21123 .zip(ranges_to_replace.iter())
21124 .find_map(|(selection, range)| {
21125 if selection.id == newest_selection_id {
21126 Some(
21127 (range.start.0 as isize - selection.head().0 as isize)
21128 ..(range.end.0 as isize - selection.head().0 as isize),
21129 )
21130 } else {
21131 None
21132 }
21133 })
21134 });
21135
21136 cx.emit(EditorEvent::InputHandled {
21137 utf16_range_to_replace: range_to_replace,
21138 text: text.into(),
21139 });
21140
21141 if let Some(new_selected_ranges) = new_selected_ranges {
21142 this.change_selections(None, window, cx, |selections| {
21143 selections.select_ranges(new_selected_ranges)
21144 });
21145 this.backspace(&Default::default(), window, cx);
21146 }
21147
21148 this.handle_input(text, window, cx);
21149 });
21150
21151 if let Some(transaction) = self.ime_transaction {
21152 self.buffer.update(cx, |buffer, cx| {
21153 buffer.group_until_transaction(transaction, cx);
21154 });
21155 }
21156
21157 self.unmark_text(window, cx);
21158 }
21159
21160 fn replace_and_mark_text_in_range(
21161 &mut self,
21162 range_utf16: Option<Range<usize>>,
21163 text: &str,
21164 new_selected_range_utf16: Option<Range<usize>>,
21165 window: &mut Window,
21166 cx: &mut Context<Self>,
21167 ) {
21168 if !self.input_enabled {
21169 return;
21170 }
21171
21172 let transaction = self.transact(window, cx, |this, window, cx| {
21173 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
21174 let snapshot = this.buffer.read(cx).read(cx);
21175 if let Some(relative_range_utf16) = range_utf16.as_ref() {
21176 for marked_range in &mut marked_ranges {
21177 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
21178 marked_range.start.0 += relative_range_utf16.start;
21179 marked_range.start =
21180 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
21181 marked_range.end =
21182 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
21183 }
21184 }
21185 Some(marked_ranges)
21186 } else if let Some(range_utf16) = range_utf16 {
21187 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21188 Some(this.selection_replacement_ranges(range_utf16, cx))
21189 } else {
21190 None
21191 };
21192
21193 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
21194 let newest_selection_id = this.selections.newest_anchor().id;
21195 this.selections
21196 .all::<OffsetUtf16>(cx)
21197 .iter()
21198 .zip(ranges_to_replace.iter())
21199 .find_map(|(selection, range)| {
21200 if selection.id == newest_selection_id {
21201 Some(
21202 (range.start.0 as isize - selection.head().0 as isize)
21203 ..(range.end.0 as isize - selection.head().0 as isize),
21204 )
21205 } else {
21206 None
21207 }
21208 })
21209 });
21210
21211 cx.emit(EditorEvent::InputHandled {
21212 utf16_range_to_replace: range_to_replace,
21213 text: text.into(),
21214 });
21215
21216 if let Some(ranges) = ranges_to_replace {
21217 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
21218 }
21219
21220 let marked_ranges = {
21221 let snapshot = this.buffer.read(cx).read(cx);
21222 this.selections
21223 .disjoint_anchors()
21224 .iter()
21225 .map(|selection| {
21226 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
21227 })
21228 .collect::<Vec<_>>()
21229 };
21230
21231 if text.is_empty() {
21232 this.unmark_text(window, cx);
21233 } else {
21234 this.highlight_text::<InputComposition>(
21235 marked_ranges.clone(),
21236 HighlightStyle {
21237 underline: Some(UnderlineStyle {
21238 thickness: px(1.),
21239 color: None,
21240 wavy: false,
21241 }),
21242 ..Default::default()
21243 },
21244 cx,
21245 );
21246 }
21247
21248 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
21249 let use_autoclose = this.use_autoclose;
21250 let use_auto_surround = this.use_auto_surround;
21251 this.set_use_autoclose(false);
21252 this.set_use_auto_surround(false);
21253 this.handle_input(text, window, cx);
21254 this.set_use_autoclose(use_autoclose);
21255 this.set_use_auto_surround(use_auto_surround);
21256
21257 if let Some(new_selected_range) = new_selected_range_utf16 {
21258 let snapshot = this.buffer.read(cx).read(cx);
21259 let new_selected_ranges = marked_ranges
21260 .into_iter()
21261 .map(|marked_range| {
21262 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
21263 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
21264 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
21265 snapshot.clip_offset_utf16(new_start, Bias::Left)
21266 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
21267 })
21268 .collect::<Vec<_>>();
21269
21270 drop(snapshot);
21271 this.change_selections(None, window, cx, |selections| {
21272 selections.select_ranges(new_selected_ranges)
21273 });
21274 }
21275 });
21276
21277 self.ime_transaction = self.ime_transaction.or(transaction);
21278 if let Some(transaction) = self.ime_transaction {
21279 self.buffer.update(cx, |buffer, cx| {
21280 buffer.group_until_transaction(transaction, cx);
21281 });
21282 }
21283
21284 if self.text_highlights::<InputComposition>(cx).is_none() {
21285 self.ime_transaction.take();
21286 }
21287 }
21288
21289 fn bounds_for_range(
21290 &mut self,
21291 range_utf16: Range<usize>,
21292 element_bounds: gpui::Bounds<Pixels>,
21293 window: &mut Window,
21294 cx: &mut Context<Self>,
21295 ) -> Option<gpui::Bounds<Pixels>> {
21296 let text_layout_details = self.text_layout_details(window);
21297 let gpui::Size {
21298 width: em_width,
21299 height: line_height,
21300 } = self.character_size(window);
21301
21302 let snapshot = self.snapshot(window, cx);
21303 let scroll_position = snapshot.scroll_position();
21304 let scroll_left = scroll_position.x * em_width;
21305
21306 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
21307 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
21308 + self.gutter_dimensions.width
21309 + self.gutter_dimensions.margin;
21310 let y = line_height * (start.row().as_f32() - scroll_position.y);
21311
21312 Some(Bounds {
21313 origin: element_bounds.origin + point(x, y),
21314 size: size(em_width, line_height),
21315 })
21316 }
21317
21318 fn character_index_for_point(
21319 &mut self,
21320 point: gpui::Point<Pixels>,
21321 _window: &mut Window,
21322 _cx: &mut Context<Self>,
21323 ) -> Option<usize> {
21324 let position_map = self.last_position_map.as_ref()?;
21325 if !position_map.text_hitbox.contains(&point) {
21326 return None;
21327 }
21328 let display_point = position_map.point_for_position(point).previous_valid;
21329 let anchor = position_map
21330 .snapshot
21331 .display_point_to_anchor(display_point, Bias::Left);
21332 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
21333 Some(utf16_offset.0)
21334 }
21335}
21336
21337trait SelectionExt {
21338 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
21339 fn spanned_rows(
21340 &self,
21341 include_end_if_at_line_start: bool,
21342 map: &DisplaySnapshot,
21343 ) -> Range<MultiBufferRow>;
21344}
21345
21346impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
21347 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
21348 let start = self
21349 .start
21350 .to_point(&map.buffer_snapshot)
21351 .to_display_point(map);
21352 let end = self
21353 .end
21354 .to_point(&map.buffer_snapshot)
21355 .to_display_point(map);
21356 if self.reversed {
21357 end..start
21358 } else {
21359 start..end
21360 }
21361 }
21362
21363 fn spanned_rows(
21364 &self,
21365 include_end_if_at_line_start: bool,
21366 map: &DisplaySnapshot,
21367 ) -> Range<MultiBufferRow> {
21368 let start = self.start.to_point(&map.buffer_snapshot);
21369 let mut end = self.end.to_point(&map.buffer_snapshot);
21370 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
21371 end.row -= 1;
21372 }
21373
21374 let buffer_start = map.prev_line_boundary(start).0;
21375 let buffer_end = map.next_line_boundary(end).0;
21376 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
21377 }
21378}
21379
21380impl<T: InvalidationRegion> InvalidationStack<T> {
21381 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
21382 where
21383 S: Clone + ToOffset,
21384 {
21385 while let Some(region) = self.last() {
21386 let all_selections_inside_invalidation_ranges =
21387 if selections.len() == region.ranges().len() {
21388 selections
21389 .iter()
21390 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
21391 .all(|(selection, invalidation_range)| {
21392 let head = selection.head().to_offset(buffer);
21393 invalidation_range.start <= head && invalidation_range.end >= head
21394 })
21395 } else {
21396 false
21397 };
21398
21399 if all_selections_inside_invalidation_ranges {
21400 break;
21401 } else {
21402 self.pop();
21403 }
21404 }
21405 }
21406}
21407
21408impl<T> Default for InvalidationStack<T> {
21409 fn default() -> Self {
21410 Self(Default::default())
21411 }
21412}
21413
21414impl<T> Deref for InvalidationStack<T> {
21415 type Target = Vec<T>;
21416
21417 fn deref(&self) -> &Self::Target {
21418 &self.0
21419 }
21420}
21421
21422impl<T> DerefMut for InvalidationStack<T> {
21423 fn deref_mut(&mut self) -> &mut Self::Target {
21424 &mut self.0
21425 }
21426}
21427
21428impl InvalidationRegion for SnippetState {
21429 fn ranges(&self) -> &[Range<Anchor>] {
21430 &self.ranges[self.active_index]
21431 }
21432}
21433
21434fn inline_completion_edit_text(
21435 current_snapshot: &BufferSnapshot,
21436 edits: &[(Range<Anchor>, String)],
21437 edit_preview: &EditPreview,
21438 include_deletions: bool,
21439 cx: &App,
21440) -> HighlightedText {
21441 let edits = edits
21442 .iter()
21443 .map(|(anchor, text)| {
21444 (
21445 anchor.start.text_anchor..anchor.end.text_anchor,
21446 text.clone(),
21447 )
21448 })
21449 .collect::<Vec<_>>();
21450
21451 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
21452}
21453
21454pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
21455 match severity {
21456 lsp::DiagnosticSeverity::ERROR => colors.error,
21457 lsp::DiagnosticSeverity::WARNING => colors.warning,
21458 lsp::DiagnosticSeverity::INFORMATION => colors.info,
21459 lsp::DiagnosticSeverity::HINT => colors.info,
21460 _ => colors.ignored,
21461 }
21462}
21463
21464pub fn styled_runs_for_code_label<'a>(
21465 label: &'a CodeLabel,
21466 syntax_theme: &'a theme::SyntaxTheme,
21467) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
21468 let fade_out = HighlightStyle {
21469 fade_out: Some(0.35),
21470 ..Default::default()
21471 };
21472
21473 let mut prev_end = label.filter_range.end;
21474 label
21475 .runs
21476 .iter()
21477 .enumerate()
21478 .flat_map(move |(ix, (range, highlight_id))| {
21479 let style = if let Some(style) = highlight_id.style(syntax_theme) {
21480 style
21481 } else {
21482 return Default::default();
21483 };
21484 let mut muted_style = style;
21485 muted_style.highlight(fade_out);
21486
21487 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
21488 if range.start >= label.filter_range.end {
21489 if range.start > prev_end {
21490 runs.push((prev_end..range.start, fade_out));
21491 }
21492 runs.push((range.clone(), muted_style));
21493 } else if range.end <= label.filter_range.end {
21494 runs.push((range.clone(), style));
21495 } else {
21496 runs.push((range.start..label.filter_range.end, style));
21497 runs.push((label.filter_range.end..range.end, muted_style));
21498 }
21499 prev_end = cmp::max(prev_end, range.end);
21500
21501 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
21502 runs.push((prev_end..label.text.len(), fade_out));
21503 }
21504
21505 runs
21506 })
21507}
21508
21509pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
21510 let mut prev_index = 0;
21511 let mut prev_codepoint: Option<char> = None;
21512 text.char_indices()
21513 .chain([(text.len(), '\0')])
21514 .filter_map(move |(index, codepoint)| {
21515 let prev_codepoint = prev_codepoint.replace(codepoint)?;
21516 let is_boundary = index == text.len()
21517 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
21518 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
21519 if is_boundary {
21520 let chunk = &text[prev_index..index];
21521 prev_index = index;
21522 Some(chunk)
21523 } else {
21524 None
21525 }
21526 })
21527}
21528
21529pub trait RangeToAnchorExt: Sized {
21530 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
21531
21532 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
21533 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
21534 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
21535 }
21536}
21537
21538impl<T: ToOffset> RangeToAnchorExt for Range<T> {
21539 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
21540 let start_offset = self.start.to_offset(snapshot);
21541 let end_offset = self.end.to_offset(snapshot);
21542 if start_offset == end_offset {
21543 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
21544 } else {
21545 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
21546 }
21547 }
21548}
21549
21550pub trait RowExt {
21551 fn as_f32(&self) -> f32;
21552
21553 fn next_row(&self) -> Self;
21554
21555 fn previous_row(&self) -> Self;
21556
21557 fn minus(&self, other: Self) -> u32;
21558}
21559
21560impl RowExt for DisplayRow {
21561 fn as_f32(&self) -> f32 {
21562 self.0 as f32
21563 }
21564
21565 fn next_row(&self) -> Self {
21566 Self(self.0 + 1)
21567 }
21568
21569 fn previous_row(&self) -> Self {
21570 Self(self.0.saturating_sub(1))
21571 }
21572
21573 fn minus(&self, other: Self) -> u32 {
21574 self.0 - other.0
21575 }
21576}
21577
21578impl RowExt for MultiBufferRow {
21579 fn as_f32(&self) -> f32 {
21580 self.0 as f32
21581 }
21582
21583 fn next_row(&self) -> Self {
21584 Self(self.0 + 1)
21585 }
21586
21587 fn previous_row(&self) -> Self {
21588 Self(self.0.saturating_sub(1))
21589 }
21590
21591 fn minus(&self, other: Self) -> u32 {
21592 self.0 - other.0
21593 }
21594}
21595
21596trait RowRangeExt {
21597 type Row;
21598
21599 fn len(&self) -> usize;
21600
21601 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
21602}
21603
21604impl RowRangeExt for Range<MultiBufferRow> {
21605 type Row = MultiBufferRow;
21606
21607 fn len(&self) -> usize {
21608 (self.end.0 - self.start.0) as usize
21609 }
21610
21611 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21612 (self.start.0..self.end.0).map(MultiBufferRow)
21613 }
21614}
21615
21616impl RowRangeExt for Range<DisplayRow> {
21617 type Row = DisplayRow;
21618
21619 fn len(&self) -> usize {
21620 (self.end.0 - self.start.0) as usize
21621 }
21622
21623 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21624 (self.start.0..self.end.0).map(DisplayRow)
21625 }
21626}
21627
21628/// If select range has more than one line, we
21629/// just point the cursor to range.start.
21630fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21631 if range.start.row == range.end.row {
21632 range
21633 } else {
21634 range.start..range.start
21635 }
21636}
21637pub struct KillRing(ClipboardItem);
21638impl Global for KillRing {}
21639
21640const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21641
21642enum BreakpointPromptEditAction {
21643 Log,
21644 Condition,
21645 HitCondition,
21646}
21647
21648struct BreakpointPromptEditor {
21649 pub(crate) prompt: Entity<Editor>,
21650 editor: WeakEntity<Editor>,
21651 breakpoint_anchor: Anchor,
21652 breakpoint: Breakpoint,
21653 edit_action: BreakpointPromptEditAction,
21654 block_ids: HashSet<CustomBlockId>,
21655 editor_margins: Arc<Mutex<EditorMargins>>,
21656 _subscriptions: Vec<Subscription>,
21657}
21658
21659impl BreakpointPromptEditor {
21660 const MAX_LINES: u8 = 4;
21661
21662 fn new(
21663 editor: WeakEntity<Editor>,
21664 breakpoint_anchor: Anchor,
21665 breakpoint: Breakpoint,
21666 edit_action: BreakpointPromptEditAction,
21667 window: &mut Window,
21668 cx: &mut Context<Self>,
21669 ) -> Self {
21670 let base_text = match edit_action {
21671 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21672 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21673 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21674 }
21675 .map(|msg| msg.to_string())
21676 .unwrap_or_default();
21677
21678 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21679 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21680
21681 let prompt = cx.new(|cx| {
21682 let mut prompt = Editor::new(
21683 EditorMode::AutoHeight {
21684 max_lines: Self::MAX_LINES as usize,
21685 },
21686 buffer,
21687 None,
21688 window,
21689 cx,
21690 );
21691 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21692 prompt.set_show_cursor_when_unfocused(false, cx);
21693 prompt.set_placeholder_text(
21694 match edit_action {
21695 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21696 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21697 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21698 },
21699 cx,
21700 );
21701
21702 prompt
21703 });
21704
21705 Self {
21706 prompt,
21707 editor,
21708 breakpoint_anchor,
21709 breakpoint,
21710 edit_action,
21711 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21712 block_ids: Default::default(),
21713 _subscriptions: vec![],
21714 }
21715 }
21716
21717 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21718 self.block_ids.extend(block_ids)
21719 }
21720
21721 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21722 if let Some(editor) = self.editor.upgrade() {
21723 let message = self
21724 .prompt
21725 .read(cx)
21726 .buffer
21727 .read(cx)
21728 .as_singleton()
21729 .expect("A multi buffer in breakpoint prompt isn't possible")
21730 .read(cx)
21731 .as_rope()
21732 .to_string();
21733
21734 editor.update(cx, |editor, cx| {
21735 editor.edit_breakpoint_at_anchor(
21736 self.breakpoint_anchor,
21737 self.breakpoint.clone(),
21738 match self.edit_action {
21739 BreakpointPromptEditAction::Log => {
21740 BreakpointEditAction::EditLogMessage(message.into())
21741 }
21742 BreakpointPromptEditAction::Condition => {
21743 BreakpointEditAction::EditCondition(message.into())
21744 }
21745 BreakpointPromptEditAction::HitCondition => {
21746 BreakpointEditAction::EditHitCondition(message.into())
21747 }
21748 },
21749 cx,
21750 );
21751
21752 editor.remove_blocks(self.block_ids.clone(), None, cx);
21753 cx.focus_self(window);
21754 });
21755 }
21756 }
21757
21758 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21759 self.editor
21760 .update(cx, |editor, cx| {
21761 editor.remove_blocks(self.block_ids.clone(), None, cx);
21762 window.focus(&editor.focus_handle);
21763 })
21764 .log_err();
21765 }
21766
21767 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21768 let settings = ThemeSettings::get_global(cx);
21769 let text_style = TextStyle {
21770 color: if self.prompt.read(cx).read_only(cx) {
21771 cx.theme().colors().text_disabled
21772 } else {
21773 cx.theme().colors().text
21774 },
21775 font_family: settings.buffer_font.family.clone(),
21776 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21777 font_size: settings.buffer_font_size(cx).into(),
21778 font_weight: settings.buffer_font.weight,
21779 line_height: relative(settings.buffer_line_height.value()),
21780 ..Default::default()
21781 };
21782 EditorElement::new(
21783 &self.prompt,
21784 EditorStyle {
21785 background: cx.theme().colors().editor_background,
21786 local_player: cx.theme().players().local(),
21787 text: text_style,
21788 ..Default::default()
21789 },
21790 )
21791 }
21792}
21793
21794impl Render for BreakpointPromptEditor {
21795 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21796 let editor_margins = *self.editor_margins.lock();
21797 let gutter_dimensions = editor_margins.gutter;
21798 h_flex()
21799 .key_context("Editor")
21800 .bg(cx.theme().colors().editor_background)
21801 .border_y_1()
21802 .border_color(cx.theme().status().info_border)
21803 .size_full()
21804 .py(window.line_height() / 2.5)
21805 .on_action(cx.listener(Self::confirm))
21806 .on_action(cx.listener(Self::cancel))
21807 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21808 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21809 }
21810}
21811
21812impl Focusable for BreakpointPromptEditor {
21813 fn focus_handle(&self, cx: &App) -> FocusHandle {
21814 self.prompt.focus_handle(cx)
21815 }
21816}
21817
21818fn all_edits_insertions_or_deletions(
21819 edits: &Vec<(Range<Anchor>, String)>,
21820 snapshot: &MultiBufferSnapshot,
21821) -> bool {
21822 let mut all_insertions = true;
21823 let mut all_deletions = true;
21824
21825 for (range, new_text) in edits.iter() {
21826 let range_is_empty = range.to_offset(&snapshot).is_empty();
21827 let text_is_empty = new_text.is_empty();
21828
21829 if range_is_empty != text_is_empty {
21830 if range_is_empty {
21831 all_deletions = false;
21832 } else {
21833 all_insertions = false;
21834 }
21835 } else {
21836 return false;
21837 }
21838
21839 if !all_insertions && !all_deletions {
21840 return false;
21841 }
21842 }
21843 all_insertions || all_deletions
21844}
21845
21846struct MissingEditPredictionKeybindingTooltip;
21847
21848impl Render for MissingEditPredictionKeybindingTooltip {
21849 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21850 ui::tooltip_container(window, cx, |container, _, cx| {
21851 container
21852 .flex_shrink_0()
21853 .max_w_80()
21854 .min_h(rems_from_px(124.))
21855 .justify_between()
21856 .child(
21857 v_flex()
21858 .flex_1()
21859 .text_ui_sm(cx)
21860 .child(Label::new("Conflict with Accept Keybinding"))
21861 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21862 )
21863 .child(
21864 h_flex()
21865 .pb_1()
21866 .gap_1()
21867 .items_end()
21868 .w_full()
21869 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21870 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21871 }))
21872 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21873 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21874 })),
21875 )
21876 })
21877 }
21878}
21879
21880#[derive(Debug, Clone, Copy, PartialEq)]
21881pub struct LineHighlight {
21882 pub background: Background,
21883 pub border: Option<gpui::Hsla>,
21884 pub include_gutter: bool,
21885 pub type_id: Option<TypeId>,
21886}
21887
21888fn render_diff_hunk_controls(
21889 row: u32,
21890 status: &DiffHunkStatus,
21891 hunk_range: Range<Anchor>,
21892 is_created_file: bool,
21893 line_height: Pixels,
21894 editor: &Entity<Editor>,
21895 _window: &mut Window,
21896 cx: &mut App,
21897) -> AnyElement {
21898 h_flex()
21899 .h(line_height)
21900 .mr_1()
21901 .gap_1()
21902 .px_0p5()
21903 .pb_1()
21904 .border_x_1()
21905 .border_b_1()
21906 .border_color(cx.theme().colors().border_variant)
21907 .rounded_b_lg()
21908 .bg(cx.theme().colors().editor_background)
21909 .gap_1()
21910 .block_mouse_except_scroll()
21911 .shadow_md()
21912 .child(if status.has_secondary_hunk() {
21913 Button::new(("stage", row as u64), "Stage")
21914 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21915 .tooltip({
21916 let focus_handle = editor.focus_handle(cx);
21917 move |window, cx| {
21918 Tooltip::for_action_in(
21919 "Stage Hunk",
21920 &::git::ToggleStaged,
21921 &focus_handle,
21922 window,
21923 cx,
21924 )
21925 }
21926 })
21927 .on_click({
21928 let editor = editor.clone();
21929 move |_event, _window, cx| {
21930 editor.update(cx, |editor, cx| {
21931 editor.stage_or_unstage_diff_hunks(
21932 true,
21933 vec![hunk_range.start..hunk_range.start],
21934 cx,
21935 );
21936 });
21937 }
21938 })
21939 } else {
21940 Button::new(("unstage", row as u64), "Unstage")
21941 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21942 .tooltip({
21943 let focus_handle = editor.focus_handle(cx);
21944 move |window, cx| {
21945 Tooltip::for_action_in(
21946 "Unstage Hunk",
21947 &::git::ToggleStaged,
21948 &focus_handle,
21949 window,
21950 cx,
21951 )
21952 }
21953 })
21954 .on_click({
21955 let editor = editor.clone();
21956 move |_event, _window, cx| {
21957 editor.update(cx, |editor, cx| {
21958 editor.stage_or_unstage_diff_hunks(
21959 false,
21960 vec![hunk_range.start..hunk_range.start],
21961 cx,
21962 );
21963 });
21964 }
21965 })
21966 })
21967 .child(
21968 Button::new(("restore", row as u64), "Restore")
21969 .tooltip({
21970 let focus_handle = editor.focus_handle(cx);
21971 move |window, cx| {
21972 Tooltip::for_action_in(
21973 "Restore Hunk",
21974 &::git::Restore,
21975 &focus_handle,
21976 window,
21977 cx,
21978 )
21979 }
21980 })
21981 .on_click({
21982 let editor = editor.clone();
21983 move |_event, window, cx| {
21984 editor.update(cx, |editor, cx| {
21985 let snapshot = editor.snapshot(window, cx);
21986 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
21987 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
21988 });
21989 }
21990 })
21991 .disabled(is_created_file),
21992 )
21993 .when(
21994 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
21995 |el| {
21996 el.child(
21997 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
21998 .shape(IconButtonShape::Square)
21999 .icon_size(IconSize::Small)
22000 // .disabled(!has_multiple_hunks)
22001 .tooltip({
22002 let focus_handle = editor.focus_handle(cx);
22003 move |window, cx| {
22004 Tooltip::for_action_in(
22005 "Next Hunk",
22006 &GoToHunk,
22007 &focus_handle,
22008 window,
22009 cx,
22010 )
22011 }
22012 })
22013 .on_click({
22014 let editor = editor.clone();
22015 move |_event, window, cx| {
22016 editor.update(cx, |editor, cx| {
22017 let snapshot = editor.snapshot(window, cx);
22018 let position =
22019 hunk_range.end.to_point(&snapshot.buffer_snapshot);
22020 editor.go_to_hunk_before_or_after_position(
22021 &snapshot,
22022 position,
22023 Direction::Next,
22024 window,
22025 cx,
22026 );
22027 editor.expand_selected_diff_hunks(cx);
22028 });
22029 }
22030 }),
22031 )
22032 .child(
22033 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
22034 .shape(IconButtonShape::Square)
22035 .icon_size(IconSize::Small)
22036 // .disabled(!has_multiple_hunks)
22037 .tooltip({
22038 let focus_handle = editor.focus_handle(cx);
22039 move |window, cx| {
22040 Tooltip::for_action_in(
22041 "Previous Hunk",
22042 &GoToPreviousHunk,
22043 &focus_handle,
22044 window,
22045 cx,
22046 )
22047 }
22048 })
22049 .on_click({
22050 let editor = editor.clone();
22051 move |_event, window, cx| {
22052 editor.update(cx, |editor, cx| {
22053 let snapshot = editor.snapshot(window, cx);
22054 let point =
22055 hunk_range.start.to_point(&snapshot.buffer_snapshot);
22056 editor.go_to_hunk_before_or_after_position(
22057 &snapshot,
22058 point,
22059 Direction::Prev,
22060 window,
22061 cx,
22062 );
22063 editor.expand_selected_diff_hunks(cx);
22064 });
22065 }
22066 }),
22067 )
22068 },
22069 )
22070 .into_any_element()
22071}