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, SearchSettings,
67 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::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(bool),
720}
721
722impl MinimapVisibility {
723 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
724 if mode.is_full() {
725 Self::Enabled(EditorSettings::get_global(cx).minimap.minimap_enabled())
726 } else {
727 Self::Disabled
728 }
729 }
730
731 fn disabled(&self) -> bool {
732 match *self {
733 Self::Disabled => true,
734 _ => false,
735 }
736 }
737
738 fn visible(&self) -> bool {
739 match *self {
740 Self::Enabled(visible) => visible,
741 _ => false,
742 }
743 }
744
745 fn toggle_visibility(&self) -> Self {
746 match *self {
747 Self::Enabled(visible) => Self::Enabled(!visible),
748 Self::Disabled => Self::Disabled,
749 }
750 }
751}
752
753#[derive(Clone, Debug)]
754struct RunnableTasks {
755 templates: Vec<(TaskSourceKind, TaskTemplate)>,
756 offset: multi_buffer::Anchor,
757 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
758 column: u32,
759 // Values of all named captures, including those starting with '_'
760 extra_variables: HashMap<String, String>,
761 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
762 context_range: Range<BufferOffset>,
763}
764
765impl RunnableTasks {
766 fn resolve<'a>(
767 &'a self,
768 cx: &'a task::TaskContext,
769 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
770 self.templates.iter().filter_map(|(kind, template)| {
771 template
772 .resolve_task(&kind.to_id_base(), cx)
773 .map(|task| (kind.clone(), task))
774 })
775 }
776}
777
778#[derive(Clone)]
779pub struct ResolvedTasks {
780 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
781 position: Anchor,
782}
783
784#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
785struct BufferOffset(usize);
786
787// Addons allow storing per-editor state in other crates (e.g. Vim)
788pub trait Addon: 'static {
789 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
790
791 fn render_buffer_header_controls(
792 &self,
793 _: &ExcerptInfo,
794 _: &Window,
795 _: &App,
796 ) -> Option<AnyElement> {
797 None
798 }
799
800 fn to_any(&self) -> &dyn std::any::Any;
801
802 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
803 None
804 }
805}
806
807/// A set of caret positions, registered when the editor was edited.
808pub struct ChangeList {
809 changes: Vec<Vec<Anchor>>,
810 /// Currently "selected" change.
811 position: Option<usize>,
812}
813
814impl ChangeList {
815 pub fn new() -> Self {
816 Self {
817 changes: Vec::new(),
818 position: None,
819 }
820 }
821
822 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
823 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
824 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
825 if self.changes.is_empty() {
826 return None;
827 }
828
829 let prev = self.position.unwrap_or(self.changes.len());
830 let next = if direction == Direction::Prev {
831 prev.saturating_sub(count)
832 } else {
833 (prev + count).min(self.changes.len() - 1)
834 };
835 self.position = Some(next);
836 self.changes.get(next).map(|anchors| anchors.as_slice())
837 }
838
839 /// Adds a new change to the list, resetting the change list position.
840 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
841 self.position.take();
842 if pop_state {
843 self.changes.pop();
844 }
845 self.changes.push(new_positions.clone());
846 }
847
848 pub fn last(&self) -> Option<&[Anchor]> {
849 self.changes.last().map(|anchors| anchors.as_slice())
850 }
851}
852
853#[derive(Clone)]
854struct InlineBlamePopoverState {
855 scroll_handle: ScrollHandle,
856 commit_message: Option<ParsedCommitMessage>,
857 markdown: Entity<Markdown>,
858}
859
860struct InlineBlamePopover {
861 position: gpui::Point<Pixels>,
862 show_task: Option<Task<()>>,
863 hide_task: Option<Task<()>>,
864 popover_bounds: Option<Bounds<Pixels>>,
865 popover_state: InlineBlamePopoverState,
866}
867
868/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
869/// a breakpoint on them.
870#[derive(Clone, Copy, Debug)]
871struct PhantomBreakpointIndicator {
872 display_row: DisplayRow,
873 /// There's a small debounce between hovering over the line and showing the indicator.
874 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
875 is_active: bool,
876 collides_with_existing_breakpoint: bool,
877}
878/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
879///
880/// See the [module level documentation](self) for more information.
881pub struct Editor {
882 focus_handle: FocusHandle,
883 last_focused_descendant: Option<WeakFocusHandle>,
884 /// The text buffer being edited
885 buffer: Entity<MultiBuffer>,
886 /// Map of how text in the buffer should be displayed.
887 /// Handles soft wraps, folds, fake inlay text insertions, etc.
888 pub display_map: Entity<DisplayMap>,
889 pub selections: SelectionsCollection,
890 pub scroll_manager: ScrollManager,
891 /// When inline assist editors are linked, they all render cursors because
892 /// typing enters text into each of them, even the ones that aren't focused.
893 pub(crate) show_cursor_when_unfocused: bool,
894 columnar_selection_tail: Option<Anchor>,
895 add_selections_state: Option<AddSelectionsState>,
896 select_next_state: Option<SelectNextState>,
897 select_prev_state: Option<SelectNextState>,
898 selection_history: SelectionHistory,
899 autoclose_regions: Vec<AutocloseRegion>,
900 snippet_stack: InvalidationStack<SnippetState>,
901 select_syntax_node_history: SelectSyntaxNodeHistory,
902 ime_transaction: Option<TransactionId>,
903 pub diagnostics_max_severity: DiagnosticSeverity,
904 active_diagnostics: ActiveDiagnostic,
905 show_inline_diagnostics: bool,
906 inline_diagnostics_update: Task<()>,
907 inline_diagnostics_enabled: bool,
908 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
909 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
910 hard_wrap: Option<usize>,
911
912 // TODO: make this a access method
913 pub project: Option<Entity<Project>>,
914 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
915 completion_provider: Option<Box<dyn CompletionProvider>>,
916 collaboration_hub: Option<Box<dyn CollaborationHub>>,
917 blink_manager: Entity<BlinkManager>,
918 show_cursor_names: bool,
919 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
920 pub show_local_selections: bool,
921 mode: EditorMode,
922 show_breadcrumbs: bool,
923 show_gutter: bool,
924 show_scrollbars: bool,
925 minimap_visibility: MinimapVisibility,
926 disable_expand_excerpt_buttons: bool,
927 show_line_numbers: Option<bool>,
928 use_relative_line_numbers: Option<bool>,
929 show_git_diff_gutter: Option<bool>,
930 show_code_actions: Option<bool>,
931 show_runnables: Option<bool>,
932 show_breakpoints: Option<bool>,
933 show_wrap_guides: Option<bool>,
934 show_indent_guides: Option<bool>,
935 placeholder_text: Option<Arc<str>>,
936 highlight_order: usize,
937 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
938 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
939 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
940 scrollbar_marker_state: ScrollbarMarkerState,
941 active_indent_guides_state: ActiveIndentGuidesState,
942 nav_history: Option<ItemNavHistory>,
943 context_menu: RefCell<Option<CodeContextMenu>>,
944 context_menu_options: Option<ContextMenuOptions>,
945 mouse_context_menu: Option<MouseContextMenu>,
946 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
947 inline_blame_popover: Option<InlineBlamePopover>,
948 signature_help_state: SignatureHelpState,
949 auto_signature_help: Option<bool>,
950 find_all_references_task_sources: Vec<Anchor>,
951 next_completion_id: CompletionId,
952 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
953 code_actions_task: Option<Task<Result<()>>>,
954 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
955 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
956 document_highlights_task: Option<Task<()>>,
957 linked_editing_range_task: Option<Task<Option<()>>>,
958 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
959 pending_rename: Option<RenameState>,
960 searchable: bool,
961 cursor_shape: CursorShape,
962 current_line_highlight: Option<CurrentLineHighlight>,
963 collapse_matches: bool,
964 autoindent_mode: Option<AutoindentMode>,
965 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
966 input_enabled: bool,
967 use_modal_editing: bool,
968 read_only: bool,
969 leader_id: Option<CollaboratorId>,
970 remote_id: Option<ViewId>,
971 pub hover_state: HoverState,
972 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
973 gutter_hovered: bool,
974 hovered_link_state: Option<HoveredLinkState>,
975 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
976 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
977 active_inline_completion: Option<InlineCompletionState>,
978 /// Used to prevent flickering as the user types while the menu is open
979 stale_inline_completion_in_menu: Option<InlineCompletionState>,
980 edit_prediction_settings: EditPredictionSettings,
981 inline_completions_hidden_for_vim_mode: bool,
982 show_inline_completions_override: Option<bool>,
983 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
984 edit_prediction_preview: EditPredictionPreview,
985 edit_prediction_indent_conflict: bool,
986 edit_prediction_requires_modifier_in_indent_conflict: bool,
987 inlay_hint_cache: InlayHintCache,
988 next_inlay_id: usize,
989 _subscriptions: Vec<Subscription>,
990 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
991 gutter_dimensions: GutterDimensions,
992 style: Option<EditorStyle>,
993 text_style_refinement: Option<TextStyleRefinement>,
994 next_editor_action_id: EditorActionId,
995 editor_actions:
996 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
997 use_autoclose: bool,
998 use_auto_surround: bool,
999 auto_replace_emoji_shortcode: bool,
1000 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1001 show_git_blame_gutter: bool,
1002 show_git_blame_inline: bool,
1003 show_git_blame_inline_delay_task: Option<Task<()>>,
1004 git_blame_inline_enabled: bool,
1005 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1006 serialize_dirty_buffers: bool,
1007 show_selection_menu: Option<bool>,
1008 blame: Option<Entity<GitBlame>>,
1009 blame_subscription: Option<Subscription>,
1010 custom_context_menu: Option<
1011 Box<
1012 dyn 'static
1013 + Fn(
1014 &mut Self,
1015 DisplayPoint,
1016 &mut Window,
1017 &mut Context<Self>,
1018 ) -> Option<Entity<ui::ContextMenu>>,
1019 >,
1020 >,
1021 last_bounds: Option<Bounds<Pixels>>,
1022 last_position_map: Option<Rc<PositionMap>>,
1023 expect_bounds_change: Option<Bounds<Pixels>>,
1024 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1025 tasks_update_task: Option<Task<()>>,
1026 breakpoint_store: Option<Entity<BreakpointStore>>,
1027 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1028 in_project_search: bool,
1029 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1030 breadcrumb_header: Option<String>,
1031 focused_block: Option<FocusedBlock>,
1032 next_scroll_position: NextScrollCursorCenterTopBottom,
1033 addons: HashMap<TypeId, Box<dyn Addon>>,
1034 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1035 load_diff_task: Option<Shared<Task<()>>>,
1036 /// Whether we are temporarily displaying a diff other than git's
1037 temporary_diff_override: bool,
1038 selection_mark_mode: bool,
1039 toggle_fold_multiple_buffers: Task<()>,
1040 _scroll_cursor_center_top_bottom_task: Task<()>,
1041 serialize_selections: Task<()>,
1042 serialize_folds: Task<()>,
1043 mouse_cursor_hidden: bool,
1044 minimap: Option<Entity<Self>>,
1045 hide_mouse_mode: HideMouseMode,
1046 pub change_list: ChangeList,
1047 inline_value_cache: InlineValueCache,
1048}
1049
1050#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1051enum NextScrollCursorCenterTopBottom {
1052 #[default]
1053 Center,
1054 Top,
1055 Bottom,
1056}
1057
1058impl NextScrollCursorCenterTopBottom {
1059 fn next(&self) -> Self {
1060 match self {
1061 Self::Center => Self::Top,
1062 Self::Top => Self::Bottom,
1063 Self::Bottom => Self::Center,
1064 }
1065 }
1066}
1067
1068#[derive(Clone)]
1069pub struct EditorSnapshot {
1070 pub mode: EditorMode,
1071 show_gutter: bool,
1072 show_line_numbers: Option<bool>,
1073 show_git_diff_gutter: Option<bool>,
1074 show_runnables: Option<bool>,
1075 show_breakpoints: Option<bool>,
1076 git_blame_gutter_max_author_length: Option<usize>,
1077 pub display_snapshot: DisplaySnapshot,
1078 pub placeholder_text: Option<Arc<str>>,
1079 is_focused: bool,
1080 scroll_anchor: ScrollAnchor,
1081 ongoing_scroll: OngoingScroll,
1082 current_line_highlight: CurrentLineHighlight,
1083 gutter_hovered: bool,
1084}
1085
1086#[derive(Default, Debug, Clone, Copy)]
1087pub struct GutterDimensions {
1088 pub left_padding: Pixels,
1089 pub right_padding: Pixels,
1090 pub width: Pixels,
1091 pub margin: Pixels,
1092 pub git_blame_entries_width: Option<Pixels>,
1093}
1094
1095impl GutterDimensions {
1096 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1097 Self {
1098 margin: Self::default_gutter_margin(font_id, font_size, cx),
1099 ..Default::default()
1100 }
1101 }
1102
1103 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1104 -cx.text_system().descent(font_id, font_size)
1105 }
1106 /// The full width of the space taken up by the gutter.
1107 pub fn full_width(&self) -> Pixels {
1108 self.margin + self.width
1109 }
1110
1111 /// The width of the space reserved for the fold indicators,
1112 /// use alongside 'justify_end' and `gutter_width` to
1113 /// right align content with the line numbers
1114 pub fn fold_area_width(&self) -> Pixels {
1115 self.margin + self.right_padding
1116 }
1117}
1118
1119#[derive(Debug)]
1120pub struct RemoteSelection {
1121 pub replica_id: ReplicaId,
1122 pub selection: Selection<Anchor>,
1123 pub cursor_shape: CursorShape,
1124 pub collaborator_id: CollaboratorId,
1125 pub line_mode: bool,
1126 pub user_name: Option<SharedString>,
1127 pub color: PlayerColor,
1128}
1129
1130#[derive(Clone, Debug)]
1131struct SelectionHistoryEntry {
1132 selections: Arc<[Selection<Anchor>]>,
1133 select_next_state: Option<SelectNextState>,
1134 select_prev_state: Option<SelectNextState>,
1135 add_selections_state: Option<AddSelectionsState>,
1136}
1137
1138enum SelectionHistoryMode {
1139 Normal,
1140 Undoing,
1141 Redoing,
1142}
1143
1144#[derive(Clone, PartialEq, Eq, Hash)]
1145struct HoveredCursor {
1146 replica_id: u16,
1147 selection_id: usize,
1148}
1149
1150impl Default for SelectionHistoryMode {
1151 fn default() -> Self {
1152 Self::Normal
1153 }
1154}
1155
1156#[derive(Default)]
1157struct SelectionHistory {
1158 #[allow(clippy::type_complexity)]
1159 selections_by_transaction:
1160 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1161 mode: SelectionHistoryMode,
1162 undo_stack: VecDeque<SelectionHistoryEntry>,
1163 redo_stack: VecDeque<SelectionHistoryEntry>,
1164}
1165
1166impl SelectionHistory {
1167 fn insert_transaction(
1168 &mut self,
1169 transaction_id: TransactionId,
1170 selections: Arc<[Selection<Anchor>]>,
1171 ) {
1172 self.selections_by_transaction
1173 .insert(transaction_id, (selections, None));
1174 }
1175
1176 #[allow(clippy::type_complexity)]
1177 fn transaction(
1178 &self,
1179 transaction_id: TransactionId,
1180 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1181 self.selections_by_transaction.get(&transaction_id)
1182 }
1183
1184 #[allow(clippy::type_complexity)]
1185 fn transaction_mut(
1186 &mut self,
1187 transaction_id: TransactionId,
1188 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1189 self.selections_by_transaction.get_mut(&transaction_id)
1190 }
1191
1192 fn push(&mut self, entry: SelectionHistoryEntry) {
1193 if !entry.selections.is_empty() {
1194 match self.mode {
1195 SelectionHistoryMode::Normal => {
1196 self.push_undo(entry);
1197 self.redo_stack.clear();
1198 }
1199 SelectionHistoryMode::Undoing => self.push_redo(entry),
1200 SelectionHistoryMode::Redoing => self.push_undo(entry),
1201 }
1202 }
1203 }
1204
1205 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1206 if self
1207 .undo_stack
1208 .back()
1209 .map_or(true, |e| e.selections != entry.selections)
1210 {
1211 self.undo_stack.push_back(entry);
1212 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1213 self.undo_stack.pop_front();
1214 }
1215 }
1216 }
1217
1218 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1219 if self
1220 .redo_stack
1221 .back()
1222 .map_or(true, |e| e.selections != entry.selections)
1223 {
1224 self.redo_stack.push_back(entry);
1225 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1226 self.redo_stack.pop_front();
1227 }
1228 }
1229 }
1230}
1231
1232#[derive(Clone, Copy)]
1233pub struct RowHighlightOptions {
1234 pub autoscroll: bool,
1235 pub include_gutter: bool,
1236}
1237
1238impl Default for RowHighlightOptions {
1239 fn default() -> Self {
1240 Self {
1241 autoscroll: Default::default(),
1242 include_gutter: true,
1243 }
1244 }
1245}
1246
1247struct RowHighlight {
1248 index: usize,
1249 range: Range<Anchor>,
1250 color: Hsla,
1251 options: RowHighlightOptions,
1252 type_id: TypeId,
1253}
1254
1255#[derive(Clone, Debug)]
1256struct AddSelectionsState {
1257 above: bool,
1258 stack: Vec<usize>,
1259}
1260
1261#[derive(Clone)]
1262struct SelectNextState {
1263 query: AhoCorasick,
1264 wordwise: bool,
1265 done: bool,
1266}
1267
1268impl std::fmt::Debug for SelectNextState {
1269 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1270 f.debug_struct(std::any::type_name::<Self>())
1271 .field("wordwise", &self.wordwise)
1272 .field("done", &self.done)
1273 .finish()
1274 }
1275}
1276
1277#[derive(Debug)]
1278struct AutocloseRegion {
1279 selection_id: usize,
1280 range: Range<Anchor>,
1281 pair: BracketPair,
1282}
1283
1284#[derive(Debug)]
1285struct SnippetState {
1286 ranges: Vec<Vec<Range<Anchor>>>,
1287 active_index: usize,
1288 choices: Vec<Option<Vec<String>>>,
1289}
1290
1291#[doc(hidden)]
1292pub struct RenameState {
1293 pub range: Range<Anchor>,
1294 pub old_name: Arc<str>,
1295 pub editor: Entity<Editor>,
1296 block_id: CustomBlockId,
1297}
1298
1299struct InvalidationStack<T>(Vec<T>);
1300
1301struct RegisteredInlineCompletionProvider {
1302 provider: Arc<dyn InlineCompletionProviderHandle>,
1303 _subscription: Subscription,
1304}
1305
1306#[derive(Debug, PartialEq, Eq)]
1307pub struct ActiveDiagnosticGroup {
1308 pub active_range: Range<Anchor>,
1309 pub active_message: String,
1310 pub group_id: usize,
1311 pub blocks: HashSet<CustomBlockId>,
1312}
1313
1314#[derive(Debug, PartialEq, Eq)]
1315
1316pub(crate) enum ActiveDiagnostic {
1317 None,
1318 All,
1319 Group(ActiveDiagnosticGroup),
1320}
1321
1322#[derive(Serialize, Deserialize, Clone, Debug)]
1323pub struct ClipboardSelection {
1324 /// The number of bytes in this selection.
1325 pub len: usize,
1326 /// Whether this was a full-line selection.
1327 pub is_entire_line: bool,
1328 /// The indentation of the first line when this content was originally copied.
1329 pub first_line_indent: u32,
1330}
1331
1332// selections, scroll behavior, was newest selection reversed
1333type SelectSyntaxNodeHistoryState = (
1334 Box<[Selection<usize>]>,
1335 SelectSyntaxNodeScrollBehavior,
1336 bool,
1337);
1338
1339#[derive(Default)]
1340struct SelectSyntaxNodeHistory {
1341 stack: Vec<SelectSyntaxNodeHistoryState>,
1342 // disable temporarily to allow changing selections without losing the stack
1343 pub disable_clearing: bool,
1344}
1345
1346impl SelectSyntaxNodeHistory {
1347 pub fn try_clear(&mut self) {
1348 if !self.disable_clearing {
1349 self.stack.clear();
1350 }
1351 }
1352
1353 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1354 self.stack.push(selection);
1355 }
1356
1357 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1358 self.stack.pop()
1359 }
1360}
1361
1362enum SelectSyntaxNodeScrollBehavior {
1363 CursorTop,
1364 FitSelection,
1365 CursorBottom,
1366}
1367
1368#[derive(Debug)]
1369pub(crate) struct NavigationData {
1370 cursor_anchor: Anchor,
1371 cursor_position: Point,
1372 scroll_anchor: ScrollAnchor,
1373 scroll_top_row: u32,
1374}
1375
1376#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1377pub enum GotoDefinitionKind {
1378 Symbol,
1379 Declaration,
1380 Type,
1381 Implementation,
1382}
1383
1384#[derive(Debug, Clone)]
1385enum InlayHintRefreshReason {
1386 ModifiersChanged(bool),
1387 Toggle(bool),
1388 SettingsChange(InlayHintSettings),
1389 NewLinesShown,
1390 BufferEdited(HashSet<Arc<Language>>),
1391 RefreshRequested,
1392 ExcerptsRemoved(Vec<ExcerptId>),
1393}
1394
1395impl InlayHintRefreshReason {
1396 fn description(&self) -> &'static str {
1397 match self {
1398 Self::ModifiersChanged(_) => "modifiers changed",
1399 Self::Toggle(_) => "toggle",
1400 Self::SettingsChange(_) => "settings change",
1401 Self::NewLinesShown => "new lines shown",
1402 Self::BufferEdited(_) => "buffer edited",
1403 Self::RefreshRequested => "refresh requested",
1404 Self::ExcerptsRemoved(_) => "excerpts removed",
1405 }
1406 }
1407}
1408
1409pub enum FormatTarget {
1410 Buffers,
1411 Ranges(Vec<Range<MultiBufferPoint>>),
1412}
1413
1414pub(crate) struct FocusedBlock {
1415 id: BlockId,
1416 focus_handle: WeakFocusHandle,
1417}
1418
1419#[derive(Clone)]
1420enum JumpData {
1421 MultiBufferRow {
1422 row: MultiBufferRow,
1423 line_offset_from_top: u32,
1424 },
1425 MultiBufferPoint {
1426 excerpt_id: ExcerptId,
1427 position: Point,
1428 anchor: text::Anchor,
1429 line_offset_from_top: u32,
1430 },
1431}
1432
1433pub enum MultibufferSelectionMode {
1434 First,
1435 All,
1436}
1437
1438#[derive(Clone, Copy, Debug, Default)]
1439pub struct RewrapOptions {
1440 pub override_language_settings: bool,
1441 pub preserve_existing_whitespace: bool,
1442}
1443
1444impl Editor {
1445 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1446 let buffer = cx.new(|cx| Buffer::local("", cx));
1447 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1448 Self::new(
1449 EditorMode::SingleLine { auto_width: false },
1450 buffer,
1451 None,
1452 window,
1453 cx,
1454 )
1455 }
1456
1457 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1458 let buffer = cx.new(|cx| Buffer::local("", cx));
1459 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1460 Self::new(EditorMode::full(), buffer, None, window, cx)
1461 }
1462
1463 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1464 let buffer = cx.new(|cx| Buffer::local("", cx));
1465 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1466 Self::new(
1467 EditorMode::SingleLine { auto_width: true },
1468 buffer,
1469 None,
1470 window,
1471 cx,
1472 )
1473 }
1474
1475 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1476 let buffer = cx.new(|cx| Buffer::local("", cx));
1477 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1478 Self::new(
1479 EditorMode::AutoHeight { max_lines },
1480 buffer,
1481 None,
1482 window,
1483 cx,
1484 )
1485 }
1486
1487 pub fn for_buffer(
1488 buffer: Entity<Buffer>,
1489 project: Option<Entity<Project>>,
1490 window: &mut Window,
1491 cx: &mut Context<Self>,
1492 ) -> Self {
1493 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1494 Self::new(EditorMode::full(), buffer, project, window, cx)
1495 }
1496
1497 pub fn for_multibuffer(
1498 buffer: Entity<MultiBuffer>,
1499 project: Option<Entity<Project>>,
1500 window: &mut Window,
1501 cx: &mut Context<Self>,
1502 ) -> Self {
1503 Self::new(EditorMode::full(), buffer, project, window, cx)
1504 }
1505
1506 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1507 let mut clone = Self::new(
1508 self.mode.clone(),
1509 self.buffer.clone(),
1510 self.project.clone(),
1511 window,
1512 cx,
1513 );
1514 self.display_map.update(cx, |display_map, cx| {
1515 let snapshot = display_map.snapshot(cx);
1516 clone.display_map.update(cx, |display_map, cx| {
1517 display_map.set_state(&snapshot, cx);
1518 });
1519 });
1520 clone.folds_did_change(cx);
1521 clone.selections.clone_state(&self.selections);
1522 clone.scroll_manager.clone_state(&self.scroll_manager);
1523 clone.searchable = self.searchable;
1524 clone.read_only = self.read_only;
1525 clone
1526 }
1527
1528 pub fn new(
1529 mode: EditorMode,
1530 buffer: Entity<MultiBuffer>,
1531 project: Option<Entity<Project>>,
1532 window: &mut Window,
1533 cx: &mut Context<Self>,
1534 ) -> Self {
1535 Editor::new_internal(mode, buffer, project, None, window, cx)
1536 }
1537
1538 fn new_internal(
1539 mode: EditorMode,
1540 buffer: Entity<MultiBuffer>,
1541 project: Option<Entity<Project>>,
1542 display_map: Option<Entity<DisplayMap>>,
1543 window: &mut Window,
1544 cx: &mut Context<Self>,
1545 ) -> Self {
1546 debug_assert!(
1547 display_map.is_none() || mode.is_minimap(),
1548 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1549 );
1550
1551 let full_mode = mode.is_full();
1552 let diagnostics_max_severity = if full_mode {
1553 EditorSettings::get_global(cx)
1554 .diagnostics_max_severity
1555 .unwrap_or(DiagnosticSeverity::Hint)
1556 } else {
1557 DiagnosticSeverity::Off
1558 };
1559 let style = window.text_style();
1560 let font_size = style.font_size.to_pixels(window.rem_size());
1561 let editor = cx.entity().downgrade();
1562 let fold_placeholder = FoldPlaceholder {
1563 constrain_width: true,
1564 render: Arc::new(move |fold_id, fold_range, cx| {
1565 let editor = editor.clone();
1566 div()
1567 .id(fold_id)
1568 .bg(cx.theme().colors().ghost_element_background)
1569 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1570 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1571 .rounded_xs()
1572 .size_full()
1573 .cursor_pointer()
1574 .child("⋯")
1575 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1576 .on_click(move |_, _window, cx| {
1577 editor
1578 .update(cx, |editor, cx| {
1579 editor.unfold_ranges(
1580 &[fold_range.start..fold_range.end],
1581 true,
1582 false,
1583 cx,
1584 );
1585 cx.stop_propagation();
1586 })
1587 .ok();
1588 })
1589 .into_any()
1590 }),
1591 merge_adjacent: true,
1592 ..FoldPlaceholder::default()
1593 };
1594 let display_map = display_map.unwrap_or_else(|| {
1595 cx.new(|cx| {
1596 DisplayMap::new(
1597 buffer.clone(),
1598 style.font(),
1599 font_size,
1600 None,
1601 FILE_HEADER_HEIGHT,
1602 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1603 fold_placeholder,
1604 diagnostics_max_severity,
1605 cx,
1606 )
1607 })
1608 });
1609
1610 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1611
1612 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1613
1614 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1615 .then(|| language_settings::SoftWrap::None);
1616
1617 let mut project_subscriptions = Vec::new();
1618 if mode.is_full() {
1619 if let Some(project) = project.as_ref() {
1620 project_subscriptions.push(cx.subscribe_in(
1621 project,
1622 window,
1623 |editor, _, event, window, cx| match event {
1624 project::Event::RefreshCodeLens => {
1625 // we always query lens with actions, without storing them, always refreshing them
1626 }
1627 project::Event::RefreshInlayHints => {
1628 editor
1629 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1630 }
1631 project::Event::SnippetEdit(id, snippet_edits) => {
1632 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1633 let focus_handle = editor.focus_handle(cx);
1634 if focus_handle.is_focused(window) {
1635 let snapshot = buffer.read(cx).snapshot();
1636 for (range, snippet) in snippet_edits {
1637 let editor_range =
1638 language::range_from_lsp(*range).to_offset(&snapshot);
1639 editor
1640 .insert_snippet(
1641 &[editor_range],
1642 snippet.clone(),
1643 window,
1644 cx,
1645 )
1646 .ok();
1647 }
1648 }
1649 }
1650 }
1651 _ => {}
1652 },
1653 ));
1654 if let Some(task_inventory) = project
1655 .read(cx)
1656 .task_store()
1657 .read(cx)
1658 .task_inventory()
1659 .cloned()
1660 {
1661 project_subscriptions.push(cx.observe_in(
1662 &task_inventory,
1663 window,
1664 |editor, _, window, cx| {
1665 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1666 },
1667 ));
1668 };
1669
1670 project_subscriptions.push(cx.subscribe_in(
1671 &project.read(cx).breakpoint_store(),
1672 window,
1673 |editor, _, event, window, cx| match event {
1674 BreakpointStoreEvent::ClearDebugLines => {
1675 editor.clear_row_highlights::<ActiveDebugLine>();
1676 editor.refresh_inline_values(cx);
1677 }
1678 BreakpointStoreEvent::SetDebugLine => {
1679 if editor.go_to_active_debug_line(window, cx) {
1680 cx.stop_propagation();
1681 }
1682
1683 editor.refresh_inline_values(cx);
1684 }
1685 _ => {}
1686 },
1687 ));
1688 }
1689 }
1690
1691 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1692
1693 let inlay_hint_settings =
1694 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1695 let focus_handle = cx.focus_handle();
1696 cx.on_focus(&focus_handle, window, Self::handle_focus)
1697 .detach();
1698 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1699 .detach();
1700 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1701 .detach();
1702 cx.on_blur(&focus_handle, window, Self::handle_blur)
1703 .detach();
1704
1705 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1706 Some(false)
1707 } else {
1708 None
1709 };
1710
1711 let breakpoint_store = match (&mode, project.as_ref()) {
1712 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1713 _ => None,
1714 };
1715
1716 let mut code_action_providers = Vec::new();
1717 let mut load_uncommitted_diff = None;
1718 if let Some(project) = project.clone() {
1719 load_uncommitted_diff = Some(
1720 update_uncommitted_diff_for_buffer(
1721 cx.entity(),
1722 &project,
1723 buffer.read(cx).all_buffers(),
1724 buffer.clone(),
1725 cx,
1726 )
1727 .shared(),
1728 );
1729 code_action_providers.push(Rc::new(project) as Rc<_>);
1730 }
1731
1732 let mut this = Self {
1733 focus_handle,
1734 show_cursor_when_unfocused: false,
1735 last_focused_descendant: None,
1736 buffer: buffer.clone(),
1737 display_map: display_map.clone(),
1738 selections,
1739 scroll_manager: ScrollManager::new(cx),
1740 columnar_selection_tail: None,
1741 add_selections_state: None,
1742 select_next_state: None,
1743 select_prev_state: None,
1744 selection_history: SelectionHistory::default(),
1745 autoclose_regions: Vec::new(),
1746 snippet_stack: InvalidationStack::default(),
1747 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1748 ime_transaction: None,
1749 active_diagnostics: ActiveDiagnostic::None,
1750 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1751 inline_diagnostics_update: Task::ready(()),
1752 inline_diagnostics: Vec::new(),
1753 soft_wrap_mode_override,
1754 diagnostics_max_severity,
1755 hard_wrap: None,
1756 completion_provider: project.clone().map(|project| Box::new(project) as _),
1757 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1758 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1759 project,
1760 blink_manager: blink_manager.clone(),
1761 show_local_selections: true,
1762 show_scrollbars: full_mode,
1763 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1764 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1765 show_gutter: mode.is_full(),
1766 show_line_numbers: None,
1767 use_relative_line_numbers: None,
1768 disable_expand_excerpt_buttons: false,
1769 show_git_diff_gutter: None,
1770 show_code_actions: None,
1771 show_runnables: None,
1772 show_breakpoints: None,
1773 show_wrap_guides: None,
1774 show_indent_guides,
1775 placeholder_text: None,
1776 highlight_order: 0,
1777 highlighted_rows: HashMap::default(),
1778 background_highlights: TreeMap::default(),
1779 gutter_highlights: TreeMap::default(),
1780 scrollbar_marker_state: ScrollbarMarkerState::default(),
1781 active_indent_guides_state: ActiveIndentGuidesState::default(),
1782 nav_history: None,
1783 context_menu: RefCell::new(None),
1784 context_menu_options: None,
1785 mouse_context_menu: None,
1786 completion_tasks: Vec::new(),
1787 inline_blame_popover: None,
1788 signature_help_state: SignatureHelpState::default(),
1789 auto_signature_help: None,
1790 find_all_references_task_sources: Vec::new(),
1791 next_completion_id: 0,
1792 next_inlay_id: 0,
1793 code_action_providers,
1794 available_code_actions: None,
1795 code_actions_task: None,
1796 quick_selection_highlight_task: None,
1797 debounced_selection_highlight_task: None,
1798 document_highlights_task: None,
1799 linked_editing_range_task: None,
1800 pending_rename: None,
1801 searchable: true,
1802 cursor_shape: EditorSettings::get_global(cx)
1803 .cursor_shape
1804 .unwrap_or_default(),
1805 current_line_highlight: None,
1806 autoindent_mode: Some(AutoindentMode::EachLine),
1807 collapse_matches: false,
1808 workspace: None,
1809 input_enabled: true,
1810 use_modal_editing: mode.is_full(),
1811 read_only: mode.is_minimap(),
1812 use_autoclose: true,
1813 use_auto_surround: true,
1814 auto_replace_emoji_shortcode: false,
1815 jsx_tag_auto_close_enabled_in_any_buffer: false,
1816 leader_id: None,
1817 remote_id: None,
1818 hover_state: HoverState::default(),
1819 pending_mouse_down: None,
1820 hovered_link_state: None,
1821 edit_prediction_provider: None,
1822 active_inline_completion: None,
1823 stale_inline_completion_in_menu: None,
1824 edit_prediction_preview: EditPredictionPreview::Inactive {
1825 released_too_fast: false,
1826 },
1827 inline_diagnostics_enabled: mode.is_full(),
1828 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1829 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1830
1831 gutter_hovered: false,
1832 pixel_position_of_newest_cursor: None,
1833 last_bounds: None,
1834 last_position_map: None,
1835 expect_bounds_change: None,
1836 gutter_dimensions: GutterDimensions::default(),
1837 style: None,
1838 show_cursor_names: false,
1839 hovered_cursors: HashMap::default(),
1840 next_editor_action_id: EditorActionId::default(),
1841 editor_actions: Rc::default(),
1842 inline_completions_hidden_for_vim_mode: false,
1843 show_inline_completions_override: None,
1844 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1845 edit_prediction_settings: EditPredictionSettings::Disabled,
1846 edit_prediction_indent_conflict: false,
1847 edit_prediction_requires_modifier_in_indent_conflict: true,
1848 custom_context_menu: None,
1849 show_git_blame_gutter: false,
1850 show_git_blame_inline: false,
1851 show_selection_menu: None,
1852 show_git_blame_inline_delay_task: None,
1853 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1854 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1855 serialize_dirty_buffers: !mode.is_minimap()
1856 && ProjectSettings::get_global(cx)
1857 .session
1858 .restore_unsaved_buffers,
1859 blame: None,
1860 blame_subscription: None,
1861 tasks: BTreeMap::default(),
1862
1863 breakpoint_store,
1864 gutter_breakpoint_indicator: (None, None),
1865 _subscriptions: vec![
1866 cx.observe(&buffer, Self::on_buffer_changed),
1867 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1868 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1869 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1870 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1871 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1872 cx.observe_window_activation(window, |editor, window, cx| {
1873 let active = window.is_window_active();
1874 editor.blink_manager.update(cx, |blink_manager, cx| {
1875 if active {
1876 blink_manager.enable(cx);
1877 } else {
1878 blink_manager.disable(cx);
1879 }
1880 });
1881 }),
1882 ],
1883 tasks_update_task: None,
1884 linked_edit_ranges: Default::default(),
1885 in_project_search: false,
1886 previous_search_ranges: None,
1887 breadcrumb_header: None,
1888 focused_block: None,
1889 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1890 addons: HashMap::default(),
1891 registered_buffers: HashMap::default(),
1892 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1893 selection_mark_mode: false,
1894 toggle_fold_multiple_buffers: Task::ready(()),
1895 serialize_selections: Task::ready(()),
1896 serialize_folds: Task::ready(()),
1897 text_style_refinement: None,
1898 load_diff_task: load_uncommitted_diff,
1899 temporary_diff_override: false,
1900 mouse_cursor_hidden: false,
1901 minimap: None,
1902 hide_mouse_mode: EditorSettings::get_global(cx)
1903 .hide_mouse
1904 .unwrap_or_default(),
1905 change_list: ChangeList::new(),
1906 mode,
1907 };
1908 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1909 this._subscriptions
1910 .push(cx.observe(breakpoints, |_, _, cx| {
1911 cx.notify();
1912 }));
1913 }
1914 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1915 this._subscriptions.extend(project_subscriptions);
1916
1917 this._subscriptions.push(cx.subscribe_in(
1918 &cx.entity(),
1919 window,
1920 |editor, _, e: &EditorEvent, window, cx| match e {
1921 EditorEvent::ScrollPositionChanged { local, .. } => {
1922 if *local {
1923 let new_anchor = editor.scroll_manager.anchor();
1924 let snapshot = editor.snapshot(window, cx);
1925 editor.update_restoration_data(cx, move |data| {
1926 data.scroll_position = (
1927 new_anchor.top_row(&snapshot.buffer_snapshot),
1928 new_anchor.offset,
1929 );
1930 });
1931 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
1932 editor.inline_blame_popover.take();
1933 }
1934 }
1935 EditorEvent::Edited { .. } => {
1936 if !vim_enabled(cx) {
1937 let (map, selections) = editor.selections.all_adjusted_display(cx);
1938 let pop_state = editor
1939 .change_list
1940 .last()
1941 .map(|previous| {
1942 previous.len() == selections.len()
1943 && previous.iter().enumerate().all(|(ix, p)| {
1944 p.to_display_point(&map).row()
1945 == selections[ix].head().row()
1946 })
1947 })
1948 .unwrap_or(false);
1949 let new_positions = selections
1950 .into_iter()
1951 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
1952 .collect();
1953 editor
1954 .change_list
1955 .push_to_change_list(pop_state, new_positions);
1956 }
1957 }
1958 _ => (),
1959 },
1960 ));
1961
1962 if let Some(dap_store) = this
1963 .project
1964 .as_ref()
1965 .map(|project| project.read(cx).dap_store())
1966 {
1967 let weak_editor = cx.weak_entity();
1968
1969 this._subscriptions
1970 .push(
1971 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
1972 let session_entity = cx.entity();
1973 weak_editor
1974 .update(cx, |editor, cx| {
1975 editor._subscriptions.push(
1976 cx.subscribe(&session_entity, Self::on_debug_session_event),
1977 );
1978 })
1979 .ok();
1980 }),
1981 );
1982
1983 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
1984 this._subscriptions
1985 .push(cx.subscribe(&session, Self::on_debug_session_event));
1986 }
1987 }
1988
1989 this.end_selection(window, cx);
1990 this.scroll_manager.show_scrollbars(window, cx);
1991 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1992
1993 if full_mode {
1994 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1995 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1996
1997 if this.git_blame_inline_enabled {
1998 this.start_git_blame_inline(false, window, cx);
1999 }
2000
2001 this.go_to_active_debug_line(window, cx);
2002
2003 if let Some(buffer) = buffer.read(cx).as_singleton() {
2004 if let Some(project) = this.project.as_ref() {
2005 let handle = project.update(cx, |project, cx| {
2006 project.register_buffer_with_language_servers(&buffer, cx)
2007 });
2008 this.registered_buffers
2009 .insert(buffer.read(cx).remote_id(), handle);
2010 }
2011 }
2012
2013 this.minimap = this.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2014 }
2015
2016 this.report_editor_event("Editor Opened", None, cx);
2017 this
2018 }
2019
2020 pub fn deploy_mouse_context_menu(
2021 &mut self,
2022 position: gpui::Point<Pixels>,
2023 context_menu: Entity<ContextMenu>,
2024 window: &mut Window,
2025 cx: &mut Context<Self>,
2026 ) {
2027 self.mouse_context_menu = Some(MouseContextMenu::new(
2028 self,
2029 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2030 context_menu,
2031 window,
2032 cx,
2033 ));
2034 }
2035
2036 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2037 self.mouse_context_menu
2038 .as_ref()
2039 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2040 }
2041
2042 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2043 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2044 }
2045
2046 fn key_context_internal(
2047 &self,
2048 has_active_edit_prediction: bool,
2049 window: &Window,
2050 cx: &App,
2051 ) -> KeyContext {
2052 let mut key_context = KeyContext::new_with_defaults();
2053 key_context.add("Editor");
2054 let mode = match self.mode {
2055 EditorMode::SingleLine { .. } => "single_line",
2056 EditorMode::AutoHeight { .. } => "auto_height",
2057 EditorMode::Minimap { .. } => "minimap",
2058 EditorMode::Full { .. } => "full",
2059 };
2060
2061 if EditorSettings::jupyter_enabled(cx) {
2062 key_context.add("jupyter");
2063 }
2064
2065 key_context.set("mode", mode);
2066 if self.pending_rename.is_some() {
2067 key_context.add("renaming");
2068 }
2069
2070 match self.context_menu.borrow().as_ref() {
2071 Some(CodeContextMenu::Completions(_)) => {
2072 key_context.add("menu");
2073 key_context.add("showing_completions");
2074 }
2075 Some(CodeContextMenu::CodeActions(_)) => {
2076 key_context.add("menu");
2077 key_context.add("showing_code_actions")
2078 }
2079 None => {}
2080 }
2081
2082 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2083 if !self.focus_handle(cx).contains_focused(window, cx)
2084 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2085 {
2086 for addon in self.addons.values() {
2087 addon.extend_key_context(&mut key_context, cx)
2088 }
2089 }
2090
2091 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2092 if let Some(extension) = singleton_buffer
2093 .read(cx)
2094 .file()
2095 .and_then(|file| file.path().extension()?.to_str())
2096 {
2097 key_context.set("extension", extension.to_string());
2098 }
2099 } else {
2100 key_context.add("multibuffer");
2101 }
2102
2103 if has_active_edit_prediction {
2104 if self.edit_prediction_in_conflict() {
2105 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2106 } else {
2107 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2108 key_context.add("copilot_suggestion");
2109 }
2110 }
2111
2112 if self.selection_mark_mode {
2113 key_context.add("selection_mode");
2114 }
2115
2116 key_context
2117 }
2118
2119 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2120 self.mouse_cursor_hidden = match origin {
2121 HideMouseCursorOrigin::TypingAction => {
2122 matches!(
2123 self.hide_mouse_mode,
2124 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2125 )
2126 }
2127 HideMouseCursorOrigin::MovementAction => {
2128 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2129 }
2130 };
2131 }
2132
2133 pub fn edit_prediction_in_conflict(&self) -> bool {
2134 if !self.show_edit_predictions_in_menu() {
2135 return false;
2136 }
2137
2138 let showing_completions = self
2139 .context_menu
2140 .borrow()
2141 .as_ref()
2142 .map_or(false, |context| {
2143 matches!(context, CodeContextMenu::Completions(_))
2144 });
2145
2146 showing_completions
2147 || self.edit_prediction_requires_modifier()
2148 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2149 // bindings to insert tab characters.
2150 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2151 }
2152
2153 pub fn accept_edit_prediction_keybind(
2154 &self,
2155 window: &Window,
2156 cx: &App,
2157 ) -> AcceptEditPredictionBinding {
2158 let key_context = self.key_context_internal(true, window, cx);
2159 let in_conflict = self.edit_prediction_in_conflict();
2160
2161 AcceptEditPredictionBinding(
2162 window
2163 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2164 .into_iter()
2165 .filter(|binding| {
2166 !in_conflict
2167 || binding
2168 .keystrokes()
2169 .first()
2170 .map_or(false, |keystroke| keystroke.modifiers.modified())
2171 })
2172 .rev()
2173 .min_by_key(|binding| {
2174 binding
2175 .keystrokes()
2176 .first()
2177 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
2178 }),
2179 )
2180 }
2181
2182 pub fn new_file(
2183 workspace: &mut Workspace,
2184 _: &workspace::NewFile,
2185 window: &mut Window,
2186 cx: &mut Context<Workspace>,
2187 ) {
2188 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2189 "Failed to create buffer",
2190 window,
2191 cx,
2192 |e, _, _| match e.error_code() {
2193 ErrorCode::RemoteUpgradeRequired => Some(format!(
2194 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2195 e.error_tag("required").unwrap_or("the latest version")
2196 )),
2197 _ => None,
2198 },
2199 );
2200 }
2201
2202 pub fn new_in_workspace(
2203 workspace: &mut Workspace,
2204 window: &mut Window,
2205 cx: &mut Context<Workspace>,
2206 ) -> Task<Result<Entity<Editor>>> {
2207 let project = workspace.project().clone();
2208 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2209
2210 cx.spawn_in(window, async move |workspace, cx| {
2211 let buffer = create.await?;
2212 workspace.update_in(cx, |workspace, window, cx| {
2213 let editor =
2214 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2215 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2216 editor
2217 })
2218 })
2219 }
2220
2221 fn new_file_vertical(
2222 workspace: &mut Workspace,
2223 _: &workspace::NewFileSplitVertical,
2224 window: &mut Window,
2225 cx: &mut Context<Workspace>,
2226 ) {
2227 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2228 }
2229
2230 fn new_file_horizontal(
2231 workspace: &mut Workspace,
2232 _: &workspace::NewFileSplitHorizontal,
2233 window: &mut Window,
2234 cx: &mut Context<Workspace>,
2235 ) {
2236 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2237 }
2238
2239 fn new_file_in_direction(
2240 workspace: &mut Workspace,
2241 direction: SplitDirection,
2242 window: &mut Window,
2243 cx: &mut Context<Workspace>,
2244 ) {
2245 let project = workspace.project().clone();
2246 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2247
2248 cx.spawn_in(window, async move |workspace, cx| {
2249 let buffer = create.await?;
2250 workspace.update_in(cx, move |workspace, window, cx| {
2251 workspace.split_item(
2252 direction,
2253 Box::new(
2254 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2255 ),
2256 window,
2257 cx,
2258 )
2259 })?;
2260 anyhow::Ok(())
2261 })
2262 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2263 match e.error_code() {
2264 ErrorCode::RemoteUpgradeRequired => Some(format!(
2265 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2266 e.error_tag("required").unwrap_or("the latest version")
2267 )),
2268 _ => None,
2269 }
2270 });
2271 }
2272
2273 pub fn leader_id(&self) -> Option<CollaboratorId> {
2274 self.leader_id
2275 }
2276
2277 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2278 &self.buffer
2279 }
2280
2281 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2282 self.workspace.as_ref()?.0.upgrade()
2283 }
2284
2285 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2286 self.buffer().read(cx).title(cx)
2287 }
2288
2289 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2290 let git_blame_gutter_max_author_length = self
2291 .render_git_blame_gutter(cx)
2292 .then(|| {
2293 if let Some(blame) = self.blame.as_ref() {
2294 let max_author_length =
2295 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2296 Some(max_author_length)
2297 } else {
2298 None
2299 }
2300 })
2301 .flatten();
2302
2303 EditorSnapshot {
2304 mode: self.mode.clone(),
2305 show_gutter: self.show_gutter,
2306 show_line_numbers: self.show_line_numbers,
2307 show_git_diff_gutter: self.show_git_diff_gutter,
2308 show_runnables: self.show_runnables,
2309 show_breakpoints: self.show_breakpoints,
2310 git_blame_gutter_max_author_length,
2311 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2312 scroll_anchor: self.scroll_manager.anchor(),
2313 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2314 placeholder_text: self.placeholder_text.clone(),
2315 is_focused: self.focus_handle.is_focused(window),
2316 current_line_highlight: self
2317 .current_line_highlight
2318 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2319 gutter_hovered: self.gutter_hovered,
2320 }
2321 }
2322
2323 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2324 self.buffer.read(cx).language_at(point, cx)
2325 }
2326
2327 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2328 self.buffer.read(cx).read(cx).file_at(point).cloned()
2329 }
2330
2331 pub fn active_excerpt(
2332 &self,
2333 cx: &App,
2334 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2335 self.buffer
2336 .read(cx)
2337 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2338 }
2339
2340 pub fn mode(&self) -> &EditorMode {
2341 &self.mode
2342 }
2343
2344 pub fn set_mode(&mut self, mode: EditorMode) {
2345 self.mode = mode;
2346 }
2347
2348 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2349 self.collaboration_hub.as_deref()
2350 }
2351
2352 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2353 self.collaboration_hub = Some(hub);
2354 }
2355
2356 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2357 self.in_project_search = in_project_search;
2358 }
2359
2360 pub fn set_custom_context_menu(
2361 &mut self,
2362 f: impl 'static
2363 + Fn(
2364 &mut Self,
2365 DisplayPoint,
2366 &mut Window,
2367 &mut Context<Self>,
2368 ) -> Option<Entity<ui::ContextMenu>>,
2369 ) {
2370 self.custom_context_menu = Some(Box::new(f))
2371 }
2372
2373 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
2374 self.completion_provider = provider;
2375 }
2376
2377 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2378 self.semantics_provider.clone()
2379 }
2380
2381 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2382 self.semantics_provider = provider;
2383 }
2384
2385 pub fn set_edit_prediction_provider<T>(
2386 &mut self,
2387 provider: Option<Entity<T>>,
2388 window: &mut Window,
2389 cx: &mut Context<Self>,
2390 ) where
2391 T: EditPredictionProvider,
2392 {
2393 self.edit_prediction_provider =
2394 provider.map(|provider| RegisteredInlineCompletionProvider {
2395 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2396 if this.focus_handle.is_focused(window) {
2397 this.update_visible_inline_completion(window, cx);
2398 }
2399 }),
2400 provider: Arc::new(provider),
2401 });
2402 self.update_edit_prediction_settings(cx);
2403 self.refresh_inline_completion(false, false, window, cx);
2404 }
2405
2406 pub fn placeholder_text(&self) -> Option<&str> {
2407 self.placeholder_text.as_deref()
2408 }
2409
2410 pub fn set_placeholder_text(
2411 &mut self,
2412 placeholder_text: impl Into<Arc<str>>,
2413 cx: &mut Context<Self>,
2414 ) {
2415 let placeholder_text = Some(placeholder_text.into());
2416 if self.placeholder_text != placeholder_text {
2417 self.placeholder_text = placeholder_text;
2418 cx.notify();
2419 }
2420 }
2421
2422 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2423 self.cursor_shape = cursor_shape;
2424
2425 // Disrupt blink for immediate user feedback that the cursor shape has changed
2426 self.blink_manager.update(cx, BlinkManager::show_cursor);
2427
2428 cx.notify();
2429 }
2430
2431 pub fn set_current_line_highlight(
2432 &mut self,
2433 current_line_highlight: Option<CurrentLineHighlight>,
2434 ) {
2435 self.current_line_highlight = current_line_highlight;
2436 }
2437
2438 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2439 self.collapse_matches = collapse_matches;
2440 }
2441
2442 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2443 let buffers = self.buffer.read(cx).all_buffers();
2444 let Some(project) = self.project.as_ref() else {
2445 return;
2446 };
2447 project.update(cx, |project, cx| {
2448 for buffer in buffers {
2449 self.registered_buffers
2450 .entry(buffer.read(cx).remote_id())
2451 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2452 }
2453 })
2454 }
2455
2456 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2457 if self.collapse_matches {
2458 return range.start..range.start;
2459 }
2460 range.clone()
2461 }
2462
2463 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2464 if self.display_map.read(cx).clip_at_line_ends != clip {
2465 self.display_map
2466 .update(cx, |map, _| map.clip_at_line_ends = clip);
2467 }
2468 }
2469
2470 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2471 self.input_enabled = input_enabled;
2472 }
2473
2474 pub fn set_inline_completions_hidden_for_vim_mode(
2475 &mut self,
2476 hidden: bool,
2477 window: &mut Window,
2478 cx: &mut Context<Self>,
2479 ) {
2480 if hidden != self.inline_completions_hidden_for_vim_mode {
2481 self.inline_completions_hidden_for_vim_mode = hidden;
2482 if hidden {
2483 self.update_visible_inline_completion(window, cx);
2484 } else {
2485 self.refresh_inline_completion(true, false, window, cx);
2486 }
2487 }
2488 }
2489
2490 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2491 self.menu_inline_completions_policy = value;
2492 }
2493
2494 pub fn set_autoindent(&mut self, autoindent: bool) {
2495 if autoindent {
2496 self.autoindent_mode = Some(AutoindentMode::EachLine);
2497 } else {
2498 self.autoindent_mode = None;
2499 }
2500 }
2501
2502 pub fn read_only(&self, cx: &App) -> bool {
2503 self.read_only || self.buffer.read(cx).read_only()
2504 }
2505
2506 pub fn set_read_only(&mut self, read_only: bool) {
2507 self.read_only = read_only;
2508 }
2509
2510 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2511 self.use_autoclose = autoclose;
2512 }
2513
2514 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2515 self.use_auto_surround = auto_surround;
2516 }
2517
2518 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2519 self.auto_replace_emoji_shortcode = auto_replace;
2520 }
2521
2522 pub fn toggle_edit_predictions(
2523 &mut self,
2524 _: &ToggleEditPrediction,
2525 window: &mut Window,
2526 cx: &mut Context<Self>,
2527 ) {
2528 if self.show_inline_completions_override.is_some() {
2529 self.set_show_edit_predictions(None, window, cx);
2530 } else {
2531 let show_edit_predictions = !self.edit_predictions_enabled();
2532 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2533 }
2534 }
2535
2536 pub fn set_show_edit_predictions(
2537 &mut self,
2538 show_edit_predictions: Option<bool>,
2539 window: &mut Window,
2540 cx: &mut Context<Self>,
2541 ) {
2542 self.show_inline_completions_override = show_edit_predictions;
2543 self.update_edit_prediction_settings(cx);
2544
2545 if let Some(false) = show_edit_predictions {
2546 self.discard_inline_completion(false, cx);
2547 } else {
2548 self.refresh_inline_completion(false, true, window, cx);
2549 }
2550 }
2551
2552 fn inline_completions_disabled_in_scope(
2553 &self,
2554 buffer: &Entity<Buffer>,
2555 buffer_position: language::Anchor,
2556 cx: &App,
2557 ) -> bool {
2558 let snapshot = buffer.read(cx).snapshot();
2559 let settings = snapshot.settings_at(buffer_position, cx);
2560
2561 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2562 return false;
2563 };
2564
2565 scope.override_name().map_or(false, |scope_name| {
2566 settings
2567 .edit_predictions_disabled_in
2568 .iter()
2569 .any(|s| s == scope_name)
2570 })
2571 }
2572
2573 pub fn set_use_modal_editing(&mut self, to: bool) {
2574 self.use_modal_editing = to;
2575 }
2576
2577 pub fn use_modal_editing(&self) -> bool {
2578 self.use_modal_editing
2579 }
2580
2581 fn selections_did_change(
2582 &mut self,
2583 local: bool,
2584 old_cursor_position: &Anchor,
2585 show_completions: bool,
2586 window: &mut Window,
2587 cx: &mut Context<Self>,
2588 ) {
2589 window.invalidate_character_coordinates();
2590
2591 // Copy selections to primary selection buffer
2592 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2593 if local {
2594 let selections = self.selections.all::<usize>(cx);
2595 let buffer_handle = self.buffer.read(cx).read(cx);
2596
2597 let mut text = String::new();
2598 for (index, selection) in selections.iter().enumerate() {
2599 let text_for_selection = buffer_handle
2600 .text_for_range(selection.start..selection.end)
2601 .collect::<String>();
2602
2603 text.push_str(&text_for_selection);
2604 if index != selections.len() - 1 {
2605 text.push('\n');
2606 }
2607 }
2608
2609 if !text.is_empty() {
2610 cx.write_to_primary(ClipboardItem::new_string(text));
2611 }
2612 }
2613
2614 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2615 self.buffer.update(cx, |buffer, cx| {
2616 buffer.set_active_selections(
2617 &self.selections.disjoint_anchors(),
2618 self.selections.line_mode,
2619 self.cursor_shape,
2620 cx,
2621 )
2622 });
2623 }
2624 let display_map = self
2625 .display_map
2626 .update(cx, |display_map, cx| display_map.snapshot(cx));
2627 let buffer = &display_map.buffer_snapshot;
2628 self.add_selections_state = None;
2629 self.select_next_state = None;
2630 self.select_prev_state = None;
2631 self.select_syntax_node_history.try_clear();
2632 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2633 self.snippet_stack
2634 .invalidate(&self.selections.disjoint_anchors(), buffer);
2635 self.take_rename(false, window, cx);
2636
2637 let new_cursor_position = self.selections.newest_anchor().head();
2638
2639 self.push_to_nav_history(
2640 *old_cursor_position,
2641 Some(new_cursor_position.to_point(buffer)),
2642 false,
2643 cx,
2644 );
2645
2646 if local {
2647 let new_cursor_position = self.selections.newest_anchor().head();
2648 let mut context_menu = self.context_menu.borrow_mut();
2649 let completion_menu = match context_menu.as_ref() {
2650 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2651 _ => {
2652 *context_menu = None;
2653 None
2654 }
2655 };
2656 if let Some(buffer_id) = new_cursor_position.buffer_id {
2657 if !self.registered_buffers.contains_key(&buffer_id) {
2658 if let Some(project) = self.project.as_ref() {
2659 project.update(cx, |project, cx| {
2660 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2661 return;
2662 };
2663 self.registered_buffers.insert(
2664 buffer_id,
2665 project.register_buffer_with_language_servers(&buffer, cx),
2666 );
2667 })
2668 }
2669 }
2670 }
2671
2672 if let Some(completion_menu) = completion_menu {
2673 let cursor_position = new_cursor_position.to_offset(buffer);
2674 let (word_range, kind) =
2675 buffer.surrounding_word(completion_menu.initial_position, true);
2676 if kind == Some(CharKind::Word)
2677 && word_range.to_inclusive().contains(&cursor_position)
2678 {
2679 let mut completion_menu = completion_menu.clone();
2680 drop(context_menu);
2681
2682 let query = Self::completion_query(buffer, cursor_position);
2683 cx.spawn(async move |this, cx| {
2684 completion_menu
2685 .filter(query.as_deref(), cx.background_executor().clone())
2686 .await;
2687
2688 this.update(cx, |this, cx| {
2689 let mut context_menu = this.context_menu.borrow_mut();
2690 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2691 else {
2692 return;
2693 };
2694
2695 if menu.id > completion_menu.id {
2696 return;
2697 }
2698
2699 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2700 drop(context_menu);
2701 cx.notify();
2702 })
2703 })
2704 .detach();
2705
2706 if show_completions {
2707 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2708 }
2709 } else {
2710 drop(context_menu);
2711 self.hide_context_menu(window, cx);
2712 }
2713 } else {
2714 drop(context_menu);
2715 }
2716
2717 hide_hover(self, cx);
2718
2719 if old_cursor_position.to_display_point(&display_map).row()
2720 != new_cursor_position.to_display_point(&display_map).row()
2721 {
2722 self.available_code_actions.take();
2723 }
2724 self.refresh_code_actions(window, cx);
2725 self.refresh_document_highlights(cx);
2726 self.refresh_selected_text_highlights(false, window, cx);
2727 refresh_matching_bracket_highlights(self, window, cx);
2728 self.update_visible_inline_completion(window, cx);
2729 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2730 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2731 self.inline_blame_popover.take();
2732 if self.git_blame_inline_enabled {
2733 self.start_inline_blame_timer(window, cx);
2734 }
2735 }
2736
2737 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2738 cx.emit(EditorEvent::SelectionsChanged { local });
2739
2740 let selections = &self.selections.disjoint;
2741 if selections.len() == 1 {
2742 cx.emit(SearchEvent::ActiveMatchChanged)
2743 }
2744 if local {
2745 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2746 let inmemory_selections = selections
2747 .iter()
2748 .map(|s| {
2749 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2750 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2751 })
2752 .collect();
2753 self.update_restoration_data(cx, |data| {
2754 data.selections = inmemory_selections;
2755 });
2756
2757 if WorkspaceSettings::get(None, cx).restore_on_startup
2758 != RestoreOnStartupBehavior::None
2759 {
2760 if let Some(workspace_id) =
2761 self.workspace.as_ref().and_then(|workspace| workspace.1)
2762 {
2763 let snapshot = self.buffer().read(cx).snapshot(cx);
2764 let selections = selections.clone();
2765 let background_executor = cx.background_executor().clone();
2766 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2767 self.serialize_selections = cx.background_spawn(async move {
2768 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2769 let db_selections = selections
2770 .iter()
2771 .map(|selection| {
2772 (
2773 selection.start.to_offset(&snapshot),
2774 selection.end.to_offset(&snapshot),
2775 )
2776 })
2777 .collect();
2778
2779 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2780 .await
2781 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2782 .log_err();
2783 });
2784 }
2785 }
2786 }
2787 }
2788
2789 cx.notify();
2790 }
2791
2792 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2793 use text::ToOffset as _;
2794 use text::ToPoint as _;
2795
2796 if self.mode.is_minimap()
2797 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2798 {
2799 return;
2800 }
2801
2802 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2803 return;
2804 };
2805
2806 let snapshot = singleton.read(cx).snapshot();
2807 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2808 let display_snapshot = display_map.snapshot(cx);
2809
2810 display_snapshot
2811 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2812 .map(|fold| {
2813 fold.range.start.text_anchor.to_point(&snapshot)
2814 ..fold.range.end.text_anchor.to_point(&snapshot)
2815 })
2816 .collect()
2817 });
2818 self.update_restoration_data(cx, |data| {
2819 data.folds = inmemory_folds;
2820 });
2821
2822 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2823 return;
2824 };
2825 let background_executor = cx.background_executor().clone();
2826 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2827 let db_folds = self.display_map.update(cx, |display_map, cx| {
2828 display_map
2829 .snapshot(cx)
2830 .folds_in_range(0..snapshot.len())
2831 .map(|fold| {
2832 (
2833 fold.range.start.text_anchor.to_offset(&snapshot),
2834 fold.range.end.text_anchor.to_offset(&snapshot),
2835 )
2836 })
2837 .collect()
2838 });
2839 self.serialize_folds = cx.background_spawn(async move {
2840 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2841 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2842 .await
2843 .with_context(|| {
2844 format!(
2845 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2846 )
2847 })
2848 .log_err();
2849 });
2850 }
2851
2852 pub fn sync_selections(
2853 &mut self,
2854 other: Entity<Editor>,
2855 cx: &mut Context<Self>,
2856 ) -> gpui::Subscription {
2857 let other_selections = other.read(cx).selections.disjoint.to_vec();
2858 self.selections.change_with(cx, |selections| {
2859 selections.select_anchors(other_selections);
2860 });
2861
2862 let other_subscription =
2863 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2864 EditorEvent::SelectionsChanged { local: true } => {
2865 let other_selections = other.read(cx).selections.disjoint.to_vec();
2866 if other_selections.is_empty() {
2867 return;
2868 }
2869 this.selections.change_with(cx, |selections| {
2870 selections.select_anchors(other_selections);
2871 });
2872 }
2873 _ => {}
2874 });
2875
2876 let this_subscription =
2877 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2878 EditorEvent::SelectionsChanged { local: true } => {
2879 let these_selections = this.selections.disjoint.to_vec();
2880 if these_selections.is_empty() {
2881 return;
2882 }
2883 other.update(cx, |other_editor, cx| {
2884 other_editor.selections.change_with(cx, |selections| {
2885 selections.select_anchors(these_selections);
2886 })
2887 });
2888 }
2889 _ => {}
2890 });
2891
2892 Subscription::join(other_subscription, this_subscription)
2893 }
2894
2895 pub fn change_selections<R>(
2896 &mut self,
2897 autoscroll: Option<Autoscroll>,
2898 window: &mut Window,
2899 cx: &mut Context<Self>,
2900 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2901 ) -> R {
2902 self.change_selections_inner(autoscroll, true, window, cx, change)
2903 }
2904
2905 fn change_selections_inner<R>(
2906 &mut self,
2907 autoscroll: Option<Autoscroll>,
2908 request_completions: bool,
2909 window: &mut Window,
2910 cx: &mut Context<Self>,
2911 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2912 ) -> R {
2913 let old_cursor_position = self.selections.newest_anchor().head();
2914 self.push_to_selection_history();
2915
2916 let (changed, result) = self.selections.change_with(cx, change);
2917
2918 if changed {
2919 if let Some(autoscroll) = autoscroll {
2920 self.request_autoscroll(autoscroll, cx);
2921 }
2922 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2923
2924 if self.should_open_signature_help_automatically(
2925 &old_cursor_position,
2926 self.signature_help_state.backspace_pressed(),
2927 cx,
2928 ) {
2929 self.show_signature_help(&ShowSignatureHelp, window, cx);
2930 }
2931 self.signature_help_state.set_backspace_pressed(false);
2932 }
2933
2934 result
2935 }
2936
2937 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2938 where
2939 I: IntoIterator<Item = (Range<S>, T)>,
2940 S: ToOffset,
2941 T: Into<Arc<str>>,
2942 {
2943 if self.read_only(cx) {
2944 return;
2945 }
2946
2947 self.buffer
2948 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2949 }
2950
2951 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2952 where
2953 I: IntoIterator<Item = (Range<S>, T)>,
2954 S: ToOffset,
2955 T: Into<Arc<str>>,
2956 {
2957 if self.read_only(cx) {
2958 return;
2959 }
2960
2961 self.buffer.update(cx, |buffer, cx| {
2962 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2963 });
2964 }
2965
2966 pub fn edit_with_block_indent<I, S, T>(
2967 &mut self,
2968 edits: I,
2969 original_indent_columns: Vec<Option<u32>>,
2970 cx: &mut Context<Self>,
2971 ) where
2972 I: IntoIterator<Item = (Range<S>, T)>,
2973 S: ToOffset,
2974 T: Into<Arc<str>>,
2975 {
2976 if self.read_only(cx) {
2977 return;
2978 }
2979
2980 self.buffer.update(cx, |buffer, cx| {
2981 buffer.edit(
2982 edits,
2983 Some(AutoindentMode::Block {
2984 original_indent_columns,
2985 }),
2986 cx,
2987 )
2988 });
2989 }
2990
2991 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2992 self.hide_context_menu(window, cx);
2993
2994 match phase {
2995 SelectPhase::Begin {
2996 position,
2997 add,
2998 click_count,
2999 } => self.begin_selection(position, add, click_count, window, cx),
3000 SelectPhase::BeginColumnar {
3001 position,
3002 goal_column,
3003 reset,
3004 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3005 SelectPhase::Extend {
3006 position,
3007 click_count,
3008 } => self.extend_selection(position, click_count, window, cx),
3009 SelectPhase::Update {
3010 position,
3011 goal_column,
3012 scroll_delta,
3013 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3014 SelectPhase::End => self.end_selection(window, cx),
3015 }
3016 }
3017
3018 fn extend_selection(
3019 &mut self,
3020 position: DisplayPoint,
3021 click_count: usize,
3022 window: &mut Window,
3023 cx: &mut Context<Self>,
3024 ) {
3025 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3026 let tail = self.selections.newest::<usize>(cx).tail();
3027 self.begin_selection(position, false, click_count, window, cx);
3028
3029 let position = position.to_offset(&display_map, Bias::Left);
3030 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3031
3032 let mut pending_selection = self
3033 .selections
3034 .pending_anchor()
3035 .expect("extend_selection not called with pending selection");
3036 if position >= tail {
3037 pending_selection.start = tail_anchor;
3038 } else {
3039 pending_selection.end = tail_anchor;
3040 pending_selection.reversed = true;
3041 }
3042
3043 let mut pending_mode = self.selections.pending_mode().unwrap();
3044 match &mut pending_mode {
3045 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3046 _ => {}
3047 }
3048
3049 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3050
3051 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3052 s.set_pending(pending_selection, pending_mode)
3053 });
3054 }
3055
3056 fn begin_selection(
3057 &mut self,
3058 position: DisplayPoint,
3059 add: bool,
3060 click_count: usize,
3061 window: &mut Window,
3062 cx: &mut Context<Self>,
3063 ) {
3064 if !self.focus_handle.is_focused(window) {
3065 self.last_focused_descendant = None;
3066 window.focus(&self.focus_handle);
3067 }
3068
3069 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3070 let buffer = &display_map.buffer_snapshot;
3071 let position = display_map.clip_point(position, Bias::Left);
3072
3073 let start;
3074 let end;
3075 let mode;
3076 let mut auto_scroll;
3077 match click_count {
3078 1 => {
3079 start = buffer.anchor_before(position.to_point(&display_map));
3080 end = start;
3081 mode = SelectMode::Character;
3082 auto_scroll = true;
3083 }
3084 2 => {
3085 let range = movement::surrounding_word(&display_map, position);
3086 start = buffer.anchor_before(range.start.to_point(&display_map));
3087 end = buffer.anchor_before(range.end.to_point(&display_map));
3088 mode = SelectMode::Word(start..end);
3089 auto_scroll = true;
3090 }
3091 3 => {
3092 let position = display_map
3093 .clip_point(position, Bias::Left)
3094 .to_point(&display_map);
3095 let line_start = display_map.prev_line_boundary(position).0;
3096 let next_line_start = buffer.clip_point(
3097 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3098 Bias::Left,
3099 );
3100 start = buffer.anchor_before(line_start);
3101 end = buffer.anchor_before(next_line_start);
3102 mode = SelectMode::Line(start..end);
3103 auto_scroll = true;
3104 }
3105 _ => {
3106 start = buffer.anchor_before(0);
3107 end = buffer.anchor_before(buffer.len());
3108 mode = SelectMode::All;
3109 auto_scroll = false;
3110 }
3111 }
3112 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3113
3114 let point_to_delete: Option<usize> = {
3115 let selected_points: Vec<Selection<Point>> =
3116 self.selections.disjoint_in_range(start..end, cx);
3117
3118 if !add || click_count > 1 {
3119 None
3120 } else if !selected_points.is_empty() {
3121 Some(selected_points[0].id)
3122 } else {
3123 let clicked_point_already_selected =
3124 self.selections.disjoint.iter().find(|selection| {
3125 selection.start.to_point(buffer) == start.to_point(buffer)
3126 || selection.end.to_point(buffer) == end.to_point(buffer)
3127 });
3128
3129 clicked_point_already_selected.map(|selection| selection.id)
3130 }
3131 };
3132
3133 let selections_count = self.selections.count();
3134
3135 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3136 if let Some(point_to_delete) = point_to_delete {
3137 s.delete(point_to_delete);
3138
3139 if selections_count == 1 {
3140 s.set_pending_anchor_range(start..end, mode);
3141 }
3142 } else {
3143 if !add {
3144 s.clear_disjoint();
3145 }
3146
3147 s.set_pending_anchor_range(start..end, mode);
3148 }
3149 });
3150 }
3151
3152 fn begin_columnar_selection(
3153 &mut self,
3154 position: DisplayPoint,
3155 goal_column: u32,
3156 reset: bool,
3157 window: &mut Window,
3158 cx: &mut Context<Self>,
3159 ) {
3160 if !self.focus_handle.is_focused(window) {
3161 self.last_focused_descendant = None;
3162 window.focus(&self.focus_handle);
3163 }
3164
3165 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3166
3167 if reset {
3168 let pointer_position = display_map
3169 .buffer_snapshot
3170 .anchor_before(position.to_point(&display_map));
3171
3172 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3173 s.clear_disjoint();
3174 s.set_pending_anchor_range(
3175 pointer_position..pointer_position,
3176 SelectMode::Character,
3177 );
3178 });
3179 }
3180
3181 let tail = self.selections.newest::<Point>(cx).tail();
3182 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3183
3184 if !reset {
3185 self.select_columns(
3186 tail.to_display_point(&display_map),
3187 position,
3188 goal_column,
3189 &display_map,
3190 window,
3191 cx,
3192 );
3193 }
3194 }
3195
3196 fn update_selection(
3197 &mut self,
3198 position: DisplayPoint,
3199 goal_column: u32,
3200 scroll_delta: gpui::Point<f32>,
3201 window: &mut Window,
3202 cx: &mut Context<Self>,
3203 ) {
3204 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3205
3206 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3207 let tail = tail.to_display_point(&display_map);
3208 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3209 } else if let Some(mut pending) = self.selections.pending_anchor() {
3210 let buffer = self.buffer.read(cx).snapshot(cx);
3211 let head;
3212 let tail;
3213 let mode = self.selections.pending_mode().unwrap();
3214 match &mode {
3215 SelectMode::Character => {
3216 head = position.to_point(&display_map);
3217 tail = pending.tail().to_point(&buffer);
3218 }
3219 SelectMode::Word(original_range) => {
3220 let original_display_range = original_range.start.to_display_point(&display_map)
3221 ..original_range.end.to_display_point(&display_map);
3222 let original_buffer_range = original_display_range.start.to_point(&display_map)
3223 ..original_display_range.end.to_point(&display_map);
3224 if movement::is_inside_word(&display_map, position)
3225 || original_display_range.contains(&position)
3226 {
3227 let word_range = movement::surrounding_word(&display_map, position);
3228 if word_range.start < original_display_range.start {
3229 head = word_range.start.to_point(&display_map);
3230 } else {
3231 head = word_range.end.to_point(&display_map);
3232 }
3233 } else {
3234 head = position.to_point(&display_map);
3235 }
3236
3237 if head <= original_buffer_range.start {
3238 tail = original_buffer_range.end;
3239 } else {
3240 tail = original_buffer_range.start;
3241 }
3242 }
3243 SelectMode::Line(original_range) => {
3244 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3245
3246 let position = display_map
3247 .clip_point(position, Bias::Left)
3248 .to_point(&display_map);
3249 let line_start = display_map.prev_line_boundary(position).0;
3250 let next_line_start = buffer.clip_point(
3251 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3252 Bias::Left,
3253 );
3254
3255 if line_start < original_range.start {
3256 head = line_start
3257 } else {
3258 head = next_line_start
3259 }
3260
3261 if head <= original_range.start {
3262 tail = original_range.end;
3263 } else {
3264 tail = original_range.start;
3265 }
3266 }
3267 SelectMode::All => {
3268 return;
3269 }
3270 };
3271
3272 if head < tail {
3273 pending.start = buffer.anchor_before(head);
3274 pending.end = buffer.anchor_before(tail);
3275 pending.reversed = true;
3276 } else {
3277 pending.start = buffer.anchor_before(tail);
3278 pending.end = buffer.anchor_before(head);
3279 pending.reversed = false;
3280 }
3281
3282 self.change_selections(None, window, cx, |s| {
3283 s.set_pending(pending, mode);
3284 });
3285 } else {
3286 log::error!("update_selection dispatched with no pending selection");
3287 return;
3288 }
3289
3290 self.apply_scroll_delta(scroll_delta, window, cx);
3291 cx.notify();
3292 }
3293
3294 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3295 self.columnar_selection_tail.take();
3296 if self.selections.pending_anchor().is_some() {
3297 let selections = self.selections.all::<usize>(cx);
3298 self.change_selections(None, window, cx, |s| {
3299 s.select(selections);
3300 s.clear_pending();
3301 });
3302 }
3303 }
3304
3305 fn select_columns(
3306 &mut self,
3307 tail: DisplayPoint,
3308 head: DisplayPoint,
3309 goal_column: u32,
3310 display_map: &DisplaySnapshot,
3311 window: &mut Window,
3312 cx: &mut Context<Self>,
3313 ) {
3314 let start_row = cmp::min(tail.row(), head.row());
3315 let end_row = cmp::max(tail.row(), head.row());
3316 let start_column = cmp::min(tail.column(), goal_column);
3317 let end_column = cmp::max(tail.column(), goal_column);
3318 let reversed = start_column < tail.column();
3319
3320 let selection_ranges = (start_row.0..=end_row.0)
3321 .map(DisplayRow)
3322 .filter_map(|row| {
3323 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
3324 let start = display_map
3325 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3326 .to_point(display_map);
3327 let end = display_map
3328 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3329 .to_point(display_map);
3330 if reversed {
3331 Some(end..start)
3332 } else {
3333 Some(start..end)
3334 }
3335 } else {
3336 None
3337 }
3338 })
3339 .collect::<Vec<_>>();
3340
3341 self.change_selections(None, window, cx, |s| {
3342 s.select_ranges(selection_ranges);
3343 });
3344 cx.notify();
3345 }
3346
3347 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3348 self.selections
3349 .all_adjusted(cx)
3350 .iter()
3351 .any(|selection| !selection.is_empty())
3352 }
3353
3354 pub fn has_pending_nonempty_selection(&self) -> bool {
3355 let pending_nonempty_selection = match self.selections.pending_anchor() {
3356 Some(Selection { start, end, .. }) => start != end,
3357 None => false,
3358 };
3359
3360 pending_nonempty_selection
3361 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3362 }
3363
3364 pub fn has_pending_selection(&self) -> bool {
3365 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3366 }
3367
3368 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3369 self.selection_mark_mode = false;
3370
3371 if self.clear_expanded_diff_hunks(cx) {
3372 cx.notify();
3373 return;
3374 }
3375 if self.dismiss_menus_and_popups(true, window, cx) {
3376 return;
3377 }
3378
3379 if self.mode.is_full()
3380 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3381 {
3382 return;
3383 }
3384
3385 cx.propagate();
3386 }
3387
3388 pub fn dismiss_menus_and_popups(
3389 &mut self,
3390 is_user_requested: bool,
3391 window: &mut Window,
3392 cx: &mut Context<Self>,
3393 ) -> bool {
3394 if self.take_rename(false, window, cx).is_some() {
3395 return true;
3396 }
3397
3398 if hide_hover(self, cx) {
3399 return true;
3400 }
3401
3402 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3403 return true;
3404 }
3405
3406 if self.hide_context_menu(window, cx).is_some() {
3407 return true;
3408 }
3409
3410 if self.mouse_context_menu.take().is_some() {
3411 return true;
3412 }
3413
3414 if is_user_requested && self.discard_inline_completion(true, cx) {
3415 return true;
3416 }
3417
3418 if self.snippet_stack.pop().is_some() {
3419 return true;
3420 }
3421
3422 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3423 self.dismiss_diagnostics(cx);
3424 return true;
3425 }
3426
3427 false
3428 }
3429
3430 fn linked_editing_ranges_for(
3431 &self,
3432 selection: Range<text::Anchor>,
3433 cx: &App,
3434 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3435 if self.linked_edit_ranges.is_empty() {
3436 return None;
3437 }
3438 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3439 selection.end.buffer_id.and_then(|end_buffer_id| {
3440 if selection.start.buffer_id != Some(end_buffer_id) {
3441 return None;
3442 }
3443 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3444 let snapshot = buffer.read(cx).snapshot();
3445 self.linked_edit_ranges
3446 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3447 .map(|ranges| (ranges, snapshot, buffer))
3448 })?;
3449 use text::ToOffset as TO;
3450 // find offset from the start of current range to current cursor position
3451 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3452
3453 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3454 let start_difference = start_offset - start_byte_offset;
3455 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3456 let end_difference = end_offset - start_byte_offset;
3457 // Current range has associated linked ranges.
3458 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3459 for range in linked_ranges.iter() {
3460 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3461 let end_offset = start_offset + end_difference;
3462 let start_offset = start_offset + start_difference;
3463 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3464 continue;
3465 }
3466 if self.selections.disjoint_anchor_ranges().any(|s| {
3467 if s.start.buffer_id != selection.start.buffer_id
3468 || s.end.buffer_id != selection.end.buffer_id
3469 {
3470 return false;
3471 }
3472 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3473 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3474 }) {
3475 continue;
3476 }
3477 let start = buffer_snapshot.anchor_after(start_offset);
3478 let end = buffer_snapshot.anchor_after(end_offset);
3479 linked_edits
3480 .entry(buffer.clone())
3481 .or_default()
3482 .push(start..end);
3483 }
3484 Some(linked_edits)
3485 }
3486
3487 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3488 let text: Arc<str> = text.into();
3489
3490 if self.read_only(cx) {
3491 return;
3492 }
3493
3494 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3495
3496 let selections = self.selections.all_adjusted(cx);
3497 let mut bracket_inserted = false;
3498 let mut edits = Vec::new();
3499 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3500 let mut new_selections = Vec::with_capacity(selections.len());
3501 let mut new_autoclose_regions = Vec::new();
3502 let snapshot = self.buffer.read(cx).read(cx);
3503 let mut clear_linked_edit_ranges = false;
3504
3505 for (selection, autoclose_region) in
3506 self.selections_with_autoclose_regions(selections, &snapshot)
3507 {
3508 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3509 // Determine if the inserted text matches the opening or closing
3510 // bracket of any of this language's bracket pairs.
3511 let mut bracket_pair = None;
3512 let mut is_bracket_pair_start = false;
3513 let mut is_bracket_pair_end = false;
3514 if !text.is_empty() {
3515 let mut bracket_pair_matching_end = None;
3516 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3517 // and they are removing the character that triggered IME popup.
3518 for (pair, enabled) in scope.brackets() {
3519 if !pair.close && !pair.surround {
3520 continue;
3521 }
3522
3523 if enabled && pair.start.ends_with(text.as_ref()) {
3524 let prefix_len = pair.start.len() - text.len();
3525 let preceding_text_matches_prefix = prefix_len == 0
3526 || (selection.start.column >= (prefix_len as u32)
3527 && snapshot.contains_str_at(
3528 Point::new(
3529 selection.start.row,
3530 selection.start.column - (prefix_len as u32),
3531 ),
3532 &pair.start[..prefix_len],
3533 ));
3534 if preceding_text_matches_prefix {
3535 bracket_pair = Some(pair.clone());
3536 is_bracket_pair_start = true;
3537 break;
3538 }
3539 }
3540 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3541 {
3542 // take first bracket pair matching end, but don't break in case a later bracket
3543 // pair matches start
3544 bracket_pair_matching_end = Some(pair.clone());
3545 }
3546 }
3547 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3548 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3549 is_bracket_pair_end = true;
3550 }
3551 }
3552
3553 if let Some(bracket_pair) = bracket_pair {
3554 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3555 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3556 let auto_surround =
3557 self.use_auto_surround && snapshot_settings.use_auto_surround;
3558 if selection.is_empty() {
3559 if is_bracket_pair_start {
3560 // If the inserted text is a suffix of an opening bracket and the
3561 // selection is preceded by the rest of the opening bracket, then
3562 // insert the closing bracket.
3563 let following_text_allows_autoclose = snapshot
3564 .chars_at(selection.start)
3565 .next()
3566 .map_or(true, |c| scope.should_autoclose_before(c));
3567
3568 let preceding_text_allows_autoclose = selection.start.column == 0
3569 || snapshot.reversed_chars_at(selection.start).next().map_or(
3570 true,
3571 |c| {
3572 bracket_pair.start != bracket_pair.end
3573 || !snapshot
3574 .char_classifier_at(selection.start)
3575 .is_word(c)
3576 },
3577 );
3578
3579 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3580 && bracket_pair.start.len() == 1
3581 {
3582 let target = bracket_pair.start.chars().next().unwrap();
3583 let current_line_count = snapshot
3584 .reversed_chars_at(selection.start)
3585 .take_while(|&c| c != '\n')
3586 .filter(|&c| c == target)
3587 .count();
3588 current_line_count % 2 == 1
3589 } else {
3590 false
3591 };
3592
3593 if autoclose
3594 && bracket_pair.close
3595 && following_text_allows_autoclose
3596 && preceding_text_allows_autoclose
3597 && !is_closing_quote
3598 {
3599 let anchor = snapshot.anchor_before(selection.end);
3600 new_selections.push((selection.map(|_| anchor), text.len()));
3601 new_autoclose_regions.push((
3602 anchor,
3603 text.len(),
3604 selection.id,
3605 bracket_pair.clone(),
3606 ));
3607 edits.push((
3608 selection.range(),
3609 format!("{}{}", text, bracket_pair.end).into(),
3610 ));
3611 bracket_inserted = true;
3612 continue;
3613 }
3614 }
3615
3616 if let Some(region) = autoclose_region {
3617 // If the selection is followed by an auto-inserted closing bracket,
3618 // then don't insert that closing bracket again; just move the selection
3619 // past the closing bracket.
3620 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3621 && text.as_ref() == region.pair.end.as_str();
3622 if should_skip {
3623 let anchor = snapshot.anchor_after(selection.end);
3624 new_selections
3625 .push((selection.map(|_| anchor), region.pair.end.len()));
3626 continue;
3627 }
3628 }
3629
3630 let always_treat_brackets_as_autoclosed = snapshot
3631 .language_settings_at(selection.start, cx)
3632 .always_treat_brackets_as_autoclosed;
3633 if always_treat_brackets_as_autoclosed
3634 && is_bracket_pair_end
3635 && snapshot.contains_str_at(selection.end, text.as_ref())
3636 {
3637 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3638 // and the inserted text is a closing bracket and the selection is followed
3639 // by the closing bracket then move the selection past the closing bracket.
3640 let anchor = snapshot.anchor_after(selection.end);
3641 new_selections.push((selection.map(|_| anchor), text.len()));
3642 continue;
3643 }
3644 }
3645 // If an opening bracket is 1 character long and is typed while
3646 // text is selected, then surround that text with the bracket pair.
3647 else if auto_surround
3648 && bracket_pair.surround
3649 && is_bracket_pair_start
3650 && bracket_pair.start.chars().count() == 1
3651 {
3652 edits.push((selection.start..selection.start, text.clone()));
3653 edits.push((
3654 selection.end..selection.end,
3655 bracket_pair.end.as_str().into(),
3656 ));
3657 bracket_inserted = true;
3658 new_selections.push((
3659 Selection {
3660 id: selection.id,
3661 start: snapshot.anchor_after(selection.start),
3662 end: snapshot.anchor_before(selection.end),
3663 reversed: selection.reversed,
3664 goal: selection.goal,
3665 },
3666 0,
3667 ));
3668 continue;
3669 }
3670 }
3671 }
3672
3673 if self.auto_replace_emoji_shortcode
3674 && selection.is_empty()
3675 && text.as_ref().ends_with(':')
3676 {
3677 if let Some(possible_emoji_short_code) =
3678 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3679 {
3680 if !possible_emoji_short_code.is_empty() {
3681 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3682 let emoji_shortcode_start = Point::new(
3683 selection.start.row,
3684 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3685 );
3686
3687 // Remove shortcode from buffer
3688 edits.push((
3689 emoji_shortcode_start..selection.start,
3690 "".to_string().into(),
3691 ));
3692 new_selections.push((
3693 Selection {
3694 id: selection.id,
3695 start: snapshot.anchor_after(emoji_shortcode_start),
3696 end: snapshot.anchor_before(selection.start),
3697 reversed: selection.reversed,
3698 goal: selection.goal,
3699 },
3700 0,
3701 ));
3702
3703 // Insert emoji
3704 let selection_start_anchor = snapshot.anchor_after(selection.start);
3705 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3706 edits.push((selection.start..selection.end, emoji.to_string().into()));
3707
3708 continue;
3709 }
3710 }
3711 }
3712 }
3713
3714 // If not handling any auto-close operation, then just replace the selected
3715 // text with the given input and move the selection to the end of the
3716 // newly inserted text.
3717 let anchor = snapshot.anchor_after(selection.end);
3718 if !self.linked_edit_ranges.is_empty() {
3719 let start_anchor = snapshot.anchor_before(selection.start);
3720
3721 let is_word_char = text.chars().next().map_or(true, |char| {
3722 let classifier = snapshot
3723 .char_classifier_at(start_anchor.to_offset(&snapshot))
3724 .ignore_punctuation(true);
3725 classifier.is_word(char)
3726 });
3727
3728 if is_word_char {
3729 if let Some(ranges) = self
3730 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3731 {
3732 for (buffer, edits) in ranges {
3733 linked_edits
3734 .entry(buffer.clone())
3735 .or_default()
3736 .extend(edits.into_iter().map(|range| (range, text.clone())));
3737 }
3738 }
3739 } else {
3740 clear_linked_edit_ranges = true;
3741 }
3742 }
3743
3744 new_selections.push((selection.map(|_| anchor), 0));
3745 edits.push((selection.start..selection.end, text.clone()));
3746 }
3747
3748 drop(snapshot);
3749
3750 self.transact(window, cx, |this, window, cx| {
3751 if clear_linked_edit_ranges {
3752 this.linked_edit_ranges.clear();
3753 }
3754 let initial_buffer_versions =
3755 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3756
3757 this.buffer.update(cx, |buffer, cx| {
3758 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3759 });
3760 for (buffer, edits) in linked_edits {
3761 buffer.update(cx, |buffer, cx| {
3762 let snapshot = buffer.snapshot();
3763 let edits = edits
3764 .into_iter()
3765 .map(|(range, text)| {
3766 use text::ToPoint as TP;
3767 let end_point = TP::to_point(&range.end, &snapshot);
3768 let start_point = TP::to_point(&range.start, &snapshot);
3769 (start_point..end_point, text)
3770 })
3771 .sorted_by_key(|(range, _)| range.start);
3772 buffer.edit(edits, None, cx);
3773 })
3774 }
3775 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3776 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3777 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3778 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3779 .zip(new_selection_deltas)
3780 .map(|(selection, delta)| Selection {
3781 id: selection.id,
3782 start: selection.start + delta,
3783 end: selection.end + delta,
3784 reversed: selection.reversed,
3785 goal: SelectionGoal::None,
3786 })
3787 .collect::<Vec<_>>();
3788
3789 let mut i = 0;
3790 for (position, delta, selection_id, pair) in new_autoclose_regions {
3791 let position = position.to_offset(&map.buffer_snapshot) + delta;
3792 let start = map.buffer_snapshot.anchor_before(position);
3793 let end = map.buffer_snapshot.anchor_after(position);
3794 while let Some(existing_state) = this.autoclose_regions.get(i) {
3795 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3796 Ordering::Less => i += 1,
3797 Ordering::Greater => break,
3798 Ordering::Equal => {
3799 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3800 Ordering::Less => i += 1,
3801 Ordering::Equal => break,
3802 Ordering::Greater => break,
3803 }
3804 }
3805 }
3806 }
3807 this.autoclose_regions.insert(
3808 i,
3809 AutocloseRegion {
3810 selection_id,
3811 range: start..end,
3812 pair,
3813 },
3814 );
3815 }
3816
3817 let had_active_inline_completion = this.has_active_inline_completion();
3818 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3819 s.select(new_selections)
3820 });
3821
3822 if !bracket_inserted {
3823 if let Some(on_type_format_task) =
3824 this.trigger_on_type_formatting(text.to_string(), window, cx)
3825 {
3826 on_type_format_task.detach_and_log_err(cx);
3827 }
3828 }
3829
3830 let editor_settings = EditorSettings::get_global(cx);
3831 if bracket_inserted
3832 && (editor_settings.auto_signature_help
3833 || editor_settings.show_signature_help_after_edits)
3834 {
3835 this.show_signature_help(&ShowSignatureHelp, window, cx);
3836 }
3837
3838 let trigger_in_words =
3839 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3840 if this.hard_wrap.is_some() {
3841 let latest: Range<Point> = this.selections.newest(cx).range();
3842 if latest.is_empty()
3843 && this
3844 .buffer()
3845 .read(cx)
3846 .snapshot(cx)
3847 .line_len(MultiBufferRow(latest.start.row))
3848 == latest.start.column
3849 {
3850 this.rewrap_impl(
3851 RewrapOptions {
3852 override_language_settings: true,
3853 preserve_existing_whitespace: true,
3854 },
3855 cx,
3856 )
3857 }
3858 }
3859 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3860 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3861 this.refresh_inline_completion(true, false, window, cx);
3862 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3863 });
3864 }
3865
3866 fn find_possible_emoji_shortcode_at_position(
3867 snapshot: &MultiBufferSnapshot,
3868 position: Point,
3869 ) -> Option<String> {
3870 let mut chars = Vec::new();
3871 let mut found_colon = false;
3872 for char in snapshot.reversed_chars_at(position).take(100) {
3873 // Found a possible emoji shortcode in the middle of the buffer
3874 if found_colon {
3875 if char.is_whitespace() {
3876 chars.reverse();
3877 return Some(chars.iter().collect());
3878 }
3879 // If the previous character is not a whitespace, we are in the middle of a word
3880 // and we only want to complete the shortcode if the word is made up of other emojis
3881 let mut containing_word = String::new();
3882 for ch in snapshot
3883 .reversed_chars_at(position)
3884 .skip(chars.len() + 1)
3885 .take(100)
3886 {
3887 if ch.is_whitespace() {
3888 break;
3889 }
3890 containing_word.push(ch);
3891 }
3892 let containing_word = containing_word.chars().rev().collect::<String>();
3893 if util::word_consists_of_emojis(containing_word.as_str()) {
3894 chars.reverse();
3895 return Some(chars.iter().collect());
3896 }
3897 }
3898
3899 if char.is_whitespace() || !char.is_ascii() {
3900 return None;
3901 }
3902 if char == ':' {
3903 found_colon = true;
3904 } else {
3905 chars.push(char);
3906 }
3907 }
3908 // Found a possible emoji shortcode at the beginning of the buffer
3909 chars.reverse();
3910 Some(chars.iter().collect())
3911 }
3912
3913 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3914 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3915 self.transact(window, cx, |this, window, cx| {
3916 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
3917 let selections = this.selections.all::<usize>(cx);
3918 let multi_buffer = this.buffer.read(cx);
3919 let buffer = multi_buffer.snapshot(cx);
3920 selections
3921 .iter()
3922 .map(|selection| {
3923 let start_point = selection.start.to_point(&buffer);
3924 let mut existing_indent =
3925 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3926 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
3927 let start = selection.start;
3928 let end = selection.end;
3929 let selection_is_empty = start == end;
3930 let language_scope = buffer.language_scope_at(start);
3931 let (
3932 comment_delimiter,
3933 doc_delimiter,
3934 insert_extra_newline,
3935 indent_on_newline,
3936 indent_on_extra_newline,
3937 ) = if let Some(language) = &language_scope {
3938 let mut insert_extra_newline =
3939 insert_extra_newline_brackets(&buffer, start..end, language)
3940 || insert_extra_newline_tree_sitter(&buffer, start..end);
3941
3942 // Comment extension on newline is allowed only for cursor selections
3943 let comment_delimiter = maybe!({
3944 if !selection_is_empty {
3945 return None;
3946 }
3947
3948 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3949 return None;
3950 }
3951
3952 let delimiters = language.line_comment_prefixes();
3953 let max_len_of_delimiter =
3954 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3955 let (snapshot, range) =
3956 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3957
3958 let num_of_whitespaces = snapshot
3959 .chars_for_range(range.clone())
3960 .take_while(|c| c.is_whitespace())
3961 .count();
3962 let comment_candidate = snapshot
3963 .chars_for_range(range)
3964 .skip(num_of_whitespaces)
3965 .take(max_len_of_delimiter)
3966 .collect::<String>();
3967 let (delimiter, trimmed_len) = delimiters
3968 .iter()
3969 .filter_map(|delimiter| {
3970 let prefix = delimiter.trim_end();
3971 if comment_candidate.starts_with(prefix) {
3972 Some((delimiter, prefix.len()))
3973 } else {
3974 None
3975 }
3976 })
3977 .max_by_key(|(_, len)| *len)?;
3978
3979 let cursor_is_placed_after_comment_marker =
3980 num_of_whitespaces + trimmed_len <= start_point.column as usize;
3981 if cursor_is_placed_after_comment_marker {
3982 Some(delimiter.clone())
3983 } else {
3984 None
3985 }
3986 });
3987
3988 let mut indent_on_newline = IndentSize::spaces(0);
3989 let mut indent_on_extra_newline = IndentSize::spaces(0);
3990
3991 let doc_delimiter = maybe!({
3992 if !selection_is_empty {
3993 return None;
3994 }
3995
3996 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3997 return None;
3998 }
3999
4000 let DocumentationConfig {
4001 start: start_tag,
4002 end: end_tag,
4003 prefix: delimiter,
4004 tab_size: len,
4005 } = language.documentation()?;
4006
4007 let is_within_block_comment = buffer
4008 .language_scope_at(start_point)
4009 .is_some_and(|scope| scope.override_name() == Some("comment"));
4010 if !is_within_block_comment {
4011 return None;
4012 }
4013
4014 let (snapshot, range) =
4015 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4016
4017 let num_of_whitespaces = snapshot
4018 .chars_for_range(range.clone())
4019 .take_while(|c| c.is_whitespace())
4020 .count();
4021
4022 // 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.
4023 let column = start_point.column;
4024 let cursor_is_after_start_tag = {
4025 let start_tag_len = start_tag.len();
4026 let start_tag_line = snapshot
4027 .chars_for_range(range.clone())
4028 .skip(num_of_whitespaces)
4029 .take(start_tag_len)
4030 .collect::<String>();
4031 if start_tag_line.starts_with(start_tag.as_ref()) {
4032 num_of_whitespaces + start_tag_len <= column as usize
4033 } else {
4034 false
4035 }
4036 };
4037
4038 let cursor_is_after_delimiter = {
4039 let delimiter_trim = delimiter.trim_end();
4040 let delimiter_line = snapshot
4041 .chars_for_range(range.clone())
4042 .skip(num_of_whitespaces)
4043 .take(delimiter_trim.len())
4044 .collect::<String>();
4045 if delimiter_line.starts_with(delimiter_trim) {
4046 num_of_whitespaces + delimiter_trim.len() <= column as usize
4047 } else {
4048 false
4049 }
4050 };
4051
4052 let cursor_is_before_end_tag_if_exists = {
4053 let mut char_position = 0u32;
4054 let mut end_tag_offset = None;
4055
4056 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4057 if let Some(byte_pos) = chunk.find(&**end_tag) {
4058 let chars_before_match =
4059 chunk[..byte_pos].chars().count() as u32;
4060 end_tag_offset =
4061 Some(char_position + chars_before_match);
4062 break 'outer;
4063 }
4064 char_position += chunk.chars().count() as u32;
4065 }
4066
4067 if let Some(end_tag_offset) = end_tag_offset {
4068 let cursor_is_before_end_tag = column <= end_tag_offset;
4069 if cursor_is_after_start_tag {
4070 if cursor_is_before_end_tag {
4071 insert_extra_newline = true;
4072 }
4073 let cursor_is_at_start_of_end_tag =
4074 column == end_tag_offset;
4075 if cursor_is_at_start_of_end_tag {
4076 indent_on_extra_newline.len = (*len).into();
4077 }
4078 }
4079 cursor_is_before_end_tag
4080 } else {
4081 true
4082 }
4083 };
4084
4085 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4086 && cursor_is_before_end_tag_if_exists
4087 {
4088 if cursor_is_after_start_tag {
4089 indent_on_newline.len = (*len).into();
4090 }
4091 Some(delimiter.clone())
4092 } else {
4093 None
4094 }
4095 });
4096
4097 (
4098 comment_delimiter,
4099 doc_delimiter,
4100 insert_extra_newline,
4101 indent_on_newline,
4102 indent_on_extra_newline,
4103 )
4104 } else {
4105 (
4106 None,
4107 None,
4108 false,
4109 IndentSize::default(),
4110 IndentSize::default(),
4111 )
4112 };
4113
4114 let prevent_auto_indent = doc_delimiter.is_some();
4115 let delimiter = comment_delimiter.or(doc_delimiter);
4116
4117 let capacity_for_delimiter =
4118 delimiter.as_deref().map(str::len).unwrap_or_default();
4119 let mut new_text = String::with_capacity(
4120 1 + capacity_for_delimiter
4121 + existing_indent.len as usize
4122 + indent_on_newline.len as usize
4123 + indent_on_extra_newline.len as usize,
4124 );
4125 new_text.push('\n');
4126 new_text.extend(existing_indent.chars());
4127 new_text.extend(indent_on_newline.chars());
4128
4129 if let Some(delimiter) = &delimiter {
4130 new_text.push_str(delimiter);
4131 }
4132
4133 if insert_extra_newline {
4134 new_text.push('\n');
4135 new_text.extend(existing_indent.chars());
4136 new_text.extend(indent_on_extra_newline.chars());
4137 }
4138
4139 let anchor = buffer.anchor_after(end);
4140 let new_selection = selection.map(|_| anchor);
4141 (
4142 ((start..end, new_text), prevent_auto_indent),
4143 (insert_extra_newline, new_selection),
4144 )
4145 })
4146 .unzip()
4147 };
4148
4149 let mut auto_indent_edits = Vec::new();
4150 let mut edits = Vec::new();
4151 for (edit, prevent_auto_indent) in edits_with_flags {
4152 if prevent_auto_indent {
4153 edits.push(edit);
4154 } else {
4155 auto_indent_edits.push(edit);
4156 }
4157 }
4158 if !edits.is_empty() {
4159 this.edit(edits, cx);
4160 }
4161 if !auto_indent_edits.is_empty() {
4162 this.edit_with_autoindent(auto_indent_edits, cx);
4163 }
4164
4165 let buffer = this.buffer.read(cx).snapshot(cx);
4166 let new_selections = selection_info
4167 .into_iter()
4168 .map(|(extra_newline_inserted, new_selection)| {
4169 let mut cursor = new_selection.end.to_point(&buffer);
4170 if extra_newline_inserted {
4171 cursor.row -= 1;
4172 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4173 }
4174 new_selection.map(|_| cursor)
4175 })
4176 .collect();
4177
4178 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4179 s.select(new_selections)
4180 });
4181 this.refresh_inline_completion(true, false, window, cx);
4182 });
4183 }
4184
4185 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4186 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4187
4188 let buffer = self.buffer.read(cx);
4189 let snapshot = buffer.snapshot(cx);
4190
4191 let mut edits = Vec::new();
4192 let mut rows = Vec::new();
4193
4194 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4195 let cursor = selection.head();
4196 let row = cursor.row;
4197
4198 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4199
4200 let newline = "\n".to_string();
4201 edits.push((start_of_line..start_of_line, newline));
4202
4203 rows.push(row + rows_inserted as u32);
4204 }
4205
4206 self.transact(window, cx, |editor, window, cx| {
4207 editor.edit(edits, cx);
4208
4209 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4210 let mut index = 0;
4211 s.move_cursors_with(|map, _, _| {
4212 let row = rows[index];
4213 index += 1;
4214
4215 let point = Point::new(row, 0);
4216 let boundary = map.next_line_boundary(point).1;
4217 let clipped = map.clip_point(boundary, Bias::Left);
4218
4219 (clipped, SelectionGoal::None)
4220 });
4221 });
4222
4223 let mut indent_edits = Vec::new();
4224 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4225 for row in rows {
4226 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4227 for (row, indent) in indents {
4228 if indent.len == 0 {
4229 continue;
4230 }
4231
4232 let text = match indent.kind {
4233 IndentKind::Space => " ".repeat(indent.len as usize),
4234 IndentKind::Tab => "\t".repeat(indent.len as usize),
4235 };
4236 let point = Point::new(row.0, 0);
4237 indent_edits.push((point..point, text));
4238 }
4239 }
4240 editor.edit(indent_edits, cx);
4241 });
4242 }
4243
4244 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4245 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4246
4247 let buffer = self.buffer.read(cx);
4248 let snapshot = buffer.snapshot(cx);
4249
4250 let mut edits = Vec::new();
4251 let mut rows = Vec::new();
4252 let mut rows_inserted = 0;
4253
4254 for selection in self.selections.all_adjusted(cx) {
4255 let cursor = selection.head();
4256 let row = cursor.row;
4257
4258 let point = Point::new(row + 1, 0);
4259 let start_of_line = snapshot.clip_point(point, Bias::Left);
4260
4261 let newline = "\n".to_string();
4262 edits.push((start_of_line..start_of_line, newline));
4263
4264 rows_inserted += 1;
4265 rows.push(row + rows_inserted);
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 insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4307 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4308 original_indent_columns: Vec::new(),
4309 });
4310 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4311 }
4312
4313 fn insert_with_autoindent_mode(
4314 &mut self,
4315 text: &str,
4316 autoindent_mode: Option<AutoindentMode>,
4317 window: &mut Window,
4318 cx: &mut Context<Self>,
4319 ) {
4320 if self.read_only(cx) {
4321 return;
4322 }
4323
4324 let text: Arc<str> = text.into();
4325 self.transact(window, cx, |this, window, cx| {
4326 let old_selections = this.selections.all_adjusted(cx);
4327 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4328 let anchors = {
4329 let snapshot = buffer.read(cx);
4330 old_selections
4331 .iter()
4332 .map(|s| {
4333 let anchor = snapshot.anchor_after(s.head());
4334 s.map(|_| anchor)
4335 })
4336 .collect::<Vec<_>>()
4337 };
4338 buffer.edit(
4339 old_selections
4340 .iter()
4341 .map(|s| (s.start..s.end, text.clone())),
4342 autoindent_mode,
4343 cx,
4344 );
4345 anchors
4346 });
4347
4348 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4349 s.select_anchors(selection_anchors);
4350 });
4351
4352 cx.notify();
4353 });
4354 }
4355
4356 fn trigger_completion_on_input(
4357 &mut self,
4358 text: &str,
4359 trigger_in_words: bool,
4360 window: &mut Window,
4361 cx: &mut Context<Self>,
4362 ) {
4363 let ignore_completion_provider = self
4364 .context_menu
4365 .borrow()
4366 .as_ref()
4367 .map(|menu| match menu {
4368 CodeContextMenu::Completions(completions_menu) => {
4369 completions_menu.ignore_completion_provider
4370 }
4371 CodeContextMenu::CodeActions(_) => false,
4372 })
4373 .unwrap_or(false);
4374
4375 if ignore_completion_provider {
4376 self.show_word_completions(&ShowWordCompletions, window, cx);
4377 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
4378 self.show_completions(
4379 &ShowCompletions {
4380 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4381 },
4382 window,
4383 cx,
4384 );
4385 } else {
4386 self.hide_context_menu(window, cx);
4387 }
4388 }
4389
4390 fn is_completion_trigger(
4391 &self,
4392 text: &str,
4393 trigger_in_words: bool,
4394 cx: &mut Context<Self>,
4395 ) -> bool {
4396 let position = self.selections.newest_anchor().head();
4397 let multibuffer = self.buffer.read(cx);
4398 let Some(buffer) = position
4399 .buffer_id
4400 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4401 else {
4402 return false;
4403 };
4404
4405 if let Some(completion_provider) = &self.completion_provider {
4406 completion_provider.is_completion_trigger(
4407 &buffer,
4408 position.text_anchor,
4409 text,
4410 trigger_in_words,
4411 cx,
4412 )
4413 } else {
4414 false
4415 }
4416 }
4417
4418 /// If any empty selections is touching the start of its innermost containing autoclose
4419 /// region, expand it to select the brackets.
4420 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4421 let selections = self.selections.all::<usize>(cx);
4422 let buffer = self.buffer.read(cx).read(cx);
4423 let new_selections = self
4424 .selections_with_autoclose_regions(selections, &buffer)
4425 .map(|(mut selection, region)| {
4426 if !selection.is_empty() {
4427 return selection;
4428 }
4429
4430 if let Some(region) = region {
4431 let mut range = region.range.to_offset(&buffer);
4432 if selection.start == range.start && range.start >= region.pair.start.len() {
4433 range.start -= region.pair.start.len();
4434 if buffer.contains_str_at(range.start, ®ion.pair.start)
4435 && buffer.contains_str_at(range.end, ®ion.pair.end)
4436 {
4437 range.end += region.pair.end.len();
4438 selection.start = range.start;
4439 selection.end = range.end;
4440
4441 return selection;
4442 }
4443 }
4444 }
4445
4446 let always_treat_brackets_as_autoclosed = buffer
4447 .language_settings_at(selection.start, cx)
4448 .always_treat_brackets_as_autoclosed;
4449
4450 if !always_treat_brackets_as_autoclosed {
4451 return selection;
4452 }
4453
4454 if let Some(scope) = buffer.language_scope_at(selection.start) {
4455 for (pair, enabled) in scope.brackets() {
4456 if !enabled || !pair.close {
4457 continue;
4458 }
4459
4460 if buffer.contains_str_at(selection.start, &pair.end) {
4461 let pair_start_len = pair.start.len();
4462 if buffer.contains_str_at(
4463 selection.start.saturating_sub(pair_start_len),
4464 &pair.start,
4465 ) {
4466 selection.start -= pair_start_len;
4467 selection.end += pair.end.len();
4468
4469 return selection;
4470 }
4471 }
4472 }
4473 }
4474
4475 selection
4476 })
4477 .collect();
4478
4479 drop(buffer);
4480 self.change_selections(None, window, cx, |selections| {
4481 selections.select(new_selections)
4482 });
4483 }
4484
4485 /// Iterate the given selections, and for each one, find the smallest surrounding
4486 /// autoclose region. This uses the ordering of the selections and the autoclose
4487 /// regions to avoid repeated comparisons.
4488 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4489 &'a self,
4490 selections: impl IntoIterator<Item = Selection<D>>,
4491 buffer: &'a MultiBufferSnapshot,
4492 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4493 let mut i = 0;
4494 let mut regions = self.autoclose_regions.as_slice();
4495 selections.into_iter().map(move |selection| {
4496 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4497
4498 let mut enclosing = None;
4499 while let Some(pair_state) = regions.get(i) {
4500 if pair_state.range.end.to_offset(buffer) < range.start {
4501 regions = ®ions[i + 1..];
4502 i = 0;
4503 } else if pair_state.range.start.to_offset(buffer) > range.end {
4504 break;
4505 } else {
4506 if pair_state.selection_id == selection.id {
4507 enclosing = Some(pair_state);
4508 }
4509 i += 1;
4510 }
4511 }
4512
4513 (selection, enclosing)
4514 })
4515 }
4516
4517 /// Remove any autoclose regions that no longer contain their selection.
4518 fn invalidate_autoclose_regions(
4519 &mut self,
4520 mut selections: &[Selection<Anchor>],
4521 buffer: &MultiBufferSnapshot,
4522 ) {
4523 self.autoclose_regions.retain(|state| {
4524 let mut i = 0;
4525 while let Some(selection) = selections.get(i) {
4526 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4527 selections = &selections[1..];
4528 continue;
4529 }
4530 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4531 break;
4532 }
4533 if selection.id == state.selection_id {
4534 return true;
4535 } else {
4536 i += 1;
4537 }
4538 }
4539 false
4540 });
4541 }
4542
4543 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4544 let offset = position.to_offset(buffer);
4545 let (word_range, kind) = buffer.surrounding_word(offset, true);
4546 if offset > word_range.start && kind == Some(CharKind::Word) {
4547 Some(
4548 buffer
4549 .text_for_range(word_range.start..offset)
4550 .collect::<String>(),
4551 )
4552 } else {
4553 None
4554 }
4555 }
4556
4557 pub fn toggle_inline_values(
4558 &mut self,
4559 _: &ToggleInlineValues,
4560 _: &mut Window,
4561 cx: &mut Context<Self>,
4562 ) {
4563 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4564
4565 self.refresh_inline_values(cx);
4566 }
4567
4568 pub fn toggle_inlay_hints(
4569 &mut self,
4570 _: &ToggleInlayHints,
4571 _: &mut Window,
4572 cx: &mut Context<Self>,
4573 ) {
4574 self.refresh_inlay_hints(
4575 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4576 cx,
4577 );
4578 }
4579
4580 pub fn inlay_hints_enabled(&self) -> bool {
4581 self.inlay_hint_cache.enabled
4582 }
4583
4584 pub fn inline_values_enabled(&self) -> bool {
4585 self.inline_value_cache.enabled
4586 }
4587
4588 #[cfg(any(test, feature = "test-support"))]
4589 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4590 self.display_map
4591 .read(cx)
4592 .current_inlays()
4593 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4594 .cloned()
4595 .collect()
4596 }
4597
4598 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4599 if self.semantics_provider.is_none() || !self.mode.is_full() {
4600 return;
4601 }
4602
4603 let reason_description = reason.description();
4604 let ignore_debounce = matches!(
4605 reason,
4606 InlayHintRefreshReason::SettingsChange(_)
4607 | InlayHintRefreshReason::Toggle(_)
4608 | InlayHintRefreshReason::ExcerptsRemoved(_)
4609 | InlayHintRefreshReason::ModifiersChanged(_)
4610 );
4611 let (invalidate_cache, required_languages) = match reason {
4612 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4613 match self.inlay_hint_cache.modifiers_override(enabled) {
4614 Some(enabled) => {
4615 if enabled {
4616 (InvalidationStrategy::RefreshRequested, None)
4617 } else {
4618 self.splice_inlays(
4619 &self
4620 .visible_inlay_hints(cx)
4621 .iter()
4622 .map(|inlay| inlay.id)
4623 .collect::<Vec<InlayId>>(),
4624 Vec::new(),
4625 cx,
4626 );
4627 return;
4628 }
4629 }
4630 None => return,
4631 }
4632 }
4633 InlayHintRefreshReason::Toggle(enabled) => {
4634 if self.inlay_hint_cache.toggle(enabled) {
4635 if enabled {
4636 (InvalidationStrategy::RefreshRequested, None)
4637 } else {
4638 self.splice_inlays(
4639 &self
4640 .visible_inlay_hints(cx)
4641 .iter()
4642 .map(|inlay| inlay.id)
4643 .collect::<Vec<InlayId>>(),
4644 Vec::new(),
4645 cx,
4646 );
4647 return;
4648 }
4649 } else {
4650 return;
4651 }
4652 }
4653 InlayHintRefreshReason::SettingsChange(new_settings) => {
4654 match self.inlay_hint_cache.update_settings(
4655 &self.buffer,
4656 new_settings,
4657 self.visible_inlay_hints(cx),
4658 cx,
4659 ) {
4660 ControlFlow::Break(Some(InlaySplice {
4661 to_remove,
4662 to_insert,
4663 })) => {
4664 self.splice_inlays(&to_remove, to_insert, cx);
4665 return;
4666 }
4667 ControlFlow::Break(None) => return,
4668 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4669 }
4670 }
4671 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4672 if let Some(InlaySplice {
4673 to_remove,
4674 to_insert,
4675 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4676 {
4677 self.splice_inlays(&to_remove, to_insert, cx);
4678 }
4679 self.display_map.update(cx, |display_map, _| {
4680 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4681 });
4682 return;
4683 }
4684 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4685 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4686 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4687 }
4688 InlayHintRefreshReason::RefreshRequested => {
4689 (InvalidationStrategy::RefreshRequested, None)
4690 }
4691 };
4692
4693 if let Some(InlaySplice {
4694 to_remove,
4695 to_insert,
4696 }) = self.inlay_hint_cache.spawn_hint_refresh(
4697 reason_description,
4698 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4699 invalidate_cache,
4700 ignore_debounce,
4701 cx,
4702 ) {
4703 self.splice_inlays(&to_remove, to_insert, cx);
4704 }
4705 }
4706
4707 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4708 self.display_map
4709 .read(cx)
4710 .current_inlays()
4711 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4712 .cloned()
4713 .collect()
4714 }
4715
4716 pub fn excerpts_for_inlay_hints_query(
4717 &self,
4718 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4719 cx: &mut Context<Editor>,
4720 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4721 let Some(project) = self.project.as_ref() else {
4722 return HashMap::default();
4723 };
4724 let project = project.read(cx);
4725 let multi_buffer = self.buffer().read(cx);
4726 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4727 let multi_buffer_visible_start = self
4728 .scroll_manager
4729 .anchor()
4730 .anchor
4731 .to_point(&multi_buffer_snapshot);
4732 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4733 multi_buffer_visible_start
4734 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4735 Bias::Left,
4736 );
4737 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4738 multi_buffer_snapshot
4739 .range_to_buffer_ranges(multi_buffer_visible_range)
4740 .into_iter()
4741 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4742 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4743 let buffer_file = project::File::from_dyn(buffer.file())?;
4744 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4745 let worktree_entry = buffer_worktree
4746 .read(cx)
4747 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4748 if worktree_entry.is_ignored {
4749 return None;
4750 }
4751
4752 let language = buffer.language()?;
4753 if let Some(restrict_to_languages) = restrict_to_languages {
4754 if !restrict_to_languages.contains(language) {
4755 return None;
4756 }
4757 }
4758 Some((
4759 excerpt_id,
4760 (
4761 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4762 buffer.version().clone(),
4763 excerpt_visible_range,
4764 ),
4765 ))
4766 })
4767 .collect()
4768 }
4769
4770 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4771 TextLayoutDetails {
4772 text_system: window.text_system().clone(),
4773 editor_style: self.style.clone().unwrap(),
4774 rem_size: window.rem_size(),
4775 scroll_anchor: self.scroll_manager.anchor(),
4776 visible_rows: self.visible_line_count(),
4777 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4778 }
4779 }
4780
4781 pub fn splice_inlays(
4782 &self,
4783 to_remove: &[InlayId],
4784 to_insert: Vec<Inlay>,
4785 cx: &mut Context<Self>,
4786 ) {
4787 self.display_map.update(cx, |display_map, cx| {
4788 display_map.splice_inlays(to_remove, to_insert, cx)
4789 });
4790 cx.notify();
4791 }
4792
4793 fn trigger_on_type_formatting(
4794 &self,
4795 input: String,
4796 window: &mut Window,
4797 cx: &mut Context<Self>,
4798 ) -> Option<Task<Result<()>>> {
4799 if input.len() != 1 {
4800 return None;
4801 }
4802
4803 let project = self.project.as_ref()?;
4804 let position = self.selections.newest_anchor().head();
4805 let (buffer, buffer_position) = self
4806 .buffer
4807 .read(cx)
4808 .text_anchor_for_position(position, cx)?;
4809
4810 let settings = language_settings::language_settings(
4811 buffer
4812 .read(cx)
4813 .language_at(buffer_position)
4814 .map(|l| l.name()),
4815 buffer.read(cx).file(),
4816 cx,
4817 );
4818 if !settings.use_on_type_format {
4819 return None;
4820 }
4821
4822 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4823 // hence we do LSP request & edit on host side only — add formats to host's history.
4824 let push_to_lsp_host_history = true;
4825 // If this is not the host, append its history with new edits.
4826 let push_to_client_history = project.read(cx).is_via_collab();
4827
4828 let on_type_formatting = project.update(cx, |project, cx| {
4829 project.on_type_format(
4830 buffer.clone(),
4831 buffer_position,
4832 input,
4833 push_to_lsp_host_history,
4834 cx,
4835 )
4836 });
4837 Some(cx.spawn_in(window, async move |editor, cx| {
4838 if let Some(transaction) = on_type_formatting.await? {
4839 if push_to_client_history {
4840 buffer
4841 .update(cx, |buffer, _| {
4842 buffer.push_transaction(transaction, Instant::now());
4843 buffer.finalize_last_transaction();
4844 })
4845 .ok();
4846 }
4847 editor.update(cx, |editor, cx| {
4848 editor.refresh_document_highlights(cx);
4849 })?;
4850 }
4851 Ok(())
4852 }))
4853 }
4854
4855 pub fn show_word_completions(
4856 &mut self,
4857 _: &ShowWordCompletions,
4858 window: &mut Window,
4859 cx: &mut Context<Self>,
4860 ) {
4861 self.open_completions_menu(true, None, window, cx);
4862 }
4863
4864 pub fn show_completions(
4865 &mut self,
4866 options: &ShowCompletions,
4867 window: &mut Window,
4868 cx: &mut Context<Self>,
4869 ) {
4870 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4871 }
4872
4873 fn open_completions_menu(
4874 &mut self,
4875 ignore_completion_provider: bool,
4876 trigger: Option<&str>,
4877 window: &mut Window,
4878 cx: &mut Context<Self>,
4879 ) {
4880 if self.pending_rename.is_some() {
4881 return;
4882 }
4883 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4884 return;
4885 }
4886
4887 let position = self.selections.newest_anchor().head();
4888 if position.diff_base_anchor.is_some() {
4889 return;
4890 }
4891 let (buffer, buffer_position) =
4892 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4893 output
4894 } else {
4895 return;
4896 };
4897 let buffer_snapshot = buffer.read(cx).snapshot();
4898 let show_completion_documentation = buffer_snapshot
4899 .settings_at(buffer_position, cx)
4900 .show_completion_documentation;
4901
4902 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4903
4904 let trigger_kind = match trigger {
4905 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4906 CompletionTriggerKind::TRIGGER_CHARACTER
4907 }
4908 _ => CompletionTriggerKind::INVOKED,
4909 };
4910 let completion_context = CompletionContext {
4911 trigger_character: trigger.and_then(|trigger| {
4912 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4913 Some(String::from(trigger))
4914 } else {
4915 None
4916 }
4917 }),
4918 trigger_kind,
4919 };
4920
4921 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4922 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4923 let word_to_exclude = buffer_snapshot
4924 .text_for_range(old_range.clone())
4925 .collect::<String>();
4926 (
4927 buffer_snapshot.anchor_before(old_range.start)
4928 ..buffer_snapshot.anchor_after(old_range.end),
4929 Some(word_to_exclude),
4930 )
4931 } else {
4932 (buffer_position..buffer_position, None)
4933 };
4934
4935 let completion_settings = language_settings(
4936 buffer_snapshot
4937 .language_at(buffer_position)
4938 .map(|language| language.name()),
4939 buffer_snapshot.file(),
4940 cx,
4941 )
4942 .completions;
4943
4944 // The document can be large, so stay in reasonable bounds when searching for words,
4945 // otherwise completion pop-up might be slow to appear.
4946 const WORD_LOOKUP_ROWS: u32 = 5_000;
4947 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4948 let min_word_search = buffer_snapshot.clip_point(
4949 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4950 Bias::Left,
4951 );
4952 let max_word_search = buffer_snapshot.clip_point(
4953 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4954 Bias::Right,
4955 );
4956 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4957 ..buffer_snapshot.point_to_offset(max_word_search);
4958
4959 let provider = self
4960 .completion_provider
4961 .as_ref()
4962 .filter(|_| !ignore_completion_provider);
4963 let skip_digits = query
4964 .as_ref()
4965 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4966
4967 let (mut words, provided_completions) = match provider {
4968 Some(provider) => {
4969 let completions = provider.completions(
4970 position.excerpt_id,
4971 &buffer,
4972 buffer_position,
4973 completion_context,
4974 window,
4975 cx,
4976 );
4977
4978 let words = match completion_settings.words {
4979 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4980 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4981 .background_spawn(async move {
4982 buffer_snapshot.words_in_range(WordsQuery {
4983 fuzzy_contents: None,
4984 range: word_search_range,
4985 skip_digits,
4986 })
4987 }),
4988 };
4989
4990 (words, completions)
4991 }
4992 None => (
4993 cx.background_spawn(async move {
4994 buffer_snapshot.words_in_range(WordsQuery {
4995 fuzzy_contents: None,
4996 range: word_search_range,
4997 skip_digits,
4998 })
4999 }),
5000 Task::ready(Ok(None)),
5001 ),
5002 };
5003
5004 let sort_completions = provider
5005 .as_ref()
5006 .map_or(false, |provider| provider.sort_completions());
5007
5008 let filter_completions = provider
5009 .as_ref()
5010 .map_or(true, |provider| provider.filter_completions());
5011
5012 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5013
5014 let id = post_inc(&mut self.next_completion_id);
5015 let task = cx.spawn_in(window, async move |editor, cx| {
5016 async move {
5017 editor.update(cx, |this, _| {
5018 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5019 })?;
5020
5021 let mut completions = Vec::new();
5022 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
5023 completions.extend(provided_completions);
5024 if completion_settings.words == WordsCompletionMode::Fallback {
5025 words = Task::ready(BTreeMap::default());
5026 }
5027 }
5028
5029 let mut words = words.await;
5030 if let Some(word_to_exclude) = &word_to_exclude {
5031 words.remove(word_to_exclude);
5032 }
5033 for lsp_completion in &completions {
5034 words.remove(&lsp_completion.new_text);
5035 }
5036 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5037 replace_range: old_range.clone(),
5038 new_text: word.clone(),
5039 label: CodeLabel::plain(word, None),
5040 icon_path: None,
5041 documentation: None,
5042 source: CompletionSource::BufferWord {
5043 word_range,
5044 resolved: false,
5045 },
5046 insert_text_mode: Some(InsertTextMode::AS_IS),
5047 confirm: None,
5048 }));
5049
5050 let menu = if completions.is_empty() {
5051 None
5052 } else {
5053 let mut menu = CompletionsMenu::new(
5054 id,
5055 sort_completions,
5056 show_completion_documentation,
5057 ignore_completion_provider,
5058 position,
5059 buffer.clone(),
5060 completions.into(),
5061 snippet_sort_order,
5062 );
5063
5064 menu.filter(
5065 if filter_completions {
5066 query.as_deref()
5067 } else {
5068 None
5069 },
5070 cx.background_executor().clone(),
5071 )
5072 .await;
5073
5074 menu.visible().then_some(menu)
5075 };
5076
5077 editor.update_in(cx, |editor, window, cx| {
5078 match editor.context_menu.borrow().as_ref() {
5079 None => {}
5080 Some(CodeContextMenu::Completions(prev_menu)) => {
5081 if prev_menu.id > id {
5082 return;
5083 }
5084 }
5085 _ => return,
5086 }
5087
5088 if editor.focus_handle.is_focused(window) && menu.is_some() {
5089 let mut menu = menu.unwrap();
5090 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
5091 crate::hover_popover::hide_hover(editor, cx);
5092 *editor.context_menu.borrow_mut() =
5093 Some(CodeContextMenu::Completions(menu));
5094
5095 if editor.show_edit_predictions_in_menu() {
5096 editor.update_visible_inline_completion(window, cx);
5097 } else {
5098 editor.discard_inline_completion(false, cx);
5099 }
5100
5101 cx.notify();
5102 } else if editor.completion_tasks.len() <= 1 {
5103 // If there are no more completion tasks and the last menu was
5104 // empty, we should hide it.
5105 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5106 // If it was already hidden and we don't show inline
5107 // completions in the menu, we should also show the
5108 // inline-completion when available.
5109 if was_hidden && editor.show_edit_predictions_in_menu() {
5110 editor.update_visible_inline_completion(window, cx);
5111 }
5112 }
5113 })?;
5114
5115 anyhow::Ok(())
5116 }
5117 .log_err()
5118 .await
5119 });
5120
5121 self.completion_tasks.push((id, task));
5122 }
5123
5124 #[cfg(feature = "test-support")]
5125 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5126 let menu = self.context_menu.borrow();
5127 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5128 let completions = menu.completions.borrow();
5129 Some(completions.to_vec())
5130 } else {
5131 None
5132 }
5133 }
5134
5135 pub fn confirm_completion(
5136 &mut self,
5137 action: &ConfirmCompletion,
5138 window: &mut Window,
5139 cx: &mut Context<Self>,
5140 ) -> Option<Task<Result<()>>> {
5141 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5142 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5143 }
5144
5145 pub fn confirm_completion_insert(
5146 &mut self,
5147 _: &ConfirmCompletionInsert,
5148 window: &mut Window,
5149 cx: &mut Context<Self>,
5150 ) -> Option<Task<Result<()>>> {
5151 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5152 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5153 }
5154
5155 pub fn confirm_completion_replace(
5156 &mut self,
5157 _: &ConfirmCompletionReplace,
5158 window: &mut Window,
5159 cx: &mut Context<Self>,
5160 ) -> Option<Task<Result<()>>> {
5161 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5162 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5163 }
5164
5165 pub fn compose_completion(
5166 &mut self,
5167 action: &ComposeCompletion,
5168 window: &mut Window,
5169 cx: &mut Context<Self>,
5170 ) -> Option<Task<Result<()>>> {
5171 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5172 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5173 }
5174
5175 fn do_completion(
5176 &mut self,
5177 item_ix: Option<usize>,
5178 intent: CompletionIntent,
5179 window: &mut Window,
5180 cx: &mut Context<Editor>,
5181 ) -> Option<Task<Result<()>>> {
5182 use language::ToOffset as _;
5183
5184 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5185 else {
5186 return None;
5187 };
5188
5189 let candidate_id = {
5190 let entries = completions_menu.entries.borrow();
5191 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5192 if self.show_edit_predictions_in_menu() {
5193 self.discard_inline_completion(true, cx);
5194 }
5195 mat.candidate_id
5196 };
5197
5198 let buffer_handle = completions_menu.buffer;
5199 let completion = completions_menu
5200 .completions
5201 .borrow()
5202 .get(candidate_id)?
5203 .clone();
5204 cx.stop_propagation();
5205
5206 let snapshot = self.buffer.read(cx).snapshot(cx);
5207 let newest_anchor = self.selections.newest_anchor();
5208
5209 let snippet;
5210 let new_text;
5211 if completion.is_snippet() {
5212 let mut snippet_source = completion.new_text.clone();
5213 if let Some(scope) = snapshot.language_scope_at(newest_anchor.head()) {
5214 if scope.prefers_label_for_snippet_in_completion() {
5215 if let Some(label) = completion.label() {
5216 if matches!(
5217 completion.kind(),
5218 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
5219 ) {
5220 snippet_source = label;
5221 }
5222 }
5223 }
5224 }
5225 snippet = Some(Snippet::parse(&snippet_source).log_err()?);
5226 new_text = snippet.as_ref().unwrap().text.clone();
5227 } else {
5228 snippet = None;
5229 new_text = completion.new_text.clone();
5230 };
5231
5232 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
5233 let buffer = buffer_handle.read(cx);
5234 let replace_range_multibuffer = {
5235 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5236 let multibuffer_anchor = snapshot
5237 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5238 .unwrap()
5239 ..snapshot
5240 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5241 .unwrap();
5242 multibuffer_anchor.start.to_offset(&snapshot)
5243 ..multibuffer_anchor.end.to_offset(&snapshot)
5244 };
5245 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5246 return None;
5247 }
5248
5249 let old_text = buffer
5250 .text_for_range(replace_range.clone())
5251 .collect::<String>();
5252 let lookbehind = newest_anchor
5253 .start
5254 .text_anchor
5255 .to_offset(buffer)
5256 .saturating_sub(replace_range.start);
5257 let lookahead = replace_range
5258 .end
5259 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5260 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5261 let suffix = &old_text[lookbehind.min(old_text.len())..];
5262
5263 let selections = self.selections.all::<usize>(cx);
5264 let mut ranges = Vec::new();
5265 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5266
5267 for selection in &selections {
5268 let range = if selection.id == newest_anchor.id {
5269 replace_range_multibuffer.clone()
5270 } else {
5271 let mut range = selection.range();
5272
5273 // if prefix is present, don't duplicate it
5274 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5275 range.start = range.start.saturating_sub(lookbehind);
5276
5277 // if suffix is also present, mimic the newest cursor and replace it
5278 if selection.id != newest_anchor.id
5279 && snapshot.contains_str_at(range.end, suffix)
5280 {
5281 range.end += lookahead;
5282 }
5283 }
5284 range
5285 };
5286
5287 ranges.push(range.clone());
5288
5289 if !self.linked_edit_ranges.is_empty() {
5290 let start_anchor = snapshot.anchor_before(range.start);
5291 let end_anchor = snapshot.anchor_after(range.end);
5292 if let Some(ranges) = self
5293 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5294 {
5295 for (buffer, edits) in ranges {
5296 linked_edits
5297 .entry(buffer.clone())
5298 .or_default()
5299 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5300 }
5301 }
5302 }
5303 }
5304
5305 cx.emit(EditorEvent::InputHandled {
5306 utf16_range_to_replace: None,
5307 text: new_text.clone().into(),
5308 });
5309
5310 self.transact(window, cx, |this, window, cx| {
5311 if let Some(mut snippet) = snippet {
5312 snippet.text = new_text.to_string();
5313 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5314 } else {
5315 this.buffer.update(cx, |buffer, cx| {
5316 let auto_indent = match completion.insert_text_mode {
5317 Some(InsertTextMode::AS_IS) => None,
5318 _ => this.autoindent_mode.clone(),
5319 };
5320 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5321 buffer.edit(edits, auto_indent, cx);
5322 });
5323 }
5324 for (buffer, edits) in linked_edits {
5325 buffer.update(cx, |buffer, cx| {
5326 let snapshot = buffer.snapshot();
5327 let edits = edits
5328 .into_iter()
5329 .map(|(range, text)| {
5330 use text::ToPoint as TP;
5331 let end_point = TP::to_point(&range.end, &snapshot);
5332 let start_point = TP::to_point(&range.start, &snapshot);
5333 (start_point..end_point, text)
5334 })
5335 .sorted_by_key(|(range, _)| range.start);
5336 buffer.edit(edits, None, cx);
5337 })
5338 }
5339
5340 this.refresh_inline_completion(true, false, window, cx);
5341 });
5342
5343 let show_new_completions_on_confirm = completion
5344 .confirm
5345 .as_ref()
5346 .map_or(false, |confirm| confirm(intent, window, cx));
5347 if show_new_completions_on_confirm {
5348 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5349 }
5350
5351 let provider = self.completion_provider.as_ref()?;
5352 drop(completion);
5353 let apply_edits = provider.apply_additional_edits_for_completion(
5354 buffer_handle,
5355 completions_menu.completions.clone(),
5356 candidate_id,
5357 true,
5358 cx,
5359 );
5360
5361 let editor_settings = EditorSettings::get_global(cx);
5362 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5363 // After the code completion is finished, users often want to know what signatures are needed.
5364 // so we should automatically call signature_help
5365 self.show_signature_help(&ShowSignatureHelp, window, cx);
5366 }
5367
5368 Some(cx.foreground_executor().spawn(async move {
5369 apply_edits.await?;
5370 Ok(())
5371 }))
5372 }
5373
5374 pub fn toggle_code_actions(
5375 &mut self,
5376 action: &ToggleCodeActions,
5377 window: &mut Window,
5378 cx: &mut Context<Self>,
5379 ) {
5380 let quick_launch = action.quick_launch;
5381 let mut context_menu = self.context_menu.borrow_mut();
5382 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5383 if code_actions.deployed_from == action.deployed_from {
5384 // Toggle if we're selecting the same one
5385 *context_menu = None;
5386 cx.notify();
5387 return;
5388 } else {
5389 // Otherwise, clear it and start a new one
5390 *context_menu = None;
5391 cx.notify();
5392 }
5393 }
5394 drop(context_menu);
5395 let snapshot = self.snapshot(window, cx);
5396 let deployed_from = action.deployed_from.clone();
5397 let mut task = self.code_actions_task.take();
5398 let action = action.clone();
5399 cx.spawn_in(window, async move |editor, cx| {
5400 while let Some(prev_task) = task {
5401 prev_task.await.log_err();
5402 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5403 }
5404
5405 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5406 if editor.focus_handle.is_focused(window) {
5407 let multibuffer_point = match &action.deployed_from {
5408 Some(CodeActionSource::Indicator(row)) => {
5409 DisplayPoint::new(*row, 0).to_point(&snapshot)
5410 }
5411 _ => editor.selections.newest::<Point>(cx).head(),
5412 };
5413 let (buffer, buffer_row) = snapshot
5414 .buffer_snapshot
5415 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5416 .and_then(|(buffer_snapshot, range)| {
5417 editor
5418 .buffer
5419 .read(cx)
5420 .buffer(buffer_snapshot.remote_id())
5421 .map(|buffer| (buffer, range.start.row))
5422 })?;
5423 let (_, code_actions) = editor
5424 .available_code_actions
5425 .clone()
5426 .and_then(|(location, code_actions)| {
5427 let snapshot = location.buffer.read(cx).snapshot();
5428 let point_range = location.range.to_point(&snapshot);
5429 let point_range = point_range.start.row..=point_range.end.row;
5430 if point_range.contains(&buffer_row) {
5431 Some((location, code_actions))
5432 } else {
5433 None
5434 }
5435 })
5436 .unzip();
5437 let buffer_id = buffer.read(cx).remote_id();
5438 let tasks = editor
5439 .tasks
5440 .get(&(buffer_id, buffer_row))
5441 .map(|t| Arc::new(t.to_owned()));
5442 if tasks.is_none() && code_actions.is_none() {
5443 return None;
5444 }
5445
5446 editor.completion_tasks.clear();
5447 editor.discard_inline_completion(false, cx);
5448 let task_context =
5449 tasks
5450 .as_ref()
5451 .zip(editor.project.clone())
5452 .map(|(tasks, project)| {
5453 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5454 });
5455
5456 Some(cx.spawn_in(window, async move |editor, cx| {
5457 let task_context = match task_context {
5458 Some(task_context) => task_context.await,
5459 None => None,
5460 };
5461 let resolved_tasks =
5462 tasks
5463 .zip(task_context.clone())
5464 .map(|(tasks, task_context)| ResolvedTasks {
5465 templates: tasks.resolve(&task_context).collect(),
5466 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5467 multibuffer_point.row,
5468 tasks.column,
5469 )),
5470 });
5471 let debug_scenarios = editor.update(cx, |editor, cx| {
5472 if cx.has_flag::<DebuggerFeatureFlag>() {
5473 maybe!({
5474 let project = editor.project.as_ref()?;
5475 let dap_store = project.read(cx).dap_store();
5476 let mut scenarios = vec![];
5477 let resolved_tasks = resolved_tasks.as_ref()?;
5478 let buffer = buffer.read(cx);
5479 let language = buffer.language()?;
5480 let file = buffer.file();
5481 let debug_adapter =
5482 language_settings(language.name().into(), file, cx)
5483 .debuggers
5484 .first()
5485 .map(SharedString::from)
5486 .or_else(|| {
5487 language
5488 .config()
5489 .debuggers
5490 .first()
5491 .map(SharedString::from)
5492 })?;
5493
5494 dap_store.update(cx, |dap_store, cx| {
5495 for (_, task) in &resolved_tasks.templates {
5496 if let Some(scenario) = dap_store
5497 .debug_scenario_for_build_task(
5498 task.original_task().clone(),
5499 debug_adapter.clone().into(),
5500 task.display_label().to_owned().into(),
5501 cx,
5502 )
5503 {
5504 scenarios.push(scenario);
5505 }
5506 }
5507 });
5508 Some(scenarios)
5509 })
5510 .unwrap_or_default()
5511 } else {
5512 vec![]
5513 }
5514 })?;
5515 let spawn_straight_away = quick_launch
5516 && resolved_tasks
5517 .as_ref()
5518 .map_or(false, |tasks| tasks.templates.len() == 1)
5519 && code_actions
5520 .as_ref()
5521 .map_or(true, |actions| actions.is_empty())
5522 && debug_scenarios.is_empty();
5523 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5524 crate::hover_popover::hide_hover(editor, cx);
5525 *editor.context_menu.borrow_mut() =
5526 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5527 buffer,
5528 actions: CodeActionContents::new(
5529 resolved_tasks,
5530 code_actions,
5531 debug_scenarios,
5532 task_context.unwrap_or_default(),
5533 ),
5534 selected_item: Default::default(),
5535 scroll_handle: UniformListScrollHandle::default(),
5536 deployed_from,
5537 }));
5538 if spawn_straight_away {
5539 if let Some(task) = editor.confirm_code_action(
5540 &ConfirmCodeAction { item_ix: Some(0) },
5541 window,
5542 cx,
5543 ) {
5544 cx.notify();
5545 return task;
5546 }
5547 }
5548 cx.notify();
5549 Task::ready(Ok(()))
5550 }) {
5551 task.await
5552 } else {
5553 Ok(())
5554 }
5555 }))
5556 } else {
5557 Some(Task::ready(Ok(())))
5558 }
5559 })?;
5560 if let Some(task) = spawned_test_task {
5561 task.await?;
5562 }
5563
5564 anyhow::Ok(())
5565 })
5566 .detach_and_log_err(cx);
5567 }
5568
5569 pub fn confirm_code_action(
5570 &mut self,
5571 action: &ConfirmCodeAction,
5572 window: &mut Window,
5573 cx: &mut Context<Self>,
5574 ) -> Option<Task<Result<()>>> {
5575 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5576
5577 let actions_menu =
5578 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5579 menu
5580 } else {
5581 return None;
5582 };
5583
5584 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5585 let action = actions_menu.actions.get(action_ix)?;
5586 let title = action.label();
5587 let buffer = actions_menu.buffer;
5588 let workspace = self.workspace()?;
5589
5590 match action {
5591 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5592 workspace.update(cx, |workspace, cx| {
5593 workspace.schedule_resolved_task(
5594 task_source_kind,
5595 resolved_task,
5596 false,
5597 window,
5598 cx,
5599 );
5600
5601 Some(Task::ready(Ok(())))
5602 })
5603 }
5604 CodeActionsItem::CodeAction {
5605 excerpt_id,
5606 action,
5607 provider,
5608 } => {
5609 let apply_code_action =
5610 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5611 let workspace = workspace.downgrade();
5612 Some(cx.spawn_in(window, async move |editor, cx| {
5613 let project_transaction = apply_code_action.await?;
5614 Self::open_project_transaction(
5615 &editor,
5616 workspace,
5617 project_transaction,
5618 title,
5619 cx,
5620 )
5621 .await
5622 }))
5623 }
5624 CodeActionsItem::DebugScenario(scenario) => {
5625 let context = actions_menu.actions.context.clone();
5626
5627 workspace.update(cx, |workspace, cx| {
5628 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
5629 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5630 });
5631 Some(Task::ready(Ok(())))
5632 }
5633 }
5634 }
5635
5636 pub async fn open_project_transaction(
5637 this: &WeakEntity<Editor>,
5638 workspace: WeakEntity<Workspace>,
5639 transaction: ProjectTransaction,
5640 title: String,
5641 cx: &mut AsyncWindowContext,
5642 ) -> Result<()> {
5643 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5644 cx.update(|_, cx| {
5645 entries.sort_unstable_by_key(|(buffer, _)| {
5646 buffer.read(cx).file().map(|f| f.path().clone())
5647 });
5648 })?;
5649
5650 // If the project transaction's edits are all contained within this editor, then
5651 // avoid opening a new editor to display them.
5652
5653 if let Some((buffer, transaction)) = entries.first() {
5654 if entries.len() == 1 {
5655 let excerpt = this.update(cx, |editor, cx| {
5656 editor
5657 .buffer()
5658 .read(cx)
5659 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5660 })?;
5661 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5662 if excerpted_buffer == *buffer {
5663 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5664 let excerpt_range = excerpt_range.to_offset(buffer);
5665 buffer
5666 .edited_ranges_for_transaction::<usize>(transaction)
5667 .all(|range| {
5668 excerpt_range.start <= range.start
5669 && excerpt_range.end >= range.end
5670 })
5671 })?;
5672
5673 if all_edits_within_excerpt {
5674 return Ok(());
5675 }
5676 }
5677 }
5678 }
5679 } else {
5680 return Ok(());
5681 }
5682
5683 let mut ranges_to_highlight = Vec::new();
5684 let excerpt_buffer = cx.new(|cx| {
5685 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5686 for (buffer_handle, transaction) in &entries {
5687 let edited_ranges = buffer_handle
5688 .read(cx)
5689 .edited_ranges_for_transaction::<Point>(transaction)
5690 .collect::<Vec<_>>();
5691 let (ranges, _) = multibuffer.set_excerpts_for_path(
5692 PathKey::for_buffer(buffer_handle, cx),
5693 buffer_handle.clone(),
5694 edited_ranges,
5695 DEFAULT_MULTIBUFFER_CONTEXT,
5696 cx,
5697 );
5698
5699 ranges_to_highlight.extend(ranges);
5700 }
5701 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5702 multibuffer
5703 })?;
5704
5705 workspace.update_in(cx, |workspace, window, cx| {
5706 let project = workspace.project().clone();
5707 let editor =
5708 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5709 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5710 editor.update(cx, |editor, cx| {
5711 editor.highlight_background::<Self>(
5712 &ranges_to_highlight,
5713 |theme| theme.editor_highlighted_line_background,
5714 cx,
5715 );
5716 });
5717 })?;
5718
5719 Ok(())
5720 }
5721
5722 pub fn clear_code_action_providers(&mut self) {
5723 self.code_action_providers.clear();
5724 self.available_code_actions.take();
5725 }
5726
5727 pub fn add_code_action_provider(
5728 &mut self,
5729 provider: Rc<dyn CodeActionProvider>,
5730 window: &mut Window,
5731 cx: &mut Context<Self>,
5732 ) {
5733 if self
5734 .code_action_providers
5735 .iter()
5736 .any(|existing_provider| existing_provider.id() == provider.id())
5737 {
5738 return;
5739 }
5740
5741 self.code_action_providers.push(provider);
5742 self.refresh_code_actions(window, cx);
5743 }
5744
5745 pub fn remove_code_action_provider(
5746 &mut self,
5747 id: Arc<str>,
5748 window: &mut Window,
5749 cx: &mut Context<Self>,
5750 ) {
5751 self.code_action_providers
5752 .retain(|provider| provider.id() != id);
5753 self.refresh_code_actions(window, cx);
5754 }
5755
5756 pub fn code_actions_enabled(&self, cx: &App) -> bool {
5757 !self.code_action_providers.is_empty()
5758 && EditorSettings::get_global(cx).toolbar.code_actions
5759 }
5760
5761 pub fn has_available_code_actions(&self) -> bool {
5762 self.available_code_actions
5763 .as_ref()
5764 .is_some_and(|(_, actions)| !actions.is_empty())
5765 }
5766
5767 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
5768 &self.context_menu
5769 }
5770
5771 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5772 let newest_selection = self.selections.newest_anchor().clone();
5773 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5774 let buffer = self.buffer.read(cx);
5775 if newest_selection.head().diff_base_anchor.is_some() {
5776 return None;
5777 }
5778 let (start_buffer, start) =
5779 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5780 let (end_buffer, end) =
5781 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5782 if start_buffer != end_buffer {
5783 return None;
5784 }
5785
5786 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5787 cx.background_executor()
5788 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5789 .await;
5790
5791 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5792 let providers = this.code_action_providers.clone();
5793 let tasks = this
5794 .code_action_providers
5795 .iter()
5796 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5797 .collect::<Vec<_>>();
5798 (providers, tasks)
5799 })?;
5800
5801 let mut actions = Vec::new();
5802 for (provider, provider_actions) in
5803 providers.into_iter().zip(future::join_all(tasks).await)
5804 {
5805 if let Some(provider_actions) = provider_actions.log_err() {
5806 actions.extend(provider_actions.into_iter().map(|action| {
5807 AvailableCodeAction {
5808 excerpt_id: newest_selection.start.excerpt_id,
5809 action,
5810 provider: provider.clone(),
5811 }
5812 }));
5813 }
5814 }
5815
5816 this.update(cx, |this, cx| {
5817 this.available_code_actions = if actions.is_empty() {
5818 None
5819 } else {
5820 Some((
5821 Location {
5822 buffer: start_buffer,
5823 range: start..end,
5824 },
5825 actions.into(),
5826 ))
5827 };
5828 cx.notify();
5829 })
5830 }));
5831 None
5832 }
5833
5834 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5835 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5836 self.show_git_blame_inline = false;
5837
5838 self.show_git_blame_inline_delay_task =
5839 Some(cx.spawn_in(window, async move |this, cx| {
5840 cx.background_executor().timer(delay).await;
5841
5842 this.update(cx, |this, cx| {
5843 this.show_git_blame_inline = true;
5844 cx.notify();
5845 })
5846 .log_err();
5847 }));
5848 }
5849 }
5850
5851 fn show_blame_popover(
5852 &mut self,
5853 blame_entry: &BlameEntry,
5854 position: gpui::Point<Pixels>,
5855 cx: &mut Context<Self>,
5856 ) {
5857 if let Some(state) = &mut self.inline_blame_popover {
5858 state.hide_task.take();
5859 cx.notify();
5860 } else {
5861 let delay = EditorSettings::get_global(cx).hover_popover_delay;
5862 let show_task = cx.spawn(async move |editor, cx| {
5863 cx.background_executor()
5864 .timer(std::time::Duration::from_millis(delay))
5865 .await;
5866 editor
5867 .update(cx, |editor, cx| {
5868 if let Some(state) = &mut editor.inline_blame_popover {
5869 state.show_task = None;
5870 cx.notify();
5871 }
5872 })
5873 .ok();
5874 });
5875 let Some(blame) = self.blame.as_ref() else {
5876 return;
5877 };
5878 let blame = blame.read(cx);
5879 let details = blame.details_for_entry(&blame_entry);
5880 let markdown = cx.new(|cx| {
5881 Markdown::new(
5882 details
5883 .as_ref()
5884 .map(|message| message.message.clone())
5885 .unwrap_or_default(),
5886 None,
5887 None,
5888 cx,
5889 )
5890 });
5891 self.inline_blame_popover = Some(InlineBlamePopover {
5892 position,
5893 show_task: Some(show_task),
5894 hide_task: None,
5895 popover_bounds: None,
5896 popover_state: InlineBlamePopoverState {
5897 scroll_handle: ScrollHandle::new(),
5898 commit_message: details,
5899 markdown,
5900 },
5901 });
5902 }
5903 }
5904
5905 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
5906 if let Some(state) = &mut self.inline_blame_popover {
5907 if state.show_task.is_some() {
5908 self.inline_blame_popover.take();
5909 cx.notify();
5910 } else {
5911 let hide_task = cx.spawn(async move |editor, cx| {
5912 cx.background_executor()
5913 .timer(std::time::Duration::from_millis(100))
5914 .await;
5915 editor
5916 .update(cx, |editor, cx| {
5917 editor.inline_blame_popover.take();
5918 cx.notify();
5919 })
5920 .ok();
5921 });
5922 state.hide_task = Some(hide_task);
5923 }
5924 }
5925 }
5926
5927 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5928 if self.pending_rename.is_some() {
5929 return None;
5930 }
5931
5932 let provider = self.semantics_provider.clone()?;
5933 let buffer = self.buffer.read(cx);
5934 let newest_selection = self.selections.newest_anchor().clone();
5935 let cursor_position = newest_selection.head();
5936 let (cursor_buffer, cursor_buffer_position) =
5937 buffer.text_anchor_for_position(cursor_position, cx)?;
5938 let (tail_buffer, tail_buffer_position) =
5939 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5940 if cursor_buffer != tail_buffer {
5941 return None;
5942 }
5943
5944 let snapshot = cursor_buffer.read(cx).snapshot();
5945 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
5946 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
5947 if start_word_range != end_word_range {
5948 self.document_highlights_task.take();
5949 self.clear_background_highlights::<DocumentHighlightRead>(cx);
5950 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
5951 return None;
5952 }
5953
5954 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5955 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5956 cx.background_executor()
5957 .timer(Duration::from_millis(debounce))
5958 .await;
5959
5960 let highlights = if let Some(highlights) = cx
5961 .update(|cx| {
5962 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5963 })
5964 .ok()
5965 .flatten()
5966 {
5967 highlights.await.log_err()
5968 } else {
5969 None
5970 };
5971
5972 if let Some(highlights) = highlights {
5973 this.update(cx, |this, cx| {
5974 if this.pending_rename.is_some() {
5975 return;
5976 }
5977
5978 let buffer_id = cursor_position.buffer_id;
5979 let buffer = this.buffer.read(cx);
5980 if !buffer
5981 .text_anchor_for_position(cursor_position, cx)
5982 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5983 {
5984 return;
5985 }
5986
5987 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5988 let mut write_ranges = Vec::new();
5989 let mut read_ranges = Vec::new();
5990 for highlight in highlights {
5991 for (excerpt_id, excerpt_range) in
5992 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5993 {
5994 let start = highlight
5995 .range
5996 .start
5997 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5998 let end = highlight
5999 .range
6000 .end
6001 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6002 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6003 continue;
6004 }
6005
6006 let range = Anchor {
6007 buffer_id,
6008 excerpt_id,
6009 text_anchor: start,
6010 diff_base_anchor: None,
6011 }..Anchor {
6012 buffer_id,
6013 excerpt_id,
6014 text_anchor: end,
6015 diff_base_anchor: None,
6016 };
6017 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6018 write_ranges.push(range);
6019 } else {
6020 read_ranges.push(range);
6021 }
6022 }
6023 }
6024
6025 this.highlight_background::<DocumentHighlightRead>(
6026 &read_ranges,
6027 |theme| theme.editor_document_highlight_read_background,
6028 cx,
6029 );
6030 this.highlight_background::<DocumentHighlightWrite>(
6031 &write_ranges,
6032 |theme| theme.editor_document_highlight_write_background,
6033 cx,
6034 );
6035 cx.notify();
6036 })
6037 .log_err();
6038 }
6039 }));
6040 None
6041 }
6042
6043 fn prepare_highlight_query_from_selection(
6044 &mut self,
6045 cx: &mut Context<Editor>,
6046 ) -> Option<(String, Range<Anchor>)> {
6047 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6048 return None;
6049 }
6050 if !EditorSettings::get_global(cx).selection_highlight {
6051 return None;
6052 }
6053 if self.selections.count() != 1 || self.selections.line_mode {
6054 return None;
6055 }
6056 let selection = self.selections.newest::<Point>(cx);
6057 if selection.is_empty() || selection.start.row != selection.end.row {
6058 return None;
6059 }
6060 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6061 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6062 let query = multi_buffer_snapshot
6063 .text_for_range(selection_anchor_range.clone())
6064 .collect::<String>();
6065 if query.trim().is_empty() {
6066 return None;
6067 }
6068 Some((query, selection_anchor_range))
6069 }
6070
6071 fn update_selection_occurrence_highlights(
6072 &mut self,
6073 query_text: String,
6074 query_range: Range<Anchor>,
6075 multi_buffer_range_to_query: Range<Point>,
6076 use_debounce: bool,
6077 window: &mut Window,
6078 cx: &mut Context<Editor>,
6079 ) -> Task<()> {
6080 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6081 cx.spawn_in(window, async move |editor, cx| {
6082 if use_debounce {
6083 cx.background_executor()
6084 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6085 .await;
6086 }
6087 let match_task = cx.background_spawn(async move {
6088 let buffer_ranges = multi_buffer_snapshot
6089 .range_to_buffer_ranges(multi_buffer_range_to_query)
6090 .into_iter()
6091 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6092 let mut match_ranges = Vec::new();
6093 let Ok(regex) = project::search::SearchQuery::text(
6094 query_text.clone(),
6095 false,
6096 false,
6097 false,
6098 Default::default(),
6099 Default::default(),
6100 false,
6101 None,
6102 ) else {
6103 return Vec::default();
6104 };
6105 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6106 match_ranges.extend(
6107 regex
6108 .search(&buffer_snapshot, Some(search_range.clone()))
6109 .await
6110 .into_iter()
6111 .filter_map(|match_range| {
6112 let match_start = buffer_snapshot
6113 .anchor_after(search_range.start + match_range.start);
6114 let match_end = buffer_snapshot
6115 .anchor_before(search_range.start + match_range.end);
6116 let match_anchor_range = Anchor::range_in_buffer(
6117 excerpt_id,
6118 buffer_snapshot.remote_id(),
6119 match_start..match_end,
6120 );
6121 (match_anchor_range != query_range).then_some(match_anchor_range)
6122 }),
6123 );
6124 }
6125 match_ranges
6126 });
6127 let match_ranges = match_task.await;
6128 editor
6129 .update_in(cx, |editor, _, cx| {
6130 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6131 if !match_ranges.is_empty() {
6132 editor.highlight_background::<SelectedTextHighlight>(
6133 &match_ranges,
6134 |theme| theme.editor_document_highlight_bracket_background,
6135 cx,
6136 )
6137 }
6138 })
6139 .log_err();
6140 })
6141 }
6142
6143 fn refresh_selected_text_highlights(
6144 &mut self,
6145 on_buffer_edit: bool,
6146 window: &mut Window,
6147 cx: &mut Context<Editor>,
6148 ) {
6149 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6150 else {
6151 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6152 self.quick_selection_highlight_task.take();
6153 self.debounced_selection_highlight_task.take();
6154 return;
6155 };
6156 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6157 if on_buffer_edit
6158 || self
6159 .quick_selection_highlight_task
6160 .as_ref()
6161 .map_or(true, |(prev_anchor_range, _)| {
6162 prev_anchor_range != &query_range
6163 })
6164 {
6165 let multi_buffer_visible_start = self
6166 .scroll_manager
6167 .anchor()
6168 .anchor
6169 .to_point(&multi_buffer_snapshot);
6170 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6171 multi_buffer_visible_start
6172 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6173 Bias::Left,
6174 );
6175 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6176 self.quick_selection_highlight_task = Some((
6177 query_range.clone(),
6178 self.update_selection_occurrence_highlights(
6179 query_text.clone(),
6180 query_range.clone(),
6181 multi_buffer_visible_range,
6182 false,
6183 window,
6184 cx,
6185 ),
6186 ));
6187 }
6188 if on_buffer_edit
6189 || self
6190 .debounced_selection_highlight_task
6191 .as_ref()
6192 .map_or(true, |(prev_anchor_range, _)| {
6193 prev_anchor_range != &query_range
6194 })
6195 {
6196 let multi_buffer_start = multi_buffer_snapshot
6197 .anchor_before(0)
6198 .to_point(&multi_buffer_snapshot);
6199 let multi_buffer_end = multi_buffer_snapshot
6200 .anchor_after(multi_buffer_snapshot.len())
6201 .to_point(&multi_buffer_snapshot);
6202 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6203 self.debounced_selection_highlight_task = Some((
6204 query_range.clone(),
6205 self.update_selection_occurrence_highlights(
6206 query_text,
6207 query_range,
6208 multi_buffer_full_range,
6209 true,
6210 window,
6211 cx,
6212 ),
6213 ));
6214 }
6215 }
6216
6217 pub fn refresh_inline_completion(
6218 &mut self,
6219 debounce: bool,
6220 user_requested: bool,
6221 window: &mut Window,
6222 cx: &mut Context<Self>,
6223 ) -> Option<()> {
6224 let provider = self.edit_prediction_provider()?;
6225 let cursor = self.selections.newest_anchor().head();
6226 let (buffer, cursor_buffer_position) =
6227 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6228
6229 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6230 self.discard_inline_completion(false, cx);
6231 return None;
6232 }
6233
6234 if !user_requested
6235 && (!self.should_show_edit_predictions()
6236 || !self.is_focused(window)
6237 || buffer.read(cx).is_empty())
6238 {
6239 self.discard_inline_completion(false, cx);
6240 return None;
6241 }
6242
6243 self.update_visible_inline_completion(window, cx);
6244 provider.refresh(
6245 self.project.clone(),
6246 buffer,
6247 cursor_buffer_position,
6248 debounce,
6249 cx,
6250 );
6251 Some(())
6252 }
6253
6254 fn show_edit_predictions_in_menu(&self) -> bool {
6255 match self.edit_prediction_settings {
6256 EditPredictionSettings::Disabled => false,
6257 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6258 }
6259 }
6260
6261 pub fn edit_predictions_enabled(&self) -> bool {
6262 match self.edit_prediction_settings {
6263 EditPredictionSettings::Disabled => false,
6264 EditPredictionSettings::Enabled { .. } => true,
6265 }
6266 }
6267
6268 fn edit_prediction_requires_modifier(&self) -> bool {
6269 match self.edit_prediction_settings {
6270 EditPredictionSettings::Disabled => false,
6271 EditPredictionSettings::Enabled {
6272 preview_requires_modifier,
6273 ..
6274 } => preview_requires_modifier,
6275 }
6276 }
6277
6278 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6279 if self.edit_prediction_provider.is_none() {
6280 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6281 } else {
6282 let selection = self.selections.newest_anchor();
6283 let cursor = selection.head();
6284
6285 if let Some((buffer, cursor_buffer_position)) =
6286 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6287 {
6288 self.edit_prediction_settings =
6289 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6290 }
6291 }
6292 }
6293
6294 fn edit_prediction_settings_at_position(
6295 &self,
6296 buffer: &Entity<Buffer>,
6297 buffer_position: language::Anchor,
6298 cx: &App,
6299 ) -> EditPredictionSettings {
6300 if !self.mode.is_full()
6301 || !self.show_inline_completions_override.unwrap_or(true)
6302 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6303 {
6304 return EditPredictionSettings::Disabled;
6305 }
6306
6307 let buffer = buffer.read(cx);
6308
6309 let file = buffer.file();
6310
6311 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6312 return EditPredictionSettings::Disabled;
6313 };
6314
6315 let by_provider = matches!(
6316 self.menu_inline_completions_policy,
6317 MenuInlineCompletionsPolicy::ByProvider
6318 );
6319
6320 let show_in_menu = by_provider
6321 && self
6322 .edit_prediction_provider
6323 .as_ref()
6324 .map_or(false, |provider| {
6325 provider.provider.show_completions_in_menu()
6326 });
6327
6328 let preview_requires_modifier =
6329 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6330
6331 EditPredictionSettings::Enabled {
6332 show_in_menu,
6333 preview_requires_modifier,
6334 }
6335 }
6336
6337 fn should_show_edit_predictions(&self) -> bool {
6338 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6339 }
6340
6341 pub fn edit_prediction_preview_is_active(&self) -> bool {
6342 matches!(
6343 self.edit_prediction_preview,
6344 EditPredictionPreview::Active { .. }
6345 )
6346 }
6347
6348 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6349 let cursor = self.selections.newest_anchor().head();
6350 if let Some((buffer, cursor_position)) =
6351 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6352 {
6353 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6354 } else {
6355 false
6356 }
6357 }
6358
6359 pub fn supports_minimap(&self, cx: &App) -> bool {
6360 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6361 }
6362
6363 fn edit_predictions_enabled_in_buffer(
6364 &self,
6365 buffer: &Entity<Buffer>,
6366 buffer_position: language::Anchor,
6367 cx: &App,
6368 ) -> bool {
6369 maybe!({
6370 if self.read_only(cx) {
6371 return Some(false);
6372 }
6373 let provider = self.edit_prediction_provider()?;
6374 if !provider.is_enabled(&buffer, buffer_position, cx) {
6375 return Some(false);
6376 }
6377 let buffer = buffer.read(cx);
6378 let Some(file) = buffer.file() else {
6379 return Some(true);
6380 };
6381 let settings = all_language_settings(Some(file), cx);
6382 Some(settings.edit_predictions_enabled_for_file(file, cx))
6383 })
6384 .unwrap_or(false)
6385 }
6386
6387 fn cycle_inline_completion(
6388 &mut self,
6389 direction: Direction,
6390 window: &mut Window,
6391 cx: &mut Context<Self>,
6392 ) -> Option<()> {
6393 let provider = self.edit_prediction_provider()?;
6394 let cursor = self.selections.newest_anchor().head();
6395 let (buffer, cursor_buffer_position) =
6396 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6397 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6398 return None;
6399 }
6400
6401 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6402 self.update_visible_inline_completion(window, cx);
6403
6404 Some(())
6405 }
6406
6407 pub fn show_inline_completion(
6408 &mut self,
6409 _: &ShowEditPrediction,
6410 window: &mut Window,
6411 cx: &mut Context<Self>,
6412 ) {
6413 if !self.has_active_inline_completion() {
6414 self.refresh_inline_completion(false, true, window, cx);
6415 return;
6416 }
6417
6418 self.update_visible_inline_completion(window, cx);
6419 }
6420
6421 pub fn display_cursor_names(
6422 &mut self,
6423 _: &DisplayCursorNames,
6424 window: &mut Window,
6425 cx: &mut Context<Self>,
6426 ) {
6427 self.show_cursor_names(window, cx);
6428 }
6429
6430 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6431 self.show_cursor_names = true;
6432 cx.notify();
6433 cx.spawn_in(window, async move |this, cx| {
6434 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6435 this.update(cx, |this, cx| {
6436 this.show_cursor_names = false;
6437 cx.notify()
6438 })
6439 .ok()
6440 })
6441 .detach();
6442 }
6443
6444 pub fn next_edit_prediction(
6445 &mut self,
6446 _: &NextEditPrediction,
6447 window: &mut Window,
6448 cx: &mut Context<Self>,
6449 ) {
6450 if self.has_active_inline_completion() {
6451 self.cycle_inline_completion(Direction::Next, window, cx);
6452 } else {
6453 let is_copilot_disabled = self
6454 .refresh_inline_completion(false, true, window, cx)
6455 .is_none();
6456 if is_copilot_disabled {
6457 cx.propagate();
6458 }
6459 }
6460 }
6461
6462 pub fn previous_edit_prediction(
6463 &mut self,
6464 _: &PreviousEditPrediction,
6465 window: &mut Window,
6466 cx: &mut Context<Self>,
6467 ) {
6468 if self.has_active_inline_completion() {
6469 self.cycle_inline_completion(Direction::Prev, window, cx);
6470 } else {
6471 let is_copilot_disabled = self
6472 .refresh_inline_completion(false, true, window, cx)
6473 .is_none();
6474 if is_copilot_disabled {
6475 cx.propagate();
6476 }
6477 }
6478 }
6479
6480 pub fn accept_edit_prediction(
6481 &mut self,
6482 _: &AcceptEditPrediction,
6483 window: &mut Window,
6484 cx: &mut Context<Self>,
6485 ) {
6486 if self.show_edit_predictions_in_menu() {
6487 self.hide_context_menu(window, cx);
6488 }
6489
6490 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6491 return;
6492 };
6493
6494 self.report_inline_completion_event(
6495 active_inline_completion.completion_id.clone(),
6496 true,
6497 cx,
6498 );
6499
6500 match &active_inline_completion.completion {
6501 InlineCompletion::Move { target, .. } => {
6502 let target = *target;
6503
6504 if let Some(position_map) = &self.last_position_map {
6505 if position_map
6506 .visible_row_range
6507 .contains(&target.to_display_point(&position_map.snapshot).row())
6508 || !self.edit_prediction_requires_modifier()
6509 {
6510 self.unfold_ranges(&[target..target], true, false, cx);
6511 // Note that this is also done in vim's handler of the Tab action.
6512 self.change_selections(
6513 Some(Autoscroll::newest()),
6514 window,
6515 cx,
6516 |selections| {
6517 selections.select_anchor_ranges([target..target]);
6518 },
6519 );
6520 self.clear_row_highlights::<EditPredictionPreview>();
6521
6522 self.edit_prediction_preview
6523 .set_previous_scroll_position(None);
6524 } else {
6525 self.edit_prediction_preview
6526 .set_previous_scroll_position(Some(
6527 position_map.snapshot.scroll_anchor,
6528 ));
6529
6530 self.highlight_rows::<EditPredictionPreview>(
6531 target..target,
6532 cx.theme().colors().editor_highlighted_line_background,
6533 RowHighlightOptions {
6534 autoscroll: true,
6535 ..Default::default()
6536 },
6537 cx,
6538 );
6539 self.request_autoscroll(Autoscroll::fit(), cx);
6540 }
6541 }
6542 }
6543 InlineCompletion::Edit { edits, .. } => {
6544 if let Some(provider) = self.edit_prediction_provider() {
6545 provider.accept(cx);
6546 }
6547
6548 // Store the transaction ID and selections before applying the edit
6549 let transaction_id_prev =
6550 self.buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
6551
6552 let snapshot = self.buffer.read(cx).snapshot(cx);
6553 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6554
6555 self.buffer.update(cx, |buffer, cx| {
6556 buffer.edit(edits.iter().cloned(), None, cx)
6557 });
6558
6559 self.change_selections(None, window, cx, |s| {
6560 s.select_anchor_ranges([last_edit_end..last_edit_end]);
6561 });
6562
6563 let selections = self.selections.disjoint_anchors();
6564 if let Some(transaction_id_now) =
6565 self.buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
6566 {
6567 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
6568 if has_new_transaction {
6569 self.selection_history
6570 .insert_transaction(transaction_id_now, selections);
6571 }
6572 }
6573
6574 self.update_visible_inline_completion(window, cx);
6575 if self.active_inline_completion.is_none() {
6576 self.refresh_inline_completion(true, true, window, cx);
6577 }
6578
6579 cx.notify();
6580 }
6581 }
6582
6583 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6584 }
6585
6586 pub fn accept_partial_inline_completion(
6587 &mut self,
6588 _: &AcceptPartialEditPrediction,
6589 window: &mut Window,
6590 cx: &mut Context<Self>,
6591 ) {
6592 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6593 return;
6594 };
6595 if self.selections.count() != 1 {
6596 return;
6597 }
6598
6599 self.report_inline_completion_event(
6600 active_inline_completion.completion_id.clone(),
6601 true,
6602 cx,
6603 );
6604
6605 match &active_inline_completion.completion {
6606 InlineCompletion::Move { target, .. } => {
6607 let target = *target;
6608 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6609 selections.select_anchor_ranges([target..target]);
6610 });
6611 }
6612 InlineCompletion::Edit { edits, .. } => {
6613 // Find an insertion that starts at the cursor position.
6614 let snapshot = self.buffer.read(cx).snapshot(cx);
6615 let cursor_offset = self.selections.newest::<usize>(cx).head();
6616 let insertion = edits.iter().find_map(|(range, text)| {
6617 let range = range.to_offset(&snapshot);
6618 if range.is_empty() && range.start == cursor_offset {
6619 Some(text)
6620 } else {
6621 None
6622 }
6623 });
6624
6625 if let Some(text) = insertion {
6626 let mut partial_completion = text
6627 .chars()
6628 .by_ref()
6629 .take_while(|c| c.is_alphabetic())
6630 .collect::<String>();
6631 if partial_completion.is_empty() {
6632 partial_completion = text
6633 .chars()
6634 .by_ref()
6635 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6636 .collect::<String>();
6637 }
6638
6639 cx.emit(EditorEvent::InputHandled {
6640 utf16_range_to_replace: None,
6641 text: partial_completion.clone().into(),
6642 });
6643
6644 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6645
6646 self.refresh_inline_completion(true, true, window, cx);
6647 cx.notify();
6648 } else {
6649 self.accept_edit_prediction(&Default::default(), window, cx);
6650 }
6651 }
6652 }
6653 }
6654
6655 fn discard_inline_completion(
6656 &mut self,
6657 should_report_inline_completion_event: bool,
6658 cx: &mut Context<Self>,
6659 ) -> bool {
6660 if should_report_inline_completion_event {
6661 let completion_id = self
6662 .active_inline_completion
6663 .as_ref()
6664 .and_then(|active_completion| active_completion.completion_id.clone());
6665
6666 self.report_inline_completion_event(completion_id, false, cx);
6667 }
6668
6669 if let Some(provider) = self.edit_prediction_provider() {
6670 provider.discard(cx);
6671 }
6672
6673 self.take_active_inline_completion(cx)
6674 }
6675
6676 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6677 let Some(provider) = self.edit_prediction_provider() else {
6678 return;
6679 };
6680
6681 let Some((_, buffer, _)) = self
6682 .buffer
6683 .read(cx)
6684 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6685 else {
6686 return;
6687 };
6688
6689 let extension = buffer
6690 .read(cx)
6691 .file()
6692 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6693
6694 let event_type = match accepted {
6695 true => "Edit Prediction Accepted",
6696 false => "Edit Prediction Discarded",
6697 };
6698 telemetry::event!(
6699 event_type,
6700 provider = provider.name(),
6701 prediction_id = id,
6702 suggestion_accepted = accepted,
6703 file_extension = extension,
6704 );
6705 }
6706
6707 pub fn has_active_inline_completion(&self) -> bool {
6708 self.active_inline_completion.is_some()
6709 }
6710
6711 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6712 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6713 return false;
6714 };
6715
6716 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6717 self.clear_highlights::<InlineCompletionHighlight>(cx);
6718 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6719 true
6720 }
6721
6722 /// Returns true when we're displaying the edit prediction popover below the cursor
6723 /// like we are not previewing and the LSP autocomplete menu is visible
6724 /// or we are in `when_holding_modifier` mode.
6725 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6726 if self.edit_prediction_preview_is_active()
6727 || !self.show_edit_predictions_in_menu()
6728 || !self.edit_predictions_enabled()
6729 {
6730 return false;
6731 }
6732
6733 if self.has_visible_completions_menu() {
6734 return true;
6735 }
6736
6737 has_completion && self.edit_prediction_requires_modifier()
6738 }
6739
6740 fn handle_modifiers_changed(
6741 &mut self,
6742 modifiers: Modifiers,
6743 position_map: &PositionMap,
6744 window: &mut Window,
6745 cx: &mut Context<Self>,
6746 ) {
6747 if self.show_edit_predictions_in_menu() {
6748 self.update_edit_prediction_preview(&modifiers, window, cx);
6749 }
6750
6751 self.update_selection_mode(&modifiers, position_map, window, cx);
6752
6753 let mouse_position = window.mouse_position();
6754 if !position_map.text_hitbox.is_hovered(window) {
6755 return;
6756 }
6757
6758 self.update_hovered_link(
6759 position_map.point_for_position(mouse_position),
6760 &position_map.snapshot,
6761 modifiers,
6762 window,
6763 cx,
6764 )
6765 }
6766
6767 fn update_selection_mode(
6768 &mut self,
6769 modifiers: &Modifiers,
6770 position_map: &PositionMap,
6771 window: &mut Window,
6772 cx: &mut Context<Self>,
6773 ) {
6774 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6775 return;
6776 }
6777
6778 let mouse_position = window.mouse_position();
6779 let point_for_position = position_map.point_for_position(mouse_position);
6780 let position = point_for_position.previous_valid;
6781
6782 self.select(
6783 SelectPhase::BeginColumnar {
6784 position,
6785 reset: false,
6786 goal_column: point_for_position.exact_unclipped.column(),
6787 },
6788 window,
6789 cx,
6790 );
6791 }
6792
6793 fn update_edit_prediction_preview(
6794 &mut self,
6795 modifiers: &Modifiers,
6796 window: &mut Window,
6797 cx: &mut Context<Self>,
6798 ) {
6799 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6800 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6801 return;
6802 };
6803
6804 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6805 if matches!(
6806 self.edit_prediction_preview,
6807 EditPredictionPreview::Inactive { .. }
6808 ) {
6809 self.edit_prediction_preview = EditPredictionPreview::Active {
6810 previous_scroll_position: None,
6811 since: Instant::now(),
6812 };
6813
6814 self.update_visible_inline_completion(window, cx);
6815 cx.notify();
6816 }
6817 } else if let EditPredictionPreview::Active {
6818 previous_scroll_position,
6819 since,
6820 } = self.edit_prediction_preview
6821 {
6822 if let (Some(previous_scroll_position), Some(position_map)) =
6823 (previous_scroll_position, self.last_position_map.as_ref())
6824 {
6825 self.set_scroll_position(
6826 previous_scroll_position
6827 .scroll_position(&position_map.snapshot.display_snapshot),
6828 window,
6829 cx,
6830 );
6831 }
6832
6833 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6834 released_too_fast: since.elapsed() < Duration::from_millis(200),
6835 };
6836 self.clear_row_highlights::<EditPredictionPreview>();
6837 self.update_visible_inline_completion(window, cx);
6838 cx.notify();
6839 }
6840 }
6841
6842 fn update_visible_inline_completion(
6843 &mut self,
6844 _window: &mut Window,
6845 cx: &mut Context<Self>,
6846 ) -> Option<()> {
6847 let selection = self.selections.newest_anchor();
6848 let cursor = selection.head();
6849 let multibuffer = self.buffer.read(cx).snapshot(cx);
6850 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6851 let excerpt_id = cursor.excerpt_id;
6852
6853 let show_in_menu = self.show_edit_predictions_in_menu();
6854 let completions_menu_has_precedence = !show_in_menu
6855 && (self.context_menu.borrow().is_some()
6856 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6857
6858 if completions_menu_has_precedence
6859 || !offset_selection.is_empty()
6860 || self
6861 .active_inline_completion
6862 .as_ref()
6863 .map_or(false, |completion| {
6864 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6865 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6866 !invalidation_range.contains(&offset_selection.head())
6867 })
6868 {
6869 self.discard_inline_completion(false, cx);
6870 return None;
6871 }
6872
6873 self.take_active_inline_completion(cx);
6874 let Some(provider) = self.edit_prediction_provider() else {
6875 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6876 return None;
6877 };
6878
6879 let (buffer, cursor_buffer_position) =
6880 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6881
6882 self.edit_prediction_settings =
6883 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6884
6885 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6886
6887 if self.edit_prediction_indent_conflict {
6888 let cursor_point = cursor.to_point(&multibuffer);
6889
6890 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6891
6892 if let Some((_, indent)) = indents.iter().next() {
6893 if indent.len == cursor_point.column {
6894 self.edit_prediction_indent_conflict = false;
6895 }
6896 }
6897 }
6898
6899 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6900 let edits = inline_completion
6901 .edits
6902 .into_iter()
6903 .flat_map(|(range, new_text)| {
6904 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6905 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6906 Some((start..end, new_text))
6907 })
6908 .collect::<Vec<_>>();
6909 if edits.is_empty() {
6910 return None;
6911 }
6912
6913 let first_edit_start = edits.first().unwrap().0.start;
6914 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6915 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6916
6917 let last_edit_end = edits.last().unwrap().0.end;
6918 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6919 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6920
6921 let cursor_row = cursor.to_point(&multibuffer).row;
6922
6923 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6924
6925 let mut inlay_ids = Vec::new();
6926 let invalidation_row_range;
6927 let move_invalidation_row_range = if cursor_row < edit_start_row {
6928 Some(cursor_row..edit_end_row)
6929 } else if cursor_row > edit_end_row {
6930 Some(edit_start_row..cursor_row)
6931 } else {
6932 None
6933 };
6934 let is_move =
6935 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6936 let completion = if is_move {
6937 invalidation_row_range =
6938 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6939 let target = first_edit_start;
6940 InlineCompletion::Move { target, snapshot }
6941 } else {
6942 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6943 && !self.inline_completions_hidden_for_vim_mode;
6944
6945 if show_completions_in_buffer {
6946 if edits
6947 .iter()
6948 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6949 {
6950 let mut inlays = Vec::new();
6951 for (range, new_text) in &edits {
6952 let inlay = Inlay::inline_completion(
6953 post_inc(&mut self.next_inlay_id),
6954 range.start,
6955 new_text.as_str(),
6956 );
6957 inlay_ids.push(inlay.id);
6958 inlays.push(inlay);
6959 }
6960
6961 self.splice_inlays(&[], inlays, cx);
6962 } else {
6963 let background_color = cx.theme().status().deleted_background;
6964 self.highlight_text::<InlineCompletionHighlight>(
6965 edits.iter().map(|(range, _)| range.clone()).collect(),
6966 HighlightStyle {
6967 background_color: Some(background_color),
6968 ..Default::default()
6969 },
6970 cx,
6971 );
6972 }
6973 }
6974
6975 invalidation_row_range = edit_start_row..edit_end_row;
6976
6977 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6978 if provider.show_tab_accept_marker() {
6979 EditDisplayMode::TabAccept
6980 } else {
6981 EditDisplayMode::Inline
6982 }
6983 } else {
6984 EditDisplayMode::DiffPopover
6985 };
6986
6987 InlineCompletion::Edit {
6988 edits,
6989 edit_preview: inline_completion.edit_preview,
6990 display_mode,
6991 snapshot,
6992 }
6993 };
6994
6995 let invalidation_range = multibuffer
6996 .anchor_before(Point::new(invalidation_row_range.start, 0))
6997 ..multibuffer.anchor_after(Point::new(
6998 invalidation_row_range.end,
6999 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7000 ));
7001
7002 self.stale_inline_completion_in_menu = None;
7003 self.active_inline_completion = Some(InlineCompletionState {
7004 inlay_ids,
7005 completion,
7006 completion_id: inline_completion.id,
7007 invalidation_range,
7008 });
7009
7010 cx.notify();
7011
7012 Some(())
7013 }
7014
7015 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7016 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7017 }
7018
7019 fn clear_tasks(&mut self) {
7020 self.tasks.clear()
7021 }
7022
7023 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7024 if self.tasks.insert(key, value).is_some() {
7025 // This case should hopefully be rare, but just in case...
7026 log::error!(
7027 "multiple different run targets found on a single line, only the last target will be rendered"
7028 )
7029 }
7030 }
7031
7032 /// Get all display points of breakpoints that will be rendered within editor
7033 ///
7034 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7035 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7036 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7037 fn active_breakpoints(
7038 &self,
7039 range: Range<DisplayRow>,
7040 window: &mut Window,
7041 cx: &mut Context<Self>,
7042 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7043 let mut breakpoint_display_points = HashMap::default();
7044
7045 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7046 return breakpoint_display_points;
7047 };
7048
7049 let snapshot = self.snapshot(window, cx);
7050
7051 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7052 let Some(project) = self.project.as_ref() else {
7053 return breakpoint_display_points;
7054 };
7055
7056 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7057 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7058
7059 for (buffer_snapshot, range, excerpt_id) in
7060 multi_buffer_snapshot.range_to_buffer_ranges(range)
7061 {
7062 let Some(buffer) = project.read_with(cx, |this, cx| {
7063 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
7064 }) else {
7065 continue;
7066 };
7067 let breakpoints = breakpoint_store.read(cx).breakpoints(
7068 &buffer,
7069 Some(
7070 buffer_snapshot.anchor_before(range.start)
7071 ..buffer_snapshot.anchor_after(range.end),
7072 ),
7073 buffer_snapshot,
7074 cx,
7075 );
7076 for (breakpoint, state) in breakpoints {
7077 let multi_buffer_anchor =
7078 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7079 let position = multi_buffer_anchor
7080 .to_point(&multi_buffer_snapshot)
7081 .to_display_point(&snapshot);
7082
7083 breakpoint_display_points.insert(
7084 position.row(),
7085 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7086 );
7087 }
7088 }
7089
7090 breakpoint_display_points
7091 }
7092
7093 fn breakpoint_context_menu(
7094 &self,
7095 anchor: Anchor,
7096 window: &mut Window,
7097 cx: &mut Context<Self>,
7098 ) -> Entity<ui::ContextMenu> {
7099 let weak_editor = cx.weak_entity();
7100 let focus_handle = self.focus_handle(cx);
7101
7102 let row = self
7103 .buffer
7104 .read(cx)
7105 .snapshot(cx)
7106 .summary_for_anchor::<Point>(&anchor)
7107 .row;
7108
7109 let breakpoint = self
7110 .breakpoint_at_row(row, window, cx)
7111 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7112
7113 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7114 "Edit Log Breakpoint"
7115 } else {
7116 "Set Log Breakpoint"
7117 };
7118
7119 let condition_breakpoint_msg = if breakpoint
7120 .as_ref()
7121 .is_some_and(|bp| bp.1.condition.is_some())
7122 {
7123 "Edit Condition Breakpoint"
7124 } else {
7125 "Set Condition Breakpoint"
7126 };
7127
7128 let hit_condition_breakpoint_msg = if breakpoint
7129 .as_ref()
7130 .is_some_and(|bp| bp.1.hit_condition.is_some())
7131 {
7132 "Edit Hit Condition Breakpoint"
7133 } else {
7134 "Set Hit Condition Breakpoint"
7135 };
7136
7137 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7138 "Unset Breakpoint"
7139 } else {
7140 "Set Breakpoint"
7141 };
7142
7143 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
7144 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
7145
7146 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7147 BreakpointState::Enabled => Some("Disable"),
7148 BreakpointState::Disabled => Some("Enable"),
7149 });
7150
7151 let (anchor, breakpoint) =
7152 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7153
7154 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7155 menu.on_blur_subscription(Subscription::new(|| {}))
7156 .context(focus_handle)
7157 .when(run_to_cursor, |this| {
7158 let weak_editor = weak_editor.clone();
7159 this.entry("Run to cursor", None, move |window, cx| {
7160 weak_editor
7161 .update(cx, |editor, cx| {
7162 editor.change_selections(None, window, cx, |s| {
7163 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7164 });
7165 })
7166 .ok();
7167
7168 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
7169 })
7170 .separator()
7171 })
7172 .when_some(toggle_state_msg, |this, msg| {
7173 this.entry(msg, None, {
7174 let weak_editor = weak_editor.clone();
7175 let breakpoint = breakpoint.clone();
7176 move |_window, cx| {
7177 weak_editor
7178 .update(cx, |this, cx| {
7179 this.edit_breakpoint_at_anchor(
7180 anchor,
7181 breakpoint.as_ref().clone(),
7182 BreakpointEditAction::InvertState,
7183 cx,
7184 );
7185 })
7186 .log_err();
7187 }
7188 })
7189 })
7190 .entry(set_breakpoint_msg, None, {
7191 let weak_editor = weak_editor.clone();
7192 let breakpoint = breakpoint.clone();
7193 move |_window, cx| {
7194 weak_editor
7195 .update(cx, |this, cx| {
7196 this.edit_breakpoint_at_anchor(
7197 anchor,
7198 breakpoint.as_ref().clone(),
7199 BreakpointEditAction::Toggle,
7200 cx,
7201 );
7202 })
7203 .log_err();
7204 }
7205 })
7206 .entry(log_breakpoint_msg, None, {
7207 let breakpoint = breakpoint.clone();
7208 let weak_editor = weak_editor.clone();
7209 move |window, cx| {
7210 weak_editor
7211 .update(cx, |this, cx| {
7212 this.add_edit_breakpoint_block(
7213 anchor,
7214 breakpoint.as_ref(),
7215 BreakpointPromptEditAction::Log,
7216 window,
7217 cx,
7218 );
7219 })
7220 .log_err();
7221 }
7222 })
7223 .entry(condition_breakpoint_msg, None, {
7224 let breakpoint = breakpoint.clone();
7225 let weak_editor = weak_editor.clone();
7226 move |window, cx| {
7227 weak_editor
7228 .update(cx, |this, cx| {
7229 this.add_edit_breakpoint_block(
7230 anchor,
7231 breakpoint.as_ref(),
7232 BreakpointPromptEditAction::Condition,
7233 window,
7234 cx,
7235 );
7236 })
7237 .log_err();
7238 }
7239 })
7240 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7241 weak_editor
7242 .update(cx, |this, cx| {
7243 this.add_edit_breakpoint_block(
7244 anchor,
7245 breakpoint.as_ref(),
7246 BreakpointPromptEditAction::HitCondition,
7247 window,
7248 cx,
7249 );
7250 })
7251 .log_err();
7252 })
7253 })
7254 }
7255
7256 fn render_breakpoint(
7257 &self,
7258 position: Anchor,
7259 row: DisplayRow,
7260 breakpoint: &Breakpoint,
7261 state: Option<BreakpointSessionState>,
7262 cx: &mut Context<Self>,
7263 ) -> IconButton {
7264 let is_rejected = state.is_some_and(|s| !s.verified);
7265 // Is it a breakpoint that shows up when hovering over gutter?
7266 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7267 (false, false),
7268 |PhantomBreakpointIndicator {
7269 is_active,
7270 display_row,
7271 collides_with_existing_breakpoint,
7272 }| {
7273 (
7274 is_active && display_row == row,
7275 collides_with_existing_breakpoint,
7276 )
7277 },
7278 );
7279
7280 let (color, icon) = {
7281 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7282 (false, false) => ui::IconName::DebugBreakpoint,
7283 (true, false) => ui::IconName::DebugLogBreakpoint,
7284 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7285 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7286 };
7287
7288 let color = if is_phantom {
7289 Color::Hint
7290 } else if is_rejected {
7291 Color::Disabled
7292 } else {
7293 Color::Debugger
7294 };
7295
7296 (color, icon)
7297 };
7298
7299 let breakpoint = Arc::from(breakpoint.clone());
7300
7301 let alt_as_text = gpui::Keystroke {
7302 modifiers: Modifiers::secondary_key(),
7303 ..Default::default()
7304 };
7305 let primary_action_text = if breakpoint.is_disabled() {
7306 "Enable breakpoint"
7307 } else if is_phantom && !collides_with_existing {
7308 "Set breakpoint"
7309 } else {
7310 "Unset breakpoint"
7311 };
7312 let focus_handle = self.focus_handle.clone();
7313
7314 let meta = if is_rejected {
7315 SharedString::from("No executable code is associated with this line.")
7316 } else if collides_with_existing && !breakpoint.is_disabled() {
7317 SharedString::from(format!(
7318 "{alt_as_text}-click to disable,\nright-click for more options."
7319 ))
7320 } else {
7321 SharedString::from("Right-click for more options.")
7322 };
7323 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7324 .icon_size(IconSize::XSmall)
7325 .size(ui::ButtonSize::None)
7326 .when(is_rejected, |this| {
7327 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7328 })
7329 .icon_color(color)
7330 .style(ButtonStyle::Transparent)
7331 .on_click(cx.listener({
7332 let breakpoint = breakpoint.clone();
7333
7334 move |editor, event: &ClickEvent, window, cx| {
7335 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7336 BreakpointEditAction::InvertState
7337 } else {
7338 BreakpointEditAction::Toggle
7339 };
7340
7341 window.focus(&editor.focus_handle(cx));
7342 editor.edit_breakpoint_at_anchor(
7343 position,
7344 breakpoint.as_ref().clone(),
7345 edit_action,
7346 cx,
7347 );
7348 }
7349 }))
7350 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7351 editor.set_breakpoint_context_menu(
7352 row,
7353 Some(position),
7354 event.down.position,
7355 window,
7356 cx,
7357 );
7358 }))
7359 .tooltip(move |window, cx| {
7360 Tooltip::with_meta_in(
7361 primary_action_text,
7362 Some(&ToggleBreakpoint),
7363 meta.clone(),
7364 &focus_handle,
7365 window,
7366 cx,
7367 )
7368 })
7369 }
7370
7371 fn build_tasks_context(
7372 project: &Entity<Project>,
7373 buffer: &Entity<Buffer>,
7374 buffer_row: u32,
7375 tasks: &Arc<RunnableTasks>,
7376 cx: &mut Context<Self>,
7377 ) -> Task<Option<task::TaskContext>> {
7378 let position = Point::new(buffer_row, tasks.column);
7379 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7380 let location = Location {
7381 buffer: buffer.clone(),
7382 range: range_start..range_start,
7383 };
7384 // Fill in the environmental variables from the tree-sitter captures
7385 let mut captured_task_variables = TaskVariables::default();
7386 for (capture_name, value) in tasks.extra_variables.clone() {
7387 captured_task_variables.insert(
7388 task::VariableName::Custom(capture_name.into()),
7389 value.clone(),
7390 );
7391 }
7392 project.update(cx, |project, cx| {
7393 project.task_store().update(cx, |task_store, cx| {
7394 task_store.task_context_for_location(captured_task_variables, location, cx)
7395 })
7396 })
7397 }
7398
7399 pub fn spawn_nearest_task(
7400 &mut self,
7401 action: &SpawnNearestTask,
7402 window: &mut Window,
7403 cx: &mut Context<Self>,
7404 ) {
7405 let Some((workspace, _)) = self.workspace.clone() else {
7406 return;
7407 };
7408 let Some(project) = self.project.clone() else {
7409 return;
7410 };
7411
7412 // Try to find a closest, enclosing node using tree-sitter that has a
7413 // task
7414 let Some((buffer, buffer_row, tasks)) = self
7415 .find_enclosing_node_task(cx)
7416 // Or find the task that's closest in row-distance.
7417 .or_else(|| self.find_closest_task(cx))
7418 else {
7419 return;
7420 };
7421
7422 let reveal_strategy = action.reveal;
7423 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7424 cx.spawn_in(window, async move |_, cx| {
7425 let context = task_context.await?;
7426 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7427
7428 let resolved = &mut resolved_task.resolved;
7429 resolved.reveal = reveal_strategy;
7430
7431 workspace
7432 .update_in(cx, |workspace, window, cx| {
7433 workspace.schedule_resolved_task(
7434 task_source_kind,
7435 resolved_task,
7436 false,
7437 window,
7438 cx,
7439 );
7440 })
7441 .ok()
7442 })
7443 .detach();
7444 }
7445
7446 fn find_closest_task(
7447 &mut self,
7448 cx: &mut Context<Self>,
7449 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7450 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7451
7452 let ((buffer_id, row), tasks) = self
7453 .tasks
7454 .iter()
7455 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7456
7457 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7458 let tasks = Arc::new(tasks.to_owned());
7459 Some((buffer, *row, tasks))
7460 }
7461
7462 fn find_enclosing_node_task(
7463 &mut self,
7464 cx: &mut Context<Self>,
7465 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7466 let snapshot = self.buffer.read(cx).snapshot(cx);
7467 let offset = self.selections.newest::<usize>(cx).head();
7468 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7469 let buffer_id = excerpt.buffer().remote_id();
7470
7471 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7472 let mut cursor = layer.node().walk();
7473
7474 while cursor.goto_first_child_for_byte(offset).is_some() {
7475 if cursor.node().end_byte() == offset {
7476 cursor.goto_next_sibling();
7477 }
7478 }
7479
7480 // Ascend to the smallest ancestor that contains the range and has a task.
7481 loop {
7482 let node = cursor.node();
7483 let node_range = node.byte_range();
7484 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7485
7486 // Check if this node contains our offset
7487 if node_range.start <= offset && node_range.end >= offset {
7488 // If it contains offset, check for task
7489 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7490 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7491 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7492 }
7493 }
7494
7495 if !cursor.goto_parent() {
7496 break;
7497 }
7498 }
7499 None
7500 }
7501
7502 fn render_run_indicator(
7503 &self,
7504 _style: &EditorStyle,
7505 is_active: bool,
7506 row: DisplayRow,
7507 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7508 cx: &mut Context<Self>,
7509 ) -> IconButton {
7510 let color = Color::Muted;
7511 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7512
7513 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7514 .shape(ui::IconButtonShape::Square)
7515 .icon_size(IconSize::XSmall)
7516 .icon_color(color)
7517 .toggle_state(is_active)
7518 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7519 let quick_launch = e.down.button == MouseButton::Left;
7520 window.focus(&editor.focus_handle(cx));
7521 editor.toggle_code_actions(
7522 &ToggleCodeActions {
7523 deployed_from: Some(CodeActionSource::Indicator(row)),
7524 quick_launch,
7525 },
7526 window,
7527 cx,
7528 );
7529 }))
7530 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7531 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7532 }))
7533 }
7534
7535 pub fn context_menu_visible(&self) -> bool {
7536 !self.edit_prediction_preview_is_active()
7537 && self
7538 .context_menu
7539 .borrow()
7540 .as_ref()
7541 .map_or(false, |menu| menu.visible())
7542 }
7543
7544 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7545 self.context_menu
7546 .borrow()
7547 .as_ref()
7548 .map(|menu| menu.origin())
7549 }
7550
7551 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7552 self.context_menu_options = Some(options);
7553 }
7554
7555 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7556 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7557
7558 fn render_edit_prediction_popover(
7559 &mut self,
7560 text_bounds: &Bounds<Pixels>,
7561 content_origin: gpui::Point<Pixels>,
7562 right_margin: Pixels,
7563 editor_snapshot: &EditorSnapshot,
7564 visible_row_range: Range<DisplayRow>,
7565 scroll_top: f32,
7566 scroll_bottom: f32,
7567 line_layouts: &[LineWithInvisibles],
7568 line_height: Pixels,
7569 scroll_pixel_position: gpui::Point<Pixels>,
7570 newest_selection_head: Option<DisplayPoint>,
7571 editor_width: Pixels,
7572 style: &EditorStyle,
7573 window: &mut Window,
7574 cx: &mut App,
7575 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7576 if self.mode().is_minimap() {
7577 return None;
7578 }
7579 let active_inline_completion = self.active_inline_completion.as_ref()?;
7580
7581 if self.edit_prediction_visible_in_cursor_popover(true) {
7582 return None;
7583 }
7584
7585 match &active_inline_completion.completion {
7586 InlineCompletion::Move { target, .. } => {
7587 let target_display_point = target.to_display_point(editor_snapshot);
7588
7589 if self.edit_prediction_requires_modifier() {
7590 if !self.edit_prediction_preview_is_active() {
7591 return None;
7592 }
7593
7594 self.render_edit_prediction_modifier_jump_popover(
7595 text_bounds,
7596 content_origin,
7597 visible_row_range,
7598 line_layouts,
7599 line_height,
7600 scroll_pixel_position,
7601 newest_selection_head,
7602 target_display_point,
7603 window,
7604 cx,
7605 )
7606 } else {
7607 self.render_edit_prediction_eager_jump_popover(
7608 text_bounds,
7609 content_origin,
7610 editor_snapshot,
7611 visible_row_range,
7612 scroll_top,
7613 scroll_bottom,
7614 line_height,
7615 scroll_pixel_position,
7616 target_display_point,
7617 editor_width,
7618 window,
7619 cx,
7620 )
7621 }
7622 }
7623 InlineCompletion::Edit {
7624 display_mode: EditDisplayMode::Inline,
7625 ..
7626 } => None,
7627 InlineCompletion::Edit {
7628 display_mode: EditDisplayMode::TabAccept,
7629 edits,
7630 ..
7631 } => {
7632 let range = &edits.first()?.0;
7633 let target_display_point = range.end.to_display_point(editor_snapshot);
7634
7635 self.render_edit_prediction_end_of_line_popover(
7636 "Accept",
7637 editor_snapshot,
7638 visible_row_range,
7639 target_display_point,
7640 line_height,
7641 scroll_pixel_position,
7642 content_origin,
7643 editor_width,
7644 window,
7645 cx,
7646 )
7647 }
7648 InlineCompletion::Edit {
7649 edits,
7650 edit_preview,
7651 display_mode: EditDisplayMode::DiffPopover,
7652 snapshot,
7653 } => self.render_edit_prediction_diff_popover(
7654 text_bounds,
7655 content_origin,
7656 right_margin,
7657 editor_snapshot,
7658 visible_row_range,
7659 line_layouts,
7660 line_height,
7661 scroll_pixel_position,
7662 newest_selection_head,
7663 editor_width,
7664 style,
7665 edits,
7666 edit_preview,
7667 snapshot,
7668 window,
7669 cx,
7670 ),
7671 }
7672 }
7673
7674 fn render_edit_prediction_modifier_jump_popover(
7675 &mut self,
7676 text_bounds: &Bounds<Pixels>,
7677 content_origin: gpui::Point<Pixels>,
7678 visible_row_range: Range<DisplayRow>,
7679 line_layouts: &[LineWithInvisibles],
7680 line_height: Pixels,
7681 scroll_pixel_position: gpui::Point<Pixels>,
7682 newest_selection_head: Option<DisplayPoint>,
7683 target_display_point: DisplayPoint,
7684 window: &mut Window,
7685 cx: &mut App,
7686 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7687 let scrolled_content_origin =
7688 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7689
7690 const SCROLL_PADDING_Y: Pixels = px(12.);
7691
7692 if target_display_point.row() < visible_row_range.start {
7693 return self.render_edit_prediction_scroll_popover(
7694 |_| SCROLL_PADDING_Y,
7695 IconName::ArrowUp,
7696 visible_row_range,
7697 line_layouts,
7698 newest_selection_head,
7699 scrolled_content_origin,
7700 window,
7701 cx,
7702 );
7703 } else if target_display_point.row() >= visible_row_range.end {
7704 return self.render_edit_prediction_scroll_popover(
7705 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7706 IconName::ArrowDown,
7707 visible_row_range,
7708 line_layouts,
7709 newest_selection_head,
7710 scrolled_content_origin,
7711 window,
7712 cx,
7713 );
7714 }
7715
7716 const POLE_WIDTH: Pixels = px(2.);
7717
7718 let line_layout =
7719 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7720 let target_column = target_display_point.column() as usize;
7721
7722 let target_x = line_layout.x_for_index(target_column);
7723 let target_y =
7724 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7725
7726 let flag_on_right = target_x < text_bounds.size.width / 2.;
7727
7728 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7729 border_color.l += 0.001;
7730
7731 let mut element = v_flex()
7732 .items_end()
7733 .when(flag_on_right, |el| el.items_start())
7734 .child(if flag_on_right {
7735 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7736 .rounded_bl(px(0.))
7737 .rounded_tl(px(0.))
7738 .border_l_2()
7739 .border_color(border_color)
7740 } else {
7741 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7742 .rounded_br(px(0.))
7743 .rounded_tr(px(0.))
7744 .border_r_2()
7745 .border_color(border_color)
7746 })
7747 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7748 .into_any();
7749
7750 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7751
7752 let mut origin = scrolled_content_origin + point(target_x, target_y)
7753 - point(
7754 if flag_on_right {
7755 POLE_WIDTH
7756 } else {
7757 size.width - POLE_WIDTH
7758 },
7759 size.height - line_height,
7760 );
7761
7762 origin.x = origin.x.max(content_origin.x);
7763
7764 element.prepaint_at(origin, window, cx);
7765
7766 Some((element, origin))
7767 }
7768
7769 fn render_edit_prediction_scroll_popover(
7770 &mut self,
7771 to_y: impl Fn(Size<Pixels>) -> Pixels,
7772 scroll_icon: IconName,
7773 visible_row_range: Range<DisplayRow>,
7774 line_layouts: &[LineWithInvisibles],
7775 newest_selection_head: Option<DisplayPoint>,
7776 scrolled_content_origin: gpui::Point<Pixels>,
7777 window: &mut Window,
7778 cx: &mut App,
7779 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7780 let mut element = self
7781 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7782 .into_any();
7783
7784 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7785
7786 let cursor = newest_selection_head?;
7787 let cursor_row_layout =
7788 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7789 let cursor_column = cursor.column() as usize;
7790
7791 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7792
7793 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7794
7795 element.prepaint_at(origin, window, cx);
7796 Some((element, origin))
7797 }
7798
7799 fn render_edit_prediction_eager_jump_popover(
7800 &mut self,
7801 text_bounds: &Bounds<Pixels>,
7802 content_origin: gpui::Point<Pixels>,
7803 editor_snapshot: &EditorSnapshot,
7804 visible_row_range: Range<DisplayRow>,
7805 scroll_top: f32,
7806 scroll_bottom: f32,
7807 line_height: Pixels,
7808 scroll_pixel_position: gpui::Point<Pixels>,
7809 target_display_point: DisplayPoint,
7810 editor_width: Pixels,
7811 window: &mut Window,
7812 cx: &mut App,
7813 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7814 if target_display_point.row().as_f32() < scroll_top {
7815 let mut element = self
7816 .render_edit_prediction_line_popover(
7817 "Jump to Edit",
7818 Some(IconName::ArrowUp),
7819 window,
7820 cx,
7821 )?
7822 .into_any();
7823
7824 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7825 let offset = point(
7826 (text_bounds.size.width - size.width) / 2.,
7827 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7828 );
7829
7830 let origin = text_bounds.origin + offset;
7831 element.prepaint_at(origin, window, cx);
7832 Some((element, origin))
7833 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7834 let mut element = self
7835 .render_edit_prediction_line_popover(
7836 "Jump to Edit",
7837 Some(IconName::ArrowDown),
7838 window,
7839 cx,
7840 )?
7841 .into_any();
7842
7843 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7844 let offset = point(
7845 (text_bounds.size.width - size.width) / 2.,
7846 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7847 );
7848
7849 let origin = text_bounds.origin + offset;
7850 element.prepaint_at(origin, window, cx);
7851 Some((element, origin))
7852 } else {
7853 self.render_edit_prediction_end_of_line_popover(
7854 "Jump to Edit",
7855 editor_snapshot,
7856 visible_row_range,
7857 target_display_point,
7858 line_height,
7859 scroll_pixel_position,
7860 content_origin,
7861 editor_width,
7862 window,
7863 cx,
7864 )
7865 }
7866 }
7867
7868 fn render_edit_prediction_end_of_line_popover(
7869 self: &mut Editor,
7870 label: &'static str,
7871 editor_snapshot: &EditorSnapshot,
7872 visible_row_range: Range<DisplayRow>,
7873 target_display_point: DisplayPoint,
7874 line_height: Pixels,
7875 scroll_pixel_position: gpui::Point<Pixels>,
7876 content_origin: gpui::Point<Pixels>,
7877 editor_width: Pixels,
7878 window: &mut Window,
7879 cx: &mut App,
7880 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7881 let target_line_end = DisplayPoint::new(
7882 target_display_point.row(),
7883 editor_snapshot.line_len(target_display_point.row()),
7884 );
7885
7886 let mut element = self
7887 .render_edit_prediction_line_popover(label, None, window, cx)?
7888 .into_any();
7889
7890 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7891
7892 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7893
7894 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7895 let mut origin = start_point
7896 + line_origin
7897 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7898 origin.x = origin.x.max(content_origin.x);
7899
7900 let max_x = content_origin.x + editor_width - size.width;
7901
7902 if origin.x > max_x {
7903 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7904
7905 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7906 origin.y += offset;
7907 IconName::ArrowUp
7908 } else {
7909 origin.y -= offset;
7910 IconName::ArrowDown
7911 };
7912
7913 element = self
7914 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7915 .into_any();
7916
7917 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7918
7919 origin.x = content_origin.x + editor_width - size.width - px(2.);
7920 }
7921
7922 element.prepaint_at(origin, window, cx);
7923 Some((element, origin))
7924 }
7925
7926 fn render_edit_prediction_diff_popover(
7927 self: &Editor,
7928 text_bounds: &Bounds<Pixels>,
7929 content_origin: gpui::Point<Pixels>,
7930 right_margin: Pixels,
7931 editor_snapshot: &EditorSnapshot,
7932 visible_row_range: Range<DisplayRow>,
7933 line_layouts: &[LineWithInvisibles],
7934 line_height: Pixels,
7935 scroll_pixel_position: gpui::Point<Pixels>,
7936 newest_selection_head: Option<DisplayPoint>,
7937 editor_width: Pixels,
7938 style: &EditorStyle,
7939 edits: &Vec<(Range<Anchor>, String)>,
7940 edit_preview: &Option<language::EditPreview>,
7941 snapshot: &language::BufferSnapshot,
7942 window: &mut Window,
7943 cx: &mut App,
7944 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7945 let edit_start = edits
7946 .first()
7947 .unwrap()
7948 .0
7949 .start
7950 .to_display_point(editor_snapshot);
7951 let edit_end = edits
7952 .last()
7953 .unwrap()
7954 .0
7955 .end
7956 .to_display_point(editor_snapshot);
7957
7958 let is_visible = visible_row_range.contains(&edit_start.row())
7959 || visible_row_range.contains(&edit_end.row());
7960 if !is_visible {
7961 return None;
7962 }
7963
7964 let highlighted_edits =
7965 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7966
7967 let styled_text = highlighted_edits.to_styled_text(&style.text);
7968 let line_count = highlighted_edits.text.lines().count();
7969
7970 const BORDER_WIDTH: Pixels = px(1.);
7971
7972 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7973 let has_keybind = keybind.is_some();
7974
7975 let mut element = h_flex()
7976 .items_start()
7977 .child(
7978 h_flex()
7979 .bg(cx.theme().colors().editor_background)
7980 .border(BORDER_WIDTH)
7981 .shadow_sm()
7982 .border_color(cx.theme().colors().border)
7983 .rounded_l_lg()
7984 .when(line_count > 1, |el| el.rounded_br_lg())
7985 .pr_1()
7986 .child(styled_text),
7987 )
7988 .child(
7989 h_flex()
7990 .h(line_height + BORDER_WIDTH * 2.)
7991 .px_1p5()
7992 .gap_1()
7993 // Workaround: For some reason, there's a gap if we don't do this
7994 .ml(-BORDER_WIDTH)
7995 .shadow(vec![gpui::BoxShadow {
7996 color: gpui::black().opacity(0.05),
7997 offset: point(px(1.), px(1.)),
7998 blur_radius: px(2.),
7999 spread_radius: px(0.),
8000 }])
8001 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8002 .border(BORDER_WIDTH)
8003 .border_color(cx.theme().colors().border)
8004 .rounded_r_lg()
8005 .id("edit_prediction_diff_popover_keybind")
8006 .when(!has_keybind, |el| {
8007 let status_colors = cx.theme().status();
8008
8009 el.bg(status_colors.error_background)
8010 .border_color(status_colors.error.opacity(0.6))
8011 .child(Icon::new(IconName::Info).color(Color::Error))
8012 .cursor_default()
8013 .hoverable_tooltip(move |_window, cx| {
8014 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8015 })
8016 })
8017 .children(keybind),
8018 )
8019 .into_any();
8020
8021 let longest_row =
8022 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8023 let longest_line_width = if visible_row_range.contains(&longest_row) {
8024 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8025 } else {
8026 layout_line(
8027 longest_row,
8028 editor_snapshot,
8029 style,
8030 editor_width,
8031 |_| false,
8032 window,
8033 cx,
8034 )
8035 .width
8036 };
8037
8038 let viewport_bounds =
8039 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8040 right: -right_margin,
8041 ..Default::default()
8042 });
8043
8044 let x_after_longest =
8045 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8046 - scroll_pixel_position.x;
8047
8048 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8049
8050 // Fully visible if it can be displayed within the window (allow overlapping other
8051 // panes). However, this is only allowed if the popover starts within text_bounds.
8052 let can_position_to_the_right = x_after_longest < text_bounds.right()
8053 && x_after_longest + element_bounds.width < viewport_bounds.right();
8054
8055 let mut origin = if can_position_to_the_right {
8056 point(
8057 x_after_longest,
8058 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8059 - scroll_pixel_position.y,
8060 )
8061 } else {
8062 let cursor_row = newest_selection_head.map(|head| head.row());
8063 let above_edit = edit_start
8064 .row()
8065 .0
8066 .checked_sub(line_count as u32)
8067 .map(DisplayRow);
8068 let below_edit = Some(edit_end.row() + 1);
8069 let above_cursor =
8070 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8071 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8072
8073 // Place the edit popover adjacent to the edit if there is a location
8074 // available that is onscreen and does not obscure the cursor. Otherwise,
8075 // place it adjacent to the cursor.
8076 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8077 .into_iter()
8078 .flatten()
8079 .find(|&start_row| {
8080 let end_row = start_row + line_count as u32;
8081 visible_row_range.contains(&start_row)
8082 && visible_row_range.contains(&end_row)
8083 && cursor_row.map_or(true, |cursor_row| {
8084 !((start_row..end_row).contains(&cursor_row))
8085 })
8086 })?;
8087
8088 content_origin
8089 + point(
8090 -scroll_pixel_position.x,
8091 row_target.as_f32() * line_height - scroll_pixel_position.y,
8092 )
8093 };
8094
8095 origin.x -= BORDER_WIDTH;
8096
8097 window.defer_draw(element, origin, 1);
8098
8099 // Do not return an element, since it will already be drawn due to defer_draw.
8100 None
8101 }
8102
8103 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8104 px(30.)
8105 }
8106
8107 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8108 if self.read_only(cx) {
8109 cx.theme().players().read_only()
8110 } else {
8111 self.style.as_ref().unwrap().local_player
8112 }
8113 }
8114
8115 fn render_edit_prediction_accept_keybind(
8116 &self,
8117 window: &mut Window,
8118 cx: &App,
8119 ) -> Option<AnyElement> {
8120 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
8121 let accept_keystroke = accept_binding.keystroke()?;
8122
8123 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8124
8125 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8126 Color::Accent
8127 } else {
8128 Color::Muted
8129 };
8130
8131 h_flex()
8132 .px_0p5()
8133 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8134 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8135 .text_size(TextSize::XSmall.rems(cx))
8136 .child(h_flex().children(ui::render_modifiers(
8137 &accept_keystroke.modifiers,
8138 PlatformStyle::platform(),
8139 Some(modifiers_color),
8140 Some(IconSize::XSmall.rems().into()),
8141 true,
8142 )))
8143 .when(is_platform_style_mac, |parent| {
8144 parent.child(accept_keystroke.key.clone())
8145 })
8146 .when(!is_platform_style_mac, |parent| {
8147 parent.child(
8148 Key::new(
8149 util::capitalize(&accept_keystroke.key),
8150 Some(Color::Default),
8151 )
8152 .size(Some(IconSize::XSmall.rems().into())),
8153 )
8154 })
8155 .into_any()
8156 .into()
8157 }
8158
8159 fn render_edit_prediction_line_popover(
8160 &self,
8161 label: impl Into<SharedString>,
8162 icon: Option<IconName>,
8163 window: &mut Window,
8164 cx: &App,
8165 ) -> Option<Stateful<Div>> {
8166 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8167
8168 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8169 let has_keybind = keybind.is_some();
8170
8171 let result = h_flex()
8172 .id("ep-line-popover")
8173 .py_0p5()
8174 .pl_1()
8175 .pr(padding_right)
8176 .gap_1()
8177 .rounded_md()
8178 .border_1()
8179 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8180 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8181 .shadow_sm()
8182 .when(!has_keybind, |el| {
8183 let status_colors = cx.theme().status();
8184
8185 el.bg(status_colors.error_background)
8186 .border_color(status_colors.error.opacity(0.6))
8187 .pl_2()
8188 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8189 .cursor_default()
8190 .hoverable_tooltip(move |_window, cx| {
8191 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8192 })
8193 })
8194 .children(keybind)
8195 .child(
8196 Label::new(label)
8197 .size(LabelSize::Small)
8198 .when(!has_keybind, |el| {
8199 el.color(cx.theme().status().error.into()).strikethrough()
8200 }),
8201 )
8202 .when(!has_keybind, |el| {
8203 el.child(
8204 h_flex().ml_1().child(
8205 Icon::new(IconName::Info)
8206 .size(IconSize::Small)
8207 .color(cx.theme().status().error.into()),
8208 ),
8209 )
8210 })
8211 .when_some(icon, |element, icon| {
8212 element.child(
8213 div()
8214 .mt(px(1.5))
8215 .child(Icon::new(icon).size(IconSize::Small)),
8216 )
8217 });
8218
8219 Some(result)
8220 }
8221
8222 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8223 let accent_color = cx.theme().colors().text_accent;
8224 let editor_bg_color = cx.theme().colors().editor_background;
8225 editor_bg_color.blend(accent_color.opacity(0.1))
8226 }
8227
8228 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8229 let accent_color = cx.theme().colors().text_accent;
8230 let editor_bg_color = cx.theme().colors().editor_background;
8231 editor_bg_color.blend(accent_color.opacity(0.6))
8232 }
8233
8234 fn render_edit_prediction_cursor_popover(
8235 &self,
8236 min_width: Pixels,
8237 max_width: Pixels,
8238 cursor_point: Point,
8239 style: &EditorStyle,
8240 accept_keystroke: Option<&gpui::Keystroke>,
8241 _window: &Window,
8242 cx: &mut Context<Editor>,
8243 ) -> Option<AnyElement> {
8244 let provider = self.edit_prediction_provider.as_ref()?;
8245
8246 if provider.provider.needs_terms_acceptance(cx) {
8247 return Some(
8248 h_flex()
8249 .min_w(min_width)
8250 .flex_1()
8251 .px_2()
8252 .py_1()
8253 .gap_3()
8254 .elevation_2(cx)
8255 .hover(|style| style.bg(cx.theme().colors().element_hover))
8256 .id("accept-terms")
8257 .cursor_pointer()
8258 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8259 .on_click(cx.listener(|this, _event, window, cx| {
8260 cx.stop_propagation();
8261 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8262 window.dispatch_action(
8263 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8264 cx,
8265 );
8266 }))
8267 .child(
8268 h_flex()
8269 .flex_1()
8270 .gap_2()
8271 .child(Icon::new(IconName::ZedPredict))
8272 .child(Label::new("Accept Terms of Service"))
8273 .child(div().w_full())
8274 .child(
8275 Icon::new(IconName::ArrowUpRight)
8276 .color(Color::Muted)
8277 .size(IconSize::Small),
8278 )
8279 .into_any_element(),
8280 )
8281 .into_any(),
8282 );
8283 }
8284
8285 let is_refreshing = provider.provider.is_refreshing(cx);
8286
8287 fn pending_completion_container() -> Div {
8288 h_flex()
8289 .h_full()
8290 .flex_1()
8291 .gap_2()
8292 .child(Icon::new(IconName::ZedPredict))
8293 }
8294
8295 let completion = match &self.active_inline_completion {
8296 Some(prediction) => {
8297 if !self.has_visible_completions_menu() {
8298 const RADIUS: Pixels = px(6.);
8299 const BORDER_WIDTH: Pixels = px(1.);
8300
8301 return Some(
8302 h_flex()
8303 .elevation_2(cx)
8304 .border(BORDER_WIDTH)
8305 .border_color(cx.theme().colors().border)
8306 .when(accept_keystroke.is_none(), |el| {
8307 el.border_color(cx.theme().status().error)
8308 })
8309 .rounded(RADIUS)
8310 .rounded_tl(px(0.))
8311 .overflow_hidden()
8312 .child(div().px_1p5().child(match &prediction.completion {
8313 InlineCompletion::Move { target, snapshot } => {
8314 use text::ToPoint as _;
8315 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8316 {
8317 Icon::new(IconName::ZedPredictDown)
8318 } else {
8319 Icon::new(IconName::ZedPredictUp)
8320 }
8321 }
8322 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8323 }))
8324 .child(
8325 h_flex()
8326 .gap_1()
8327 .py_1()
8328 .px_2()
8329 .rounded_r(RADIUS - BORDER_WIDTH)
8330 .border_l_1()
8331 .border_color(cx.theme().colors().border)
8332 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8333 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8334 el.child(
8335 Label::new("Hold")
8336 .size(LabelSize::Small)
8337 .when(accept_keystroke.is_none(), |el| {
8338 el.strikethrough()
8339 })
8340 .line_height_style(LineHeightStyle::UiLabel),
8341 )
8342 })
8343 .id("edit_prediction_cursor_popover_keybind")
8344 .when(accept_keystroke.is_none(), |el| {
8345 let status_colors = cx.theme().status();
8346
8347 el.bg(status_colors.error_background)
8348 .border_color(status_colors.error.opacity(0.6))
8349 .child(Icon::new(IconName::Info).color(Color::Error))
8350 .cursor_default()
8351 .hoverable_tooltip(move |_window, cx| {
8352 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8353 .into()
8354 })
8355 })
8356 .when_some(
8357 accept_keystroke.as_ref(),
8358 |el, accept_keystroke| {
8359 el.child(h_flex().children(ui::render_modifiers(
8360 &accept_keystroke.modifiers,
8361 PlatformStyle::platform(),
8362 Some(Color::Default),
8363 Some(IconSize::XSmall.rems().into()),
8364 false,
8365 )))
8366 },
8367 ),
8368 )
8369 .into_any(),
8370 );
8371 }
8372
8373 self.render_edit_prediction_cursor_popover_preview(
8374 prediction,
8375 cursor_point,
8376 style,
8377 cx,
8378 )?
8379 }
8380
8381 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8382 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8383 stale_completion,
8384 cursor_point,
8385 style,
8386 cx,
8387 )?,
8388
8389 None => {
8390 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8391 }
8392 },
8393
8394 None => pending_completion_container().child(Label::new("No Prediction")),
8395 };
8396
8397 let completion = if is_refreshing {
8398 completion
8399 .with_animation(
8400 "loading-completion",
8401 Animation::new(Duration::from_secs(2))
8402 .repeat()
8403 .with_easing(pulsating_between(0.4, 0.8)),
8404 |label, delta| label.opacity(delta),
8405 )
8406 .into_any_element()
8407 } else {
8408 completion.into_any_element()
8409 };
8410
8411 let has_completion = self.active_inline_completion.is_some();
8412
8413 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8414 Some(
8415 h_flex()
8416 .min_w(min_width)
8417 .max_w(max_width)
8418 .flex_1()
8419 .elevation_2(cx)
8420 .border_color(cx.theme().colors().border)
8421 .child(
8422 div()
8423 .flex_1()
8424 .py_1()
8425 .px_2()
8426 .overflow_hidden()
8427 .child(completion),
8428 )
8429 .when_some(accept_keystroke, |el, accept_keystroke| {
8430 if !accept_keystroke.modifiers.modified() {
8431 return el;
8432 }
8433
8434 el.child(
8435 h_flex()
8436 .h_full()
8437 .border_l_1()
8438 .rounded_r_lg()
8439 .border_color(cx.theme().colors().border)
8440 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8441 .gap_1()
8442 .py_1()
8443 .px_2()
8444 .child(
8445 h_flex()
8446 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8447 .when(is_platform_style_mac, |parent| parent.gap_1())
8448 .child(h_flex().children(ui::render_modifiers(
8449 &accept_keystroke.modifiers,
8450 PlatformStyle::platform(),
8451 Some(if !has_completion {
8452 Color::Muted
8453 } else {
8454 Color::Default
8455 }),
8456 None,
8457 false,
8458 ))),
8459 )
8460 .child(Label::new("Preview").into_any_element())
8461 .opacity(if has_completion { 1.0 } else { 0.4 }),
8462 )
8463 })
8464 .into_any(),
8465 )
8466 }
8467
8468 fn render_edit_prediction_cursor_popover_preview(
8469 &self,
8470 completion: &InlineCompletionState,
8471 cursor_point: Point,
8472 style: &EditorStyle,
8473 cx: &mut Context<Editor>,
8474 ) -> Option<Div> {
8475 use text::ToPoint as _;
8476
8477 fn render_relative_row_jump(
8478 prefix: impl Into<String>,
8479 current_row: u32,
8480 target_row: u32,
8481 ) -> Div {
8482 let (row_diff, arrow) = if target_row < current_row {
8483 (current_row - target_row, IconName::ArrowUp)
8484 } else {
8485 (target_row - current_row, IconName::ArrowDown)
8486 };
8487
8488 h_flex()
8489 .child(
8490 Label::new(format!("{}{}", prefix.into(), row_diff))
8491 .color(Color::Muted)
8492 .size(LabelSize::Small),
8493 )
8494 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8495 }
8496
8497 match &completion.completion {
8498 InlineCompletion::Move {
8499 target, snapshot, ..
8500 } => Some(
8501 h_flex()
8502 .px_2()
8503 .gap_2()
8504 .flex_1()
8505 .child(
8506 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8507 Icon::new(IconName::ZedPredictDown)
8508 } else {
8509 Icon::new(IconName::ZedPredictUp)
8510 },
8511 )
8512 .child(Label::new("Jump to Edit")),
8513 ),
8514
8515 InlineCompletion::Edit {
8516 edits,
8517 edit_preview,
8518 snapshot,
8519 display_mode: _,
8520 } => {
8521 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8522
8523 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8524 &snapshot,
8525 &edits,
8526 edit_preview.as_ref()?,
8527 true,
8528 cx,
8529 )
8530 .first_line_preview();
8531
8532 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8533 .with_default_highlights(&style.text, highlighted_edits.highlights);
8534
8535 let preview = h_flex()
8536 .gap_1()
8537 .min_w_16()
8538 .child(styled_text)
8539 .when(has_more_lines, |parent| parent.child("…"));
8540
8541 let left = if first_edit_row != cursor_point.row {
8542 render_relative_row_jump("", cursor_point.row, first_edit_row)
8543 .into_any_element()
8544 } else {
8545 Icon::new(IconName::ZedPredict).into_any_element()
8546 };
8547
8548 Some(
8549 h_flex()
8550 .h_full()
8551 .flex_1()
8552 .gap_2()
8553 .pr_1()
8554 .overflow_x_hidden()
8555 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8556 .child(left)
8557 .child(preview),
8558 )
8559 }
8560 }
8561 }
8562
8563 pub fn render_context_menu(
8564 &self,
8565 style: &EditorStyle,
8566 max_height_in_lines: u32,
8567 window: &mut Window,
8568 cx: &mut Context<Editor>,
8569 ) -> Option<AnyElement> {
8570 let menu = self.context_menu.borrow();
8571 let menu = menu.as_ref()?;
8572 if !menu.visible() {
8573 return None;
8574 };
8575 Some(menu.render(style, max_height_in_lines, window, cx))
8576 }
8577
8578 fn render_context_menu_aside(
8579 &mut self,
8580 max_size: Size<Pixels>,
8581 window: &mut Window,
8582 cx: &mut Context<Editor>,
8583 ) -> Option<AnyElement> {
8584 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8585 if menu.visible() {
8586 menu.render_aside(self, max_size, window, cx)
8587 } else {
8588 None
8589 }
8590 })
8591 }
8592
8593 fn hide_context_menu(
8594 &mut self,
8595 window: &mut Window,
8596 cx: &mut Context<Self>,
8597 ) -> Option<CodeContextMenu> {
8598 cx.notify();
8599 self.completion_tasks.clear();
8600 let context_menu = self.context_menu.borrow_mut().take();
8601 self.stale_inline_completion_in_menu.take();
8602 self.update_visible_inline_completion(window, cx);
8603 context_menu
8604 }
8605
8606 fn show_snippet_choices(
8607 &mut self,
8608 choices: &Vec<String>,
8609 selection: Range<Anchor>,
8610 cx: &mut Context<Self>,
8611 ) {
8612 if selection.start.buffer_id.is_none() {
8613 return;
8614 }
8615 let buffer_id = selection.start.buffer_id.unwrap();
8616 let buffer = self.buffer().read(cx).buffer(buffer_id);
8617 let id = post_inc(&mut self.next_completion_id);
8618 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8619
8620 if let Some(buffer) = buffer {
8621 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8622 CompletionsMenu::new_snippet_choices(
8623 id,
8624 true,
8625 choices,
8626 selection,
8627 buffer,
8628 snippet_sort_order,
8629 ),
8630 ));
8631 }
8632 }
8633
8634 pub fn insert_snippet(
8635 &mut self,
8636 insertion_ranges: &[Range<usize>],
8637 snippet: Snippet,
8638 window: &mut Window,
8639 cx: &mut Context<Self>,
8640 ) -> Result<()> {
8641 struct Tabstop<T> {
8642 is_end_tabstop: bool,
8643 ranges: Vec<Range<T>>,
8644 choices: Option<Vec<String>>,
8645 }
8646
8647 let tabstops = self.buffer.update(cx, |buffer, cx| {
8648 let snippet_text: Arc<str> = snippet.text.clone().into();
8649 let edits = insertion_ranges
8650 .iter()
8651 .cloned()
8652 .map(|range| (range, snippet_text.clone()));
8653 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8654
8655 let snapshot = &*buffer.read(cx);
8656 let snippet = &snippet;
8657 snippet
8658 .tabstops
8659 .iter()
8660 .map(|tabstop| {
8661 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8662 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8663 });
8664 let mut tabstop_ranges = tabstop
8665 .ranges
8666 .iter()
8667 .flat_map(|tabstop_range| {
8668 let mut delta = 0_isize;
8669 insertion_ranges.iter().map(move |insertion_range| {
8670 let insertion_start = insertion_range.start as isize + delta;
8671 delta +=
8672 snippet.text.len() as isize - insertion_range.len() as isize;
8673
8674 let start = ((insertion_start + tabstop_range.start) as usize)
8675 .min(snapshot.len());
8676 let end = ((insertion_start + tabstop_range.end) as usize)
8677 .min(snapshot.len());
8678 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8679 })
8680 })
8681 .collect::<Vec<_>>();
8682 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8683
8684 Tabstop {
8685 is_end_tabstop,
8686 ranges: tabstop_ranges,
8687 choices: tabstop.choices.clone(),
8688 }
8689 })
8690 .collect::<Vec<_>>()
8691 });
8692 if let Some(tabstop) = tabstops.first() {
8693 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8694 s.select_ranges(tabstop.ranges.iter().cloned());
8695 });
8696
8697 if let Some(choices) = &tabstop.choices {
8698 if let Some(selection) = tabstop.ranges.first() {
8699 self.show_snippet_choices(choices, selection.clone(), cx)
8700 }
8701 }
8702
8703 // If we're already at the last tabstop and it's at the end of the snippet,
8704 // we're done, we don't need to keep the state around.
8705 if !tabstop.is_end_tabstop {
8706 let choices = tabstops
8707 .iter()
8708 .map(|tabstop| tabstop.choices.clone())
8709 .collect();
8710
8711 let ranges = tabstops
8712 .into_iter()
8713 .map(|tabstop| tabstop.ranges)
8714 .collect::<Vec<_>>();
8715
8716 self.snippet_stack.push(SnippetState {
8717 active_index: 0,
8718 ranges,
8719 choices,
8720 });
8721 }
8722
8723 // Check whether the just-entered snippet ends with an auto-closable bracket.
8724 if self.autoclose_regions.is_empty() {
8725 let snapshot = self.buffer.read(cx).snapshot(cx);
8726 for selection in &mut self.selections.all::<Point>(cx) {
8727 let selection_head = selection.head();
8728 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8729 continue;
8730 };
8731
8732 let mut bracket_pair = None;
8733 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8734 let prev_chars = snapshot
8735 .reversed_chars_at(selection_head)
8736 .collect::<String>();
8737 for (pair, enabled) in scope.brackets() {
8738 if enabled
8739 && pair.close
8740 && prev_chars.starts_with(pair.start.as_str())
8741 && next_chars.starts_with(pair.end.as_str())
8742 {
8743 bracket_pair = Some(pair.clone());
8744 break;
8745 }
8746 }
8747 if let Some(pair) = bracket_pair {
8748 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8749 let autoclose_enabled =
8750 self.use_autoclose && snapshot_settings.use_autoclose;
8751 if autoclose_enabled {
8752 let start = snapshot.anchor_after(selection_head);
8753 let end = snapshot.anchor_after(selection_head);
8754 self.autoclose_regions.push(AutocloseRegion {
8755 selection_id: selection.id,
8756 range: start..end,
8757 pair,
8758 });
8759 }
8760 }
8761 }
8762 }
8763 }
8764 Ok(())
8765 }
8766
8767 pub fn move_to_next_snippet_tabstop(
8768 &mut self,
8769 window: &mut Window,
8770 cx: &mut Context<Self>,
8771 ) -> bool {
8772 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8773 }
8774
8775 pub fn move_to_prev_snippet_tabstop(
8776 &mut self,
8777 window: &mut Window,
8778 cx: &mut Context<Self>,
8779 ) -> bool {
8780 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8781 }
8782
8783 pub fn move_to_snippet_tabstop(
8784 &mut self,
8785 bias: Bias,
8786 window: &mut Window,
8787 cx: &mut Context<Self>,
8788 ) -> bool {
8789 if let Some(mut snippet) = self.snippet_stack.pop() {
8790 match bias {
8791 Bias::Left => {
8792 if snippet.active_index > 0 {
8793 snippet.active_index -= 1;
8794 } else {
8795 self.snippet_stack.push(snippet);
8796 return false;
8797 }
8798 }
8799 Bias::Right => {
8800 if snippet.active_index + 1 < snippet.ranges.len() {
8801 snippet.active_index += 1;
8802 } else {
8803 self.snippet_stack.push(snippet);
8804 return false;
8805 }
8806 }
8807 }
8808 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8809 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8810 s.select_anchor_ranges(current_ranges.iter().cloned())
8811 });
8812
8813 if let Some(choices) = &snippet.choices[snippet.active_index] {
8814 if let Some(selection) = current_ranges.first() {
8815 self.show_snippet_choices(&choices, selection.clone(), cx);
8816 }
8817 }
8818
8819 // If snippet state is not at the last tabstop, push it back on the stack
8820 if snippet.active_index + 1 < snippet.ranges.len() {
8821 self.snippet_stack.push(snippet);
8822 }
8823 return true;
8824 }
8825 }
8826
8827 false
8828 }
8829
8830 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8831 self.transact(window, cx, |this, window, cx| {
8832 this.select_all(&SelectAll, window, cx);
8833 this.insert("", window, cx);
8834 });
8835 }
8836
8837 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8838 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8839 self.transact(window, cx, |this, window, cx| {
8840 this.select_autoclose_pair(window, cx);
8841 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8842 if !this.linked_edit_ranges.is_empty() {
8843 let selections = this.selections.all::<MultiBufferPoint>(cx);
8844 let snapshot = this.buffer.read(cx).snapshot(cx);
8845
8846 for selection in selections.iter() {
8847 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8848 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8849 if selection_start.buffer_id != selection_end.buffer_id {
8850 continue;
8851 }
8852 if let Some(ranges) =
8853 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8854 {
8855 for (buffer, entries) in ranges {
8856 linked_ranges.entry(buffer).or_default().extend(entries);
8857 }
8858 }
8859 }
8860 }
8861
8862 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8863 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8864 for selection in &mut selections {
8865 if selection.is_empty() {
8866 let old_head = selection.head();
8867 let mut new_head =
8868 movement::left(&display_map, old_head.to_display_point(&display_map))
8869 .to_point(&display_map);
8870 if let Some((buffer, line_buffer_range)) = display_map
8871 .buffer_snapshot
8872 .buffer_line_for_row(MultiBufferRow(old_head.row))
8873 {
8874 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8875 let indent_len = match indent_size.kind {
8876 IndentKind::Space => {
8877 buffer.settings_at(line_buffer_range.start, cx).tab_size
8878 }
8879 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8880 };
8881 if old_head.column <= indent_size.len && old_head.column > 0 {
8882 let indent_len = indent_len.get();
8883 new_head = cmp::min(
8884 new_head,
8885 MultiBufferPoint::new(
8886 old_head.row,
8887 ((old_head.column - 1) / indent_len) * indent_len,
8888 ),
8889 );
8890 }
8891 }
8892
8893 selection.set_head(new_head, SelectionGoal::None);
8894 }
8895 }
8896
8897 this.signature_help_state.set_backspace_pressed(true);
8898 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8899 s.select(selections)
8900 });
8901 this.insert("", window, cx);
8902 let empty_str: Arc<str> = Arc::from("");
8903 for (buffer, edits) in linked_ranges {
8904 let snapshot = buffer.read(cx).snapshot();
8905 use text::ToPoint as TP;
8906
8907 let edits = edits
8908 .into_iter()
8909 .map(|range| {
8910 let end_point = TP::to_point(&range.end, &snapshot);
8911 let mut start_point = TP::to_point(&range.start, &snapshot);
8912
8913 if end_point == start_point {
8914 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8915 .saturating_sub(1);
8916 start_point =
8917 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8918 };
8919
8920 (start_point..end_point, empty_str.clone())
8921 })
8922 .sorted_by_key(|(range, _)| range.start)
8923 .collect::<Vec<_>>();
8924 buffer.update(cx, |this, cx| {
8925 this.edit(edits, None, cx);
8926 })
8927 }
8928 this.refresh_inline_completion(true, false, window, cx);
8929 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8930 });
8931 }
8932
8933 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8934 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8935 self.transact(window, cx, |this, window, cx| {
8936 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8937 s.move_with(|map, selection| {
8938 if selection.is_empty() {
8939 let cursor = movement::right(map, selection.head());
8940 selection.end = cursor;
8941 selection.reversed = true;
8942 selection.goal = SelectionGoal::None;
8943 }
8944 })
8945 });
8946 this.insert("", window, cx);
8947 this.refresh_inline_completion(true, false, window, cx);
8948 });
8949 }
8950
8951 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8952 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8953 if self.move_to_prev_snippet_tabstop(window, cx) {
8954 return;
8955 }
8956 self.outdent(&Outdent, window, cx);
8957 }
8958
8959 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8960 if self.move_to_next_snippet_tabstop(window, cx) {
8961 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8962 return;
8963 }
8964 if self.read_only(cx) {
8965 return;
8966 }
8967 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8968 let mut selections = self.selections.all_adjusted(cx);
8969 let buffer = self.buffer.read(cx);
8970 let snapshot = buffer.snapshot(cx);
8971 let rows_iter = selections.iter().map(|s| s.head().row);
8972 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8973
8974 let has_some_cursor_in_whitespace = selections
8975 .iter()
8976 .filter(|selection| selection.is_empty())
8977 .any(|selection| {
8978 let cursor = selection.head();
8979 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8980 cursor.column < current_indent.len
8981 });
8982
8983 let mut edits = Vec::new();
8984 let mut prev_edited_row = 0;
8985 let mut row_delta = 0;
8986 for selection in &mut selections {
8987 if selection.start.row != prev_edited_row {
8988 row_delta = 0;
8989 }
8990 prev_edited_row = selection.end.row;
8991
8992 // If the selection is non-empty, then increase the indentation of the selected lines.
8993 if !selection.is_empty() {
8994 row_delta =
8995 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8996 continue;
8997 }
8998
8999 let cursor = selection.head();
9000 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9001 if let Some(suggested_indent) =
9002 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9003 {
9004 // Don't do anything if already at suggested indent
9005 // and there is any other cursor which is not
9006 if has_some_cursor_in_whitespace
9007 && cursor.column == current_indent.len
9008 && current_indent.len == suggested_indent.len
9009 {
9010 continue;
9011 }
9012
9013 // Adjust line and move cursor to suggested indent
9014 // if cursor is not at suggested indent
9015 if cursor.column < suggested_indent.len
9016 && cursor.column <= current_indent.len
9017 && current_indent.len <= suggested_indent.len
9018 {
9019 selection.start = Point::new(cursor.row, suggested_indent.len);
9020 selection.end = selection.start;
9021 if row_delta == 0 {
9022 edits.extend(Buffer::edit_for_indent_size_adjustment(
9023 cursor.row,
9024 current_indent,
9025 suggested_indent,
9026 ));
9027 row_delta = suggested_indent.len - current_indent.len;
9028 }
9029 continue;
9030 }
9031
9032 // If current indent is more than suggested indent
9033 // only move cursor to current indent and skip indent
9034 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9035 selection.start = Point::new(cursor.row, current_indent.len);
9036 selection.end = selection.start;
9037 continue;
9038 }
9039 }
9040
9041 // Otherwise, insert a hard or soft tab.
9042 let settings = buffer.language_settings_at(cursor, cx);
9043 let tab_size = if settings.hard_tabs {
9044 IndentSize::tab()
9045 } else {
9046 let tab_size = settings.tab_size.get();
9047 let indent_remainder = snapshot
9048 .text_for_range(Point::new(cursor.row, 0)..cursor)
9049 .flat_map(str::chars)
9050 .fold(row_delta % tab_size, |counter: u32, c| {
9051 if c == '\t' {
9052 0
9053 } else {
9054 (counter + 1) % tab_size
9055 }
9056 });
9057
9058 let chars_to_next_tab_stop = tab_size - indent_remainder;
9059 IndentSize::spaces(chars_to_next_tab_stop)
9060 };
9061 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9062 selection.end = selection.start;
9063 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9064 row_delta += tab_size.len;
9065 }
9066
9067 self.transact(window, cx, |this, window, cx| {
9068 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9069 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9070 s.select(selections)
9071 });
9072 this.refresh_inline_completion(true, false, window, cx);
9073 });
9074 }
9075
9076 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9077 if self.read_only(cx) {
9078 return;
9079 }
9080 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9081 let mut selections = self.selections.all::<Point>(cx);
9082 let mut prev_edited_row = 0;
9083 let mut row_delta = 0;
9084 let mut edits = Vec::new();
9085 let buffer = self.buffer.read(cx);
9086 let snapshot = buffer.snapshot(cx);
9087 for selection in &mut selections {
9088 if selection.start.row != prev_edited_row {
9089 row_delta = 0;
9090 }
9091 prev_edited_row = selection.end.row;
9092
9093 row_delta =
9094 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9095 }
9096
9097 self.transact(window, cx, |this, window, cx| {
9098 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9099 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9100 s.select(selections)
9101 });
9102 });
9103 }
9104
9105 fn indent_selection(
9106 buffer: &MultiBuffer,
9107 snapshot: &MultiBufferSnapshot,
9108 selection: &mut Selection<Point>,
9109 edits: &mut Vec<(Range<Point>, String)>,
9110 delta_for_start_row: u32,
9111 cx: &App,
9112 ) -> u32 {
9113 let settings = buffer.language_settings_at(selection.start, cx);
9114 let tab_size = settings.tab_size.get();
9115 let indent_kind = if settings.hard_tabs {
9116 IndentKind::Tab
9117 } else {
9118 IndentKind::Space
9119 };
9120 let mut start_row = selection.start.row;
9121 let mut end_row = selection.end.row + 1;
9122
9123 // If a selection ends at the beginning of a line, don't indent
9124 // that last line.
9125 if selection.end.column == 0 && selection.end.row > selection.start.row {
9126 end_row -= 1;
9127 }
9128
9129 // Avoid re-indenting a row that has already been indented by a
9130 // previous selection, but still update this selection's column
9131 // to reflect that indentation.
9132 if delta_for_start_row > 0 {
9133 start_row += 1;
9134 selection.start.column += delta_for_start_row;
9135 if selection.end.row == selection.start.row {
9136 selection.end.column += delta_for_start_row;
9137 }
9138 }
9139
9140 let mut delta_for_end_row = 0;
9141 let has_multiple_rows = start_row + 1 != end_row;
9142 for row in start_row..end_row {
9143 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9144 let indent_delta = match (current_indent.kind, indent_kind) {
9145 (IndentKind::Space, IndentKind::Space) => {
9146 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9147 IndentSize::spaces(columns_to_next_tab_stop)
9148 }
9149 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9150 (_, IndentKind::Tab) => IndentSize::tab(),
9151 };
9152
9153 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9154 0
9155 } else {
9156 selection.start.column
9157 };
9158 let row_start = Point::new(row, start);
9159 edits.push((
9160 row_start..row_start,
9161 indent_delta.chars().collect::<String>(),
9162 ));
9163
9164 // Update this selection's endpoints to reflect the indentation.
9165 if row == selection.start.row {
9166 selection.start.column += indent_delta.len;
9167 }
9168 if row == selection.end.row {
9169 selection.end.column += indent_delta.len;
9170 delta_for_end_row = indent_delta.len;
9171 }
9172 }
9173
9174 if selection.start.row == selection.end.row {
9175 delta_for_start_row + delta_for_end_row
9176 } else {
9177 delta_for_end_row
9178 }
9179 }
9180
9181 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9182 if self.read_only(cx) {
9183 return;
9184 }
9185 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9186 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9187 let selections = self.selections.all::<Point>(cx);
9188 let mut deletion_ranges = Vec::new();
9189 let mut last_outdent = None;
9190 {
9191 let buffer = self.buffer.read(cx);
9192 let snapshot = buffer.snapshot(cx);
9193 for selection in &selections {
9194 let settings = buffer.language_settings_at(selection.start, cx);
9195 let tab_size = settings.tab_size.get();
9196 let mut rows = selection.spanned_rows(false, &display_map);
9197
9198 // Avoid re-outdenting a row that has already been outdented by a
9199 // previous selection.
9200 if let Some(last_row) = last_outdent {
9201 if last_row == rows.start {
9202 rows.start = rows.start.next_row();
9203 }
9204 }
9205 let has_multiple_rows = rows.len() > 1;
9206 for row in rows.iter_rows() {
9207 let indent_size = snapshot.indent_size_for_line(row);
9208 if indent_size.len > 0 {
9209 let deletion_len = match indent_size.kind {
9210 IndentKind::Space => {
9211 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9212 if columns_to_prev_tab_stop == 0 {
9213 tab_size
9214 } else {
9215 columns_to_prev_tab_stop
9216 }
9217 }
9218 IndentKind::Tab => 1,
9219 };
9220 let start = if has_multiple_rows
9221 || deletion_len > selection.start.column
9222 || indent_size.len < selection.start.column
9223 {
9224 0
9225 } else {
9226 selection.start.column - deletion_len
9227 };
9228 deletion_ranges.push(
9229 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9230 );
9231 last_outdent = Some(row);
9232 }
9233 }
9234 }
9235 }
9236
9237 self.transact(window, cx, |this, window, cx| {
9238 this.buffer.update(cx, |buffer, cx| {
9239 let empty_str: Arc<str> = Arc::default();
9240 buffer.edit(
9241 deletion_ranges
9242 .into_iter()
9243 .map(|range| (range, empty_str.clone())),
9244 None,
9245 cx,
9246 );
9247 });
9248 let selections = this.selections.all::<usize>(cx);
9249 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9250 s.select(selections)
9251 });
9252 });
9253 }
9254
9255 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9256 if self.read_only(cx) {
9257 return;
9258 }
9259 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9260 let selections = self
9261 .selections
9262 .all::<usize>(cx)
9263 .into_iter()
9264 .map(|s| s.range());
9265
9266 self.transact(window, cx, |this, window, cx| {
9267 this.buffer.update(cx, |buffer, cx| {
9268 buffer.autoindent_ranges(selections, cx);
9269 });
9270 let selections = this.selections.all::<usize>(cx);
9271 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9272 s.select(selections)
9273 });
9274 });
9275 }
9276
9277 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9278 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9279 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9280 let selections = self.selections.all::<Point>(cx);
9281
9282 let mut new_cursors = Vec::new();
9283 let mut edit_ranges = Vec::new();
9284 let mut selections = selections.iter().peekable();
9285 while let Some(selection) = selections.next() {
9286 let mut rows = selection.spanned_rows(false, &display_map);
9287 let goal_display_column = selection.head().to_display_point(&display_map).column();
9288
9289 // Accumulate contiguous regions of rows that we want to delete.
9290 while let Some(next_selection) = selections.peek() {
9291 let next_rows = next_selection.spanned_rows(false, &display_map);
9292 if next_rows.start <= rows.end {
9293 rows.end = next_rows.end;
9294 selections.next().unwrap();
9295 } else {
9296 break;
9297 }
9298 }
9299
9300 let buffer = &display_map.buffer_snapshot;
9301 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9302 let edit_end;
9303 let cursor_buffer_row;
9304 if buffer.max_point().row >= rows.end.0 {
9305 // If there's a line after the range, delete the \n from the end of the row range
9306 // and position the cursor on the next line.
9307 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9308 cursor_buffer_row = rows.end;
9309 } else {
9310 // If there isn't a line after the range, delete the \n from the line before the
9311 // start of the row range and position the cursor there.
9312 edit_start = edit_start.saturating_sub(1);
9313 edit_end = buffer.len();
9314 cursor_buffer_row = rows.start.previous_row();
9315 }
9316
9317 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9318 *cursor.column_mut() =
9319 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9320
9321 new_cursors.push((
9322 selection.id,
9323 buffer.anchor_after(cursor.to_point(&display_map)),
9324 ));
9325 edit_ranges.push(edit_start..edit_end);
9326 }
9327
9328 self.transact(window, cx, |this, window, cx| {
9329 let buffer = this.buffer.update(cx, |buffer, cx| {
9330 let empty_str: Arc<str> = Arc::default();
9331 buffer.edit(
9332 edit_ranges
9333 .into_iter()
9334 .map(|range| (range, empty_str.clone())),
9335 None,
9336 cx,
9337 );
9338 buffer.snapshot(cx)
9339 });
9340 let new_selections = new_cursors
9341 .into_iter()
9342 .map(|(id, cursor)| {
9343 let cursor = cursor.to_point(&buffer);
9344 Selection {
9345 id,
9346 start: cursor,
9347 end: cursor,
9348 reversed: false,
9349 goal: SelectionGoal::None,
9350 }
9351 })
9352 .collect();
9353
9354 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9355 s.select(new_selections);
9356 });
9357 });
9358 }
9359
9360 pub fn join_lines_impl(
9361 &mut self,
9362 insert_whitespace: bool,
9363 window: &mut Window,
9364 cx: &mut Context<Self>,
9365 ) {
9366 if self.read_only(cx) {
9367 return;
9368 }
9369 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9370 for selection in self.selections.all::<Point>(cx) {
9371 let start = MultiBufferRow(selection.start.row);
9372 // Treat single line selections as if they include the next line. Otherwise this action
9373 // would do nothing for single line selections individual cursors.
9374 let end = if selection.start.row == selection.end.row {
9375 MultiBufferRow(selection.start.row + 1)
9376 } else {
9377 MultiBufferRow(selection.end.row)
9378 };
9379
9380 if let Some(last_row_range) = row_ranges.last_mut() {
9381 if start <= last_row_range.end {
9382 last_row_range.end = end;
9383 continue;
9384 }
9385 }
9386 row_ranges.push(start..end);
9387 }
9388
9389 let snapshot = self.buffer.read(cx).snapshot(cx);
9390 let mut cursor_positions = Vec::new();
9391 for row_range in &row_ranges {
9392 let anchor = snapshot.anchor_before(Point::new(
9393 row_range.end.previous_row().0,
9394 snapshot.line_len(row_range.end.previous_row()),
9395 ));
9396 cursor_positions.push(anchor..anchor);
9397 }
9398
9399 self.transact(window, cx, |this, window, cx| {
9400 for row_range in row_ranges.into_iter().rev() {
9401 for row in row_range.iter_rows().rev() {
9402 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9403 let next_line_row = row.next_row();
9404 let indent = snapshot.indent_size_for_line(next_line_row);
9405 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9406
9407 let replace =
9408 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9409 " "
9410 } else {
9411 ""
9412 };
9413
9414 this.buffer.update(cx, |buffer, cx| {
9415 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9416 });
9417 }
9418 }
9419
9420 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9421 s.select_anchor_ranges(cursor_positions)
9422 });
9423 });
9424 }
9425
9426 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9427 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9428 self.join_lines_impl(true, window, cx);
9429 }
9430
9431 pub fn sort_lines_case_sensitive(
9432 &mut self,
9433 _: &SortLinesCaseSensitive,
9434 window: &mut Window,
9435 cx: &mut Context<Self>,
9436 ) {
9437 self.manipulate_lines(window, cx, |lines| lines.sort())
9438 }
9439
9440 pub fn sort_lines_case_insensitive(
9441 &mut self,
9442 _: &SortLinesCaseInsensitive,
9443 window: &mut Window,
9444 cx: &mut Context<Self>,
9445 ) {
9446 self.manipulate_lines(window, cx, |lines| {
9447 lines.sort_by_key(|line| line.to_lowercase())
9448 })
9449 }
9450
9451 pub fn unique_lines_case_insensitive(
9452 &mut self,
9453 _: &UniqueLinesCaseInsensitive,
9454 window: &mut Window,
9455 cx: &mut Context<Self>,
9456 ) {
9457 self.manipulate_lines(window, cx, |lines| {
9458 let mut seen = HashSet::default();
9459 lines.retain(|line| seen.insert(line.to_lowercase()));
9460 })
9461 }
9462
9463 pub fn unique_lines_case_sensitive(
9464 &mut self,
9465 _: &UniqueLinesCaseSensitive,
9466 window: &mut Window,
9467 cx: &mut Context<Self>,
9468 ) {
9469 self.manipulate_lines(window, cx, |lines| {
9470 let mut seen = HashSet::default();
9471 lines.retain(|line| seen.insert(*line));
9472 })
9473 }
9474
9475 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9476 let Some(project) = self.project.clone() else {
9477 return;
9478 };
9479 self.reload(project, window, cx)
9480 .detach_and_notify_err(window, cx);
9481 }
9482
9483 pub fn restore_file(
9484 &mut self,
9485 _: &::git::RestoreFile,
9486 window: &mut Window,
9487 cx: &mut Context<Self>,
9488 ) {
9489 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9490 let mut buffer_ids = HashSet::default();
9491 let snapshot = self.buffer().read(cx).snapshot(cx);
9492 for selection in self.selections.all::<usize>(cx) {
9493 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9494 }
9495
9496 let buffer = self.buffer().read(cx);
9497 let ranges = buffer_ids
9498 .into_iter()
9499 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9500 .collect::<Vec<_>>();
9501
9502 self.restore_hunks_in_ranges(ranges, window, cx);
9503 }
9504
9505 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9506 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9507 let selections = self
9508 .selections
9509 .all(cx)
9510 .into_iter()
9511 .map(|s| s.range())
9512 .collect();
9513 self.restore_hunks_in_ranges(selections, window, cx);
9514 }
9515
9516 pub fn restore_hunks_in_ranges(
9517 &mut self,
9518 ranges: Vec<Range<Point>>,
9519 window: &mut Window,
9520 cx: &mut Context<Editor>,
9521 ) {
9522 let mut revert_changes = HashMap::default();
9523 let chunk_by = self
9524 .snapshot(window, cx)
9525 .hunks_for_ranges(ranges)
9526 .into_iter()
9527 .chunk_by(|hunk| hunk.buffer_id);
9528 for (buffer_id, hunks) in &chunk_by {
9529 let hunks = hunks.collect::<Vec<_>>();
9530 for hunk in &hunks {
9531 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9532 }
9533 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9534 }
9535 drop(chunk_by);
9536 if !revert_changes.is_empty() {
9537 self.transact(window, cx, |editor, window, cx| {
9538 editor.restore(revert_changes, window, cx);
9539 });
9540 }
9541 }
9542
9543 pub fn open_active_item_in_terminal(
9544 &mut self,
9545 _: &OpenInTerminal,
9546 window: &mut Window,
9547 cx: &mut Context<Self>,
9548 ) {
9549 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9550 let project_path = buffer.read(cx).project_path(cx)?;
9551 let project = self.project.as_ref()?.read(cx);
9552 let entry = project.entry_for_path(&project_path, cx)?;
9553 let parent = match &entry.canonical_path {
9554 Some(canonical_path) => canonical_path.to_path_buf(),
9555 None => project.absolute_path(&project_path, cx)?,
9556 }
9557 .parent()?
9558 .to_path_buf();
9559 Some(parent)
9560 }) {
9561 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9562 }
9563 }
9564
9565 fn set_breakpoint_context_menu(
9566 &mut self,
9567 display_row: DisplayRow,
9568 position: Option<Anchor>,
9569 clicked_point: gpui::Point<Pixels>,
9570 window: &mut Window,
9571 cx: &mut Context<Self>,
9572 ) {
9573 if !cx.has_flag::<DebuggerFeatureFlag>() {
9574 return;
9575 }
9576 let source = self
9577 .buffer
9578 .read(cx)
9579 .snapshot(cx)
9580 .anchor_before(Point::new(display_row.0, 0u32));
9581
9582 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9583
9584 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9585 self,
9586 source,
9587 clicked_point,
9588 context_menu,
9589 window,
9590 cx,
9591 );
9592 }
9593
9594 fn add_edit_breakpoint_block(
9595 &mut self,
9596 anchor: Anchor,
9597 breakpoint: &Breakpoint,
9598 edit_action: BreakpointPromptEditAction,
9599 window: &mut Window,
9600 cx: &mut Context<Self>,
9601 ) {
9602 let weak_editor = cx.weak_entity();
9603 let bp_prompt = cx.new(|cx| {
9604 BreakpointPromptEditor::new(
9605 weak_editor,
9606 anchor,
9607 breakpoint.clone(),
9608 edit_action,
9609 window,
9610 cx,
9611 )
9612 });
9613
9614 let height = bp_prompt.update(cx, |this, cx| {
9615 this.prompt
9616 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9617 });
9618 let cloned_prompt = bp_prompt.clone();
9619 let blocks = vec![BlockProperties {
9620 style: BlockStyle::Sticky,
9621 placement: BlockPlacement::Above(anchor),
9622 height: Some(height),
9623 render: Arc::new(move |cx| {
9624 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9625 cloned_prompt.clone().into_any_element()
9626 }),
9627 priority: 0,
9628 render_in_minimap: true,
9629 }];
9630
9631 let focus_handle = bp_prompt.focus_handle(cx);
9632 window.focus(&focus_handle);
9633
9634 let block_ids = self.insert_blocks(blocks, None, cx);
9635 bp_prompt.update(cx, |prompt, _| {
9636 prompt.add_block_ids(block_ids);
9637 });
9638 }
9639
9640 pub(crate) fn breakpoint_at_row(
9641 &self,
9642 row: u32,
9643 window: &mut Window,
9644 cx: &mut Context<Self>,
9645 ) -> Option<(Anchor, Breakpoint)> {
9646 let snapshot = self.snapshot(window, cx);
9647 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9648
9649 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9650 }
9651
9652 pub(crate) fn breakpoint_at_anchor(
9653 &self,
9654 breakpoint_position: Anchor,
9655 snapshot: &EditorSnapshot,
9656 cx: &mut Context<Self>,
9657 ) -> Option<(Anchor, Breakpoint)> {
9658 let project = self.project.clone()?;
9659
9660 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9661 snapshot
9662 .buffer_snapshot
9663 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9664 })?;
9665
9666 let enclosing_excerpt = breakpoint_position.excerpt_id;
9667 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
9668 let buffer_snapshot = buffer.read(cx).snapshot();
9669
9670 let row = buffer_snapshot
9671 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9672 .row;
9673
9674 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9675 let anchor_end = snapshot
9676 .buffer_snapshot
9677 .anchor_after(Point::new(row, line_len));
9678
9679 let bp = self
9680 .breakpoint_store
9681 .as_ref()?
9682 .read_with(cx, |breakpoint_store, cx| {
9683 breakpoint_store
9684 .breakpoints(
9685 &buffer,
9686 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9687 &buffer_snapshot,
9688 cx,
9689 )
9690 .next()
9691 .and_then(|(bp, _)| {
9692 let breakpoint_row = buffer_snapshot
9693 .summary_for_anchor::<text::PointUtf16>(&bp.position)
9694 .row;
9695
9696 if breakpoint_row == row {
9697 snapshot
9698 .buffer_snapshot
9699 .anchor_in_excerpt(enclosing_excerpt, bp.position)
9700 .map(|position| (position, bp.bp.clone()))
9701 } else {
9702 None
9703 }
9704 })
9705 });
9706 bp
9707 }
9708
9709 pub fn edit_log_breakpoint(
9710 &mut self,
9711 _: &EditLogBreakpoint,
9712 window: &mut Window,
9713 cx: &mut Context<Self>,
9714 ) {
9715 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9716 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9717 message: None,
9718 state: BreakpointState::Enabled,
9719 condition: None,
9720 hit_condition: None,
9721 });
9722
9723 self.add_edit_breakpoint_block(
9724 anchor,
9725 &breakpoint,
9726 BreakpointPromptEditAction::Log,
9727 window,
9728 cx,
9729 );
9730 }
9731 }
9732
9733 fn breakpoints_at_cursors(
9734 &self,
9735 window: &mut Window,
9736 cx: &mut Context<Self>,
9737 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9738 let snapshot = self.snapshot(window, cx);
9739 let cursors = self
9740 .selections
9741 .disjoint_anchors()
9742 .into_iter()
9743 .map(|selection| {
9744 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9745
9746 let breakpoint_position = self
9747 .breakpoint_at_row(cursor_position.row, window, cx)
9748 .map(|bp| bp.0)
9749 .unwrap_or_else(|| {
9750 snapshot
9751 .display_snapshot
9752 .buffer_snapshot
9753 .anchor_after(Point::new(cursor_position.row, 0))
9754 });
9755
9756 let breakpoint = self
9757 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9758 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9759
9760 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9761 })
9762 // 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.
9763 .collect::<HashMap<Anchor, _>>();
9764
9765 cursors.into_iter().collect()
9766 }
9767
9768 pub fn enable_breakpoint(
9769 &mut self,
9770 _: &crate::actions::EnableBreakpoint,
9771 window: &mut Window,
9772 cx: &mut Context<Self>,
9773 ) {
9774 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9775 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9776 continue;
9777 };
9778 self.edit_breakpoint_at_anchor(
9779 anchor,
9780 breakpoint,
9781 BreakpointEditAction::InvertState,
9782 cx,
9783 );
9784 }
9785 }
9786
9787 pub fn disable_breakpoint(
9788 &mut self,
9789 _: &crate::actions::DisableBreakpoint,
9790 window: &mut Window,
9791 cx: &mut Context<Self>,
9792 ) {
9793 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9794 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9795 continue;
9796 };
9797 self.edit_breakpoint_at_anchor(
9798 anchor,
9799 breakpoint,
9800 BreakpointEditAction::InvertState,
9801 cx,
9802 );
9803 }
9804 }
9805
9806 pub fn toggle_breakpoint(
9807 &mut self,
9808 _: &crate::actions::ToggleBreakpoint,
9809 window: &mut Window,
9810 cx: &mut Context<Self>,
9811 ) {
9812 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9813 if let Some(breakpoint) = breakpoint {
9814 self.edit_breakpoint_at_anchor(
9815 anchor,
9816 breakpoint,
9817 BreakpointEditAction::Toggle,
9818 cx,
9819 );
9820 } else {
9821 self.edit_breakpoint_at_anchor(
9822 anchor,
9823 Breakpoint::new_standard(),
9824 BreakpointEditAction::Toggle,
9825 cx,
9826 );
9827 }
9828 }
9829 }
9830
9831 pub fn edit_breakpoint_at_anchor(
9832 &mut self,
9833 breakpoint_position: Anchor,
9834 breakpoint: Breakpoint,
9835 edit_action: BreakpointEditAction,
9836 cx: &mut Context<Self>,
9837 ) {
9838 let Some(breakpoint_store) = &self.breakpoint_store else {
9839 return;
9840 };
9841
9842 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9843 if breakpoint_position == Anchor::min() {
9844 self.buffer()
9845 .read(cx)
9846 .excerpt_buffer_ids()
9847 .into_iter()
9848 .next()
9849 } else {
9850 None
9851 }
9852 }) else {
9853 return;
9854 };
9855
9856 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9857 return;
9858 };
9859
9860 breakpoint_store.update(cx, |breakpoint_store, cx| {
9861 breakpoint_store.toggle_breakpoint(
9862 buffer,
9863 BreakpointWithPosition {
9864 position: breakpoint_position.text_anchor,
9865 bp: breakpoint,
9866 },
9867 edit_action,
9868 cx,
9869 );
9870 });
9871
9872 cx.notify();
9873 }
9874
9875 #[cfg(any(test, feature = "test-support"))]
9876 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9877 self.breakpoint_store.clone()
9878 }
9879
9880 pub fn prepare_restore_change(
9881 &self,
9882 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9883 hunk: &MultiBufferDiffHunk,
9884 cx: &mut App,
9885 ) -> Option<()> {
9886 if hunk.is_created_file() {
9887 return None;
9888 }
9889 let buffer = self.buffer.read(cx);
9890 let diff = buffer.diff_for(hunk.buffer_id)?;
9891 let buffer = buffer.buffer(hunk.buffer_id)?;
9892 let buffer = buffer.read(cx);
9893 let original_text = diff
9894 .read(cx)
9895 .base_text()
9896 .as_rope()
9897 .slice(hunk.diff_base_byte_range.clone());
9898 let buffer_snapshot = buffer.snapshot();
9899 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9900 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9901 probe
9902 .0
9903 .start
9904 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9905 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9906 }) {
9907 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9908 Some(())
9909 } else {
9910 None
9911 }
9912 }
9913
9914 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9915 self.manipulate_lines(window, cx, |lines| lines.reverse())
9916 }
9917
9918 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9919 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9920 }
9921
9922 fn manipulate_lines<Fn>(
9923 &mut self,
9924 window: &mut Window,
9925 cx: &mut Context<Self>,
9926 mut callback: Fn,
9927 ) where
9928 Fn: FnMut(&mut Vec<&str>),
9929 {
9930 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9931
9932 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9933 let buffer = self.buffer.read(cx).snapshot(cx);
9934
9935 let mut edits = Vec::new();
9936
9937 let selections = self.selections.all::<Point>(cx);
9938 let mut selections = selections.iter().peekable();
9939 let mut contiguous_row_selections = Vec::new();
9940 let mut new_selections = Vec::new();
9941 let mut added_lines = 0;
9942 let mut removed_lines = 0;
9943
9944 while let Some(selection) = selections.next() {
9945 let (start_row, end_row) = consume_contiguous_rows(
9946 &mut contiguous_row_selections,
9947 selection,
9948 &display_map,
9949 &mut selections,
9950 );
9951
9952 let start_point = Point::new(start_row.0, 0);
9953 let end_point = Point::new(
9954 end_row.previous_row().0,
9955 buffer.line_len(end_row.previous_row()),
9956 );
9957 let text = buffer
9958 .text_for_range(start_point..end_point)
9959 .collect::<String>();
9960
9961 let mut lines = text.split('\n').collect_vec();
9962
9963 let lines_before = lines.len();
9964 callback(&mut lines);
9965 let lines_after = lines.len();
9966
9967 edits.push((start_point..end_point, lines.join("\n")));
9968
9969 // Selections must change based on added and removed line count
9970 let start_row =
9971 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9972 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9973 new_selections.push(Selection {
9974 id: selection.id,
9975 start: start_row,
9976 end: end_row,
9977 goal: SelectionGoal::None,
9978 reversed: selection.reversed,
9979 });
9980
9981 if lines_after > lines_before {
9982 added_lines += lines_after - lines_before;
9983 } else if lines_before > lines_after {
9984 removed_lines += lines_before - lines_after;
9985 }
9986 }
9987
9988 self.transact(window, cx, |this, window, cx| {
9989 let buffer = this.buffer.update(cx, |buffer, cx| {
9990 buffer.edit(edits, None, cx);
9991 buffer.snapshot(cx)
9992 });
9993
9994 // Recalculate offsets on newly edited buffer
9995 let new_selections = new_selections
9996 .iter()
9997 .map(|s| {
9998 let start_point = Point::new(s.start.0, 0);
9999 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10000 Selection {
10001 id: s.id,
10002 start: buffer.point_to_offset(start_point),
10003 end: buffer.point_to_offset(end_point),
10004 goal: s.goal,
10005 reversed: s.reversed,
10006 }
10007 })
10008 .collect();
10009
10010 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10011 s.select(new_selections);
10012 });
10013
10014 this.request_autoscroll(Autoscroll::fit(), cx);
10015 });
10016 }
10017
10018 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10019 self.manipulate_text(window, cx, |text| {
10020 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10021 if has_upper_case_characters {
10022 text.to_lowercase()
10023 } else {
10024 text.to_uppercase()
10025 }
10026 })
10027 }
10028
10029 pub fn convert_to_upper_case(
10030 &mut self,
10031 _: &ConvertToUpperCase,
10032 window: &mut Window,
10033 cx: &mut Context<Self>,
10034 ) {
10035 self.manipulate_text(window, cx, |text| text.to_uppercase())
10036 }
10037
10038 pub fn convert_to_lower_case(
10039 &mut self,
10040 _: &ConvertToLowerCase,
10041 window: &mut Window,
10042 cx: &mut Context<Self>,
10043 ) {
10044 self.manipulate_text(window, cx, |text| text.to_lowercase())
10045 }
10046
10047 pub fn convert_to_title_case(
10048 &mut self,
10049 _: &ConvertToTitleCase,
10050 window: &mut Window,
10051 cx: &mut Context<Self>,
10052 ) {
10053 self.manipulate_text(window, cx, |text| {
10054 text.split('\n')
10055 .map(|line| line.to_case(Case::Title))
10056 .join("\n")
10057 })
10058 }
10059
10060 pub fn convert_to_snake_case(
10061 &mut self,
10062 _: &ConvertToSnakeCase,
10063 window: &mut Window,
10064 cx: &mut Context<Self>,
10065 ) {
10066 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10067 }
10068
10069 pub fn convert_to_kebab_case(
10070 &mut self,
10071 _: &ConvertToKebabCase,
10072 window: &mut Window,
10073 cx: &mut Context<Self>,
10074 ) {
10075 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10076 }
10077
10078 pub fn convert_to_upper_camel_case(
10079 &mut self,
10080 _: &ConvertToUpperCamelCase,
10081 window: &mut Window,
10082 cx: &mut Context<Self>,
10083 ) {
10084 self.manipulate_text(window, cx, |text| {
10085 text.split('\n')
10086 .map(|line| line.to_case(Case::UpperCamel))
10087 .join("\n")
10088 })
10089 }
10090
10091 pub fn convert_to_lower_camel_case(
10092 &mut self,
10093 _: &ConvertToLowerCamelCase,
10094 window: &mut Window,
10095 cx: &mut Context<Self>,
10096 ) {
10097 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10098 }
10099
10100 pub fn convert_to_opposite_case(
10101 &mut self,
10102 _: &ConvertToOppositeCase,
10103 window: &mut Window,
10104 cx: &mut Context<Self>,
10105 ) {
10106 self.manipulate_text(window, cx, |text| {
10107 text.chars()
10108 .fold(String::with_capacity(text.len()), |mut t, c| {
10109 if c.is_uppercase() {
10110 t.extend(c.to_lowercase());
10111 } else {
10112 t.extend(c.to_uppercase());
10113 }
10114 t
10115 })
10116 })
10117 }
10118
10119 pub fn convert_to_rot13(
10120 &mut self,
10121 _: &ConvertToRot13,
10122 window: &mut Window,
10123 cx: &mut Context<Self>,
10124 ) {
10125 self.manipulate_text(window, cx, |text| {
10126 text.chars()
10127 .map(|c| match c {
10128 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10129 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10130 _ => c,
10131 })
10132 .collect()
10133 })
10134 }
10135
10136 pub fn convert_to_rot47(
10137 &mut self,
10138 _: &ConvertToRot47,
10139 window: &mut Window,
10140 cx: &mut Context<Self>,
10141 ) {
10142 self.manipulate_text(window, cx, |text| {
10143 text.chars()
10144 .map(|c| {
10145 let code_point = c as u32;
10146 if code_point >= 33 && code_point <= 126 {
10147 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10148 }
10149 c
10150 })
10151 .collect()
10152 })
10153 }
10154
10155 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10156 where
10157 Fn: FnMut(&str) -> String,
10158 {
10159 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10160 let buffer = self.buffer.read(cx).snapshot(cx);
10161
10162 let mut new_selections = Vec::new();
10163 let mut edits = Vec::new();
10164 let mut selection_adjustment = 0i32;
10165
10166 for selection in self.selections.all::<usize>(cx) {
10167 let selection_is_empty = selection.is_empty();
10168
10169 let (start, end) = if selection_is_empty {
10170 let word_range = movement::surrounding_word(
10171 &display_map,
10172 selection.start.to_display_point(&display_map),
10173 );
10174 let start = word_range.start.to_offset(&display_map, Bias::Left);
10175 let end = word_range.end.to_offset(&display_map, Bias::Left);
10176 (start, end)
10177 } else {
10178 (selection.start, selection.end)
10179 };
10180
10181 let text = buffer.text_for_range(start..end).collect::<String>();
10182 let old_length = text.len() as i32;
10183 let text = callback(&text);
10184
10185 new_selections.push(Selection {
10186 start: (start as i32 - selection_adjustment) as usize,
10187 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10188 goal: SelectionGoal::None,
10189 ..selection
10190 });
10191
10192 selection_adjustment += old_length - text.len() as i32;
10193
10194 edits.push((start..end, text));
10195 }
10196
10197 self.transact(window, cx, |this, window, cx| {
10198 this.buffer.update(cx, |buffer, cx| {
10199 buffer.edit(edits, None, cx);
10200 });
10201
10202 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10203 s.select(new_selections);
10204 });
10205
10206 this.request_autoscroll(Autoscroll::fit(), cx);
10207 });
10208 }
10209
10210 pub fn duplicate(
10211 &mut self,
10212 upwards: bool,
10213 whole_lines: bool,
10214 window: &mut Window,
10215 cx: &mut Context<Self>,
10216 ) {
10217 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10218
10219 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10220 let buffer = &display_map.buffer_snapshot;
10221 let selections = self.selections.all::<Point>(cx);
10222
10223 let mut edits = Vec::new();
10224 let mut selections_iter = selections.iter().peekable();
10225 while let Some(selection) = selections_iter.next() {
10226 let mut rows = selection.spanned_rows(false, &display_map);
10227 // duplicate line-wise
10228 if whole_lines || selection.start == selection.end {
10229 // Avoid duplicating the same lines twice.
10230 while let Some(next_selection) = selections_iter.peek() {
10231 let next_rows = next_selection.spanned_rows(false, &display_map);
10232 if next_rows.start < rows.end {
10233 rows.end = next_rows.end;
10234 selections_iter.next().unwrap();
10235 } else {
10236 break;
10237 }
10238 }
10239
10240 // Copy the text from the selected row region and splice it either at the start
10241 // or end of the region.
10242 let start = Point::new(rows.start.0, 0);
10243 let end = Point::new(
10244 rows.end.previous_row().0,
10245 buffer.line_len(rows.end.previous_row()),
10246 );
10247 let text = buffer
10248 .text_for_range(start..end)
10249 .chain(Some("\n"))
10250 .collect::<String>();
10251 let insert_location = if upwards {
10252 Point::new(rows.end.0, 0)
10253 } else {
10254 start
10255 };
10256 edits.push((insert_location..insert_location, text));
10257 } else {
10258 // duplicate character-wise
10259 let start = selection.start;
10260 let end = selection.end;
10261 let text = buffer.text_for_range(start..end).collect::<String>();
10262 edits.push((selection.end..selection.end, text));
10263 }
10264 }
10265
10266 self.transact(window, cx, |this, _, cx| {
10267 this.buffer.update(cx, |buffer, cx| {
10268 buffer.edit(edits, None, cx);
10269 });
10270
10271 this.request_autoscroll(Autoscroll::fit(), cx);
10272 });
10273 }
10274
10275 pub fn duplicate_line_up(
10276 &mut self,
10277 _: &DuplicateLineUp,
10278 window: &mut Window,
10279 cx: &mut Context<Self>,
10280 ) {
10281 self.duplicate(true, true, window, cx);
10282 }
10283
10284 pub fn duplicate_line_down(
10285 &mut self,
10286 _: &DuplicateLineDown,
10287 window: &mut Window,
10288 cx: &mut Context<Self>,
10289 ) {
10290 self.duplicate(false, true, window, cx);
10291 }
10292
10293 pub fn duplicate_selection(
10294 &mut self,
10295 _: &DuplicateSelection,
10296 window: &mut Window,
10297 cx: &mut Context<Self>,
10298 ) {
10299 self.duplicate(false, false, window, cx);
10300 }
10301
10302 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10303 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10304
10305 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10306 let buffer = self.buffer.read(cx).snapshot(cx);
10307
10308 let mut edits = Vec::new();
10309 let mut unfold_ranges = Vec::new();
10310 let mut refold_creases = Vec::new();
10311
10312 let selections = self.selections.all::<Point>(cx);
10313 let mut selections = selections.iter().peekable();
10314 let mut contiguous_row_selections = Vec::new();
10315 let mut new_selections = Vec::new();
10316
10317 while let Some(selection) = selections.next() {
10318 // Find all the selections that span a contiguous row range
10319 let (start_row, end_row) = consume_contiguous_rows(
10320 &mut contiguous_row_selections,
10321 selection,
10322 &display_map,
10323 &mut selections,
10324 );
10325
10326 // Move the text spanned by the row range to be before the line preceding the row range
10327 if start_row.0 > 0 {
10328 let range_to_move = Point::new(
10329 start_row.previous_row().0,
10330 buffer.line_len(start_row.previous_row()),
10331 )
10332 ..Point::new(
10333 end_row.previous_row().0,
10334 buffer.line_len(end_row.previous_row()),
10335 );
10336 let insertion_point = display_map
10337 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10338 .0;
10339
10340 // Don't move lines across excerpts
10341 if buffer
10342 .excerpt_containing(insertion_point..range_to_move.end)
10343 .is_some()
10344 {
10345 let text = buffer
10346 .text_for_range(range_to_move.clone())
10347 .flat_map(|s| s.chars())
10348 .skip(1)
10349 .chain(['\n'])
10350 .collect::<String>();
10351
10352 edits.push((
10353 buffer.anchor_after(range_to_move.start)
10354 ..buffer.anchor_before(range_to_move.end),
10355 String::new(),
10356 ));
10357 let insertion_anchor = buffer.anchor_after(insertion_point);
10358 edits.push((insertion_anchor..insertion_anchor, text));
10359
10360 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10361
10362 // Move selections up
10363 new_selections.extend(contiguous_row_selections.drain(..).map(
10364 |mut selection| {
10365 selection.start.row -= row_delta;
10366 selection.end.row -= row_delta;
10367 selection
10368 },
10369 ));
10370
10371 // Move folds up
10372 unfold_ranges.push(range_to_move.clone());
10373 for fold in display_map.folds_in_range(
10374 buffer.anchor_before(range_to_move.start)
10375 ..buffer.anchor_after(range_to_move.end),
10376 ) {
10377 let mut start = fold.range.start.to_point(&buffer);
10378 let mut end = fold.range.end.to_point(&buffer);
10379 start.row -= row_delta;
10380 end.row -= row_delta;
10381 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10382 }
10383 }
10384 }
10385
10386 // If we didn't move line(s), preserve the existing selections
10387 new_selections.append(&mut contiguous_row_selections);
10388 }
10389
10390 self.transact(window, cx, |this, window, cx| {
10391 this.unfold_ranges(&unfold_ranges, true, true, cx);
10392 this.buffer.update(cx, |buffer, cx| {
10393 for (range, text) in edits {
10394 buffer.edit([(range, text)], None, cx);
10395 }
10396 });
10397 this.fold_creases(refold_creases, true, window, cx);
10398 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10399 s.select(new_selections);
10400 })
10401 });
10402 }
10403
10404 pub fn move_line_down(
10405 &mut self,
10406 _: &MoveLineDown,
10407 window: &mut Window,
10408 cx: &mut Context<Self>,
10409 ) {
10410 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10411
10412 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10413 let buffer = self.buffer.read(cx).snapshot(cx);
10414
10415 let mut edits = Vec::new();
10416 let mut unfold_ranges = Vec::new();
10417 let mut refold_creases = Vec::new();
10418
10419 let selections = self.selections.all::<Point>(cx);
10420 let mut selections = selections.iter().peekable();
10421 let mut contiguous_row_selections = Vec::new();
10422 let mut new_selections = Vec::new();
10423
10424 while let Some(selection) = selections.next() {
10425 // Find all the selections that span a contiguous row range
10426 let (start_row, end_row) = consume_contiguous_rows(
10427 &mut contiguous_row_selections,
10428 selection,
10429 &display_map,
10430 &mut selections,
10431 );
10432
10433 // Move the text spanned by the row range to be after the last line of the row range
10434 if end_row.0 <= buffer.max_point().row {
10435 let range_to_move =
10436 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10437 let insertion_point = display_map
10438 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10439 .0;
10440
10441 // Don't move lines across excerpt boundaries
10442 if buffer
10443 .excerpt_containing(range_to_move.start..insertion_point)
10444 .is_some()
10445 {
10446 let mut text = String::from("\n");
10447 text.extend(buffer.text_for_range(range_to_move.clone()));
10448 text.pop(); // Drop trailing newline
10449 edits.push((
10450 buffer.anchor_after(range_to_move.start)
10451 ..buffer.anchor_before(range_to_move.end),
10452 String::new(),
10453 ));
10454 let insertion_anchor = buffer.anchor_after(insertion_point);
10455 edits.push((insertion_anchor..insertion_anchor, text));
10456
10457 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10458
10459 // Move selections down
10460 new_selections.extend(contiguous_row_selections.drain(..).map(
10461 |mut selection| {
10462 selection.start.row += row_delta;
10463 selection.end.row += row_delta;
10464 selection
10465 },
10466 ));
10467
10468 // Move folds down
10469 unfold_ranges.push(range_to_move.clone());
10470 for fold in display_map.folds_in_range(
10471 buffer.anchor_before(range_to_move.start)
10472 ..buffer.anchor_after(range_to_move.end),
10473 ) {
10474 let mut start = fold.range.start.to_point(&buffer);
10475 let mut end = fold.range.end.to_point(&buffer);
10476 start.row += row_delta;
10477 end.row += row_delta;
10478 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10479 }
10480 }
10481 }
10482
10483 // If we didn't move line(s), preserve the existing selections
10484 new_selections.append(&mut contiguous_row_selections);
10485 }
10486
10487 self.transact(window, cx, |this, window, cx| {
10488 this.unfold_ranges(&unfold_ranges, true, true, cx);
10489 this.buffer.update(cx, |buffer, cx| {
10490 for (range, text) in edits {
10491 buffer.edit([(range, text)], None, cx);
10492 }
10493 });
10494 this.fold_creases(refold_creases, true, window, cx);
10495 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10496 s.select(new_selections)
10497 });
10498 });
10499 }
10500
10501 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10502 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10503 let text_layout_details = &self.text_layout_details(window);
10504 self.transact(window, cx, |this, window, cx| {
10505 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10506 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10507 s.move_with(|display_map, selection| {
10508 if !selection.is_empty() {
10509 return;
10510 }
10511
10512 let mut head = selection.head();
10513 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10514 if head.column() == display_map.line_len(head.row()) {
10515 transpose_offset = display_map
10516 .buffer_snapshot
10517 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10518 }
10519
10520 if transpose_offset == 0 {
10521 return;
10522 }
10523
10524 *head.column_mut() += 1;
10525 head = display_map.clip_point(head, Bias::Right);
10526 let goal = SelectionGoal::HorizontalPosition(
10527 display_map
10528 .x_for_display_point(head, text_layout_details)
10529 .into(),
10530 );
10531 selection.collapse_to(head, goal);
10532
10533 let transpose_start = display_map
10534 .buffer_snapshot
10535 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10536 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10537 let transpose_end = display_map
10538 .buffer_snapshot
10539 .clip_offset(transpose_offset + 1, Bias::Right);
10540 if let Some(ch) =
10541 display_map.buffer_snapshot.chars_at(transpose_start).next()
10542 {
10543 edits.push((transpose_start..transpose_offset, String::new()));
10544 edits.push((transpose_end..transpose_end, ch.to_string()));
10545 }
10546 }
10547 });
10548 edits
10549 });
10550 this.buffer
10551 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10552 let selections = this.selections.all::<usize>(cx);
10553 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10554 s.select(selections);
10555 });
10556 });
10557 }
10558
10559 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10560 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10561 self.rewrap_impl(RewrapOptions::default(), cx)
10562 }
10563
10564 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10565 let buffer = self.buffer.read(cx).snapshot(cx);
10566 let selections = self.selections.all::<Point>(cx);
10567 let mut selections = selections.iter().peekable();
10568
10569 let mut edits = Vec::new();
10570 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10571
10572 while let Some(selection) = selections.next() {
10573 let mut start_row = selection.start.row;
10574 let mut end_row = selection.end.row;
10575
10576 // Skip selections that overlap with a range that has already been rewrapped.
10577 let selection_range = start_row..end_row;
10578 if rewrapped_row_ranges
10579 .iter()
10580 .any(|range| range.overlaps(&selection_range))
10581 {
10582 continue;
10583 }
10584
10585 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10586
10587 // Since not all lines in the selection may be at the same indent
10588 // level, choose the indent size that is the most common between all
10589 // of the lines.
10590 //
10591 // If there is a tie, we use the deepest indent.
10592 let (indent_size, indent_end) = {
10593 let mut indent_size_occurrences = HashMap::default();
10594 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10595
10596 for row in start_row..=end_row {
10597 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10598 rows_by_indent_size.entry(indent).or_default().push(row);
10599 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10600 }
10601
10602 let indent_size = indent_size_occurrences
10603 .into_iter()
10604 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10605 .map(|(indent, _)| indent)
10606 .unwrap_or_default();
10607 let row = rows_by_indent_size[&indent_size][0];
10608 let indent_end = Point::new(row, indent_size.len);
10609
10610 (indent_size, indent_end)
10611 };
10612
10613 let mut line_prefix = indent_size.chars().collect::<String>();
10614
10615 let mut inside_comment = false;
10616 if let Some(comment_prefix) =
10617 buffer
10618 .language_scope_at(selection.head())
10619 .and_then(|language| {
10620 language
10621 .line_comment_prefixes()
10622 .iter()
10623 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10624 .cloned()
10625 })
10626 {
10627 line_prefix.push_str(&comment_prefix);
10628 inside_comment = true;
10629 }
10630
10631 let language_settings = buffer.language_settings_at(selection.head(), cx);
10632 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10633 RewrapBehavior::InComments => inside_comment,
10634 RewrapBehavior::InSelections => !selection.is_empty(),
10635 RewrapBehavior::Anywhere => true,
10636 };
10637
10638 let should_rewrap = options.override_language_settings
10639 || allow_rewrap_based_on_language
10640 || self.hard_wrap.is_some();
10641 if !should_rewrap {
10642 continue;
10643 }
10644
10645 if selection.is_empty() {
10646 'expand_upwards: while start_row > 0 {
10647 let prev_row = start_row - 1;
10648 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10649 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10650 {
10651 start_row = prev_row;
10652 } else {
10653 break 'expand_upwards;
10654 }
10655 }
10656
10657 'expand_downwards: while end_row < buffer.max_point().row {
10658 let next_row = end_row + 1;
10659 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10660 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10661 {
10662 end_row = next_row;
10663 } else {
10664 break 'expand_downwards;
10665 }
10666 }
10667 }
10668
10669 let start = Point::new(start_row, 0);
10670 let start_offset = start.to_offset(&buffer);
10671 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10672 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10673 let Some(lines_without_prefixes) = selection_text
10674 .lines()
10675 .map(|line| {
10676 line.strip_prefix(&line_prefix)
10677 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10678 .with_context(|| {
10679 format!("line did not start with prefix {line_prefix:?}: {line:?}")
10680 })
10681 })
10682 .collect::<Result<Vec<_>, _>>()
10683 .log_err()
10684 else {
10685 continue;
10686 };
10687
10688 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10689 buffer
10690 .language_settings_at(Point::new(start_row, 0), cx)
10691 .preferred_line_length as usize
10692 });
10693 let wrapped_text = wrap_with_prefix(
10694 line_prefix,
10695 lines_without_prefixes.join("\n"),
10696 wrap_column,
10697 tab_size,
10698 options.preserve_existing_whitespace,
10699 );
10700
10701 // TODO: should always use char-based diff while still supporting cursor behavior that
10702 // matches vim.
10703 let mut diff_options = DiffOptions::default();
10704 if options.override_language_settings {
10705 diff_options.max_word_diff_len = 0;
10706 diff_options.max_word_diff_line_count = 0;
10707 } else {
10708 diff_options.max_word_diff_len = usize::MAX;
10709 diff_options.max_word_diff_line_count = usize::MAX;
10710 }
10711
10712 for (old_range, new_text) in
10713 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10714 {
10715 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10716 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10717 edits.push((edit_start..edit_end, new_text));
10718 }
10719
10720 rewrapped_row_ranges.push(start_row..=end_row);
10721 }
10722
10723 self.buffer
10724 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10725 }
10726
10727 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10728 let mut text = String::new();
10729 let buffer = self.buffer.read(cx).snapshot(cx);
10730 let mut selections = self.selections.all::<Point>(cx);
10731 let mut clipboard_selections = Vec::with_capacity(selections.len());
10732 {
10733 let max_point = buffer.max_point();
10734 let mut is_first = true;
10735 for selection in &mut selections {
10736 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10737 if is_entire_line {
10738 selection.start = Point::new(selection.start.row, 0);
10739 if !selection.is_empty() && selection.end.column == 0 {
10740 selection.end = cmp::min(max_point, selection.end);
10741 } else {
10742 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10743 }
10744 selection.goal = SelectionGoal::None;
10745 }
10746 if is_first {
10747 is_first = false;
10748 } else {
10749 text += "\n";
10750 }
10751 let mut len = 0;
10752 for chunk in buffer.text_for_range(selection.start..selection.end) {
10753 text.push_str(chunk);
10754 len += chunk.len();
10755 }
10756 clipboard_selections.push(ClipboardSelection {
10757 len,
10758 is_entire_line,
10759 first_line_indent: buffer
10760 .indent_size_for_line(MultiBufferRow(selection.start.row))
10761 .len,
10762 });
10763 }
10764 }
10765
10766 self.transact(window, cx, |this, window, cx| {
10767 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10768 s.select(selections);
10769 });
10770 this.insert("", window, cx);
10771 });
10772 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10773 }
10774
10775 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10776 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10777 let item = self.cut_common(window, cx);
10778 cx.write_to_clipboard(item);
10779 }
10780
10781 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10782 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10783 self.change_selections(None, window, cx, |s| {
10784 s.move_with(|snapshot, sel| {
10785 if sel.is_empty() {
10786 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10787 }
10788 });
10789 });
10790 let item = self.cut_common(window, cx);
10791 cx.set_global(KillRing(item))
10792 }
10793
10794 pub fn kill_ring_yank(
10795 &mut self,
10796 _: &KillRingYank,
10797 window: &mut Window,
10798 cx: &mut Context<Self>,
10799 ) {
10800 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10801 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10802 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10803 (kill_ring.text().to_string(), kill_ring.metadata_json())
10804 } else {
10805 return;
10806 }
10807 } else {
10808 return;
10809 };
10810 self.do_paste(&text, metadata, false, window, cx);
10811 }
10812
10813 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10814 self.do_copy(true, cx);
10815 }
10816
10817 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10818 self.do_copy(false, cx);
10819 }
10820
10821 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10822 let selections = self.selections.all::<Point>(cx);
10823 let buffer = self.buffer.read(cx).read(cx);
10824 let mut text = String::new();
10825
10826 let mut clipboard_selections = Vec::with_capacity(selections.len());
10827 {
10828 let max_point = buffer.max_point();
10829 let mut is_first = true;
10830 for selection in &selections {
10831 let mut start = selection.start;
10832 let mut end = selection.end;
10833 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10834 if is_entire_line {
10835 start = Point::new(start.row, 0);
10836 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10837 }
10838
10839 let mut trimmed_selections = Vec::new();
10840 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10841 let row = MultiBufferRow(start.row);
10842 let first_indent = buffer.indent_size_for_line(row);
10843 if first_indent.len == 0 || start.column > first_indent.len {
10844 trimmed_selections.push(start..end);
10845 } else {
10846 trimmed_selections.push(
10847 Point::new(row.0, first_indent.len)
10848 ..Point::new(row.0, buffer.line_len(row)),
10849 );
10850 for row in start.row + 1..=end.row {
10851 let mut line_len = buffer.line_len(MultiBufferRow(row));
10852 if row == end.row {
10853 line_len = end.column;
10854 }
10855 if line_len == 0 {
10856 trimmed_selections
10857 .push(Point::new(row, 0)..Point::new(row, line_len));
10858 continue;
10859 }
10860 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10861 if row_indent_size.len >= first_indent.len {
10862 trimmed_selections.push(
10863 Point::new(row, first_indent.len)..Point::new(row, line_len),
10864 );
10865 } else {
10866 trimmed_selections.clear();
10867 trimmed_selections.push(start..end);
10868 break;
10869 }
10870 }
10871 }
10872 } else {
10873 trimmed_selections.push(start..end);
10874 }
10875
10876 for trimmed_range in trimmed_selections {
10877 if is_first {
10878 is_first = false;
10879 } else {
10880 text += "\n";
10881 }
10882 let mut len = 0;
10883 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10884 text.push_str(chunk);
10885 len += chunk.len();
10886 }
10887 clipboard_selections.push(ClipboardSelection {
10888 len,
10889 is_entire_line,
10890 first_line_indent: buffer
10891 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10892 .len,
10893 });
10894 }
10895 }
10896 }
10897
10898 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10899 text,
10900 clipboard_selections,
10901 ));
10902 }
10903
10904 pub fn do_paste(
10905 &mut self,
10906 text: &String,
10907 clipboard_selections: Option<Vec<ClipboardSelection>>,
10908 handle_entire_lines: bool,
10909 window: &mut Window,
10910 cx: &mut Context<Self>,
10911 ) {
10912 if self.read_only(cx) {
10913 return;
10914 }
10915
10916 let clipboard_text = Cow::Borrowed(text);
10917
10918 self.transact(window, cx, |this, window, cx| {
10919 if let Some(mut clipboard_selections) = clipboard_selections {
10920 let old_selections = this.selections.all::<usize>(cx);
10921 let all_selections_were_entire_line =
10922 clipboard_selections.iter().all(|s| s.is_entire_line);
10923 let first_selection_indent_column =
10924 clipboard_selections.first().map(|s| s.first_line_indent);
10925 if clipboard_selections.len() != old_selections.len() {
10926 clipboard_selections.drain(..);
10927 }
10928 let cursor_offset = this.selections.last::<usize>(cx).head();
10929 let mut auto_indent_on_paste = true;
10930
10931 this.buffer.update(cx, |buffer, cx| {
10932 let snapshot = buffer.read(cx);
10933 auto_indent_on_paste = snapshot
10934 .language_settings_at(cursor_offset, cx)
10935 .auto_indent_on_paste;
10936
10937 let mut start_offset = 0;
10938 let mut edits = Vec::new();
10939 let mut original_indent_columns = Vec::new();
10940 for (ix, selection) in old_selections.iter().enumerate() {
10941 let to_insert;
10942 let entire_line;
10943 let original_indent_column;
10944 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10945 let end_offset = start_offset + clipboard_selection.len;
10946 to_insert = &clipboard_text[start_offset..end_offset];
10947 entire_line = clipboard_selection.is_entire_line;
10948 start_offset = end_offset + 1;
10949 original_indent_column = Some(clipboard_selection.first_line_indent);
10950 } else {
10951 to_insert = clipboard_text.as_str();
10952 entire_line = all_selections_were_entire_line;
10953 original_indent_column = first_selection_indent_column
10954 }
10955
10956 // If the corresponding selection was empty when this slice of the
10957 // clipboard text was written, then the entire line containing the
10958 // selection was copied. If this selection is also currently empty,
10959 // then paste the line before the current line of the buffer.
10960 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10961 let column = selection.start.to_point(&snapshot).column as usize;
10962 let line_start = selection.start - column;
10963 line_start..line_start
10964 } else {
10965 selection.range()
10966 };
10967
10968 edits.push((range, to_insert));
10969 original_indent_columns.push(original_indent_column);
10970 }
10971 drop(snapshot);
10972
10973 buffer.edit(
10974 edits,
10975 if auto_indent_on_paste {
10976 Some(AutoindentMode::Block {
10977 original_indent_columns,
10978 })
10979 } else {
10980 None
10981 },
10982 cx,
10983 );
10984 });
10985
10986 let selections = this.selections.all::<usize>(cx);
10987 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10988 s.select(selections)
10989 });
10990 } else {
10991 this.insert(&clipboard_text, window, cx);
10992 }
10993 });
10994 }
10995
10996 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10997 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10998 if let Some(item) = cx.read_from_clipboard() {
10999 let entries = item.entries();
11000
11001 match entries.first() {
11002 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11003 // of all the pasted entries.
11004 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11005 .do_paste(
11006 clipboard_string.text(),
11007 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11008 true,
11009 window,
11010 cx,
11011 ),
11012 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11013 }
11014 }
11015 }
11016
11017 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11018 if self.read_only(cx) {
11019 return;
11020 }
11021
11022 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11023
11024 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11025 if let Some((selections, _)) =
11026 self.selection_history.transaction(transaction_id).cloned()
11027 {
11028 self.change_selections(None, window, cx, |s| {
11029 s.select_anchors(selections.to_vec());
11030 });
11031 } else {
11032 log::error!(
11033 "No entry in selection_history found for undo. \
11034 This may correspond to a bug where undo does not update the selection. \
11035 If this is occurring, please add details to \
11036 https://github.com/zed-industries/zed/issues/22692"
11037 );
11038 }
11039 self.request_autoscroll(Autoscroll::fit(), cx);
11040 self.unmark_text(window, cx);
11041 self.refresh_inline_completion(true, false, window, cx);
11042 cx.emit(EditorEvent::Edited { transaction_id });
11043 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11044 }
11045 }
11046
11047 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11048 if self.read_only(cx) {
11049 return;
11050 }
11051
11052 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11053
11054 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11055 if let Some((_, Some(selections))) =
11056 self.selection_history.transaction(transaction_id).cloned()
11057 {
11058 self.change_selections(None, window, cx, |s| {
11059 s.select_anchors(selections.to_vec());
11060 });
11061 } else {
11062 log::error!(
11063 "No entry in selection_history found for redo. \
11064 This may correspond to a bug where undo does not update the selection. \
11065 If this is occurring, please add details to \
11066 https://github.com/zed-industries/zed/issues/22692"
11067 );
11068 }
11069 self.request_autoscroll(Autoscroll::fit(), cx);
11070 self.unmark_text(window, cx);
11071 self.refresh_inline_completion(true, false, window, cx);
11072 cx.emit(EditorEvent::Edited { transaction_id });
11073 }
11074 }
11075
11076 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11077 self.buffer
11078 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11079 }
11080
11081 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11082 self.buffer
11083 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11084 }
11085
11086 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11087 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11088 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11089 s.move_with(|map, selection| {
11090 let cursor = if selection.is_empty() {
11091 movement::left(map, selection.start)
11092 } else {
11093 selection.start
11094 };
11095 selection.collapse_to(cursor, SelectionGoal::None);
11096 });
11097 })
11098 }
11099
11100 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11101 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11102 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11103 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11104 })
11105 }
11106
11107 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11108 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11109 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11110 s.move_with(|map, selection| {
11111 let cursor = if selection.is_empty() {
11112 movement::right(map, selection.end)
11113 } else {
11114 selection.end
11115 };
11116 selection.collapse_to(cursor, SelectionGoal::None)
11117 });
11118 })
11119 }
11120
11121 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11122 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11123 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11124 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11125 })
11126 }
11127
11128 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11129 if self.take_rename(true, window, cx).is_some() {
11130 return;
11131 }
11132
11133 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11134 cx.propagate();
11135 return;
11136 }
11137
11138 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11139
11140 let text_layout_details = &self.text_layout_details(window);
11141 let selection_count = self.selections.count();
11142 let first_selection = self.selections.first_anchor();
11143
11144 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11145 s.move_with(|map, selection| {
11146 if !selection.is_empty() {
11147 selection.goal = SelectionGoal::None;
11148 }
11149 let (cursor, goal) = movement::up(
11150 map,
11151 selection.start,
11152 selection.goal,
11153 false,
11154 text_layout_details,
11155 );
11156 selection.collapse_to(cursor, goal);
11157 });
11158 });
11159
11160 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11161 {
11162 cx.propagate();
11163 }
11164 }
11165
11166 pub fn move_up_by_lines(
11167 &mut self,
11168 action: &MoveUpByLines,
11169 window: &mut Window,
11170 cx: &mut Context<Self>,
11171 ) {
11172 if self.take_rename(true, window, cx).is_some() {
11173 return;
11174 }
11175
11176 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11177 cx.propagate();
11178 return;
11179 }
11180
11181 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11182
11183 let text_layout_details = &self.text_layout_details(window);
11184
11185 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11186 s.move_with(|map, selection| {
11187 if !selection.is_empty() {
11188 selection.goal = SelectionGoal::None;
11189 }
11190 let (cursor, goal) = movement::up_by_rows(
11191 map,
11192 selection.start,
11193 action.lines,
11194 selection.goal,
11195 false,
11196 text_layout_details,
11197 );
11198 selection.collapse_to(cursor, goal);
11199 });
11200 })
11201 }
11202
11203 pub fn move_down_by_lines(
11204 &mut self,
11205 action: &MoveDownByLines,
11206 window: &mut Window,
11207 cx: &mut Context<Self>,
11208 ) {
11209 if self.take_rename(true, window, cx).is_some() {
11210 return;
11211 }
11212
11213 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11214 cx.propagate();
11215 return;
11216 }
11217
11218 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11219
11220 let text_layout_details = &self.text_layout_details(window);
11221
11222 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11223 s.move_with(|map, selection| {
11224 if !selection.is_empty() {
11225 selection.goal = SelectionGoal::None;
11226 }
11227 let (cursor, goal) = movement::down_by_rows(
11228 map,
11229 selection.start,
11230 action.lines,
11231 selection.goal,
11232 false,
11233 text_layout_details,
11234 );
11235 selection.collapse_to(cursor, goal);
11236 });
11237 })
11238 }
11239
11240 pub fn select_down_by_lines(
11241 &mut self,
11242 action: &SelectDownByLines,
11243 window: &mut Window,
11244 cx: &mut Context<Self>,
11245 ) {
11246 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11247 let text_layout_details = &self.text_layout_details(window);
11248 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11249 s.move_heads_with(|map, head, goal| {
11250 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11251 })
11252 })
11253 }
11254
11255 pub fn select_up_by_lines(
11256 &mut self,
11257 action: &SelectUpByLines,
11258 window: &mut Window,
11259 cx: &mut Context<Self>,
11260 ) {
11261 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11262 let text_layout_details = &self.text_layout_details(window);
11263 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11264 s.move_heads_with(|map, head, goal| {
11265 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11266 })
11267 })
11268 }
11269
11270 pub fn select_page_up(
11271 &mut self,
11272 _: &SelectPageUp,
11273 window: &mut Window,
11274 cx: &mut Context<Self>,
11275 ) {
11276 let Some(row_count) = self.visible_row_count() else {
11277 return;
11278 };
11279
11280 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11281
11282 let text_layout_details = &self.text_layout_details(window);
11283
11284 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11285 s.move_heads_with(|map, head, goal| {
11286 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11287 })
11288 })
11289 }
11290
11291 pub fn move_page_up(
11292 &mut self,
11293 action: &MovePageUp,
11294 window: &mut Window,
11295 cx: &mut Context<Self>,
11296 ) {
11297 if self.take_rename(true, window, cx).is_some() {
11298 return;
11299 }
11300
11301 if self
11302 .context_menu
11303 .borrow_mut()
11304 .as_mut()
11305 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
11306 .unwrap_or(false)
11307 {
11308 return;
11309 }
11310
11311 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11312 cx.propagate();
11313 return;
11314 }
11315
11316 let Some(row_count) = self.visible_row_count() else {
11317 return;
11318 };
11319
11320 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11321
11322 let autoscroll = if action.center_cursor {
11323 Autoscroll::center()
11324 } else {
11325 Autoscroll::fit()
11326 };
11327
11328 let text_layout_details = &self.text_layout_details(window);
11329
11330 self.change_selections(Some(autoscroll), window, cx, |s| {
11331 s.move_with(|map, selection| {
11332 if !selection.is_empty() {
11333 selection.goal = SelectionGoal::None;
11334 }
11335 let (cursor, goal) = movement::up_by_rows(
11336 map,
11337 selection.end,
11338 row_count,
11339 selection.goal,
11340 false,
11341 text_layout_details,
11342 );
11343 selection.collapse_to(cursor, goal);
11344 });
11345 });
11346 }
11347
11348 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11349 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11350 let text_layout_details = &self.text_layout_details(window);
11351 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11352 s.move_heads_with(|map, head, goal| {
11353 movement::up(map, head, goal, false, text_layout_details)
11354 })
11355 })
11356 }
11357
11358 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11359 self.take_rename(true, window, cx);
11360
11361 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11362 cx.propagate();
11363 return;
11364 }
11365
11366 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11367
11368 let text_layout_details = &self.text_layout_details(window);
11369 let selection_count = self.selections.count();
11370 let first_selection = self.selections.first_anchor();
11371
11372 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11373 s.move_with(|map, selection| {
11374 if !selection.is_empty() {
11375 selection.goal = SelectionGoal::None;
11376 }
11377 let (cursor, goal) = movement::down(
11378 map,
11379 selection.end,
11380 selection.goal,
11381 false,
11382 text_layout_details,
11383 );
11384 selection.collapse_to(cursor, goal);
11385 });
11386 });
11387
11388 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11389 {
11390 cx.propagate();
11391 }
11392 }
11393
11394 pub fn select_page_down(
11395 &mut self,
11396 _: &SelectPageDown,
11397 window: &mut Window,
11398 cx: &mut Context<Self>,
11399 ) {
11400 let Some(row_count) = self.visible_row_count() else {
11401 return;
11402 };
11403
11404 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11405
11406 let text_layout_details = &self.text_layout_details(window);
11407
11408 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11409 s.move_heads_with(|map, head, goal| {
11410 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11411 })
11412 })
11413 }
11414
11415 pub fn move_page_down(
11416 &mut self,
11417 action: &MovePageDown,
11418 window: &mut Window,
11419 cx: &mut Context<Self>,
11420 ) {
11421 if self.take_rename(true, window, cx).is_some() {
11422 return;
11423 }
11424
11425 if self
11426 .context_menu
11427 .borrow_mut()
11428 .as_mut()
11429 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
11430 .unwrap_or(false)
11431 {
11432 return;
11433 }
11434
11435 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11436 cx.propagate();
11437 return;
11438 }
11439
11440 let Some(row_count) = self.visible_row_count() else {
11441 return;
11442 };
11443
11444 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11445
11446 let autoscroll = if action.center_cursor {
11447 Autoscroll::center()
11448 } else {
11449 Autoscroll::fit()
11450 };
11451
11452 let text_layout_details = &self.text_layout_details(window);
11453 self.change_selections(Some(autoscroll), window, cx, |s| {
11454 s.move_with(|map, selection| {
11455 if !selection.is_empty() {
11456 selection.goal = SelectionGoal::None;
11457 }
11458 let (cursor, goal) = movement::down_by_rows(
11459 map,
11460 selection.end,
11461 row_count,
11462 selection.goal,
11463 false,
11464 text_layout_details,
11465 );
11466 selection.collapse_to(cursor, goal);
11467 });
11468 });
11469 }
11470
11471 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11472 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11473 let text_layout_details = &self.text_layout_details(window);
11474 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11475 s.move_heads_with(|map, head, goal| {
11476 movement::down(map, head, goal, false, text_layout_details)
11477 })
11478 });
11479 }
11480
11481 pub fn context_menu_first(
11482 &mut self,
11483 _: &ContextMenuFirst,
11484 _window: &mut Window,
11485 cx: &mut Context<Self>,
11486 ) {
11487 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11488 context_menu.select_first(self.completion_provider.as_deref(), cx);
11489 }
11490 }
11491
11492 pub fn context_menu_prev(
11493 &mut self,
11494 _: &ContextMenuPrevious,
11495 _window: &mut Window,
11496 cx: &mut Context<Self>,
11497 ) {
11498 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11499 context_menu.select_prev(self.completion_provider.as_deref(), cx);
11500 }
11501 }
11502
11503 pub fn context_menu_next(
11504 &mut self,
11505 _: &ContextMenuNext,
11506 _window: &mut Window,
11507 cx: &mut Context<Self>,
11508 ) {
11509 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11510 context_menu.select_next(self.completion_provider.as_deref(), cx);
11511 }
11512 }
11513
11514 pub fn context_menu_last(
11515 &mut self,
11516 _: &ContextMenuLast,
11517 _window: &mut Window,
11518 cx: &mut Context<Self>,
11519 ) {
11520 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11521 context_menu.select_last(self.completion_provider.as_deref(), cx);
11522 }
11523 }
11524
11525 pub fn move_to_previous_word_start(
11526 &mut self,
11527 _: &MoveToPreviousWordStart,
11528 window: &mut Window,
11529 cx: &mut Context<Self>,
11530 ) {
11531 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11532 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11533 s.move_cursors_with(|map, head, _| {
11534 (
11535 movement::previous_word_start(map, head),
11536 SelectionGoal::None,
11537 )
11538 });
11539 })
11540 }
11541
11542 pub fn move_to_previous_subword_start(
11543 &mut self,
11544 _: &MoveToPreviousSubwordStart,
11545 window: &mut Window,
11546 cx: &mut Context<Self>,
11547 ) {
11548 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11549 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11550 s.move_cursors_with(|map, head, _| {
11551 (
11552 movement::previous_subword_start(map, head),
11553 SelectionGoal::None,
11554 )
11555 });
11556 })
11557 }
11558
11559 pub fn select_to_previous_word_start(
11560 &mut self,
11561 _: &SelectToPreviousWordStart,
11562 window: &mut Window,
11563 cx: &mut Context<Self>,
11564 ) {
11565 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11566 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11567 s.move_heads_with(|map, head, _| {
11568 (
11569 movement::previous_word_start(map, head),
11570 SelectionGoal::None,
11571 )
11572 });
11573 })
11574 }
11575
11576 pub fn select_to_previous_subword_start(
11577 &mut self,
11578 _: &SelectToPreviousSubwordStart,
11579 window: &mut Window,
11580 cx: &mut Context<Self>,
11581 ) {
11582 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11583 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11584 s.move_heads_with(|map, head, _| {
11585 (
11586 movement::previous_subword_start(map, head),
11587 SelectionGoal::None,
11588 )
11589 });
11590 })
11591 }
11592
11593 pub fn delete_to_previous_word_start(
11594 &mut self,
11595 action: &DeleteToPreviousWordStart,
11596 window: &mut Window,
11597 cx: &mut Context<Self>,
11598 ) {
11599 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11600 self.transact(window, cx, |this, window, cx| {
11601 this.select_autoclose_pair(window, cx);
11602 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11603 s.move_with(|map, selection| {
11604 if selection.is_empty() {
11605 let cursor = if action.ignore_newlines {
11606 movement::previous_word_start(map, selection.head())
11607 } else {
11608 movement::previous_word_start_or_newline(map, selection.head())
11609 };
11610 selection.set_head(cursor, SelectionGoal::None);
11611 }
11612 });
11613 });
11614 this.insert("", window, cx);
11615 });
11616 }
11617
11618 pub fn delete_to_previous_subword_start(
11619 &mut self,
11620 _: &DeleteToPreviousSubwordStart,
11621 window: &mut Window,
11622 cx: &mut Context<Self>,
11623 ) {
11624 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11625 self.transact(window, cx, |this, window, cx| {
11626 this.select_autoclose_pair(window, cx);
11627 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11628 s.move_with(|map, selection| {
11629 if selection.is_empty() {
11630 let cursor = movement::previous_subword_start(map, selection.head());
11631 selection.set_head(cursor, SelectionGoal::None);
11632 }
11633 });
11634 });
11635 this.insert("", window, cx);
11636 });
11637 }
11638
11639 pub fn move_to_next_word_end(
11640 &mut self,
11641 _: &MoveToNextWordEnd,
11642 window: &mut Window,
11643 cx: &mut Context<Self>,
11644 ) {
11645 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11646 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11647 s.move_cursors_with(|map, head, _| {
11648 (movement::next_word_end(map, head), SelectionGoal::None)
11649 });
11650 })
11651 }
11652
11653 pub fn move_to_next_subword_end(
11654 &mut self,
11655 _: &MoveToNextSubwordEnd,
11656 window: &mut Window,
11657 cx: &mut Context<Self>,
11658 ) {
11659 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11660 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11661 s.move_cursors_with(|map, head, _| {
11662 (movement::next_subword_end(map, head), SelectionGoal::None)
11663 });
11664 })
11665 }
11666
11667 pub fn select_to_next_word_end(
11668 &mut self,
11669 _: &SelectToNextWordEnd,
11670 window: &mut Window,
11671 cx: &mut Context<Self>,
11672 ) {
11673 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11674 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11675 s.move_heads_with(|map, head, _| {
11676 (movement::next_word_end(map, head), SelectionGoal::None)
11677 });
11678 })
11679 }
11680
11681 pub fn select_to_next_subword_end(
11682 &mut self,
11683 _: &SelectToNextSubwordEnd,
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_heads_with(|map, head, _| {
11690 (movement::next_subword_end(map, head), SelectionGoal::None)
11691 });
11692 })
11693 }
11694
11695 pub fn delete_to_next_word_end(
11696 &mut self,
11697 action: &DeleteToNextWordEnd,
11698 window: &mut Window,
11699 cx: &mut Context<Self>,
11700 ) {
11701 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11702 self.transact(window, cx, |this, window, cx| {
11703 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11704 s.move_with(|map, selection| {
11705 if selection.is_empty() {
11706 let cursor = if action.ignore_newlines {
11707 movement::next_word_end(map, selection.head())
11708 } else {
11709 movement::next_word_end_or_newline(map, selection.head())
11710 };
11711 selection.set_head(cursor, SelectionGoal::None);
11712 }
11713 });
11714 });
11715 this.insert("", window, cx);
11716 });
11717 }
11718
11719 pub fn delete_to_next_subword_end(
11720 &mut self,
11721 _: &DeleteToNextSubwordEnd,
11722 window: &mut Window,
11723 cx: &mut Context<Self>,
11724 ) {
11725 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11726 self.transact(window, cx, |this, window, cx| {
11727 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11728 s.move_with(|map, selection| {
11729 if selection.is_empty() {
11730 let cursor = movement::next_subword_end(map, selection.head());
11731 selection.set_head(cursor, SelectionGoal::None);
11732 }
11733 });
11734 });
11735 this.insert("", window, cx);
11736 });
11737 }
11738
11739 pub fn move_to_beginning_of_line(
11740 &mut self,
11741 action: &MoveToBeginningOfLine,
11742 window: &mut Window,
11743 cx: &mut Context<Self>,
11744 ) {
11745 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11746 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11747 s.move_cursors_with(|map, head, _| {
11748 (
11749 movement::indented_line_beginning(
11750 map,
11751 head,
11752 action.stop_at_soft_wraps,
11753 action.stop_at_indent,
11754 ),
11755 SelectionGoal::None,
11756 )
11757 });
11758 })
11759 }
11760
11761 pub fn select_to_beginning_of_line(
11762 &mut self,
11763 action: &SelectToBeginningOfLine,
11764 window: &mut Window,
11765 cx: &mut Context<Self>,
11766 ) {
11767 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11768 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11769 s.move_heads_with(|map, head, _| {
11770 (
11771 movement::indented_line_beginning(
11772 map,
11773 head,
11774 action.stop_at_soft_wraps,
11775 action.stop_at_indent,
11776 ),
11777 SelectionGoal::None,
11778 )
11779 });
11780 });
11781 }
11782
11783 pub fn delete_to_beginning_of_line(
11784 &mut self,
11785 action: &DeleteToBeginningOfLine,
11786 window: &mut Window,
11787 cx: &mut Context<Self>,
11788 ) {
11789 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11790 self.transact(window, cx, |this, window, cx| {
11791 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11792 s.move_with(|_, selection| {
11793 selection.reversed = true;
11794 });
11795 });
11796
11797 this.select_to_beginning_of_line(
11798 &SelectToBeginningOfLine {
11799 stop_at_soft_wraps: false,
11800 stop_at_indent: action.stop_at_indent,
11801 },
11802 window,
11803 cx,
11804 );
11805 this.backspace(&Backspace, window, cx);
11806 });
11807 }
11808
11809 pub fn move_to_end_of_line(
11810 &mut self,
11811 action: &MoveToEndOfLine,
11812 window: &mut Window,
11813 cx: &mut Context<Self>,
11814 ) {
11815 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11816 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11817 s.move_cursors_with(|map, head, _| {
11818 (
11819 movement::line_end(map, head, action.stop_at_soft_wraps),
11820 SelectionGoal::None,
11821 )
11822 });
11823 })
11824 }
11825
11826 pub fn select_to_end_of_line(
11827 &mut self,
11828 action: &SelectToEndOfLine,
11829 window: &mut Window,
11830 cx: &mut Context<Self>,
11831 ) {
11832 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11833 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11834 s.move_heads_with(|map, head, _| {
11835 (
11836 movement::line_end(map, head, action.stop_at_soft_wraps),
11837 SelectionGoal::None,
11838 )
11839 });
11840 })
11841 }
11842
11843 pub fn delete_to_end_of_line(
11844 &mut self,
11845 _: &DeleteToEndOfLine,
11846 window: &mut Window,
11847 cx: &mut Context<Self>,
11848 ) {
11849 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11850 self.transact(window, cx, |this, window, cx| {
11851 this.select_to_end_of_line(
11852 &SelectToEndOfLine {
11853 stop_at_soft_wraps: false,
11854 },
11855 window,
11856 cx,
11857 );
11858 this.delete(&Delete, window, cx);
11859 });
11860 }
11861
11862 pub fn cut_to_end_of_line(
11863 &mut self,
11864 _: &CutToEndOfLine,
11865 window: &mut Window,
11866 cx: &mut Context<Self>,
11867 ) {
11868 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11869 self.transact(window, cx, |this, window, cx| {
11870 this.select_to_end_of_line(
11871 &SelectToEndOfLine {
11872 stop_at_soft_wraps: false,
11873 },
11874 window,
11875 cx,
11876 );
11877 this.cut(&Cut, window, cx);
11878 });
11879 }
11880
11881 pub fn move_to_start_of_paragraph(
11882 &mut self,
11883 _: &MoveToStartOfParagraph,
11884 window: &mut Window,
11885 cx: &mut Context<Self>,
11886 ) {
11887 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11888 cx.propagate();
11889 return;
11890 }
11891 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11892 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11893 s.move_with(|map, selection| {
11894 selection.collapse_to(
11895 movement::start_of_paragraph(map, selection.head(), 1),
11896 SelectionGoal::None,
11897 )
11898 });
11899 })
11900 }
11901
11902 pub fn move_to_end_of_paragraph(
11903 &mut self,
11904 _: &MoveToEndOfParagraph,
11905 window: &mut Window,
11906 cx: &mut Context<Self>,
11907 ) {
11908 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11909 cx.propagate();
11910 return;
11911 }
11912 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11913 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11914 s.move_with(|map, selection| {
11915 selection.collapse_to(
11916 movement::end_of_paragraph(map, selection.head(), 1),
11917 SelectionGoal::None,
11918 )
11919 });
11920 })
11921 }
11922
11923 pub fn select_to_start_of_paragraph(
11924 &mut self,
11925 _: &SelectToStartOfParagraph,
11926 window: &mut Window,
11927 cx: &mut Context<Self>,
11928 ) {
11929 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11930 cx.propagate();
11931 return;
11932 }
11933 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11934 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11935 s.move_heads_with(|map, head, _| {
11936 (
11937 movement::start_of_paragraph(map, head, 1),
11938 SelectionGoal::None,
11939 )
11940 });
11941 })
11942 }
11943
11944 pub fn select_to_end_of_paragraph(
11945 &mut self,
11946 _: &SelectToEndOfParagraph,
11947 window: &mut Window,
11948 cx: &mut Context<Self>,
11949 ) {
11950 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11951 cx.propagate();
11952 return;
11953 }
11954 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11955 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11956 s.move_heads_with(|map, head, _| {
11957 (
11958 movement::end_of_paragraph(map, head, 1),
11959 SelectionGoal::None,
11960 )
11961 });
11962 })
11963 }
11964
11965 pub fn move_to_start_of_excerpt(
11966 &mut self,
11967 _: &MoveToStartOfExcerpt,
11968 window: &mut Window,
11969 cx: &mut Context<Self>,
11970 ) {
11971 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11972 cx.propagate();
11973 return;
11974 }
11975 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11976 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11977 s.move_with(|map, selection| {
11978 selection.collapse_to(
11979 movement::start_of_excerpt(
11980 map,
11981 selection.head(),
11982 workspace::searchable::Direction::Prev,
11983 ),
11984 SelectionGoal::None,
11985 )
11986 });
11987 })
11988 }
11989
11990 pub fn move_to_start_of_next_excerpt(
11991 &mut self,
11992 _: &MoveToStartOfNextExcerpt,
11993 window: &mut Window,
11994 cx: &mut Context<Self>,
11995 ) {
11996 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11997 cx.propagate();
11998 return;
11999 }
12000
12001 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12002 s.move_with(|map, selection| {
12003 selection.collapse_to(
12004 movement::start_of_excerpt(
12005 map,
12006 selection.head(),
12007 workspace::searchable::Direction::Next,
12008 ),
12009 SelectionGoal::None,
12010 )
12011 });
12012 })
12013 }
12014
12015 pub fn move_to_end_of_excerpt(
12016 &mut self,
12017 _: &MoveToEndOfExcerpt,
12018 window: &mut Window,
12019 cx: &mut Context<Self>,
12020 ) {
12021 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12022 cx.propagate();
12023 return;
12024 }
12025 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12026 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12027 s.move_with(|map, selection| {
12028 selection.collapse_to(
12029 movement::end_of_excerpt(
12030 map,
12031 selection.head(),
12032 workspace::searchable::Direction::Next,
12033 ),
12034 SelectionGoal::None,
12035 )
12036 });
12037 })
12038 }
12039
12040 pub fn move_to_end_of_previous_excerpt(
12041 &mut self,
12042 _: &MoveToEndOfPreviousExcerpt,
12043 window: &mut Window,
12044 cx: &mut Context<Self>,
12045 ) {
12046 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12047 cx.propagate();
12048 return;
12049 }
12050 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12051 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12052 s.move_with(|map, selection| {
12053 selection.collapse_to(
12054 movement::end_of_excerpt(
12055 map,
12056 selection.head(),
12057 workspace::searchable::Direction::Prev,
12058 ),
12059 SelectionGoal::None,
12060 )
12061 });
12062 })
12063 }
12064
12065 pub fn select_to_start_of_excerpt(
12066 &mut self,
12067 _: &SelectToStartOfExcerpt,
12068 window: &mut Window,
12069 cx: &mut Context<Self>,
12070 ) {
12071 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12072 cx.propagate();
12073 return;
12074 }
12075 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12076 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12077 s.move_heads_with(|map, head, _| {
12078 (
12079 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12080 SelectionGoal::None,
12081 )
12082 });
12083 })
12084 }
12085
12086 pub fn select_to_start_of_next_excerpt(
12087 &mut self,
12088 _: &SelectToStartOfNextExcerpt,
12089 window: &mut Window,
12090 cx: &mut Context<Self>,
12091 ) {
12092 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12093 cx.propagate();
12094 return;
12095 }
12096 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12097 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12098 s.move_heads_with(|map, head, _| {
12099 (
12100 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12101 SelectionGoal::None,
12102 )
12103 });
12104 })
12105 }
12106
12107 pub fn select_to_end_of_excerpt(
12108 &mut self,
12109 _: &SelectToEndOfExcerpt,
12110 window: &mut Window,
12111 cx: &mut Context<Self>,
12112 ) {
12113 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12114 cx.propagate();
12115 return;
12116 }
12117 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12118 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12119 s.move_heads_with(|map, head, _| {
12120 (
12121 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12122 SelectionGoal::None,
12123 )
12124 });
12125 })
12126 }
12127
12128 pub fn select_to_end_of_previous_excerpt(
12129 &mut self,
12130 _: &SelectToEndOfPreviousExcerpt,
12131 window: &mut Window,
12132 cx: &mut Context<Self>,
12133 ) {
12134 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12135 cx.propagate();
12136 return;
12137 }
12138 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12139 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12140 s.move_heads_with(|map, head, _| {
12141 (
12142 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12143 SelectionGoal::None,
12144 )
12145 });
12146 })
12147 }
12148
12149 pub fn move_to_beginning(
12150 &mut self,
12151 _: &MoveToBeginning,
12152 window: &mut Window,
12153 cx: &mut Context<Self>,
12154 ) {
12155 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12156 cx.propagate();
12157 return;
12158 }
12159 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12160 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12161 s.select_ranges(vec![0..0]);
12162 });
12163 }
12164
12165 pub fn select_to_beginning(
12166 &mut self,
12167 _: &SelectToBeginning,
12168 window: &mut Window,
12169 cx: &mut Context<Self>,
12170 ) {
12171 let mut selection = self.selections.last::<Point>(cx);
12172 selection.set_head(Point::zero(), SelectionGoal::None);
12173 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12174 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12175 s.select(vec![selection]);
12176 });
12177 }
12178
12179 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12180 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12181 cx.propagate();
12182 return;
12183 }
12184 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12185 let cursor = self.buffer.read(cx).read(cx).len();
12186 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12187 s.select_ranges(vec![cursor..cursor])
12188 });
12189 }
12190
12191 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12192 self.nav_history = nav_history;
12193 }
12194
12195 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12196 self.nav_history.as_ref()
12197 }
12198
12199 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12200 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12201 }
12202
12203 fn push_to_nav_history(
12204 &mut self,
12205 cursor_anchor: Anchor,
12206 new_position: Option<Point>,
12207 is_deactivate: bool,
12208 cx: &mut Context<Self>,
12209 ) {
12210 if let Some(nav_history) = self.nav_history.as_mut() {
12211 let buffer = self.buffer.read(cx).read(cx);
12212 let cursor_position = cursor_anchor.to_point(&buffer);
12213 let scroll_state = self.scroll_manager.anchor();
12214 let scroll_top_row = scroll_state.top_row(&buffer);
12215 drop(buffer);
12216
12217 if let Some(new_position) = new_position {
12218 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12219 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12220 return;
12221 }
12222 }
12223
12224 nav_history.push(
12225 Some(NavigationData {
12226 cursor_anchor,
12227 cursor_position,
12228 scroll_anchor: scroll_state,
12229 scroll_top_row,
12230 }),
12231 cx,
12232 );
12233 cx.emit(EditorEvent::PushedToNavHistory {
12234 anchor: cursor_anchor,
12235 is_deactivate,
12236 })
12237 }
12238 }
12239
12240 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12241 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12242 let buffer = self.buffer.read(cx).snapshot(cx);
12243 let mut selection = self.selections.first::<usize>(cx);
12244 selection.set_head(buffer.len(), SelectionGoal::None);
12245 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12246 s.select(vec![selection]);
12247 });
12248 }
12249
12250 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12251 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12252 let end = self.buffer.read(cx).read(cx).len();
12253 self.change_selections(None, window, cx, |s| {
12254 s.select_ranges(vec![0..end]);
12255 });
12256 }
12257
12258 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12259 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12260 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12261 let mut selections = self.selections.all::<Point>(cx);
12262 let max_point = display_map.buffer_snapshot.max_point();
12263 for selection in &mut selections {
12264 let rows = selection.spanned_rows(true, &display_map);
12265 selection.start = Point::new(rows.start.0, 0);
12266 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12267 selection.reversed = false;
12268 }
12269 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12270 s.select(selections);
12271 });
12272 }
12273
12274 pub fn split_selection_into_lines(
12275 &mut self,
12276 _: &SplitSelectionIntoLines,
12277 window: &mut Window,
12278 cx: &mut Context<Self>,
12279 ) {
12280 let selections = self
12281 .selections
12282 .all::<Point>(cx)
12283 .into_iter()
12284 .map(|selection| selection.start..selection.end)
12285 .collect::<Vec<_>>();
12286 self.unfold_ranges(&selections, true, true, cx);
12287
12288 let mut new_selection_ranges = Vec::new();
12289 {
12290 let buffer = self.buffer.read(cx).read(cx);
12291 for selection in selections {
12292 for row in selection.start.row..selection.end.row {
12293 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12294 new_selection_ranges.push(cursor..cursor);
12295 }
12296
12297 let is_multiline_selection = selection.start.row != selection.end.row;
12298 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12299 // so this action feels more ergonomic when paired with other selection operations
12300 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12301 if !should_skip_last {
12302 new_selection_ranges.push(selection.end..selection.end);
12303 }
12304 }
12305 }
12306 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12307 s.select_ranges(new_selection_ranges);
12308 });
12309 }
12310
12311 pub fn add_selection_above(
12312 &mut self,
12313 _: &AddSelectionAbove,
12314 window: &mut Window,
12315 cx: &mut Context<Self>,
12316 ) {
12317 self.add_selection(true, window, cx);
12318 }
12319
12320 pub fn add_selection_below(
12321 &mut self,
12322 _: &AddSelectionBelow,
12323 window: &mut Window,
12324 cx: &mut Context<Self>,
12325 ) {
12326 self.add_selection(false, window, cx);
12327 }
12328
12329 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12330 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12331
12332 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12333 let mut selections = self.selections.all::<Point>(cx);
12334 let text_layout_details = self.text_layout_details(window);
12335 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12336 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12337 let range = oldest_selection.display_range(&display_map).sorted();
12338
12339 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12340 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12341 let positions = start_x.min(end_x)..start_x.max(end_x);
12342
12343 selections.clear();
12344 let mut stack = Vec::new();
12345 for row in range.start.row().0..=range.end.row().0 {
12346 if let Some(selection) = self.selections.build_columnar_selection(
12347 &display_map,
12348 DisplayRow(row),
12349 &positions,
12350 oldest_selection.reversed,
12351 &text_layout_details,
12352 ) {
12353 stack.push(selection.id);
12354 selections.push(selection);
12355 }
12356 }
12357
12358 if above {
12359 stack.reverse();
12360 }
12361
12362 AddSelectionsState { above, stack }
12363 });
12364
12365 let last_added_selection = *state.stack.last().unwrap();
12366 let mut new_selections = Vec::new();
12367 if above == state.above {
12368 let end_row = if above {
12369 DisplayRow(0)
12370 } else {
12371 display_map.max_point().row()
12372 };
12373
12374 'outer: for selection in selections {
12375 if selection.id == last_added_selection {
12376 let range = selection.display_range(&display_map).sorted();
12377 debug_assert_eq!(range.start.row(), range.end.row());
12378 let mut row = range.start.row();
12379 let positions =
12380 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12381 px(start)..px(end)
12382 } else {
12383 let start_x =
12384 display_map.x_for_display_point(range.start, &text_layout_details);
12385 let end_x =
12386 display_map.x_for_display_point(range.end, &text_layout_details);
12387 start_x.min(end_x)..start_x.max(end_x)
12388 };
12389
12390 while row != end_row {
12391 if above {
12392 row.0 -= 1;
12393 } else {
12394 row.0 += 1;
12395 }
12396
12397 if let Some(new_selection) = self.selections.build_columnar_selection(
12398 &display_map,
12399 row,
12400 &positions,
12401 selection.reversed,
12402 &text_layout_details,
12403 ) {
12404 state.stack.push(new_selection.id);
12405 if above {
12406 new_selections.push(new_selection);
12407 new_selections.push(selection);
12408 } else {
12409 new_selections.push(selection);
12410 new_selections.push(new_selection);
12411 }
12412
12413 continue 'outer;
12414 }
12415 }
12416 }
12417
12418 new_selections.push(selection);
12419 }
12420 } else {
12421 new_selections = selections;
12422 new_selections.retain(|s| s.id != last_added_selection);
12423 state.stack.pop();
12424 }
12425
12426 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12427 s.select(new_selections);
12428 });
12429 if state.stack.len() > 1 {
12430 self.add_selections_state = Some(state);
12431 }
12432 }
12433
12434 fn select_match_ranges(
12435 &mut self,
12436 range: Range<usize>,
12437 reversed: bool,
12438 replace_newest: bool,
12439 auto_scroll: Option<Autoscroll>,
12440 window: &mut Window,
12441 cx: &mut Context<Editor>,
12442 ) {
12443 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12444 self.change_selections(auto_scroll, window, cx, |s| {
12445 if replace_newest {
12446 s.delete(s.newest_anchor().id);
12447 }
12448 if reversed {
12449 s.insert_range(range.end..range.start);
12450 } else {
12451 s.insert_range(range);
12452 }
12453 });
12454 }
12455
12456 pub fn select_next_match_internal(
12457 &mut self,
12458 display_map: &DisplaySnapshot,
12459 replace_newest: bool,
12460 autoscroll: Option<Autoscroll>,
12461 window: &mut Window,
12462 cx: &mut Context<Self>,
12463 ) -> Result<()> {
12464 let buffer = &display_map.buffer_snapshot;
12465 let mut selections = self.selections.all::<usize>(cx);
12466 if let Some(mut select_next_state) = self.select_next_state.take() {
12467 let query = &select_next_state.query;
12468 if !select_next_state.done {
12469 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12470 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12471 let mut next_selected_range = None;
12472
12473 let bytes_after_last_selection =
12474 buffer.bytes_in_range(last_selection.end..buffer.len());
12475 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12476 let query_matches = query
12477 .stream_find_iter(bytes_after_last_selection)
12478 .map(|result| (last_selection.end, result))
12479 .chain(
12480 query
12481 .stream_find_iter(bytes_before_first_selection)
12482 .map(|result| (0, result)),
12483 );
12484
12485 for (start_offset, query_match) in query_matches {
12486 let query_match = query_match.unwrap(); // can only fail due to I/O
12487 let offset_range =
12488 start_offset + query_match.start()..start_offset + query_match.end();
12489 let display_range = offset_range.start.to_display_point(display_map)
12490 ..offset_range.end.to_display_point(display_map);
12491
12492 if !select_next_state.wordwise
12493 || (!movement::is_inside_word(display_map, display_range.start)
12494 && !movement::is_inside_word(display_map, display_range.end))
12495 {
12496 // TODO: This is n^2, because we might check all the selections
12497 if !selections
12498 .iter()
12499 .any(|selection| selection.range().overlaps(&offset_range))
12500 {
12501 next_selected_range = Some(offset_range);
12502 break;
12503 }
12504 }
12505 }
12506
12507 if let Some(next_selected_range) = next_selected_range {
12508 self.select_match_ranges(
12509 next_selected_range,
12510 last_selection.reversed,
12511 replace_newest,
12512 autoscroll,
12513 window,
12514 cx,
12515 );
12516 } else {
12517 select_next_state.done = true;
12518 }
12519 }
12520
12521 self.select_next_state = Some(select_next_state);
12522 } else {
12523 let mut only_carets = true;
12524 let mut same_text_selected = true;
12525 let mut selected_text = None;
12526
12527 let mut selections_iter = selections.iter().peekable();
12528 while let Some(selection) = selections_iter.next() {
12529 if selection.start != selection.end {
12530 only_carets = false;
12531 }
12532
12533 if same_text_selected {
12534 if selected_text.is_none() {
12535 selected_text =
12536 Some(buffer.text_for_range(selection.range()).collect::<String>());
12537 }
12538
12539 if let Some(next_selection) = selections_iter.peek() {
12540 if next_selection.range().len() == selection.range().len() {
12541 let next_selected_text = buffer
12542 .text_for_range(next_selection.range())
12543 .collect::<String>();
12544 if Some(next_selected_text) != selected_text {
12545 same_text_selected = false;
12546 selected_text = None;
12547 }
12548 } else {
12549 same_text_selected = false;
12550 selected_text = None;
12551 }
12552 }
12553 }
12554 }
12555
12556 if only_carets {
12557 for selection in &mut selections {
12558 let word_range = movement::surrounding_word(
12559 display_map,
12560 selection.start.to_display_point(display_map),
12561 );
12562 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12563 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12564 selection.goal = SelectionGoal::None;
12565 selection.reversed = false;
12566 self.select_match_ranges(
12567 selection.start..selection.end,
12568 selection.reversed,
12569 replace_newest,
12570 autoscroll,
12571 window,
12572 cx,
12573 );
12574 }
12575
12576 if selections.len() == 1 {
12577 let selection = selections
12578 .last()
12579 .expect("ensured that there's only one selection");
12580 let query = buffer
12581 .text_for_range(selection.start..selection.end)
12582 .collect::<String>();
12583 let is_empty = query.is_empty();
12584 let select_state = SelectNextState {
12585 query: AhoCorasick::new(&[query])?,
12586 wordwise: true,
12587 done: is_empty,
12588 };
12589 self.select_next_state = Some(select_state);
12590 } else {
12591 self.select_next_state = None;
12592 }
12593 } else if let Some(selected_text) = selected_text {
12594 self.select_next_state = Some(SelectNextState {
12595 query: AhoCorasick::new(&[selected_text])?,
12596 wordwise: false,
12597 done: false,
12598 });
12599 self.select_next_match_internal(
12600 display_map,
12601 replace_newest,
12602 autoscroll,
12603 window,
12604 cx,
12605 )?;
12606 }
12607 }
12608 Ok(())
12609 }
12610
12611 pub fn select_all_matches(
12612 &mut self,
12613 _action: &SelectAllMatches,
12614 window: &mut Window,
12615 cx: &mut Context<Self>,
12616 ) -> Result<()> {
12617 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12618
12619 self.push_to_selection_history();
12620 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12621
12622 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12623 let Some(select_next_state) = self.select_next_state.as_mut() else {
12624 return Ok(());
12625 };
12626 if select_next_state.done {
12627 return Ok(());
12628 }
12629
12630 let mut new_selections = Vec::new();
12631
12632 let reversed = self.selections.oldest::<usize>(cx).reversed;
12633 let buffer = &display_map.buffer_snapshot;
12634 let query_matches = select_next_state
12635 .query
12636 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12637
12638 for query_match in query_matches.into_iter() {
12639 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12640 let offset_range = if reversed {
12641 query_match.end()..query_match.start()
12642 } else {
12643 query_match.start()..query_match.end()
12644 };
12645 let display_range = offset_range.start.to_display_point(&display_map)
12646 ..offset_range.end.to_display_point(&display_map);
12647
12648 if !select_next_state.wordwise
12649 || (!movement::is_inside_word(&display_map, display_range.start)
12650 && !movement::is_inside_word(&display_map, display_range.end))
12651 {
12652 new_selections.push(offset_range.start..offset_range.end);
12653 }
12654 }
12655
12656 select_next_state.done = true;
12657 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12658 self.change_selections(None, window, cx, |selections| {
12659 selections.select_ranges(new_selections)
12660 });
12661
12662 Ok(())
12663 }
12664
12665 pub fn select_next(
12666 &mut self,
12667 action: &SelectNext,
12668 window: &mut Window,
12669 cx: &mut Context<Self>,
12670 ) -> Result<()> {
12671 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12672 self.push_to_selection_history();
12673 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12674 self.select_next_match_internal(
12675 &display_map,
12676 action.replace_newest,
12677 Some(Autoscroll::newest()),
12678 window,
12679 cx,
12680 )?;
12681 Ok(())
12682 }
12683
12684 pub fn select_previous(
12685 &mut self,
12686 action: &SelectPrevious,
12687 window: &mut Window,
12688 cx: &mut Context<Self>,
12689 ) -> Result<()> {
12690 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12691 self.push_to_selection_history();
12692 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12693 let buffer = &display_map.buffer_snapshot;
12694 let mut selections = self.selections.all::<usize>(cx);
12695 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12696 let query = &select_prev_state.query;
12697 if !select_prev_state.done {
12698 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12699 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12700 let mut next_selected_range = None;
12701 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12702 let bytes_before_last_selection =
12703 buffer.reversed_bytes_in_range(0..last_selection.start);
12704 let bytes_after_first_selection =
12705 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12706 let query_matches = query
12707 .stream_find_iter(bytes_before_last_selection)
12708 .map(|result| (last_selection.start, result))
12709 .chain(
12710 query
12711 .stream_find_iter(bytes_after_first_selection)
12712 .map(|result| (buffer.len(), result)),
12713 );
12714 for (end_offset, query_match) in query_matches {
12715 let query_match = query_match.unwrap(); // can only fail due to I/O
12716 let offset_range =
12717 end_offset - query_match.end()..end_offset - query_match.start();
12718 let display_range = offset_range.start.to_display_point(&display_map)
12719 ..offset_range.end.to_display_point(&display_map);
12720
12721 if !select_prev_state.wordwise
12722 || (!movement::is_inside_word(&display_map, display_range.start)
12723 && !movement::is_inside_word(&display_map, display_range.end))
12724 {
12725 next_selected_range = Some(offset_range);
12726 break;
12727 }
12728 }
12729
12730 if let Some(next_selected_range) = next_selected_range {
12731 self.select_match_ranges(
12732 next_selected_range,
12733 last_selection.reversed,
12734 action.replace_newest,
12735 Some(Autoscroll::newest()),
12736 window,
12737 cx,
12738 );
12739 } else {
12740 select_prev_state.done = true;
12741 }
12742 }
12743
12744 self.select_prev_state = Some(select_prev_state);
12745 } else {
12746 let mut only_carets = true;
12747 let mut same_text_selected = true;
12748 let mut selected_text = None;
12749
12750 let mut selections_iter = selections.iter().peekable();
12751 while let Some(selection) = selections_iter.next() {
12752 if selection.start != selection.end {
12753 only_carets = false;
12754 }
12755
12756 if same_text_selected {
12757 if selected_text.is_none() {
12758 selected_text =
12759 Some(buffer.text_for_range(selection.range()).collect::<String>());
12760 }
12761
12762 if let Some(next_selection) = selections_iter.peek() {
12763 if next_selection.range().len() == selection.range().len() {
12764 let next_selected_text = buffer
12765 .text_for_range(next_selection.range())
12766 .collect::<String>();
12767 if Some(next_selected_text) != selected_text {
12768 same_text_selected = false;
12769 selected_text = None;
12770 }
12771 } else {
12772 same_text_selected = false;
12773 selected_text = None;
12774 }
12775 }
12776 }
12777 }
12778
12779 if only_carets {
12780 for selection in &mut selections {
12781 let word_range = movement::surrounding_word(
12782 &display_map,
12783 selection.start.to_display_point(&display_map),
12784 );
12785 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12786 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12787 selection.goal = SelectionGoal::None;
12788 selection.reversed = false;
12789 self.select_match_ranges(
12790 selection.start..selection.end,
12791 selection.reversed,
12792 action.replace_newest,
12793 Some(Autoscroll::newest()),
12794 window,
12795 cx,
12796 );
12797 }
12798 if selections.len() == 1 {
12799 let selection = selections
12800 .last()
12801 .expect("ensured that there's only one selection");
12802 let query = buffer
12803 .text_for_range(selection.start..selection.end)
12804 .collect::<String>();
12805 let is_empty = query.is_empty();
12806 let select_state = SelectNextState {
12807 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12808 wordwise: true,
12809 done: is_empty,
12810 };
12811 self.select_prev_state = Some(select_state);
12812 } else {
12813 self.select_prev_state = None;
12814 }
12815 } else if let Some(selected_text) = selected_text {
12816 self.select_prev_state = Some(SelectNextState {
12817 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12818 wordwise: false,
12819 done: false,
12820 });
12821 self.select_previous(action, window, cx)?;
12822 }
12823 }
12824 Ok(())
12825 }
12826
12827 pub fn find_next_match(
12828 &mut self,
12829 _: &FindNextMatch,
12830 window: &mut Window,
12831 cx: &mut Context<Self>,
12832 ) -> Result<()> {
12833 let selections = self.selections.disjoint_anchors();
12834 match selections.first() {
12835 Some(first) if selections.len() >= 2 => {
12836 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12837 s.select_ranges([first.range()]);
12838 });
12839 }
12840 _ => self.select_next(
12841 &SelectNext {
12842 replace_newest: true,
12843 },
12844 window,
12845 cx,
12846 )?,
12847 }
12848 Ok(())
12849 }
12850
12851 pub fn find_previous_match(
12852 &mut self,
12853 _: &FindPreviousMatch,
12854 window: &mut Window,
12855 cx: &mut Context<Self>,
12856 ) -> Result<()> {
12857 let selections = self.selections.disjoint_anchors();
12858 match selections.last() {
12859 Some(last) if selections.len() >= 2 => {
12860 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12861 s.select_ranges([last.range()]);
12862 });
12863 }
12864 _ => self.select_previous(
12865 &SelectPrevious {
12866 replace_newest: true,
12867 },
12868 window,
12869 cx,
12870 )?,
12871 }
12872 Ok(())
12873 }
12874
12875 pub fn toggle_comments(
12876 &mut self,
12877 action: &ToggleComments,
12878 window: &mut Window,
12879 cx: &mut Context<Self>,
12880 ) {
12881 if self.read_only(cx) {
12882 return;
12883 }
12884 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12885 let text_layout_details = &self.text_layout_details(window);
12886 self.transact(window, cx, |this, window, cx| {
12887 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12888 let mut edits = Vec::new();
12889 let mut selection_edit_ranges = Vec::new();
12890 let mut last_toggled_row = None;
12891 let snapshot = this.buffer.read(cx).read(cx);
12892 let empty_str: Arc<str> = Arc::default();
12893 let mut suffixes_inserted = Vec::new();
12894 let ignore_indent = action.ignore_indent;
12895
12896 fn comment_prefix_range(
12897 snapshot: &MultiBufferSnapshot,
12898 row: MultiBufferRow,
12899 comment_prefix: &str,
12900 comment_prefix_whitespace: &str,
12901 ignore_indent: bool,
12902 ) -> Range<Point> {
12903 let indent_size = if ignore_indent {
12904 0
12905 } else {
12906 snapshot.indent_size_for_line(row).len
12907 };
12908
12909 let start = Point::new(row.0, indent_size);
12910
12911 let mut line_bytes = snapshot
12912 .bytes_in_range(start..snapshot.max_point())
12913 .flatten()
12914 .copied();
12915
12916 // If this line currently begins with the line comment prefix, then record
12917 // the range containing the prefix.
12918 if line_bytes
12919 .by_ref()
12920 .take(comment_prefix.len())
12921 .eq(comment_prefix.bytes())
12922 {
12923 // Include any whitespace that matches the comment prefix.
12924 let matching_whitespace_len = line_bytes
12925 .zip(comment_prefix_whitespace.bytes())
12926 .take_while(|(a, b)| a == b)
12927 .count() as u32;
12928 let end = Point::new(
12929 start.row,
12930 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12931 );
12932 start..end
12933 } else {
12934 start..start
12935 }
12936 }
12937
12938 fn comment_suffix_range(
12939 snapshot: &MultiBufferSnapshot,
12940 row: MultiBufferRow,
12941 comment_suffix: &str,
12942 comment_suffix_has_leading_space: bool,
12943 ) -> Range<Point> {
12944 let end = Point::new(row.0, snapshot.line_len(row));
12945 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12946
12947 let mut line_end_bytes = snapshot
12948 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12949 .flatten()
12950 .copied();
12951
12952 let leading_space_len = if suffix_start_column > 0
12953 && line_end_bytes.next() == Some(b' ')
12954 && comment_suffix_has_leading_space
12955 {
12956 1
12957 } else {
12958 0
12959 };
12960
12961 // If this line currently begins with the line comment prefix, then record
12962 // the range containing the prefix.
12963 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12964 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12965 start..end
12966 } else {
12967 end..end
12968 }
12969 }
12970
12971 // TODO: Handle selections that cross excerpts
12972 for selection in &mut selections {
12973 let start_column = snapshot
12974 .indent_size_for_line(MultiBufferRow(selection.start.row))
12975 .len;
12976 let language = if let Some(language) =
12977 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12978 {
12979 language
12980 } else {
12981 continue;
12982 };
12983
12984 selection_edit_ranges.clear();
12985
12986 // If multiple selections contain a given row, avoid processing that
12987 // row more than once.
12988 let mut start_row = MultiBufferRow(selection.start.row);
12989 if last_toggled_row == Some(start_row) {
12990 start_row = start_row.next_row();
12991 }
12992 let end_row =
12993 if selection.end.row > selection.start.row && selection.end.column == 0 {
12994 MultiBufferRow(selection.end.row - 1)
12995 } else {
12996 MultiBufferRow(selection.end.row)
12997 };
12998 last_toggled_row = Some(end_row);
12999
13000 if start_row > end_row {
13001 continue;
13002 }
13003
13004 // If the language has line comments, toggle those.
13005 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13006
13007 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13008 if ignore_indent {
13009 full_comment_prefixes = full_comment_prefixes
13010 .into_iter()
13011 .map(|s| Arc::from(s.trim_end()))
13012 .collect();
13013 }
13014
13015 if !full_comment_prefixes.is_empty() {
13016 let first_prefix = full_comment_prefixes
13017 .first()
13018 .expect("prefixes is non-empty");
13019 let prefix_trimmed_lengths = full_comment_prefixes
13020 .iter()
13021 .map(|p| p.trim_end_matches(' ').len())
13022 .collect::<SmallVec<[usize; 4]>>();
13023
13024 let mut all_selection_lines_are_comments = true;
13025
13026 for row in start_row.0..=end_row.0 {
13027 let row = MultiBufferRow(row);
13028 if start_row < end_row && snapshot.is_line_blank(row) {
13029 continue;
13030 }
13031
13032 let prefix_range = full_comment_prefixes
13033 .iter()
13034 .zip(prefix_trimmed_lengths.iter().copied())
13035 .map(|(prefix, trimmed_prefix_len)| {
13036 comment_prefix_range(
13037 snapshot.deref(),
13038 row,
13039 &prefix[..trimmed_prefix_len],
13040 &prefix[trimmed_prefix_len..],
13041 ignore_indent,
13042 )
13043 })
13044 .max_by_key(|range| range.end.column - range.start.column)
13045 .expect("prefixes is non-empty");
13046
13047 if prefix_range.is_empty() {
13048 all_selection_lines_are_comments = false;
13049 }
13050
13051 selection_edit_ranges.push(prefix_range);
13052 }
13053
13054 if all_selection_lines_are_comments {
13055 edits.extend(
13056 selection_edit_ranges
13057 .iter()
13058 .cloned()
13059 .map(|range| (range, empty_str.clone())),
13060 );
13061 } else {
13062 let min_column = selection_edit_ranges
13063 .iter()
13064 .map(|range| range.start.column)
13065 .min()
13066 .unwrap_or(0);
13067 edits.extend(selection_edit_ranges.iter().map(|range| {
13068 let position = Point::new(range.start.row, min_column);
13069 (position..position, first_prefix.clone())
13070 }));
13071 }
13072 } else if let Some((full_comment_prefix, comment_suffix)) =
13073 language.block_comment_delimiters()
13074 {
13075 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13076 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13077 let prefix_range = comment_prefix_range(
13078 snapshot.deref(),
13079 start_row,
13080 comment_prefix,
13081 comment_prefix_whitespace,
13082 ignore_indent,
13083 );
13084 let suffix_range = comment_suffix_range(
13085 snapshot.deref(),
13086 end_row,
13087 comment_suffix.trim_start_matches(' '),
13088 comment_suffix.starts_with(' '),
13089 );
13090
13091 if prefix_range.is_empty() || suffix_range.is_empty() {
13092 edits.push((
13093 prefix_range.start..prefix_range.start,
13094 full_comment_prefix.clone(),
13095 ));
13096 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13097 suffixes_inserted.push((end_row, comment_suffix.len()));
13098 } else {
13099 edits.push((prefix_range, empty_str.clone()));
13100 edits.push((suffix_range, empty_str.clone()));
13101 }
13102 } else {
13103 continue;
13104 }
13105 }
13106
13107 drop(snapshot);
13108 this.buffer.update(cx, |buffer, cx| {
13109 buffer.edit(edits, None, cx);
13110 });
13111
13112 // Adjust selections so that they end before any comment suffixes that
13113 // were inserted.
13114 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13115 let mut selections = this.selections.all::<Point>(cx);
13116 let snapshot = this.buffer.read(cx).read(cx);
13117 for selection in &mut selections {
13118 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13119 match row.cmp(&MultiBufferRow(selection.end.row)) {
13120 Ordering::Less => {
13121 suffixes_inserted.next();
13122 continue;
13123 }
13124 Ordering::Greater => break,
13125 Ordering::Equal => {
13126 if selection.end.column == snapshot.line_len(row) {
13127 if selection.is_empty() {
13128 selection.start.column -= suffix_len as u32;
13129 }
13130 selection.end.column -= suffix_len as u32;
13131 }
13132 break;
13133 }
13134 }
13135 }
13136 }
13137
13138 drop(snapshot);
13139 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13140 s.select(selections)
13141 });
13142
13143 let selections = this.selections.all::<Point>(cx);
13144 let selections_on_single_row = selections.windows(2).all(|selections| {
13145 selections[0].start.row == selections[1].start.row
13146 && selections[0].end.row == selections[1].end.row
13147 && selections[0].start.row == selections[0].end.row
13148 });
13149 let selections_selecting = selections
13150 .iter()
13151 .any(|selection| selection.start != selection.end);
13152 let advance_downwards = action.advance_downwards
13153 && selections_on_single_row
13154 && !selections_selecting
13155 && !matches!(this.mode, EditorMode::SingleLine { .. });
13156
13157 if advance_downwards {
13158 let snapshot = this.buffer.read(cx).snapshot(cx);
13159
13160 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13161 s.move_cursors_with(|display_snapshot, display_point, _| {
13162 let mut point = display_point.to_point(display_snapshot);
13163 point.row += 1;
13164 point = snapshot.clip_point(point, Bias::Left);
13165 let display_point = point.to_display_point(display_snapshot);
13166 let goal = SelectionGoal::HorizontalPosition(
13167 display_snapshot
13168 .x_for_display_point(display_point, text_layout_details)
13169 .into(),
13170 );
13171 (display_point, goal)
13172 })
13173 });
13174 }
13175 });
13176 }
13177
13178 pub fn select_enclosing_symbol(
13179 &mut self,
13180 _: &SelectEnclosingSymbol,
13181 window: &mut Window,
13182 cx: &mut Context<Self>,
13183 ) {
13184 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13185
13186 let buffer = self.buffer.read(cx).snapshot(cx);
13187 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13188
13189 fn update_selection(
13190 selection: &Selection<usize>,
13191 buffer_snap: &MultiBufferSnapshot,
13192 ) -> Option<Selection<usize>> {
13193 let cursor = selection.head();
13194 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13195 for symbol in symbols.iter().rev() {
13196 let start = symbol.range.start.to_offset(buffer_snap);
13197 let end = symbol.range.end.to_offset(buffer_snap);
13198 let new_range = start..end;
13199 if start < selection.start || end > selection.end {
13200 return Some(Selection {
13201 id: selection.id,
13202 start: new_range.start,
13203 end: new_range.end,
13204 goal: SelectionGoal::None,
13205 reversed: selection.reversed,
13206 });
13207 }
13208 }
13209 None
13210 }
13211
13212 let mut selected_larger_symbol = false;
13213 let new_selections = old_selections
13214 .iter()
13215 .map(|selection| match update_selection(selection, &buffer) {
13216 Some(new_selection) => {
13217 if new_selection.range() != selection.range() {
13218 selected_larger_symbol = true;
13219 }
13220 new_selection
13221 }
13222 None => selection.clone(),
13223 })
13224 .collect::<Vec<_>>();
13225
13226 if selected_larger_symbol {
13227 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13228 s.select(new_selections);
13229 });
13230 }
13231 }
13232
13233 pub fn select_larger_syntax_node(
13234 &mut self,
13235 _: &SelectLargerSyntaxNode,
13236 window: &mut Window,
13237 cx: &mut Context<Self>,
13238 ) {
13239 let Some(visible_row_count) = self.visible_row_count() else {
13240 return;
13241 };
13242 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13243 if old_selections.is_empty() {
13244 return;
13245 }
13246
13247 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13248
13249 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13250 let buffer = self.buffer.read(cx).snapshot(cx);
13251
13252 let mut selected_larger_node = false;
13253 let mut new_selections = old_selections
13254 .iter()
13255 .map(|selection| {
13256 let old_range = selection.start..selection.end;
13257
13258 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13259 // manually select word at selection
13260 if ["string_content", "inline"].contains(&node.kind()) {
13261 let word_range = {
13262 let display_point = buffer
13263 .offset_to_point(old_range.start)
13264 .to_display_point(&display_map);
13265 let Range { start, end } =
13266 movement::surrounding_word(&display_map, display_point);
13267 start.to_point(&display_map).to_offset(&buffer)
13268 ..end.to_point(&display_map).to_offset(&buffer)
13269 };
13270 // ignore if word is already selected
13271 if !word_range.is_empty() && old_range != word_range {
13272 let last_word_range = {
13273 let display_point = buffer
13274 .offset_to_point(old_range.end)
13275 .to_display_point(&display_map);
13276 let Range { start, end } =
13277 movement::surrounding_word(&display_map, display_point);
13278 start.to_point(&display_map).to_offset(&buffer)
13279 ..end.to_point(&display_map).to_offset(&buffer)
13280 };
13281 // only select word if start and end point belongs to same word
13282 if word_range == last_word_range {
13283 selected_larger_node = true;
13284 return Selection {
13285 id: selection.id,
13286 start: word_range.start,
13287 end: word_range.end,
13288 goal: SelectionGoal::None,
13289 reversed: selection.reversed,
13290 };
13291 }
13292 }
13293 }
13294 }
13295
13296 let mut new_range = old_range.clone();
13297 while let Some((_node, containing_range)) =
13298 buffer.syntax_ancestor(new_range.clone())
13299 {
13300 new_range = match containing_range {
13301 MultiOrSingleBufferOffsetRange::Single(_) => break,
13302 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13303 };
13304 if !display_map.intersects_fold(new_range.start)
13305 && !display_map.intersects_fold(new_range.end)
13306 {
13307 break;
13308 }
13309 }
13310
13311 selected_larger_node |= new_range != old_range;
13312 Selection {
13313 id: selection.id,
13314 start: new_range.start,
13315 end: new_range.end,
13316 goal: SelectionGoal::None,
13317 reversed: selection.reversed,
13318 }
13319 })
13320 .collect::<Vec<_>>();
13321
13322 if !selected_larger_node {
13323 return; // don't put this call in the history
13324 }
13325
13326 // scroll based on transformation done to the last selection created by the user
13327 let (last_old, last_new) = old_selections
13328 .last()
13329 .zip(new_selections.last().cloned())
13330 .expect("old_selections isn't empty");
13331
13332 // revert selection
13333 let is_selection_reversed = {
13334 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13335 new_selections.last_mut().expect("checked above").reversed =
13336 should_newest_selection_be_reversed;
13337 should_newest_selection_be_reversed
13338 };
13339
13340 if selected_larger_node {
13341 self.select_syntax_node_history.disable_clearing = true;
13342 self.change_selections(None, window, cx, |s| {
13343 s.select(new_selections.clone());
13344 });
13345 self.select_syntax_node_history.disable_clearing = false;
13346 }
13347
13348 let start_row = last_new.start.to_display_point(&display_map).row().0;
13349 let end_row = last_new.end.to_display_point(&display_map).row().0;
13350 let selection_height = end_row - start_row + 1;
13351 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13352
13353 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13354 let scroll_behavior = if fits_on_the_screen {
13355 self.request_autoscroll(Autoscroll::fit(), cx);
13356 SelectSyntaxNodeScrollBehavior::FitSelection
13357 } else if is_selection_reversed {
13358 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13359 SelectSyntaxNodeScrollBehavior::CursorTop
13360 } else {
13361 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13362 SelectSyntaxNodeScrollBehavior::CursorBottom
13363 };
13364
13365 self.select_syntax_node_history.push((
13366 old_selections,
13367 scroll_behavior,
13368 is_selection_reversed,
13369 ));
13370 }
13371
13372 pub fn select_smaller_syntax_node(
13373 &mut self,
13374 _: &SelectSmallerSyntaxNode,
13375 window: &mut Window,
13376 cx: &mut Context<Self>,
13377 ) {
13378 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13379
13380 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13381 self.select_syntax_node_history.pop()
13382 {
13383 if let Some(selection) = selections.last_mut() {
13384 selection.reversed = is_selection_reversed;
13385 }
13386
13387 self.select_syntax_node_history.disable_clearing = true;
13388 self.change_selections(None, window, cx, |s| {
13389 s.select(selections.to_vec());
13390 });
13391 self.select_syntax_node_history.disable_clearing = false;
13392
13393 match scroll_behavior {
13394 SelectSyntaxNodeScrollBehavior::CursorTop => {
13395 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13396 }
13397 SelectSyntaxNodeScrollBehavior::FitSelection => {
13398 self.request_autoscroll(Autoscroll::fit(), cx);
13399 }
13400 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13401 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13402 }
13403 }
13404 }
13405 }
13406
13407 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13408 if !EditorSettings::get_global(cx).gutter.runnables {
13409 self.clear_tasks();
13410 return Task::ready(());
13411 }
13412 let project = self.project.as_ref().map(Entity::downgrade);
13413 let task_sources = self.lsp_task_sources(cx);
13414 cx.spawn_in(window, async move |editor, cx| {
13415 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13416 let Some(project) = project.and_then(|p| p.upgrade()) else {
13417 return;
13418 };
13419 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13420 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13421 }) else {
13422 return;
13423 };
13424
13425 let hide_runnables = project
13426 .update(cx, |project, cx| {
13427 // Do not display any test indicators in non-dev server remote projects.
13428 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13429 })
13430 .unwrap_or(true);
13431 if hide_runnables {
13432 return;
13433 }
13434 let new_rows =
13435 cx.background_spawn({
13436 let snapshot = display_snapshot.clone();
13437 async move {
13438 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13439 }
13440 })
13441 .await;
13442 let Ok(lsp_tasks) =
13443 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13444 else {
13445 return;
13446 };
13447 let lsp_tasks = lsp_tasks.await;
13448
13449 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13450 lsp_tasks
13451 .into_iter()
13452 .flat_map(|(kind, tasks)| {
13453 tasks.into_iter().filter_map(move |(location, task)| {
13454 Some((kind.clone(), location?, task))
13455 })
13456 })
13457 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13458 let buffer = location.target.buffer;
13459 let buffer_snapshot = buffer.read(cx).snapshot();
13460 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13461 |(excerpt_id, snapshot, _)| {
13462 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13463 display_snapshot
13464 .buffer_snapshot
13465 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13466 } else {
13467 None
13468 }
13469 },
13470 );
13471 if let Some(offset) = offset {
13472 let task_buffer_range =
13473 location.target.range.to_point(&buffer_snapshot);
13474 let context_buffer_range =
13475 task_buffer_range.to_offset(&buffer_snapshot);
13476 let context_range = BufferOffset(context_buffer_range.start)
13477 ..BufferOffset(context_buffer_range.end);
13478
13479 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13480 .or_insert_with(|| RunnableTasks {
13481 templates: Vec::new(),
13482 offset,
13483 column: task_buffer_range.start.column,
13484 extra_variables: HashMap::default(),
13485 context_range,
13486 })
13487 .templates
13488 .push((kind, task.original_task().clone()));
13489 }
13490
13491 acc
13492 })
13493 }) else {
13494 return;
13495 };
13496
13497 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
13498 editor
13499 .update(cx, |editor, _| {
13500 editor.clear_tasks();
13501 for (key, mut value) in rows {
13502 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13503 value.templates.extend(lsp_tasks.templates);
13504 }
13505
13506 editor.insert_tasks(key, value);
13507 }
13508 for (key, value) in lsp_tasks_by_rows {
13509 editor.insert_tasks(key, value);
13510 }
13511 })
13512 .ok();
13513 })
13514 }
13515 fn fetch_runnable_ranges(
13516 snapshot: &DisplaySnapshot,
13517 range: Range<Anchor>,
13518 ) -> Vec<language::RunnableRange> {
13519 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13520 }
13521
13522 fn runnable_rows(
13523 project: Entity<Project>,
13524 snapshot: DisplaySnapshot,
13525 runnable_ranges: Vec<RunnableRange>,
13526 mut cx: AsyncWindowContext,
13527 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13528 runnable_ranges
13529 .into_iter()
13530 .filter_map(|mut runnable| {
13531 let tasks = cx
13532 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13533 .ok()?;
13534 if tasks.is_empty() {
13535 return None;
13536 }
13537
13538 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13539
13540 let row = snapshot
13541 .buffer_snapshot
13542 .buffer_line_for_row(MultiBufferRow(point.row))?
13543 .1
13544 .start
13545 .row;
13546
13547 let context_range =
13548 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13549 Some((
13550 (runnable.buffer_id, row),
13551 RunnableTasks {
13552 templates: tasks,
13553 offset: snapshot
13554 .buffer_snapshot
13555 .anchor_before(runnable.run_range.start),
13556 context_range,
13557 column: point.column,
13558 extra_variables: runnable.extra_captures,
13559 },
13560 ))
13561 })
13562 .collect()
13563 }
13564
13565 fn templates_with_tags(
13566 project: &Entity<Project>,
13567 runnable: &mut Runnable,
13568 cx: &mut App,
13569 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13570 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13571 let (worktree_id, file) = project
13572 .buffer_for_id(runnable.buffer, cx)
13573 .and_then(|buffer| buffer.read(cx).file())
13574 .map(|file| (file.worktree_id(cx), file.clone()))
13575 .unzip();
13576
13577 (
13578 project.task_store().read(cx).task_inventory().cloned(),
13579 worktree_id,
13580 file,
13581 )
13582 });
13583
13584 let mut templates_with_tags = mem::take(&mut runnable.tags)
13585 .into_iter()
13586 .flat_map(|RunnableTag(tag)| {
13587 inventory
13588 .as_ref()
13589 .into_iter()
13590 .flat_map(|inventory| {
13591 inventory.read(cx).list_tasks(
13592 file.clone(),
13593 Some(runnable.language.clone()),
13594 worktree_id,
13595 cx,
13596 )
13597 })
13598 .filter(move |(_, template)| {
13599 template.tags.iter().any(|source_tag| source_tag == &tag)
13600 })
13601 })
13602 .sorted_by_key(|(kind, _)| kind.to_owned())
13603 .collect::<Vec<_>>();
13604 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13605 // Strongest source wins; if we have worktree tag binding, prefer that to
13606 // global and language bindings;
13607 // if we have a global binding, prefer that to language binding.
13608 let first_mismatch = templates_with_tags
13609 .iter()
13610 .position(|(tag_source, _)| tag_source != leading_tag_source);
13611 if let Some(index) = first_mismatch {
13612 templates_with_tags.truncate(index);
13613 }
13614 }
13615
13616 templates_with_tags
13617 }
13618
13619 pub fn move_to_enclosing_bracket(
13620 &mut self,
13621 _: &MoveToEnclosingBracket,
13622 window: &mut Window,
13623 cx: &mut Context<Self>,
13624 ) {
13625 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13626 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13627 s.move_offsets_with(|snapshot, selection| {
13628 let Some(enclosing_bracket_ranges) =
13629 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13630 else {
13631 return;
13632 };
13633
13634 let mut best_length = usize::MAX;
13635 let mut best_inside = false;
13636 let mut best_in_bracket_range = false;
13637 let mut best_destination = None;
13638 for (open, close) in enclosing_bracket_ranges {
13639 let close = close.to_inclusive();
13640 let length = close.end() - open.start;
13641 let inside = selection.start >= open.end && selection.end <= *close.start();
13642 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13643 || close.contains(&selection.head());
13644
13645 // If best is next to a bracket and current isn't, skip
13646 if !in_bracket_range && best_in_bracket_range {
13647 continue;
13648 }
13649
13650 // Prefer smaller lengths unless best is inside and current isn't
13651 if length > best_length && (best_inside || !inside) {
13652 continue;
13653 }
13654
13655 best_length = length;
13656 best_inside = inside;
13657 best_in_bracket_range = in_bracket_range;
13658 best_destination = Some(
13659 if close.contains(&selection.start) && close.contains(&selection.end) {
13660 if inside { open.end } else { open.start }
13661 } else if inside {
13662 *close.start()
13663 } else {
13664 *close.end()
13665 },
13666 );
13667 }
13668
13669 if let Some(destination) = best_destination {
13670 selection.collapse_to(destination, SelectionGoal::None);
13671 }
13672 })
13673 });
13674 }
13675
13676 pub fn undo_selection(
13677 &mut self,
13678 _: &UndoSelection,
13679 window: &mut Window,
13680 cx: &mut Context<Self>,
13681 ) {
13682 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13683 self.end_selection(window, cx);
13684 self.selection_history.mode = SelectionHistoryMode::Undoing;
13685 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13686 self.change_selections(None, window, cx, |s| {
13687 s.select_anchors(entry.selections.to_vec())
13688 });
13689 self.select_next_state = entry.select_next_state;
13690 self.select_prev_state = entry.select_prev_state;
13691 self.add_selections_state = entry.add_selections_state;
13692 self.request_autoscroll(Autoscroll::newest(), cx);
13693 }
13694 self.selection_history.mode = SelectionHistoryMode::Normal;
13695 }
13696
13697 pub fn redo_selection(
13698 &mut self,
13699 _: &RedoSelection,
13700 window: &mut Window,
13701 cx: &mut Context<Self>,
13702 ) {
13703 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13704 self.end_selection(window, cx);
13705 self.selection_history.mode = SelectionHistoryMode::Redoing;
13706 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13707 self.change_selections(None, window, cx, |s| {
13708 s.select_anchors(entry.selections.to_vec())
13709 });
13710 self.select_next_state = entry.select_next_state;
13711 self.select_prev_state = entry.select_prev_state;
13712 self.add_selections_state = entry.add_selections_state;
13713 self.request_autoscroll(Autoscroll::newest(), cx);
13714 }
13715 self.selection_history.mode = SelectionHistoryMode::Normal;
13716 }
13717
13718 pub fn expand_excerpts(
13719 &mut self,
13720 action: &ExpandExcerpts,
13721 _: &mut Window,
13722 cx: &mut Context<Self>,
13723 ) {
13724 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13725 }
13726
13727 pub fn expand_excerpts_down(
13728 &mut self,
13729 action: &ExpandExcerptsDown,
13730 _: &mut Window,
13731 cx: &mut Context<Self>,
13732 ) {
13733 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13734 }
13735
13736 pub fn expand_excerpts_up(
13737 &mut self,
13738 action: &ExpandExcerptsUp,
13739 _: &mut Window,
13740 cx: &mut Context<Self>,
13741 ) {
13742 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13743 }
13744
13745 pub fn expand_excerpts_for_direction(
13746 &mut self,
13747 lines: u32,
13748 direction: ExpandExcerptDirection,
13749
13750 cx: &mut Context<Self>,
13751 ) {
13752 let selections = self.selections.disjoint_anchors();
13753
13754 let lines = if lines == 0 {
13755 EditorSettings::get_global(cx).expand_excerpt_lines
13756 } else {
13757 lines
13758 };
13759
13760 self.buffer.update(cx, |buffer, cx| {
13761 let snapshot = buffer.snapshot(cx);
13762 let mut excerpt_ids = selections
13763 .iter()
13764 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13765 .collect::<Vec<_>>();
13766 excerpt_ids.sort();
13767 excerpt_ids.dedup();
13768 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13769 })
13770 }
13771
13772 pub fn expand_excerpt(
13773 &mut self,
13774 excerpt: ExcerptId,
13775 direction: ExpandExcerptDirection,
13776 window: &mut Window,
13777 cx: &mut Context<Self>,
13778 ) {
13779 let current_scroll_position = self.scroll_position(cx);
13780 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13781 let mut should_scroll_up = false;
13782
13783 if direction == ExpandExcerptDirection::Down {
13784 let multi_buffer = self.buffer.read(cx);
13785 let snapshot = multi_buffer.snapshot(cx);
13786 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13787 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13788 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13789 let buffer_snapshot = buffer.read(cx).snapshot();
13790 let excerpt_end_row =
13791 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13792 let last_row = buffer_snapshot.max_point().row;
13793 let lines_below = last_row.saturating_sub(excerpt_end_row);
13794 should_scroll_up = lines_below >= lines_to_expand;
13795 }
13796 }
13797 }
13798 }
13799
13800 self.buffer.update(cx, |buffer, cx| {
13801 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13802 });
13803
13804 if should_scroll_up {
13805 let new_scroll_position =
13806 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13807 self.set_scroll_position(new_scroll_position, window, cx);
13808 }
13809 }
13810
13811 pub fn go_to_singleton_buffer_point(
13812 &mut self,
13813 point: Point,
13814 window: &mut Window,
13815 cx: &mut Context<Self>,
13816 ) {
13817 self.go_to_singleton_buffer_range(point..point, window, cx);
13818 }
13819
13820 pub fn go_to_singleton_buffer_range(
13821 &mut self,
13822 range: Range<Point>,
13823 window: &mut Window,
13824 cx: &mut Context<Self>,
13825 ) {
13826 let multibuffer = self.buffer().read(cx);
13827 let Some(buffer) = multibuffer.as_singleton() else {
13828 return;
13829 };
13830 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13831 return;
13832 };
13833 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13834 return;
13835 };
13836 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13837 s.select_anchor_ranges([start..end])
13838 });
13839 }
13840
13841 pub fn go_to_diagnostic(
13842 &mut self,
13843 _: &GoToDiagnostic,
13844 window: &mut Window,
13845 cx: &mut Context<Self>,
13846 ) {
13847 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13848 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13849 }
13850
13851 pub fn go_to_prev_diagnostic(
13852 &mut self,
13853 _: &GoToPreviousDiagnostic,
13854 window: &mut Window,
13855 cx: &mut Context<Self>,
13856 ) {
13857 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13858 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13859 }
13860
13861 pub fn go_to_diagnostic_impl(
13862 &mut self,
13863 direction: Direction,
13864 window: &mut Window,
13865 cx: &mut Context<Self>,
13866 ) {
13867 let buffer = self.buffer.read(cx).snapshot(cx);
13868 let selection = self.selections.newest::<usize>(cx);
13869
13870 let mut active_group_id = None;
13871 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13872 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13873 active_group_id = Some(active_group.group_id);
13874 }
13875 }
13876
13877 fn filtered(
13878 snapshot: EditorSnapshot,
13879 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13880 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13881 diagnostics
13882 .filter(|entry| entry.range.start != entry.range.end)
13883 .filter(|entry| !entry.diagnostic.is_unnecessary)
13884 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13885 }
13886
13887 let snapshot = self.snapshot(window, cx);
13888 let before = filtered(
13889 snapshot.clone(),
13890 buffer
13891 .diagnostics_in_range(0..selection.start)
13892 .filter(|entry| entry.range.start <= selection.start),
13893 );
13894 let after = filtered(
13895 snapshot,
13896 buffer
13897 .diagnostics_in_range(selection.start..buffer.len())
13898 .filter(|entry| entry.range.start >= selection.start),
13899 );
13900
13901 let mut found: Option<DiagnosticEntry<usize>> = None;
13902 if direction == Direction::Prev {
13903 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13904 {
13905 for diagnostic in prev_diagnostics.into_iter().rev() {
13906 if diagnostic.range.start != selection.start
13907 || active_group_id
13908 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13909 {
13910 found = Some(diagnostic);
13911 break 'outer;
13912 }
13913 }
13914 }
13915 } else {
13916 for diagnostic in after.chain(before) {
13917 if diagnostic.range.start != selection.start
13918 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13919 {
13920 found = Some(diagnostic);
13921 break;
13922 }
13923 }
13924 }
13925 let Some(next_diagnostic) = found else {
13926 return;
13927 };
13928
13929 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13930 return;
13931 };
13932 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13933 s.select_ranges(vec![
13934 next_diagnostic.range.start..next_diagnostic.range.start,
13935 ])
13936 });
13937 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13938 self.refresh_inline_completion(false, true, window, cx);
13939 }
13940
13941 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13942 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13943 let snapshot = self.snapshot(window, cx);
13944 let selection = self.selections.newest::<Point>(cx);
13945 self.go_to_hunk_before_or_after_position(
13946 &snapshot,
13947 selection.head(),
13948 Direction::Next,
13949 window,
13950 cx,
13951 );
13952 }
13953
13954 pub fn go_to_hunk_before_or_after_position(
13955 &mut self,
13956 snapshot: &EditorSnapshot,
13957 position: Point,
13958 direction: Direction,
13959 window: &mut Window,
13960 cx: &mut Context<Editor>,
13961 ) {
13962 let row = if direction == Direction::Next {
13963 self.hunk_after_position(snapshot, position)
13964 .map(|hunk| hunk.row_range.start)
13965 } else {
13966 self.hunk_before_position(snapshot, position)
13967 };
13968
13969 if let Some(row) = row {
13970 let destination = Point::new(row.0, 0);
13971 let autoscroll = Autoscroll::center();
13972
13973 self.unfold_ranges(&[destination..destination], false, false, cx);
13974 self.change_selections(Some(autoscroll), window, cx, |s| {
13975 s.select_ranges([destination..destination]);
13976 });
13977 }
13978 }
13979
13980 fn hunk_after_position(
13981 &mut self,
13982 snapshot: &EditorSnapshot,
13983 position: Point,
13984 ) -> Option<MultiBufferDiffHunk> {
13985 snapshot
13986 .buffer_snapshot
13987 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13988 .find(|hunk| hunk.row_range.start.0 > position.row)
13989 .or_else(|| {
13990 snapshot
13991 .buffer_snapshot
13992 .diff_hunks_in_range(Point::zero()..position)
13993 .find(|hunk| hunk.row_range.end.0 < position.row)
13994 })
13995 }
13996
13997 fn go_to_prev_hunk(
13998 &mut self,
13999 _: &GoToPreviousHunk,
14000 window: &mut Window,
14001 cx: &mut Context<Self>,
14002 ) {
14003 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14004 let snapshot = self.snapshot(window, cx);
14005 let selection = self.selections.newest::<Point>(cx);
14006 self.go_to_hunk_before_or_after_position(
14007 &snapshot,
14008 selection.head(),
14009 Direction::Prev,
14010 window,
14011 cx,
14012 );
14013 }
14014
14015 fn hunk_before_position(
14016 &mut self,
14017 snapshot: &EditorSnapshot,
14018 position: Point,
14019 ) -> Option<MultiBufferRow> {
14020 snapshot
14021 .buffer_snapshot
14022 .diff_hunk_before(position)
14023 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14024 }
14025
14026 fn go_to_next_change(
14027 &mut self,
14028 _: &GoToNextChange,
14029 window: &mut Window,
14030 cx: &mut Context<Self>,
14031 ) {
14032 if let Some(selections) = self
14033 .change_list
14034 .next_change(1, Direction::Next)
14035 .map(|s| s.to_vec())
14036 {
14037 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14038 let map = s.display_map();
14039 s.select_display_ranges(selections.iter().map(|a| {
14040 let point = a.to_display_point(&map);
14041 point..point
14042 }))
14043 })
14044 }
14045 }
14046
14047 fn go_to_previous_change(
14048 &mut self,
14049 _: &GoToPreviousChange,
14050 window: &mut Window,
14051 cx: &mut Context<Self>,
14052 ) {
14053 if let Some(selections) = self
14054 .change_list
14055 .next_change(1, Direction::Prev)
14056 .map(|s| s.to_vec())
14057 {
14058 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14059 let map = s.display_map();
14060 s.select_display_ranges(selections.iter().map(|a| {
14061 let point = a.to_display_point(&map);
14062 point..point
14063 }))
14064 })
14065 }
14066 }
14067
14068 fn go_to_line<T: 'static>(
14069 &mut self,
14070 position: Anchor,
14071 highlight_color: Option<Hsla>,
14072 window: &mut Window,
14073 cx: &mut Context<Self>,
14074 ) {
14075 let snapshot = self.snapshot(window, cx).display_snapshot;
14076 let position = position.to_point(&snapshot.buffer_snapshot);
14077 let start = snapshot
14078 .buffer_snapshot
14079 .clip_point(Point::new(position.row, 0), Bias::Left);
14080 let end = start + Point::new(1, 0);
14081 let start = snapshot.buffer_snapshot.anchor_before(start);
14082 let end = snapshot.buffer_snapshot.anchor_before(end);
14083
14084 self.highlight_rows::<T>(
14085 start..end,
14086 highlight_color
14087 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14088 Default::default(),
14089 cx,
14090 );
14091
14092 if self.buffer.read(cx).is_singleton() {
14093 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14094 }
14095 }
14096
14097 pub fn go_to_definition(
14098 &mut self,
14099 _: &GoToDefinition,
14100 window: &mut Window,
14101 cx: &mut Context<Self>,
14102 ) -> Task<Result<Navigated>> {
14103 let definition =
14104 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14105 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14106 cx.spawn_in(window, async move |editor, cx| {
14107 if definition.await? == Navigated::Yes {
14108 return Ok(Navigated::Yes);
14109 }
14110 match fallback_strategy {
14111 GoToDefinitionFallback::None => Ok(Navigated::No),
14112 GoToDefinitionFallback::FindAllReferences => {
14113 match editor.update_in(cx, |editor, window, cx| {
14114 editor.find_all_references(&FindAllReferences, window, cx)
14115 })? {
14116 Some(references) => references.await,
14117 None => Ok(Navigated::No),
14118 }
14119 }
14120 }
14121 })
14122 }
14123
14124 pub fn go_to_declaration(
14125 &mut self,
14126 _: &GoToDeclaration,
14127 window: &mut Window,
14128 cx: &mut Context<Self>,
14129 ) -> Task<Result<Navigated>> {
14130 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14131 }
14132
14133 pub fn go_to_declaration_split(
14134 &mut self,
14135 _: &GoToDeclaration,
14136 window: &mut Window,
14137 cx: &mut Context<Self>,
14138 ) -> Task<Result<Navigated>> {
14139 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14140 }
14141
14142 pub fn go_to_implementation(
14143 &mut self,
14144 _: &GoToImplementation,
14145 window: &mut Window,
14146 cx: &mut Context<Self>,
14147 ) -> Task<Result<Navigated>> {
14148 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14149 }
14150
14151 pub fn go_to_implementation_split(
14152 &mut self,
14153 _: &GoToImplementationSplit,
14154 window: &mut Window,
14155 cx: &mut Context<Self>,
14156 ) -> Task<Result<Navigated>> {
14157 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14158 }
14159
14160 pub fn go_to_type_definition(
14161 &mut self,
14162 _: &GoToTypeDefinition,
14163 window: &mut Window,
14164 cx: &mut Context<Self>,
14165 ) -> Task<Result<Navigated>> {
14166 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14167 }
14168
14169 pub fn go_to_definition_split(
14170 &mut self,
14171 _: &GoToDefinitionSplit,
14172 window: &mut Window,
14173 cx: &mut Context<Self>,
14174 ) -> Task<Result<Navigated>> {
14175 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14176 }
14177
14178 pub fn go_to_type_definition_split(
14179 &mut self,
14180 _: &GoToTypeDefinitionSplit,
14181 window: &mut Window,
14182 cx: &mut Context<Self>,
14183 ) -> Task<Result<Navigated>> {
14184 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14185 }
14186
14187 fn go_to_definition_of_kind(
14188 &mut self,
14189 kind: GotoDefinitionKind,
14190 split: bool,
14191 window: &mut Window,
14192 cx: &mut Context<Self>,
14193 ) -> Task<Result<Navigated>> {
14194 let Some(provider) = self.semantics_provider.clone() else {
14195 return Task::ready(Ok(Navigated::No));
14196 };
14197 let head = self.selections.newest::<usize>(cx).head();
14198 let buffer = self.buffer.read(cx);
14199 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14200 text_anchor
14201 } else {
14202 return Task::ready(Ok(Navigated::No));
14203 };
14204
14205 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14206 return Task::ready(Ok(Navigated::No));
14207 };
14208
14209 cx.spawn_in(window, async move |editor, cx| {
14210 let definitions = definitions.await?;
14211 let navigated = editor
14212 .update_in(cx, |editor, window, cx| {
14213 editor.navigate_to_hover_links(
14214 Some(kind),
14215 definitions
14216 .into_iter()
14217 .filter(|location| {
14218 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14219 })
14220 .map(HoverLink::Text)
14221 .collect::<Vec<_>>(),
14222 split,
14223 window,
14224 cx,
14225 )
14226 })?
14227 .await?;
14228 anyhow::Ok(navigated)
14229 })
14230 }
14231
14232 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14233 let selection = self.selections.newest_anchor();
14234 let head = selection.head();
14235 let tail = selection.tail();
14236
14237 let Some((buffer, start_position)) =
14238 self.buffer.read(cx).text_anchor_for_position(head, cx)
14239 else {
14240 return;
14241 };
14242
14243 let end_position = if head != tail {
14244 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14245 return;
14246 };
14247 Some(pos)
14248 } else {
14249 None
14250 };
14251
14252 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14253 let url = if let Some(end_pos) = end_position {
14254 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14255 } else {
14256 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14257 };
14258
14259 if let Some(url) = url {
14260 editor.update(cx, |_, cx| {
14261 cx.open_url(&url);
14262 })
14263 } else {
14264 Ok(())
14265 }
14266 });
14267
14268 url_finder.detach();
14269 }
14270
14271 pub fn open_selected_filename(
14272 &mut self,
14273 _: &OpenSelectedFilename,
14274 window: &mut Window,
14275 cx: &mut Context<Self>,
14276 ) {
14277 let Some(workspace) = self.workspace() else {
14278 return;
14279 };
14280
14281 let position = self.selections.newest_anchor().head();
14282
14283 let Some((buffer, buffer_position)) =
14284 self.buffer.read(cx).text_anchor_for_position(position, cx)
14285 else {
14286 return;
14287 };
14288
14289 let project = self.project.clone();
14290
14291 cx.spawn_in(window, async move |_, cx| {
14292 let result = find_file(&buffer, project, buffer_position, cx).await;
14293
14294 if let Some((_, path)) = result {
14295 workspace
14296 .update_in(cx, |workspace, window, cx| {
14297 workspace.open_resolved_path(path, window, cx)
14298 })?
14299 .await?;
14300 }
14301 anyhow::Ok(())
14302 })
14303 .detach();
14304 }
14305
14306 pub(crate) fn navigate_to_hover_links(
14307 &mut self,
14308 kind: Option<GotoDefinitionKind>,
14309 mut definitions: Vec<HoverLink>,
14310 split: bool,
14311 window: &mut Window,
14312 cx: &mut Context<Editor>,
14313 ) -> Task<Result<Navigated>> {
14314 // If there is one definition, just open it directly
14315 if definitions.len() == 1 {
14316 let definition = definitions.pop().unwrap();
14317
14318 enum TargetTaskResult {
14319 Location(Option<Location>),
14320 AlreadyNavigated,
14321 }
14322
14323 let target_task = match definition {
14324 HoverLink::Text(link) => {
14325 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14326 }
14327 HoverLink::InlayHint(lsp_location, server_id) => {
14328 let computation =
14329 self.compute_target_location(lsp_location, server_id, window, cx);
14330 cx.background_spawn(async move {
14331 let location = computation.await?;
14332 Ok(TargetTaskResult::Location(location))
14333 })
14334 }
14335 HoverLink::Url(url) => {
14336 cx.open_url(&url);
14337 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14338 }
14339 HoverLink::File(path) => {
14340 if let Some(workspace) = self.workspace() {
14341 cx.spawn_in(window, async move |_, cx| {
14342 workspace
14343 .update_in(cx, |workspace, window, cx| {
14344 workspace.open_resolved_path(path, window, cx)
14345 })?
14346 .await
14347 .map(|_| TargetTaskResult::AlreadyNavigated)
14348 })
14349 } else {
14350 Task::ready(Ok(TargetTaskResult::Location(None)))
14351 }
14352 }
14353 };
14354 cx.spawn_in(window, async move |editor, cx| {
14355 let target = match target_task.await.context("target resolution task")? {
14356 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14357 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14358 TargetTaskResult::Location(Some(target)) => target,
14359 };
14360
14361 editor.update_in(cx, |editor, window, cx| {
14362 let Some(workspace) = editor.workspace() else {
14363 return Navigated::No;
14364 };
14365 let pane = workspace.read(cx).active_pane().clone();
14366
14367 let range = target.range.to_point(target.buffer.read(cx));
14368 let range = editor.range_for_match(&range);
14369 let range = collapse_multiline_range(range);
14370
14371 if !split
14372 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14373 {
14374 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14375 } else {
14376 window.defer(cx, move |window, cx| {
14377 let target_editor: Entity<Self> =
14378 workspace.update(cx, |workspace, cx| {
14379 let pane = if split {
14380 workspace.adjacent_pane(window, cx)
14381 } else {
14382 workspace.active_pane().clone()
14383 };
14384
14385 workspace.open_project_item(
14386 pane,
14387 target.buffer.clone(),
14388 true,
14389 true,
14390 window,
14391 cx,
14392 )
14393 });
14394 target_editor.update(cx, |target_editor, cx| {
14395 // When selecting a definition in a different buffer, disable the nav history
14396 // to avoid creating a history entry at the previous cursor location.
14397 pane.update(cx, |pane, _| pane.disable_history());
14398 target_editor.go_to_singleton_buffer_range(range, window, cx);
14399 pane.update(cx, |pane, _| pane.enable_history());
14400 });
14401 });
14402 }
14403 Navigated::Yes
14404 })
14405 })
14406 } else if !definitions.is_empty() {
14407 cx.spawn_in(window, async move |editor, cx| {
14408 let (title, location_tasks, workspace) = editor
14409 .update_in(cx, |editor, window, cx| {
14410 let tab_kind = match kind {
14411 Some(GotoDefinitionKind::Implementation) => "Implementations",
14412 _ => "Definitions",
14413 };
14414 let title = definitions
14415 .iter()
14416 .find_map(|definition| match definition {
14417 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14418 let buffer = origin.buffer.read(cx);
14419 format!(
14420 "{} for {}",
14421 tab_kind,
14422 buffer
14423 .text_for_range(origin.range.clone())
14424 .collect::<String>()
14425 )
14426 }),
14427 HoverLink::InlayHint(_, _) => None,
14428 HoverLink::Url(_) => None,
14429 HoverLink::File(_) => None,
14430 })
14431 .unwrap_or(tab_kind.to_string());
14432 let location_tasks = definitions
14433 .into_iter()
14434 .map(|definition| match definition {
14435 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14436 HoverLink::InlayHint(lsp_location, server_id) => editor
14437 .compute_target_location(lsp_location, server_id, window, cx),
14438 HoverLink::Url(_) => Task::ready(Ok(None)),
14439 HoverLink::File(_) => Task::ready(Ok(None)),
14440 })
14441 .collect::<Vec<_>>();
14442 (title, location_tasks, editor.workspace().clone())
14443 })
14444 .context("location tasks preparation")?;
14445
14446 let locations = future::join_all(location_tasks)
14447 .await
14448 .into_iter()
14449 .filter_map(|location| location.transpose())
14450 .collect::<Result<_>>()
14451 .context("location tasks")?;
14452
14453 let Some(workspace) = workspace else {
14454 return Ok(Navigated::No);
14455 };
14456 let opened = workspace
14457 .update_in(cx, |workspace, window, cx| {
14458 Self::open_locations_in_multibuffer(
14459 workspace,
14460 locations,
14461 title,
14462 split,
14463 MultibufferSelectionMode::First,
14464 window,
14465 cx,
14466 )
14467 })
14468 .ok();
14469
14470 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14471 })
14472 } else {
14473 Task::ready(Ok(Navigated::No))
14474 }
14475 }
14476
14477 fn compute_target_location(
14478 &self,
14479 lsp_location: lsp::Location,
14480 server_id: LanguageServerId,
14481 window: &mut Window,
14482 cx: &mut Context<Self>,
14483 ) -> Task<anyhow::Result<Option<Location>>> {
14484 let Some(project) = self.project.clone() else {
14485 return Task::ready(Ok(None));
14486 };
14487
14488 cx.spawn_in(window, async move |editor, cx| {
14489 let location_task = editor.update(cx, |_, cx| {
14490 project.update(cx, |project, cx| {
14491 let language_server_name = project
14492 .language_server_statuses(cx)
14493 .find(|(id, _)| server_id == *id)
14494 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14495 language_server_name.map(|language_server_name| {
14496 project.open_local_buffer_via_lsp(
14497 lsp_location.uri.clone(),
14498 server_id,
14499 language_server_name,
14500 cx,
14501 )
14502 })
14503 })
14504 })?;
14505 let location = match location_task {
14506 Some(task) => Some({
14507 let target_buffer_handle = task.await.context("open local buffer")?;
14508 let range = target_buffer_handle.update(cx, |target_buffer, _| {
14509 let target_start = target_buffer
14510 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14511 let target_end = target_buffer
14512 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14513 target_buffer.anchor_after(target_start)
14514 ..target_buffer.anchor_before(target_end)
14515 })?;
14516 Location {
14517 buffer: target_buffer_handle,
14518 range,
14519 }
14520 }),
14521 None => None,
14522 };
14523 Ok(location)
14524 })
14525 }
14526
14527 pub fn find_all_references(
14528 &mut self,
14529 _: &FindAllReferences,
14530 window: &mut Window,
14531 cx: &mut Context<Self>,
14532 ) -> Option<Task<Result<Navigated>>> {
14533 let selection = self.selections.newest::<usize>(cx);
14534 let multi_buffer = self.buffer.read(cx);
14535 let head = selection.head();
14536
14537 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14538 let head_anchor = multi_buffer_snapshot.anchor_at(
14539 head,
14540 if head < selection.tail() {
14541 Bias::Right
14542 } else {
14543 Bias::Left
14544 },
14545 );
14546
14547 match self
14548 .find_all_references_task_sources
14549 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14550 {
14551 Ok(_) => {
14552 log::info!(
14553 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14554 );
14555 return None;
14556 }
14557 Err(i) => {
14558 self.find_all_references_task_sources.insert(i, head_anchor);
14559 }
14560 }
14561
14562 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14563 let workspace = self.workspace()?;
14564 let project = workspace.read(cx).project().clone();
14565 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14566 Some(cx.spawn_in(window, async move |editor, cx| {
14567 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14568 if let Ok(i) = editor
14569 .find_all_references_task_sources
14570 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14571 {
14572 editor.find_all_references_task_sources.remove(i);
14573 }
14574 });
14575
14576 let locations = references.await?;
14577 if locations.is_empty() {
14578 return anyhow::Ok(Navigated::No);
14579 }
14580
14581 workspace.update_in(cx, |workspace, window, cx| {
14582 let title = locations
14583 .first()
14584 .as_ref()
14585 .map(|location| {
14586 let buffer = location.buffer.read(cx);
14587 format!(
14588 "References to `{}`",
14589 buffer
14590 .text_for_range(location.range.clone())
14591 .collect::<String>()
14592 )
14593 })
14594 .unwrap();
14595 Self::open_locations_in_multibuffer(
14596 workspace,
14597 locations,
14598 title,
14599 false,
14600 MultibufferSelectionMode::First,
14601 window,
14602 cx,
14603 );
14604 Navigated::Yes
14605 })
14606 }))
14607 }
14608
14609 /// Opens a multibuffer with the given project locations in it
14610 pub fn open_locations_in_multibuffer(
14611 workspace: &mut Workspace,
14612 mut locations: Vec<Location>,
14613 title: String,
14614 split: bool,
14615 multibuffer_selection_mode: MultibufferSelectionMode,
14616 window: &mut Window,
14617 cx: &mut Context<Workspace>,
14618 ) {
14619 // If there are multiple definitions, open them in a multibuffer
14620 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14621 let mut locations = locations.into_iter().peekable();
14622 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14623 let capability = workspace.project().read(cx).capability();
14624
14625 let excerpt_buffer = cx.new(|cx| {
14626 let mut multibuffer = MultiBuffer::new(capability);
14627 while let Some(location) = locations.next() {
14628 let buffer = location.buffer.read(cx);
14629 let mut ranges_for_buffer = Vec::new();
14630 let range = location.range.to_point(buffer);
14631 ranges_for_buffer.push(range.clone());
14632
14633 while let Some(next_location) = locations.peek() {
14634 if next_location.buffer == location.buffer {
14635 ranges_for_buffer.push(next_location.range.to_point(buffer));
14636 locations.next();
14637 } else {
14638 break;
14639 }
14640 }
14641
14642 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14643 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14644 PathKey::for_buffer(&location.buffer, cx),
14645 location.buffer.clone(),
14646 ranges_for_buffer,
14647 DEFAULT_MULTIBUFFER_CONTEXT,
14648 cx,
14649 );
14650 ranges.extend(new_ranges)
14651 }
14652
14653 multibuffer.with_title(title)
14654 });
14655
14656 let editor = cx.new(|cx| {
14657 Editor::for_multibuffer(
14658 excerpt_buffer,
14659 Some(workspace.project().clone()),
14660 window,
14661 cx,
14662 )
14663 });
14664 editor.update(cx, |editor, cx| {
14665 match multibuffer_selection_mode {
14666 MultibufferSelectionMode::First => {
14667 if let Some(first_range) = ranges.first() {
14668 editor.change_selections(None, window, cx, |selections| {
14669 selections.clear_disjoint();
14670 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14671 });
14672 }
14673 editor.highlight_background::<Self>(
14674 &ranges,
14675 |theme| theme.editor_highlighted_line_background,
14676 cx,
14677 );
14678 }
14679 MultibufferSelectionMode::All => {
14680 editor.change_selections(None, window, cx, |selections| {
14681 selections.clear_disjoint();
14682 selections.select_anchor_ranges(ranges);
14683 });
14684 }
14685 }
14686 editor.register_buffers_with_language_servers(cx);
14687 });
14688
14689 let item = Box::new(editor);
14690 let item_id = item.item_id();
14691
14692 if split {
14693 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14694 } else {
14695 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14696 let (preview_item_id, preview_item_idx) =
14697 workspace.active_pane().update(cx, |pane, _| {
14698 (pane.preview_item_id(), pane.preview_item_idx())
14699 });
14700
14701 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14702
14703 if let Some(preview_item_id) = preview_item_id {
14704 workspace.active_pane().update(cx, |pane, cx| {
14705 pane.remove_item(preview_item_id, false, false, window, cx);
14706 });
14707 }
14708 } else {
14709 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14710 }
14711 }
14712 workspace.active_pane().update(cx, |pane, cx| {
14713 pane.set_preview_item_id(Some(item_id), cx);
14714 });
14715 }
14716
14717 pub fn rename(
14718 &mut self,
14719 _: &Rename,
14720 window: &mut Window,
14721 cx: &mut Context<Self>,
14722 ) -> Option<Task<Result<()>>> {
14723 use language::ToOffset as _;
14724
14725 let provider = self.semantics_provider.clone()?;
14726 let selection = self.selections.newest_anchor().clone();
14727 let (cursor_buffer, cursor_buffer_position) = self
14728 .buffer
14729 .read(cx)
14730 .text_anchor_for_position(selection.head(), cx)?;
14731 let (tail_buffer, cursor_buffer_position_end) = self
14732 .buffer
14733 .read(cx)
14734 .text_anchor_for_position(selection.tail(), cx)?;
14735 if tail_buffer != cursor_buffer {
14736 return None;
14737 }
14738
14739 let snapshot = cursor_buffer.read(cx).snapshot();
14740 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14741 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14742 let prepare_rename = provider
14743 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14744 .unwrap_or_else(|| Task::ready(Ok(None)));
14745 drop(snapshot);
14746
14747 Some(cx.spawn_in(window, async move |this, cx| {
14748 let rename_range = if let Some(range) = prepare_rename.await? {
14749 Some(range)
14750 } else {
14751 this.update(cx, |this, cx| {
14752 let buffer = this.buffer.read(cx).snapshot(cx);
14753 let mut buffer_highlights = this
14754 .document_highlights_for_position(selection.head(), &buffer)
14755 .filter(|highlight| {
14756 highlight.start.excerpt_id == selection.head().excerpt_id
14757 && highlight.end.excerpt_id == selection.head().excerpt_id
14758 });
14759 buffer_highlights
14760 .next()
14761 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14762 })?
14763 };
14764 if let Some(rename_range) = rename_range {
14765 this.update_in(cx, |this, window, cx| {
14766 let snapshot = cursor_buffer.read(cx).snapshot();
14767 let rename_buffer_range = rename_range.to_offset(&snapshot);
14768 let cursor_offset_in_rename_range =
14769 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14770 let cursor_offset_in_rename_range_end =
14771 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14772
14773 this.take_rename(false, window, cx);
14774 let buffer = this.buffer.read(cx).read(cx);
14775 let cursor_offset = selection.head().to_offset(&buffer);
14776 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14777 let rename_end = rename_start + rename_buffer_range.len();
14778 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14779 let mut old_highlight_id = None;
14780 let old_name: Arc<str> = buffer
14781 .chunks(rename_start..rename_end, true)
14782 .map(|chunk| {
14783 if old_highlight_id.is_none() {
14784 old_highlight_id = chunk.syntax_highlight_id;
14785 }
14786 chunk.text
14787 })
14788 .collect::<String>()
14789 .into();
14790
14791 drop(buffer);
14792
14793 // Position the selection in the rename editor so that it matches the current selection.
14794 this.show_local_selections = false;
14795 let rename_editor = cx.new(|cx| {
14796 let mut editor = Editor::single_line(window, cx);
14797 editor.buffer.update(cx, |buffer, cx| {
14798 buffer.edit([(0..0, old_name.clone())], None, cx)
14799 });
14800 let rename_selection_range = match cursor_offset_in_rename_range
14801 .cmp(&cursor_offset_in_rename_range_end)
14802 {
14803 Ordering::Equal => {
14804 editor.select_all(&SelectAll, window, cx);
14805 return editor;
14806 }
14807 Ordering::Less => {
14808 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14809 }
14810 Ordering::Greater => {
14811 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14812 }
14813 };
14814 if rename_selection_range.end > old_name.len() {
14815 editor.select_all(&SelectAll, window, cx);
14816 } else {
14817 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14818 s.select_ranges([rename_selection_range]);
14819 });
14820 }
14821 editor
14822 });
14823 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14824 if e == &EditorEvent::Focused {
14825 cx.emit(EditorEvent::FocusedIn)
14826 }
14827 })
14828 .detach();
14829
14830 let write_highlights =
14831 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14832 let read_highlights =
14833 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14834 let ranges = write_highlights
14835 .iter()
14836 .flat_map(|(_, ranges)| ranges.iter())
14837 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14838 .cloned()
14839 .collect();
14840
14841 this.highlight_text::<Rename>(
14842 ranges,
14843 HighlightStyle {
14844 fade_out: Some(0.6),
14845 ..Default::default()
14846 },
14847 cx,
14848 );
14849 let rename_focus_handle = rename_editor.focus_handle(cx);
14850 window.focus(&rename_focus_handle);
14851 let block_id = this.insert_blocks(
14852 [BlockProperties {
14853 style: BlockStyle::Flex,
14854 placement: BlockPlacement::Below(range.start),
14855 height: Some(1),
14856 render: Arc::new({
14857 let rename_editor = rename_editor.clone();
14858 move |cx: &mut BlockContext| {
14859 let mut text_style = cx.editor_style.text.clone();
14860 if let Some(highlight_style) = old_highlight_id
14861 .and_then(|h| h.style(&cx.editor_style.syntax))
14862 {
14863 text_style = text_style.highlight(highlight_style);
14864 }
14865 div()
14866 .block_mouse_down()
14867 .pl(cx.anchor_x)
14868 .child(EditorElement::new(
14869 &rename_editor,
14870 EditorStyle {
14871 background: cx.theme().system().transparent,
14872 local_player: cx.editor_style.local_player,
14873 text: text_style,
14874 scrollbar_width: cx.editor_style.scrollbar_width,
14875 syntax: cx.editor_style.syntax.clone(),
14876 status: cx.editor_style.status.clone(),
14877 inlay_hints_style: HighlightStyle {
14878 font_weight: Some(FontWeight::BOLD),
14879 ..make_inlay_hints_style(cx.app)
14880 },
14881 inline_completion_styles: make_suggestion_styles(
14882 cx.app,
14883 ),
14884 ..EditorStyle::default()
14885 },
14886 ))
14887 .into_any_element()
14888 }
14889 }),
14890 priority: 0,
14891 render_in_minimap: true,
14892 }],
14893 Some(Autoscroll::fit()),
14894 cx,
14895 )[0];
14896 this.pending_rename = Some(RenameState {
14897 range,
14898 old_name,
14899 editor: rename_editor,
14900 block_id,
14901 });
14902 })?;
14903 }
14904
14905 Ok(())
14906 }))
14907 }
14908
14909 pub fn confirm_rename(
14910 &mut self,
14911 _: &ConfirmRename,
14912 window: &mut Window,
14913 cx: &mut Context<Self>,
14914 ) -> Option<Task<Result<()>>> {
14915 let rename = self.take_rename(false, window, cx)?;
14916 let workspace = self.workspace()?.downgrade();
14917 let (buffer, start) = self
14918 .buffer
14919 .read(cx)
14920 .text_anchor_for_position(rename.range.start, cx)?;
14921 let (end_buffer, _) = self
14922 .buffer
14923 .read(cx)
14924 .text_anchor_for_position(rename.range.end, cx)?;
14925 if buffer != end_buffer {
14926 return None;
14927 }
14928
14929 let old_name = rename.old_name;
14930 let new_name = rename.editor.read(cx).text(cx);
14931
14932 let rename = self.semantics_provider.as_ref()?.perform_rename(
14933 &buffer,
14934 start,
14935 new_name.clone(),
14936 cx,
14937 )?;
14938
14939 Some(cx.spawn_in(window, async move |editor, cx| {
14940 let project_transaction = rename.await?;
14941 Self::open_project_transaction(
14942 &editor,
14943 workspace,
14944 project_transaction,
14945 format!("Rename: {} → {}", old_name, new_name),
14946 cx,
14947 )
14948 .await?;
14949
14950 editor.update(cx, |editor, cx| {
14951 editor.refresh_document_highlights(cx);
14952 })?;
14953 Ok(())
14954 }))
14955 }
14956
14957 fn take_rename(
14958 &mut self,
14959 moving_cursor: bool,
14960 window: &mut Window,
14961 cx: &mut Context<Self>,
14962 ) -> Option<RenameState> {
14963 let rename = self.pending_rename.take()?;
14964 if rename.editor.focus_handle(cx).is_focused(window) {
14965 window.focus(&self.focus_handle);
14966 }
14967
14968 self.remove_blocks(
14969 [rename.block_id].into_iter().collect(),
14970 Some(Autoscroll::fit()),
14971 cx,
14972 );
14973 self.clear_highlights::<Rename>(cx);
14974 self.show_local_selections = true;
14975
14976 if moving_cursor {
14977 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14978 editor.selections.newest::<usize>(cx).head()
14979 });
14980
14981 // Update the selection to match the position of the selection inside
14982 // the rename editor.
14983 let snapshot = self.buffer.read(cx).read(cx);
14984 let rename_range = rename.range.to_offset(&snapshot);
14985 let cursor_in_editor = snapshot
14986 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14987 .min(rename_range.end);
14988 drop(snapshot);
14989
14990 self.change_selections(None, window, cx, |s| {
14991 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14992 });
14993 } else {
14994 self.refresh_document_highlights(cx);
14995 }
14996
14997 Some(rename)
14998 }
14999
15000 pub fn pending_rename(&self) -> Option<&RenameState> {
15001 self.pending_rename.as_ref()
15002 }
15003
15004 fn format(
15005 &mut self,
15006 _: &Format,
15007 window: &mut Window,
15008 cx: &mut Context<Self>,
15009 ) -> Option<Task<Result<()>>> {
15010 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15011
15012 let project = match &self.project {
15013 Some(project) => project.clone(),
15014 None => return None,
15015 };
15016
15017 Some(self.perform_format(
15018 project,
15019 FormatTrigger::Manual,
15020 FormatTarget::Buffers,
15021 window,
15022 cx,
15023 ))
15024 }
15025
15026 fn format_selections(
15027 &mut self,
15028 _: &FormatSelections,
15029 window: &mut Window,
15030 cx: &mut Context<Self>,
15031 ) -> Option<Task<Result<()>>> {
15032 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15033
15034 let project = match &self.project {
15035 Some(project) => project.clone(),
15036 None => return None,
15037 };
15038
15039 let ranges = self
15040 .selections
15041 .all_adjusted(cx)
15042 .into_iter()
15043 .map(|selection| selection.range())
15044 .collect_vec();
15045
15046 Some(self.perform_format(
15047 project,
15048 FormatTrigger::Manual,
15049 FormatTarget::Ranges(ranges),
15050 window,
15051 cx,
15052 ))
15053 }
15054
15055 fn perform_format(
15056 &mut self,
15057 project: Entity<Project>,
15058 trigger: FormatTrigger,
15059 target: FormatTarget,
15060 window: &mut Window,
15061 cx: &mut Context<Self>,
15062 ) -> Task<Result<()>> {
15063 let buffer = self.buffer.clone();
15064 let (buffers, target) = match target {
15065 FormatTarget::Buffers => {
15066 let mut buffers = buffer.read(cx).all_buffers();
15067 if trigger == FormatTrigger::Save {
15068 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15069 }
15070 (buffers, LspFormatTarget::Buffers)
15071 }
15072 FormatTarget::Ranges(selection_ranges) => {
15073 let multi_buffer = buffer.read(cx);
15074 let snapshot = multi_buffer.read(cx);
15075 let mut buffers = HashSet::default();
15076 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15077 BTreeMap::new();
15078 for selection_range in selection_ranges {
15079 for (buffer, buffer_range, _) in
15080 snapshot.range_to_buffer_ranges(selection_range)
15081 {
15082 let buffer_id = buffer.remote_id();
15083 let start = buffer.anchor_before(buffer_range.start);
15084 let end = buffer.anchor_after(buffer_range.end);
15085 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15086 buffer_id_to_ranges
15087 .entry(buffer_id)
15088 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15089 .or_insert_with(|| vec![start..end]);
15090 }
15091 }
15092 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15093 }
15094 };
15095
15096 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
15097 let selections_prev = transaction_id_prev
15098 .and_then(|transaction_id_prev| {
15099 // default to selections as they were after the last edit, if we have them,
15100 // instead of how they are now.
15101 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15102 // will take you back to where you made the last edit, instead of staying where you scrolled
15103 self.selection_history
15104 .transaction(transaction_id_prev)
15105 .map(|t| t.0.clone())
15106 })
15107 .unwrap_or_else(|| {
15108 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15109 self.selections.disjoint_anchors()
15110 });
15111
15112 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15113 let format = project.update(cx, |project, cx| {
15114 project.format(buffers, target, true, trigger, cx)
15115 });
15116
15117 cx.spawn_in(window, async move |editor, cx| {
15118 let transaction = futures::select_biased! {
15119 transaction = format.log_err().fuse() => transaction,
15120 () = timeout => {
15121 log::warn!("timed out waiting for formatting");
15122 None
15123 }
15124 };
15125
15126 buffer
15127 .update(cx, |buffer, cx| {
15128 if let Some(transaction) = transaction {
15129 if !buffer.is_singleton() {
15130 buffer.push_transaction(&transaction.0, cx);
15131 }
15132 }
15133 cx.notify();
15134 })
15135 .ok();
15136
15137 if let Some(transaction_id_now) =
15138 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15139 {
15140 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15141 if has_new_transaction {
15142 _ = editor.update(cx, |editor, _| {
15143 editor
15144 .selection_history
15145 .insert_transaction(transaction_id_now, selections_prev);
15146 });
15147 }
15148 }
15149
15150 Ok(())
15151 })
15152 }
15153
15154 fn organize_imports(
15155 &mut self,
15156 _: &OrganizeImports,
15157 window: &mut Window,
15158 cx: &mut Context<Self>,
15159 ) -> Option<Task<Result<()>>> {
15160 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15161 let project = match &self.project {
15162 Some(project) => project.clone(),
15163 None => return None,
15164 };
15165 Some(self.perform_code_action_kind(
15166 project,
15167 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15168 window,
15169 cx,
15170 ))
15171 }
15172
15173 fn perform_code_action_kind(
15174 &mut self,
15175 project: Entity<Project>,
15176 kind: CodeActionKind,
15177 window: &mut Window,
15178 cx: &mut Context<Self>,
15179 ) -> Task<Result<()>> {
15180 let buffer = self.buffer.clone();
15181 let buffers = buffer.read(cx).all_buffers();
15182 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15183 let apply_action = project.update(cx, |project, cx| {
15184 project.apply_code_action_kind(buffers, kind, true, cx)
15185 });
15186 cx.spawn_in(window, async move |_, cx| {
15187 let transaction = futures::select_biased! {
15188 () = timeout => {
15189 log::warn!("timed out waiting for executing code action");
15190 None
15191 }
15192 transaction = apply_action.log_err().fuse() => transaction,
15193 };
15194 buffer
15195 .update(cx, |buffer, cx| {
15196 // check if we need this
15197 if let Some(transaction) = transaction {
15198 if !buffer.is_singleton() {
15199 buffer.push_transaction(&transaction.0, cx);
15200 }
15201 }
15202 cx.notify();
15203 })
15204 .ok();
15205 Ok(())
15206 })
15207 }
15208
15209 fn restart_language_server(
15210 &mut self,
15211 _: &RestartLanguageServer,
15212 _: &mut Window,
15213 cx: &mut Context<Self>,
15214 ) {
15215 if let Some(project) = self.project.clone() {
15216 self.buffer.update(cx, |multi_buffer, cx| {
15217 project.update(cx, |project, cx| {
15218 project.restart_language_servers_for_buffers(
15219 multi_buffer.all_buffers().into_iter().collect(),
15220 cx,
15221 );
15222 });
15223 })
15224 }
15225 }
15226
15227 fn stop_language_server(
15228 &mut self,
15229 _: &StopLanguageServer,
15230 _: &mut Window,
15231 cx: &mut Context<Self>,
15232 ) {
15233 if let Some(project) = self.project.clone() {
15234 self.buffer.update(cx, |multi_buffer, cx| {
15235 project.update(cx, |project, cx| {
15236 project.stop_language_servers_for_buffers(
15237 multi_buffer.all_buffers().into_iter().collect(),
15238 cx,
15239 );
15240 cx.emit(project::Event::RefreshInlayHints);
15241 });
15242 });
15243 }
15244 }
15245
15246 fn cancel_language_server_work(
15247 workspace: &mut Workspace,
15248 _: &actions::CancelLanguageServerWork,
15249 _: &mut Window,
15250 cx: &mut Context<Workspace>,
15251 ) {
15252 let project = workspace.project();
15253 let buffers = workspace
15254 .active_item(cx)
15255 .and_then(|item| item.act_as::<Editor>(cx))
15256 .map_or(HashSet::default(), |editor| {
15257 editor.read(cx).buffer.read(cx).all_buffers()
15258 });
15259 project.update(cx, |project, cx| {
15260 project.cancel_language_server_work_for_buffers(buffers, cx);
15261 });
15262 }
15263
15264 fn show_character_palette(
15265 &mut self,
15266 _: &ShowCharacterPalette,
15267 window: &mut Window,
15268 _: &mut Context<Self>,
15269 ) {
15270 window.show_character_palette();
15271 }
15272
15273 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15274 if self.mode.is_minimap() {
15275 return;
15276 }
15277
15278 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15279 let buffer = self.buffer.read(cx).snapshot(cx);
15280 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15281 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15282 let is_valid = buffer
15283 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15284 .any(|entry| {
15285 entry.diagnostic.is_primary
15286 && !entry.range.is_empty()
15287 && entry.range.start == primary_range_start
15288 && entry.diagnostic.message == active_diagnostics.active_message
15289 });
15290
15291 if !is_valid {
15292 self.dismiss_diagnostics(cx);
15293 }
15294 }
15295 }
15296
15297 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15298 match &self.active_diagnostics {
15299 ActiveDiagnostic::Group(group) => Some(group),
15300 _ => None,
15301 }
15302 }
15303
15304 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15305 self.dismiss_diagnostics(cx);
15306 self.active_diagnostics = ActiveDiagnostic::All;
15307 }
15308
15309 fn activate_diagnostics(
15310 &mut self,
15311 buffer_id: BufferId,
15312 diagnostic: DiagnosticEntry<usize>,
15313 window: &mut Window,
15314 cx: &mut Context<Self>,
15315 ) {
15316 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15317 return;
15318 }
15319 self.dismiss_diagnostics(cx);
15320 let snapshot = self.snapshot(window, cx);
15321 let buffer = self.buffer.read(cx).snapshot(cx);
15322 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15323 return;
15324 };
15325
15326 let diagnostic_group = buffer
15327 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15328 .collect::<Vec<_>>();
15329
15330 let blocks =
15331 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15332
15333 let blocks = self.display_map.update(cx, |display_map, cx| {
15334 display_map.insert_blocks(blocks, cx).into_iter().collect()
15335 });
15336 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15337 active_range: buffer.anchor_before(diagnostic.range.start)
15338 ..buffer.anchor_after(diagnostic.range.end),
15339 active_message: diagnostic.diagnostic.message.clone(),
15340 group_id: diagnostic.diagnostic.group_id,
15341 blocks,
15342 });
15343 cx.notify();
15344 }
15345
15346 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15347 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15348 return;
15349 };
15350
15351 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15352 if let ActiveDiagnostic::Group(group) = prev {
15353 self.display_map.update(cx, |display_map, cx| {
15354 display_map.remove_blocks(group.blocks, cx);
15355 });
15356 cx.notify();
15357 }
15358 }
15359
15360 /// Disable inline diagnostics rendering for this editor.
15361 pub fn disable_inline_diagnostics(&mut self) {
15362 self.inline_diagnostics_enabled = false;
15363 self.inline_diagnostics_update = Task::ready(());
15364 self.inline_diagnostics.clear();
15365 }
15366
15367 pub fn diagnostics_enabled(&self) -> bool {
15368 self.mode.is_full()
15369 }
15370
15371 pub fn inline_diagnostics_enabled(&self) -> bool {
15372 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15373 }
15374
15375 pub fn show_inline_diagnostics(&self) -> bool {
15376 self.show_inline_diagnostics
15377 }
15378
15379 pub fn toggle_inline_diagnostics(
15380 &mut self,
15381 _: &ToggleInlineDiagnostics,
15382 window: &mut Window,
15383 cx: &mut Context<Editor>,
15384 ) {
15385 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15386 self.refresh_inline_diagnostics(false, window, cx);
15387 }
15388
15389 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15390 self.diagnostics_max_severity = severity;
15391 self.display_map.update(cx, |display_map, _| {
15392 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15393 });
15394 }
15395
15396 pub fn toggle_diagnostics(
15397 &mut self,
15398 _: &ToggleDiagnostics,
15399 window: &mut Window,
15400 cx: &mut Context<Editor>,
15401 ) {
15402 if !self.diagnostics_enabled() {
15403 return;
15404 }
15405
15406 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15407 EditorSettings::get_global(cx)
15408 .diagnostics_max_severity
15409 .filter(|severity| severity != &DiagnosticSeverity::Off)
15410 .unwrap_or(DiagnosticSeverity::Hint)
15411 } else {
15412 DiagnosticSeverity::Off
15413 };
15414 self.set_max_diagnostics_severity(new_severity, cx);
15415 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15416 self.active_diagnostics = ActiveDiagnostic::None;
15417 self.inline_diagnostics_update = Task::ready(());
15418 self.inline_diagnostics.clear();
15419 } else {
15420 self.refresh_inline_diagnostics(false, window, cx);
15421 }
15422
15423 cx.notify();
15424 }
15425
15426 pub fn toggle_minimap(
15427 &mut self,
15428 _: &ToggleMinimap,
15429 window: &mut Window,
15430 cx: &mut Context<Editor>,
15431 ) {
15432 if self.supports_minimap(cx) {
15433 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15434 }
15435 }
15436
15437 fn refresh_inline_diagnostics(
15438 &mut self,
15439 debounce: bool,
15440 window: &mut Window,
15441 cx: &mut Context<Self>,
15442 ) {
15443 let max_severity = ProjectSettings::get_global(cx)
15444 .diagnostics
15445 .inline
15446 .max_severity
15447 .unwrap_or(self.diagnostics_max_severity);
15448
15449 if !self.inline_diagnostics_enabled()
15450 || !self.show_inline_diagnostics
15451 || max_severity == DiagnosticSeverity::Off
15452 {
15453 self.inline_diagnostics_update = Task::ready(());
15454 self.inline_diagnostics.clear();
15455 return;
15456 }
15457
15458 let debounce_ms = ProjectSettings::get_global(cx)
15459 .diagnostics
15460 .inline
15461 .update_debounce_ms;
15462 let debounce = if debounce && debounce_ms > 0 {
15463 Some(Duration::from_millis(debounce_ms))
15464 } else {
15465 None
15466 };
15467 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15468 let editor = editor.upgrade().unwrap();
15469
15470 if let Some(debounce) = debounce {
15471 cx.background_executor().timer(debounce).await;
15472 }
15473 let Some(snapshot) = editor
15474 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15475 .ok()
15476 else {
15477 return;
15478 };
15479
15480 let new_inline_diagnostics = cx
15481 .background_spawn(async move {
15482 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15483 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15484 let message = diagnostic_entry
15485 .diagnostic
15486 .message
15487 .split_once('\n')
15488 .map(|(line, _)| line)
15489 .map(SharedString::new)
15490 .unwrap_or_else(|| {
15491 SharedString::from(diagnostic_entry.diagnostic.message)
15492 });
15493 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15494 let (Ok(i) | Err(i)) = inline_diagnostics
15495 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15496 inline_diagnostics.insert(
15497 i,
15498 (
15499 start_anchor,
15500 InlineDiagnostic {
15501 message,
15502 group_id: diagnostic_entry.diagnostic.group_id,
15503 start: diagnostic_entry.range.start.to_point(&snapshot),
15504 is_primary: diagnostic_entry.diagnostic.is_primary,
15505 severity: diagnostic_entry.diagnostic.severity,
15506 },
15507 ),
15508 );
15509 }
15510 inline_diagnostics
15511 })
15512 .await;
15513
15514 editor
15515 .update(cx, |editor, cx| {
15516 editor.inline_diagnostics = new_inline_diagnostics;
15517 cx.notify();
15518 })
15519 .ok();
15520 });
15521 }
15522
15523 pub fn set_selections_from_remote(
15524 &mut self,
15525 selections: Vec<Selection<Anchor>>,
15526 pending_selection: Option<Selection<Anchor>>,
15527 window: &mut Window,
15528 cx: &mut Context<Self>,
15529 ) {
15530 let old_cursor_position = self.selections.newest_anchor().head();
15531 self.selections.change_with(cx, |s| {
15532 s.select_anchors(selections);
15533 if let Some(pending_selection) = pending_selection {
15534 s.set_pending(pending_selection, SelectMode::Character);
15535 } else {
15536 s.clear_pending();
15537 }
15538 });
15539 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15540 }
15541
15542 fn push_to_selection_history(&mut self) {
15543 self.selection_history.push(SelectionHistoryEntry {
15544 selections: self.selections.disjoint_anchors(),
15545 select_next_state: self.select_next_state.clone(),
15546 select_prev_state: self.select_prev_state.clone(),
15547 add_selections_state: self.add_selections_state.clone(),
15548 });
15549 }
15550
15551 pub fn transact(
15552 &mut self,
15553 window: &mut Window,
15554 cx: &mut Context<Self>,
15555 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15556 ) -> Option<TransactionId> {
15557 self.start_transaction_at(Instant::now(), window, cx);
15558 update(self, window, cx);
15559 self.end_transaction_at(Instant::now(), cx)
15560 }
15561
15562 pub fn start_transaction_at(
15563 &mut self,
15564 now: Instant,
15565 window: &mut Window,
15566 cx: &mut Context<Self>,
15567 ) {
15568 self.end_selection(window, cx);
15569 if let Some(tx_id) = self
15570 .buffer
15571 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15572 {
15573 self.selection_history
15574 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15575 cx.emit(EditorEvent::TransactionBegun {
15576 transaction_id: tx_id,
15577 })
15578 }
15579 }
15580
15581 pub fn end_transaction_at(
15582 &mut self,
15583 now: Instant,
15584 cx: &mut Context<Self>,
15585 ) -> Option<TransactionId> {
15586 if let Some(transaction_id) = self
15587 .buffer
15588 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15589 {
15590 if let Some((_, end_selections)) =
15591 self.selection_history.transaction_mut(transaction_id)
15592 {
15593 *end_selections = Some(self.selections.disjoint_anchors());
15594 } else {
15595 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15596 }
15597
15598 cx.emit(EditorEvent::Edited { transaction_id });
15599 Some(transaction_id)
15600 } else {
15601 None
15602 }
15603 }
15604
15605 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15606 if self.selection_mark_mode {
15607 self.change_selections(None, window, cx, |s| {
15608 s.move_with(|_, sel| {
15609 sel.collapse_to(sel.head(), SelectionGoal::None);
15610 });
15611 })
15612 }
15613 self.selection_mark_mode = true;
15614 cx.notify();
15615 }
15616
15617 pub fn swap_selection_ends(
15618 &mut self,
15619 _: &actions::SwapSelectionEnds,
15620 window: &mut Window,
15621 cx: &mut Context<Self>,
15622 ) {
15623 self.change_selections(None, window, cx, |s| {
15624 s.move_with(|_, sel| {
15625 if sel.start != sel.end {
15626 sel.reversed = !sel.reversed
15627 }
15628 });
15629 });
15630 self.request_autoscroll(Autoscroll::newest(), cx);
15631 cx.notify();
15632 }
15633
15634 pub fn toggle_fold(
15635 &mut self,
15636 _: &actions::ToggleFold,
15637 window: &mut Window,
15638 cx: &mut Context<Self>,
15639 ) {
15640 if self.is_singleton(cx) {
15641 let selection = self.selections.newest::<Point>(cx);
15642
15643 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15644 let range = if selection.is_empty() {
15645 let point = selection.head().to_display_point(&display_map);
15646 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15647 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15648 .to_point(&display_map);
15649 start..end
15650 } else {
15651 selection.range()
15652 };
15653 if display_map.folds_in_range(range).next().is_some() {
15654 self.unfold_lines(&Default::default(), window, cx)
15655 } else {
15656 self.fold(&Default::default(), window, cx)
15657 }
15658 } else {
15659 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15660 let buffer_ids: HashSet<_> = self
15661 .selections
15662 .disjoint_anchor_ranges()
15663 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15664 .collect();
15665
15666 let should_unfold = buffer_ids
15667 .iter()
15668 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15669
15670 for buffer_id in buffer_ids {
15671 if should_unfold {
15672 self.unfold_buffer(buffer_id, cx);
15673 } else {
15674 self.fold_buffer(buffer_id, cx);
15675 }
15676 }
15677 }
15678 }
15679
15680 pub fn toggle_fold_recursive(
15681 &mut self,
15682 _: &actions::ToggleFoldRecursive,
15683 window: &mut Window,
15684 cx: &mut Context<Self>,
15685 ) {
15686 let selection = self.selections.newest::<Point>(cx);
15687
15688 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15689 let range = if selection.is_empty() {
15690 let point = selection.head().to_display_point(&display_map);
15691 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15692 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15693 .to_point(&display_map);
15694 start..end
15695 } else {
15696 selection.range()
15697 };
15698 if display_map.folds_in_range(range).next().is_some() {
15699 self.unfold_recursive(&Default::default(), window, cx)
15700 } else {
15701 self.fold_recursive(&Default::default(), window, cx)
15702 }
15703 }
15704
15705 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15706 if self.is_singleton(cx) {
15707 let mut to_fold = Vec::new();
15708 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15709 let selections = self.selections.all_adjusted(cx);
15710
15711 for selection in selections {
15712 let range = selection.range().sorted();
15713 let buffer_start_row = range.start.row;
15714
15715 if range.start.row != range.end.row {
15716 let mut found = false;
15717 let mut row = range.start.row;
15718 while row <= range.end.row {
15719 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15720 {
15721 found = true;
15722 row = crease.range().end.row + 1;
15723 to_fold.push(crease);
15724 } else {
15725 row += 1
15726 }
15727 }
15728 if found {
15729 continue;
15730 }
15731 }
15732
15733 for row in (0..=range.start.row).rev() {
15734 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15735 if crease.range().end.row >= buffer_start_row {
15736 to_fold.push(crease);
15737 if row <= range.start.row {
15738 break;
15739 }
15740 }
15741 }
15742 }
15743 }
15744
15745 self.fold_creases(to_fold, true, window, cx);
15746 } else {
15747 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15748 let buffer_ids = self
15749 .selections
15750 .disjoint_anchor_ranges()
15751 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15752 .collect::<HashSet<_>>();
15753 for buffer_id in buffer_ids {
15754 self.fold_buffer(buffer_id, cx);
15755 }
15756 }
15757 }
15758
15759 fn fold_at_level(
15760 &mut self,
15761 fold_at: &FoldAtLevel,
15762 window: &mut Window,
15763 cx: &mut Context<Self>,
15764 ) {
15765 if !self.buffer.read(cx).is_singleton() {
15766 return;
15767 }
15768
15769 let fold_at_level = fold_at.0;
15770 let snapshot = self.buffer.read(cx).snapshot(cx);
15771 let mut to_fold = Vec::new();
15772 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15773
15774 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15775 while start_row < end_row {
15776 match self
15777 .snapshot(window, cx)
15778 .crease_for_buffer_row(MultiBufferRow(start_row))
15779 {
15780 Some(crease) => {
15781 let nested_start_row = crease.range().start.row + 1;
15782 let nested_end_row = crease.range().end.row;
15783
15784 if current_level < fold_at_level {
15785 stack.push((nested_start_row, nested_end_row, current_level + 1));
15786 } else if current_level == fold_at_level {
15787 to_fold.push(crease);
15788 }
15789
15790 start_row = nested_end_row + 1;
15791 }
15792 None => start_row += 1,
15793 }
15794 }
15795 }
15796
15797 self.fold_creases(to_fold, true, window, cx);
15798 }
15799
15800 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15801 if self.buffer.read(cx).is_singleton() {
15802 let mut fold_ranges = Vec::new();
15803 let snapshot = self.buffer.read(cx).snapshot(cx);
15804
15805 for row in 0..snapshot.max_row().0 {
15806 if let Some(foldable_range) = self
15807 .snapshot(window, cx)
15808 .crease_for_buffer_row(MultiBufferRow(row))
15809 {
15810 fold_ranges.push(foldable_range);
15811 }
15812 }
15813
15814 self.fold_creases(fold_ranges, true, window, cx);
15815 } else {
15816 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15817 editor
15818 .update_in(cx, |editor, _, cx| {
15819 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15820 editor.fold_buffer(buffer_id, cx);
15821 }
15822 })
15823 .ok();
15824 });
15825 }
15826 }
15827
15828 pub fn fold_function_bodies(
15829 &mut self,
15830 _: &actions::FoldFunctionBodies,
15831 window: &mut Window,
15832 cx: &mut Context<Self>,
15833 ) {
15834 let snapshot = self.buffer.read(cx).snapshot(cx);
15835
15836 let ranges = snapshot
15837 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15838 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15839 .collect::<Vec<_>>();
15840
15841 let creases = ranges
15842 .into_iter()
15843 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15844 .collect();
15845
15846 self.fold_creases(creases, true, window, cx);
15847 }
15848
15849 pub fn fold_recursive(
15850 &mut self,
15851 _: &actions::FoldRecursive,
15852 window: &mut Window,
15853 cx: &mut Context<Self>,
15854 ) {
15855 let mut to_fold = Vec::new();
15856 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15857 let selections = self.selections.all_adjusted(cx);
15858
15859 for selection in selections {
15860 let range = selection.range().sorted();
15861 let buffer_start_row = range.start.row;
15862
15863 if range.start.row != range.end.row {
15864 let mut found = false;
15865 for row in range.start.row..=range.end.row {
15866 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15867 found = true;
15868 to_fold.push(crease);
15869 }
15870 }
15871 if found {
15872 continue;
15873 }
15874 }
15875
15876 for row in (0..=range.start.row).rev() {
15877 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15878 if crease.range().end.row >= buffer_start_row {
15879 to_fold.push(crease);
15880 } else {
15881 break;
15882 }
15883 }
15884 }
15885 }
15886
15887 self.fold_creases(to_fold, true, window, cx);
15888 }
15889
15890 pub fn fold_at(
15891 &mut self,
15892 buffer_row: MultiBufferRow,
15893 window: &mut Window,
15894 cx: &mut Context<Self>,
15895 ) {
15896 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15897
15898 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15899 let autoscroll = self
15900 .selections
15901 .all::<Point>(cx)
15902 .iter()
15903 .any(|selection| crease.range().overlaps(&selection.range()));
15904
15905 self.fold_creases(vec![crease], autoscroll, window, cx);
15906 }
15907 }
15908
15909 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15910 if self.is_singleton(cx) {
15911 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15912 let buffer = &display_map.buffer_snapshot;
15913 let selections = self.selections.all::<Point>(cx);
15914 let ranges = selections
15915 .iter()
15916 .map(|s| {
15917 let range = s.display_range(&display_map).sorted();
15918 let mut start = range.start.to_point(&display_map);
15919 let mut end = range.end.to_point(&display_map);
15920 start.column = 0;
15921 end.column = buffer.line_len(MultiBufferRow(end.row));
15922 start..end
15923 })
15924 .collect::<Vec<_>>();
15925
15926 self.unfold_ranges(&ranges, true, true, cx);
15927 } else {
15928 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15929 let buffer_ids = self
15930 .selections
15931 .disjoint_anchor_ranges()
15932 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15933 .collect::<HashSet<_>>();
15934 for buffer_id in buffer_ids {
15935 self.unfold_buffer(buffer_id, cx);
15936 }
15937 }
15938 }
15939
15940 pub fn unfold_recursive(
15941 &mut self,
15942 _: &UnfoldRecursive,
15943 _window: &mut Window,
15944 cx: &mut Context<Self>,
15945 ) {
15946 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15947 let selections = self.selections.all::<Point>(cx);
15948 let ranges = selections
15949 .iter()
15950 .map(|s| {
15951 let mut range = s.display_range(&display_map).sorted();
15952 *range.start.column_mut() = 0;
15953 *range.end.column_mut() = display_map.line_len(range.end.row());
15954 let start = range.start.to_point(&display_map);
15955 let end = range.end.to_point(&display_map);
15956 start..end
15957 })
15958 .collect::<Vec<_>>();
15959
15960 self.unfold_ranges(&ranges, true, true, cx);
15961 }
15962
15963 pub fn unfold_at(
15964 &mut self,
15965 buffer_row: MultiBufferRow,
15966 _window: &mut Window,
15967 cx: &mut Context<Self>,
15968 ) {
15969 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15970
15971 let intersection_range = Point::new(buffer_row.0, 0)
15972 ..Point::new(
15973 buffer_row.0,
15974 display_map.buffer_snapshot.line_len(buffer_row),
15975 );
15976
15977 let autoscroll = self
15978 .selections
15979 .all::<Point>(cx)
15980 .iter()
15981 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15982
15983 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15984 }
15985
15986 pub fn unfold_all(
15987 &mut self,
15988 _: &actions::UnfoldAll,
15989 _window: &mut Window,
15990 cx: &mut Context<Self>,
15991 ) {
15992 if self.buffer.read(cx).is_singleton() {
15993 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15994 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15995 } else {
15996 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15997 editor
15998 .update(cx, |editor, cx| {
15999 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16000 editor.unfold_buffer(buffer_id, cx);
16001 }
16002 })
16003 .ok();
16004 });
16005 }
16006 }
16007
16008 pub fn fold_selected_ranges(
16009 &mut self,
16010 _: &FoldSelectedRanges,
16011 window: &mut Window,
16012 cx: &mut Context<Self>,
16013 ) {
16014 let selections = self.selections.all_adjusted(cx);
16015 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16016 let ranges = selections
16017 .into_iter()
16018 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16019 .collect::<Vec<_>>();
16020 self.fold_creases(ranges, true, window, cx);
16021 }
16022
16023 pub fn fold_ranges<T: ToOffset + Clone>(
16024 &mut self,
16025 ranges: Vec<Range<T>>,
16026 auto_scroll: bool,
16027 window: &mut Window,
16028 cx: &mut Context<Self>,
16029 ) {
16030 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16031 let ranges = ranges
16032 .into_iter()
16033 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16034 .collect::<Vec<_>>();
16035 self.fold_creases(ranges, auto_scroll, window, cx);
16036 }
16037
16038 pub fn fold_creases<T: ToOffset + Clone>(
16039 &mut self,
16040 creases: Vec<Crease<T>>,
16041 auto_scroll: bool,
16042 _window: &mut Window,
16043 cx: &mut Context<Self>,
16044 ) {
16045 if creases.is_empty() {
16046 return;
16047 }
16048
16049 let mut buffers_affected = HashSet::default();
16050 let multi_buffer = self.buffer().read(cx);
16051 for crease in &creases {
16052 if let Some((_, buffer, _)) =
16053 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16054 {
16055 buffers_affected.insert(buffer.read(cx).remote_id());
16056 };
16057 }
16058
16059 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16060
16061 if auto_scroll {
16062 self.request_autoscroll(Autoscroll::fit(), cx);
16063 }
16064
16065 cx.notify();
16066
16067 self.scrollbar_marker_state.dirty = true;
16068 self.folds_did_change(cx);
16069 }
16070
16071 /// Removes any folds whose ranges intersect any of the given ranges.
16072 pub fn unfold_ranges<T: ToOffset + Clone>(
16073 &mut self,
16074 ranges: &[Range<T>],
16075 inclusive: bool,
16076 auto_scroll: bool,
16077 cx: &mut Context<Self>,
16078 ) {
16079 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16080 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16081 });
16082 self.folds_did_change(cx);
16083 }
16084
16085 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16086 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16087 return;
16088 }
16089 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16090 self.display_map.update(cx, |display_map, cx| {
16091 display_map.fold_buffers([buffer_id], cx)
16092 });
16093 cx.emit(EditorEvent::BufferFoldToggled {
16094 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16095 folded: true,
16096 });
16097 cx.notify();
16098 }
16099
16100 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16101 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16102 return;
16103 }
16104 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16105 self.display_map.update(cx, |display_map, cx| {
16106 display_map.unfold_buffers([buffer_id], cx);
16107 });
16108 cx.emit(EditorEvent::BufferFoldToggled {
16109 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16110 folded: false,
16111 });
16112 cx.notify();
16113 }
16114
16115 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16116 self.display_map.read(cx).is_buffer_folded(buffer)
16117 }
16118
16119 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16120 self.display_map.read(cx).folded_buffers()
16121 }
16122
16123 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16124 self.display_map.update(cx, |display_map, cx| {
16125 display_map.disable_header_for_buffer(buffer_id, cx);
16126 });
16127 cx.notify();
16128 }
16129
16130 /// Removes any folds with the given ranges.
16131 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16132 &mut self,
16133 ranges: &[Range<T>],
16134 type_id: TypeId,
16135 auto_scroll: bool,
16136 cx: &mut Context<Self>,
16137 ) {
16138 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16139 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16140 });
16141 self.folds_did_change(cx);
16142 }
16143
16144 fn remove_folds_with<T: ToOffset + Clone>(
16145 &mut self,
16146 ranges: &[Range<T>],
16147 auto_scroll: bool,
16148 cx: &mut Context<Self>,
16149 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16150 ) {
16151 if ranges.is_empty() {
16152 return;
16153 }
16154
16155 let mut buffers_affected = HashSet::default();
16156 let multi_buffer = self.buffer().read(cx);
16157 for range in ranges {
16158 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16159 buffers_affected.insert(buffer.read(cx).remote_id());
16160 };
16161 }
16162
16163 self.display_map.update(cx, update);
16164
16165 if auto_scroll {
16166 self.request_autoscroll(Autoscroll::fit(), cx);
16167 }
16168
16169 cx.notify();
16170 self.scrollbar_marker_state.dirty = true;
16171 self.active_indent_guides_state.dirty = true;
16172 }
16173
16174 pub fn update_fold_widths(
16175 &mut self,
16176 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16177 cx: &mut Context<Self>,
16178 ) -> bool {
16179 self.display_map
16180 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16181 }
16182
16183 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16184 self.display_map.read(cx).fold_placeholder.clone()
16185 }
16186
16187 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16188 self.buffer.update(cx, |buffer, cx| {
16189 buffer.set_all_diff_hunks_expanded(cx);
16190 });
16191 }
16192
16193 pub fn expand_all_diff_hunks(
16194 &mut self,
16195 _: &ExpandAllDiffHunks,
16196 _window: &mut Window,
16197 cx: &mut Context<Self>,
16198 ) {
16199 self.buffer.update(cx, |buffer, cx| {
16200 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16201 });
16202 }
16203
16204 pub fn toggle_selected_diff_hunks(
16205 &mut self,
16206 _: &ToggleSelectedDiffHunks,
16207 _window: &mut Window,
16208 cx: &mut Context<Self>,
16209 ) {
16210 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16211 self.toggle_diff_hunks_in_ranges(ranges, cx);
16212 }
16213
16214 pub fn diff_hunks_in_ranges<'a>(
16215 &'a self,
16216 ranges: &'a [Range<Anchor>],
16217 buffer: &'a MultiBufferSnapshot,
16218 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16219 ranges.iter().flat_map(move |range| {
16220 let end_excerpt_id = range.end.excerpt_id;
16221 let range = range.to_point(buffer);
16222 let mut peek_end = range.end;
16223 if range.end.row < buffer.max_row().0 {
16224 peek_end = Point::new(range.end.row + 1, 0);
16225 }
16226 buffer
16227 .diff_hunks_in_range(range.start..peek_end)
16228 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16229 })
16230 }
16231
16232 pub fn has_stageable_diff_hunks_in_ranges(
16233 &self,
16234 ranges: &[Range<Anchor>],
16235 snapshot: &MultiBufferSnapshot,
16236 ) -> bool {
16237 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16238 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16239 }
16240
16241 pub fn toggle_staged_selected_diff_hunks(
16242 &mut self,
16243 _: &::git::ToggleStaged,
16244 _: &mut Window,
16245 cx: &mut Context<Self>,
16246 ) {
16247 let snapshot = self.buffer.read(cx).snapshot(cx);
16248 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16249 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16250 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16251 }
16252
16253 pub fn set_render_diff_hunk_controls(
16254 &mut self,
16255 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16256 cx: &mut Context<Self>,
16257 ) {
16258 self.render_diff_hunk_controls = render_diff_hunk_controls;
16259 cx.notify();
16260 }
16261
16262 pub fn stage_and_next(
16263 &mut self,
16264 _: &::git::StageAndNext,
16265 window: &mut Window,
16266 cx: &mut Context<Self>,
16267 ) {
16268 self.do_stage_or_unstage_and_next(true, window, cx);
16269 }
16270
16271 pub fn unstage_and_next(
16272 &mut self,
16273 _: &::git::UnstageAndNext,
16274 window: &mut Window,
16275 cx: &mut Context<Self>,
16276 ) {
16277 self.do_stage_or_unstage_and_next(false, window, cx);
16278 }
16279
16280 pub fn stage_or_unstage_diff_hunks(
16281 &mut self,
16282 stage: bool,
16283 ranges: Vec<Range<Anchor>>,
16284 cx: &mut Context<Self>,
16285 ) {
16286 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16287 cx.spawn(async move |this, cx| {
16288 task.await?;
16289 this.update(cx, |this, cx| {
16290 let snapshot = this.buffer.read(cx).snapshot(cx);
16291 let chunk_by = this
16292 .diff_hunks_in_ranges(&ranges, &snapshot)
16293 .chunk_by(|hunk| hunk.buffer_id);
16294 for (buffer_id, hunks) in &chunk_by {
16295 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16296 }
16297 })
16298 })
16299 .detach_and_log_err(cx);
16300 }
16301
16302 fn save_buffers_for_ranges_if_needed(
16303 &mut self,
16304 ranges: &[Range<Anchor>],
16305 cx: &mut Context<Editor>,
16306 ) -> Task<Result<()>> {
16307 let multibuffer = self.buffer.read(cx);
16308 let snapshot = multibuffer.read(cx);
16309 let buffer_ids: HashSet<_> = ranges
16310 .iter()
16311 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16312 .collect();
16313 drop(snapshot);
16314
16315 let mut buffers = HashSet::default();
16316 for buffer_id in buffer_ids {
16317 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16318 let buffer = buffer_entity.read(cx);
16319 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16320 {
16321 buffers.insert(buffer_entity);
16322 }
16323 }
16324 }
16325
16326 if let Some(project) = &self.project {
16327 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16328 } else {
16329 Task::ready(Ok(()))
16330 }
16331 }
16332
16333 fn do_stage_or_unstage_and_next(
16334 &mut self,
16335 stage: bool,
16336 window: &mut Window,
16337 cx: &mut Context<Self>,
16338 ) {
16339 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16340
16341 if ranges.iter().any(|range| range.start != range.end) {
16342 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16343 return;
16344 }
16345
16346 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16347 let snapshot = self.snapshot(window, cx);
16348 let position = self.selections.newest::<Point>(cx).head();
16349 let mut row = snapshot
16350 .buffer_snapshot
16351 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16352 .find(|hunk| hunk.row_range.start.0 > position.row)
16353 .map(|hunk| hunk.row_range.start);
16354
16355 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16356 // Outside of the project diff editor, wrap around to the beginning.
16357 if !all_diff_hunks_expanded {
16358 row = row.or_else(|| {
16359 snapshot
16360 .buffer_snapshot
16361 .diff_hunks_in_range(Point::zero()..position)
16362 .find(|hunk| hunk.row_range.end.0 < position.row)
16363 .map(|hunk| hunk.row_range.start)
16364 });
16365 }
16366
16367 if let Some(row) = row {
16368 let destination = Point::new(row.0, 0);
16369 let autoscroll = Autoscroll::center();
16370
16371 self.unfold_ranges(&[destination..destination], false, false, cx);
16372 self.change_selections(Some(autoscroll), window, cx, |s| {
16373 s.select_ranges([destination..destination]);
16374 });
16375 }
16376 }
16377
16378 fn do_stage_or_unstage(
16379 &self,
16380 stage: bool,
16381 buffer_id: BufferId,
16382 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16383 cx: &mut App,
16384 ) -> Option<()> {
16385 let project = self.project.as_ref()?;
16386 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16387 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16388 let buffer_snapshot = buffer.read(cx).snapshot();
16389 let file_exists = buffer_snapshot
16390 .file()
16391 .is_some_and(|file| file.disk_state().exists());
16392 diff.update(cx, |diff, cx| {
16393 diff.stage_or_unstage_hunks(
16394 stage,
16395 &hunks
16396 .map(|hunk| buffer_diff::DiffHunk {
16397 buffer_range: hunk.buffer_range,
16398 diff_base_byte_range: hunk.diff_base_byte_range,
16399 secondary_status: hunk.secondary_status,
16400 range: Point::zero()..Point::zero(), // unused
16401 })
16402 .collect::<Vec<_>>(),
16403 &buffer_snapshot,
16404 file_exists,
16405 cx,
16406 )
16407 });
16408 None
16409 }
16410
16411 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16412 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16413 self.buffer
16414 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16415 }
16416
16417 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16418 self.buffer.update(cx, |buffer, cx| {
16419 let ranges = vec![Anchor::min()..Anchor::max()];
16420 if !buffer.all_diff_hunks_expanded()
16421 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16422 {
16423 buffer.collapse_diff_hunks(ranges, cx);
16424 true
16425 } else {
16426 false
16427 }
16428 })
16429 }
16430
16431 fn toggle_diff_hunks_in_ranges(
16432 &mut self,
16433 ranges: Vec<Range<Anchor>>,
16434 cx: &mut Context<Editor>,
16435 ) {
16436 self.buffer.update(cx, |buffer, cx| {
16437 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16438 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16439 })
16440 }
16441
16442 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16443 self.buffer.update(cx, |buffer, cx| {
16444 let snapshot = buffer.snapshot(cx);
16445 let excerpt_id = range.end.excerpt_id;
16446 let point_range = range.to_point(&snapshot);
16447 let expand = !buffer.single_hunk_is_expanded(range, cx);
16448 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16449 })
16450 }
16451
16452 pub(crate) fn apply_all_diff_hunks(
16453 &mut self,
16454 _: &ApplyAllDiffHunks,
16455 window: &mut Window,
16456 cx: &mut Context<Self>,
16457 ) {
16458 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16459
16460 let buffers = self.buffer.read(cx).all_buffers();
16461 for branch_buffer in buffers {
16462 branch_buffer.update(cx, |branch_buffer, cx| {
16463 branch_buffer.merge_into_base(Vec::new(), cx);
16464 });
16465 }
16466
16467 if let Some(project) = self.project.clone() {
16468 self.save(true, project, window, cx).detach_and_log_err(cx);
16469 }
16470 }
16471
16472 pub(crate) fn apply_selected_diff_hunks(
16473 &mut self,
16474 _: &ApplyDiffHunk,
16475 window: &mut Window,
16476 cx: &mut Context<Self>,
16477 ) {
16478 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16479 let snapshot = self.snapshot(window, cx);
16480 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16481 let mut ranges_by_buffer = HashMap::default();
16482 self.transact(window, cx, |editor, _window, cx| {
16483 for hunk in hunks {
16484 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16485 ranges_by_buffer
16486 .entry(buffer.clone())
16487 .or_insert_with(Vec::new)
16488 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16489 }
16490 }
16491
16492 for (buffer, ranges) in ranges_by_buffer {
16493 buffer.update(cx, |buffer, cx| {
16494 buffer.merge_into_base(ranges, cx);
16495 });
16496 }
16497 });
16498
16499 if let Some(project) = self.project.clone() {
16500 self.save(true, project, window, cx).detach_and_log_err(cx);
16501 }
16502 }
16503
16504 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16505 if hovered != self.gutter_hovered {
16506 self.gutter_hovered = hovered;
16507 cx.notify();
16508 }
16509 }
16510
16511 pub fn insert_blocks(
16512 &mut self,
16513 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16514 autoscroll: Option<Autoscroll>,
16515 cx: &mut Context<Self>,
16516 ) -> Vec<CustomBlockId> {
16517 let blocks = self
16518 .display_map
16519 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16520 if let Some(autoscroll) = autoscroll {
16521 self.request_autoscroll(autoscroll, cx);
16522 }
16523 cx.notify();
16524 blocks
16525 }
16526
16527 pub fn resize_blocks(
16528 &mut self,
16529 heights: HashMap<CustomBlockId, u32>,
16530 autoscroll: Option<Autoscroll>,
16531 cx: &mut Context<Self>,
16532 ) {
16533 self.display_map
16534 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16535 if let Some(autoscroll) = autoscroll {
16536 self.request_autoscroll(autoscroll, cx);
16537 }
16538 cx.notify();
16539 }
16540
16541 pub fn replace_blocks(
16542 &mut self,
16543 renderers: HashMap<CustomBlockId, RenderBlock>,
16544 autoscroll: Option<Autoscroll>,
16545 cx: &mut Context<Self>,
16546 ) {
16547 self.display_map
16548 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16549 if let Some(autoscroll) = autoscroll {
16550 self.request_autoscroll(autoscroll, cx);
16551 }
16552 cx.notify();
16553 }
16554
16555 pub fn remove_blocks(
16556 &mut self,
16557 block_ids: HashSet<CustomBlockId>,
16558 autoscroll: Option<Autoscroll>,
16559 cx: &mut Context<Self>,
16560 ) {
16561 self.display_map.update(cx, |display_map, cx| {
16562 display_map.remove_blocks(block_ids, cx)
16563 });
16564 if let Some(autoscroll) = autoscroll {
16565 self.request_autoscroll(autoscroll, cx);
16566 }
16567 cx.notify();
16568 }
16569
16570 pub fn row_for_block(
16571 &self,
16572 block_id: CustomBlockId,
16573 cx: &mut Context<Self>,
16574 ) -> Option<DisplayRow> {
16575 self.display_map
16576 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16577 }
16578
16579 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16580 self.focused_block = Some(focused_block);
16581 }
16582
16583 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16584 self.focused_block.take()
16585 }
16586
16587 pub fn insert_creases(
16588 &mut self,
16589 creases: impl IntoIterator<Item = Crease<Anchor>>,
16590 cx: &mut Context<Self>,
16591 ) -> Vec<CreaseId> {
16592 self.display_map
16593 .update(cx, |map, cx| map.insert_creases(creases, cx))
16594 }
16595
16596 pub fn remove_creases(
16597 &mut self,
16598 ids: impl IntoIterator<Item = CreaseId>,
16599 cx: &mut Context<Self>,
16600 ) -> Vec<(CreaseId, Range<Anchor>)> {
16601 self.display_map
16602 .update(cx, |map, cx| map.remove_creases(ids, cx))
16603 }
16604
16605 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16606 self.display_map
16607 .update(cx, |map, cx| map.snapshot(cx))
16608 .longest_row()
16609 }
16610
16611 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16612 self.display_map
16613 .update(cx, |map, cx| map.snapshot(cx))
16614 .max_point()
16615 }
16616
16617 pub fn text(&self, cx: &App) -> String {
16618 self.buffer.read(cx).read(cx).text()
16619 }
16620
16621 pub fn is_empty(&self, cx: &App) -> bool {
16622 self.buffer.read(cx).read(cx).is_empty()
16623 }
16624
16625 pub fn text_option(&self, cx: &App) -> Option<String> {
16626 let text = self.text(cx);
16627 let text = text.trim();
16628
16629 if text.is_empty() {
16630 return None;
16631 }
16632
16633 Some(text.to_string())
16634 }
16635
16636 pub fn set_text(
16637 &mut self,
16638 text: impl Into<Arc<str>>,
16639 window: &mut Window,
16640 cx: &mut Context<Self>,
16641 ) {
16642 self.transact(window, cx, |this, _, cx| {
16643 this.buffer
16644 .read(cx)
16645 .as_singleton()
16646 .expect("you can only call set_text on editors for singleton buffers")
16647 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16648 });
16649 }
16650
16651 pub fn display_text(&self, cx: &mut App) -> String {
16652 self.display_map
16653 .update(cx, |map, cx| map.snapshot(cx))
16654 .text()
16655 }
16656
16657 fn create_minimap(
16658 &self,
16659 minimap_settings: MinimapSettings,
16660 window: &mut Window,
16661 cx: &mut Context<Self>,
16662 ) -> Option<Entity<Self>> {
16663 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
16664 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
16665 }
16666
16667 fn initialize_new_minimap(
16668 &self,
16669 minimap_settings: MinimapSettings,
16670 window: &mut Window,
16671 cx: &mut Context<Self>,
16672 ) -> Entity<Self> {
16673 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
16674
16675 let mut minimap = Editor::new_internal(
16676 EditorMode::Minimap {
16677 parent: cx.weak_entity(),
16678 },
16679 self.buffer.clone(),
16680 self.project.clone(),
16681 Some(self.display_map.clone()),
16682 window,
16683 cx,
16684 );
16685 minimap.scroll_manager.clone_state(&self.scroll_manager);
16686 minimap.set_text_style_refinement(TextStyleRefinement {
16687 font_size: Some(MINIMAP_FONT_SIZE),
16688 font_weight: Some(MINIMAP_FONT_WEIGHT),
16689 ..Default::default()
16690 });
16691 minimap.update_minimap_configuration(minimap_settings, cx);
16692 cx.new(|_| minimap)
16693 }
16694
16695 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
16696 let current_line_highlight = minimap_settings
16697 .current_line_highlight
16698 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
16699 self.set_current_line_highlight(Some(current_line_highlight));
16700 }
16701
16702 pub fn minimap(&self) -> Option<&Entity<Self>> {
16703 self.minimap
16704 .as_ref()
16705 .filter(|_| self.minimap_visibility.visible())
16706 }
16707
16708 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16709 let mut wrap_guides = smallvec![];
16710
16711 if self.show_wrap_guides == Some(false) {
16712 return wrap_guides;
16713 }
16714
16715 let settings = self.buffer.read(cx).language_settings(cx);
16716 if settings.show_wrap_guides {
16717 match self.soft_wrap_mode(cx) {
16718 SoftWrap::Column(soft_wrap) => {
16719 wrap_guides.push((soft_wrap as usize, true));
16720 }
16721 SoftWrap::Bounded(soft_wrap) => {
16722 wrap_guides.push((soft_wrap as usize, true));
16723 }
16724 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16725 }
16726 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16727 }
16728
16729 wrap_guides
16730 }
16731
16732 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16733 let settings = self.buffer.read(cx).language_settings(cx);
16734 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16735 match mode {
16736 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16737 SoftWrap::None
16738 }
16739 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16740 language_settings::SoftWrap::PreferredLineLength => {
16741 SoftWrap::Column(settings.preferred_line_length)
16742 }
16743 language_settings::SoftWrap::Bounded => {
16744 SoftWrap::Bounded(settings.preferred_line_length)
16745 }
16746 }
16747 }
16748
16749 pub fn set_soft_wrap_mode(
16750 &mut self,
16751 mode: language_settings::SoftWrap,
16752
16753 cx: &mut Context<Self>,
16754 ) {
16755 self.soft_wrap_mode_override = Some(mode);
16756 cx.notify();
16757 }
16758
16759 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16760 self.hard_wrap = hard_wrap;
16761 cx.notify();
16762 }
16763
16764 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16765 self.text_style_refinement = Some(style);
16766 }
16767
16768 /// called by the Element so we know what style we were most recently rendered with.
16769 pub(crate) fn set_style(
16770 &mut self,
16771 style: EditorStyle,
16772 window: &mut Window,
16773 cx: &mut Context<Self>,
16774 ) {
16775 // We intentionally do not inform the display map about the minimap style
16776 // so that wrapping is not recalculated and stays consistent for the editor
16777 // and its linked minimap.
16778 if !self.mode.is_minimap() {
16779 let rem_size = window.rem_size();
16780 self.display_map.update(cx, |map, cx| {
16781 map.set_font(
16782 style.text.font(),
16783 style.text.font_size.to_pixels(rem_size),
16784 cx,
16785 )
16786 });
16787 }
16788 self.style = Some(style);
16789 }
16790
16791 pub fn style(&self) -> Option<&EditorStyle> {
16792 self.style.as_ref()
16793 }
16794
16795 // Called by the element. This method is not designed to be called outside of the editor
16796 // element's layout code because it does not notify when rewrapping is computed synchronously.
16797 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16798 self.display_map
16799 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16800 }
16801
16802 pub fn set_soft_wrap(&mut self) {
16803 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16804 }
16805
16806 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16807 if self.soft_wrap_mode_override.is_some() {
16808 self.soft_wrap_mode_override.take();
16809 } else {
16810 let soft_wrap = match self.soft_wrap_mode(cx) {
16811 SoftWrap::GitDiff => return,
16812 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16813 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16814 language_settings::SoftWrap::None
16815 }
16816 };
16817 self.soft_wrap_mode_override = Some(soft_wrap);
16818 }
16819 cx.notify();
16820 }
16821
16822 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16823 let Some(workspace) = self.workspace() else {
16824 return;
16825 };
16826 let fs = workspace.read(cx).app_state().fs.clone();
16827 let current_show = TabBarSettings::get_global(cx).show;
16828 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16829 setting.show = Some(!current_show);
16830 });
16831 }
16832
16833 pub fn toggle_indent_guides(
16834 &mut self,
16835 _: &ToggleIndentGuides,
16836 _: &mut Window,
16837 cx: &mut Context<Self>,
16838 ) {
16839 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16840 self.buffer
16841 .read(cx)
16842 .language_settings(cx)
16843 .indent_guides
16844 .enabled
16845 });
16846 self.show_indent_guides = Some(!currently_enabled);
16847 cx.notify();
16848 }
16849
16850 fn should_show_indent_guides(&self) -> Option<bool> {
16851 self.show_indent_guides
16852 }
16853
16854 pub fn toggle_line_numbers(
16855 &mut self,
16856 _: &ToggleLineNumbers,
16857 _: &mut Window,
16858 cx: &mut Context<Self>,
16859 ) {
16860 let mut editor_settings = EditorSettings::get_global(cx).clone();
16861 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16862 EditorSettings::override_global(editor_settings, cx);
16863 }
16864
16865 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16866 if let Some(show_line_numbers) = self.show_line_numbers {
16867 return show_line_numbers;
16868 }
16869 EditorSettings::get_global(cx).gutter.line_numbers
16870 }
16871
16872 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16873 self.use_relative_line_numbers
16874 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16875 }
16876
16877 pub fn toggle_relative_line_numbers(
16878 &mut self,
16879 _: &ToggleRelativeLineNumbers,
16880 _: &mut Window,
16881 cx: &mut Context<Self>,
16882 ) {
16883 let is_relative = self.should_use_relative_line_numbers(cx);
16884 self.set_relative_line_number(Some(!is_relative), cx)
16885 }
16886
16887 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16888 self.use_relative_line_numbers = is_relative;
16889 cx.notify();
16890 }
16891
16892 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16893 self.show_gutter = show_gutter;
16894 cx.notify();
16895 }
16896
16897 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
16898 self.show_scrollbars = show_scrollbars;
16899 cx.notify();
16900 }
16901
16902 pub fn set_minimap_visibility(
16903 &mut self,
16904 minimap_visibility: MinimapVisibility,
16905 window: &mut Window,
16906 cx: &mut Context<Self>,
16907 ) {
16908 if self.minimap_visibility != minimap_visibility {
16909 if minimap_visibility.visible() && self.minimap.is_none() {
16910 let minimap_settings = EditorSettings::get_global(cx).minimap;
16911 self.minimap =
16912 self.create_minimap(minimap_settings.with_show_override(), window, cx);
16913 }
16914 self.minimap_visibility = minimap_visibility;
16915 cx.notify();
16916 }
16917 }
16918
16919 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16920 self.set_show_scrollbars(false, cx);
16921 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
16922 }
16923
16924 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
16925 self.show_line_numbers = Some(show_line_numbers);
16926 cx.notify();
16927 }
16928
16929 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
16930 self.disable_expand_excerpt_buttons = true;
16931 cx.notify();
16932 }
16933
16934 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
16935 self.show_git_diff_gutter = Some(show_git_diff_gutter);
16936 cx.notify();
16937 }
16938
16939 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
16940 self.show_code_actions = Some(show_code_actions);
16941 cx.notify();
16942 }
16943
16944 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
16945 self.show_runnables = Some(show_runnables);
16946 cx.notify();
16947 }
16948
16949 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
16950 self.show_breakpoints = Some(show_breakpoints);
16951 cx.notify();
16952 }
16953
16954 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
16955 if self.display_map.read(cx).masked != masked {
16956 self.display_map.update(cx, |map, _| map.masked = masked);
16957 }
16958 cx.notify()
16959 }
16960
16961 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
16962 self.show_wrap_guides = Some(show_wrap_guides);
16963 cx.notify();
16964 }
16965
16966 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
16967 self.show_indent_guides = Some(show_indent_guides);
16968 cx.notify();
16969 }
16970
16971 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
16972 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
16973 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
16974 if let Some(dir) = file.abs_path(cx).parent() {
16975 return Some(dir.to_owned());
16976 }
16977 }
16978
16979 if let Some(project_path) = buffer.read(cx).project_path(cx) {
16980 return Some(project_path.path.to_path_buf());
16981 }
16982 }
16983
16984 None
16985 }
16986
16987 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
16988 self.active_excerpt(cx)?
16989 .1
16990 .read(cx)
16991 .file()
16992 .and_then(|f| f.as_local())
16993 }
16994
16995 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16996 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16997 let buffer = buffer.read(cx);
16998 if let Some(project_path) = buffer.project_path(cx) {
16999 let project = self.project.as_ref()?.read(cx);
17000 project.absolute_path(&project_path, cx)
17001 } else {
17002 buffer
17003 .file()
17004 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17005 }
17006 })
17007 }
17008
17009 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17010 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17011 let project_path = buffer.read(cx).project_path(cx)?;
17012 let project = self.project.as_ref()?.read(cx);
17013 let entry = project.entry_for_path(&project_path, cx)?;
17014 let path = entry.path.to_path_buf();
17015 Some(path)
17016 })
17017 }
17018
17019 pub fn reveal_in_finder(
17020 &mut self,
17021 _: &RevealInFileManager,
17022 _window: &mut Window,
17023 cx: &mut Context<Self>,
17024 ) {
17025 if let Some(target) = self.target_file(cx) {
17026 cx.reveal_path(&target.abs_path(cx));
17027 }
17028 }
17029
17030 pub fn copy_path(
17031 &mut self,
17032 _: &zed_actions::workspace::CopyPath,
17033 _window: &mut Window,
17034 cx: &mut Context<Self>,
17035 ) {
17036 if let Some(path) = self.target_file_abs_path(cx) {
17037 if let Some(path) = path.to_str() {
17038 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17039 }
17040 }
17041 }
17042
17043 pub fn copy_relative_path(
17044 &mut self,
17045 _: &zed_actions::workspace::CopyRelativePath,
17046 _window: &mut Window,
17047 cx: &mut Context<Self>,
17048 ) {
17049 if let Some(path) = self.target_file_path(cx) {
17050 if let Some(path) = path.to_str() {
17051 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17052 }
17053 }
17054 }
17055
17056 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17057 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17058 buffer.read(cx).project_path(cx)
17059 } else {
17060 None
17061 }
17062 }
17063
17064 // Returns true if the editor handled a go-to-line request
17065 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17066 maybe!({
17067 let breakpoint_store = self.breakpoint_store.as_ref()?;
17068
17069 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17070 else {
17071 self.clear_row_highlights::<ActiveDebugLine>();
17072 return None;
17073 };
17074
17075 let position = active_stack_frame.position;
17076 let buffer_id = position.buffer_id?;
17077 let snapshot = self
17078 .project
17079 .as_ref()?
17080 .read(cx)
17081 .buffer_for_id(buffer_id, cx)?
17082 .read(cx)
17083 .snapshot();
17084
17085 let mut handled = false;
17086 for (id, ExcerptRange { context, .. }) in
17087 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17088 {
17089 if context.start.cmp(&position, &snapshot).is_ge()
17090 || context.end.cmp(&position, &snapshot).is_lt()
17091 {
17092 continue;
17093 }
17094 let snapshot = self.buffer.read(cx).snapshot(cx);
17095 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17096
17097 handled = true;
17098 self.clear_row_highlights::<ActiveDebugLine>();
17099
17100 self.go_to_line::<ActiveDebugLine>(
17101 multibuffer_anchor,
17102 Some(cx.theme().colors().editor_debugger_active_line_background),
17103 window,
17104 cx,
17105 );
17106
17107 cx.notify();
17108 }
17109
17110 handled.then_some(())
17111 })
17112 .is_some()
17113 }
17114
17115 pub fn copy_file_name_without_extension(
17116 &mut self,
17117 _: &CopyFileNameWithoutExtension,
17118 _: &mut Window,
17119 cx: &mut Context<Self>,
17120 ) {
17121 if let Some(file) = self.target_file(cx) {
17122 if let Some(file_stem) = file.path().file_stem() {
17123 if let Some(name) = file_stem.to_str() {
17124 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17125 }
17126 }
17127 }
17128 }
17129
17130 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17131 if let Some(file) = self.target_file(cx) {
17132 if let Some(file_name) = file.path().file_name() {
17133 if let Some(name) = file_name.to_str() {
17134 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17135 }
17136 }
17137 }
17138 }
17139
17140 pub fn toggle_git_blame(
17141 &mut self,
17142 _: &::git::Blame,
17143 window: &mut Window,
17144 cx: &mut Context<Self>,
17145 ) {
17146 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17147
17148 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17149 self.start_git_blame(true, window, cx);
17150 }
17151
17152 cx.notify();
17153 }
17154
17155 pub fn toggle_git_blame_inline(
17156 &mut self,
17157 _: &ToggleGitBlameInline,
17158 window: &mut Window,
17159 cx: &mut Context<Self>,
17160 ) {
17161 self.toggle_git_blame_inline_internal(true, window, cx);
17162 cx.notify();
17163 }
17164
17165 pub fn open_git_blame_commit(
17166 &mut self,
17167 _: &OpenGitBlameCommit,
17168 window: &mut Window,
17169 cx: &mut Context<Self>,
17170 ) {
17171 self.open_git_blame_commit_internal(window, cx);
17172 }
17173
17174 fn open_git_blame_commit_internal(
17175 &mut self,
17176 window: &mut Window,
17177 cx: &mut Context<Self>,
17178 ) -> Option<()> {
17179 let blame = self.blame.as_ref()?;
17180 let snapshot = self.snapshot(window, cx);
17181 let cursor = self.selections.newest::<Point>(cx).head();
17182 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17183 let blame_entry = blame
17184 .update(cx, |blame, cx| {
17185 blame
17186 .blame_for_rows(
17187 &[RowInfo {
17188 buffer_id: Some(buffer.remote_id()),
17189 buffer_row: Some(point.row),
17190 ..Default::default()
17191 }],
17192 cx,
17193 )
17194 .next()
17195 })
17196 .flatten()?;
17197 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17198 let repo = blame.read(cx).repository(cx)?;
17199 let workspace = self.workspace()?.downgrade();
17200 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17201 None
17202 }
17203
17204 pub fn git_blame_inline_enabled(&self) -> bool {
17205 self.git_blame_inline_enabled
17206 }
17207
17208 pub fn toggle_selection_menu(
17209 &mut self,
17210 _: &ToggleSelectionMenu,
17211 _: &mut Window,
17212 cx: &mut Context<Self>,
17213 ) {
17214 self.show_selection_menu = self
17215 .show_selection_menu
17216 .map(|show_selections_menu| !show_selections_menu)
17217 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17218
17219 cx.notify();
17220 }
17221
17222 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17223 self.show_selection_menu
17224 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17225 }
17226
17227 fn start_git_blame(
17228 &mut self,
17229 user_triggered: bool,
17230 window: &mut Window,
17231 cx: &mut Context<Self>,
17232 ) {
17233 if let Some(project) = self.project.as_ref() {
17234 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17235 return;
17236 };
17237
17238 if buffer.read(cx).file().is_none() {
17239 return;
17240 }
17241
17242 let focused = self.focus_handle(cx).contains_focused(window, cx);
17243
17244 let project = project.clone();
17245 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17246 self.blame_subscription =
17247 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17248 self.blame = Some(blame);
17249 }
17250 }
17251
17252 fn toggle_git_blame_inline_internal(
17253 &mut self,
17254 user_triggered: bool,
17255 window: &mut Window,
17256 cx: &mut Context<Self>,
17257 ) {
17258 if self.git_blame_inline_enabled {
17259 self.git_blame_inline_enabled = false;
17260 self.show_git_blame_inline = false;
17261 self.show_git_blame_inline_delay_task.take();
17262 } else {
17263 self.git_blame_inline_enabled = true;
17264 self.start_git_blame_inline(user_triggered, window, cx);
17265 }
17266
17267 cx.notify();
17268 }
17269
17270 fn start_git_blame_inline(
17271 &mut self,
17272 user_triggered: bool,
17273 window: &mut Window,
17274 cx: &mut Context<Self>,
17275 ) {
17276 self.start_git_blame(user_triggered, window, cx);
17277
17278 if ProjectSettings::get_global(cx)
17279 .git
17280 .inline_blame_delay()
17281 .is_some()
17282 {
17283 self.start_inline_blame_timer(window, cx);
17284 } else {
17285 self.show_git_blame_inline = true
17286 }
17287 }
17288
17289 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17290 self.blame.as_ref()
17291 }
17292
17293 pub fn show_git_blame_gutter(&self) -> bool {
17294 self.show_git_blame_gutter
17295 }
17296
17297 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17298 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17299 }
17300
17301 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17302 self.show_git_blame_inline
17303 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17304 && !self.newest_selection_head_on_empty_line(cx)
17305 && self.has_blame_entries(cx)
17306 }
17307
17308 fn has_blame_entries(&self, cx: &App) -> bool {
17309 self.blame()
17310 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17311 }
17312
17313 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17314 let cursor_anchor = self.selections.newest_anchor().head();
17315
17316 let snapshot = self.buffer.read(cx).snapshot(cx);
17317 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17318
17319 snapshot.line_len(buffer_row) == 0
17320 }
17321
17322 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17323 let buffer_and_selection = maybe!({
17324 let selection = self.selections.newest::<Point>(cx);
17325 let selection_range = selection.range();
17326
17327 let multi_buffer = self.buffer().read(cx);
17328 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17329 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17330
17331 let (buffer, range, _) = if selection.reversed {
17332 buffer_ranges.first()
17333 } else {
17334 buffer_ranges.last()
17335 }?;
17336
17337 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17338 ..text::ToPoint::to_point(&range.end, &buffer).row;
17339 Some((
17340 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17341 selection,
17342 ))
17343 });
17344
17345 let Some((buffer, selection)) = buffer_and_selection else {
17346 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17347 };
17348
17349 let Some(project) = self.project.as_ref() else {
17350 return Task::ready(Err(anyhow!("editor does not have project")));
17351 };
17352
17353 project.update(cx, |project, cx| {
17354 project.get_permalink_to_line(&buffer, selection, cx)
17355 })
17356 }
17357
17358 pub fn copy_permalink_to_line(
17359 &mut self,
17360 _: &CopyPermalinkToLine,
17361 window: &mut Window,
17362 cx: &mut Context<Self>,
17363 ) {
17364 let permalink_task = self.get_permalink_to_line(cx);
17365 let workspace = self.workspace();
17366
17367 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17368 Ok(permalink) => {
17369 cx.update(|_, cx| {
17370 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17371 })
17372 .ok();
17373 }
17374 Err(err) => {
17375 let message = format!("Failed to copy permalink: {err}");
17376
17377 anyhow::Result::<()>::Err(err).log_err();
17378
17379 if let Some(workspace) = workspace {
17380 workspace
17381 .update_in(cx, |workspace, _, cx| {
17382 struct CopyPermalinkToLine;
17383
17384 workspace.show_toast(
17385 Toast::new(
17386 NotificationId::unique::<CopyPermalinkToLine>(),
17387 message,
17388 ),
17389 cx,
17390 )
17391 })
17392 .ok();
17393 }
17394 }
17395 })
17396 .detach();
17397 }
17398
17399 pub fn copy_file_location(
17400 &mut self,
17401 _: &CopyFileLocation,
17402 _: &mut Window,
17403 cx: &mut Context<Self>,
17404 ) {
17405 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17406 if let Some(file) = self.target_file(cx) {
17407 if let Some(path) = file.path().to_str() {
17408 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17409 }
17410 }
17411 }
17412
17413 pub fn open_permalink_to_line(
17414 &mut self,
17415 _: &OpenPermalinkToLine,
17416 window: &mut Window,
17417 cx: &mut Context<Self>,
17418 ) {
17419 let permalink_task = self.get_permalink_to_line(cx);
17420 let workspace = self.workspace();
17421
17422 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17423 Ok(permalink) => {
17424 cx.update(|_, cx| {
17425 cx.open_url(permalink.as_ref());
17426 })
17427 .ok();
17428 }
17429 Err(err) => {
17430 let message = format!("Failed to open permalink: {err}");
17431
17432 anyhow::Result::<()>::Err(err).log_err();
17433
17434 if let Some(workspace) = workspace {
17435 workspace
17436 .update(cx, |workspace, cx| {
17437 struct OpenPermalinkToLine;
17438
17439 workspace.show_toast(
17440 Toast::new(
17441 NotificationId::unique::<OpenPermalinkToLine>(),
17442 message,
17443 ),
17444 cx,
17445 )
17446 })
17447 .ok();
17448 }
17449 }
17450 })
17451 .detach();
17452 }
17453
17454 pub fn insert_uuid_v4(
17455 &mut self,
17456 _: &InsertUuidV4,
17457 window: &mut Window,
17458 cx: &mut Context<Self>,
17459 ) {
17460 self.insert_uuid(UuidVersion::V4, window, cx);
17461 }
17462
17463 pub fn insert_uuid_v7(
17464 &mut self,
17465 _: &InsertUuidV7,
17466 window: &mut Window,
17467 cx: &mut Context<Self>,
17468 ) {
17469 self.insert_uuid(UuidVersion::V7, window, cx);
17470 }
17471
17472 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17473 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17474 self.transact(window, cx, |this, window, cx| {
17475 let edits = this
17476 .selections
17477 .all::<Point>(cx)
17478 .into_iter()
17479 .map(|selection| {
17480 let uuid = match version {
17481 UuidVersion::V4 => uuid::Uuid::new_v4(),
17482 UuidVersion::V7 => uuid::Uuid::now_v7(),
17483 };
17484
17485 (selection.range(), uuid.to_string())
17486 });
17487 this.edit(edits, cx);
17488 this.refresh_inline_completion(true, false, window, cx);
17489 });
17490 }
17491
17492 pub fn open_selections_in_multibuffer(
17493 &mut self,
17494 _: &OpenSelectionsInMultibuffer,
17495 window: &mut Window,
17496 cx: &mut Context<Self>,
17497 ) {
17498 let multibuffer = self.buffer.read(cx);
17499
17500 let Some(buffer) = multibuffer.as_singleton() else {
17501 return;
17502 };
17503
17504 let Some(workspace) = self.workspace() else {
17505 return;
17506 };
17507
17508 let locations = self
17509 .selections
17510 .disjoint_anchors()
17511 .iter()
17512 .map(|range| Location {
17513 buffer: buffer.clone(),
17514 range: range.start.text_anchor..range.end.text_anchor,
17515 })
17516 .collect::<Vec<_>>();
17517
17518 let title = multibuffer.title(cx).to_string();
17519
17520 cx.spawn_in(window, async move |_, cx| {
17521 workspace.update_in(cx, |workspace, window, cx| {
17522 Self::open_locations_in_multibuffer(
17523 workspace,
17524 locations,
17525 format!("Selections for '{title}'"),
17526 false,
17527 MultibufferSelectionMode::All,
17528 window,
17529 cx,
17530 );
17531 })
17532 })
17533 .detach();
17534 }
17535
17536 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17537 /// last highlight added will be used.
17538 ///
17539 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17540 pub fn highlight_rows<T: 'static>(
17541 &mut self,
17542 range: Range<Anchor>,
17543 color: Hsla,
17544 options: RowHighlightOptions,
17545 cx: &mut Context<Self>,
17546 ) {
17547 let snapshot = self.buffer().read(cx).snapshot(cx);
17548 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17549 let ix = row_highlights.binary_search_by(|highlight| {
17550 Ordering::Equal
17551 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17552 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17553 });
17554
17555 if let Err(mut ix) = ix {
17556 let index = post_inc(&mut self.highlight_order);
17557
17558 // If this range intersects with the preceding highlight, then merge it with
17559 // the preceding highlight. Otherwise insert a new highlight.
17560 let mut merged = false;
17561 if ix > 0 {
17562 let prev_highlight = &mut row_highlights[ix - 1];
17563 if prev_highlight
17564 .range
17565 .end
17566 .cmp(&range.start, &snapshot)
17567 .is_ge()
17568 {
17569 ix -= 1;
17570 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17571 prev_highlight.range.end = range.end;
17572 }
17573 merged = true;
17574 prev_highlight.index = index;
17575 prev_highlight.color = color;
17576 prev_highlight.options = options;
17577 }
17578 }
17579
17580 if !merged {
17581 row_highlights.insert(
17582 ix,
17583 RowHighlight {
17584 range: range.clone(),
17585 index,
17586 color,
17587 options,
17588 type_id: TypeId::of::<T>(),
17589 },
17590 );
17591 }
17592
17593 // If any of the following highlights intersect with this one, merge them.
17594 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17595 let highlight = &row_highlights[ix];
17596 if next_highlight
17597 .range
17598 .start
17599 .cmp(&highlight.range.end, &snapshot)
17600 .is_le()
17601 {
17602 if next_highlight
17603 .range
17604 .end
17605 .cmp(&highlight.range.end, &snapshot)
17606 .is_gt()
17607 {
17608 row_highlights[ix].range.end = next_highlight.range.end;
17609 }
17610 row_highlights.remove(ix + 1);
17611 } else {
17612 break;
17613 }
17614 }
17615 }
17616 }
17617
17618 /// Remove any highlighted row ranges of the given type that intersect the
17619 /// given ranges.
17620 pub fn remove_highlighted_rows<T: 'static>(
17621 &mut self,
17622 ranges_to_remove: Vec<Range<Anchor>>,
17623 cx: &mut Context<Self>,
17624 ) {
17625 let snapshot = self.buffer().read(cx).snapshot(cx);
17626 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17627 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17628 row_highlights.retain(|highlight| {
17629 while let Some(range_to_remove) = ranges_to_remove.peek() {
17630 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17631 Ordering::Less | Ordering::Equal => {
17632 ranges_to_remove.next();
17633 }
17634 Ordering::Greater => {
17635 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17636 Ordering::Less | Ordering::Equal => {
17637 return false;
17638 }
17639 Ordering::Greater => break,
17640 }
17641 }
17642 }
17643 }
17644
17645 true
17646 })
17647 }
17648
17649 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17650 pub fn clear_row_highlights<T: 'static>(&mut self) {
17651 self.highlighted_rows.remove(&TypeId::of::<T>());
17652 }
17653
17654 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17655 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17656 self.highlighted_rows
17657 .get(&TypeId::of::<T>())
17658 .map_or(&[] as &[_], |vec| vec.as_slice())
17659 .iter()
17660 .map(|highlight| (highlight.range.clone(), highlight.color))
17661 }
17662
17663 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17664 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17665 /// Allows to ignore certain kinds of highlights.
17666 pub fn highlighted_display_rows(
17667 &self,
17668 window: &mut Window,
17669 cx: &mut App,
17670 ) -> BTreeMap<DisplayRow, LineHighlight> {
17671 let snapshot = self.snapshot(window, cx);
17672 let mut used_highlight_orders = HashMap::default();
17673 self.highlighted_rows
17674 .iter()
17675 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17676 .fold(
17677 BTreeMap::<DisplayRow, LineHighlight>::new(),
17678 |mut unique_rows, highlight| {
17679 let start = highlight.range.start.to_display_point(&snapshot);
17680 let end = highlight.range.end.to_display_point(&snapshot);
17681 let start_row = start.row().0;
17682 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17683 && end.column() == 0
17684 {
17685 end.row().0.saturating_sub(1)
17686 } else {
17687 end.row().0
17688 };
17689 for row in start_row..=end_row {
17690 let used_index =
17691 used_highlight_orders.entry(row).or_insert(highlight.index);
17692 if highlight.index >= *used_index {
17693 *used_index = highlight.index;
17694 unique_rows.insert(
17695 DisplayRow(row),
17696 LineHighlight {
17697 include_gutter: highlight.options.include_gutter,
17698 border: None,
17699 background: highlight.color.into(),
17700 type_id: Some(highlight.type_id),
17701 },
17702 );
17703 }
17704 }
17705 unique_rows
17706 },
17707 )
17708 }
17709
17710 pub fn highlighted_display_row_for_autoscroll(
17711 &self,
17712 snapshot: &DisplaySnapshot,
17713 ) -> Option<DisplayRow> {
17714 self.highlighted_rows
17715 .values()
17716 .flat_map(|highlighted_rows| highlighted_rows.iter())
17717 .filter_map(|highlight| {
17718 if highlight.options.autoscroll {
17719 Some(highlight.range.start.to_display_point(snapshot).row())
17720 } else {
17721 None
17722 }
17723 })
17724 .min()
17725 }
17726
17727 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17728 self.highlight_background::<SearchWithinRange>(
17729 ranges,
17730 |colors| colors.editor_document_highlight_read_background,
17731 cx,
17732 )
17733 }
17734
17735 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17736 self.breadcrumb_header = Some(new_header);
17737 }
17738
17739 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17740 self.clear_background_highlights::<SearchWithinRange>(cx);
17741 }
17742
17743 pub fn highlight_background<T: 'static>(
17744 &mut self,
17745 ranges: &[Range<Anchor>],
17746 color_fetcher: fn(&ThemeColors) -> Hsla,
17747 cx: &mut Context<Self>,
17748 ) {
17749 self.background_highlights
17750 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17751 self.scrollbar_marker_state.dirty = true;
17752 cx.notify();
17753 }
17754
17755 pub fn clear_background_highlights<T: 'static>(
17756 &mut self,
17757 cx: &mut Context<Self>,
17758 ) -> Option<BackgroundHighlight> {
17759 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17760 if !text_highlights.1.is_empty() {
17761 self.scrollbar_marker_state.dirty = true;
17762 cx.notify();
17763 }
17764 Some(text_highlights)
17765 }
17766
17767 pub fn highlight_gutter<T: 'static>(
17768 &mut self,
17769 ranges: &[Range<Anchor>],
17770 color_fetcher: fn(&App) -> Hsla,
17771 cx: &mut Context<Self>,
17772 ) {
17773 self.gutter_highlights
17774 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17775 cx.notify();
17776 }
17777
17778 pub fn clear_gutter_highlights<T: 'static>(
17779 &mut self,
17780 cx: &mut Context<Self>,
17781 ) -> Option<GutterHighlight> {
17782 cx.notify();
17783 self.gutter_highlights.remove(&TypeId::of::<T>())
17784 }
17785
17786 #[cfg(feature = "test-support")]
17787 pub fn all_text_background_highlights(
17788 &self,
17789 window: &mut Window,
17790 cx: &mut Context<Self>,
17791 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17792 let snapshot = self.snapshot(window, cx);
17793 let buffer = &snapshot.buffer_snapshot;
17794 let start = buffer.anchor_before(0);
17795 let end = buffer.anchor_after(buffer.len());
17796 let theme = cx.theme().colors();
17797 self.background_highlights_in_range(start..end, &snapshot, theme)
17798 }
17799
17800 #[cfg(feature = "test-support")]
17801 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17802 let snapshot = self.buffer().read(cx).snapshot(cx);
17803
17804 let highlights = self
17805 .background_highlights
17806 .get(&TypeId::of::<items::BufferSearchHighlights>());
17807
17808 if let Some((_color, ranges)) = highlights {
17809 ranges
17810 .iter()
17811 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17812 .collect_vec()
17813 } else {
17814 vec![]
17815 }
17816 }
17817
17818 fn document_highlights_for_position<'a>(
17819 &'a self,
17820 position: Anchor,
17821 buffer: &'a MultiBufferSnapshot,
17822 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17823 let read_highlights = self
17824 .background_highlights
17825 .get(&TypeId::of::<DocumentHighlightRead>())
17826 .map(|h| &h.1);
17827 let write_highlights = self
17828 .background_highlights
17829 .get(&TypeId::of::<DocumentHighlightWrite>())
17830 .map(|h| &h.1);
17831 let left_position = position.bias_left(buffer);
17832 let right_position = position.bias_right(buffer);
17833 read_highlights
17834 .into_iter()
17835 .chain(write_highlights)
17836 .flat_map(move |ranges| {
17837 let start_ix = match ranges.binary_search_by(|probe| {
17838 let cmp = probe.end.cmp(&left_position, buffer);
17839 if cmp.is_ge() {
17840 Ordering::Greater
17841 } else {
17842 Ordering::Less
17843 }
17844 }) {
17845 Ok(i) | Err(i) => i,
17846 };
17847
17848 ranges[start_ix..]
17849 .iter()
17850 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17851 })
17852 }
17853
17854 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17855 self.background_highlights
17856 .get(&TypeId::of::<T>())
17857 .map_or(false, |(_, highlights)| !highlights.is_empty())
17858 }
17859
17860 pub fn background_highlights_in_range(
17861 &self,
17862 search_range: Range<Anchor>,
17863 display_snapshot: &DisplaySnapshot,
17864 theme: &ThemeColors,
17865 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17866 let mut results = Vec::new();
17867 for (color_fetcher, ranges) in self.background_highlights.values() {
17868 let color = color_fetcher(theme);
17869 let start_ix = match ranges.binary_search_by(|probe| {
17870 let cmp = probe
17871 .end
17872 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17873 if cmp.is_gt() {
17874 Ordering::Greater
17875 } else {
17876 Ordering::Less
17877 }
17878 }) {
17879 Ok(i) | Err(i) => i,
17880 };
17881 for range in &ranges[start_ix..] {
17882 if range
17883 .start
17884 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17885 .is_ge()
17886 {
17887 break;
17888 }
17889
17890 let start = range.start.to_display_point(display_snapshot);
17891 let end = range.end.to_display_point(display_snapshot);
17892 results.push((start..end, color))
17893 }
17894 }
17895 results
17896 }
17897
17898 pub fn background_highlight_row_ranges<T: 'static>(
17899 &self,
17900 search_range: Range<Anchor>,
17901 display_snapshot: &DisplaySnapshot,
17902 count: usize,
17903 ) -> Vec<RangeInclusive<DisplayPoint>> {
17904 let mut results = Vec::new();
17905 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
17906 return vec![];
17907 };
17908
17909 let start_ix = match ranges.binary_search_by(|probe| {
17910 let cmp = probe
17911 .end
17912 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17913 if cmp.is_gt() {
17914 Ordering::Greater
17915 } else {
17916 Ordering::Less
17917 }
17918 }) {
17919 Ok(i) | Err(i) => i,
17920 };
17921 let mut push_region = |start: Option<Point>, end: Option<Point>| {
17922 if let (Some(start_display), Some(end_display)) = (start, end) {
17923 results.push(
17924 start_display.to_display_point(display_snapshot)
17925 ..=end_display.to_display_point(display_snapshot),
17926 );
17927 }
17928 };
17929 let mut start_row: Option<Point> = None;
17930 let mut end_row: Option<Point> = None;
17931 if ranges.len() > count {
17932 return Vec::new();
17933 }
17934 for range in &ranges[start_ix..] {
17935 if range
17936 .start
17937 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17938 .is_ge()
17939 {
17940 break;
17941 }
17942 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
17943 if let Some(current_row) = &end_row {
17944 if end.row == current_row.row {
17945 continue;
17946 }
17947 }
17948 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
17949 if start_row.is_none() {
17950 assert_eq!(end_row, None);
17951 start_row = Some(start);
17952 end_row = Some(end);
17953 continue;
17954 }
17955 if let Some(current_end) = end_row.as_mut() {
17956 if start.row > current_end.row + 1 {
17957 push_region(start_row, end_row);
17958 start_row = Some(start);
17959 end_row = Some(end);
17960 } else {
17961 // Merge two hunks.
17962 *current_end = end;
17963 }
17964 } else {
17965 unreachable!();
17966 }
17967 }
17968 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
17969 push_region(start_row, end_row);
17970 results
17971 }
17972
17973 pub fn gutter_highlights_in_range(
17974 &self,
17975 search_range: Range<Anchor>,
17976 display_snapshot: &DisplaySnapshot,
17977 cx: &App,
17978 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17979 let mut results = Vec::new();
17980 for (color_fetcher, ranges) in self.gutter_highlights.values() {
17981 let color = color_fetcher(cx);
17982 let start_ix = match ranges.binary_search_by(|probe| {
17983 let cmp = probe
17984 .end
17985 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17986 if cmp.is_gt() {
17987 Ordering::Greater
17988 } else {
17989 Ordering::Less
17990 }
17991 }) {
17992 Ok(i) | Err(i) => i,
17993 };
17994 for range in &ranges[start_ix..] {
17995 if range
17996 .start
17997 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17998 .is_ge()
17999 {
18000 break;
18001 }
18002
18003 let start = range.start.to_display_point(display_snapshot);
18004 let end = range.end.to_display_point(display_snapshot);
18005 results.push((start..end, color))
18006 }
18007 }
18008 results
18009 }
18010
18011 /// Get the text ranges corresponding to the redaction query
18012 pub fn redacted_ranges(
18013 &self,
18014 search_range: Range<Anchor>,
18015 display_snapshot: &DisplaySnapshot,
18016 cx: &App,
18017 ) -> Vec<Range<DisplayPoint>> {
18018 display_snapshot
18019 .buffer_snapshot
18020 .redacted_ranges(search_range, |file| {
18021 if let Some(file) = file {
18022 file.is_private()
18023 && EditorSettings::get(
18024 Some(SettingsLocation {
18025 worktree_id: file.worktree_id(cx),
18026 path: file.path().as_ref(),
18027 }),
18028 cx,
18029 )
18030 .redact_private_values
18031 } else {
18032 false
18033 }
18034 })
18035 .map(|range| {
18036 range.start.to_display_point(display_snapshot)
18037 ..range.end.to_display_point(display_snapshot)
18038 })
18039 .collect()
18040 }
18041
18042 pub fn highlight_text<T: 'static>(
18043 &mut self,
18044 ranges: Vec<Range<Anchor>>,
18045 style: HighlightStyle,
18046 cx: &mut Context<Self>,
18047 ) {
18048 self.display_map.update(cx, |map, _| {
18049 map.highlight_text(TypeId::of::<T>(), ranges, style)
18050 });
18051 cx.notify();
18052 }
18053
18054 pub(crate) fn highlight_inlays<T: 'static>(
18055 &mut self,
18056 highlights: Vec<InlayHighlight>,
18057 style: HighlightStyle,
18058 cx: &mut Context<Self>,
18059 ) {
18060 self.display_map.update(cx, |map, _| {
18061 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18062 });
18063 cx.notify();
18064 }
18065
18066 pub fn text_highlights<'a, T: 'static>(
18067 &'a self,
18068 cx: &'a App,
18069 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18070 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18071 }
18072
18073 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18074 let cleared = self
18075 .display_map
18076 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18077 if cleared {
18078 cx.notify();
18079 }
18080 }
18081
18082 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18083 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18084 && self.focus_handle.is_focused(window)
18085 }
18086
18087 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18088 self.show_cursor_when_unfocused = is_enabled;
18089 cx.notify();
18090 }
18091
18092 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18093 cx.notify();
18094 }
18095
18096 fn on_debug_session_event(
18097 &mut self,
18098 _session: Entity<Session>,
18099 event: &SessionEvent,
18100 cx: &mut Context<Self>,
18101 ) {
18102 match event {
18103 SessionEvent::InvalidateInlineValue => {
18104 self.refresh_inline_values(cx);
18105 }
18106 _ => {}
18107 }
18108 }
18109
18110 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18111 let Some(project) = self.project.clone() else {
18112 return;
18113 };
18114
18115 if !self.inline_value_cache.enabled {
18116 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18117 self.splice_inlays(&inlays, Vec::new(), cx);
18118 return;
18119 }
18120
18121 let current_execution_position = self
18122 .highlighted_rows
18123 .get(&TypeId::of::<ActiveDebugLine>())
18124 .and_then(|lines| lines.last().map(|line| line.range.start));
18125
18126 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18127 let inline_values = editor
18128 .update(cx, |editor, cx| {
18129 let Some(current_execution_position) = current_execution_position else {
18130 return Some(Task::ready(Ok(Vec::new())));
18131 };
18132
18133 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18134 let snapshot = buffer.snapshot(cx);
18135
18136 let excerpt = snapshot.excerpt_containing(
18137 current_execution_position..current_execution_position,
18138 )?;
18139
18140 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18141 })?;
18142
18143 let range =
18144 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18145
18146 project.inline_values(buffer, range, cx)
18147 })
18148 .ok()
18149 .flatten()?
18150 .await
18151 .context("refreshing debugger inlays")
18152 .log_err()?;
18153
18154 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18155
18156 for (buffer_id, inline_value) in inline_values
18157 .into_iter()
18158 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18159 {
18160 buffer_inline_values
18161 .entry(buffer_id)
18162 .or_default()
18163 .push(inline_value);
18164 }
18165
18166 editor
18167 .update(cx, |editor, cx| {
18168 let snapshot = editor.buffer.read(cx).snapshot(cx);
18169 let mut new_inlays = Vec::default();
18170
18171 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18172 let buffer_id = buffer_snapshot.remote_id();
18173 buffer_inline_values
18174 .get(&buffer_id)
18175 .into_iter()
18176 .flatten()
18177 .for_each(|hint| {
18178 let inlay = Inlay::debugger_hint(
18179 post_inc(&mut editor.next_inlay_id),
18180 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18181 hint.text(),
18182 );
18183
18184 new_inlays.push(inlay);
18185 });
18186 }
18187
18188 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18189 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18190
18191 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18192 })
18193 .ok()?;
18194 Some(())
18195 });
18196 }
18197
18198 fn on_buffer_event(
18199 &mut self,
18200 multibuffer: &Entity<MultiBuffer>,
18201 event: &multi_buffer::Event,
18202 window: &mut Window,
18203 cx: &mut Context<Self>,
18204 ) {
18205 match event {
18206 multi_buffer::Event::Edited {
18207 singleton_buffer_edited,
18208 edited_buffer: buffer_edited,
18209 } => {
18210 self.scrollbar_marker_state.dirty = true;
18211 self.active_indent_guides_state.dirty = true;
18212 self.refresh_active_diagnostics(cx);
18213 self.refresh_code_actions(window, cx);
18214 self.refresh_selected_text_highlights(true, window, cx);
18215 refresh_matching_bracket_highlights(self, window, cx);
18216 if self.has_active_inline_completion() {
18217 self.update_visible_inline_completion(window, cx);
18218 }
18219 if let Some(buffer) = buffer_edited {
18220 let buffer_id = buffer.read(cx).remote_id();
18221 if !self.registered_buffers.contains_key(&buffer_id) {
18222 if let Some(project) = self.project.as_ref() {
18223 project.update(cx, |project, cx| {
18224 self.registered_buffers.insert(
18225 buffer_id,
18226 project.register_buffer_with_language_servers(&buffer, cx),
18227 );
18228 })
18229 }
18230 }
18231 }
18232 cx.emit(EditorEvent::BufferEdited);
18233 cx.emit(SearchEvent::MatchesInvalidated);
18234 if *singleton_buffer_edited {
18235 if let Some(project) = &self.project {
18236 #[allow(clippy::mutable_key_type)]
18237 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18238 multibuffer
18239 .all_buffers()
18240 .into_iter()
18241 .filter_map(|buffer| {
18242 buffer.update(cx, |buffer, cx| {
18243 let language = buffer.language()?;
18244 let should_discard = project.update(cx, |project, cx| {
18245 project.is_local()
18246 && !project.has_language_servers_for(buffer, cx)
18247 });
18248 should_discard.not().then_some(language.clone())
18249 })
18250 })
18251 .collect::<HashSet<_>>()
18252 });
18253 if !languages_affected.is_empty() {
18254 self.refresh_inlay_hints(
18255 InlayHintRefreshReason::BufferEdited(languages_affected),
18256 cx,
18257 );
18258 }
18259 }
18260 }
18261
18262 let Some(project) = &self.project else { return };
18263 let (telemetry, is_via_ssh) = {
18264 let project = project.read(cx);
18265 let telemetry = project.client().telemetry().clone();
18266 let is_via_ssh = project.is_via_ssh();
18267 (telemetry, is_via_ssh)
18268 };
18269 refresh_linked_ranges(self, window, cx);
18270 telemetry.log_edit_event("editor", is_via_ssh);
18271 }
18272 multi_buffer::Event::ExcerptsAdded {
18273 buffer,
18274 predecessor,
18275 excerpts,
18276 } => {
18277 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18278 let buffer_id = buffer.read(cx).remote_id();
18279 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18280 if let Some(project) = &self.project {
18281 update_uncommitted_diff_for_buffer(
18282 cx.entity(),
18283 project,
18284 [buffer.clone()],
18285 self.buffer.clone(),
18286 cx,
18287 )
18288 .detach();
18289 }
18290 }
18291 cx.emit(EditorEvent::ExcerptsAdded {
18292 buffer: buffer.clone(),
18293 predecessor: *predecessor,
18294 excerpts: excerpts.clone(),
18295 });
18296 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18297 }
18298 multi_buffer::Event::ExcerptsRemoved {
18299 ids,
18300 removed_buffer_ids,
18301 } => {
18302 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18303 let buffer = self.buffer.read(cx);
18304 self.registered_buffers
18305 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18306 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18307 cx.emit(EditorEvent::ExcerptsRemoved {
18308 ids: ids.clone(),
18309 removed_buffer_ids: removed_buffer_ids.clone(),
18310 })
18311 }
18312 multi_buffer::Event::ExcerptsEdited {
18313 excerpt_ids,
18314 buffer_ids,
18315 } => {
18316 self.display_map.update(cx, |map, cx| {
18317 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18318 });
18319 cx.emit(EditorEvent::ExcerptsEdited {
18320 ids: excerpt_ids.clone(),
18321 })
18322 }
18323 multi_buffer::Event::ExcerptsExpanded { ids } => {
18324 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18325 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18326 }
18327 multi_buffer::Event::Reparsed(buffer_id) => {
18328 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18329 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18330
18331 cx.emit(EditorEvent::Reparsed(*buffer_id));
18332 }
18333 multi_buffer::Event::DiffHunksToggled => {
18334 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18335 }
18336 multi_buffer::Event::LanguageChanged(buffer_id) => {
18337 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18338 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18339 cx.emit(EditorEvent::Reparsed(*buffer_id));
18340 cx.notify();
18341 }
18342 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18343 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18344 multi_buffer::Event::FileHandleChanged
18345 | multi_buffer::Event::Reloaded
18346 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18347 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18348 multi_buffer::Event::DiagnosticsUpdated => {
18349 self.refresh_active_diagnostics(cx);
18350 self.refresh_inline_diagnostics(true, window, cx);
18351 self.scrollbar_marker_state.dirty = true;
18352 cx.notify();
18353 }
18354 _ => {}
18355 };
18356 }
18357
18358 pub fn start_temporary_diff_override(&mut self) {
18359 self.load_diff_task.take();
18360 self.temporary_diff_override = true;
18361 }
18362
18363 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18364 self.temporary_diff_override = false;
18365 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18366 self.buffer.update(cx, |buffer, cx| {
18367 buffer.set_all_diff_hunks_collapsed(cx);
18368 });
18369
18370 if let Some(project) = self.project.clone() {
18371 self.load_diff_task = Some(
18372 update_uncommitted_diff_for_buffer(
18373 cx.entity(),
18374 &project,
18375 self.buffer.read(cx).all_buffers(),
18376 self.buffer.clone(),
18377 cx,
18378 )
18379 .shared(),
18380 );
18381 }
18382 }
18383
18384 fn on_display_map_changed(
18385 &mut self,
18386 _: Entity<DisplayMap>,
18387 _: &mut Window,
18388 cx: &mut Context<Self>,
18389 ) {
18390 cx.notify();
18391 }
18392
18393 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18394 let new_severity = if self.diagnostics_enabled() {
18395 EditorSettings::get_global(cx)
18396 .diagnostics_max_severity
18397 .unwrap_or(DiagnosticSeverity::Hint)
18398 } else {
18399 DiagnosticSeverity::Off
18400 };
18401 self.set_max_diagnostics_severity(new_severity, cx);
18402 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18403 self.update_edit_prediction_settings(cx);
18404 self.refresh_inline_completion(true, false, window, cx);
18405 self.refresh_inlay_hints(
18406 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18407 self.selections.newest_anchor().head(),
18408 &self.buffer.read(cx).snapshot(cx),
18409 cx,
18410 )),
18411 cx,
18412 );
18413
18414 let old_cursor_shape = self.cursor_shape;
18415
18416 {
18417 let editor_settings = EditorSettings::get_global(cx);
18418 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18419 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18420 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18421 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18422 }
18423
18424 if old_cursor_shape != self.cursor_shape {
18425 cx.emit(EditorEvent::CursorShapeChanged);
18426 }
18427
18428 let project_settings = ProjectSettings::get_global(cx);
18429 self.serialize_dirty_buffers =
18430 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18431
18432 if self.mode.is_full() {
18433 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18434 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18435 if self.show_inline_diagnostics != show_inline_diagnostics {
18436 self.show_inline_diagnostics = show_inline_diagnostics;
18437 self.refresh_inline_diagnostics(false, window, cx);
18438 }
18439
18440 if self.git_blame_inline_enabled != inline_blame_enabled {
18441 self.toggle_git_blame_inline_internal(false, window, cx);
18442 }
18443
18444 let minimap_settings = EditorSettings::get_global(cx).minimap;
18445 if self.minimap_visibility.visible() != minimap_settings.minimap_enabled() {
18446 self.set_minimap_visibility(
18447 self.minimap_visibility.toggle_visibility(),
18448 window,
18449 cx,
18450 );
18451 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18452 minimap_entity.update(cx, |minimap_editor, cx| {
18453 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18454 })
18455 }
18456 }
18457
18458 cx.notify();
18459 }
18460
18461 pub fn set_searchable(&mut self, searchable: bool) {
18462 self.searchable = searchable;
18463 }
18464
18465 pub fn searchable(&self) -> bool {
18466 self.searchable
18467 }
18468
18469 fn open_proposed_changes_editor(
18470 &mut self,
18471 _: &OpenProposedChangesEditor,
18472 window: &mut Window,
18473 cx: &mut Context<Self>,
18474 ) {
18475 let Some(workspace) = self.workspace() else {
18476 cx.propagate();
18477 return;
18478 };
18479
18480 let selections = self.selections.all::<usize>(cx);
18481 let multi_buffer = self.buffer.read(cx);
18482 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18483 let mut new_selections_by_buffer = HashMap::default();
18484 for selection in selections {
18485 for (buffer, range, _) in
18486 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18487 {
18488 let mut range = range.to_point(buffer);
18489 range.start.column = 0;
18490 range.end.column = buffer.line_len(range.end.row);
18491 new_selections_by_buffer
18492 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18493 .or_insert(Vec::new())
18494 .push(range)
18495 }
18496 }
18497
18498 let proposed_changes_buffers = new_selections_by_buffer
18499 .into_iter()
18500 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18501 .collect::<Vec<_>>();
18502 let proposed_changes_editor = cx.new(|cx| {
18503 ProposedChangesEditor::new(
18504 "Proposed changes",
18505 proposed_changes_buffers,
18506 self.project.clone(),
18507 window,
18508 cx,
18509 )
18510 });
18511
18512 window.defer(cx, move |window, cx| {
18513 workspace.update(cx, |workspace, cx| {
18514 workspace.active_pane().update(cx, |pane, cx| {
18515 pane.add_item(
18516 Box::new(proposed_changes_editor),
18517 true,
18518 true,
18519 None,
18520 window,
18521 cx,
18522 );
18523 });
18524 });
18525 });
18526 }
18527
18528 pub fn open_excerpts_in_split(
18529 &mut self,
18530 _: &OpenExcerptsSplit,
18531 window: &mut Window,
18532 cx: &mut Context<Self>,
18533 ) {
18534 self.open_excerpts_common(None, true, window, cx)
18535 }
18536
18537 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18538 self.open_excerpts_common(None, false, window, cx)
18539 }
18540
18541 fn open_excerpts_common(
18542 &mut self,
18543 jump_data: Option<JumpData>,
18544 split: bool,
18545 window: &mut Window,
18546 cx: &mut Context<Self>,
18547 ) {
18548 let Some(workspace) = self.workspace() else {
18549 cx.propagate();
18550 return;
18551 };
18552
18553 if self.buffer.read(cx).is_singleton() {
18554 cx.propagate();
18555 return;
18556 }
18557
18558 let mut new_selections_by_buffer = HashMap::default();
18559 match &jump_data {
18560 Some(JumpData::MultiBufferPoint {
18561 excerpt_id,
18562 position,
18563 anchor,
18564 line_offset_from_top,
18565 }) => {
18566 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18567 if let Some(buffer) = multi_buffer_snapshot
18568 .buffer_id_for_excerpt(*excerpt_id)
18569 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18570 {
18571 let buffer_snapshot = buffer.read(cx).snapshot();
18572 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18573 language::ToPoint::to_point(anchor, &buffer_snapshot)
18574 } else {
18575 buffer_snapshot.clip_point(*position, Bias::Left)
18576 };
18577 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18578 new_selections_by_buffer.insert(
18579 buffer,
18580 (
18581 vec![jump_to_offset..jump_to_offset],
18582 Some(*line_offset_from_top),
18583 ),
18584 );
18585 }
18586 }
18587 Some(JumpData::MultiBufferRow {
18588 row,
18589 line_offset_from_top,
18590 }) => {
18591 let point = MultiBufferPoint::new(row.0, 0);
18592 if let Some((buffer, buffer_point, _)) =
18593 self.buffer.read(cx).point_to_buffer_point(point, cx)
18594 {
18595 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18596 new_selections_by_buffer
18597 .entry(buffer)
18598 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18599 .0
18600 .push(buffer_offset..buffer_offset)
18601 }
18602 }
18603 None => {
18604 let selections = self.selections.all::<usize>(cx);
18605 let multi_buffer = self.buffer.read(cx);
18606 for selection in selections {
18607 for (snapshot, range, _, anchor) in multi_buffer
18608 .snapshot(cx)
18609 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18610 {
18611 if let Some(anchor) = anchor {
18612 // selection is in a deleted hunk
18613 let Some(buffer_id) = anchor.buffer_id else {
18614 continue;
18615 };
18616 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18617 continue;
18618 };
18619 let offset = text::ToOffset::to_offset(
18620 &anchor.text_anchor,
18621 &buffer_handle.read(cx).snapshot(),
18622 );
18623 let range = offset..offset;
18624 new_selections_by_buffer
18625 .entry(buffer_handle)
18626 .or_insert((Vec::new(), None))
18627 .0
18628 .push(range)
18629 } else {
18630 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18631 else {
18632 continue;
18633 };
18634 new_selections_by_buffer
18635 .entry(buffer_handle)
18636 .or_insert((Vec::new(), None))
18637 .0
18638 .push(range)
18639 }
18640 }
18641 }
18642 }
18643 }
18644
18645 new_selections_by_buffer
18646 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18647
18648 if new_selections_by_buffer.is_empty() {
18649 return;
18650 }
18651
18652 // We defer the pane interaction because we ourselves are a workspace item
18653 // and activating a new item causes the pane to call a method on us reentrantly,
18654 // which panics if we're on the stack.
18655 window.defer(cx, move |window, cx| {
18656 workspace.update(cx, |workspace, cx| {
18657 let pane = if split {
18658 workspace.adjacent_pane(window, cx)
18659 } else {
18660 workspace.active_pane().clone()
18661 };
18662
18663 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18664 let editor = buffer
18665 .read(cx)
18666 .file()
18667 .is_none()
18668 .then(|| {
18669 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18670 // so `workspace.open_project_item` will never find them, always opening a new editor.
18671 // Instead, we try to activate the existing editor in the pane first.
18672 let (editor, pane_item_index) =
18673 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18674 let editor = item.downcast::<Editor>()?;
18675 let singleton_buffer =
18676 editor.read(cx).buffer().read(cx).as_singleton()?;
18677 if singleton_buffer == buffer {
18678 Some((editor, i))
18679 } else {
18680 None
18681 }
18682 })?;
18683 pane.update(cx, |pane, cx| {
18684 pane.activate_item(pane_item_index, true, true, window, cx)
18685 });
18686 Some(editor)
18687 })
18688 .flatten()
18689 .unwrap_or_else(|| {
18690 workspace.open_project_item::<Self>(
18691 pane.clone(),
18692 buffer,
18693 true,
18694 true,
18695 window,
18696 cx,
18697 )
18698 });
18699
18700 editor.update(cx, |editor, cx| {
18701 let autoscroll = match scroll_offset {
18702 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18703 None => Autoscroll::newest(),
18704 };
18705 let nav_history = editor.nav_history.take();
18706 editor.change_selections(Some(autoscroll), window, cx, |s| {
18707 s.select_ranges(ranges);
18708 });
18709 editor.nav_history = nav_history;
18710 });
18711 }
18712 })
18713 });
18714 }
18715
18716 // For now, don't allow opening excerpts in buffers that aren't backed by
18717 // regular project files.
18718 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18719 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18720 }
18721
18722 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18723 let snapshot = self.buffer.read(cx).read(cx);
18724 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18725 Some(
18726 ranges
18727 .iter()
18728 .map(move |range| {
18729 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18730 })
18731 .collect(),
18732 )
18733 }
18734
18735 fn selection_replacement_ranges(
18736 &self,
18737 range: Range<OffsetUtf16>,
18738 cx: &mut App,
18739 ) -> Vec<Range<OffsetUtf16>> {
18740 let selections = self.selections.all::<OffsetUtf16>(cx);
18741 let newest_selection = selections
18742 .iter()
18743 .max_by_key(|selection| selection.id)
18744 .unwrap();
18745 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
18746 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
18747 let snapshot = self.buffer.read(cx).read(cx);
18748 selections
18749 .into_iter()
18750 .map(|mut selection| {
18751 selection.start.0 =
18752 (selection.start.0 as isize).saturating_add(start_delta) as usize;
18753 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
18754 snapshot.clip_offset_utf16(selection.start, Bias::Left)
18755 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
18756 })
18757 .collect()
18758 }
18759
18760 fn report_editor_event(
18761 &self,
18762 event_type: &'static str,
18763 file_extension: Option<String>,
18764 cx: &App,
18765 ) {
18766 if cfg!(any(test, feature = "test-support")) {
18767 return;
18768 }
18769
18770 let Some(project) = &self.project else { return };
18771
18772 // If None, we are in a file without an extension
18773 let file = self
18774 .buffer
18775 .read(cx)
18776 .as_singleton()
18777 .and_then(|b| b.read(cx).file());
18778 let file_extension = file_extension.or(file
18779 .as_ref()
18780 .and_then(|file| Path::new(file.file_name(cx)).extension())
18781 .and_then(|e| e.to_str())
18782 .map(|a| a.to_string()));
18783
18784 let vim_mode = vim_enabled(cx);
18785
18786 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
18787 let copilot_enabled = edit_predictions_provider
18788 == language::language_settings::EditPredictionProvider::Copilot;
18789 let copilot_enabled_for_language = self
18790 .buffer
18791 .read(cx)
18792 .language_settings(cx)
18793 .show_edit_predictions;
18794
18795 let project = project.read(cx);
18796 telemetry::event!(
18797 event_type,
18798 file_extension,
18799 vim_mode,
18800 copilot_enabled,
18801 copilot_enabled_for_language,
18802 edit_predictions_provider,
18803 is_via_ssh = project.is_via_ssh(),
18804 );
18805 }
18806
18807 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
18808 /// with each line being an array of {text, highlight} objects.
18809 fn copy_highlight_json(
18810 &mut self,
18811 _: &CopyHighlightJson,
18812 window: &mut Window,
18813 cx: &mut Context<Self>,
18814 ) {
18815 #[derive(Serialize)]
18816 struct Chunk<'a> {
18817 text: String,
18818 highlight: Option<&'a str>,
18819 }
18820
18821 let snapshot = self.buffer.read(cx).snapshot(cx);
18822 let range = self
18823 .selected_text_range(false, window, cx)
18824 .and_then(|selection| {
18825 if selection.range.is_empty() {
18826 None
18827 } else {
18828 Some(selection.range)
18829 }
18830 })
18831 .unwrap_or_else(|| 0..snapshot.len());
18832
18833 let chunks = snapshot.chunks(range, true);
18834 let mut lines = Vec::new();
18835 let mut line: VecDeque<Chunk> = VecDeque::new();
18836
18837 let Some(style) = self.style.as_ref() else {
18838 return;
18839 };
18840
18841 for chunk in chunks {
18842 let highlight = chunk
18843 .syntax_highlight_id
18844 .and_then(|id| id.name(&style.syntax));
18845 let mut chunk_lines = chunk.text.split('\n').peekable();
18846 while let Some(text) = chunk_lines.next() {
18847 let mut merged_with_last_token = false;
18848 if let Some(last_token) = line.back_mut() {
18849 if last_token.highlight == highlight {
18850 last_token.text.push_str(text);
18851 merged_with_last_token = true;
18852 }
18853 }
18854
18855 if !merged_with_last_token {
18856 line.push_back(Chunk {
18857 text: text.into(),
18858 highlight,
18859 });
18860 }
18861
18862 if chunk_lines.peek().is_some() {
18863 if line.len() > 1 && line.front().unwrap().text.is_empty() {
18864 line.pop_front();
18865 }
18866 if line.len() > 1 && line.back().unwrap().text.is_empty() {
18867 line.pop_back();
18868 }
18869
18870 lines.push(mem::take(&mut line));
18871 }
18872 }
18873 }
18874
18875 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
18876 return;
18877 };
18878 cx.write_to_clipboard(ClipboardItem::new_string(lines));
18879 }
18880
18881 pub fn open_context_menu(
18882 &mut self,
18883 _: &OpenContextMenu,
18884 window: &mut Window,
18885 cx: &mut Context<Self>,
18886 ) {
18887 self.request_autoscroll(Autoscroll::newest(), cx);
18888 let position = self.selections.newest_display(cx).start;
18889 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
18890 }
18891
18892 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
18893 &self.inlay_hint_cache
18894 }
18895
18896 pub fn replay_insert_event(
18897 &mut self,
18898 text: &str,
18899 relative_utf16_range: Option<Range<isize>>,
18900 window: &mut Window,
18901 cx: &mut Context<Self>,
18902 ) {
18903 if !self.input_enabled {
18904 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18905 return;
18906 }
18907 if let Some(relative_utf16_range) = relative_utf16_range {
18908 let selections = self.selections.all::<OffsetUtf16>(cx);
18909 self.change_selections(None, window, cx, |s| {
18910 let new_ranges = selections.into_iter().map(|range| {
18911 let start = OffsetUtf16(
18912 range
18913 .head()
18914 .0
18915 .saturating_add_signed(relative_utf16_range.start),
18916 );
18917 let end = OffsetUtf16(
18918 range
18919 .head()
18920 .0
18921 .saturating_add_signed(relative_utf16_range.end),
18922 );
18923 start..end
18924 });
18925 s.select_ranges(new_ranges);
18926 });
18927 }
18928
18929 self.handle_input(text, window, cx);
18930 }
18931
18932 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
18933 let Some(provider) = self.semantics_provider.as_ref() else {
18934 return false;
18935 };
18936
18937 let mut supports = false;
18938 self.buffer().update(cx, |this, cx| {
18939 this.for_each_buffer(|buffer| {
18940 supports |= provider.supports_inlay_hints(buffer, cx);
18941 });
18942 });
18943
18944 supports
18945 }
18946
18947 pub fn is_focused(&self, window: &Window) -> bool {
18948 self.focus_handle.is_focused(window)
18949 }
18950
18951 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18952 cx.emit(EditorEvent::Focused);
18953
18954 if let Some(descendant) = self
18955 .last_focused_descendant
18956 .take()
18957 .and_then(|descendant| descendant.upgrade())
18958 {
18959 window.focus(&descendant);
18960 } else {
18961 if let Some(blame) = self.blame.as_ref() {
18962 blame.update(cx, GitBlame::focus)
18963 }
18964
18965 self.blink_manager.update(cx, BlinkManager::enable);
18966 self.show_cursor_names(window, cx);
18967 self.buffer.update(cx, |buffer, cx| {
18968 buffer.finalize_last_transaction(cx);
18969 if self.leader_id.is_none() {
18970 buffer.set_active_selections(
18971 &self.selections.disjoint_anchors(),
18972 self.selections.line_mode,
18973 self.cursor_shape,
18974 cx,
18975 );
18976 }
18977 });
18978 }
18979 }
18980
18981 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18982 cx.emit(EditorEvent::FocusedIn)
18983 }
18984
18985 fn handle_focus_out(
18986 &mut self,
18987 event: FocusOutEvent,
18988 _window: &mut Window,
18989 cx: &mut Context<Self>,
18990 ) {
18991 if event.blurred != self.focus_handle {
18992 self.last_focused_descendant = Some(event.blurred);
18993 }
18994 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
18995 }
18996
18997 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18998 self.blink_manager.update(cx, BlinkManager::disable);
18999 self.buffer
19000 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19001
19002 if let Some(blame) = self.blame.as_ref() {
19003 blame.update(cx, GitBlame::blur)
19004 }
19005 if !self.hover_state.focused(window, cx) {
19006 hide_hover(self, cx);
19007 }
19008 if !self
19009 .context_menu
19010 .borrow()
19011 .as_ref()
19012 .is_some_and(|context_menu| context_menu.focused(window, cx))
19013 {
19014 self.hide_context_menu(window, cx);
19015 }
19016 self.discard_inline_completion(false, cx);
19017 cx.emit(EditorEvent::Blurred);
19018 cx.notify();
19019 }
19020
19021 pub fn register_action<A: Action>(
19022 &mut self,
19023 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19024 ) -> Subscription {
19025 let id = self.next_editor_action_id.post_inc();
19026 let listener = Arc::new(listener);
19027 self.editor_actions.borrow_mut().insert(
19028 id,
19029 Box::new(move |window, _| {
19030 let listener = listener.clone();
19031 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19032 let action = action.downcast_ref().unwrap();
19033 if phase == DispatchPhase::Bubble {
19034 listener(action, window, cx)
19035 }
19036 })
19037 }),
19038 );
19039
19040 let editor_actions = self.editor_actions.clone();
19041 Subscription::new(move || {
19042 editor_actions.borrow_mut().remove(&id);
19043 })
19044 }
19045
19046 pub fn file_header_size(&self) -> u32 {
19047 FILE_HEADER_HEIGHT
19048 }
19049
19050 pub fn restore(
19051 &mut self,
19052 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19053 window: &mut Window,
19054 cx: &mut Context<Self>,
19055 ) {
19056 let workspace = self.workspace();
19057 let project = self.project.as_ref();
19058 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19059 let mut tasks = Vec::new();
19060 for (buffer_id, changes) in revert_changes {
19061 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19062 buffer.update(cx, |buffer, cx| {
19063 buffer.edit(
19064 changes
19065 .into_iter()
19066 .map(|(range, text)| (range, text.to_string())),
19067 None,
19068 cx,
19069 );
19070 });
19071
19072 if let Some(project) =
19073 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19074 {
19075 project.update(cx, |project, cx| {
19076 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19077 })
19078 }
19079 }
19080 }
19081 tasks
19082 });
19083 cx.spawn_in(window, async move |_, cx| {
19084 for (buffer, task) in save_tasks {
19085 let result = task.await;
19086 if result.is_err() {
19087 let Some(path) = buffer
19088 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19089 .ok()
19090 else {
19091 continue;
19092 };
19093 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19094 let Some(task) = cx
19095 .update_window_entity(&workspace, |workspace, window, cx| {
19096 workspace
19097 .open_path_preview(path, None, false, false, false, window, cx)
19098 })
19099 .ok()
19100 else {
19101 continue;
19102 };
19103 task.await.log_err();
19104 }
19105 }
19106 }
19107 })
19108 .detach();
19109 self.change_selections(None, window, cx, |selections| selections.refresh());
19110 }
19111
19112 pub fn to_pixel_point(
19113 &self,
19114 source: multi_buffer::Anchor,
19115 editor_snapshot: &EditorSnapshot,
19116 window: &mut Window,
19117 ) -> Option<gpui::Point<Pixels>> {
19118 let source_point = source.to_display_point(editor_snapshot);
19119 self.display_to_pixel_point(source_point, editor_snapshot, window)
19120 }
19121
19122 pub fn display_to_pixel_point(
19123 &self,
19124 source: DisplayPoint,
19125 editor_snapshot: &EditorSnapshot,
19126 window: &mut Window,
19127 ) -> Option<gpui::Point<Pixels>> {
19128 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19129 let text_layout_details = self.text_layout_details(window);
19130 let scroll_top = text_layout_details
19131 .scroll_anchor
19132 .scroll_position(editor_snapshot)
19133 .y;
19134
19135 if source.row().as_f32() < scroll_top.floor() {
19136 return None;
19137 }
19138 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19139 let source_y = line_height * (source.row().as_f32() - scroll_top);
19140 Some(gpui::Point::new(source_x, source_y))
19141 }
19142
19143 pub fn has_visible_completions_menu(&self) -> bool {
19144 !self.edit_prediction_preview_is_active()
19145 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19146 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19147 })
19148 }
19149
19150 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19151 if self.mode.is_minimap() {
19152 return;
19153 }
19154 self.addons
19155 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19156 }
19157
19158 pub fn unregister_addon<T: Addon>(&mut self) {
19159 self.addons.remove(&std::any::TypeId::of::<T>());
19160 }
19161
19162 pub fn addon<T: Addon>(&self) -> Option<&T> {
19163 let type_id = std::any::TypeId::of::<T>();
19164 self.addons
19165 .get(&type_id)
19166 .and_then(|item| item.to_any().downcast_ref::<T>())
19167 }
19168
19169 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19170 let type_id = std::any::TypeId::of::<T>();
19171 self.addons
19172 .get_mut(&type_id)
19173 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19174 }
19175
19176 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19177 let text_layout_details = self.text_layout_details(window);
19178 let style = &text_layout_details.editor_style;
19179 let font_id = window.text_system().resolve_font(&style.text.font());
19180 let font_size = style.text.font_size.to_pixels(window.rem_size());
19181 let line_height = style.text.line_height_in_pixels(window.rem_size());
19182 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19183
19184 gpui::Size::new(em_width, line_height)
19185 }
19186
19187 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
19188 self.load_diff_task.clone()
19189 }
19190
19191 fn read_metadata_from_db(
19192 &mut self,
19193 item_id: u64,
19194 workspace_id: WorkspaceId,
19195 window: &mut Window,
19196 cx: &mut Context<Editor>,
19197 ) {
19198 if self.is_singleton(cx)
19199 && !self.mode.is_minimap()
19200 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
19201 {
19202 let buffer_snapshot = OnceCell::new();
19203
19204 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
19205 if !folds.is_empty() {
19206 let snapshot =
19207 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19208 self.fold_ranges(
19209 folds
19210 .into_iter()
19211 .map(|(start, end)| {
19212 snapshot.clip_offset(start, Bias::Left)
19213 ..snapshot.clip_offset(end, Bias::Right)
19214 })
19215 .collect(),
19216 false,
19217 window,
19218 cx,
19219 );
19220 }
19221 }
19222
19223 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
19224 if !selections.is_empty() {
19225 let snapshot =
19226 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19227 self.change_selections(None, window, cx, |s| {
19228 s.select_ranges(selections.into_iter().map(|(start, end)| {
19229 snapshot.clip_offset(start, Bias::Left)
19230 ..snapshot.clip_offset(end, Bias::Right)
19231 }));
19232 });
19233 }
19234 };
19235 }
19236
19237 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
19238 }
19239}
19240
19241fn vim_enabled(cx: &App) -> bool {
19242 cx.global::<SettingsStore>()
19243 .raw_user_settings()
19244 .get("vim_mode")
19245 == Some(&serde_json::Value::Bool(true))
19246}
19247
19248// Consider user intent and default settings
19249fn choose_completion_range(
19250 completion: &Completion,
19251 intent: CompletionIntent,
19252 buffer: &Entity<Buffer>,
19253 cx: &mut Context<Editor>,
19254) -> Range<usize> {
19255 fn should_replace(
19256 completion: &Completion,
19257 insert_range: &Range<text::Anchor>,
19258 intent: CompletionIntent,
19259 completion_mode_setting: LspInsertMode,
19260 buffer: &Buffer,
19261 ) -> bool {
19262 // specific actions take precedence over settings
19263 match intent {
19264 CompletionIntent::CompleteWithInsert => return false,
19265 CompletionIntent::CompleteWithReplace => return true,
19266 CompletionIntent::Complete | CompletionIntent::Compose => {}
19267 }
19268
19269 match completion_mode_setting {
19270 LspInsertMode::Insert => false,
19271 LspInsertMode::Replace => true,
19272 LspInsertMode::ReplaceSubsequence => {
19273 let mut text_to_replace = buffer.chars_for_range(
19274 buffer.anchor_before(completion.replace_range.start)
19275 ..buffer.anchor_after(completion.replace_range.end),
19276 );
19277 let mut completion_text = completion.new_text.chars();
19278
19279 // is `text_to_replace` a subsequence of `completion_text`
19280 text_to_replace
19281 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
19282 }
19283 LspInsertMode::ReplaceSuffix => {
19284 let range_after_cursor = insert_range.end..completion.replace_range.end;
19285
19286 let text_after_cursor = buffer
19287 .text_for_range(
19288 buffer.anchor_before(range_after_cursor.start)
19289 ..buffer.anchor_after(range_after_cursor.end),
19290 )
19291 .collect::<String>();
19292 completion.new_text.ends_with(&text_after_cursor)
19293 }
19294 }
19295 }
19296
19297 let buffer = buffer.read(cx);
19298
19299 if let CompletionSource::Lsp {
19300 insert_range: Some(insert_range),
19301 ..
19302 } = &completion.source
19303 {
19304 let completion_mode_setting =
19305 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
19306 .completions
19307 .lsp_insert_mode;
19308
19309 if !should_replace(
19310 completion,
19311 &insert_range,
19312 intent,
19313 completion_mode_setting,
19314 buffer,
19315 ) {
19316 return insert_range.to_offset(buffer);
19317 }
19318 }
19319
19320 completion.replace_range.to_offset(buffer)
19321}
19322
19323fn insert_extra_newline_brackets(
19324 buffer: &MultiBufferSnapshot,
19325 range: Range<usize>,
19326 language: &language::LanguageScope,
19327) -> bool {
19328 let leading_whitespace_len = buffer
19329 .reversed_chars_at(range.start)
19330 .take_while(|c| c.is_whitespace() && *c != '\n')
19331 .map(|c| c.len_utf8())
19332 .sum::<usize>();
19333 let trailing_whitespace_len = buffer
19334 .chars_at(range.end)
19335 .take_while(|c| c.is_whitespace() && *c != '\n')
19336 .map(|c| c.len_utf8())
19337 .sum::<usize>();
19338 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
19339
19340 language.brackets().any(|(pair, enabled)| {
19341 let pair_start = pair.start.trim_end();
19342 let pair_end = pair.end.trim_start();
19343
19344 enabled
19345 && pair.newline
19346 && buffer.contains_str_at(range.end, pair_end)
19347 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
19348 })
19349}
19350
19351fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
19352 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
19353 [(buffer, range, _)] => (*buffer, range.clone()),
19354 _ => return false,
19355 };
19356 let pair = {
19357 let mut result: Option<BracketMatch> = None;
19358
19359 for pair in buffer
19360 .all_bracket_ranges(range.clone())
19361 .filter(move |pair| {
19362 pair.open_range.start <= range.start && pair.close_range.end >= range.end
19363 })
19364 {
19365 let len = pair.close_range.end - pair.open_range.start;
19366
19367 if let Some(existing) = &result {
19368 let existing_len = existing.close_range.end - existing.open_range.start;
19369 if len > existing_len {
19370 continue;
19371 }
19372 }
19373
19374 result = Some(pair);
19375 }
19376
19377 result
19378 };
19379 let Some(pair) = pair else {
19380 return false;
19381 };
19382 pair.newline_only
19383 && buffer
19384 .chars_for_range(pair.open_range.end..range.start)
19385 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
19386 .all(|c| c.is_whitespace() && c != '\n')
19387}
19388
19389fn update_uncommitted_diff_for_buffer(
19390 editor: Entity<Editor>,
19391 project: &Entity<Project>,
19392 buffers: impl IntoIterator<Item = Entity<Buffer>>,
19393 buffer: Entity<MultiBuffer>,
19394 cx: &mut App,
19395) -> Task<()> {
19396 let mut tasks = Vec::new();
19397 project.update(cx, |project, cx| {
19398 for buffer in buffers {
19399 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
19400 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
19401 }
19402 }
19403 });
19404 cx.spawn(async move |cx| {
19405 let diffs = future::join_all(tasks).await;
19406 if editor
19407 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19408 .unwrap_or(false)
19409 {
19410 return;
19411 }
19412
19413 buffer
19414 .update(cx, |buffer, cx| {
19415 for diff in diffs.into_iter().flatten() {
19416 buffer.add_diff(diff, cx);
19417 }
19418 })
19419 .ok();
19420 })
19421}
19422
19423fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
19424 let tab_size = tab_size.get() as usize;
19425 let mut width = offset;
19426
19427 for ch in text.chars() {
19428 width += if ch == '\t' {
19429 tab_size - (width % tab_size)
19430 } else {
19431 1
19432 };
19433 }
19434
19435 width - offset
19436}
19437
19438#[cfg(test)]
19439mod tests {
19440 use super::*;
19441
19442 #[test]
19443 fn test_string_size_with_expanded_tabs() {
19444 let nz = |val| NonZeroU32::new(val).unwrap();
19445 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
19446 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
19447 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
19448 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
19449 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
19450 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
19451 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
19452 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
19453 }
19454}
19455
19456/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
19457struct WordBreakingTokenizer<'a> {
19458 input: &'a str,
19459}
19460
19461impl<'a> WordBreakingTokenizer<'a> {
19462 fn new(input: &'a str) -> Self {
19463 Self { input }
19464 }
19465}
19466
19467fn is_char_ideographic(ch: char) -> bool {
19468 use unicode_script::Script::*;
19469 use unicode_script::UnicodeScript;
19470 matches!(ch.script(), Han | Tangut | Yi)
19471}
19472
19473fn is_grapheme_ideographic(text: &str) -> bool {
19474 text.chars().any(is_char_ideographic)
19475}
19476
19477fn is_grapheme_whitespace(text: &str) -> bool {
19478 text.chars().any(|x| x.is_whitespace())
19479}
19480
19481fn should_stay_with_preceding_ideograph(text: &str) -> bool {
19482 text.chars().next().map_or(false, |ch| {
19483 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
19484 })
19485}
19486
19487#[derive(PartialEq, Eq, Debug, Clone, Copy)]
19488enum WordBreakToken<'a> {
19489 Word { token: &'a str, grapheme_len: usize },
19490 InlineWhitespace { token: &'a str, grapheme_len: usize },
19491 Newline,
19492}
19493
19494impl<'a> Iterator for WordBreakingTokenizer<'a> {
19495 /// Yields a span, the count of graphemes in the token, and whether it was
19496 /// whitespace. Note that it also breaks at word boundaries.
19497 type Item = WordBreakToken<'a>;
19498
19499 fn next(&mut self) -> Option<Self::Item> {
19500 use unicode_segmentation::UnicodeSegmentation;
19501 if self.input.is_empty() {
19502 return None;
19503 }
19504
19505 let mut iter = self.input.graphemes(true).peekable();
19506 let mut offset = 0;
19507 let mut grapheme_len = 0;
19508 if let Some(first_grapheme) = iter.next() {
19509 let is_newline = first_grapheme == "\n";
19510 let is_whitespace = is_grapheme_whitespace(first_grapheme);
19511 offset += first_grapheme.len();
19512 grapheme_len += 1;
19513 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
19514 if let Some(grapheme) = iter.peek().copied() {
19515 if should_stay_with_preceding_ideograph(grapheme) {
19516 offset += grapheme.len();
19517 grapheme_len += 1;
19518 }
19519 }
19520 } else {
19521 let mut words = self.input[offset..].split_word_bound_indices().peekable();
19522 let mut next_word_bound = words.peek().copied();
19523 if next_word_bound.map_or(false, |(i, _)| i == 0) {
19524 next_word_bound = words.next();
19525 }
19526 while let Some(grapheme) = iter.peek().copied() {
19527 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19528 break;
19529 };
19530 if is_grapheme_whitespace(grapheme) != is_whitespace
19531 || (grapheme == "\n") != is_newline
19532 {
19533 break;
19534 };
19535 offset += grapheme.len();
19536 grapheme_len += 1;
19537 iter.next();
19538 }
19539 }
19540 let token = &self.input[..offset];
19541 self.input = &self.input[offset..];
19542 if token == "\n" {
19543 Some(WordBreakToken::Newline)
19544 } else if is_whitespace {
19545 Some(WordBreakToken::InlineWhitespace {
19546 token,
19547 grapheme_len,
19548 })
19549 } else {
19550 Some(WordBreakToken::Word {
19551 token,
19552 grapheme_len,
19553 })
19554 }
19555 } else {
19556 None
19557 }
19558 }
19559}
19560
19561#[test]
19562fn test_word_breaking_tokenizer() {
19563 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
19564 ("", &[]),
19565 (" ", &[whitespace(" ", 2)]),
19566 ("Ʒ", &[word("Ʒ", 1)]),
19567 ("Ǽ", &[word("Ǽ", 1)]),
19568 ("⋑", &[word("⋑", 1)]),
19569 ("⋑⋑", &[word("⋑⋑", 2)]),
19570 (
19571 "原理,进而",
19572 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
19573 ),
19574 (
19575 "hello world",
19576 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
19577 ),
19578 (
19579 "hello, world",
19580 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
19581 ),
19582 (
19583 " hello world",
19584 &[
19585 whitespace(" ", 2),
19586 word("hello", 5),
19587 whitespace(" ", 1),
19588 word("world", 5),
19589 ],
19590 ),
19591 (
19592 "这是什么 \n 钢笔",
19593 &[
19594 word("这", 1),
19595 word("是", 1),
19596 word("什", 1),
19597 word("么", 1),
19598 whitespace(" ", 1),
19599 newline(),
19600 whitespace(" ", 1),
19601 word("钢", 1),
19602 word("笔", 1),
19603 ],
19604 ),
19605 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
19606 ];
19607
19608 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19609 WordBreakToken::Word {
19610 token,
19611 grapheme_len,
19612 }
19613 }
19614
19615 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19616 WordBreakToken::InlineWhitespace {
19617 token,
19618 grapheme_len,
19619 }
19620 }
19621
19622 fn newline() -> WordBreakToken<'static> {
19623 WordBreakToken::Newline
19624 }
19625
19626 for (input, result) in tests {
19627 assert_eq!(
19628 WordBreakingTokenizer::new(input)
19629 .collect::<Vec<_>>()
19630 .as_slice(),
19631 *result,
19632 );
19633 }
19634}
19635
19636fn wrap_with_prefix(
19637 line_prefix: String,
19638 unwrapped_text: String,
19639 wrap_column: usize,
19640 tab_size: NonZeroU32,
19641 preserve_existing_whitespace: bool,
19642) -> String {
19643 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
19644 let mut wrapped_text = String::new();
19645 let mut current_line = line_prefix.clone();
19646
19647 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
19648 let mut current_line_len = line_prefix_len;
19649 let mut in_whitespace = false;
19650 for token in tokenizer {
19651 let have_preceding_whitespace = in_whitespace;
19652 match token {
19653 WordBreakToken::Word {
19654 token,
19655 grapheme_len,
19656 } => {
19657 in_whitespace = false;
19658 if current_line_len + grapheme_len > wrap_column
19659 && current_line_len != line_prefix_len
19660 {
19661 wrapped_text.push_str(current_line.trim_end());
19662 wrapped_text.push('\n');
19663 current_line.truncate(line_prefix.len());
19664 current_line_len = line_prefix_len;
19665 }
19666 current_line.push_str(token);
19667 current_line_len += grapheme_len;
19668 }
19669 WordBreakToken::InlineWhitespace {
19670 mut token,
19671 mut grapheme_len,
19672 } => {
19673 in_whitespace = true;
19674 if have_preceding_whitespace && !preserve_existing_whitespace {
19675 continue;
19676 }
19677 if !preserve_existing_whitespace {
19678 token = " ";
19679 grapheme_len = 1;
19680 }
19681 if current_line_len + grapheme_len > wrap_column {
19682 wrapped_text.push_str(current_line.trim_end());
19683 wrapped_text.push('\n');
19684 current_line.truncate(line_prefix.len());
19685 current_line_len = line_prefix_len;
19686 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
19687 current_line.push_str(token);
19688 current_line_len += grapheme_len;
19689 }
19690 }
19691 WordBreakToken::Newline => {
19692 in_whitespace = true;
19693 if preserve_existing_whitespace {
19694 wrapped_text.push_str(current_line.trim_end());
19695 wrapped_text.push('\n');
19696 current_line.truncate(line_prefix.len());
19697 current_line_len = line_prefix_len;
19698 } else if have_preceding_whitespace {
19699 continue;
19700 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
19701 {
19702 wrapped_text.push_str(current_line.trim_end());
19703 wrapped_text.push('\n');
19704 current_line.truncate(line_prefix.len());
19705 current_line_len = line_prefix_len;
19706 } else if current_line_len != line_prefix_len {
19707 current_line.push(' ');
19708 current_line_len += 1;
19709 }
19710 }
19711 }
19712 }
19713
19714 if !current_line.is_empty() {
19715 wrapped_text.push_str(¤t_line);
19716 }
19717 wrapped_text
19718}
19719
19720#[test]
19721fn test_wrap_with_prefix() {
19722 assert_eq!(
19723 wrap_with_prefix(
19724 "# ".to_string(),
19725 "abcdefg".to_string(),
19726 4,
19727 NonZeroU32::new(4).unwrap(),
19728 false,
19729 ),
19730 "# abcdefg"
19731 );
19732 assert_eq!(
19733 wrap_with_prefix(
19734 "".to_string(),
19735 "\thello world".to_string(),
19736 8,
19737 NonZeroU32::new(4).unwrap(),
19738 false,
19739 ),
19740 "hello\nworld"
19741 );
19742 assert_eq!(
19743 wrap_with_prefix(
19744 "// ".to_string(),
19745 "xx \nyy zz aa bb cc".to_string(),
19746 12,
19747 NonZeroU32::new(4).unwrap(),
19748 false,
19749 ),
19750 "// xx yy zz\n// aa bb cc"
19751 );
19752 assert_eq!(
19753 wrap_with_prefix(
19754 String::new(),
19755 "这是什么 \n 钢笔".to_string(),
19756 3,
19757 NonZeroU32::new(4).unwrap(),
19758 false,
19759 ),
19760 "这是什\n么 钢\n笔"
19761 );
19762}
19763
19764pub trait CollaborationHub {
19765 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
19766 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
19767 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
19768}
19769
19770impl CollaborationHub for Entity<Project> {
19771 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
19772 self.read(cx).collaborators()
19773 }
19774
19775 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
19776 self.read(cx).user_store().read(cx).participant_indices()
19777 }
19778
19779 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
19780 let this = self.read(cx);
19781 let user_ids = this.collaborators().values().map(|c| c.user_id);
19782 this.user_store().read_with(cx, |user_store, cx| {
19783 user_store.participant_names(user_ids, cx)
19784 })
19785 }
19786}
19787
19788pub trait SemanticsProvider {
19789 fn hover(
19790 &self,
19791 buffer: &Entity<Buffer>,
19792 position: text::Anchor,
19793 cx: &mut App,
19794 ) -> Option<Task<Vec<project::Hover>>>;
19795
19796 fn inline_values(
19797 &self,
19798 buffer_handle: Entity<Buffer>,
19799 range: Range<text::Anchor>,
19800 cx: &mut App,
19801 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19802
19803 fn inlay_hints(
19804 &self,
19805 buffer_handle: Entity<Buffer>,
19806 range: Range<text::Anchor>,
19807 cx: &mut App,
19808 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19809
19810 fn resolve_inlay_hint(
19811 &self,
19812 hint: InlayHint,
19813 buffer_handle: Entity<Buffer>,
19814 server_id: LanguageServerId,
19815 cx: &mut App,
19816 ) -> Option<Task<anyhow::Result<InlayHint>>>;
19817
19818 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
19819
19820 fn document_highlights(
19821 &self,
19822 buffer: &Entity<Buffer>,
19823 position: text::Anchor,
19824 cx: &mut App,
19825 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
19826
19827 fn definitions(
19828 &self,
19829 buffer: &Entity<Buffer>,
19830 position: text::Anchor,
19831 kind: GotoDefinitionKind,
19832 cx: &mut App,
19833 ) -> Option<Task<Result<Vec<LocationLink>>>>;
19834
19835 fn range_for_rename(
19836 &self,
19837 buffer: &Entity<Buffer>,
19838 position: text::Anchor,
19839 cx: &mut App,
19840 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
19841
19842 fn perform_rename(
19843 &self,
19844 buffer: &Entity<Buffer>,
19845 position: text::Anchor,
19846 new_name: String,
19847 cx: &mut App,
19848 ) -> Option<Task<Result<ProjectTransaction>>>;
19849}
19850
19851pub trait CompletionProvider {
19852 fn completions(
19853 &self,
19854 excerpt_id: ExcerptId,
19855 buffer: &Entity<Buffer>,
19856 buffer_position: text::Anchor,
19857 trigger: CompletionContext,
19858 window: &mut Window,
19859 cx: &mut Context<Editor>,
19860 ) -> Task<Result<Option<Vec<Completion>>>>;
19861
19862 fn resolve_completions(
19863 &self,
19864 buffer: Entity<Buffer>,
19865 completion_indices: Vec<usize>,
19866 completions: Rc<RefCell<Box<[Completion]>>>,
19867 cx: &mut Context<Editor>,
19868 ) -> Task<Result<bool>>;
19869
19870 fn apply_additional_edits_for_completion(
19871 &self,
19872 _buffer: Entity<Buffer>,
19873 _completions: Rc<RefCell<Box<[Completion]>>>,
19874 _completion_index: usize,
19875 _push_to_history: bool,
19876 _cx: &mut Context<Editor>,
19877 ) -> Task<Result<Option<language::Transaction>>> {
19878 Task::ready(Ok(None))
19879 }
19880
19881 fn is_completion_trigger(
19882 &self,
19883 buffer: &Entity<Buffer>,
19884 position: language::Anchor,
19885 text: &str,
19886 trigger_in_words: bool,
19887 cx: &mut Context<Editor>,
19888 ) -> bool;
19889
19890 fn sort_completions(&self) -> bool {
19891 true
19892 }
19893
19894 fn filter_completions(&self) -> bool {
19895 true
19896 }
19897}
19898
19899pub trait CodeActionProvider {
19900 fn id(&self) -> Arc<str>;
19901
19902 fn code_actions(
19903 &self,
19904 buffer: &Entity<Buffer>,
19905 range: Range<text::Anchor>,
19906 window: &mut Window,
19907 cx: &mut App,
19908 ) -> Task<Result<Vec<CodeAction>>>;
19909
19910 fn apply_code_action(
19911 &self,
19912 buffer_handle: Entity<Buffer>,
19913 action: CodeAction,
19914 excerpt_id: ExcerptId,
19915 push_to_history: bool,
19916 window: &mut Window,
19917 cx: &mut App,
19918 ) -> Task<Result<ProjectTransaction>>;
19919}
19920
19921impl CodeActionProvider for Entity<Project> {
19922 fn id(&self) -> Arc<str> {
19923 "project".into()
19924 }
19925
19926 fn code_actions(
19927 &self,
19928 buffer: &Entity<Buffer>,
19929 range: Range<text::Anchor>,
19930 _window: &mut Window,
19931 cx: &mut App,
19932 ) -> Task<Result<Vec<CodeAction>>> {
19933 self.update(cx, |project, cx| {
19934 let code_lens = project.code_lens(buffer, range.clone(), cx);
19935 let code_actions = project.code_actions(buffer, range, None, cx);
19936 cx.background_spawn(async move {
19937 let (code_lens, code_actions) = join(code_lens, code_actions).await;
19938 Ok(code_lens
19939 .context("code lens fetch")?
19940 .into_iter()
19941 .chain(code_actions.context("code action fetch")?)
19942 .collect())
19943 })
19944 })
19945 }
19946
19947 fn apply_code_action(
19948 &self,
19949 buffer_handle: Entity<Buffer>,
19950 action: CodeAction,
19951 _excerpt_id: ExcerptId,
19952 push_to_history: bool,
19953 _window: &mut Window,
19954 cx: &mut App,
19955 ) -> Task<Result<ProjectTransaction>> {
19956 self.update(cx, |project, cx| {
19957 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19958 })
19959 }
19960}
19961
19962fn snippet_completions(
19963 project: &Project,
19964 buffer: &Entity<Buffer>,
19965 buffer_position: text::Anchor,
19966 cx: &mut App,
19967) -> Task<Result<Vec<Completion>>> {
19968 let languages = buffer.read(cx).languages_at(buffer_position);
19969 let snippet_store = project.snippets().read(cx);
19970
19971 let scopes: Vec<_> = languages
19972 .iter()
19973 .filter_map(|language| {
19974 let language_name = language.lsp_id();
19975 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19976
19977 if snippets.is_empty() {
19978 None
19979 } else {
19980 Some((language.default_scope(), snippets))
19981 }
19982 })
19983 .collect();
19984
19985 if scopes.is_empty() {
19986 return Task::ready(Ok(vec![]));
19987 }
19988
19989 let snapshot = buffer.read(cx).text_snapshot();
19990 let chars: String = snapshot
19991 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19992 .collect();
19993 let executor = cx.background_executor().clone();
19994
19995 cx.background_spawn(async move {
19996 let mut all_results: Vec<Completion> = Vec::new();
19997 for (scope, snippets) in scopes.into_iter() {
19998 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19999 let mut last_word = chars
20000 .chars()
20001 .take_while(|c| classifier.is_word(*c))
20002 .collect::<String>();
20003 last_word = last_word.chars().rev().collect();
20004
20005 if last_word.is_empty() {
20006 return Ok(vec![]);
20007 }
20008
20009 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
20010 let to_lsp = |point: &text::Anchor| {
20011 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
20012 point_to_lsp(end)
20013 };
20014 let lsp_end = to_lsp(&buffer_position);
20015
20016 let candidates = snippets
20017 .iter()
20018 .enumerate()
20019 .flat_map(|(ix, snippet)| {
20020 snippet
20021 .prefix
20022 .iter()
20023 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
20024 })
20025 .collect::<Vec<StringMatchCandidate>>();
20026
20027 let mut matches = fuzzy::match_strings(
20028 &candidates,
20029 &last_word,
20030 last_word.chars().any(|c| c.is_uppercase()),
20031 100,
20032 &Default::default(),
20033 executor.clone(),
20034 )
20035 .await;
20036
20037 // Remove all candidates where the query's start does not match the start of any word in the candidate
20038 if let Some(query_start) = last_word.chars().next() {
20039 matches.retain(|string_match| {
20040 split_words(&string_match.string).any(|word| {
20041 // Check that the first codepoint of the word as lowercase matches the first
20042 // codepoint of the query as lowercase
20043 word.chars()
20044 .flat_map(|codepoint| codepoint.to_lowercase())
20045 .zip(query_start.to_lowercase())
20046 .all(|(word_cp, query_cp)| word_cp == query_cp)
20047 })
20048 });
20049 }
20050
20051 let matched_strings = matches
20052 .into_iter()
20053 .map(|m| m.string)
20054 .collect::<HashSet<_>>();
20055
20056 let mut result: Vec<Completion> = snippets
20057 .iter()
20058 .filter_map(|snippet| {
20059 let matching_prefix = snippet
20060 .prefix
20061 .iter()
20062 .find(|prefix| matched_strings.contains(*prefix))?;
20063 let start = as_offset - last_word.len();
20064 let start = snapshot.anchor_before(start);
20065 let range = start..buffer_position;
20066 let lsp_start = to_lsp(&start);
20067 let lsp_range = lsp::Range {
20068 start: lsp_start,
20069 end: lsp_end,
20070 };
20071 Some(Completion {
20072 replace_range: range,
20073 new_text: snippet.body.clone(),
20074 source: CompletionSource::Lsp {
20075 insert_range: None,
20076 server_id: LanguageServerId(usize::MAX),
20077 resolved: true,
20078 lsp_completion: Box::new(lsp::CompletionItem {
20079 label: snippet.prefix.first().unwrap().clone(),
20080 kind: Some(CompletionItemKind::SNIPPET),
20081 label_details: snippet.description.as_ref().map(|description| {
20082 lsp::CompletionItemLabelDetails {
20083 detail: Some(description.clone()),
20084 description: None,
20085 }
20086 }),
20087 insert_text_format: Some(InsertTextFormat::SNIPPET),
20088 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20089 lsp::InsertReplaceEdit {
20090 new_text: snippet.body.clone(),
20091 insert: lsp_range,
20092 replace: lsp_range,
20093 },
20094 )),
20095 filter_text: Some(snippet.body.clone()),
20096 sort_text: Some(char::MAX.to_string()),
20097 ..lsp::CompletionItem::default()
20098 }),
20099 lsp_defaults: None,
20100 },
20101 label: CodeLabel {
20102 text: matching_prefix.clone(),
20103 runs: Vec::new(),
20104 filter_range: 0..matching_prefix.len(),
20105 },
20106 icon_path: None,
20107 documentation: Some(
20108 CompletionDocumentation::SingleLineAndMultiLinePlainText {
20109 single_line: snippet.name.clone().into(),
20110 plain_text: snippet
20111 .description
20112 .clone()
20113 .map(|description| description.into()),
20114 },
20115 ),
20116 insert_text_mode: None,
20117 confirm: None,
20118 })
20119 })
20120 .collect();
20121
20122 all_results.append(&mut result);
20123 }
20124
20125 Ok(all_results)
20126 })
20127}
20128
20129impl CompletionProvider for Entity<Project> {
20130 fn completions(
20131 &self,
20132 _excerpt_id: ExcerptId,
20133 buffer: &Entity<Buffer>,
20134 buffer_position: text::Anchor,
20135 options: CompletionContext,
20136 _window: &mut Window,
20137 cx: &mut Context<Editor>,
20138 ) -> Task<Result<Option<Vec<Completion>>>> {
20139 self.update(cx, |project, cx| {
20140 let snippets = snippet_completions(project, buffer, buffer_position, cx);
20141 let project_completions = project.completions(buffer, buffer_position, options, cx);
20142 cx.background_spawn(async move {
20143 let snippets_completions = snippets.await?;
20144 match project_completions.await? {
20145 Some(mut completions) => {
20146 completions.extend(snippets_completions);
20147 Ok(Some(completions))
20148 }
20149 None => {
20150 if snippets_completions.is_empty() {
20151 Ok(None)
20152 } else {
20153 Ok(Some(snippets_completions))
20154 }
20155 }
20156 }
20157 })
20158 })
20159 }
20160
20161 fn resolve_completions(
20162 &self,
20163 buffer: Entity<Buffer>,
20164 completion_indices: Vec<usize>,
20165 completions: Rc<RefCell<Box<[Completion]>>>,
20166 cx: &mut Context<Editor>,
20167 ) -> Task<Result<bool>> {
20168 self.update(cx, |project, cx| {
20169 project.lsp_store().update(cx, |lsp_store, cx| {
20170 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
20171 })
20172 })
20173 }
20174
20175 fn apply_additional_edits_for_completion(
20176 &self,
20177 buffer: Entity<Buffer>,
20178 completions: Rc<RefCell<Box<[Completion]>>>,
20179 completion_index: usize,
20180 push_to_history: bool,
20181 cx: &mut Context<Editor>,
20182 ) -> Task<Result<Option<language::Transaction>>> {
20183 self.update(cx, |project, cx| {
20184 project.lsp_store().update(cx, |lsp_store, cx| {
20185 lsp_store.apply_additional_edits_for_completion(
20186 buffer,
20187 completions,
20188 completion_index,
20189 push_to_history,
20190 cx,
20191 )
20192 })
20193 })
20194 }
20195
20196 fn is_completion_trigger(
20197 &self,
20198 buffer: &Entity<Buffer>,
20199 position: language::Anchor,
20200 text: &str,
20201 trigger_in_words: bool,
20202 cx: &mut Context<Editor>,
20203 ) -> bool {
20204 let mut chars = text.chars();
20205 let char = if let Some(char) = chars.next() {
20206 char
20207 } else {
20208 return false;
20209 };
20210 if chars.next().is_some() {
20211 return false;
20212 }
20213
20214 let buffer = buffer.read(cx);
20215 let snapshot = buffer.snapshot();
20216 if !snapshot.settings_at(position, cx).show_completions_on_input {
20217 return false;
20218 }
20219 let classifier = snapshot.char_classifier_at(position).for_completion(true);
20220 if trigger_in_words && classifier.is_word(char) {
20221 return true;
20222 }
20223
20224 buffer.completion_triggers().contains(text)
20225 }
20226}
20227
20228impl SemanticsProvider for Entity<Project> {
20229 fn hover(
20230 &self,
20231 buffer: &Entity<Buffer>,
20232 position: text::Anchor,
20233 cx: &mut App,
20234 ) -> Option<Task<Vec<project::Hover>>> {
20235 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
20236 }
20237
20238 fn document_highlights(
20239 &self,
20240 buffer: &Entity<Buffer>,
20241 position: text::Anchor,
20242 cx: &mut App,
20243 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
20244 Some(self.update(cx, |project, cx| {
20245 project.document_highlights(buffer, position, cx)
20246 }))
20247 }
20248
20249 fn definitions(
20250 &self,
20251 buffer: &Entity<Buffer>,
20252 position: text::Anchor,
20253 kind: GotoDefinitionKind,
20254 cx: &mut App,
20255 ) -> Option<Task<Result<Vec<LocationLink>>>> {
20256 Some(self.update(cx, |project, cx| match kind {
20257 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
20258 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
20259 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
20260 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
20261 }))
20262 }
20263
20264 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
20265 // TODO: make this work for remote projects
20266 self.update(cx, |project, cx| {
20267 if project
20268 .active_debug_session(cx)
20269 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
20270 {
20271 return true;
20272 }
20273
20274 buffer.update(cx, |buffer, cx| {
20275 project.any_language_server_supports_inlay_hints(buffer, cx)
20276 })
20277 })
20278 }
20279
20280 fn inline_values(
20281 &self,
20282 buffer_handle: Entity<Buffer>,
20283
20284 range: Range<text::Anchor>,
20285 cx: &mut App,
20286 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20287 self.update(cx, |project, cx| {
20288 let (session, active_stack_frame) = project.active_debug_session(cx)?;
20289
20290 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
20291 })
20292 }
20293
20294 fn inlay_hints(
20295 &self,
20296 buffer_handle: Entity<Buffer>,
20297 range: Range<text::Anchor>,
20298 cx: &mut App,
20299 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20300 Some(self.update(cx, |project, cx| {
20301 project.inlay_hints(buffer_handle, range, cx)
20302 }))
20303 }
20304
20305 fn resolve_inlay_hint(
20306 &self,
20307 hint: InlayHint,
20308 buffer_handle: Entity<Buffer>,
20309 server_id: LanguageServerId,
20310 cx: &mut App,
20311 ) -> Option<Task<anyhow::Result<InlayHint>>> {
20312 Some(self.update(cx, |project, cx| {
20313 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
20314 }))
20315 }
20316
20317 fn range_for_rename(
20318 &self,
20319 buffer: &Entity<Buffer>,
20320 position: text::Anchor,
20321 cx: &mut App,
20322 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
20323 Some(self.update(cx, |project, cx| {
20324 let buffer = buffer.clone();
20325 let task = project.prepare_rename(buffer.clone(), position, cx);
20326 cx.spawn(async move |_, cx| {
20327 Ok(match task.await? {
20328 PrepareRenameResponse::Success(range) => Some(range),
20329 PrepareRenameResponse::InvalidPosition => None,
20330 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
20331 // Fallback on using TreeSitter info to determine identifier range
20332 buffer.update(cx, |buffer, _| {
20333 let snapshot = buffer.snapshot();
20334 let (range, kind) = snapshot.surrounding_word(position);
20335 if kind != Some(CharKind::Word) {
20336 return None;
20337 }
20338 Some(
20339 snapshot.anchor_before(range.start)
20340 ..snapshot.anchor_after(range.end),
20341 )
20342 })?
20343 }
20344 })
20345 })
20346 }))
20347 }
20348
20349 fn perform_rename(
20350 &self,
20351 buffer: &Entity<Buffer>,
20352 position: text::Anchor,
20353 new_name: String,
20354 cx: &mut App,
20355 ) -> Option<Task<Result<ProjectTransaction>>> {
20356 Some(self.update(cx, |project, cx| {
20357 project.perform_rename(buffer.clone(), position, new_name, cx)
20358 }))
20359 }
20360}
20361
20362fn inlay_hint_settings(
20363 location: Anchor,
20364 snapshot: &MultiBufferSnapshot,
20365 cx: &mut Context<Editor>,
20366) -> InlayHintSettings {
20367 let file = snapshot.file_at(location);
20368 let language = snapshot.language_at(location).map(|l| l.name());
20369 language_settings(language, file, cx).inlay_hints
20370}
20371
20372fn consume_contiguous_rows(
20373 contiguous_row_selections: &mut Vec<Selection<Point>>,
20374 selection: &Selection<Point>,
20375 display_map: &DisplaySnapshot,
20376 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
20377) -> (MultiBufferRow, MultiBufferRow) {
20378 contiguous_row_selections.push(selection.clone());
20379 let start_row = MultiBufferRow(selection.start.row);
20380 let mut end_row = ending_row(selection, display_map);
20381
20382 while let Some(next_selection) = selections.peek() {
20383 if next_selection.start.row <= end_row.0 {
20384 end_row = ending_row(next_selection, display_map);
20385 contiguous_row_selections.push(selections.next().unwrap().clone());
20386 } else {
20387 break;
20388 }
20389 }
20390 (start_row, end_row)
20391}
20392
20393fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
20394 if next_selection.end.column > 0 || next_selection.is_empty() {
20395 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
20396 } else {
20397 MultiBufferRow(next_selection.end.row)
20398 }
20399}
20400
20401impl EditorSnapshot {
20402 pub fn remote_selections_in_range<'a>(
20403 &'a self,
20404 range: &'a Range<Anchor>,
20405 collaboration_hub: &dyn CollaborationHub,
20406 cx: &'a App,
20407 ) -> impl 'a + Iterator<Item = RemoteSelection> {
20408 let participant_names = collaboration_hub.user_names(cx);
20409 let participant_indices = collaboration_hub.user_participant_indices(cx);
20410 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20411 let collaborators_by_replica_id = collaborators_by_peer_id
20412 .values()
20413 .map(|collaborator| (collaborator.replica_id, collaborator))
20414 .collect::<HashMap<_, _>>();
20415 self.buffer_snapshot
20416 .selections_in_range(range, false)
20417 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20418 if replica_id == AGENT_REPLICA_ID {
20419 Some(RemoteSelection {
20420 replica_id,
20421 selection,
20422 cursor_shape,
20423 line_mode,
20424 collaborator_id: CollaboratorId::Agent,
20425 user_name: Some("Agent".into()),
20426 color: cx.theme().players().agent(),
20427 })
20428 } else {
20429 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20430 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20431 let user_name = participant_names.get(&collaborator.user_id).cloned();
20432 Some(RemoteSelection {
20433 replica_id,
20434 selection,
20435 cursor_shape,
20436 line_mode,
20437 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20438 user_name,
20439 color: if let Some(index) = participant_index {
20440 cx.theme().players().color_for_participant(index.0)
20441 } else {
20442 cx.theme().players().absent()
20443 },
20444 })
20445 }
20446 })
20447 }
20448
20449 pub fn hunks_for_ranges(
20450 &self,
20451 ranges: impl IntoIterator<Item = Range<Point>>,
20452 ) -> Vec<MultiBufferDiffHunk> {
20453 let mut hunks = Vec::new();
20454 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20455 HashMap::default();
20456 for query_range in ranges {
20457 let query_rows =
20458 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20459 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20460 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20461 ) {
20462 // Include deleted hunks that are adjacent to the query range, because
20463 // otherwise they would be missed.
20464 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20465 if hunk.status().is_deleted() {
20466 intersects_range |= hunk.row_range.start == query_rows.end;
20467 intersects_range |= hunk.row_range.end == query_rows.start;
20468 }
20469 if intersects_range {
20470 if !processed_buffer_rows
20471 .entry(hunk.buffer_id)
20472 .or_default()
20473 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20474 {
20475 continue;
20476 }
20477 hunks.push(hunk);
20478 }
20479 }
20480 }
20481
20482 hunks
20483 }
20484
20485 fn display_diff_hunks_for_rows<'a>(
20486 &'a self,
20487 display_rows: Range<DisplayRow>,
20488 folded_buffers: &'a HashSet<BufferId>,
20489 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20490 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20491 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20492
20493 self.buffer_snapshot
20494 .diff_hunks_in_range(buffer_start..buffer_end)
20495 .filter_map(|hunk| {
20496 if folded_buffers.contains(&hunk.buffer_id) {
20497 return None;
20498 }
20499
20500 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20501 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20502
20503 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20504 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20505
20506 let display_hunk = if hunk_display_start.column() != 0 {
20507 DisplayDiffHunk::Folded {
20508 display_row: hunk_display_start.row(),
20509 }
20510 } else {
20511 let mut end_row = hunk_display_end.row();
20512 if hunk_display_end.column() > 0 {
20513 end_row.0 += 1;
20514 }
20515 let is_created_file = hunk.is_created_file();
20516 DisplayDiffHunk::Unfolded {
20517 status: hunk.status(),
20518 diff_base_byte_range: hunk.diff_base_byte_range,
20519 display_row_range: hunk_display_start.row()..end_row,
20520 multi_buffer_range: Anchor::range_in_buffer(
20521 hunk.excerpt_id,
20522 hunk.buffer_id,
20523 hunk.buffer_range,
20524 ),
20525 is_created_file,
20526 }
20527 };
20528
20529 Some(display_hunk)
20530 })
20531 }
20532
20533 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20534 self.display_snapshot.buffer_snapshot.language_at(position)
20535 }
20536
20537 pub fn is_focused(&self) -> bool {
20538 self.is_focused
20539 }
20540
20541 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20542 self.placeholder_text.as_ref()
20543 }
20544
20545 pub fn scroll_position(&self) -> gpui::Point<f32> {
20546 self.scroll_anchor.scroll_position(&self.display_snapshot)
20547 }
20548
20549 fn gutter_dimensions(
20550 &self,
20551 font_id: FontId,
20552 font_size: Pixels,
20553 max_line_number_width: Pixels,
20554 cx: &App,
20555 ) -> Option<GutterDimensions> {
20556 if !self.show_gutter {
20557 return None;
20558 }
20559
20560 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20561 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20562
20563 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20564 matches!(
20565 ProjectSettings::get_global(cx).git.git_gutter,
20566 Some(GitGutterSetting::TrackedFiles)
20567 )
20568 });
20569 let gutter_settings = EditorSettings::get_global(cx).gutter;
20570 let show_line_numbers = self
20571 .show_line_numbers
20572 .unwrap_or(gutter_settings.line_numbers);
20573 let line_gutter_width = if show_line_numbers {
20574 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20575 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20576 max_line_number_width.max(min_width_for_number_on_gutter)
20577 } else {
20578 0.0.into()
20579 };
20580
20581 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20582 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20583
20584 let git_blame_entries_width =
20585 self.git_blame_gutter_max_author_length
20586 .map(|max_author_length| {
20587 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20588 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20589
20590 /// The number of characters to dedicate to gaps and margins.
20591 const SPACING_WIDTH: usize = 4;
20592
20593 let max_char_count = max_author_length.min(renderer.max_author_length())
20594 + ::git::SHORT_SHA_LENGTH
20595 + MAX_RELATIVE_TIMESTAMP.len()
20596 + SPACING_WIDTH;
20597
20598 em_advance * max_char_count
20599 });
20600
20601 let is_singleton = self.buffer_snapshot.is_singleton();
20602
20603 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
20604 left_padding += if !is_singleton {
20605 em_width * 4.0
20606 } else if show_runnables || show_breakpoints {
20607 em_width * 3.0
20608 } else if show_git_gutter && show_line_numbers {
20609 em_width * 2.0
20610 } else if show_git_gutter || show_line_numbers {
20611 em_width
20612 } else {
20613 px(0.)
20614 };
20615
20616 let shows_folds = is_singleton && gutter_settings.folds;
20617
20618 let right_padding = if shows_folds && show_line_numbers {
20619 em_width * 4.0
20620 } else if shows_folds || (!is_singleton && show_line_numbers) {
20621 em_width * 3.0
20622 } else if show_line_numbers {
20623 em_width
20624 } else {
20625 px(0.)
20626 };
20627
20628 Some(GutterDimensions {
20629 left_padding,
20630 right_padding,
20631 width: line_gutter_width + left_padding + right_padding,
20632 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
20633 git_blame_entries_width,
20634 })
20635 }
20636
20637 pub fn render_crease_toggle(
20638 &self,
20639 buffer_row: MultiBufferRow,
20640 row_contains_cursor: bool,
20641 editor: Entity<Editor>,
20642 window: &mut Window,
20643 cx: &mut App,
20644 ) -> Option<AnyElement> {
20645 let folded = self.is_line_folded(buffer_row);
20646 let mut is_foldable = false;
20647
20648 if let Some(crease) = self
20649 .crease_snapshot
20650 .query_row(buffer_row, &self.buffer_snapshot)
20651 {
20652 is_foldable = true;
20653 match crease {
20654 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20655 if let Some(render_toggle) = render_toggle {
20656 let toggle_callback =
20657 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20658 if folded {
20659 editor.update(cx, |editor, cx| {
20660 editor.fold_at(buffer_row, window, cx)
20661 });
20662 } else {
20663 editor.update(cx, |editor, cx| {
20664 editor.unfold_at(buffer_row, window, cx)
20665 });
20666 }
20667 });
20668 return Some((render_toggle)(
20669 buffer_row,
20670 folded,
20671 toggle_callback,
20672 window,
20673 cx,
20674 ));
20675 }
20676 }
20677 }
20678 }
20679
20680 is_foldable |= self.starts_indent(buffer_row);
20681
20682 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20683 Some(
20684 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20685 .toggle_state(folded)
20686 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20687 if folded {
20688 this.unfold_at(buffer_row, window, cx);
20689 } else {
20690 this.fold_at(buffer_row, window, cx);
20691 }
20692 }))
20693 .into_any_element(),
20694 )
20695 } else {
20696 None
20697 }
20698 }
20699
20700 pub fn render_crease_trailer(
20701 &self,
20702 buffer_row: MultiBufferRow,
20703 window: &mut Window,
20704 cx: &mut App,
20705 ) -> Option<AnyElement> {
20706 let folded = self.is_line_folded(buffer_row);
20707 if let Crease::Inline { render_trailer, .. } = self
20708 .crease_snapshot
20709 .query_row(buffer_row, &self.buffer_snapshot)?
20710 {
20711 let render_trailer = render_trailer.as_ref()?;
20712 Some(render_trailer(buffer_row, folded, window, cx))
20713 } else {
20714 None
20715 }
20716 }
20717}
20718
20719impl Deref for EditorSnapshot {
20720 type Target = DisplaySnapshot;
20721
20722 fn deref(&self) -> &Self::Target {
20723 &self.display_snapshot
20724 }
20725}
20726
20727#[derive(Clone, Debug, PartialEq, Eq)]
20728pub enum EditorEvent {
20729 InputIgnored {
20730 text: Arc<str>,
20731 },
20732 InputHandled {
20733 utf16_range_to_replace: Option<Range<isize>>,
20734 text: Arc<str>,
20735 },
20736 ExcerptsAdded {
20737 buffer: Entity<Buffer>,
20738 predecessor: ExcerptId,
20739 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
20740 },
20741 ExcerptsRemoved {
20742 ids: Vec<ExcerptId>,
20743 removed_buffer_ids: Vec<BufferId>,
20744 },
20745 BufferFoldToggled {
20746 ids: Vec<ExcerptId>,
20747 folded: bool,
20748 },
20749 ExcerptsEdited {
20750 ids: Vec<ExcerptId>,
20751 },
20752 ExcerptsExpanded {
20753 ids: Vec<ExcerptId>,
20754 },
20755 BufferEdited,
20756 Edited {
20757 transaction_id: clock::Lamport,
20758 },
20759 Reparsed(BufferId),
20760 Focused,
20761 FocusedIn,
20762 Blurred,
20763 DirtyChanged,
20764 Saved,
20765 TitleChanged,
20766 DiffBaseChanged,
20767 SelectionsChanged {
20768 local: bool,
20769 },
20770 ScrollPositionChanged {
20771 local: bool,
20772 autoscroll: bool,
20773 },
20774 Closed,
20775 TransactionUndone {
20776 transaction_id: clock::Lamport,
20777 },
20778 TransactionBegun {
20779 transaction_id: clock::Lamport,
20780 },
20781 Reloaded,
20782 CursorShapeChanged,
20783 PushedToNavHistory {
20784 anchor: Anchor,
20785 is_deactivate: bool,
20786 },
20787}
20788
20789impl EventEmitter<EditorEvent> for Editor {}
20790
20791impl Focusable for Editor {
20792 fn focus_handle(&self, _cx: &App) -> FocusHandle {
20793 self.focus_handle.clone()
20794 }
20795}
20796
20797impl Render for Editor {
20798 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20799 let settings = ThemeSettings::get_global(cx);
20800
20801 let mut text_style = match self.mode {
20802 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
20803 color: cx.theme().colors().editor_foreground,
20804 font_family: settings.ui_font.family.clone(),
20805 font_features: settings.ui_font.features.clone(),
20806 font_fallbacks: settings.ui_font.fallbacks.clone(),
20807 font_size: rems(0.875).into(),
20808 font_weight: settings.ui_font.weight,
20809 line_height: relative(settings.buffer_line_height.value()),
20810 ..Default::default()
20811 },
20812 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
20813 color: cx.theme().colors().editor_foreground,
20814 font_family: settings.buffer_font.family.clone(),
20815 font_features: settings.buffer_font.features.clone(),
20816 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20817 font_size: settings.buffer_font_size(cx).into(),
20818 font_weight: settings.buffer_font.weight,
20819 line_height: relative(settings.buffer_line_height.value()),
20820 ..Default::default()
20821 },
20822 };
20823 if let Some(text_style_refinement) = &self.text_style_refinement {
20824 text_style.refine(text_style_refinement)
20825 }
20826
20827 let background = match self.mode {
20828 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
20829 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
20830 EditorMode::Full { .. } => cx.theme().colors().editor_background,
20831 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
20832 };
20833
20834 EditorElement::new(
20835 &cx.entity(),
20836 EditorStyle {
20837 background,
20838 local_player: cx.theme().players().local(),
20839 text: text_style,
20840 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
20841 syntax: cx.theme().syntax().clone(),
20842 status: cx.theme().status().clone(),
20843 inlay_hints_style: make_inlay_hints_style(cx),
20844 inline_completion_styles: make_suggestion_styles(cx),
20845 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
20846 show_underlines: !self.mode.is_minimap(),
20847 },
20848 )
20849 }
20850}
20851
20852impl EntityInputHandler for Editor {
20853 fn text_for_range(
20854 &mut self,
20855 range_utf16: Range<usize>,
20856 adjusted_range: &mut Option<Range<usize>>,
20857 _: &mut Window,
20858 cx: &mut Context<Self>,
20859 ) -> Option<String> {
20860 let snapshot = self.buffer.read(cx).read(cx);
20861 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
20862 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
20863 if (start.0..end.0) != range_utf16 {
20864 adjusted_range.replace(start.0..end.0);
20865 }
20866 Some(snapshot.text_for_range(start..end).collect())
20867 }
20868
20869 fn selected_text_range(
20870 &mut self,
20871 ignore_disabled_input: bool,
20872 _: &mut Window,
20873 cx: &mut Context<Self>,
20874 ) -> Option<UTF16Selection> {
20875 // Prevent the IME menu from appearing when holding down an alphabetic key
20876 // while input is disabled.
20877 if !ignore_disabled_input && !self.input_enabled {
20878 return None;
20879 }
20880
20881 let selection = self.selections.newest::<OffsetUtf16>(cx);
20882 let range = selection.range();
20883
20884 Some(UTF16Selection {
20885 range: range.start.0..range.end.0,
20886 reversed: selection.reversed,
20887 })
20888 }
20889
20890 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
20891 let snapshot = self.buffer.read(cx).read(cx);
20892 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
20893 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
20894 }
20895
20896 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20897 self.clear_highlights::<InputComposition>(cx);
20898 self.ime_transaction.take();
20899 }
20900
20901 fn replace_text_in_range(
20902 &mut self,
20903 range_utf16: Option<Range<usize>>,
20904 text: &str,
20905 window: &mut Window,
20906 cx: &mut Context<Self>,
20907 ) {
20908 if !self.input_enabled {
20909 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20910 return;
20911 }
20912
20913 self.transact(window, cx, |this, window, cx| {
20914 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
20915 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20916 Some(this.selection_replacement_ranges(range_utf16, cx))
20917 } else {
20918 this.marked_text_ranges(cx)
20919 };
20920
20921 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
20922 let newest_selection_id = this.selections.newest_anchor().id;
20923 this.selections
20924 .all::<OffsetUtf16>(cx)
20925 .iter()
20926 .zip(ranges_to_replace.iter())
20927 .find_map(|(selection, range)| {
20928 if selection.id == newest_selection_id {
20929 Some(
20930 (range.start.0 as isize - selection.head().0 as isize)
20931 ..(range.end.0 as isize - selection.head().0 as isize),
20932 )
20933 } else {
20934 None
20935 }
20936 })
20937 });
20938
20939 cx.emit(EditorEvent::InputHandled {
20940 utf16_range_to_replace: range_to_replace,
20941 text: text.into(),
20942 });
20943
20944 if let Some(new_selected_ranges) = new_selected_ranges {
20945 this.change_selections(None, window, cx, |selections| {
20946 selections.select_ranges(new_selected_ranges)
20947 });
20948 this.backspace(&Default::default(), window, cx);
20949 }
20950
20951 this.handle_input(text, window, cx);
20952 });
20953
20954 if let Some(transaction) = self.ime_transaction {
20955 self.buffer.update(cx, |buffer, cx| {
20956 buffer.group_until_transaction(transaction, cx);
20957 });
20958 }
20959
20960 self.unmark_text(window, cx);
20961 }
20962
20963 fn replace_and_mark_text_in_range(
20964 &mut self,
20965 range_utf16: Option<Range<usize>>,
20966 text: &str,
20967 new_selected_range_utf16: Option<Range<usize>>,
20968 window: &mut Window,
20969 cx: &mut Context<Self>,
20970 ) {
20971 if !self.input_enabled {
20972 return;
20973 }
20974
20975 let transaction = self.transact(window, cx, |this, window, cx| {
20976 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
20977 let snapshot = this.buffer.read(cx).read(cx);
20978 if let Some(relative_range_utf16) = range_utf16.as_ref() {
20979 for marked_range in &mut marked_ranges {
20980 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
20981 marked_range.start.0 += relative_range_utf16.start;
20982 marked_range.start =
20983 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
20984 marked_range.end =
20985 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
20986 }
20987 }
20988 Some(marked_ranges)
20989 } else if let Some(range_utf16) = range_utf16 {
20990 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20991 Some(this.selection_replacement_ranges(range_utf16, cx))
20992 } else {
20993 None
20994 };
20995
20996 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
20997 let newest_selection_id = this.selections.newest_anchor().id;
20998 this.selections
20999 .all::<OffsetUtf16>(cx)
21000 .iter()
21001 .zip(ranges_to_replace.iter())
21002 .find_map(|(selection, range)| {
21003 if selection.id == newest_selection_id {
21004 Some(
21005 (range.start.0 as isize - selection.head().0 as isize)
21006 ..(range.end.0 as isize - selection.head().0 as isize),
21007 )
21008 } else {
21009 None
21010 }
21011 })
21012 });
21013
21014 cx.emit(EditorEvent::InputHandled {
21015 utf16_range_to_replace: range_to_replace,
21016 text: text.into(),
21017 });
21018
21019 if let Some(ranges) = ranges_to_replace {
21020 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
21021 }
21022
21023 let marked_ranges = {
21024 let snapshot = this.buffer.read(cx).read(cx);
21025 this.selections
21026 .disjoint_anchors()
21027 .iter()
21028 .map(|selection| {
21029 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
21030 })
21031 .collect::<Vec<_>>()
21032 };
21033
21034 if text.is_empty() {
21035 this.unmark_text(window, cx);
21036 } else {
21037 this.highlight_text::<InputComposition>(
21038 marked_ranges.clone(),
21039 HighlightStyle {
21040 underline: Some(UnderlineStyle {
21041 thickness: px(1.),
21042 color: None,
21043 wavy: false,
21044 }),
21045 ..Default::default()
21046 },
21047 cx,
21048 );
21049 }
21050
21051 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
21052 let use_autoclose = this.use_autoclose;
21053 let use_auto_surround = this.use_auto_surround;
21054 this.set_use_autoclose(false);
21055 this.set_use_auto_surround(false);
21056 this.handle_input(text, window, cx);
21057 this.set_use_autoclose(use_autoclose);
21058 this.set_use_auto_surround(use_auto_surround);
21059
21060 if let Some(new_selected_range) = new_selected_range_utf16 {
21061 let snapshot = this.buffer.read(cx).read(cx);
21062 let new_selected_ranges = marked_ranges
21063 .into_iter()
21064 .map(|marked_range| {
21065 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
21066 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
21067 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
21068 snapshot.clip_offset_utf16(new_start, Bias::Left)
21069 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
21070 })
21071 .collect::<Vec<_>>();
21072
21073 drop(snapshot);
21074 this.change_selections(None, window, cx, |selections| {
21075 selections.select_ranges(new_selected_ranges)
21076 });
21077 }
21078 });
21079
21080 self.ime_transaction = self.ime_transaction.or(transaction);
21081 if let Some(transaction) = self.ime_transaction {
21082 self.buffer.update(cx, |buffer, cx| {
21083 buffer.group_until_transaction(transaction, cx);
21084 });
21085 }
21086
21087 if self.text_highlights::<InputComposition>(cx).is_none() {
21088 self.ime_transaction.take();
21089 }
21090 }
21091
21092 fn bounds_for_range(
21093 &mut self,
21094 range_utf16: Range<usize>,
21095 element_bounds: gpui::Bounds<Pixels>,
21096 window: &mut Window,
21097 cx: &mut Context<Self>,
21098 ) -> Option<gpui::Bounds<Pixels>> {
21099 let text_layout_details = self.text_layout_details(window);
21100 let gpui::Size {
21101 width: em_width,
21102 height: line_height,
21103 } = self.character_size(window);
21104
21105 let snapshot = self.snapshot(window, cx);
21106 let scroll_position = snapshot.scroll_position();
21107 let scroll_left = scroll_position.x * em_width;
21108
21109 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
21110 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
21111 + self.gutter_dimensions.width
21112 + self.gutter_dimensions.margin;
21113 let y = line_height * (start.row().as_f32() - scroll_position.y);
21114
21115 Some(Bounds {
21116 origin: element_bounds.origin + point(x, y),
21117 size: size(em_width, line_height),
21118 })
21119 }
21120
21121 fn character_index_for_point(
21122 &mut self,
21123 point: gpui::Point<Pixels>,
21124 _window: &mut Window,
21125 _cx: &mut Context<Self>,
21126 ) -> Option<usize> {
21127 let position_map = self.last_position_map.as_ref()?;
21128 if !position_map.text_hitbox.contains(&point) {
21129 return None;
21130 }
21131 let display_point = position_map.point_for_position(point).previous_valid;
21132 let anchor = position_map
21133 .snapshot
21134 .display_point_to_anchor(display_point, Bias::Left);
21135 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
21136 Some(utf16_offset.0)
21137 }
21138}
21139
21140trait SelectionExt {
21141 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
21142 fn spanned_rows(
21143 &self,
21144 include_end_if_at_line_start: bool,
21145 map: &DisplaySnapshot,
21146 ) -> Range<MultiBufferRow>;
21147}
21148
21149impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
21150 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
21151 let start = self
21152 .start
21153 .to_point(&map.buffer_snapshot)
21154 .to_display_point(map);
21155 let end = self
21156 .end
21157 .to_point(&map.buffer_snapshot)
21158 .to_display_point(map);
21159 if self.reversed {
21160 end..start
21161 } else {
21162 start..end
21163 }
21164 }
21165
21166 fn spanned_rows(
21167 &self,
21168 include_end_if_at_line_start: bool,
21169 map: &DisplaySnapshot,
21170 ) -> Range<MultiBufferRow> {
21171 let start = self.start.to_point(&map.buffer_snapshot);
21172 let mut end = self.end.to_point(&map.buffer_snapshot);
21173 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
21174 end.row -= 1;
21175 }
21176
21177 let buffer_start = map.prev_line_boundary(start).0;
21178 let buffer_end = map.next_line_boundary(end).0;
21179 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
21180 }
21181}
21182
21183impl<T: InvalidationRegion> InvalidationStack<T> {
21184 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
21185 where
21186 S: Clone + ToOffset,
21187 {
21188 while let Some(region) = self.last() {
21189 let all_selections_inside_invalidation_ranges =
21190 if selections.len() == region.ranges().len() {
21191 selections
21192 .iter()
21193 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
21194 .all(|(selection, invalidation_range)| {
21195 let head = selection.head().to_offset(buffer);
21196 invalidation_range.start <= head && invalidation_range.end >= head
21197 })
21198 } else {
21199 false
21200 };
21201
21202 if all_selections_inside_invalidation_ranges {
21203 break;
21204 } else {
21205 self.pop();
21206 }
21207 }
21208 }
21209}
21210
21211impl<T> Default for InvalidationStack<T> {
21212 fn default() -> Self {
21213 Self(Default::default())
21214 }
21215}
21216
21217impl<T> Deref for InvalidationStack<T> {
21218 type Target = Vec<T>;
21219
21220 fn deref(&self) -> &Self::Target {
21221 &self.0
21222 }
21223}
21224
21225impl<T> DerefMut for InvalidationStack<T> {
21226 fn deref_mut(&mut self) -> &mut Self::Target {
21227 &mut self.0
21228 }
21229}
21230
21231impl InvalidationRegion for SnippetState {
21232 fn ranges(&self) -> &[Range<Anchor>] {
21233 &self.ranges[self.active_index]
21234 }
21235}
21236
21237fn inline_completion_edit_text(
21238 current_snapshot: &BufferSnapshot,
21239 edits: &[(Range<Anchor>, String)],
21240 edit_preview: &EditPreview,
21241 include_deletions: bool,
21242 cx: &App,
21243) -> HighlightedText {
21244 let edits = edits
21245 .iter()
21246 .map(|(anchor, text)| {
21247 (
21248 anchor.start.text_anchor..anchor.end.text_anchor,
21249 text.clone(),
21250 )
21251 })
21252 .collect::<Vec<_>>();
21253
21254 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
21255}
21256
21257pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
21258 match severity {
21259 lsp::DiagnosticSeverity::ERROR => colors.error,
21260 lsp::DiagnosticSeverity::WARNING => colors.warning,
21261 lsp::DiagnosticSeverity::INFORMATION => colors.info,
21262 lsp::DiagnosticSeverity::HINT => colors.info,
21263 _ => colors.ignored,
21264 }
21265}
21266
21267pub fn styled_runs_for_code_label<'a>(
21268 label: &'a CodeLabel,
21269 syntax_theme: &'a theme::SyntaxTheme,
21270) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
21271 let fade_out = HighlightStyle {
21272 fade_out: Some(0.35),
21273 ..Default::default()
21274 };
21275
21276 let mut prev_end = label.filter_range.end;
21277 label
21278 .runs
21279 .iter()
21280 .enumerate()
21281 .flat_map(move |(ix, (range, highlight_id))| {
21282 let style = if let Some(style) = highlight_id.style(syntax_theme) {
21283 style
21284 } else {
21285 return Default::default();
21286 };
21287 let mut muted_style = style;
21288 muted_style.highlight(fade_out);
21289
21290 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
21291 if range.start >= label.filter_range.end {
21292 if range.start > prev_end {
21293 runs.push((prev_end..range.start, fade_out));
21294 }
21295 runs.push((range.clone(), muted_style));
21296 } else if range.end <= label.filter_range.end {
21297 runs.push((range.clone(), style));
21298 } else {
21299 runs.push((range.start..label.filter_range.end, style));
21300 runs.push((label.filter_range.end..range.end, muted_style));
21301 }
21302 prev_end = cmp::max(prev_end, range.end);
21303
21304 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
21305 runs.push((prev_end..label.text.len(), fade_out));
21306 }
21307
21308 runs
21309 })
21310}
21311
21312pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
21313 let mut prev_index = 0;
21314 let mut prev_codepoint: Option<char> = None;
21315 text.char_indices()
21316 .chain([(text.len(), '\0')])
21317 .filter_map(move |(index, codepoint)| {
21318 let prev_codepoint = prev_codepoint.replace(codepoint)?;
21319 let is_boundary = index == text.len()
21320 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
21321 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
21322 if is_boundary {
21323 let chunk = &text[prev_index..index];
21324 prev_index = index;
21325 Some(chunk)
21326 } else {
21327 None
21328 }
21329 })
21330}
21331
21332pub trait RangeToAnchorExt: Sized {
21333 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
21334
21335 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
21336 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
21337 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
21338 }
21339}
21340
21341impl<T: ToOffset> RangeToAnchorExt for Range<T> {
21342 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
21343 let start_offset = self.start.to_offset(snapshot);
21344 let end_offset = self.end.to_offset(snapshot);
21345 if start_offset == end_offset {
21346 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
21347 } else {
21348 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
21349 }
21350 }
21351}
21352
21353pub trait RowExt {
21354 fn as_f32(&self) -> f32;
21355
21356 fn next_row(&self) -> Self;
21357
21358 fn previous_row(&self) -> Self;
21359
21360 fn minus(&self, other: Self) -> u32;
21361}
21362
21363impl RowExt for DisplayRow {
21364 fn as_f32(&self) -> f32 {
21365 self.0 as f32
21366 }
21367
21368 fn next_row(&self) -> Self {
21369 Self(self.0 + 1)
21370 }
21371
21372 fn previous_row(&self) -> Self {
21373 Self(self.0.saturating_sub(1))
21374 }
21375
21376 fn minus(&self, other: Self) -> u32 {
21377 self.0 - other.0
21378 }
21379}
21380
21381impl RowExt for MultiBufferRow {
21382 fn as_f32(&self) -> f32 {
21383 self.0 as f32
21384 }
21385
21386 fn next_row(&self) -> Self {
21387 Self(self.0 + 1)
21388 }
21389
21390 fn previous_row(&self) -> Self {
21391 Self(self.0.saturating_sub(1))
21392 }
21393
21394 fn minus(&self, other: Self) -> u32 {
21395 self.0 - other.0
21396 }
21397}
21398
21399trait RowRangeExt {
21400 type Row;
21401
21402 fn len(&self) -> usize;
21403
21404 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
21405}
21406
21407impl RowRangeExt for Range<MultiBufferRow> {
21408 type Row = MultiBufferRow;
21409
21410 fn len(&self) -> usize {
21411 (self.end.0 - self.start.0) as usize
21412 }
21413
21414 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21415 (self.start.0..self.end.0).map(MultiBufferRow)
21416 }
21417}
21418
21419impl RowRangeExt for Range<DisplayRow> {
21420 type Row = DisplayRow;
21421
21422 fn len(&self) -> usize {
21423 (self.end.0 - self.start.0) as usize
21424 }
21425
21426 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21427 (self.start.0..self.end.0).map(DisplayRow)
21428 }
21429}
21430
21431/// If select range has more than one line, we
21432/// just point the cursor to range.start.
21433fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21434 if range.start.row == range.end.row {
21435 range
21436 } else {
21437 range.start..range.start
21438 }
21439}
21440pub struct KillRing(ClipboardItem);
21441impl Global for KillRing {}
21442
21443const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21444
21445enum BreakpointPromptEditAction {
21446 Log,
21447 Condition,
21448 HitCondition,
21449}
21450
21451struct BreakpointPromptEditor {
21452 pub(crate) prompt: Entity<Editor>,
21453 editor: WeakEntity<Editor>,
21454 breakpoint_anchor: Anchor,
21455 breakpoint: Breakpoint,
21456 edit_action: BreakpointPromptEditAction,
21457 block_ids: HashSet<CustomBlockId>,
21458 editor_margins: Arc<Mutex<EditorMargins>>,
21459 _subscriptions: Vec<Subscription>,
21460}
21461
21462impl BreakpointPromptEditor {
21463 const MAX_LINES: u8 = 4;
21464
21465 fn new(
21466 editor: WeakEntity<Editor>,
21467 breakpoint_anchor: Anchor,
21468 breakpoint: Breakpoint,
21469 edit_action: BreakpointPromptEditAction,
21470 window: &mut Window,
21471 cx: &mut Context<Self>,
21472 ) -> Self {
21473 let base_text = match edit_action {
21474 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21475 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21476 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21477 }
21478 .map(|msg| msg.to_string())
21479 .unwrap_or_default();
21480
21481 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21482 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21483
21484 let prompt = cx.new(|cx| {
21485 let mut prompt = Editor::new(
21486 EditorMode::AutoHeight {
21487 max_lines: Self::MAX_LINES as usize,
21488 },
21489 buffer,
21490 None,
21491 window,
21492 cx,
21493 );
21494 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21495 prompt.set_show_cursor_when_unfocused(false, cx);
21496 prompt.set_placeholder_text(
21497 match edit_action {
21498 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21499 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21500 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21501 },
21502 cx,
21503 );
21504
21505 prompt
21506 });
21507
21508 Self {
21509 prompt,
21510 editor,
21511 breakpoint_anchor,
21512 breakpoint,
21513 edit_action,
21514 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21515 block_ids: Default::default(),
21516 _subscriptions: vec![],
21517 }
21518 }
21519
21520 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21521 self.block_ids.extend(block_ids)
21522 }
21523
21524 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21525 if let Some(editor) = self.editor.upgrade() {
21526 let message = self
21527 .prompt
21528 .read(cx)
21529 .buffer
21530 .read(cx)
21531 .as_singleton()
21532 .expect("A multi buffer in breakpoint prompt isn't possible")
21533 .read(cx)
21534 .as_rope()
21535 .to_string();
21536
21537 editor.update(cx, |editor, cx| {
21538 editor.edit_breakpoint_at_anchor(
21539 self.breakpoint_anchor,
21540 self.breakpoint.clone(),
21541 match self.edit_action {
21542 BreakpointPromptEditAction::Log => {
21543 BreakpointEditAction::EditLogMessage(message.into())
21544 }
21545 BreakpointPromptEditAction::Condition => {
21546 BreakpointEditAction::EditCondition(message.into())
21547 }
21548 BreakpointPromptEditAction::HitCondition => {
21549 BreakpointEditAction::EditHitCondition(message.into())
21550 }
21551 },
21552 cx,
21553 );
21554
21555 editor.remove_blocks(self.block_ids.clone(), None, cx);
21556 cx.focus_self(window);
21557 });
21558 }
21559 }
21560
21561 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21562 self.editor
21563 .update(cx, |editor, cx| {
21564 editor.remove_blocks(self.block_ids.clone(), None, cx);
21565 window.focus(&editor.focus_handle);
21566 })
21567 .log_err();
21568 }
21569
21570 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21571 let settings = ThemeSettings::get_global(cx);
21572 let text_style = TextStyle {
21573 color: if self.prompt.read(cx).read_only(cx) {
21574 cx.theme().colors().text_disabled
21575 } else {
21576 cx.theme().colors().text
21577 },
21578 font_family: settings.buffer_font.family.clone(),
21579 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21580 font_size: settings.buffer_font_size(cx).into(),
21581 font_weight: settings.buffer_font.weight,
21582 line_height: relative(settings.buffer_line_height.value()),
21583 ..Default::default()
21584 };
21585 EditorElement::new(
21586 &self.prompt,
21587 EditorStyle {
21588 background: cx.theme().colors().editor_background,
21589 local_player: cx.theme().players().local(),
21590 text: text_style,
21591 ..Default::default()
21592 },
21593 )
21594 }
21595}
21596
21597impl Render for BreakpointPromptEditor {
21598 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21599 let editor_margins = *self.editor_margins.lock();
21600 let gutter_dimensions = editor_margins.gutter;
21601 h_flex()
21602 .key_context("Editor")
21603 .bg(cx.theme().colors().editor_background)
21604 .border_y_1()
21605 .border_color(cx.theme().status().info_border)
21606 .size_full()
21607 .py(window.line_height() / 2.5)
21608 .on_action(cx.listener(Self::confirm))
21609 .on_action(cx.listener(Self::cancel))
21610 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21611 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21612 }
21613}
21614
21615impl Focusable for BreakpointPromptEditor {
21616 fn focus_handle(&self, cx: &App) -> FocusHandle {
21617 self.prompt.focus_handle(cx)
21618 }
21619}
21620
21621fn all_edits_insertions_or_deletions(
21622 edits: &Vec<(Range<Anchor>, String)>,
21623 snapshot: &MultiBufferSnapshot,
21624) -> bool {
21625 let mut all_insertions = true;
21626 let mut all_deletions = true;
21627
21628 for (range, new_text) in edits.iter() {
21629 let range_is_empty = range.to_offset(&snapshot).is_empty();
21630 let text_is_empty = new_text.is_empty();
21631
21632 if range_is_empty != text_is_empty {
21633 if range_is_empty {
21634 all_deletions = false;
21635 } else {
21636 all_insertions = false;
21637 }
21638 } else {
21639 return false;
21640 }
21641
21642 if !all_insertions && !all_deletions {
21643 return false;
21644 }
21645 }
21646 all_insertions || all_deletions
21647}
21648
21649struct MissingEditPredictionKeybindingTooltip;
21650
21651impl Render for MissingEditPredictionKeybindingTooltip {
21652 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21653 ui::tooltip_container(window, cx, |container, _, cx| {
21654 container
21655 .flex_shrink_0()
21656 .max_w_80()
21657 .min_h(rems_from_px(124.))
21658 .justify_between()
21659 .child(
21660 v_flex()
21661 .flex_1()
21662 .text_ui_sm(cx)
21663 .child(Label::new("Conflict with Accept Keybinding"))
21664 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21665 )
21666 .child(
21667 h_flex()
21668 .pb_1()
21669 .gap_1()
21670 .items_end()
21671 .w_full()
21672 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21673 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21674 }))
21675 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21676 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21677 })),
21678 )
21679 })
21680 }
21681}
21682
21683#[derive(Debug, Clone, Copy, PartialEq)]
21684pub struct LineHighlight {
21685 pub background: Background,
21686 pub border: Option<gpui::Hsla>,
21687 pub include_gutter: bool,
21688 pub type_id: Option<TypeId>,
21689}
21690
21691fn render_diff_hunk_controls(
21692 row: u32,
21693 status: &DiffHunkStatus,
21694 hunk_range: Range<Anchor>,
21695 is_created_file: bool,
21696 line_height: Pixels,
21697 editor: &Entity<Editor>,
21698 _window: &mut Window,
21699 cx: &mut App,
21700) -> AnyElement {
21701 h_flex()
21702 .h(line_height)
21703 .mr_1()
21704 .gap_1()
21705 .px_0p5()
21706 .pb_1()
21707 .border_x_1()
21708 .border_b_1()
21709 .border_color(cx.theme().colors().border_variant)
21710 .rounded_b_lg()
21711 .bg(cx.theme().colors().editor_background)
21712 .gap_1()
21713 .occlude()
21714 .shadow_md()
21715 .child(if status.has_secondary_hunk() {
21716 Button::new(("stage", row as u64), "Stage")
21717 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21718 .tooltip({
21719 let focus_handle = editor.focus_handle(cx);
21720 move |window, cx| {
21721 Tooltip::for_action_in(
21722 "Stage Hunk",
21723 &::git::ToggleStaged,
21724 &focus_handle,
21725 window,
21726 cx,
21727 )
21728 }
21729 })
21730 .on_click({
21731 let editor = editor.clone();
21732 move |_event, _window, cx| {
21733 editor.update(cx, |editor, cx| {
21734 editor.stage_or_unstage_diff_hunks(
21735 true,
21736 vec![hunk_range.start..hunk_range.start],
21737 cx,
21738 );
21739 });
21740 }
21741 })
21742 } else {
21743 Button::new(("unstage", row as u64), "Unstage")
21744 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21745 .tooltip({
21746 let focus_handle = editor.focus_handle(cx);
21747 move |window, cx| {
21748 Tooltip::for_action_in(
21749 "Unstage Hunk",
21750 &::git::ToggleStaged,
21751 &focus_handle,
21752 window,
21753 cx,
21754 )
21755 }
21756 })
21757 .on_click({
21758 let editor = editor.clone();
21759 move |_event, _window, cx| {
21760 editor.update(cx, |editor, cx| {
21761 editor.stage_or_unstage_diff_hunks(
21762 false,
21763 vec![hunk_range.start..hunk_range.start],
21764 cx,
21765 );
21766 });
21767 }
21768 })
21769 })
21770 .child(
21771 Button::new(("restore", row as u64), "Restore")
21772 .tooltip({
21773 let focus_handle = editor.focus_handle(cx);
21774 move |window, cx| {
21775 Tooltip::for_action_in(
21776 "Restore Hunk",
21777 &::git::Restore,
21778 &focus_handle,
21779 window,
21780 cx,
21781 )
21782 }
21783 })
21784 .on_click({
21785 let editor = editor.clone();
21786 move |_event, window, cx| {
21787 editor.update(cx, |editor, cx| {
21788 let snapshot = editor.snapshot(window, cx);
21789 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
21790 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
21791 });
21792 }
21793 })
21794 .disabled(is_created_file),
21795 )
21796 .when(
21797 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
21798 |el| {
21799 el.child(
21800 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
21801 .shape(IconButtonShape::Square)
21802 .icon_size(IconSize::Small)
21803 // .disabled(!has_multiple_hunks)
21804 .tooltip({
21805 let focus_handle = editor.focus_handle(cx);
21806 move |window, cx| {
21807 Tooltip::for_action_in(
21808 "Next Hunk",
21809 &GoToHunk,
21810 &focus_handle,
21811 window,
21812 cx,
21813 )
21814 }
21815 })
21816 .on_click({
21817 let editor = editor.clone();
21818 move |_event, window, cx| {
21819 editor.update(cx, |editor, cx| {
21820 let snapshot = editor.snapshot(window, cx);
21821 let position =
21822 hunk_range.end.to_point(&snapshot.buffer_snapshot);
21823 editor.go_to_hunk_before_or_after_position(
21824 &snapshot,
21825 position,
21826 Direction::Next,
21827 window,
21828 cx,
21829 );
21830 editor.expand_selected_diff_hunks(cx);
21831 });
21832 }
21833 }),
21834 )
21835 .child(
21836 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
21837 .shape(IconButtonShape::Square)
21838 .icon_size(IconSize::Small)
21839 // .disabled(!has_multiple_hunks)
21840 .tooltip({
21841 let focus_handle = editor.focus_handle(cx);
21842 move |window, cx| {
21843 Tooltip::for_action_in(
21844 "Previous Hunk",
21845 &GoToPreviousHunk,
21846 &focus_handle,
21847 window,
21848 cx,
21849 )
21850 }
21851 })
21852 .on_click({
21853 let editor = editor.clone();
21854 move |_event, window, cx| {
21855 editor.update(cx, |editor, cx| {
21856 let snapshot = editor.snapshot(window, cx);
21857 let point =
21858 hunk_range.start.to_point(&snapshot.buffer_snapshot);
21859 editor.go_to_hunk_before_or_after_position(
21860 &snapshot,
21861 point,
21862 Direction::Prev,
21863 window,
21864 cx,
21865 );
21866 editor.expand_selected_diff_hunks(cx);
21867 });
21868 }
21869 }),
21870 )
21871 },
21872 )
21873 .into_any_element()
21874}