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_colors;
33mod lsp_ext;
34mod mouse_context_menu;
35pub mod movement;
36mod persistence;
37mod proposed_changes_editor;
38mod rust_analyzer_ext;
39pub mod scroll;
40mod selections_collection;
41pub mod tasks;
42
43#[cfg(test)]
44mod code_completion_tests;
45#[cfg(test)]
46mod editor_tests;
47#[cfg(test)]
48mod inline_completion_tests;
49mod signature_help;
50#[cfg(any(test, feature = "test-support"))]
51pub mod test;
52
53pub(crate) use actions::*;
54pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
55use aho_corasick::AhoCorasick;
56use anyhow::{Context as _, Result, anyhow};
57use blink_manager::BlinkManager;
58use buffer_diff::DiffHunkStatus;
59use client::{Collaborator, ParticipantIndex};
60use clock::{AGENT_REPLICA_ID, ReplicaId};
61use collections::{BTreeMap, HashMap, HashSet, VecDeque};
62use convert_case::{Case, Casing};
63use dap::TelemetrySpawnLocation;
64use display_map::*;
65pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
66pub use editor_settings::{
67 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
68 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowScrollbar,
69};
70use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
71pub use editor_settings_controls::*;
72use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
73pub use element::{
74 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
75};
76use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
77use futures::{
78 FutureExt, StreamExt as _,
79 future::{self, Shared, join},
80 stream::FuturesUnordered,
81};
82use fuzzy::{StringMatch, StringMatchCandidate};
83use lsp_colors::LspColorData;
84
85use ::git::blame::BlameEntry;
86use ::git::{Restore, blame::ParsedCommitMessage};
87use code_context_menus::{
88 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
89 CompletionsMenu, ContextMenuOrigin,
90};
91use git::blame::{GitBlame, GlobalBlameRenderer};
92use gpui::{
93 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
94 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
95 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
96 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
97 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
98 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
99 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
100 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
101};
102use highlight_matching_bracket::refresh_matching_bracket_highlights;
103use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
104pub use hover_popover::hover_markdown_style;
105use hover_popover::{HoverState, hide_hover};
106use indent_guides::ActiveIndentGuidesState;
107use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
108pub use inline_completion::Direction;
109use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
110pub use items::MAX_TAB_TITLE_LEN;
111use itertools::Itertools;
112use language::{
113 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
114 CursorShape, DiagnosticEntry, DiffOptions, DocumentationConfig, EditPredictionsMode,
115 EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point,
116 Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
117 language_settings::{
118 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
119 all_language_settings, language_settings,
120 },
121 point_from_lsp, text_diff_with_options,
122};
123use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
124use linked_editing_ranges::refresh_linked_ranges;
125use markdown::Markdown;
126use mouse_context_menu::MouseContextMenu;
127use persistence::DB;
128use project::{
129 BreakpointWithPosition, CompletionResponse, ProjectPath,
130 debugger::{
131 breakpoint_store::{
132 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
133 BreakpointStoreEvent,
134 },
135 session::{Session, SessionEvent},
136 },
137 project_settings::DiagnosticSeverity,
138};
139
140pub use git::blame::BlameRenderer;
141pub use proposed_changes_editor::{
142 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
143};
144use std::{cell::OnceCell, iter::Peekable, ops::Not};
145use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
146
147pub use lsp::CompletionContext;
148use lsp::{
149 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
150 LanguageServerId, LanguageServerName,
151};
152
153use language::BufferSnapshot;
154pub use lsp_ext::lsp_tasks;
155use movement::TextLayoutDetails;
156pub use multi_buffer::{
157 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
158 RowInfo, ToOffset, ToPoint,
159};
160use multi_buffer::{
161 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
162 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
163};
164use parking_lot::Mutex;
165use project::{
166 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
167 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
168 TaskSourceKind,
169 debugger::breakpoint_store::Breakpoint,
170 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
171 project_settings::{GitGutterSetting, ProjectSettings},
172};
173use rand::prelude::*;
174use rpc::{ErrorExt, proto::*};
175use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
176use selections_collection::{
177 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
178};
179use serde::{Deserialize, Serialize};
180use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
181use smallvec::{SmallVec, smallvec};
182use snippet::Snippet;
183use std::sync::Arc;
184use std::{
185 any::TypeId,
186 borrow::Cow,
187 cell::RefCell,
188 cmp::{self, Ordering, Reverse},
189 mem,
190 num::NonZeroU32,
191 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
192 path::{Path, PathBuf},
193 rc::Rc,
194 time::{Duration, Instant},
195};
196pub use sum_tree::Bias;
197use sum_tree::TreeMap;
198use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
199use theme::{
200 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
201 observe_buffer_font_size_adjustment,
202};
203use ui::{
204 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
205 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
206};
207use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
208use workspace::{
209 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
210 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
211 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
212 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
213 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
214 searchable::SearchEvent,
215};
216
217use crate::{
218 code_context_menus::CompletionsMenuSource,
219 hover_links::{find_url, find_url_from_range},
220};
221use crate::{
222 editor_settings::MultiCursorModifier,
223 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
224};
225
226pub const FILE_HEADER_HEIGHT: u32 = 2;
227pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
228pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
229const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
230const MAX_LINE_LEN: usize = 1024;
231const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
232const MAX_SELECTION_HISTORY_LEN: usize = 1024;
233pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
234#[doc(hidden)]
235pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
236const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
237
238pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
239pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
240pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
241
242pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
243pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
244pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
245
246pub type RenderDiffHunkControlsFn = Arc<
247 dyn Fn(
248 u32,
249 &DiffHunkStatus,
250 Range<Anchor>,
251 bool,
252 Pixels,
253 &Entity<Editor>,
254 &mut Window,
255 &mut App,
256 ) -> AnyElement,
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 DebuggerValue(usize),
279 // LSP
280 Hint(usize),
281 Color(usize),
282}
283
284impl InlayId {
285 fn id(&self) -> usize {
286 match self {
287 Self::InlineCompletion(id) => *id,
288 Self::DebuggerValue(id) => *id,
289 Self::Hint(id) => *id,
290 Self::Color(id) => *id,
291 }
292 }
293}
294
295pub enum ActiveDebugLine {}
296pub enum DebugStackFrameLine {}
297enum DocumentHighlightRead {}
298enum DocumentHighlightWrite {}
299enum InputComposition {}
300pub enum PendingInput {}
301enum SelectedTextHighlight {}
302
303pub enum ConflictsOuter {}
304pub enum ConflictsOurs {}
305pub enum ConflictsTheirs {}
306pub enum ConflictsOursMarker {}
307pub enum ConflictsTheirsMarker {}
308
309#[derive(Debug, Copy, Clone, PartialEq, Eq)]
310pub enum Navigated {
311 Yes,
312 No,
313}
314
315impl Navigated {
316 pub fn from_bool(yes: bool) -> Navigated {
317 if yes { Navigated::Yes } else { Navigated::No }
318 }
319}
320
321#[derive(Debug, Clone, PartialEq, Eq)]
322enum DisplayDiffHunk {
323 Folded {
324 display_row: DisplayRow,
325 },
326 Unfolded {
327 is_created_file: bool,
328 diff_base_byte_range: Range<usize>,
329 display_row_range: Range<DisplayRow>,
330 multi_buffer_range: Range<Anchor>,
331 status: DiffHunkStatus,
332 },
333}
334
335pub enum HideMouseCursorOrigin {
336 TypingAction,
337 MovementAction,
338}
339
340pub fn init_settings(cx: &mut App) {
341 EditorSettings::register(cx);
342}
343
344pub fn init(cx: &mut App) {
345 init_settings(cx);
346
347 cx.set_global(GlobalBlameRenderer(Arc::new(())));
348
349 workspace::register_project_item::<Editor>(cx);
350 workspace::FollowableViewRegistry::register::<Editor>(cx);
351 workspace::register_serializable_item::<Editor>(cx);
352
353 cx.observe_new(
354 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
355 workspace.register_action(Editor::new_file);
356 workspace.register_action(Editor::new_file_vertical);
357 workspace.register_action(Editor::new_file_horizontal);
358 workspace.register_action(Editor::cancel_language_server_work);
359 },
360 )
361 .detach();
362
363 cx.on_action(move |_: &workspace::NewFile, cx| {
364 let app_state = workspace::AppState::global(cx);
365 if let Some(app_state) = app_state.upgrade() {
366 workspace::open_new(
367 Default::default(),
368 app_state,
369 cx,
370 |workspace, window, cx| {
371 Editor::new_file(workspace, &Default::default(), window, cx)
372 },
373 )
374 .detach();
375 }
376 });
377 cx.on_action(move |_: &workspace::NewWindow, cx| {
378 let app_state = workspace::AppState::global(cx);
379 if let Some(app_state) = app_state.upgrade() {
380 workspace::open_new(
381 Default::default(),
382 app_state,
383 cx,
384 |workspace, window, cx| {
385 cx.activate(true);
386 Editor::new_file(workspace, &Default::default(), window, cx)
387 },
388 )
389 .detach();
390 }
391 });
392}
393
394pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
395 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
396}
397
398pub trait DiagnosticRenderer {
399 fn render_group(
400 &self,
401 diagnostic_group: Vec<DiagnosticEntry<Point>>,
402 buffer_id: BufferId,
403 snapshot: EditorSnapshot,
404 editor: WeakEntity<Editor>,
405 cx: &mut App,
406 ) -> Vec<BlockProperties<Anchor>>;
407
408 fn render_hover(
409 &self,
410 diagnostic_group: Vec<DiagnosticEntry<Point>>,
411 range: Range<Point>,
412 buffer_id: BufferId,
413 cx: &mut App,
414 ) -> Option<Entity<markdown::Markdown>>;
415
416 fn open_link(
417 &self,
418 editor: &mut Editor,
419 link: SharedString,
420 window: &mut Window,
421 cx: &mut Context<Editor>,
422 );
423}
424
425pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
426
427impl GlobalDiagnosticRenderer {
428 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
429 cx.try_global::<Self>().map(|g| g.0.clone())
430 }
431}
432
433impl gpui::Global for GlobalDiagnosticRenderer {}
434pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
435 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
436}
437
438pub struct SearchWithinRange;
439
440trait InvalidationRegion {
441 fn ranges(&self) -> &[Range<Anchor>];
442}
443
444#[derive(Clone, Debug, PartialEq)]
445pub enum SelectPhase {
446 Begin {
447 position: DisplayPoint,
448 add: bool,
449 click_count: usize,
450 },
451 BeginColumnar {
452 position: DisplayPoint,
453 reset: bool,
454 mode: ColumnarMode,
455 goal_column: u32,
456 },
457 Extend {
458 position: DisplayPoint,
459 click_count: usize,
460 },
461 Update {
462 position: DisplayPoint,
463 goal_column: u32,
464 scroll_delta: gpui::Point<f32>,
465 },
466 End,
467}
468
469#[derive(Clone, Debug, PartialEq)]
470pub enum ColumnarMode {
471 FromMouse,
472 FromSelection,
473}
474
475#[derive(Clone, Debug)]
476pub enum SelectMode {
477 Character,
478 Word(Range<Anchor>),
479 Line(Range<Anchor>),
480 All,
481}
482
483#[derive(Clone, PartialEq, Eq, Debug)]
484pub enum EditorMode {
485 SingleLine {
486 auto_width: bool,
487 },
488 AutoHeight {
489 min_lines: usize,
490 max_lines: usize,
491 },
492 Full {
493 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
494 scale_ui_elements_with_buffer_font_size: bool,
495 /// When set to `true`, the editor will render a background for the active line.
496 show_active_line_background: bool,
497 /// When set to `true`, the editor's height will be determined by its content.
498 sized_by_content: bool,
499 },
500 Minimap {
501 parent: WeakEntity<Editor>,
502 },
503}
504
505impl EditorMode {
506 pub fn full() -> Self {
507 Self::Full {
508 scale_ui_elements_with_buffer_font_size: true,
509 show_active_line_background: true,
510 sized_by_content: false,
511 }
512 }
513
514 pub fn is_full(&self) -> bool {
515 matches!(self, Self::Full { .. })
516 }
517
518 fn is_minimap(&self) -> bool {
519 matches!(self, Self::Minimap { .. })
520 }
521}
522
523#[derive(Copy, Clone, Debug)]
524pub enum SoftWrap {
525 /// Prefer not to wrap at all.
526 ///
527 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
528 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
529 GitDiff,
530 /// Prefer a single line generally, unless an overly long line is encountered.
531 None,
532 /// Soft wrap lines that exceed the editor width.
533 EditorWidth,
534 /// Soft wrap lines at the preferred line length.
535 Column(u32),
536 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
537 Bounded(u32),
538}
539
540#[derive(Clone)]
541pub struct EditorStyle {
542 pub background: Hsla,
543 pub local_player: PlayerColor,
544 pub text: TextStyle,
545 pub scrollbar_width: Pixels,
546 pub syntax: Arc<SyntaxTheme>,
547 pub status: StatusColors,
548 pub inlay_hints_style: HighlightStyle,
549 pub inline_completion_styles: InlineCompletionStyles,
550 pub unnecessary_code_fade: f32,
551 pub show_underlines: bool,
552}
553
554impl Default for EditorStyle {
555 fn default() -> Self {
556 Self {
557 background: Hsla::default(),
558 local_player: PlayerColor::default(),
559 text: TextStyle::default(),
560 scrollbar_width: Pixels::default(),
561 syntax: Default::default(),
562 // HACK: Status colors don't have a real default.
563 // We should look into removing the status colors from the editor
564 // style and retrieve them directly from the theme.
565 status: StatusColors::dark(),
566 inlay_hints_style: HighlightStyle::default(),
567 inline_completion_styles: InlineCompletionStyles {
568 insertion: HighlightStyle::default(),
569 whitespace: HighlightStyle::default(),
570 },
571 unnecessary_code_fade: Default::default(),
572 show_underlines: true,
573 }
574 }
575}
576
577pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
578 let show_background = language_settings::language_settings(None, None, cx)
579 .inlay_hints
580 .show_background;
581
582 HighlightStyle {
583 color: Some(cx.theme().status().hint),
584 background_color: show_background.then(|| cx.theme().status().hint_background),
585 ..HighlightStyle::default()
586 }
587}
588
589pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
590 InlineCompletionStyles {
591 insertion: HighlightStyle {
592 color: Some(cx.theme().status().predictive),
593 ..HighlightStyle::default()
594 },
595 whitespace: HighlightStyle {
596 background_color: Some(cx.theme().status().created_background),
597 ..HighlightStyle::default()
598 },
599 }
600}
601
602type CompletionId = usize;
603
604pub(crate) enum EditDisplayMode {
605 TabAccept,
606 DiffPopover,
607 Inline,
608}
609
610enum InlineCompletion {
611 Edit {
612 edits: Vec<(Range<Anchor>, String)>,
613 edit_preview: Option<EditPreview>,
614 display_mode: EditDisplayMode,
615 snapshot: BufferSnapshot,
616 },
617 Move {
618 target: Anchor,
619 snapshot: BufferSnapshot,
620 },
621}
622
623struct InlineCompletionState {
624 inlay_ids: Vec<InlayId>,
625 completion: InlineCompletion,
626 completion_id: Option<SharedString>,
627 invalidation_range: Range<Anchor>,
628}
629
630enum EditPredictionSettings {
631 Disabled,
632 Enabled {
633 show_in_menu: bool,
634 preview_requires_modifier: bool,
635 },
636}
637
638enum InlineCompletionHighlight {}
639
640#[derive(Debug, Clone)]
641struct InlineDiagnostic {
642 message: SharedString,
643 group_id: usize,
644 is_primary: bool,
645 start: Point,
646 severity: lsp::DiagnosticSeverity,
647}
648
649pub enum MenuInlineCompletionsPolicy {
650 Never,
651 ByProvider,
652}
653
654pub enum EditPredictionPreview {
655 /// Modifier is not pressed
656 Inactive { released_too_fast: bool },
657 /// Modifier pressed
658 Active {
659 since: Instant,
660 previous_scroll_position: Option<ScrollAnchor>,
661 },
662}
663
664impl EditPredictionPreview {
665 pub fn released_too_fast(&self) -> bool {
666 match self {
667 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
668 EditPredictionPreview::Active { .. } => false,
669 }
670 }
671
672 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
673 if let EditPredictionPreview::Active {
674 previous_scroll_position,
675 ..
676 } = self
677 {
678 *previous_scroll_position = scroll_position;
679 }
680 }
681}
682
683pub struct ContextMenuOptions {
684 pub min_entries_visible: usize,
685 pub max_entries_visible: usize,
686 pub placement: Option<ContextMenuPlacement>,
687}
688
689#[derive(Debug, Clone, PartialEq, Eq)]
690pub enum ContextMenuPlacement {
691 Above,
692 Below,
693}
694
695#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
696struct EditorActionId(usize);
697
698impl EditorActionId {
699 pub fn post_inc(&mut self) -> Self {
700 let answer = self.0;
701
702 *self = Self(answer + 1);
703
704 Self(answer)
705 }
706}
707
708// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
709// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
710
711#[derive(Clone)]
712pub struct BackgroundHighlight {
713 pub range: Range<Anchor>,
714 pub color_fetcher: fn(&Theme) -> Hsla,
715}
716
717type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
718
719#[derive(Default)]
720struct ScrollbarMarkerState {
721 scrollbar_size: Size<Pixels>,
722 dirty: bool,
723 markers: Arc<[PaintQuad]>,
724 pending_refresh: Option<Task<Result<()>>>,
725}
726
727impl ScrollbarMarkerState {
728 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
729 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
730 }
731}
732
733#[derive(Clone, Copy, PartialEq, Eq)]
734pub enum MinimapVisibility {
735 Disabled,
736 Enabled {
737 /// The configuration currently present in the users settings.
738 setting_configuration: bool,
739 /// Whether to override the currently set visibility from the users setting.
740 toggle_override: bool,
741 },
742}
743
744impl MinimapVisibility {
745 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
746 if mode.is_full() {
747 Self::Enabled {
748 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
749 toggle_override: false,
750 }
751 } else {
752 Self::Disabled
753 }
754 }
755
756 fn hidden(&self) -> Self {
757 match *self {
758 Self::Enabled {
759 setting_configuration,
760 ..
761 } => Self::Enabled {
762 setting_configuration,
763 toggle_override: setting_configuration,
764 },
765 Self::Disabled => Self::Disabled,
766 }
767 }
768
769 fn disabled(&self) -> bool {
770 match *self {
771 Self::Disabled => true,
772 _ => false,
773 }
774 }
775
776 fn settings_visibility(&self) -> bool {
777 match *self {
778 Self::Enabled {
779 setting_configuration,
780 ..
781 } => setting_configuration,
782 _ => false,
783 }
784 }
785
786 fn visible(&self) -> bool {
787 match *self {
788 Self::Enabled {
789 setting_configuration,
790 toggle_override,
791 } => setting_configuration ^ toggle_override,
792 _ => false,
793 }
794 }
795
796 fn toggle_visibility(&self) -> Self {
797 match *self {
798 Self::Enabled {
799 toggle_override,
800 setting_configuration,
801 } => Self::Enabled {
802 setting_configuration,
803 toggle_override: !toggle_override,
804 },
805 Self::Disabled => Self::Disabled,
806 }
807 }
808}
809
810#[derive(Clone, Debug)]
811struct RunnableTasks {
812 templates: Vec<(TaskSourceKind, TaskTemplate)>,
813 offset: multi_buffer::Anchor,
814 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
815 column: u32,
816 // Values of all named captures, including those starting with '_'
817 extra_variables: HashMap<String, String>,
818 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
819 context_range: Range<BufferOffset>,
820}
821
822impl RunnableTasks {
823 fn resolve<'a>(
824 &'a self,
825 cx: &'a task::TaskContext,
826 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
827 self.templates.iter().filter_map(|(kind, template)| {
828 template
829 .resolve_task(&kind.to_id_base(), cx)
830 .map(|task| (kind.clone(), task))
831 })
832 }
833}
834
835#[derive(Clone)]
836pub struct ResolvedTasks {
837 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
838 position: Anchor,
839}
840
841#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
842struct BufferOffset(usize);
843
844// Addons allow storing per-editor state in other crates (e.g. Vim)
845pub trait Addon: 'static {
846 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
847
848 fn render_buffer_header_controls(
849 &self,
850 _: &ExcerptInfo,
851 _: &Window,
852 _: &App,
853 ) -> Option<AnyElement> {
854 None
855 }
856
857 fn to_any(&self) -> &dyn std::any::Any;
858
859 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
860 None
861 }
862}
863
864/// A set of caret positions, registered when the editor was edited.
865pub struct ChangeList {
866 changes: Vec<Vec<Anchor>>,
867 /// Currently "selected" change.
868 position: Option<usize>,
869}
870
871impl ChangeList {
872 pub fn new() -> Self {
873 Self {
874 changes: Vec::new(),
875 position: None,
876 }
877 }
878
879 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
880 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
881 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
882 if self.changes.is_empty() {
883 return None;
884 }
885
886 let prev = self.position.unwrap_or(self.changes.len());
887 let next = if direction == Direction::Prev {
888 prev.saturating_sub(count)
889 } else {
890 (prev + count).min(self.changes.len() - 1)
891 };
892 self.position = Some(next);
893 self.changes.get(next).map(|anchors| anchors.as_slice())
894 }
895
896 /// Adds a new change to the list, resetting the change list position.
897 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
898 self.position.take();
899 if pop_state {
900 self.changes.pop();
901 }
902 self.changes.push(new_positions.clone());
903 }
904
905 pub fn last(&self) -> Option<&[Anchor]> {
906 self.changes.last().map(|anchors| anchors.as_slice())
907 }
908}
909
910#[derive(Clone)]
911struct InlineBlamePopoverState {
912 scroll_handle: ScrollHandle,
913 commit_message: Option<ParsedCommitMessage>,
914 markdown: Entity<Markdown>,
915}
916
917struct InlineBlamePopover {
918 position: gpui::Point<Pixels>,
919 hide_task: Option<Task<()>>,
920 popover_bounds: Option<Bounds<Pixels>>,
921 popover_state: InlineBlamePopoverState,
922}
923
924enum SelectionDragState {
925 /// State when no drag related activity is detected.
926 None,
927 /// State when the mouse is down on a selection that is about to be dragged.
928 ReadyToDrag {
929 selection: Selection<Anchor>,
930 click_position: gpui::Point<Pixels>,
931 mouse_down_time: Instant,
932 },
933 /// State when the mouse is dragging the selection in the editor.
934 Dragging {
935 selection: Selection<Anchor>,
936 drop_cursor: Selection<Anchor>,
937 hide_drop_cursor: bool,
938 },
939}
940
941enum ColumnarSelectionState {
942 FromMouse {
943 selection_tail: Anchor,
944 display_point: Option<DisplayPoint>,
945 },
946 FromSelection {
947 selection_tail: Anchor,
948 },
949}
950
951/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
952/// a breakpoint on them.
953#[derive(Clone, Copy, Debug, PartialEq, Eq)]
954struct PhantomBreakpointIndicator {
955 display_row: DisplayRow,
956 /// There's a small debounce between hovering over the line and showing the indicator.
957 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
958 is_active: bool,
959 collides_with_existing_breakpoint: bool,
960}
961
962/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
963///
964/// See the [module level documentation](self) for more information.
965pub struct Editor {
966 focus_handle: FocusHandle,
967 last_focused_descendant: Option<WeakFocusHandle>,
968 /// The text buffer being edited
969 buffer: Entity<MultiBuffer>,
970 /// Map of how text in the buffer should be displayed.
971 /// Handles soft wraps, folds, fake inlay text insertions, etc.
972 pub display_map: Entity<DisplayMap>,
973 pub selections: SelectionsCollection,
974 pub scroll_manager: ScrollManager,
975 /// When inline assist editors are linked, they all render cursors because
976 /// typing enters text into each of them, even the ones that aren't focused.
977 pub(crate) show_cursor_when_unfocused: bool,
978 columnar_selection_state: Option<ColumnarSelectionState>,
979 add_selections_state: Option<AddSelectionsState>,
980 select_next_state: Option<SelectNextState>,
981 select_prev_state: Option<SelectNextState>,
982 selection_history: SelectionHistory,
983 defer_selection_effects: bool,
984 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
985 autoclose_regions: Vec<AutocloseRegion>,
986 snippet_stack: InvalidationStack<SnippetState>,
987 select_syntax_node_history: SelectSyntaxNodeHistory,
988 ime_transaction: Option<TransactionId>,
989 pub diagnostics_max_severity: DiagnosticSeverity,
990 active_diagnostics: ActiveDiagnostic,
991 show_inline_diagnostics: bool,
992 inline_diagnostics_update: Task<()>,
993 inline_diagnostics_enabled: bool,
994 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
995 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
996 hard_wrap: Option<usize>,
997
998 // TODO: make this a access method
999 pub project: Option<Entity<Project>>,
1000 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1001 completion_provider: Option<Rc<dyn CompletionProvider>>,
1002 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1003 blink_manager: Entity<BlinkManager>,
1004 show_cursor_names: bool,
1005 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1006 pub show_local_selections: bool,
1007 mode: EditorMode,
1008 show_breadcrumbs: bool,
1009 show_gutter: bool,
1010 show_scrollbars: ScrollbarAxes,
1011 minimap_visibility: MinimapVisibility,
1012 offset_content: bool,
1013 disable_expand_excerpt_buttons: bool,
1014 show_line_numbers: Option<bool>,
1015 use_relative_line_numbers: Option<bool>,
1016 show_git_diff_gutter: Option<bool>,
1017 show_code_actions: Option<bool>,
1018 show_runnables: Option<bool>,
1019 show_breakpoints: Option<bool>,
1020 show_wrap_guides: Option<bool>,
1021 show_indent_guides: Option<bool>,
1022 placeholder_text: Option<Arc<str>>,
1023 highlight_order: usize,
1024 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1025 background_highlights: TreeMap<TypeId, Vec<BackgroundHighlight>>,
1026 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1027 scrollbar_marker_state: ScrollbarMarkerState,
1028 active_indent_guides_state: ActiveIndentGuidesState,
1029 nav_history: Option<ItemNavHistory>,
1030 context_menu: RefCell<Option<CodeContextMenu>>,
1031 context_menu_options: Option<ContextMenuOptions>,
1032 mouse_context_menu: Option<MouseContextMenu>,
1033 completion_tasks: Vec<(CompletionId, Task<()>)>,
1034 inline_blame_popover: Option<InlineBlamePopover>,
1035 inline_blame_popover_show_task: Option<Task<()>>,
1036 signature_help_state: SignatureHelpState,
1037 auto_signature_help: Option<bool>,
1038 find_all_references_task_sources: Vec<Anchor>,
1039 next_completion_id: CompletionId,
1040 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1041 code_actions_task: Option<Task<Result<()>>>,
1042 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1043 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1044 document_highlights_task: Option<Task<()>>,
1045 linked_editing_range_task: Option<Task<Option<()>>>,
1046 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1047 pending_rename: Option<RenameState>,
1048 searchable: bool,
1049 cursor_shape: CursorShape,
1050 current_line_highlight: Option<CurrentLineHighlight>,
1051 collapse_matches: bool,
1052 autoindent_mode: Option<AutoindentMode>,
1053 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1054 input_enabled: bool,
1055 use_modal_editing: bool,
1056 read_only: bool,
1057 leader_id: Option<CollaboratorId>,
1058 remote_id: Option<ViewId>,
1059 pub hover_state: HoverState,
1060 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1061 gutter_hovered: bool,
1062 hovered_link_state: Option<HoveredLinkState>,
1063 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1064 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1065 active_inline_completion: Option<InlineCompletionState>,
1066 /// Used to prevent flickering as the user types while the menu is open
1067 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1068 edit_prediction_settings: EditPredictionSettings,
1069 inline_completions_hidden_for_vim_mode: bool,
1070 show_inline_completions_override: Option<bool>,
1071 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1072 edit_prediction_preview: EditPredictionPreview,
1073 edit_prediction_indent_conflict: bool,
1074 edit_prediction_requires_modifier_in_indent_conflict: bool,
1075 inlay_hint_cache: InlayHintCache,
1076 next_inlay_id: usize,
1077 _subscriptions: Vec<Subscription>,
1078 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1079 gutter_dimensions: GutterDimensions,
1080 style: Option<EditorStyle>,
1081 text_style_refinement: Option<TextStyleRefinement>,
1082 next_editor_action_id: EditorActionId,
1083 editor_actions: Rc<
1084 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1085 >,
1086 use_autoclose: bool,
1087 use_auto_surround: bool,
1088 auto_replace_emoji_shortcode: bool,
1089 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1090 show_git_blame_gutter: bool,
1091 show_git_blame_inline: bool,
1092 show_git_blame_inline_delay_task: Option<Task<()>>,
1093 git_blame_inline_enabled: bool,
1094 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1095 serialize_dirty_buffers: bool,
1096 show_selection_menu: Option<bool>,
1097 blame: Option<Entity<GitBlame>>,
1098 blame_subscription: Option<Subscription>,
1099 custom_context_menu: Option<
1100 Box<
1101 dyn 'static
1102 + Fn(
1103 &mut Self,
1104 DisplayPoint,
1105 &mut Window,
1106 &mut Context<Self>,
1107 ) -> Option<Entity<ui::ContextMenu>>,
1108 >,
1109 >,
1110 last_bounds: Option<Bounds<Pixels>>,
1111 last_position_map: Option<Rc<PositionMap>>,
1112 expect_bounds_change: Option<Bounds<Pixels>>,
1113 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1114 tasks_update_task: Option<Task<()>>,
1115 breakpoint_store: Option<Entity<BreakpointStore>>,
1116 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1117 hovered_diff_hunk_row: Option<DisplayRow>,
1118 pull_diagnostics_task: Task<()>,
1119 in_project_search: bool,
1120 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1121 breadcrumb_header: Option<String>,
1122 focused_block: Option<FocusedBlock>,
1123 next_scroll_position: NextScrollCursorCenterTopBottom,
1124 addons: HashMap<TypeId, Box<dyn Addon>>,
1125 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1126 load_diff_task: Option<Shared<Task<()>>>,
1127 /// Whether we are temporarily displaying a diff other than git's
1128 temporary_diff_override: bool,
1129 selection_mark_mode: bool,
1130 toggle_fold_multiple_buffers: Task<()>,
1131 _scroll_cursor_center_top_bottom_task: Task<()>,
1132 serialize_selections: Task<()>,
1133 serialize_folds: Task<()>,
1134 mouse_cursor_hidden: bool,
1135 minimap: Option<Entity<Self>>,
1136 hide_mouse_mode: HideMouseMode,
1137 pub change_list: ChangeList,
1138 inline_value_cache: InlineValueCache,
1139 selection_drag_state: SelectionDragState,
1140 drag_and_drop_selection_enabled: bool,
1141 next_color_inlay_id: usize,
1142 colors: Option<LspColorData>,
1143}
1144
1145#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1146enum NextScrollCursorCenterTopBottom {
1147 #[default]
1148 Center,
1149 Top,
1150 Bottom,
1151}
1152
1153impl NextScrollCursorCenterTopBottom {
1154 fn next(&self) -> Self {
1155 match self {
1156 Self::Center => Self::Top,
1157 Self::Top => Self::Bottom,
1158 Self::Bottom => Self::Center,
1159 }
1160 }
1161}
1162
1163#[derive(Clone)]
1164pub struct EditorSnapshot {
1165 pub mode: EditorMode,
1166 show_gutter: bool,
1167 show_line_numbers: Option<bool>,
1168 show_git_diff_gutter: Option<bool>,
1169 show_code_actions: Option<bool>,
1170 show_runnables: Option<bool>,
1171 show_breakpoints: Option<bool>,
1172 git_blame_gutter_max_author_length: Option<usize>,
1173 pub display_snapshot: DisplaySnapshot,
1174 pub placeholder_text: Option<Arc<str>>,
1175 is_focused: bool,
1176 scroll_anchor: ScrollAnchor,
1177 ongoing_scroll: OngoingScroll,
1178 current_line_highlight: CurrentLineHighlight,
1179 gutter_hovered: bool,
1180}
1181
1182#[derive(Default, Debug, Clone, Copy)]
1183pub struct GutterDimensions {
1184 pub left_padding: Pixels,
1185 pub right_padding: Pixels,
1186 pub width: Pixels,
1187 pub margin: Pixels,
1188 pub git_blame_entries_width: Option<Pixels>,
1189}
1190
1191impl GutterDimensions {
1192 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1193 Self {
1194 margin: Self::default_gutter_margin(font_id, font_size, cx),
1195 ..Default::default()
1196 }
1197 }
1198
1199 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1200 -cx.text_system().descent(font_id, font_size)
1201 }
1202 /// The full width of the space taken up by the gutter.
1203 pub fn full_width(&self) -> Pixels {
1204 self.margin + self.width
1205 }
1206
1207 /// The width of the space reserved for the fold indicators,
1208 /// use alongside 'justify_end' and `gutter_width` to
1209 /// right align content with the line numbers
1210 pub fn fold_area_width(&self) -> Pixels {
1211 self.margin + self.right_padding
1212 }
1213}
1214
1215#[derive(Debug)]
1216pub struct RemoteSelection {
1217 pub replica_id: ReplicaId,
1218 pub selection: Selection<Anchor>,
1219 pub cursor_shape: CursorShape,
1220 pub collaborator_id: CollaboratorId,
1221 pub line_mode: bool,
1222 pub user_name: Option<SharedString>,
1223 pub color: PlayerColor,
1224}
1225
1226#[derive(Clone, Debug)]
1227struct SelectionHistoryEntry {
1228 selections: Arc<[Selection<Anchor>]>,
1229 select_next_state: Option<SelectNextState>,
1230 select_prev_state: Option<SelectNextState>,
1231 add_selections_state: Option<AddSelectionsState>,
1232}
1233
1234#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1235enum SelectionHistoryMode {
1236 Normal,
1237 Undoing,
1238 Redoing,
1239 Skipping,
1240}
1241
1242#[derive(Clone, PartialEq, Eq, Hash)]
1243struct HoveredCursor {
1244 replica_id: u16,
1245 selection_id: usize,
1246}
1247
1248impl Default for SelectionHistoryMode {
1249 fn default() -> Self {
1250 Self::Normal
1251 }
1252}
1253
1254#[derive(Debug)]
1255pub struct SelectionEffects {
1256 nav_history: bool,
1257 completions: bool,
1258 scroll: Option<Autoscroll>,
1259}
1260
1261impl Default for SelectionEffects {
1262 fn default() -> Self {
1263 Self {
1264 nav_history: true,
1265 completions: true,
1266 scroll: Some(Autoscroll::fit()),
1267 }
1268 }
1269}
1270impl SelectionEffects {
1271 pub fn scroll(scroll: Autoscroll) -> Self {
1272 Self {
1273 scroll: Some(scroll),
1274 ..Default::default()
1275 }
1276 }
1277
1278 pub fn no_scroll() -> Self {
1279 Self {
1280 scroll: None,
1281 ..Default::default()
1282 }
1283 }
1284
1285 pub fn completions(self, completions: bool) -> Self {
1286 Self {
1287 completions,
1288 ..self
1289 }
1290 }
1291
1292 pub fn nav_history(self, nav_history: bool) -> Self {
1293 Self {
1294 nav_history,
1295 ..self
1296 }
1297 }
1298}
1299
1300struct DeferredSelectionEffectsState {
1301 changed: bool,
1302 effects: SelectionEffects,
1303 old_cursor_position: Anchor,
1304 history_entry: SelectionHistoryEntry,
1305}
1306
1307#[derive(Default)]
1308struct SelectionHistory {
1309 #[allow(clippy::type_complexity)]
1310 selections_by_transaction:
1311 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1312 mode: SelectionHistoryMode,
1313 undo_stack: VecDeque<SelectionHistoryEntry>,
1314 redo_stack: VecDeque<SelectionHistoryEntry>,
1315}
1316
1317impl SelectionHistory {
1318 #[track_caller]
1319 fn insert_transaction(
1320 &mut self,
1321 transaction_id: TransactionId,
1322 selections: Arc<[Selection<Anchor>]>,
1323 ) {
1324 if selections.is_empty() {
1325 log::error!(
1326 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1327 std::panic::Location::caller()
1328 );
1329 return;
1330 }
1331 self.selections_by_transaction
1332 .insert(transaction_id, (selections, None));
1333 }
1334
1335 #[allow(clippy::type_complexity)]
1336 fn transaction(
1337 &self,
1338 transaction_id: TransactionId,
1339 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1340 self.selections_by_transaction.get(&transaction_id)
1341 }
1342
1343 #[allow(clippy::type_complexity)]
1344 fn transaction_mut(
1345 &mut self,
1346 transaction_id: TransactionId,
1347 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1348 self.selections_by_transaction.get_mut(&transaction_id)
1349 }
1350
1351 fn push(&mut self, entry: SelectionHistoryEntry) {
1352 if !entry.selections.is_empty() {
1353 match self.mode {
1354 SelectionHistoryMode::Normal => {
1355 self.push_undo(entry);
1356 self.redo_stack.clear();
1357 }
1358 SelectionHistoryMode::Undoing => self.push_redo(entry),
1359 SelectionHistoryMode::Redoing => self.push_undo(entry),
1360 SelectionHistoryMode::Skipping => {}
1361 }
1362 }
1363 }
1364
1365 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1366 if self
1367 .undo_stack
1368 .back()
1369 .map_or(true, |e| e.selections != entry.selections)
1370 {
1371 self.undo_stack.push_back(entry);
1372 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1373 self.undo_stack.pop_front();
1374 }
1375 }
1376 }
1377
1378 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1379 if self
1380 .redo_stack
1381 .back()
1382 .map_or(true, |e| e.selections != entry.selections)
1383 {
1384 self.redo_stack.push_back(entry);
1385 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1386 self.redo_stack.pop_front();
1387 }
1388 }
1389 }
1390}
1391
1392#[derive(Clone, Copy)]
1393pub struct RowHighlightOptions {
1394 pub autoscroll: bool,
1395 pub include_gutter: bool,
1396}
1397
1398impl Default for RowHighlightOptions {
1399 fn default() -> Self {
1400 Self {
1401 autoscroll: Default::default(),
1402 include_gutter: true,
1403 }
1404 }
1405}
1406
1407struct RowHighlight {
1408 index: usize,
1409 range: Range<Anchor>,
1410 color: Hsla,
1411 options: RowHighlightOptions,
1412 type_id: TypeId,
1413}
1414
1415#[derive(Clone, Debug)]
1416struct AddSelectionsState {
1417 groups: Vec<AddSelectionsGroup>,
1418}
1419
1420#[derive(Clone, Debug)]
1421struct AddSelectionsGroup {
1422 above: bool,
1423 stack: Vec<usize>,
1424}
1425
1426#[derive(Clone)]
1427struct SelectNextState {
1428 query: AhoCorasick,
1429 wordwise: bool,
1430 done: bool,
1431}
1432
1433impl std::fmt::Debug for SelectNextState {
1434 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1435 f.debug_struct(std::any::type_name::<Self>())
1436 .field("wordwise", &self.wordwise)
1437 .field("done", &self.done)
1438 .finish()
1439 }
1440}
1441
1442#[derive(Debug)]
1443struct AutocloseRegion {
1444 selection_id: usize,
1445 range: Range<Anchor>,
1446 pair: BracketPair,
1447}
1448
1449#[derive(Debug)]
1450struct SnippetState {
1451 ranges: Vec<Vec<Range<Anchor>>>,
1452 active_index: usize,
1453 choices: Vec<Option<Vec<String>>>,
1454}
1455
1456#[doc(hidden)]
1457pub struct RenameState {
1458 pub range: Range<Anchor>,
1459 pub old_name: Arc<str>,
1460 pub editor: Entity<Editor>,
1461 block_id: CustomBlockId,
1462}
1463
1464struct InvalidationStack<T>(Vec<T>);
1465
1466struct RegisteredInlineCompletionProvider {
1467 provider: Arc<dyn InlineCompletionProviderHandle>,
1468 _subscription: Subscription,
1469}
1470
1471#[derive(Debug, PartialEq, Eq)]
1472pub struct ActiveDiagnosticGroup {
1473 pub active_range: Range<Anchor>,
1474 pub active_message: String,
1475 pub group_id: usize,
1476 pub blocks: HashSet<CustomBlockId>,
1477}
1478
1479#[derive(Debug, PartialEq, Eq)]
1480
1481pub(crate) enum ActiveDiagnostic {
1482 None,
1483 All,
1484 Group(ActiveDiagnosticGroup),
1485}
1486
1487#[derive(Serialize, Deserialize, Clone, Debug)]
1488pub struct ClipboardSelection {
1489 /// The number of bytes in this selection.
1490 pub len: usize,
1491 /// Whether this was a full-line selection.
1492 pub is_entire_line: bool,
1493 /// The indentation of the first line when this content was originally copied.
1494 pub first_line_indent: u32,
1495}
1496
1497// selections, scroll behavior, was newest selection reversed
1498type SelectSyntaxNodeHistoryState = (
1499 Box<[Selection<usize>]>,
1500 SelectSyntaxNodeScrollBehavior,
1501 bool,
1502);
1503
1504#[derive(Default)]
1505struct SelectSyntaxNodeHistory {
1506 stack: Vec<SelectSyntaxNodeHistoryState>,
1507 // disable temporarily to allow changing selections without losing the stack
1508 pub disable_clearing: bool,
1509}
1510
1511impl SelectSyntaxNodeHistory {
1512 pub fn try_clear(&mut self) {
1513 if !self.disable_clearing {
1514 self.stack.clear();
1515 }
1516 }
1517
1518 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1519 self.stack.push(selection);
1520 }
1521
1522 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1523 self.stack.pop()
1524 }
1525}
1526
1527enum SelectSyntaxNodeScrollBehavior {
1528 CursorTop,
1529 FitSelection,
1530 CursorBottom,
1531}
1532
1533#[derive(Debug)]
1534pub(crate) struct NavigationData {
1535 cursor_anchor: Anchor,
1536 cursor_position: Point,
1537 scroll_anchor: ScrollAnchor,
1538 scroll_top_row: u32,
1539}
1540
1541#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1542pub enum GotoDefinitionKind {
1543 Symbol,
1544 Declaration,
1545 Type,
1546 Implementation,
1547}
1548
1549#[derive(Debug, Clone)]
1550enum InlayHintRefreshReason {
1551 ModifiersChanged(bool),
1552 Toggle(bool),
1553 SettingsChange(InlayHintSettings),
1554 NewLinesShown,
1555 BufferEdited(HashSet<Arc<Language>>),
1556 RefreshRequested,
1557 ExcerptsRemoved(Vec<ExcerptId>),
1558}
1559
1560impl InlayHintRefreshReason {
1561 fn description(&self) -> &'static str {
1562 match self {
1563 Self::ModifiersChanged(_) => "modifiers changed",
1564 Self::Toggle(_) => "toggle",
1565 Self::SettingsChange(_) => "settings change",
1566 Self::NewLinesShown => "new lines shown",
1567 Self::BufferEdited(_) => "buffer edited",
1568 Self::RefreshRequested => "refresh requested",
1569 Self::ExcerptsRemoved(_) => "excerpts removed",
1570 }
1571 }
1572}
1573
1574pub enum FormatTarget {
1575 Buffers(HashSet<Entity<Buffer>>),
1576 Ranges(Vec<Range<MultiBufferPoint>>),
1577}
1578
1579pub(crate) struct FocusedBlock {
1580 id: BlockId,
1581 focus_handle: WeakFocusHandle,
1582}
1583
1584#[derive(Clone)]
1585enum JumpData {
1586 MultiBufferRow {
1587 row: MultiBufferRow,
1588 line_offset_from_top: u32,
1589 },
1590 MultiBufferPoint {
1591 excerpt_id: ExcerptId,
1592 position: Point,
1593 anchor: text::Anchor,
1594 line_offset_from_top: u32,
1595 },
1596}
1597
1598pub enum MultibufferSelectionMode {
1599 First,
1600 All,
1601}
1602
1603#[derive(Clone, Copy, Debug, Default)]
1604pub struct RewrapOptions {
1605 pub override_language_settings: bool,
1606 pub preserve_existing_whitespace: bool,
1607}
1608
1609impl Editor {
1610 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1611 let buffer = cx.new(|cx| Buffer::local("", cx));
1612 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1613 Self::new(
1614 EditorMode::SingleLine { auto_width: false },
1615 buffer,
1616 None,
1617 window,
1618 cx,
1619 )
1620 }
1621
1622 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1623 let buffer = cx.new(|cx| Buffer::local("", cx));
1624 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1625 Self::new(EditorMode::full(), buffer, None, window, cx)
1626 }
1627
1628 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1629 let buffer = cx.new(|cx| Buffer::local("", cx));
1630 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1631 Self::new(
1632 EditorMode::SingleLine { auto_width: true },
1633 buffer,
1634 None,
1635 window,
1636 cx,
1637 )
1638 }
1639
1640 pub fn auto_height(
1641 min_lines: usize,
1642 max_lines: usize,
1643 window: &mut Window,
1644 cx: &mut Context<Self>,
1645 ) -> Self {
1646 let buffer = cx.new(|cx| Buffer::local("", cx));
1647 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1648 Self::new(
1649 EditorMode::AutoHeight {
1650 min_lines,
1651 max_lines,
1652 },
1653 buffer,
1654 None,
1655 window,
1656 cx,
1657 )
1658 }
1659
1660 pub fn for_buffer(
1661 buffer: Entity<Buffer>,
1662 project: Option<Entity<Project>>,
1663 window: &mut Window,
1664 cx: &mut Context<Self>,
1665 ) -> Self {
1666 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1667 Self::new(EditorMode::full(), buffer, project, window, cx)
1668 }
1669
1670 pub fn for_multibuffer(
1671 buffer: Entity<MultiBuffer>,
1672 project: Option<Entity<Project>>,
1673 window: &mut Window,
1674 cx: &mut Context<Self>,
1675 ) -> Self {
1676 Self::new(EditorMode::full(), buffer, project, window, cx)
1677 }
1678
1679 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1680 let mut clone = Self::new(
1681 self.mode.clone(),
1682 self.buffer.clone(),
1683 self.project.clone(),
1684 window,
1685 cx,
1686 );
1687 self.display_map.update(cx, |display_map, cx| {
1688 let snapshot = display_map.snapshot(cx);
1689 clone.display_map.update(cx, |display_map, cx| {
1690 display_map.set_state(&snapshot, cx);
1691 });
1692 });
1693 clone.folds_did_change(cx);
1694 clone.selections.clone_state(&self.selections);
1695 clone.scroll_manager.clone_state(&self.scroll_manager);
1696 clone.searchable = self.searchable;
1697 clone.read_only = self.read_only;
1698 clone
1699 }
1700
1701 pub fn new(
1702 mode: EditorMode,
1703 buffer: Entity<MultiBuffer>,
1704 project: Option<Entity<Project>>,
1705 window: &mut Window,
1706 cx: &mut Context<Self>,
1707 ) -> Self {
1708 Editor::new_internal(mode, buffer, project, None, window, cx)
1709 }
1710
1711 fn new_internal(
1712 mode: EditorMode,
1713 buffer: Entity<MultiBuffer>,
1714 project: Option<Entity<Project>>,
1715 display_map: Option<Entity<DisplayMap>>,
1716 window: &mut Window,
1717 cx: &mut Context<Self>,
1718 ) -> Self {
1719 debug_assert!(
1720 display_map.is_none() || mode.is_minimap(),
1721 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1722 );
1723
1724 let full_mode = mode.is_full();
1725 let diagnostics_max_severity = if full_mode {
1726 EditorSettings::get_global(cx)
1727 .diagnostics_max_severity
1728 .unwrap_or(DiagnosticSeverity::Hint)
1729 } else {
1730 DiagnosticSeverity::Off
1731 };
1732 let style = window.text_style();
1733 let font_size = style.font_size.to_pixels(window.rem_size());
1734 let editor = cx.entity().downgrade();
1735 let fold_placeholder = FoldPlaceholder {
1736 constrain_width: true,
1737 render: Arc::new(move |fold_id, fold_range, cx| {
1738 let editor = editor.clone();
1739 div()
1740 .id(fold_id)
1741 .bg(cx.theme().colors().ghost_element_background)
1742 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1743 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1744 .rounded_xs()
1745 .size_full()
1746 .cursor_pointer()
1747 .child("⋯")
1748 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1749 .on_click(move |_, _window, cx| {
1750 editor
1751 .update(cx, |editor, cx| {
1752 editor.unfold_ranges(
1753 &[fold_range.start..fold_range.end],
1754 true,
1755 false,
1756 cx,
1757 );
1758 cx.stop_propagation();
1759 })
1760 .ok();
1761 })
1762 .into_any()
1763 }),
1764 merge_adjacent: true,
1765 ..FoldPlaceholder::default()
1766 };
1767 let display_map = display_map.unwrap_or_else(|| {
1768 cx.new(|cx| {
1769 DisplayMap::new(
1770 buffer.clone(),
1771 style.font(),
1772 font_size,
1773 None,
1774 FILE_HEADER_HEIGHT,
1775 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1776 fold_placeholder,
1777 diagnostics_max_severity,
1778 cx,
1779 )
1780 })
1781 });
1782
1783 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1784
1785 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1786
1787 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1788 .then(|| language_settings::SoftWrap::None);
1789
1790 let mut project_subscriptions = Vec::new();
1791 if mode.is_full() {
1792 if let Some(project) = project.as_ref() {
1793 project_subscriptions.push(cx.subscribe_in(
1794 project,
1795 window,
1796 |editor, _, event, window, cx| match event {
1797 project::Event::RefreshCodeLens => {
1798 // we always query lens with actions, without storing them, always refreshing them
1799 }
1800 project::Event::RefreshInlayHints => {
1801 editor
1802 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1803 }
1804 project::Event::LanguageServerAdded(server_id, ..)
1805 | project::Event::LanguageServerRemoved(server_id) => {
1806 if editor.tasks_update_task.is_none() {
1807 editor.tasks_update_task =
1808 Some(editor.refresh_runnables(window, cx));
1809 }
1810 editor.update_lsp_data(false, Some(*server_id), None, window, cx);
1811 }
1812 project::Event::SnippetEdit(id, snippet_edits) => {
1813 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1814 let focus_handle = editor.focus_handle(cx);
1815 if focus_handle.is_focused(window) {
1816 let snapshot = buffer.read(cx).snapshot();
1817 for (range, snippet) in snippet_edits {
1818 let editor_range =
1819 language::range_from_lsp(*range).to_offset(&snapshot);
1820 editor
1821 .insert_snippet(
1822 &[editor_range],
1823 snippet.clone(),
1824 window,
1825 cx,
1826 )
1827 .ok();
1828 }
1829 }
1830 }
1831 }
1832 _ => {}
1833 },
1834 ));
1835 if let Some(task_inventory) = project
1836 .read(cx)
1837 .task_store()
1838 .read(cx)
1839 .task_inventory()
1840 .cloned()
1841 {
1842 project_subscriptions.push(cx.observe_in(
1843 &task_inventory,
1844 window,
1845 |editor, _, window, cx| {
1846 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1847 },
1848 ));
1849 };
1850
1851 project_subscriptions.push(cx.subscribe_in(
1852 &project.read(cx).breakpoint_store(),
1853 window,
1854 |editor, _, event, window, cx| match event {
1855 BreakpointStoreEvent::ClearDebugLines => {
1856 editor.clear_row_highlights::<ActiveDebugLine>();
1857 editor.refresh_inline_values(cx);
1858 }
1859 BreakpointStoreEvent::SetDebugLine => {
1860 if editor.go_to_active_debug_line(window, cx) {
1861 cx.stop_propagation();
1862 }
1863
1864 editor.refresh_inline_values(cx);
1865 }
1866 _ => {}
1867 },
1868 ));
1869 }
1870 }
1871
1872 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1873
1874 let inlay_hint_settings =
1875 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1876 let focus_handle = cx.focus_handle();
1877 cx.on_focus(&focus_handle, window, Self::handle_focus)
1878 .detach();
1879 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1880 .detach();
1881 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1882 .detach();
1883 cx.on_blur(&focus_handle, window, Self::handle_blur)
1884 .detach();
1885 cx.observe_pending_input(window, Self::observe_pending_input)
1886 .detach();
1887
1888 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1889 Some(false)
1890 } else {
1891 None
1892 };
1893
1894 let breakpoint_store = match (&mode, project.as_ref()) {
1895 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1896 _ => None,
1897 };
1898
1899 let mut code_action_providers = Vec::new();
1900 let mut load_uncommitted_diff = None;
1901 if let Some(project) = project.clone() {
1902 load_uncommitted_diff = Some(
1903 update_uncommitted_diff_for_buffer(
1904 cx.entity(),
1905 &project,
1906 buffer.read(cx).all_buffers(),
1907 buffer.clone(),
1908 cx,
1909 )
1910 .shared(),
1911 );
1912 code_action_providers.push(Rc::new(project) as Rc<_>);
1913 }
1914
1915 let mut editor = Self {
1916 focus_handle,
1917 show_cursor_when_unfocused: false,
1918 last_focused_descendant: None,
1919 buffer: buffer.clone(),
1920 display_map: display_map.clone(),
1921 selections,
1922 scroll_manager: ScrollManager::new(cx),
1923 columnar_selection_state: None,
1924 add_selections_state: None,
1925 select_next_state: None,
1926 select_prev_state: None,
1927 selection_history: SelectionHistory::default(),
1928 defer_selection_effects: false,
1929 deferred_selection_effects_state: None,
1930 autoclose_regions: Vec::new(),
1931 snippet_stack: InvalidationStack::default(),
1932 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1933 ime_transaction: None,
1934 active_diagnostics: ActiveDiagnostic::None,
1935 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1936 inline_diagnostics_update: Task::ready(()),
1937 inline_diagnostics: Vec::new(),
1938 soft_wrap_mode_override,
1939 diagnostics_max_severity,
1940 hard_wrap: None,
1941 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1942 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1943 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1944 project,
1945 blink_manager: blink_manager.clone(),
1946 show_local_selections: true,
1947 show_scrollbars: ScrollbarAxes {
1948 horizontal: full_mode,
1949 vertical: full_mode,
1950 },
1951 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1952 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1953 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1954 show_gutter: mode.is_full(),
1955 show_line_numbers: None,
1956 use_relative_line_numbers: None,
1957 disable_expand_excerpt_buttons: false,
1958 show_git_diff_gutter: None,
1959 show_code_actions: None,
1960 show_runnables: None,
1961 show_breakpoints: None,
1962 show_wrap_guides: None,
1963 show_indent_guides,
1964 placeholder_text: None,
1965 highlight_order: 0,
1966 highlighted_rows: HashMap::default(),
1967 background_highlights: TreeMap::default(),
1968 gutter_highlights: TreeMap::default(),
1969 scrollbar_marker_state: ScrollbarMarkerState::default(),
1970 active_indent_guides_state: ActiveIndentGuidesState::default(),
1971 nav_history: None,
1972 context_menu: RefCell::new(None),
1973 context_menu_options: None,
1974 mouse_context_menu: None,
1975 completion_tasks: Vec::new(),
1976 inline_blame_popover: None,
1977 inline_blame_popover_show_task: None,
1978 signature_help_state: SignatureHelpState::default(),
1979 auto_signature_help: None,
1980 find_all_references_task_sources: Vec::new(),
1981 next_completion_id: 0,
1982 next_inlay_id: 0,
1983 code_action_providers,
1984 available_code_actions: None,
1985 code_actions_task: None,
1986 quick_selection_highlight_task: None,
1987 debounced_selection_highlight_task: None,
1988 document_highlights_task: None,
1989 linked_editing_range_task: None,
1990 pending_rename: None,
1991 searchable: true,
1992 cursor_shape: EditorSettings::get_global(cx)
1993 .cursor_shape
1994 .unwrap_or_default(),
1995 current_line_highlight: None,
1996 autoindent_mode: Some(AutoindentMode::EachLine),
1997 collapse_matches: false,
1998 workspace: None,
1999 input_enabled: true,
2000 use_modal_editing: mode.is_full(),
2001 read_only: mode.is_minimap(),
2002 use_autoclose: true,
2003 use_auto_surround: true,
2004 auto_replace_emoji_shortcode: false,
2005 jsx_tag_auto_close_enabled_in_any_buffer: false,
2006 leader_id: None,
2007 remote_id: None,
2008 hover_state: HoverState::default(),
2009 pending_mouse_down: None,
2010 hovered_link_state: None,
2011 edit_prediction_provider: None,
2012 active_inline_completion: None,
2013 stale_inline_completion_in_menu: None,
2014 edit_prediction_preview: EditPredictionPreview::Inactive {
2015 released_too_fast: false,
2016 },
2017 inline_diagnostics_enabled: mode.is_full(),
2018 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2019 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2020
2021 gutter_hovered: false,
2022 pixel_position_of_newest_cursor: None,
2023 last_bounds: None,
2024 last_position_map: None,
2025 expect_bounds_change: None,
2026 gutter_dimensions: GutterDimensions::default(),
2027 style: None,
2028 show_cursor_names: false,
2029 hovered_cursors: HashMap::default(),
2030 next_editor_action_id: EditorActionId::default(),
2031 editor_actions: Rc::default(),
2032 inline_completions_hidden_for_vim_mode: false,
2033 show_inline_completions_override: None,
2034 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
2035 edit_prediction_settings: EditPredictionSettings::Disabled,
2036 edit_prediction_indent_conflict: false,
2037 edit_prediction_requires_modifier_in_indent_conflict: true,
2038 custom_context_menu: None,
2039 show_git_blame_gutter: false,
2040 show_git_blame_inline: false,
2041 show_selection_menu: None,
2042 show_git_blame_inline_delay_task: None,
2043 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2044 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2045 serialize_dirty_buffers: !mode.is_minimap()
2046 && ProjectSettings::get_global(cx)
2047 .session
2048 .restore_unsaved_buffers,
2049 blame: None,
2050 blame_subscription: None,
2051 tasks: BTreeMap::default(),
2052
2053 breakpoint_store,
2054 gutter_breakpoint_indicator: (None, None),
2055 hovered_diff_hunk_row: None,
2056 _subscriptions: vec![
2057 cx.observe(&buffer, Self::on_buffer_changed),
2058 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2059 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2060 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2061 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2062 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2063 cx.observe_window_activation(window, |editor, window, cx| {
2064 let active = window.is_window_active();
2065 editor.blink_manager.update(cx, |blink_manager, cx| {
2066 if active {
2067 blink_manager.enable(cx);
2068 } else {
2069 blink_manager.disable(cx);
2070 }
2071 });
2072 if active {
2073 editor.show_mouse_cursor(cx);
2074 }
2075 }),
2076 ],
2077 tasks_update_task: None,
2078 pull_diagnostics_task: Task::ready(()),
2079 colors: None,
2080 next_color_inlay_id: 0,
2081 linked_edit_ranges: Default::default(),
2082 in_project_search: false,
2083 previous_search_ranges: None,
2084 breadcrumb_header: None,
2085 focused_block: None,
2086 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2087 addons: HashMap::default(),
2088 registered_buffers: HashMap::default(),
2089 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2090 selection_mark_mode: false,
2091 toggle_fold_multiple_buffers: Task::ready(()),
2092 serialize_selections: Task::ready(()),
2093 serialize_folds: Task::ready(()),
2094 text_style_refinement: None,
2095 load_diff_task: load_uncommitted_diff,
2096 temporary_diff_override: false,
2097 mouse_cursor_hidden: false,
2098 minimap: None,
2099 hide_mouse_mode: EditorSettings::get_global(cx)
2100 .hide_mouse
2101 .unwrap_or_default(),
2102 change_list: ChangeList::new(),
2103 mode,
2104 selection_drag_state: SelectionDragState::None,
2105 drag_and_drop_selection_enabled: EditorSettings::get_global(cx).drag_and_drop_selection,
2106 };
2107 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2108 editor
2109 ._subscriptions
2110 .push(cx.observe(breakpoints, |_, _, cx| {
2111 cx.notify();
2112 }));
2113 }
2114 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2115 editor._subscriptions.extend(project_subscriptions);
2116
2117 editor._subscriptions.push(cx.subscribe_in(
2118 &cx.entity(),
2119 window,
2120 |editor, _, e: &EditorEvent, window, cx| match e {
2121 EditorEvent::ScrollPositionChanged { local, .. } => {
2122 if *local {
2123 let new_anchor = editor.scroll_manager.anchor();
2124 let snapshot = editor.snapshot(window, cx);
2125 editor.update_restoration_data(cx, move |data| {
2126 data.scroll_position = (
2127 new_anchor.top_row(&snapshot.buffer_snapshot),
2128 new_anchor.offset,
2129 );
2130 });
2131 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2132 editor.inline_blame_popover.take();
2133 }
2134 }
2135 EditorEvent::Edited { .. } => {
2136 if !vim_enabled(cx) {
2137 let (map, selections) = editor.selections.all_adjusted_display(cx);
2138 let pop_state = editor
2139 .change_list
2140 .last()
2141 .map(|previous| {
2142 previous.len() == selections.len()
2143 && previous.iter().enumerate().all(|(ix, p)| {
2144 p.to_display_point(&map).row()
2145 == selections[ix].head().row()
2146 })
2147 })
2148 .unwrap_or(false);
2149 let new_positions = selections
2150 .into_iter()
2151 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2152 .collect();
2153 editor
2154 .change_list
2155 .push_to_change_list(pop_state, new_positions);
2156 }
2157 }
2158 _ => (),
2159 },
2160 ));
2161
2162 if let Some(dap_store) = editor
2163 .project
2164 .as_ref()
2165 .map(|project| project.read(cx).dap_store())
2166 {
2167 let weak_editor = cx.weak_entity();
2168
2169 editor
2170 ._subscriptions
2171 .push(
2172 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2173 let session_entity = cx.entity();
2174 weak_editor
2175 .update(cx, |editor, cx| {
2176 editor._subscriptions.push(
2177 cx.subscribe(&session_entity, Self::on_debug_session_event),
2178 );
2179 })
2180 .ok();
2181 }),
2182 );
2183
2184 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2185 editor
2186 ._subscriptions
2187 .push(cx.subscribe(&session, Self::on_debug_session_event));
2188 }
2189 }
2190
2191 // skip adding the initial selection to selection history
2192 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2193 editor.end_selection(window, cx);
2194 editor.selection_history.mode = SelectionHistoryMode::Normal;
2195
2196 editor.scroll_manager.show_scrollbars(window, cx);
2197 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2198
2199 if full_mode {
2200 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2201 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2202
2203 if editor.git_blame_inline_enabled {
2204 editor.start_git_blame_inline(false, window, cx);
2205 }
2206
2207 editor.go_to_active_debug_line(window, cx);
2208
2209 if let Some(buffer) = buffer.read(cx).as_singleton() {
2210 if let Some(project) = editor.project.as_ref() {
2211 let handle = project.update(cx, |project, cx| {
2212 project.register_buffer_with_language_servers(&buffer, cx)
2213 });
2214 editor
2215 .registered_buffers
2216 .insert(buffer.read(cx).remote_id(), handle);
2217 }
2218 }
2219
2220 editor.minimap =
2221 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2222 editor.colors = Some(LspColorData::new(cx));
2223 editor.update_lsp_data(false, None, None, window, cx);
2224 }
2225
2226 editor.report_editor_event("Editor Opened", None, cx);
2227 editor
2228 }
2229
2230 pub fn deploy_mouse_context_menu(
2231 &mut self,
2232 position: gpui::Point<Pixels>,
2233 context_menu: Entity<ContextMenu>,
2234 window: &mut Window,
2235 cx: &mut Context<Self>,
2236 ) {
2237 self.mouse_context_menu = Some(MouseContextMenu::new(
2238 self,
2239 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2240 context_menu,
2241 window,
2242 cx,
2243 ));
2244 }
2245
2246 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2247 self.mouse_context_menu
2248 .as_ref()
2249 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2250 }
2251
2252 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2253 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2254 }
2255
2256 fn key_context_internal(
2257 &self,
2258 has_active_edit_prediction: bool,
2259 window: &Window,
2260 cx: &App,
2261 ) -> KeyContext {
2262 let mut key_context = KeyContext::new_with_defaults();
2263 key_context.add("Editor");
2264 let mode = match self.mode {
2265 EditorMode::SingleLine { .. } => "single_line",
2266 EditorMode::AutoHeight { .. } => "auto_height",
2267 EditorMode::Minimap { .. } => "minimap",
2268 EditorMode::Full { .. } => "full",
2269 };
2270
2271 if EditorSettings::jupyter_enabled(cx) {
2272 key_context.add("jupyter");
2273 }
2274
2275 key_context.set("mode", mode);
2276 if self.pending_rename.is_some() {
2277 key_context.add("renaming");
2278 }
2279
2280 match self.context_menu.borrow().as_ref() {
2281 Some(CodeContextMenu::Completions(_)) => {
2282 key_context.add("menu");
2283 key_context.add("showing_completions");
2284 }
2285 Some(CodeContextMenu::CodeActions(_)) => {
2286 key_context.add("menu");
2287 key_context.add("showing_code_actions")
2288 }
2289 None => {}
2290 }
2291
2292 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2293 if !self.focus_handle(cx).contains_focused(window, cx)
2294 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2295 {
2296 for addon in self.addons.values() {
2297 addon.extend_key_context(&mut key_context, cx)
2298 }
2299 }
2300
2301 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2302 if let Some(extension) = singleton_buffer
2303 .read(cx)
2304 .file()
2305 .and_then(|file| file.path().extension()?.to_str())
2306 {
2307 key_context.set("extension", extension.to_string());
2308 }
2309 } else {
2310 key_context.add("multibuffer");
2311 }
2312
2313 if has_active_edit_prediction {
2314 if self.edit_prediction_in_conflict() {
2315 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2316 } else {
2317 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2318 key_context.add("copilot_suggestion");
2319 }
2320 }
2321
2322 if self.selection_mark_mode {
2323 key_context.add("selection_mode");
2324 }
2325
2326 key_context
2327 }
2328
2329 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2330 if self.mouse_cursor_hidden {
2331 self.mouse_cursor_hidden = false;
2332 cx.notify();
2333 }
2334 }
2335
2336 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2337 let hide_mouse_cursor = match origin {
2338 HideMouseCursorOrigin::TypingAction => {
2339 matches!(
2340 self.hide_mouse_mode,
2341 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2342 )
2343 }
2344 HideMouseCursorOrigin::MovementAction => {
2345 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2346 }
2347 };
2348 if self.mouse_cursor_hidden != hide_mouse_cursor {
2349 self.mouse_cursor_hidden = hide_mouse_cursor;
2350 cx.notify();
2351 }
2352 }
2353
2354 pub fn edit_prediction_in_conflict(&self) -> bool {
2355 if !self.show_edit_predictions_in_menu() {
2356 return false;
2357 }
2358
2359 let showing_completions = self
2360 .context_menu
2361 .borrow()
2362 .as_ref()
2363 .map_or(false, |context| {
2364 matches!(context, CodeContextMenu::Completions(_))
2365 });
2366
2367 showing_completions
2368 || self.edit_prediction_requires_modifier()
2369 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2370 // bindings to insert tab characters.
2371 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2372 }
2373
2374 pub fn accept_edit_prediction_keybind(
2375 &self,
2376 accept_partial: bool,
2377 window: &Window,
2378 cx: &App,
2379 ) -> AcceptEditPredictionBinding {
2380 let key_context = self.key_context_internal(true, window, cx);
2381 let in_conflict = self.edit_prediction_in_conflict();
2382
2383 let bindings = if accept_partial {
2384 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2385 } else {
2386 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2387 };
2388
2389 // TODO: if the binding contains multiple keystrokes, display all of them, not
2390 // just the first one.
2391 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2392 !in_conflict
2393 || binding
2394 .keystrokes()
2395 .first()
2396 .map_or(false, |keystroke| keystroke.modifiers.modified())
2397 }))
2398 }
2399
2400 pub fn new_file(
2401 workspace: &mut Workspace,
2402 _: &workspace::NewFile,
2403 window: &mut Window,
2404 cx: &mut Context<Workspace>,
2405 ) {
2406 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2407 "Failed to create buffer",
2408 window,
2409 cx,
2410 |e, _, _| match e.error_code() {
2411 ErrorCode::RemoteUpgradeRequired => Some(format!(
2412 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2413 e.error_tag("required").unwrap_or("the latest version")
2414 )),
2415 _ => None,
2416 },
2417 );
2418 }
2419
2420 pub fn new_in_workspace(
2421 workspace: &mut Workspace,
2422 window: &mut Window,
2423 cx: &mut Context<Workspace>,
2424 ) -> Task<Result<Entity<Editor>>> {
2425 let project = workspace.project().clone();
2426 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2427
2428 cx.spawn_in(window, async move |workspace, cx| {
2429 let buffer = create.await?;
2430 workspace.update_in(cx, |workspace, window, cx| {
2431 let editor =
2432 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2433 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2434 editor
2435 })
2436 })
2437 }
2438
2439 fn new_file_vertical(
2440 workspace: &mut Workspace,
2441 _: &workspace::NewFileSplitVertical,
2442 window: &mut Window,
2443 cx: &mut Context<Workspace>,
2444 ) {
2445 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2446 }
2447
2448 fn new_file_horizontal(
2449 workspace: &mut Workspace,
2450 _: &workspace::NewFileSplitHorizontal,
2451 window: &mut Window,
2452 cx: &mut Context<Workspace>,
2453 ) {
2454 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2455 }
2456
2457 fn new_file_in_direction(
2458 workspace: &mut Workspace,
2459 direction: SplitDirection,
2460 window: &mut Window,
2461 cx: &mut Context<Workspace>,
2462 ) {
2463 let project = workspace.project().clone();
2464 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2465
2466 cx.spawn_in(window, async move |workspace, cx| {
2467 let buffer = create.await?;
2468 workspace.update_in(cx, move |workspace, window, cx| {
2469 workspace.split_item(
2470 direction,
2471 Box::new(
2472 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2473 ),
2474 window,
2475 cx,
2476 )
2477 })?;
2478 anyhow::Ok(())
2479 })
2480 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2481 match e.error_code() {
2482 ErrorCode::RemoteUpgradeRequired => Some(format!(
2483 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2484 e.error_tag("required").unwrap_or("the latest version")
2485 )),
2486 _ => None,
2487 }
2488 });
2489 }
2490
2491 pub fn leader_id(&self) -> Option<CollaboratorId> {
2492 self.leader_id
2493 }
2494
2495 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2496 &self.buffer
2497 }
2498
2499 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2500 self.workspace.as_ref()?.0.upgrade()
2501 }
2502
2503 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2504 self.buffer().read(cx).title(cx)
2505 }
2506
2507 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2508 let git_blame_gutter_max_author_length = self
2509 .render_git_blame_gutter(cx)
2510 .then(|| {
2511 if let Some(blame) = self.blame.as_ref() {
2512 let max_author_length =
2513 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2514 Some(max_author_length)
2515 } else {
2516 None
2517 }
2518 })
2519 .flatten();
2520
2521 EditorSnapshot {
2522 mode: self.mode.clone(),
2523 show_gutter: self.show_gutter,
2524 show_line_numbers: self.show_line_numbers,
2525 show_git_diff_gutter: self.show_git_diff_gutter,
2526 show_code_actions: self.show_code_actions,
2527 show_runnables: self.show_runnables,
2528 show_breakpoints: self.show_breakpoints,
2529 git_blame_gutter_max_author_length,
2530 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2531 scroll_anchor: self.scroll_manager.anchor(),
2532 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2533 placeholder_text: self.placeholder_text.clone(),
2534 is_focused: self.focus_handle.is_focused(window),
2535 current_line_highlight: self
2536 .current_line_highlight
2537 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2538 gutter_hovered: self.gutter_hovered,
2539 }
2540 }
2541
2542 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2543 self.buffer.read(cx).language_at(point, cx)
2544 }
2545
2546 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2547 self.buffer.read(cx).read(cx).file_at(point).cloned()
2548 }
2549
2550 pub fn active_excerpt(
2551 &self,
2552 cx: &App,
2553 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2554 self.buffer
2555 .read(cx)
2556 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2557 }
2558
2559 pub fn mode(&self) -> &EditorMode {
2560 &self.mode
2561 }
2562
2563 pub fn set_mode(&mut self, mode: EditorMode) {
2564 self.mode = mode;
2565 }
2566
2567 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2568 self.collaboration_hub.as_deref()
2569 }
2570
2571 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2572 self.collaboration_hub = Some(hub);
2573 }
2574
2575 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2576 self.in_project_search = in_project_search;
2577 }
2578
2579 pub fn set_custom_context_menu(
2580 &mut self,
2581 f: impl 'static
2582 + Fn(
2583 &mut Self,
2584 DisplayPoint,
2585 &mut Window,
2586 &mut Context<Self>,
2587 ) -> Option<Entity<ui::ContextMenu>>,
2588 ) {
2589 self.custom_context_menu = Some(Box::new(f))
2590 }
2591
2592 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2593 self.completion_provider = provider;
2594 }
2595
2596 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2597 self.semantics_provider.clone()
2598 }
2599
2600 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2601 self.semantics_provider = provider;
2602 }
2603
2604 pub fn set_edit_prediction_provider<T>(
2605 &mut self,
2606 provider: Option<Entity<T>>,
2607 window: &mut Window,
2608 cx: &mut Context<Self>,
2609 ) where
2610 T: EditPredictionProvider,
2611 {
2612 self.edit_prediction_provider =
2613 provider.map(|provider| RegisteredInlineCompletionProvider {
2614 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2615 if this.focus_handle.is_focused(window) {
2616 this.update_visible_inline_completion(window, cx);
2617 }
2618 }),
2619 provider: Arc::new(provider),
2620 });
2621 self.update_edit_prediction_settings(cx);
2622 self.refresh_inline_completion(false, false, window, cx);
2623 }
2624
2625 pub fn placeholder_text(&self) -> Option<&str> {
2626 self.placeholder_text.as_deref()
2627 }
2628
2629 pub fn set_placeholder_text(
2630 &mut self,
2631 placeholder_text: impl Into<Arc<str>>,
2632 cx: &mut Context<Self>,
2633 ) {
2634 let placeholder_text = Some(placeholder_text.into());
2635 if self.placeholder_text != placeholder_text {
2636 self.placeholder_text = placeholder_text;
2637 cx.notify();
2638 }
2639 }
2640
2641 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2642 self.cursor_shape = cursor_shape;
2643
2644 // Disrupt blink for immediate user feedback that the cursor shape has changed
2645 self.blink_manager.update(cx, BlinkManager::show_cursor);
2646
2647 cx.notify();
2648 }
2649
2650 pub fn set_current_line_highlight(
2651 &mut self,
2652 current_line_highlight: Option<CurrentLineHighlight>,
2653 ) {
2654 self.current_line_highlight = current_line_highlight;
2655 }
2656
2657 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2658 self.collapse_matches = collapse_matches;
2659 }
2660
2661 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2662 let buffers = self.buffer.read(cx).all_buffers();
2663 let Some(project) = self.project.as_ref() else {
2664 return;
2665 };
2666 project.update(cx, |project, cx| {
2667 for buffer in buffers {
2668 self.registered_buffers
2669 .entry(buffer.read(cx).remote_id())
2670 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2671 }
2672 })
2673 }
2674
2675 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2676 if self.collapse_matches {
2677 return range.start..range.start;
2678 }
2679 range.clone()
2680 }
2681
2682 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2683 if self.display_map.read(cx).clip_at_line_ends != clip {
2684 self.display_map
2685 .update(cx, |map, _| map.clip_at_line_ends = clip);
2686 }
2687 }
2688
2689 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2690 self.input_enabled = input_enabled;
2691 }
2692
2693 pub fn set_inline_completions_hidden_for_vim_mode(
2694 &mut self,
2695 hidden: bool,
2696 window: &mut Window,
2697 cx: &mut Context<Self>,
2698 ) {
2699 if hidden != self.inline_completions_hidden_for_vim_mode {
2700 self.inline_completions_hidden_for_vim_mode = hidden;
2701 if hidden {
2702 self.update_visible_inline_completion(window, cx);
2703 } else {
2704 self.refresh_inline_completion(true, false, window, cx);
2705 }
2706 }
2707 }
2708
2709 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2710 self.menu_inline_completions_policy = value;
2711 }
2712
2713 pub fn set_autoindent(&mut self, autoindent: bool) {
2714 if autoindent {
2715 self.autoindent_mode = Some(AutoindentMode::EachLine);
2716 } else {
2717 self.autoindent_mode = None;
2718 }
2719 }
2720
2721 pub fn read_only(&self, cx: &App) -> bool {
2722 self.read_only || self.buffer.read(cx).read_only()
2723 }
2724
2725 pub fn set_read_only(&mut self, read_only: bool) {
2726 self.read_only = read_only;
2727 }
2728
2729 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2730 self.use_autoclose = autoclose;
2731 }
2732
2733 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2734 self.use_auto_surround = auto_surround;
2735 }
2736
2737 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2738 self.auto_replace_emoji_shortcode = auto_replace;
2739 }
2740
2741 pub fn toggle_edit_predictions(
2742 &mut self,
2743 _: &ToggleEditPrediction,
2744 window: &mut Window,
2745 cx: &mut Context<Self>,
2746 ) {
2747 if self.show_inline_completions_override.is_some() {
2748 self.set_show_edit_predictions(None, window, cx);
2749 } else {
2750 let show_edit_predictions = !self.edit_predictions_enabled();
2751 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2752 }
2753 }
2754
2755 pub fn set_show_edit_predictions(
2756 &mut self,
2757 show_edit_predictions: Option<bool>,
2758 window: &mut Window,
2759 cx: &mut Context<Self>,
2760 ) {
2761 self.show_inline_completions_override = show_edit_predictions;
2762 self.update_edit_prediction_settings(cx);
2763
2764 if let Some(false) = show_edit_predictions {
2765 self.discard_inline_completion(false, cx);
2766 } else {
2767 self.refresh_inline_completion(false, true, window, cx);
2768 }
2769 }
2770
2771 fn inline_completions_disabled_in_scope(
2772 &self,
2773 buffer: &Entity<Buffer>,
2774 buffer_position: language::Anchor,
2775 cx: &App,
2776 ) -> bool {
2777 let snapshot = buffer.read(cx).snapshot();
2778 let settings = snapshot.settings_at(buffer_position, cx);
2779
2780 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2781 return false;
2782 };
2783
2784 scope.override_name().map_or(false, |scope_name| {
2785 settings
2786 .edit_predictions_disabled_in
2787 .iter()
2788 .any(|s| s == scope_name)
2789 })
2790 }
2791
2792 pub fn set_use_modal_editing(&mut self, to: bool) {
2793 self.use_modal_editing = to;
2794 }
2795
2796 pub fn use_modal_editing(&self) -> bool {
2797 self.use_modal_editing
2798 }
2799
2800 fn selections_did_change(
2801 &mut self,
2802 local: bool,
2803 old_cursor_position: &Anchor,
2804 effects: SelectionEffects,
2805 window: &mut Window,
2806 cx: &mut Context<Self>,
2807 ) {
2808 window.invalidate_character_coordinates();
2809
2810 // Copy selections to primary selection buffer
2811 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2812 if local {
2813 let selections = self.selections.all::<usize>(cx);
2814 let buffer_handle = self.buffer.read(cx).read(cx);
2815
2816 let mut text = String::new();
2817 for (index, selection) in selections.iter().enumerate() {
2818 let text_for_selection = buffer_handle
2819 .text_for_range(selection.start..selection.end)
2820 .collect::<String>();
2821
2822 text.push_str(&text_for_selection);
2823 if index != selections.len() - 1 {
2824 text.push('\n');
2825 }
2826 }
2827
2828 if !text.is_empty() {
2829 cx.write_to_primary(ClipboardItem::new_string(text));
2830 }
2831 }
2832
2833 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2834 self.buffer.update(cx, |buffer, cx| {
2835 buffer.set_active_selections(
2836 &self.selections.disjoint_anchors(),
2837 self.selections.line_mode,
2838 self.cursor_shape,
2839 cx,
2840 )
2841 });
2842 }
2843 let display_map = self
2844 .display_map
2845 .update(cx, |display_map, cx| display_map.snapshot(cx));
2846 let buffer = &display_map.buffer_snapshot;
2847 if self.selections.count() == 1 {
2848 self.add_selections_state = None;
2849 }
2850 self.select_next_state = None;
2851 self.select_prev_state = None;
2852 self.select_syntax_node_history.try_clear();
2853 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2854 self.snippet_stack
2855 .invalidate(&self.selections.disjoint_anchors(), buffer);
2856 self.take_rename(false, window, cx);
2857
2858 let newest_selection = self.selections.newest_anchor();
2859 let new_cursor_position = newest_selection.head();
2860 let selection_start = newest_selection.start;
2861
2862 if effects.nav_history {
2863 self.push_to_nav_history(
2864 *old_cursor_position,
2865 Some(new_cursor_position.to_point(buffer)),
2866 false,
2867 cx,
2868 );
2869 }
2870
2871 if local {
2872 if let Some(buffer_id) = new_cursor_position.buffer_id {
2873 if !self.registered_buffers.contains_key(&buffer_id) {
2874 if let Some(project) = self.project.as_ref() {
2875 project.update(cx, |project, cx| {
2876 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2877 return;
2878 };
2879 self.registered_buffers.insert(
2880 buffer_id,
2881 project.register_buffer_with_language_servers(&buffer, cx),
2882 );
2883 })
2884 }
2885 }
2886 }
2887
2888 let mut context_menu = self.context_menu.borrow_mut();
2889 let completion_menu = match context_menu.as_ref() {
2890 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2891 Some(CodeContextMenu::CodeActions(_)) => {
2892 *context_menu = None;
2893 None
2894 }
2895 None => None,
2896 };
2897 let completion_position = completion_menu.map(|menu| menu.initial_position);
2898 drop(context_menu);
2899
2900 if effects.completions {
2901 if let Some(completion_position) = completion_position {
2902 let start_offset = selection_start.to_offset(buffer);
2903 let position_matches = start_offset == completion_position.to_offset(buffer);
2904 let continue_showing = if position_matches {
2905 if self.snippet_stack.is_empty() {
2906 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
2907 } else {
2908 // Snippet choices can be shown even when the cursor is in whitespace.
2909 // Dismissing the menu with actions like backspace is handled by
2910 // invalidation regions.
2911 true
2912 }
2913 } else {
2914 false
2915 };
2916
2917 if continue_showing {
2918 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2919 } else {
2920 self.hide_context_menu(window, cx);
2921 }
2922 }
2923 }
2924
2925 hide_hover(self, cx);
2926
2927 if old_cursor_position.to_display_point(&display_map).row()
2928 != new_cursor_position.to_display_point(&display_map).row()
2929 {
2930 self.available_code_actions.take();
2931 }
2932 self.refresh_code_actions(window, cx);
2933 self.refresh_document_highlights(cx);
2934 self.refresh_selected_text_highlights(false, window, cx);
2935 refresh_matching_bracket_highlights(self, window, cx);
2936 self.update_visible_inline_completion(window, cx);
2937 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2938 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2939 self.inline_blame_popover.take();
2940 if self.git_blame_inline_enabled {
2941 self.start_inline_blame_timer(window, cx);
2942 }
2943 }
2944
2945 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2946 cx.emit(EditorEvent::SelectionsChanged { local });
2947
2948 let selections = &self.selections.disjoint;
2949 if selections.len() == 1 {
2950 cx.emit(SearchEvent::ActiveMatchChanged)
2951 }
2952 if local {
2953 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2954 let inmemory_selections = selections
2955 .iter()
2956 .map(|s| {
2957 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2958 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2959 })
2960 .collect();
2961 self.update_restoration_data(cx, |data| {
2962 data.selections = inmemory_selections;
2963 });
2964
2965 if WorkspaceSettings::get(None, cx).restore_on_startup
2966 != RestoreOnStartupBehavior::None
2967 {
2968 if let Some(workspace_id) =
2969 self.workspace.as_ref().and_then(|workspace| workspace.1)
2970 {
2971 let snapshot = self.buffer().read(cx).snapshot(cx);
2972 let selections = selections.clone();
2973 let background_executor = cx.background_executor().clone();
2974 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2975 self.serialize_selections = cx.background_spawn(async move {
2976 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2977 let db_selections = selections
2978 .iter()
2979 .map(|selection| {
2980 (
2981 selection.start.to_offset(&snapshot),
2982 selection.end.to_offset(&snapshot),
2983 )
2984 })
2985 .collect();
2986
2987 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2988 .await
2989 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2990 .log_err();
2991 });
2992 }
2993 }
2994 }
2995 }
2996
2997 cx.notify();
2998 }
2999
3000 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3001 use text::ToOffset as _;
3002 use text::ToPoint as _;
3003
3004 if self.mode.is_minimap()
3005 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3006 {
3007 return;
3008 }
3009
3010 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3011 return;
3012 };
3013
3014 let snapshot = singleton.read(cx).snapshot();
3015 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3016 let display_snapshot = display_map.snapshot(cx);
3017
3018 display_snapshot
3019 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3020 .map(|fold| {
3021 fold.range.start.text_anchor.to_point(&snapshot)
3022 ..fold.range.end.text_anchor.to_point(&snapshot)
3023 })
3024 .collect()
3025 });
3026 self.update_restoration_data(cx, |data| {
3027 data.folds = inmemory_folds;
3028 });
3029
3030 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3031 return;
3032 };
3033 let background_executor = cx.background_executor().clone();
3034 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3035 let db_folds = self.display_map.update(cx, |display_map, cx| {
3036 display_map
3037 .snapshot(cx)
3038 .folds_in_range(0..snapshot.len())
3039 .map(|fold| {
3040 (
3041 fold.range.start.text_anchor.to_offset(&snapshot),
3042 fold.range.end.text_anchor.to_offset(&snapshot),
3043 )
3044 })
3045 .collect()
3046 });
3047 self.serialize_folds = cx.background_spawn(async move {
3048 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3049 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3050 .await
3051 .with_context(|| {
3052 format!(
3053 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3054 )
3055 })
3056 .log_err();
3057 });
3058 }
3059
3060 pub fn sync_selections(
3061 &mut self,
3062 other: Entity<Editor>,
3063 cx: &mut Context<Self>,
3064 ) -> gpui::Subscription {
3065 let other_selections = other.read(cx).selections.disjoint.to_vec();
3066 self.selections.change_with(cx, |selections| {
3067 selections.select_anchors(other_selections);
3068 });
3069
3070 let other_subscription =
3071 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3072 EditorEvent::SelectionsChanged { local: true } => {
3073 let other_selections = other.read(cx).selections.disjoint.to_vec();
3074 if other_selections.is_empty() {
3075 return;
3076 }
3077 this.selections.change_with(cx, |selections| {
3078 selections.select_anchors(other_selections);
3079 });
3080 }
3081 _ => {}
3082 });
3083
3084 let this_subscription =
3085 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3086 EditorEvent::SelectionsChanged { local: true } => {
3087 let these_selections = this.selections.disjoint.to_vec();
3088 if these_selections.is_empty() {
3089 return;
3090 }
3091 other.update(cx, |other_editor, cx| {
3092 other_editor.selections.change_with(cx, |selections| {
3093 selections.select_anchors(these_selections);
3094 })
3095 });
3096 }
3097 _ => {}
3098 });
3099
3100 Subscription::join(other_subscription, this_subscription)
3101 }
3102
3103 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3104 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3105 /// effects of selection change occur at the end of the transaction.
3106 pub fn change_selections<R>(
3107 &mut self,
3108 effects: impl Into<SelectionEffects>,
3109 window: &mut Window,
3110 cx: &mut Context<Self>,
3111 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3112 ) -> R {
3113 let effects = effects.into();
3114 if let Some(state) = &mut self.deferred_selection_effects_state {
3115 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3116 state.effects.completions = effects.completions;
3117 state.effects.nav_history |= effects.nav_history;
3118 let (changed, result) = self.selections.change_with(cx, change);
3119 state.changed |= changed;
3120 return result;
3121 }
3122 let mut state = DeferredSelectionEffectsState {
3123 changed: false,
3124 effects,
3125 old_cursor_position: self.selections.newest_anchor().head(),
3126 history_entry: SelectionHistoryEntry {
3127 selections: self.selections.disjoint_anchors(),
3128 select_next_state: self.select_next_state.clone(),
3129 select_prev_state: self.select_prev_state.clone(),
3130 add_selections_state: self.add_selections_state.clone(),
3131 },
3132 };
3133 let (changed, result) = self.selections.change_with(cx, change);
3134 state.changed = state.changed || changed;
3135 if self.defer_selection_effects {
3136 self.deferred_selection_effects_state = Some(state);
3137 } else {
3138 self.apply_selection_effects(state, window, cx);
3139 }
3140 result
3141 }
3142
3143 /// Defers the effects of selection change, so that the effects of multiple calls to
3144 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3145 /// to selection history and the state of popovers based on selection position aren't
3146 /// erroneously updated.
3147 pub fn with_selection_effects_deferred<R>(
3148 &mut self,
3149 window: &mut Window,
3150 cx: &mut Context<Self>,
3151 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3152 ) -> R {
3153 let already_deferred = self.defer_selection_effects;
3154 self.defer_selection_effects = true;
3155 let result = update(self, window, cx);
3156 if !already_deferred {
3157 self.defer_selection_effects = false;
3158 if let Some(state) = self.deferred_selection_effects_state.take() {
3159 self.apply_selection_effects(state, window, cx);
3160 }
3161 }
3162 result
3163 }
3164
3165 fn apply_selection_effects(
3166 &mut self,
3167 state: DeferredSelectionEffectsState,
3168 window: &mut Window,
3169 cx: &mut Context<Self>,
3170 ) {
3171 if state.changed {
3172 self.selection_history.push(state.history_entry);
3173
3174 if let Some(autoscroll) = state.effects.scroll {
3175 self.request_autoscroll(autoscroll, cx);
3176 }
3177
3178 let old_cursor_position = &state.old_cursor_position;
3179
3180 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3181
3182 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3183 self.show_signature_help(&ShowSignatureHelp, window, cx);
3184 }
3185 }
3186 }
3187
3188 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3189 where
3190 I: IntoIterator<Item = (Range<S>, T)>,
3191 S: ToOffset,
3192 T: Into<Arc<str>>,
3193 {
3194 if self.read_only(cx) {
3195 return;
3196 }
3197
3198 self.buffer
3199 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3200 }
3201
3202 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3203 where
3204 I: IntoIterator<Item = (Range<S>, T)>,
3205 S: ToOffset,
3206 T: Into<Arc<str>>,
3207 {
3208 if self.read_only(cx) {
3209 return;
3210 }
3211
3212 self.buffer.update(cx, |buffer, cx| {
3213 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3214 });
3215 }
3216
3217 pub fn edit_with_block_indent<I, S, T>(
3218 &mut self,
3219 edits: I,
3220 original_indent_columns: Vec<Option<u32>>,
3221 cx: &mut Context<Self>,
3222 ) where
3223 I: IntoIterator<Item = (Range<S>, T)>,
3224 S: ToOffset,
3225 T: Into<Arc<str>>,
3226 {
3227 if self.read_only(cx) {
3228 return;
3229 }
3230
3231 self.buffer.update(cx, |buffer, cx| {
3232 buffer.edit(
3233 edits,
3234 Some(AutoindentMode::Block {
3235 original_indent_columns,
3236 }),
3237 cx,
3238 )
3239 });
3240 }
3241
3242 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3243 self.hide_context_menu(window, cx);
3244
3245 match phase {
3246 SelectPhase::Begin {
3247 position,
3248 add,
3249 click_count,
3250 } => self.begin_selection(position, add, click_count, window, cx),
3251 SelectPhase::BeginColumnar {
3252 position,
3253 goal_column,
3254 reset,
3255 mode,
3256 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3257 SelectPhase::Extend {
3258 position,
3259 click_count,
3260 } => self.extend_selection(position, click_count, window, cx),
3261 SelectPhase::Update {
3262 position,
3263 goal_column,
3264 scroll_delta,
3265 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3266 SelectPhase::End => self.end_selection(window, cx),
3267 }
3268 }
3269
3270 fn extend_selection(
3271 &mut self,
3272 position: DisplayPoint,
3273 click_count: usize,
3274 window: &mut Window,
3275 cx: &mut Context<Self>,
3276 ) {
3277 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3278 let tail = self.selections.newest::<usize>(cx).tail();
3279 self.begin_selection(position, false, click_count, window, cx);
3280
3281 let position = position.to_offset(&display_map, Bias::Left);
3282 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3283
3284 let mut pending_selection = self
3285 .selections
3286 .pending_anchor()
3287 .expect("extend_selection not called with pending selection");
3288 if position >= tail {
3289 pending_selection.start = tail_anchor;
3290 } else {
3291 pending_selection.end = tail_anchor;
3292 pending_selection.reversed = true;
3293 }
3294
3295 let mut pending_mode = self.selections.pending_mode().unwrap();
3296 match &mut pending_mode {
3297 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3298 _ => {}
3299 }
3300
3301 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3302 SelectionEffects::scroll(Autoscroll::fit())
3303 } else {
3304 SelectionEffects::no_scroll()
3305 };
3306
3307 self.change_selections(effects, window, cx, |s| {
3308 s.set_pending(pending_selection, pending_mode)
3309 });
3310 }
3311
3312 fn begin_selection(
3313 &mut self,
3314 position: DisplayPoint,
3315 add: bool,
3316 click_count: usize,
3317 window: &mut Window,
3318 cx: &mut Context<Self>,
3319 ) {
3320 if !self.focus_handle.is_focused(window) {
3321 self.last_focused_descendant = None;
3322 window.focus(&self.focus_handle);
3323 }
3324
3325 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3326 let buffer = &display_map.buffer_snapshot;
3327 let position = display_map.clip_point(position, Bias::Left);
3328
3329 let start;
3330 let end;
3331 let mode;
3332 let mut auto_scroll;
3333 match click_count {
3334 1 => {
3335 start = buffer.anchor_before(position.to_point(&display_map));
3336 end = start;
3337 mode = SelectMode::Character;
3338 auto_scroll = true;
3339 }
3340 2 => {
3341 let range = movement::surrounding_word(&display_map, position);
3342 start = buffer.anchor_before(range.start.to_point(&display_map));
3343 end = buffer.anchor_before(range.end.to_point(&display_map));
3344 mode = SelectMode::Word(start..end);
3345 auto_scroll = true;
3346 }
3347 3 => {
3348 let position = display_map
3349 .clip_point(position, Bias::Left)
3350 .to_point(&display_map);
3351 let line_start = display_map.prev_line_boundary(position).0;
3352 let next_line_start = buffer.clip_point(
3353 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3354 Bias::Left,
3355 );
3356 start = buffer.anchor_before(line_start);
3357 end = buffer.anchor_before(next_line_start);
3358 mode = SelectMode::Line(start..end);
3359 auto_scroll = true;
3360 }
3361 _ => {
3362 start = buffer.anchor_before(0);
3363 end = buffer.anchor_before(buffer.len());
3364 mode = SelectMode::All;
3365 auto_scroll = false;
3366 }
3367 }
3368 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3369
3370 let point_to_delete: Option<usize> = {
3371 let selected_points: Vec<Selection<Point>> =
3372 self.selections.disjoint_in_range(start..end, cx);
3373
3374 if !add || click_count > 1 {
3375 None
3376 } else if !selected_points.is_empty() {
3377 Some(selected_points[0].id)
3378 } else {
3379 let clicked_point_already_selected =
3380 self.selections.disjoint.iter().find(|selection| {
3381 selection.start.to_point(buffer) == start.to_point(buffer)
3382 || selection.end.to_point(buffer) == end.to_point(buffer)
3383 });
3384
3385 clicked_point_already_selected.map(|selection| selection.id)
3386 }
3387 };
3388
3389 let selections_count = self.selections.count();
3390
3391 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3392 if let Some(point_to_delete) = point_to_delete {
3393 s.delete(point_to_delete);
3394
3395 if selections_count == 1 {
3396 s.set_pending_anchor_range(start..end, mode);
3397 }
3398 } else {
3399 if !add {
3400 s.clear_disjoint();
3401 }
3402
3403 s.set_pending_anchor_range(start..end, mode);
3404 }
3405 });
3406 }
3407
3408 fn begin_columnar_selection(
3409 &mut self,
3410 position: DisplayPoint,
3411 goal_column: u32,
3412 reset: bool,
3413 mode: ColumnarMode,
3414 window: &mut Window,
3415 cx: &mut Context<Self>,
3416 ) {
3417 if !self.focus_handle.is_focused(window) {
3418 self.last_focused_descendant = None;
3419 window.focus(&self.focus_handle);
3420 }
3421
3422 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3423
3424 if reset {
3425 let pointer_position = display_map
3426 .buffer_snapshot
3427 .anchor_before(position.to_point(&display_map));
3428
3429 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3430 s.clear_disjoint();
3431 s.set_pending_anchor_range(
3432 pointer_position..pointer_position,
3433 SelectMode::Character,
3434 );
3435 });
3436 };
3437
3438 let tail = self.selections.newest::<Point>(cx).tail();
3439 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3440 self.columnar_selection_state = match mode {
3441 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3442 selection_tail: selection_anchor,
3443 display_point: if reset {
3444 if position.column() != goal_column {
3445 Some(DisplayPoint::new(position.row(), goal_column))
3446 } else {
3447 None
3448 }
3449 } else {
3450 None
3451 },
3452 }),
3453 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3454 selection_tail: selection_anchor,
3455 }),
3456 };
3457
3458 if !reset {
3459 self.select_columns(position, goal_column, &display_map, window, cx);
3460 }
3461 }
3462
3463 fn update_selection(
3464 &mut self,
3465 position: DisplayPoint,
3466 goal_column: u32,
3467 scroll_delta: gpui::Point<f32>,
3468 window: &mut Window,
3469 cx: &mut Context<Self>,
3470 ) {
3471 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3472
3473 if self.columnar_selection_state.is_some() {
3474 self.select_columns(position, goal_column, &display_map, window, cx);
3475 } else if let Some(mut pending) = self.selections.pending_anchor() {
3476 let buffer = self.buffer.read(cx).snapshot(cx);
3477 let head;
3478 let tail;
3479 let mode = self.selections.pending_mode().unwrap();
3480 match &mode {
3481 SelectMode::Character => {
3482 head = position.to_point(&display_map);
3483 tail = pending.tail().to_point(&buffer);
3484 }
3485 SelectMode::Word(original_range) => {
3486 let original_display_range = original_range.start.to_display_point(&display_map)
3487 ..original_range.end.to_display_point(&display_map);
3488 let original_buffer_range = original_display_range.start.to_point(&display_map)
3489 ..original_display_range.end.to_point(&display_map);
3490 if movement::is_inside_word(&display_map, position)
3491 || original_display_range.contains(&position)
3492 {
3493 let word_range = movement::surrounding_word(&display_map, position);
3494 if word_range.start < original_display_range.start {
3495 head = word_range.start.to_point(&display_map);
3496 } else {
3497 head = word_range.end.to_point(&display_map);
3498 }
3499 } else {
3500 head = position.to_point(&display_map);
3501 }
3502
3503 if head <= original_buffer_range.start {
3504 tail = original_buffer_range.end;
3505 } else {
3506 tail = original_buffer_range.start;
3507 }
3508 }
3509 SelectMode::Line(original_range) => {
3510 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3511
3512 let position = display_map
3513 .clip_point(position, Bias::Left)
3514 .to_point(&display_map);
3515 let line_start = display_map.prev_line_boundary(position).0;
3516 let next_line_start = buffer.clip_point(
3517 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3518 Bias::Left,
3519 );
3520
3521 if line_start < original_range.start {
3522 head = line_start
3523 } else {
3524 head = next_line_start
3525 }
3526
3527 if head <= original_range.start {
3528 tail = original_range.end;
3529 } else {
3530 tail = original_range.start;
3531 }
3532 }
3533 SelectMode::All => {
3534 return;
3535 }
3536 };
3537
3538 if head < tail {
3539 pending.start = buffer.anchor_before(head);
3540 pending.end = buffer.anchor_before(tail);
3541 pending.reversed = true;
3542 } else {
3543 pending.start = buffer.anchor_before(tail);
3544 pending.end = buffer.anchor_before(head);
3545 pending.reversed = false;
3546 }
3547
3548 self.change_selections(None, window, cx, |s| {
3549 s.set_pending(pending, mode);
3550 });
3551 } else {
3552 log::error!("update_selection dispatched with no pending selection");
3553 return;
3554 }
3555
3556 self.apply_scroll_delta(scroll_delta, window, cx);
3557 cx.notify();
3558 }
3559
3560 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3561 self.columnar_selection_state.take();
3562 if self.selections.pending_anchor().is_some() {
3563 let selections = self.selections.all::<usize>(cx);
3564 self.change_selections(None, window, cx, |s| {
3565 s.select(selections);
3566 s.clear_pending();
3567 });
3568 }
3569 }
3570
3571 fn select_columns(
3572 &mut self,
3573 head: DisplayPoint,
3574 goal_column: u32,
3575 display_map: &DisplaySnapshot,
3576 window: &mut Window,
3577 cx: &mut Context<Self>,
3578 ) {
3579 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3580 return;
3581 };
3582
3583 let tail = match columnar_state {
3584 ColumnarSelectionState::FromMouse {
3585 selection_tail,
3586 display_point,
3587 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3588 ColumnarSelectionState::FromSelection { selection_tail } => {
3589 selection_tail.to_display_point(&display_map)
3590 }
3591 };
3592
3593 let start_row = cmp::min(tail.row(), head.row());
3594 let end_row = cmp::max(tail.row(), head.row());
3595 let start_column = cmp::min(tail.column(), goal_column);
3596 let end_column = cmp::max(tail.column(), goal_column);
3597 let reversed = start_column < tail.column();
3598
3599 let selection_ranges = (start_row.0..=end_row.0)
3600 .map(DisplayRow)
3601 .filter_map(|row| {
3602 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3603 || start_column <= display_map.line_len(row))
3604 && !display_map.is_block_line(row)
3605 {
3606 let start = display_map
3607 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3608 .to_point(display_map);
3609 let end = display_map
3610 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3611 .to_point(display_map);
3612 if reversed {
3613 Some(end..start)
3614 } else {
3615 Some(start..end)
3616 }
3617 } else {
3618 None
3619 }
3620 })
3621 .collect::<Vec<_>>();
3622
3623 let ranges = match columnar_state {
3624 ColumnarSelectionState::FromMouse { .. } => {
3625 let mut non_empty_ranges = selection_ranges
3626 .iter()
3627 .filter(|selection_range| selection_range.start != selection_range.end)
3628 .peekable();
3629 if non_empty_ranges.peek().is_some() {
3630 non_empty_ranges.cloned().collect()
3631 } else {
3632 selection_ranges
3633 }
3634 }
3635 _ => selection_ranges,
3636 };
3637
3638 self.change_selections(None, window, cx, |s| {
3639 s.select_ranges(ranges);
3640 });
3641 cx.notify();
3642 }
3643
3644 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3645 self.selections
3646 .all_adjusted(cx)
3647 .iter()
3648 .any(|selection| !selection.is_empty())
3649 }
3650
3651 pub fn has_pending_nonempty_selection(&self) -> bool {
3652 let pending_nonempty_selection = match self.selections.pending_anchor() {
3653 Some(Selection { start, end, .. }) => start != end,
3654 None => false,
3655 };
3656
3657 pending_nonempty_selection
3658 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3659 }
3660
3661 pub fn has_pending_selection(&self) -> bool {
3662 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3663 }
3664
3665 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3666 self.selection_mark_mode = false;
3667 self.selection_drag_state = SelectionDragState::None;
3668
3669 if self.clear_expanded_diff_hunks(cx) {
3670 cx.notify();
3671 return;
3672 }
3673 if self.dismiss_menus_and_popups(true, window, cx) {
3674 return;
3675 }
3676
3677 if self.mode.is_full()
3678 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3679 {
3680 return;
3681 }
3682
3683 cx.propagate();
3684 }
3685
3686 pub fn dismiss_menus_and_popups(
3687 &mut self,
3688 is_user_requested: bool,
3689 window: &mut Window,
3690 cx: &mut Context<Self>,
3691 ) -> bool {
3692 if self.take_rename(false, window, cx).is_some() {
3693 return true;
3694 }
3695
3696 if hide_hover(self, cx) {
3697 return true;
3698 }
3699
3700 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3701 return true;
3702 }
3703
3704 if self.hide_context_menu(window, cx).is_some() {
3705 return true;
3706 }
3707
3708 if self.mouse_context_menu.take().is_some() {
3709 return true;
3710 }
3711
3712 if is_user_requested && self.discard_inline_completion(true, cx) {
3713 return true;
3714 }
3715
3716 if self.snippet_stack.pop().is_some() {
3717 return true;
3718 }
3719
3720 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3721 self.dismiss_diagnostics(cx);
3722 return true;
3723 }
3724
3725 false
3726 }
3727
3728 fn linked_editing_ranges_for(
3729 &self,
3730 selection: Range<text::Anchor>,
3731 cx: &App,
3732 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3733 if self.linked_edit_ranges.is_empty() {
3734 return None;
3735 }
3736 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3737 selection.end.buffer_id.and_then(|end_buffer_id| {
3738 if selection.start.buffer_id != Some(end_buffer_id) {
3739 return None;
3740 }
3741 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3742 let snapshot = buffer.read(cx).snapshot();
3743 self.linked_edit_ranges
3744 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3745 .map(|ranges| (ranges, snapshot, buffer))
3746 })?;
3747 use text::ToOffset as TO;
3748 // find offset from the start of current range to current cursor position
3749 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3750
3751 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3752 let start_difference = start_offset - start_byte_offset;
3753 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3754 let end_difference = end_offset - start_byte_offset;
3755 // Current range has associated linked ranges.
3756 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3757 for range in linked_ranges.iter() {
3758 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3759 let end_offset = start_offset + end_difference;
3760 let start_offset = start_offset + start_difference;
3761 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3762 continue;
3763 }
3764 if self.selections.disjoint_anchor_ranges().any(|s| {
3765 if s.start.buffer_id != selection.start.buffer_id
3766 || s.end.buffer_id != selection.end.buffer_id
3767 {
3768 return false;
3769 }
3770 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3771 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3772 }) {
3773 continue;
3774 }
3775 let start = buffer_snapshot.anchor_after(start_offset);
3776 let end = buffer_snapshot.anchor_after(end_offset);
3777 linked_edits
3778 .entry(buffer.clone())
3779 .or_default()
3780 .push(start..end);
3781 }
3782 Some(linked_edits)
3783 }
3784
3785 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3786 let text: Arc<str> = text.into();
3787
3788 if self.read_only(cx) {
3789 return;
3790 }
3791
3792 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3793
3794 let selections = self.selections.all_adjusted(cx);
3795 let mut bracket_inserted = false;
3796 let mut edits = Vec::new();
3797 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3798 let mut new_selections = Vec::with_capacity(selections.len());
3799 let mut new_autoclose_regions = Vec::new();
3800 let snapshot = self.buffer.read(cx).read(cx);
3801 let mut clear_linked_edit_ranges = false;
3802
3803 for (selection, autoclose_region) in
3804 self.selections_with_autoclose_regions(selections, &snapshot)
3805 {
3806 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3807 // Determine if the inserted text matches the opening or closing
3808 // bracket of any of this language's bracket pairs.
3809 let mut bracket_pair = None;
3810 let mut is_bracket_pair_start = false;
3811 let mut is_bracket_pair_end = false;
3812 if !text.is_empty() {
3813 let mut bracket_pair_matching_end = None;
3814 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3815 // and they are removing the character that triggered IME popup.
3816 for (pair, enabled) in scope.brackets() {
3817 if !pair.close && !pair.surround {
3818 continue;
3819 }
3820
3821 if enabled && pair.start.ends_with(text.as_ref()) {
3822 let prefix_len = pair.start.len() - text.len();
3823 let preceding_text_matches_prefix = prefix_len == 0
3824 || (selection.start.column >= (prefix_len as u32)
3825 && snapshot.contains_str_at(
3826 Point::new(
3827 selection.start.row,
3828 selection.start.column - (prefix_len as u32),
3829 ),
3830 &pair.start[..prefix_len],
3831 ));
3832 if preceding_text_matches_prefix {
3833 bracket_pair = Some(pair.clone());
3834 is_bracket_pair_start = true;
3835 break;
3836 }
3837 }
3838 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3839 {
3840 // take first bracket pair matching end, but don't break in case a later bracket
3841 // pair matches start
3842 bracket_pair_matching_end = Some(pair.clone());
3843 }
3844 }
3845 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3846 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3847 is_bracket_pair_end = true;
3848 }
3849 }
3850
3851 if let Some(bracket_pair) = bracket_pair {
3852 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3853 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3854 let auto_surround =
3855 self.use_auto_surround && snapshot_settings.use_auto_surround;
3856 if selection.is_empty() {
3857 if is_bracket_pair_start {
3858 // If the inserted text is a suffix of an opening bracket and the
3859 // selection is preceded by the rest of the opening bracket, then
3860 // insert the closing bracket.
3861 let following_text_allows_autoclose = snapshot
3862 .chars_at(selection.start)
3863 .next()
3864 .map_or(true, |c| scope.should_autoclose_before(c));
3865
3866 let preceding_text_allows_autoclose = selection.start.column == 0
3867 || snapshot.reversed_chars_at(selection.start).next().map_or(
3868 true,
3869 |c| {
3870 bracket_pair.start != bracket_pair.end
3871 || !snapshot
3872 .char_classifier_at(selection.start)
3873 .is_word(c)
3874 },
3875 );
3876
3877 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3878 && bracket_pair.start.len() == 1
3879 {
3880 let target = bracket_pair.start.chars().next().unwrap();
3881 let current_line_count = snapshot
3882 .reversed_chars_at(selection.start)
3883 .take_while(|&c| c != '\n')
3884 .filter(|&c| c == target)
3885 .count();
3886 current_line_count % 2 == 1
3887 } else {
3888 false
3889 };
3890
3891 if autoclose
3892 && bracket_pair.close
3893 && following_text_allows_autoclose
3894 && preceding_text_allows_autoclose
3895 && !is_closing_quote
3896 {
3897 let anchor = snapshot.anchor_before(selection.end);
3898 new_selections.push((selection.map(|_| anchor), text.len()));
3899 new_autoclose_regions.push((
3900 anchor,
3901 text.len(),
3902 selection.id,
3903 bracket_pair.clone(),
3904 ));
3905 edits.push((
3906 selection.range(),
3907 format!("{}{}", text, bracket_pair.end).into(),
3908 ));
3909 bracket_inserted = true;
3910 continue;
3911 }
3912 }
3913
3914 if let Some(region) = autoclose_region {
3915 // If the selection is followed by an auto-inserted closing bracket,
3916 // then don't insert that closing bracket again; just move the selection
3917 // past the closing bracket.
3918 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3919 && text.as_ref() == region.pair.end.as_str();
3920 if should_skip {
3921 let anchor = snapshot.anchor_after(selection.end);
3922 new_selections
3923 .push((selection.map(|_| anchor), region.pair.end.len()));
3924 continue;
3925 }
3926 }
3927
3928 let always_treat_brackets_as_autoclosed = snapshot
3929 .language_settings_at(selection.start, cx)
3930 .always_treat_brackets_as_autoclosed;
3931 if always_treat_brackets_as_autoclosed
3932 && is_bracket_pair_end
3933 && snapshot.contains_str_at(selection.end, text.as_ref())
3934 {
3935 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3936 // and the inserted text is a closing bracket and the selection is followed
3937 // by the closing bracket then move the selection past the closing bracket.
3938 let anchor = snapshot.anchor_after(selection.end);
3939 new_selections.push((selection.map(|_| anchor), text.len()));
3940 continue;
3941 }
3942 }
3943 // If an opening bracket is 1 character long and is typed while
3944 // text is selected, then surround that text with the bracket pair.
3945 else if auto_surround
3946 && bracket_pair.surround
3947 && is_bracket_pair_start
3948 && bracket_pair.start.chars().count() == 1
3949 {
3950 edits.push((selection.start..selection.start, text.clone()));
3951 edits.push((
3952 selection.end..selection.end,
3953 bracket_pair.end.as_str().into(),
3954 ));
3955 bracket_inserted = true;
3956 new_selections.push((
3957 Selection {
3958 id: selection.id,
3959 start: snapshot.anchor_after(selection.start),
3960 end: snapshot.anchor_before(selection.end),
3961 reversed: selection.reversed,
3962 goal: selection.goal,
3963 },
3964 0,
3965 ));
3966 continue;
3967 }
3968 }
3969 }
3970
3971 if self.auto_replace_emoji_shortcode
3972 && selection.is_empty()
3973 && text.as_ref().ends_with(':')
3974 {
3975 if let Some(possible_emoji_short_code) =
3976 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3977 {
3978 if !possible_emoji_short_code.is_empty() {
3979 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3980 let emoji_shortcode_start = Point::new(
3981 selection.start.row,
3982 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3983 );
3984
3985 // Remove shortcode from buffer
3986 edits.push((
3987 emoji_shortcode_start..selection.start,
3988 "".to_string().into(),
3989 ));
3990 new_selections.push((
3991 Selection {
3992 id: selection.id,
3993 start: snapshot.anchor_after(emoji_shortcode_start),
3994 end: snapshot.anchor_before(selection.start),
3995 reversed: selection.reversed,
3996 goal: selection.goal,
3997 },
3998 0,
3999 ));
4000
4001 // Insert emoji
4002 let selection_start_anchor = snapshot.anchor_after(selection.start);
4003 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4004 edits.push((selection.start..selection.end, emoji.to_string().into()));
4005
4006 continue;
4007 }
4008 }
4009 }
4010 }
4011
4012 // If not handling any auto-close operation, then just replace the selected
4013 // text with the given input and move the selection to the end of the
4014 // newly inserted text.
4015 let anchor = snapshot.anchor_after(selection.end);
4016 if !self.linked_edit_ranges.is_empty() {
4017 let start_anchor = snapshot.anchor_before(selection.start);
4018
4019 let is_word_char = text.chars().next().map_or(true, |char| {
4020 let classifier = snapshot
4021 .char_classifier_at(start_anchor.to_offset(&snapshot))
4022 .ignore_punctuation(true);
4023 classifier.is_word(char)
4024 });
4025
4026 if is_word_char {
4027 if let Some(ranges) = self
4028 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4029 {
4030 for (buffer, edits) in ranges {
4031 linked_edits
4032 .entry(buffer.clone())
4033 .or_default()
4034 .extend(edits.into_iter().map(|range| (range, text.clone())));
4035 }
4036 }
4037 } else {
4038 clear_linked_edit_ranges = true;
4039 }
4040 }
4041
4042 new_selections.push((selection.map(|_| anchor), 0));
4043 edits.push((selection.start..selection.end, text.clone()));
4044 }
4045
4046 drop(snapshot);
4047
4048 self.transact(window, cx, |this, window, cx| {
4049 if clear_linked_edit_ranges {
4050 this.linked_edit_ranges.clear();
4051 }
4052 let initial_buffer_versions =
4053 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4054
4055 this.buffer.update(cx, |buffer, cx| {
4056 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4057 });
4058 for (buffer, edits) in linked_edits {
4059 buffer.update(cx, |buffer, cx| {
4060 let snapshot = buffer.snapshot();
4061 let edits = edits
4062 .into_iter()
4063 .map(|(range, text)| {
4064 use text::ToPoint as TP;
4065 let end_point = TP::to_point(&range.end, &snapshot);
4066 let start_point = TP::to_point(&range.start, &snapshot);
4067 (start_point..end_point, text)
4068 })
4069 .sorted_by_key(|(range, _)| range.start);
4070 buffer.edit(edits, None, cx);
4071 })
4072 }
4073 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4074 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4075 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4076 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4077 .zip(new_selection_deltas)
4078 .map(|(selection, delta)| Selection {
4079 id: selection.id,
4080 start: selection.start + delta,
4081 end: selection.end + delta,
4082 reversed: selection.reversed,
4083 goal: SelectionGoal::None,
4084 })
4085 .collect::<Vec<_>>();
4086
4087 let mut i = 0;
4088 for (position, delta, selection_id, pair) in new_autoclose_regions {
4089 let position = position.to_offset(&map.buffer_snapshot) + delta;
4090 let start = map.buffer_snapshot.anchor_before(position);
4091 let end = map.buffer_snapshot.anchor_after(position);
4092 while let Some(existing_state) = this.autoclose_regions.get(i) {
4093 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4094 Ordering::Less => i += 1,
4095 Ordering::Greater => break,
4096 Ordering::Equal => {
4097 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4098 Ordering::Less => i += 1,
4099 Ordering::Equal => break,
4100 Ordering::Greater => break,
4101 }
4102 }
4103 }
4104 }
4105 this.autoclose_regions.insert(
4106 i,
4107 AutocloseRegion {
4108 selection_id,
4109 range: start..end,
4110 pair,
4111 },
4112 );
4113 }
4114
4115 let had_active_inline_completion = this.has_active_inline_completion();
4116 this.change_selections(
4117 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4118 window,
4119 cx,
4120 |s| s.select(new_selections),
4121 );
4122
4123 if !bracket_inserted {
4124 if let Some(on_type_format_task) =
4125 this.trigger_on_type_formatting(text.to_string(), window, cx)
4126 {
4127 on_type_format_task.detach_and_log_err(cx);
4128 }
4129 }
4130
4131 let editor_settings = EditorSettings::get_global(cx);
4132 if bracket_inserted
4133 && (editor_settings.auto_signature_help
4134 || editor_settings.show_signature_help_after_edits)
4135 {
4136 this.show_signature_help(&ShowSignatureHelp, window, cx);
4137 }
4138
4139 let trigger_in_words =
4140 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4141 if this.hard_wrap.is_some() {
4142 let latest: Range<Point> = this.selections.newest(cx).range();
4143 if latest.is_empty()
4144 && this
4145 .buffer()
4146 .read(cx)
4147 .snapshot(cx)
4148 .line_len(MultiBufferRow(latest.start.row))
4149 == latest.start.column
4150 {
4151 this.rewrap_impl(
4152 RewrapOptions {
4153 override_language_settings: true,
4154 preserve_existing_whitespace: true,
4155 },
4156 cx,
4157 )
4158 }
4159 }
4160 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4161 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4162 this.refresh_inline_completion(true, false, window, cx);
4163 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4164 });
4165 }
4166
4167 fn find_possible_emoji_shortcode_at_position(
4168 snapshot: &MultiBufferSnapshot,
4169 position: Point,
4170 ) -> Option<String> {
4171 let mut chars = Vec::new();
4172 let mut found_colon = false;
4173 for char in snapshot.reversed_chars_at(position).take(100) {
4174 // Found a possible emoji shortcode in the middle of the buffer
4175 if found_colon {
4176 if char.is_whitespace() {
4177 chars.reverse();
4178 return Some(chars.iter().collect());
4179 }
4180 // If the previous character is not a whitespace, we are in the middle of a word
4181 // and we only want to complete the shortcode if the word is made up of other emojis
4182 let mut containing_word = String::new();
4183 for ch in snapshot
4184 .reversed_chars_at(position)
4185 .skip(chars.len() + 1)
4186 .take(100)
4187 {
4188 if ch.is_whitespace() {
4189 break;
4190 }
4191 containing_word.push(ch);
4192 }
4193 let containing_word = containing_word.chars().rev().collect::<String>();
4194 if util::word_consists_of_emojis(containing_word.as_str()) {
4195 chars.reverse();
4196 return Some(chars.iter().collect());
4197 }
4198 }
4199
4200 if char.is_whitespace() || !char.is_ascii() {
4201 return None;
4202 }
4203 if char == ':' {
4204 found_colon = true;
4205 } else {
4206 chars.push(char);
4207 }
4208 }
4209 // Found a possible emoji shortcode at the beginning of the buffer
4210 chars.reverse();
4211 Some(chars.iter().collect())
4212 }
4213
4214 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4215 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4216 self.transact(window, cx, |this, window, cx| {
4217 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4218 let selections = this.selections.all::<usize>(cx);
4219 let multi_buffer = this.buffer.read(cx);
4220 let buffer = multi_buffer.snapshot(cx);
4221 selections
4222 .iter()
4223 .map(|selection| {
4224 let start_point = selection.start.to_point(&buffer);
4225 let mut existing_indent =
4226 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4227 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4228 let start = selection.start;
4229 let end = selection.end;
4230 let selection_is_empty = start == end;
4231 let language_scope = buffer.language_scope_at(start);
4232 let (
4233 comment_delimiter,
4234 doc_delimiter,
4235 insert_extra_newline,
4236 indent_on_newline,
4237 indent_on_extra_newline,
4238 ) = if let Some(language) = &language_scope {
4239 let mut insert_extra_newline =
4240 insert_extra_newline_brackets(&buffer, start..end, language)
4241 || insert_extra_newline_tree_sitter(&buffer, start..end);
4242
4243 // Comment extension on newline is allowed only for cursor selections
4244 let comment_delimiter = maybe!({
4245 if !selection_is_empty {
4246 return None;
4247 }
4248
4249 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4250 return None;
4251 }
4252
4253 let delimiters = language.line_comment_prefixes();
4254 let max_len_of_delimiter =
4255 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4256 let (snapshot, range) =
4257 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4258
4259 let num_of_whitespaces = snapshot
4260 .chars_for_range(range.clone())
4261 .take_while(|c| c.is_whitespace())
4262 .count();
4263 let comment_candidate = snapshot
4264 .chars_for_range(range)
4265 .skip(num_of_whitespaces)
4266 .take(max_len_of_delimiter)
4267 .collect::<String>();
4268 let (delimiter, trimmed_len) = delimiters
4269 .iter()
4270 .filter_map(|delimiter| {
4271 let prefix = delimiter.trim_end();
4272 if comment_candidate.starts_with(prefix) {
4273 Some((delimiter, prefix.len()))
4274 } else {
4275 None
4276 }
4277 })
4278 .max_by_key(|(_, len)| *len)?;
4279
4280 let cursor_is_placed_after_comment_marker =
4281 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4282 if cursor_is_placed_after_comment_marker {
4283 Some(delimiter.clone())
4284 } else {
4285 None
4286 }
4287 });
4288
4289 let mut indent_on_newline = IndentSize::spaces(0);
4290 let mut indent_on_extra_newline = IndentSize::spaces(0);
4291
4292 let doc_delimiter = maybe!({
4293 if !selection_is_empty {
4294 return None;
4295 }
4296
4297 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4298 return None;
4299 }
4300
4301 let DocumentationConfig {
4302 start: start_tag,
4303 end: end_tag,
4304 prefix: delimiter,
4305 tab_size: len,
4306 } = language.documentation()?;
4307
4308 let is_within_block_comment = buffer
4309 .language_scope_at(start_point)
4310 .is_some_and(|scope| scope.override_name() == Some("comment"));
4311 if !is_within_block_comment {
4312 return None;
4313 }
4314
4315 let (snapshot, range) =
4316 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4317
4318 let num_of_whitespaces = snapshot
4319 .chars_for_range(range.clone())
4320 .take_while(|c| c.is_whitespace())
4321 .count();
4322
4323 // 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.
4324 let column = start_point.column;
4325 let cursor_is_after_start_tag = {
4326 let start_tag_len = start_tag.len();
4327 let start_tag_line = snapshot
4328 .chars_for_range(range.clone())
4329 .skip(num_of_whitespaces)
4330 .take(start_tag_len)
4331 .collect::<String>();
4332 if start_tag_line.starts_with(start_tag.as_ref()) {
4333 num_of_whitespaces + start_tag_len <= column as usize
4334 } else {
4335 false
4336 }
4337 };
4338
4339 let cursor_is_after_delimiter = {
4340 let delimiter_trim = delimiter.trim_end();
4341 let delimiter_line = snapshot
4342 .chars_for_range(range.clone())
4343 .skip(num_of_whitespaces)
4344 .take(delimiter_trim.len())
4345 .collect::<String>();
4346 if delimiter_line.starts_with(delimiter_trim) {
4347 num_of_whitespaces + delimiter_trim.len() <= column as usize
4348 } else {
4349 false
4350 }
4351 };
4352
4353 let cursor_is_before_end_tag_if_exists = {
4354 let mut char_position = 0u32;
4355 let mut end_tag_offset = None;
4356
4357 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4358 if let Some(byte_pos) = chunk.find(&**end_tag) {
4359 let chars_before_match =
4360 chunk[..byte_pos].chars().count() as u32;
4361 end_tag_offset =
4362 Some(char_position + chars_before_match);
4363 break 'outer;
4364 }
4365 char_position += chunk.chars().count() as u32;
4366 }
4367
4368 if let Some(end_tag_offset) = end_tag_offset {
4369 let cursor_is_before_end_tag = column <= end_tag_offset;
4370 if cursor_is_after_start_tag {
4371 if cursor_is_before_end_tag {
4372 insert_extra_newline = true;
4373 }
4374 let cursor_is_at_start_of_end_tag =
4375 column == end_tag_offset;
4376 if cursor_is_at_start_of_end_tag {
4377 indent_on_extra_newline.len = (*len).into();
4378 }
4379 }
4380 cursor_is_before_end_tag
4381 } else {
4382 true
4383 }
4384 };
4385
4386 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4387 && cursor_is_before_end_tag_if_exists
4388 {
4389 if cursor_is_after_start_tag {
4390 indent_on_newline.len = (*len).into();
4391 }
4392 Some(delimiter.clone())
4393 } else {
4394 None
4395 }
4396 });
4397
4398 (
4399 comment_delimiter,
4400 doc_delimiter,
4401 insert_extra_newline,
4402 indent_on_newline,
4403 indent_on_extra_newline,
4404 )
4405 } else {
4406 (
4407 None,
4408 None,
4409 false,
4410 IndentSize::default(),
4411 IndentSize::default(),
4412 )
4413 };
4414
4415 let prevent_auto_indent = doc_delimiter.is_some();
4416 let delimiter = comment_delimiter.or(doc_delimiter);
4417
4418 let capacity_for_delimiter =
4419 delimiter.as_deref().map(str::len).unwrap_or_default();
4420 let mut new_text = String::with_capacity(
4421 1 + capacity_for_delimiter
4422 + existing_indent.len as usize
4423 + indent_on_newline.len as usize
4424 + indent_on_extra_newline.len as usize,
4425 );
4426 new_text.push('\n');
4427 new_text.extend(existing_indent.chars());
4428 new_text.extend(indent_on_newline.chars());
4429
4430 if let Some(delimiter) = &delimiter {
4431 new_text.push_str(delimiter);
4432 }
4433
4434 if insert_extra_newline {
4435 new_text.push('\n');
4436 new_text.extend(existing_indent.chars());
4437 new_text.extend(indent_on_extra_newline.chars());
4438 }
4439
4440 let anchor = buffer.anchor_after(end);
4441 let new_selection = selection.map(|_| anchor);
4442 (
4443 ((start..end, new_text), prevent_auto_indent),
4444 (insert_extra_newline, new_selection),
4445 )
4446 })
4447 .unzip()
4448 };
4449
4450 let mut auto_indent_edits = Vec::new();
4451 let mut edits = Vec::new();
4452 for (edit, prevent_auto_indent) in edits_with_flags {
4453 if prevent_auto_indent {
4454 edits.push(edit);
4455 } else {
4456 auto_indent_edits.push(edit);
4457 }
4458 }
4459 if !edits.is_empty() {
4460 this.edit(edits, cx);
4461 }
4462 if !auto_indent_edits.is_empty() {
4463 this.edit_with_autoindent(auto_indent_edits, cx);
4464 }
4465
4466 let buffer = this.buffer.read(cx).snapshot(cx);
4467 let new_selections = selection_info
4468 .into_iter()
4469 .map(|(extra_newline_inserted, new_selection)| {
4470 let mut cursor = new_selection.end.to_point(&buffer);
4471 if extra_newline_inserted {
4472 cursor.row -= 1;
4473 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4474 }
4475 new_selection.map(|_| cursor)
4476 })
4477 .collect();
4478
4479 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4480 s.select(new_selections)
4481 });
4482 this.refresh_inline_completion(true, false, window, cx);
4483 });
4484 }
4485
4486 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4487 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4488
4489 let buffer = self.buffer.read(cx);
4490 let snapshot = buffer.snapshot(cx);
4491
4492 let mut edits = Vec::new();
4493 let mut rows = Vec::new();
4494
4495 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4496 let cursor = selection.head();
4497 let row = cursor.row;
4498
4499 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4500
4501 let newline = "\n".to_string();
4502 edits.push((start_of_line..start_of_line, newline));
4503
4504 rows.push(row + rows_inserted as u32);
4505 }
4506
4507 self.transact(window, cx, |editor, window, cx| {
4508 editor.edit(edits, cx);
4509
4510 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4511 let mut index = 0;
4512 s.move_cursors_with(|map, _, _| {
4513 let row = rows[index];
4514 index += 1;
4515
4516 let point = Point::new(row, 0);
4517 let boundary = map.next_line_boundary(point).1;
4518 let clipped = map.clip_point(boundary, Bias::Left);
4519
4520 (clipped, SelectionGoal::None)
4521 });
4522 });
4523
4524 let mut indent_edits = Vec::new();
4525 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4526 for row in rows {
4527 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4528 for (row, indent) in indents {
4529 if indent.len == 0 {
4530 continue;
4531 }
4532
4533 let text = match indent.kind {
4534 IndentKind::Space => " ".repeat(indent.len as usize),
4535 IndentKind::Tab => "\t".repeat(indent.len as usize),
4536 };
4537 let point = Point::new(row.0, 0);
4538 indent_edits.push((point..point, text));
4539 }
4540 }
4541 editor.edit(indent_edits, cx);
4542 });
4543 }
4544
4545 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4546 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4547
4548 let buffer = self.buffer.read(cx);
4549 let snapshot = buffer.snapshot(cx);
4550
4551 let mut edits = Vec::new();
4552 let mut rows = Vec::new();
4553 let mut rows_inserted = 0;
4554
4555 for selection in self.selections.all_adjusted(cx) {
4556 let cursor = selection.head();
4557 let row = cursor.row;
4558
4559 let point = Point::new(row + 1, 0);
4560 let start_of_line = snapshot.clip_point(point, Bias::Left);
4561
4562 let newline = "\n".to_string();
4563 edits.push((start_of_line..start_of_line, newline));
4564
4565 rows_inserted += 1;
4566 rows.push(row + rows_inserted);
4567 }
4568
4569 self.transact(window, cx, |editor, window, cx| {
4570 editor.edit(edits, cx);
4571
4572 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4573 let mut index = 0;
4574 s.move_cursors_with(|map, _, _| {
4575 let row = rows[index];
4576 index += 1;
4577
4578 let point = Point::new(row, 0);
4579 let boundary = map.next_line_boundary(point).1;
4580 let clipped = map.clip_point(boundary, Bias::Left);
4581
4582 (clipped, SelectionGoal::None)
4583 });
4584 });
4585
4586 let mut indent_edits = Vec::new();
4587 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4588 for row in rows {
4589 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4590 for (row, indent) in indents {
4591 if indent.len == 0 {
4592 continue;
4593 }
4594
4595 let text = match indent.kind {
4596 IndentKind::Space => " ".repeat(indent.len as usize),
4597 IndentKind::Tab => "\t".repeat(indent.len as usize),
4598 };
4599 let point = Point::new(row.0, 0);
4600 indent_edits.push((point..point, text));
4601 }
4602 }
4603 editor.edit(indent_edits, cx);
4604 });
4605 }
4606
4607 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4608 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4609 original_indent_columns: Vec::new(),
4610 });
4611 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4612 }
4613
4614 fn insert_with_autoindent_mode(
4615 &mut self,
4616 text: &str,
4617 autoindent_mode: Option<AutoindentMode>,
4618 window: &mut Window,
4619 cx: &mut Context<Self>,
4620 ) {
4621 if self.read_only(cx) {
4622 return;
4623 }
4624
4625 let text: Arc<str> = text.into();
4626 self.transact(window, cx, |this, window, cx| {
4627 let old_selections = this.selections.all_adjusted(cx);
4628 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4629 let anchors = {
4630 let snapshot = buffer.read(cx);
4631 old_selections
4632 .iter()
4633 .map(|s| {
4634 let anchor = snapshot.anchor_after(s.head());
4635 s.map(|_| anchor)
4636 })
4637 .collect::<Vec<_>>()
4638 };
4639 buffer.edit(
4640 old_selections
4641 .iter()
4642 .map(|s| (s.start..s.end, text.clone())),
4643 autoindent_mode,
4644 cx,
4645 );
4646 anchors
4647 });
4648
4649 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4650 s.select_anchors(selection_anchors);
4651 });
4652
4653 cx.notify();
4654 });
4655 }
4656
4657 fn trigger_completion_on_input(
4658 &mut self,
4659 text: &str,
4660 trigger_in_words: bool,
4661 window: &mut Window,
4662 cx: &mut Context<Self>,
4663 ) {
4664 let completions_source = self
4665 .context_menu
4666 .borrow()
4667 .as_ref()
4668 .and_then(|menu| match menu {
4669 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4670 CodeContextMenu::CodeActions(_) => None,
4671 });
4672
4673 match completions_source {
4674 Some(CompletionsMenuSource::Words) => {
4675 self.show_word_completions(&ShowWordCompletions, window, cx)
4676 }
4677 Some(CompletionsMenuSource::Normal)
4678 | Some(CompletionsMenuSource::SnippetChoices)
4679 | None
4680 if self.is_completion_trigger(
4681 text,
4682 trigger_in_words,
4683 completions_source.is_some(),
4684 cx,
4685 ) =>
4686 {
4687 self.show_completions(
4688 &ShowCompletions {
4689 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4690 },
4691 window,
4692 cx,
4693 )
4694 }
4695 _ => {
4696 self.hide_context_menu(window, cx);
4697 }
4698 }
4699 }
4700
4701 fn is_completion_trigger(
4702 &self,
4703 text: &str,
4704 trigger_in_words: bool,
4705 menu_is_open: bool,
4706 cx: &mut Context<Self>,
4707 ) -> bool {
4708 let position = self.selections.newest_anchor().head();
4709 let multibuffer = self.buffer.read(cx);
4710 let Some(buffer) = position
4711 .buffer_id
4712 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4713 else {
4714 return false;
4715 };
4716
4717 if let Some(completion_provider) = &self.completion_provider {
4718 completion_provider.is_completion_trigger(
4719 &buffer,
4720 position.text_anchor,
4721 text,
4722 trigger_in_words,
4723 menu_is_open,
4724 cx,
4725 )
4726 } else {
4727 false
4728 }
4729 }
4730
4731 /// If any empty selections is touching the start of its innermost containing autoclose
4732 /// region, expand it to select the brackets.
4733 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4734 let selections = self.selections.all::<usize>(cx);
4735 let buffer = self.buffer.read(cx).read(cx);
4736 let new_selections = self
4737 .selections_with_autoclose_regions(selections, &buffer)
4738 .map(|(mut selection, region)| {
4739 if !selection.is_empty() {
4740 return selection;
4741 }
4742
4743 if let Some(region) = region {
4744 let mut range = region.range.to_offset(&buffer);
4745 if selection.start == range.start && range.start >= region.pair.start.len() {
4746 range.start -= region.pair.start.len();
4747 if buffer.contains_str_at(range.start, ®ion.pair.start)
4748 && buffer.contains_str_at(range.end, ®ion.pair.end)
4749 {
4750 range.end += region.pair.end.len();
4751 selection.start = range.start;
4752 selection.end = range.end;
4753
4754 return selection;
4755 }
4756 }
4757 }
4758
4759 let always_treat_brackets_as_autoclosed = buffer
4760 .language_settings_at(selection.start, cx)
4761 .always_treat_brackets_as_autoclosed;
4762
4763 if !always_treat_brackets_as_autoclosed {
4764 return selection;
4765 }
4766
4767 if let Some(scope) = buffer.language_scope_at(selection.start) {
4768 for (pair, enabled) in scope.brackets() {
4769 if !enabled || !pair.close {
4770 continue;
4771 }
4772
4773 if buffer.contains_str_at(selection.start, &pair.end) {
4774 let pair_start_len = pair.start.len();
4775 if buffer.contains_str_at(
4776 selection.start.saturating_sub(pair_start_len),
4777 &pair.start,
4778 ) {
4779 selection.start -= pair_start_len;
4780 selection.end += pair.end.len();
4781
4782 return selection;
4783 }
4784 }
4785 }
4786 }
4787
4788 selection
4789 })
4790 .collect();
4791
4792 drop(buffer);
4793 self.change_selections(None, window, cx, |selections| {
4794 selections.select(new_selections)
4795 });
4796 }
4797
4798 /// Iterate the given selections, and for each one, find the smallest surrounding
4799 /// autoclose region. This uses the ordering of the selections and the autoclose
4800 /// regions to avoid repeated comparisons.
4801 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4802 &'a self,
4803 selections: impl IntoIterator<Item = Selection<D>>,
4804 buffer: &'a MultiBufferSnapshot,
4805 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4806 let mut i = 0;
4807 let mut regions = self.autoclose_regions.as_slice();
4808 selections.into_iter().map(move |selection| {
4809 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4810
4811 let mut enclosing = None;
4812 while let Some(pair_state) = regions.get(i) {
4813 if pair_state.range.end.to_offset(buffer) < range.start {
4814 regions = ®ions[i + 1..];
4815 i = 0;
4816 } else if pair_state.range.start.to_offset(buffer) > range.end {
4817 break;
4818 } else {
4819 if pair_state.selection_id == selection.id {
4820 enclosing = Some(pair_state);
4821 }
4822 i += 1;
4823 }
4824 }
4825
4826 (selection, enclosing)
4827 })
4828 }
4829
4830 /// Remove any autoclose regions that no longer contain their selection.
4831 fn invalidate_autoclose_regions(
4832 &mut self,
4833 mut selections: &[Selection<Anchor>],
4834 buffer: &MultiBufferSnapshot,
4835 ) {
4836 self.autoclose_regions.retain(|state| {
4837 let mut i = 0;
4838 while let Some(selection) = selections.get(i) {
4839 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4840 selections = &selections[1..];
4841 continue;
4842 }
4843 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4844 break;
4845 }
4846 if selection.id == state.selection_id {
4847 return true;
4848 } else {
4849 i += 1;
4850 }
4851 }
4852 false
4853 });
4854 }
4855
4856 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4857 let offset = position.to_offset(buffer);
4858 let (word_range, kind) = buffer.surrounding_word(offset, true);
4859 if offset > word_range.start && kind == Some(CharKind::Word) {
4860 Some(
4861 buffer
4862 .text_for_range(word_range.start..offset)
4863 .collect::<String>(),
4864 )
4865 } else {
4866 None
4867 }
4868 }
4869
4870 pub fn toggle_inline_values(
4871 &mut self,
4872 _: &ToggleInlineValues,
4873 _: &mut Window,
4874 cx: &mut Context<Self>,
4875 ) {
4876 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4877
4878 self.refresh_inline_values(cx);
4879 }
4880
4881 pub fn toggle_inlay_hints(
4882 &mut self,
4883 _: &ToggleInlayHints,
4884 _: &mut Window,
4885 cx: &mut Context<Self>,
4886 ) {
4887 self.refresh_inlay_hints(
4888 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4889 cx,
4890 );
4891 }
4892
4893 pub fn inlay_hints_enabled(&self) -> bool {
4894 self.inlay_hint_cache.enabled
4895 }
4896
4897 pub fn inline_values_enabled(&self) -> bool {
4898 self.inline_value_cache.enabled
4899 }
4900
4901 #[cfg(any(test, feature = "test-support"))]
4902 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4903 self.display_map
4904 .read(cx)
4905 .current_inlays()
4906 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4907 .cloned()
4908 .collect()
4909 }
4910
4911 #[cfg(any(test, feature = "test-support"))]
4912 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
4913 self.display_map
4914 .read(cx)
4915 .current_inlays()
4916 .cloned()
4917 .collect()
4918 }
4919
4920 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4921 if self.semantics_provider.is_none() || !self.mode.is_full() {
4922 return;
4923 }
4924
4925 let reason_description = reason.description();
4926 let ignore_debounce = matches!(
4927 reason,
4928 InlayHintRefreshReason::SettingsChange(_)
4929 | InlayHintRefreshReason::Toggle(_)
4930 | InlayHintRefreshReason::ExcerptsRemoved(_)
4931 | InlayHintRefreshReason::ModifiersChanged(_)
4932 );
4933 let (invalidate_cache, required_languages) = match reason {
4934 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4935 match self.inlay_hint_cache.modifiers_override(enabled) {
4936 Some(enabled) => {
4937 if enabled {
4938 (InvalidationStrategy::RefreshRequested, None)
4939 } else {
4940 self.splice_inlays(
4941 &self
4942 .visible_inlay_hints(cx)
4943 .iter()
4944 .map(|inlay| inlay.id)
4945 .collect::<Vec<InlayId>>(),
4946 Vec::new(),
4947 cx,
4948 );
4949 return;
4950 }
4951 }
4952 None => return,
4953 }
4954 }
4955 InlayHintRefreshReason::Toggle(enabled) => {
4956 if self.inlay_hint_cache.toggle(enabled) {
4957 if enabled {
4958 (InvalidationStrategy::RefreshRequested, None)
4959 } else {
4960 self.splice_inlays(
4961 &self
4962 .visible_inlay_hints(cx)
4963 .iter()
4964 .map(|inlay| inlay.id)
4965 .collect::<Vec<InlayId>>(),
4966 Vec::new(),
4967 cx,
4968 );
4969 return;
4970 }
4971 } else {
4972 return;
4973 }
4974 }
4975 InlayHintRefreshReason::SettingsChange(new_settings) => {
4976 match self.inlay_hint_cache.update_settings(
4977 &self.buffer,
4978 new_settings,
4979 self.visible_inlay_hints(cx),
4980 cx,
4981 ) {
4982 ControlFlow::Break(Some(InlaySplice {
4983 to_remove,
4984 to_insert,
4985 })) => {
4986 self.splice_inlays(&to_remove, to_insert, cx);
4987 return;
4988 }
4989 ControlFlow::Break(None) => return,
4990 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4991 }
4992 }
4993 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4994 if let Some(InlaySplice {
4995 to_remove,
4996 to_insert,
4997 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4998 {
4999 self.splice_inlays(&to_remove, to_insert, cx);
5000 }
5001 self.display_map.update(cx, |display_map, _| {
5002 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5003 });
5004 return;
5005 }
5006 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5007 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5008 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5009 }
5010 InlayHintRefreshReason::RefreshRequested => {
5011 (InvalidationStrategy::RefreshRequested, None)
5012 }
5013 };
5014
5015 if let Some(InlaySplice {
5016 to_remove,
5017 to_insert,
5018 }) = self.inlay_hint_cache.spawn_hint_refresh(
5019 reason_description,
5020 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
5021 invalidate_cache,
5022 ignore_debounce,
5023 cx,
5024 ) {
5025 self.splice_inlays(&to_remove, to_insert, cx);
5026 }
5027 }
5028
5029 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5030 self.display_map
5031 .read(cx)
5032 .current_inlays()
5033 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5034 .cloned()
5035 .collect()
5036 }
5037
5038 pub fn excerpts_for_inlay_hints_query(
5039 &self,
5040 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5041 cx: &mut Context<Editor>,
5042 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5043 let Some(project) = self.project.as_ref() else {
5044 return HashMap::default();
5045 };
5046 let project = project.read(cx);
5047 let multi_buffer = self.buffer().read(cx);
5048 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5049 let multi_buffer_visible_start = self
5050 .scroll_manager
5051 .anchor()
5052 .anchor
5053 .to_point(&multi_buffer_snapshot);
5054 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5055 multi_buffer_visible_start
5056 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5057 Bias::Left,
5058 );
5059 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5060 multi_buffer_snapshot
5061 .range_to_buffer_ranges(multi_buffer_visible_range)
5062 .into_iter()
5063 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5064 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5065 let buffer_file = project::File::from_dyn(buffer.file())?;
5066 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5067 let worktree_entry = buffer_worktree
5068 .read(cx)
5069 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5070 if worktree_entry.is_ignored {
5071 return None;
5072 }
5073
5074 let language = buffer.language()?;
5075 if let Some(restrict_to_languages) = restrict_to_languages {
5076 if !restrict_to_languages.contains(language) {
5077 return None;
5078 }
5079 }
5080 Some((
5081 excerpt_id,
5082 (
5083 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5084 buffer.version().clone(),
5085 excerpt_visible_range,
5086 ),
5087 ))
5088 })
5089 .collect()
5090 }
5091
5092 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5093 TextLayoutDetails {
5094 text_system: window.text_system().clone(),
5095 editor_style: self.style.clone().unwrap(),
5096 rem_size: window.rem_size(),
5097 scroll_anchor: self.scroll_manager.anchor(),
5098 visible_rows: self.visible_line_count(),
5099 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5100 }
5101 }
5102
5103 pub fn splice_inlays(
5104 &self,
5105 to_remove: &[InlayId],
5106 to_insert: Vec<Inlay>,
5107 cx: &mut Context<Self>,
5108 ) {
5109 self.display_map.update(cx, |display_map, cx| {
5110 display_map.splice_inlays(to_remove, to_insert, cx)
5111 });
5112 cx.notify();
5113 }
5114
5115 fn trigger_on_type_formatting(
5116 &self,
5117 input: String,
5118 window: &mut Window,
5119 cx: &mut Context<Self>,
5120 ) -> Option<Task<Result<()>>> {
5121 if input.len() != 1 {
5122 return None;
5123 }
5124
5125 let project = self.project.as_ref()?;
5126 let position = self.selections.newest_anchor().head();
5127 let (buffer, buffer_position) = self
5128 .buffer
5129 .read(cx)
5130 .text_anchor_for_position(position, cx)?;
5131
5132 let settings = language_settings::language_settings(
5133 buffer
5134 .read(cx)
5135 .language_at(buffer_position)
5136 .map(|l| l.name()),
5137 buffer.read(cx).file(),
5138 cx,
5139 );
5140 if !settings.use_on_type_format {
5141 return None;
5142 }
5143
5144 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5145 // hence we do LSP request & edit on host side only — add formats to host's history.
5146 let push_to_lsp_host_history = true;
5147 // If this is not the host, append its history with new edits.
5148 let push_to_client_history = project.read(cx).is_via_collab();
5149
5150 let on_type_formatting = project.update(cx, |project, cx| {
5151 project.on_type_format(
5152 buffer.clone(),
5153 buffer_position,
5154 input,
5155 push_to_lsp_host_history,
5156 cx,
5157 )
5158 });
5159 Some(cx.spawn_in(window, async move |editor, cx| {
5160 if let Some(transaction) = on_type_formatting.await? {
5161 if push_to_client_history {
5162 buffer
5163 .update(cx, |buffer, _| {
5164 buffer.push_transaction(transaction, Instant::now());
5165 buffer.finalize_last_transaction();
5166 })
5167 .ok();
5168 }
5169 editor.update(cx, |editor, cx| {
5170 editor.refresh_document_highlights(cx);
5171 })?;
5172 }
5173 Ok(())
5174 }))
5175 }
5176
5177 pub fn show_word_completions(
5178 &mut self,
5179 _: &ShowWordCompletions,
5180 window: &mut Window,
5181 cx: &mut Context<Self>,
5182 ) {
5183 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5184 }
5185
5186 pub fn show_completions(
5187 &mut self,
5188 options: &ShowCompletions,
5189 window: &mut Window,
5190 cx: &mut Context<Self>,
5191 ) {
5192 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5193 }
5194
5195 fn open_or_update_completions_menu(
5196 &mut self,
5197 requested_source: Option<CompletionsMenuSource>,
5198 trigger: Option<&str>,
5199 window: &mut Window,
5200 cx: &mut Context<Self>,
5201 ) {
5202 if self.pending_rename.is_some() {
5203 return;
5204 }
5205
5206 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5207
5208 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5209 // inserted and selected. To handle that case, the start of the selection is used so that
5210 // the menu starts with all choices.
5211 let position = self
5212 .selections
5213 .newest_anchor()
5214 .start
5215 .bias_right(&multibuffer_snapshot);
5216 if position.diff_base_anchor.is_some() {
5217 return;
5218 }
5219 let (buffer, buffer_position) =
5220 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5221 output
5222 } else {
5223 return;
5224 };
5225 let buffer_snapshot = buffer.read(cx).snapshot();
5226
5227 let query: Option<Arc<String>> =
5228 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5229
5230 drop(multibuffer_snapshot);
5231
5232 let provider = match requested_source {
5233 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5234 Some(CompletionsMenuSource::Words) => None,
5235 Some(CompletionsMenuSource::SnippetChoices) => {
5236 log::error!("bug: SnippetChoices requested_source is not handled");
5237 None
5238 }
5239 };
5240
5241 let sort_completions = provider
5242 .as_ref()
5243 .map_or(false, |provider| provider.sort_completions());
5244
5245 let filter_completions = provider
5246 .as_ref()
5247 .map_or(true, |provider| provider.filter_completions());
5248
5249 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5250 if filter_completions {
5251 menu.filter(query.clone(), provider.clone(), window, cx);
5252 }
5253 // When `is_incomplete` is false, no need to re-query completions when the current query
5254 // is a suffix of the initial query.
5255 if !menu.is_incomplete {
5256 // If the new query is a suffix of the old query (typing more characters) and
5257 // the previous result was complete, the existing completions can be filtered.
5258 //
5259 // Note that this is always true for snippet completions.
5260 let query_matches = match (&menu.initial_query, &query) {
5261 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5262 (None, _) => true,
5263 _ => false,
5264 };
5265 if query_matches {
5266 let position_matches = if menu.initial_position == position {
5267 true
5268 } else {
5269 let snapshot = self.buffer.read(cx).read(cx);
5270 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5271 };
5272 if position_matches {
5273 return;
5274 }
5275 }
5276 }
5277 };
5278
5279 let trigger_kind = match trigger {
5280 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5281 CompletionTriggerKind::TRIGGER_CHARACTER
5282 }
5283 _ => CompletionTriggerKind::INVOKED,
5284 };
5285 let completion_context = CompletionContext {
5286 trigger_character: trigger.and_then(|trigger| {
5287 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5288 Some(String::from(trigger))
5289 } else {
5290 None
5291 }
5292 }),
5293 trigger_kind,
5294 };
5295
5296 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5297 buffer_snapshot.surrounding_word(buffer_position)
5298 {
5299 let word_to_exclude = buffer_snapshot
5300 .text_for_range(word_range.clone())
5301 .collect::<String>();
5302 (
5303 buffer_snapshot.anchor_before(word_range.start)
5304 ..buffer_snapshot.anchor_after(buffer_position),
5305 Some(word_to_exclude),
5306 )
5307 } else {
5308 (buffer_position..buffer_position, None)
5309 };
5310
5311 let language = buffer_snapshot
5312 .language_at(buffer_position)
5313 .map(|language| language.name());
5314
5315 let completion_settings =
5316 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5317
5318 let show_completion_documentation = buffer_snapshot
5319 .settings_at(buffer_position, cx)
5320 .show_completion_documentation;
5321
5322 // The document can be large, so stay in reasonable bounds when searching for words,
5323 // otherwise completion pop-up might be slow to appear.
5324 const WORD_LOOKUP_ROWS: u32 = 5_000;
5325 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5326 let min_word_search = buffer_snapshot.clip_point(
5327 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5328 Bias::Left,
5329 );
5330 let max_word_search = buffer_snapshot.clip_point(
5331 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5332 Bias::Right,
5333 );
5334 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5335 ..buffer_snapshot.point_to_offset(max_word_search);
5336
5337 let skip_digits = query
5338 .as_ref()
5339 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5340
5341 let (mut words, provider_responses) = match &provider {
5342 Some(provider) => {
5343 let provider_responses = provider.completions(
5344 position.excerpt_id,
5345 &buffer,
5346 buffer_position,
5347 completion_context,
5348 window,
5349 cx,
5350 );
5351
5352 let words = match completion_settings.words {
5353 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5354 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5355 .background_spawn(async move {
5356 buffer_snapshot.words_in_range(WordsQuery {
5357 fuzzy_contents: None,
5358 range: word_search_range,
5359 skip_digits,
5360 })
5361 }),
5362 };
5363
5364 (words, provider_responses)
5365 }
5366 None => (
5367 cx.background_spawn(async move {
5368 buffer_snapshot.words_in_range(WordsQuery {
5369 fuzzy_contents: None,
5370 range: word_search_range,
5371 skip_digits,
5372 })
5373 }),
5374 Task::ready(Ok(Vec::new())),
5375 ),
5376 };
5377
5378 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5379
5380 let id = post_inc(&mut self.next_completion_id);
5381 let task = cx.spawn_in(window, async move |editor, cx| {
5382 let Ok(()) = editor.update(cx, |this, _| {
5383 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5384 }) else {
5385 return;
5386 };
5387
5388 // TODO: Ideally completions from different sources would be selectively re-queried, so
5389 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5390 let mut completions = Vec::new();
5391 let mut is_incomplete = false;
5392 if let Some(provider_responses) = provider_responses.await.log_err() {
5393 if !provider_responses.is_empty() {
5394 for response in provider_responses {
5395 completions.extend(response.completions);
5396 is_incomplete = is_incomplete || response.is_incomplete;
5397 }
5398 if completion_settings.words == WordsCompletionMode::Fallback {
5399 words = Task::ready(BTreeMap::default());
5400 }
5401 }
5402 }
5403
5404 let mut words = words.await;
5405 if let Some(word_to_exclude) = &word_to_exclude {
5406 words.remove(word_to_exclude);
5407 }
5408 for lsp_completion in &completions {
5409 words.remove(&lsp_completion.new_text);
5410 }
5411 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5412 replace_range: word_replace_range.clone(),
5413 new_text: word.clone(),
5414 label: CodeLabel::plain(word, None),
5415 icon_path: None,
5416 documentation: None,
5417 source: CompletionSource::BufferWord {
5418 word_range,
5419 resolved: false,
5420 },
5421 insert_text_mode: Some(InsertTextMode::AS_IS),
5422 confirm: None,
5423 }));
5424
5425 let menu = if completions.is_empty() {
5426 None
5427 } else {
5428 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5429 let languages = editor
5430 .workspace
5431 .as_ref()
5432 .and_then(|(workspace, _)| workspace.upgrade())
5433 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5434 let menu = CompletionsMenu::new(
5435 id,
5436 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5437 sort_completions,
5438 show_completion_documentation,
5439 position,
5440 query.clone(),
5441 is_incomplete,
5442 buffer.clone(),
5443 completions.into(),
5444 snippet_sort_order,
5445 languages,
5446 language,
5447 cx,
5448 );
5449
5450 let query = if filter_completions { query } else { None };
5451 let matches_task = if let Some(query) = query {
5452 menu.do_async_filtering(query, cx)
5453 } else {
5454 Task::ready(menu.unfiltered_matches())
5455 };
5456 (menu, matches_task)
5457 }) else {
5458 return;
5459 };
5460
5461 let matches = matches_task.await;
5462
5463 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5464 // Newer menu already set, so exit.
5465 match editor.context_menu.borrow().as_ref() {
5466 Some(CodeContextMenu::Completions(prev_menu)) => {
5467 if prev_menu.id > id {
5468 return;
5469 }
5470 }
5471 _ => {}
5472 };
5473
5474 // Only valid to take prev_menu because it the new menu is immediately set
5475 // below, or the menu is hidden.
5476 match editor.context_menu.borrow_mut().take() {
5477 Some(CodeContextMenu::Completions(prev_menu)) => {
5478 let position_matches =
5479 if prev_menu.initial_position == menu.initial_position {
5480 true
5481 } else {
5482 let snapshot = editor.buffer.read(cx).read(cx);
5483 prev_menu.initial_position.to_offset(&snapshot)
5484 == menu.initial_position.to_offset(&snapshot)
5485 };
5486 if position_matches {
5487 // Preserve markdown cache before `set_filter_results` because it will
5488 // try to populate the documentation cache.
5489 menu.preserve_markdown_cache(prev_menu);
5490 }
5491 }
5492 _ => {}
5493 };
5494
5495 menu.set_filter_results(matches, provider, window, cx);
5496 }) else {
5497 return;
5498 };
5499
5500 menu.visible().then_some(menu)
5501 };
5502
5503 editor
5504 .update_in(cx, |editor, window, cx| {
5505 if editor.focus_handle.is_focused(window) {
5506 if let Some(menu) = menu {
5507 *editor.context_menu.borrow_mut() =
5508 Some(CodeContextMenu::Completions(menu));
5509
5510 crate::hover_popover::hide_hover(editor, cx);
5511 if editor.show_edit_predictions_in_menu() {
5512 editor.update_visible_inline_completion(window, cx);
5513 } else {
5514 editor.discard_inline_completion(false, cx);
5515 }
5516
5517 cx.notify();
5518 return;
5519 }
5520 }
5521
5522 if editor.completion_tasks.len() <= 1 {
5523 // If there are no more completion tasks and the last menu was empty, we should hide it.
5524 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5525 // If it was already hidden and we don't show inline completions in the menu, we should
5526 // also show the inline-completion when available.
5527 if was_hidden && editor.show_edit_predictions_in_menu() {
5528 editor.update_visible_inline_completion(window, cx);
5529 }
5530 }
5531 })
5532 .ok();
5533 });
5534
5535 self.completion_tasks.push((id, task));
5536 }
5537
5538 #[cfg(feature = "test-support")]
5539 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5540 let menu = self.context_menu.borrow();
5541 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5542 let completions = menu.completions.borrow();
5543 Some(completions.to_vec())
5544 } else {
5545 None
5546 }
5547 }
5548
5549 pub fn with_completions_menu_matching_id<R>(
5550 &self,
5551 id: CompletionId,
5552 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5553 ) -> R {
5554 let mut context_menu = self.context_menu.borrow_mut();
5555 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5556 return f(None);
5557 };
5558 if completions_menu.id != id {
5559 return f(None);
5560 }
5561 f(Some(completions_menu))
5562 }
5563
5564 pub fn confirm_completion(
5565 &mut self,
5566 action: &ConfirmCompletion,
5567 window: &mut Window,
5568 cx: &mut Context<Self>,
5569 ) -> Option<Task<Result<()>>> {
5570 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5571 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5572 }
5573
5574 pub fn confirm_completion_insert(
5575 &mut self,
5576 _: &ConfirmCompletionInsert,
5577 window: &mut Window,
5578 cx: &mut Context<Self>,
5579 ) -> Option<Task<Result<()>>> {
5580 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5581 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5582 }
5583
5584 pub fn confirm_completion_replace(
5585 &mut self,
5586 _: &ConfirmCompletionReplace,
5587 window: &mut Window,
5588 cx: &mut Context<Self>,
5589 ) -> Option<Task<Result<()>>> {
5590 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5591 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5592 }
5593
5594 pub fn compose_completion(
5595 &mut self,
5596 action: &ComposeCompletion,
5597 window: &mut Window,
5598 cx: &mut Context<Self>,
5599 ) -> Option<Task<Result<()>>> {
5600 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5601 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5602 }
5603
5604 fn do_completion(
5605 &mut self,
5606 item_ix: Option<usize>,
5607 intent: CompletionIntent,
5608 window: &mut Window,
5609 cx: &mut Context<Editor>,
5610 ) -> Option<Task<Result<()>>> {
5611 use language::ToOffset as _;
5612
5613 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5614 else {
5615 return None;
5616 };
5617
5618 let candidate_id = {
5619 let entries = completions_menu.entries.borrow();
5620 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5621 if self.show_edit_predictions_in_menu() {
5622 self.discard_inline_completion(true, cx);
5623 }
5624 mat.candidate_id
5625 };
5626
5627 let completion = completions_menu
5628 .completions
5629 .borrow()
5630 .get(candidate_id)?
5631 .clone();
5632 cx.stop_propagation();
5633
5634 let buffer_handle = completions_menu.buffer.clone();
5635
5636 let CompletionEdit {
5637 new_text,
5638 snippet,
5639 replace_range,
5640 } = process_completion_for_edit(
5641 &completion,
5642 intent,
5643 &buffer_handle,
5644 &completions_menu.initial_position.text_anchor,
5645 cx,
5646 );
5647
5648 let buffer = buffer_handle.read(cx);
5649 let snapshot = self.buffer.read(cx).snapshot(cx);
5650 let newest_anchor = self.selections.newest_anchor();
5651 let replace_range_multibuffer = {
5652 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5653 let multibuffer_anchor = snapshot
5654 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5655 .unwrap()
5656 ..snapshot
5657 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5658 .unwrap();
5659 multibuffer_anchor.start.to_offset(&snapshot)
5660 ..multibuffer_anchor.end.to_offset(&snapshot)
5661 };
5662 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5663 return None;
5664 }
5665
5666 let old_text = buffer
5667 .text_for_range(replace_range.clone())
5668 .collect::<String>();
5669 let lookbehind = newest_anchor
5670 .start
5671 .text_anchor
5672 .to_offset(buffer)
5673 .saturating_sub(replace_range.start);
5674 let lookahead = replace_range
5675 .end
5676 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5677 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5678 let suffix = &old_text[lookbehind.min(old_text.len())..];
5679
5680 let selections = self.selections.all::<usize>(cx);
5681 let mut ranges = Vec::new();
5682 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5683
5684 for selection in &selections {
5685 let range = if selection.id == newest_anchor.id {
5686 replace_range_multibuffer.clone()
5687 } else {
5688 let mut range = selection.range();
5689
5690 // if prefix is present, don't duplicate it
5691 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5692 range.start = range.start.saturating_sub(lookbehind);
5693
5694 // if suffix is also present, mimic the newest cursor and replace it
5695 if selection.id != newest_anchor.id
5696 && snapshot.contains_str_at(range.end, suffix)
5697 {
5698 range.end += lookahead;
5699 }
5700 }
5701 range
5702 };
5703
5704 ranges.push(range.clone());
5705
5706 if !self.linked_edit_ranges.is_empty() {
5707 let start_anchor = snapshot.anchor_before(range.start);
5708 let end_anchor = snapshot.anchor_after(range.end);
5709 if let Some(ranges) = self
5710 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5711 {
5712 for (buffer, edits) in ranges {
5713 linked_edits
5714 .entry(buffer.clone())
5715 .or_default()
5716 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5717 }
5718 }
5719 }
5720 }
5721
5722 let common_prefix_len = old_text
5723 .chars()
5724 .zip(new_text.chars())
5725 .take_while(|(a, b)| a == b)
5726 .map(|(a, _)| a.len_utf8())
5727 .sum::<usize>();
5728
5729 cx.emit(EditorEvent::InputHandled {
5730 utf16_range_to_replace: None,
5731 text: new_text[common_prefix_len..].into(),
5732 });
5733
5734 self.transact(window, cx, |this, window, cx| {
5735 if let Some(mut snippet) = snippet {
5736 snippet.text = new_text.to_string();
5737 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5738 } else {
5739 this.buffer.update(cx, |buffer, cx| {
5740 let auto_indent = match completion.insert_text_mode {
5741 Some(InsertTextMode::AS_IS) => None,
5742 _ => this.autoindent_mode.clone(),
5743 };
5744 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5745 buffer.edit(edits, auto_indent, cx);
5746 });
5747 }
5748 for (buffer, edits) in linked_edits {
5749 buffer.update(cx, |buffer, cx| {
5750 let snapshot = buffer.snapshot();
5751 let edits = edits
5752 .into_iter()
5753 .map(|(range, text)| {
5754 use text::ToPoint as TP;
5755 let end_point = TP::to_point(&range.end, &snapshot);
5756 let start_point = TP::to_point(&range.start, &snapshot);
5757 (start_point..end_point, text)
5758 })
5759 .sorted_by_key(|(range, _)| range.start);
5760 buffer.edit(edits, None, cx);
5761 })
5762 }
5763
5764 this.refresh_inline_completion(true, false, window, cx);
5765 });
5766
5767 let show_new_completions_on_confirm = completion
5768 .confirm
5769 .as_ref()
5770 .map_or(false, |confirm| confirm(intent, window, cx));
5771 if show_new_completions_on_confirm {
5772 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5773 }
5774
5775 let provider = self.completion_provider.as_ref()?;
5776 drop(completion);
5777 let apply_edits = provider.apply_additional_edits_for_completion(
5778 buffer_handle,
5779 completions_menu.completions.clone(),
5780 candidate_id,
5781 true,
5782 cx,
5783 );
5784
5785 let editor_settings = EditorSettings::get_global(cx);
5786 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5787 // After the code completion is finished, users often want to know what signatures are needed.
5788 // so we should automatically call signature_help
5789 self.show_signature_help(&ShowSignatureHelp, window, cx);
5790 }
5791
5792 Some(cx.foreground_executor().spawn(async move {
5793 apply_edits.await?;
5794 Ok(())
5795 }))
5796 }
5797
5798 pub fn toggle_code_actions(
5799 &mut self,
5800 action: &ToggleCodeActions,
5801 window: &mut Window,
5802 cx: &mut Context<Self>,
5803 ) {
5804 let quick_launch = action.quick_launch;
5805 let mut context_menu = self.context_menu.borrow_mut();
5806 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5807 if code_actions.deployed_from == action.deployed_from {
5808 // Toggle if we're selecting the same one
5809 *context_menu = None;
5810 cx.notify();
5811 return;
5812 } else {
5813 // Otherwise, clear it and start a new one
5814 *context_menu = None;
5815 cx.notify();
5816 }
5817 }
5818 drop(context_menu);
5819 let snapshot = self.snapshot(window, cx);
5820 let deployed_from = action.deployed_from.clone();
5821 let action = action.clone();
5822 self.completion_tasks.clear();
5823 self.discard_inline_completion(false, cx);
5824
5825 let multibuffer_point = match &action.deployed_from {
5826 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5827 DisplayPoint::new(*row, 0).to_point(&snapshot)
5828 }
5829 _ => self.selections.newest::<Point>(cx).head(),
5830 };
5831 let Some((buffer, buffer_row)) = snapshot
5832 .buffer_snapshot
5833 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5834 .and_then(|(buffer_snapshot, range)| {
5835 self.buffer()
5836 .read(cx)
5837 .buffer(buffer_snapshot.remote_id())
5838 .map(|buffer| (buffer, range.start.row))
5839 })
5840 else {
5841 return;
5842 };
5843 let buffer_id = buffer.read(cx).remote_id();
5844 let tasks = self
5845 .tasks
5846 .get(&(buffer_id, buffer_row))
5847 .map(|t| Arc::new(t.to_owned()));
5848
5849 if !self.focus_handle.is_focused(window) {
5850 return;
5851 }
5852 let project = self.project.clone();
5853
5854 let code_actions_task = match deployed_from {
5855 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
5856 _ => self.code_actions(buffer_row, window, cx),
5857 };
5858
5859 let runnable_task = match deployed_from {
5860 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
5861 _ => {
5862 let mut task_context_task = Task::ready(None);
5863 if let Some(tasks) = &tasks {
5864 if let Some(project) = project {
5865 task_context_task =
5866 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
5867 }
5868 }
5869
5870 cx.spawn_in(window, {
5871 let buffer = buffer.clone();
5872 async move |editor, cx| {
5873 let task_context = task_context_task.await;
5874
5875 let resolved_tasks =
5876 tasks
5877 .zip(task_context.clone())
5878 .map(|(tasks, task_context)| ResolvedTasks {
5879 templates: tasks.resolve(&task_context).collect(),
5880 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5881 multibuffer_point.row,
5882 tasks.column,
5883 )),
5884 });
5885 let debug_scenarios = editor
5886 .update(cx, |editor, cx| {
5887 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
5888 })?
5889 .await;
5890 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
5891 }
5892 })
5893 }
5894 };
5895
5896 cx.spawn_in(window, async move |editor, cx| {
5897 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
5898 let code_actions = code_actions_task.await;
5899 let spawn_straight_away = quick_launch
5900 && resolved_tasks
5901 .as_ref()
5902 .map_or(false, |tasks| tasks.templates.len() == 1)
5903 && code_actions
5904 .as_ref()
5905 .map_or(true, |actions| actions.is_empty())
5906 && debug_scenarios.is_empty();
5907
5908 editor.update_in(cx, |editor, window, cx| {
5909 crate::hover_popover::hide_hover(editor, cx);
5910 *editor.context_menu.borrow_mut() =
5911 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5912 buffer,
5913 actions: CodeActionContents::new(
5914 resolved_tasks,
5915 code_actions,
5916 debug_scenarios,
5917 task_context.unwrap_or_default(),
5918 ),
5919 selected_item: Default::default(),
5920 scroll_handle: UniformListScrollHandle::default(),
5921 deployed_from,
5922 }));
5923 cx.notify();
5924 if spawn_straight_away {
5925 if let Some(task) = editor.confirm_code_action(
5926 &ConfirmCodeAction { item_ix: Some(0) },
5927 window,
5928 cx,
5929 ) {
5930 return task;
5931 }
5932 }
5933
5934 Task::ready(Ok(()))
5935 })
5936 })
5937 .detach_and_log_err(cx);
5938 }
5939
5940 fn debug_scenarios(
5941 &mut self,
5942 resolved_tasks: &Option<ResolvedTasks>,
5943 buffer: &Entity<Buffer>,
5944 cx: &mut App,
5945 ) -> Task<Vec<task::DebugScenario>> {
5946 if cx.has_flag::<DebuggerFeatureFlag>() {
5947 maybe!({
5948 let project = self.project.as_ref()?;
5949 let dap_store = project.read(cx).dap_store();
5950 let mut scenarios = vec![];
5951 let resolved_tasks = resolved_tasks.as_ref()?;
5952 let buffer = buffer.read(cx);
5953 let language = buffer.language()?;
5954 let file = buffer.file();
5955 let debug_adapter = language_settings(language.name().into(), file, cx)
5956 .debuggers
5957 .first()
5958 .map(SharedString::from)
5959 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
5960
5961 dap_store.update(cx, |dap_store, cx| {
5962 for (_, task) in &resolved_tasks.templates {
5963 let maybe_scenario = dap_store.debug_scenario_for_build_task(
5964 task.original_task().clone(),
5965 debug_adapter.clone().into(),
5966 task.display_label().to_owned().into(),
5967 cx,
5968 );
5969 scenarios.push(maybe_scenario);
5970 }
5971 });
5972 Some(cx.background_spawn(async move {
5973 let scenarios = futures::future::join_all(scenarios)
5974 .await
5975 .into_iter()
5976 .flatten()
5977 .collect::<Vec<_>>();
5978 scenarios
5979 }))
5980 })
5981 .unwrap_or_else(|| Task::ready(vec![]))
5982 } else {
5983 Task::ready(vec![])
5984 }
5985 }
5986
5987 fn code_actions(
5988 &mut self,
5989 buffer_row: u32,
5990 window: &mut Window,
5991 cx: &mut Context<Self>,
5992 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
5993 let mut task = self.code_actions_task.take();
5994 cx.spawn_in(window, async move |editor, cx| {
5995 while let Some(prev_task) = task {
5996 prev_task.await.log_err();
5997 task = editor
5998 .update(cx, |this, _| this.code_actions_task.take())
5999 .ok()?;
6000 }
6001
6002 editor
6003 .update(cx, |editor, cx| {
6004 editor
6005 .available_code_actions
6006 .clone()
6007 .and_then(|(location, code_actions)| {
6008 let snapshot = location.buffer.read(cx).snapshot();
6009 let point_range = location.range.to_point(&snapshot);
6010 let point_range = point_range.start.row..=point_range.end.row;
6011 if point_range.contains(&buffer_row) {
6012 Some(code_actions)
6013 } else {
6014 None
6015 }
6016 })
6017 })
6018 .ok()
6019 .flatten()
6020 })
6021 }
6022
6023 pub fn confirm_code_action(
6024 &mut self,
6025 action: &ConfirmCodeAction,
6026 window: &mut Window,
6027 cx: &mut Context<Self>,
6028 ) -> Option<Task<Result<()>>> {
6029 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6030
6031 let actions_menu =
6032 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6033 menu
6034 } else {
6035 return None;
6036 };
6037
6038 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6039 let action = actions_menu.actions.get(action_ix)?;
6040 let title = action.label();
6041 let buffer = actions_menu.buffer;
6042 let workspace = self.workspace()?;
6043
6044 match action {
6045 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6046 workspace.update(cx, |workspace, cx| {
6047 workspace.schedule_resolved_task(
6048 task_source_kind,
6049 resolved_task,
6050 false,
6051 window,
6052 cx,
6053 );
6054
6055 Some(Task::ready(Ok(())))
6056 })
6057 }
6058 CodeActionsItem::CodeAction {
6059 excerpt_id,
6060 action,
6061 provider,
6062 } => {
6063 let apply_code_action =
6064 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6065 let workspace = workspace.downgrade();
6066 Some(cx.spawn_in(window, async move |editor, cx| {
6067 let project_transaction = apply_code_action.await?;
6068 Self::open_project_transaction(
6069 &editor,
6070 workspace,
6071 project_transaction,
6072 title,
6073 cx,
6074 )
6075 .await
6076 }))
6077 }
6078 CodeActionsItem::DebugScenario(scenario) => {
6079 let context = actions_menu.actions.context.clone();
6080
6081 workspace.update(cx, |workspace, cx| {
6082 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6083 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
6084 });
6085 Some(Task::ready(Ok(())))
6086 }
6087 }
6088 }
6089
6090 pub async fn open_project_transaction(
6091 this: &WeakEntity<Editor>,
6092 workspace: WeakEntity<Workspace>,
6093 transaction: ProjectTransaction,
6094 title: String,
6095 cx: &mut AsyncWindowContext,
6096 ) -> Result<()> {
6097 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6098 cx.update(|_, cx| {
6099 entries.sort_unstable_by_key(|(buffer, _)| {
6100 buffer.read(cx).file().map(|f| f.path().clone())
6101 });
6102 })?;
6103
6104 // If the project transaction's edits are all contained within this editor, then
6105 // avoid opening a new editor to display them.
6106
6107 if let Some((buffer, transaction)) = entries.first() {
6108 if entries.len() == 1 {
6109 let excerpt = this.update(cx, |editor, cx| {
6110 editor
6111 .buffer()
6112 .read(cx)
6113 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6114 })?;
6115 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6116 if excerpted_buffer == *buffer {
6117 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6118 let excerpt_range = excerpt_range.to_offset(buffer);
6119 buffer
6120 .edited_ranges_for_transaction::<usize>(transaction)
6121 .all(|range| {
6122 excerpt_range.start <= range.start
6123 && excerpt_range.end >= range.end
6124 })
6125 })?;
6126
6127 if all_edits_within_excerpt {
6128 return Ok(());
6129 }
6130 }
6131 }
6132 }
6133 } else {
6134 return Ok(());
6135 }
6136
6137 let mut ranges_to_highlight = Vec::new();
6138 let excerpt_buffer = cx.new(|cx| {
6139 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6140 for (buffer_handle, transaction) in &entries {
6141 let edited_ranges = buffer_handle
6142 .read(cx)
6143 .edited_ranges_for_transaction::<Point>(transaction)
6144 .collect::<Vec<_>>();
6145 let (ranges, _) = multibuffer.set_excerpts_for_path(
6146 PathKey::for_buffer(buffer_handle, cx),
6147 buffer_handle.clone(),
6148 edited_ranges,
6149 DEFAULT_MULTIBUFFER_CONTEXT,
6150 cx,
6151 );
6152
6153 ranges_to_highlight.extend(ranges);
6154 }
6155 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6156 multibuffer
6157 })?;
6158
6159 workspace.update_in(cx, |workspace, window, cx| {
6160 let project = workspace.project().clone();
6161 let editor =
6162 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6163 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6164 editor.update(cx, |editor, cx| {
6165 editor.highlight_background::<Self>(
6166 &ranges_to_highlight,
6167 |theme| theme.colors().editor_highlighted_line_background,
6168 cx,
6169 );
6170 });
6171 })?;
6172
6173 Ok(())
6174 }
6175
6176 pub fn clear_code_action_providers(&mut self) {
6177 self.code_action_providers.clear();
6178 self.available_code_actions.take();
6179 }
6180
6181 pub fn add_code_action_provider(
6182 &mut self,
6183 provider: Rc<dyn CodeActionProvider>,
6184 window: &mut Window,
6185 cx: &mut Context<Self>,
6186 ) {
6187 if self
6188 .code_action_providers
6189 .iter()
6190 .any(|existing_provider| existing_provider.id() == provider.id())
6191 {
6192 return;
6193 }
6194
6195 self.code_action_providers.push(provider);
6196 self.refresh_code_actions(window, cx);
6197 }
6198
6199 pub fn remove_code_action_provider(
6200 &mut self,
6201 id: Arc<str>,
6202 window: &mut Window,
6203 cx: &mut Context<Self>,
6204 ) {
6205 self.code_action_providers
6206 .retain(|provider| provider.id() != id);
6207 self.refresh_code_actions(window, cx);
6208 }
6209
6210 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6211 !self.code_action_providers.is_empty()
6212 && EditorSettings::get_global(cx).toolbar.code_actions
6213 }
6214
6215 pub fn has_available_code_actions(&self) -> bool {
6216 self.available_code_actions
6217 .as_ref()
6218 .is_some_and(|(_, actions)| !actions.is_empty())
6219 }
6220
6221 fn render_inline_code_actions(
6222 &self,
6223 icon_size: ui::IconSize,
6224 display_row: DisplayRow,
6225 is_active: bool,
6226 cx: &mut Context<Self>,
6227 ) -> AnyElement {
6228 let show_tooltip = !self.context_menu_visible();
6229 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6230 .icon_size(icon_size)
6231 .shape(ui::IconButtonShape::Square)
6232 .style(ButtonStyle::Transparent)
6233 .icon_color(ui::Color::Hidden)
6234 .toggle_state(is_active)
6235 .when(show_tooltip, |this| {
6236 this.tooltip({
6237 let focus_handle = self.focus_handle.clone();
6238 move |window, cx| {
6239 Tooltip::for_action_in(
6240 "Toggle Code Actions",
6241 &ToggleCodeActions {
6242 deployed_from: None,
6243 quick_launch: false,
6244 },
6245 &focus_handle,
6246 window,
6247 cx,
6248 )
6249 }
6250 })
6251 })
6252 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6253 window.focus(&editor.focus_handle(cx));
6254 editor.toggle_code_actions(
6255 &crate::actions::ToggleCodeActions {
6256 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6257 display_row,
6258 )),
6259 quick_launch: false,
6260 },
6261 window,
6262 cx,
6263 );
6264 }))
6265 .into_any_element()
6266 }
6267
6268 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6269 &self.context_menu
6270 }
6271
6272 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6273 let newest_selection = self.selections.newest_anchor().clone();
6274 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6275 let buffer = self.buffer.read(cx);
6276 if newest_selection.head().diff_base_anchor.is_some() {
6277 return None;
6278 }
6279 let (start_buffer, start) =
6280 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6281 let (end_buffer, end) =
6282 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6283 if start_buffer != end_buffer {
6284 return None;
6285 }
6286
6287 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6288 cx.background_executor()
6289 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6290 .await;
6291
6292 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6293 let providers = this.code_action_providers.clone();
6294 let tasks = this
6295 .code_action_providers
6296 .iter()
6297 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6298 .collect::<Vec<_>>();
6299 (providers, tasks)
6300 })?;
6301
6302 let mut actions = Vec::new();
6303 for (provider, provider_actions) in
6304 providers.into_iter().zip(future::join_all(tasks).await)
6305 {
6306 if let Some(provider_actions) = provider_actions.log_err() {
6307 actions.extend(provider_actions.into_iter().map(|action| {
6308 AvailableCodeAction {
6309 excerpt_id: newest_selection.start.excerpt_id,
6310 action,
6311 provider: provider.clone(),
6312 }
6313 }));
6314 }
6315 }
6316
6317 this.update(cx, |this, cx| {
6318 this.available_code_actions = if actions.is_empty() {
6319 None
6320 } else {
6321 Some((
6322 Location {
6323 buffer: start_buffer,
6324 range: start..end,
6325 },
6326 actions.into(),
6327 ))
6328 };
6329 cx.notify();
6330 })
6331 }));
6332 None
6333 }
6334
6335 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6336 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6337 self.show_git_blame_inline = false;
6338
6339 self.show_git_blame_inline_delay_task =
6340 Some(cx.spawn_in(window, async move |this, cx| {
6341 cx.background_executor().timer(delay).await;
6342
6343 this.update(cx, |this, cx| {
6344 this.show_git_blame_inline = true;
6345 cx.notify();
6346 })
6347 .log_err();
6348 }));
6349 }
6350 }
6351
6352 fn show_blame_popover(
6353 &mut self,
6354 blame_entry: &BlameEntry,
6355 position: gpui::Point<Pixels>,
6356 cx: &mut Context<Self>,
6357 ) {
6358 if let Some(state) = &mut self.inline_blame_popover {
6359 state.hide_task.take();
6360 } else {
6361 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6362 let blame_entry = blame_entry.clone();
6363 let show_task = cx.spawn(async move |editor, cx| {
6364 cx.background_executor()
6365 .timer(std::time::Duration::from_millis(delay))
6366 .await;
6367 editor
6368 .update(cx, |editor, cx| {
6369 editor.inline_blame_popover_show_task.take();
6370 let Some(blame) = editor.blame.as_ref() else {
6371 return;
6372 };
6373 let blame = blame.read(cx);
6374 let details = blame.details_for_entry(&blame_entry);
6375 let markdown = cx.new(|cx| {
6376 Markdown::new(
6377 details
6378 .as_ref()
6379 .map(|message| message.message.clone())
6380 .unwrap_or_default(),
6381 None,
6382 None,
6383 cx,
6384 )
6385 });
6386 editor.inline_blame_popover = Some(InlineBlamePopover {
6387 position,
6388 hide_task: None,
6389 popover_bounds: None,
6390 popover_state: InlineBlamePopoverState {
6391 scroll_handle: ScrollHandle::new(),
6392 commit_message: details,
6393 markdown,
6394 },
6395 });
6396 cx.notify();
6397 })
6398 .ok();
6399 });
6400 self.inline_blame_popover_show_task = Some(show_task);
6401 }
6402 }
6403
6404 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6405 self.inline_blame_popover_show_task.take();
6406 if let Some(state) = &mut self.inline_blame_popover {
6407 let hide_task = cx.spawn(async move |editor, cx| {
6408 cx.background_executor()
6409 .timer(std::time::Duration::from_millis(100))
6410 .await;
6411 editor
6412 .update(cx, |editor, cx| {
6413 editor.inline_blame_popover.take();
6414 cx.notify();
6415 })
6416 .ok();
6417 });
6418 state.hide_task = Some(hide_task);
6419 }
6420 }
6421
6422 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6423 if self.pending_rename.is_some() {
6424 return None;
6425 }
6426
6427 let provider = self.semantics_provider.clone()?;
6428 let buffer = self.buffer.read(cx);
6429 let newest_selection = self.selections.newest_anchor().clone();
6430 let cursor_position = newest_selection.head();
6431 let (cursor_buffer, cursor_buffer_position) =
6432 buffer.text_anchor_for_position(cursor_position, cx)?;
6433 let (tail_buffer, tail_buffer_position) =
6434 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6435 if cursor_buffer != tail_buffer {
6436 return None;
6437 }
6438
6439 let snapshot = cursor_buffer.read(cx).snapshot();
6440 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6441 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6442 if start_word_range != end_word_range {
6443 self.document_highlights_task.take();
6444 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6445 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6446 return None;
6447 }
6448
6449 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6450 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6451 cx.background_executor()
6452 .timer(Duration::from_millis(debounce))
6453 .await;
6454
6455 let highlights = if let Some(highlights) = cx
6456 .update(|cx| {
6457 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6458 })
6459 .ok()
6460 .flatten()
6461 {
6462 highlights.await.log_err()
6463 } else {
6464 None
6465 };
6466
6467 if let Some(highlights) = highlights {
6468 this.update(cx, |this, cx| {
6469 if this.pending_rename.is_some() {
6470 return;
6471 }
6472
6473 let buffer_id = cursor_position.buffer_id;
6474 let buffer = this.buffer.read(cx);
6475 if !buffer
6476 .text_anchor_for_position(cursor_position, cx)
6477 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6478 {
6479 return;
6480 }
6481
6482 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6483 let mut write_ranges = Vec::new();
6484 let mut read_ranges = Vec::new();
6485 for highlight in highlights {
6486 for (excerpt_id, excerpt_range) in
6487 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6488 {
6489 let start = highlight
6490 .range
6491 .start
6492 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6493 let end = highlight
6494 .range
6495 .end
6496 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6497 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6498 continue;
6499 }
6500
6501 let range = Anchor {
6502 buffer_id,
6503 excerpt_id,
6504 text_anchor: start,
6505 diff_base_anchor: None,
6506 }..Anchor {
6507 buffer_id,
6508 excerpt_id,
6509 text_anchor: end,
6510 diff_base_anchor: None,
6511 };
6512 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6513 write_ranges.push(range);
6514 } else {
6515 read_ranges.push(range);
6516 }
6517 }
6518 }
6519
6520 this.highlight_background::<DocumentHighlightRead>(
6521 &read_ranges,
6522 |theme| theme.colors().editor_document_highlight_read_background,
6523 cx,
6524 );
6525 this.highlight_background::<DocumentHighlightWrite>(
6526 &write_ranges,
6527 |theme| theme.colors().editor_document_highlight_write_background,
6528 cx,
6529 );
6530 cx.notify();
6531 })
6532 .log_err();
6533 }
6534 }));
6535 None
6536 }
6537
6538 fn prepare_highlight_query_from_selection(
6539 &mut self,
6540 cx: &mut Context<Editor>,
6541 ) -> Option<(String, Range<Anchor>)> {
6542 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6543 return None;
6544 }
6545 if !EditorSettings::get_global(cx).selection_highlight {
6546 return None;
6547 }
6548 if self.selections.count() != 1 || self.selections.line_mode {
6549 return None;
6550 }
6551 let selection = self.selections.newest::<Point>(cx);
6552 if selection.is_empty() || selection.start.row != selection.end.row {
6553 return None;
6554 }
6555 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6556 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6557 let query = multi_buffer_snapshot
6558 .text_for_range(selection_anchor_range.clone())
6559 .collect::<String>();
6560 if query.trim().is_empty() {
6561 return None;
6562 }
6563 Some((query, selection_anchor_range))
6564 }
6565
6566 fn update_selection_occurrence_highlights(
6567 &mut self,
6568 query_text: String,
6569 query_range: Range<Anchor>,
6570 multi_buffer_range_to_query: Range<Point>,
6571 use_debounce: bool,
6572 window: &mut Window,
6573 cx: &mut Context<Editor>,
6574 ) -> Task<()> {
6575 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6576 cx.spawn_in(window, async move |editor, cx| {
6577 if use_debounce {
6578 cx.background_executor()
6579 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6580 .await;
6581 }
6582 let match_task = cx.background_spawn(async move {
6583 let buffer_ranges = multi_buffer_snapshot
6584 .range_to_buffer_ranges(multi_buffer_range_to_query)
6585 .into_iter()
6586 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6587 let mut match_ranges = Vec::new();
6588 let Ok(regex) = project::search::SearchQuery::text(
6589 query_text.clone(),
6590 false,
6591 false,
6592 false,
6593 Default::default(),
6594 Default::default(),
6595 false,
6596 None,
6597 ) else {
6598 return Vec::default();
6599 };
6600 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6601 match_ranges.extend(
6602 regex
6603 .search(&buffer_snapshot, Some(search_range.clone()))
6604 .await
6605 .into_iter()
6606 .filter_map(|match_range| {
6607 let match_start = buffer_snapshot
6608 .anchor_after(search_range.start + match_range.start);
6609 let match_end = buffer_snapshot
6610 .anchor_before(search_range.start + match_range.end);
6611 let match_anchor_range = Anchor::range_in_buffer(
6612 excerpt_id,
6613 buffer_snapshot.remote_id(),
6614 match_start..match_end,
6615 );
6616 (match_anchor_range != query_range).then_some(match_anchor_range)
6617 }),
6618 );
6619 }
6620 match_ranges
6621 });
6622 let match_ranges = match_task.await;
6623 editor
6624 .update_in(cx, |editor, _, cx| {
6625 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6626 if !match_ranges.is_empty() {
6627 editor.highlight_background::<SelectedTextHighlight>(
6628 &match_ranges,
6629 |theme| theme.colors().editor_document_highlight_bracket_background,
6630 cx,
6631 )
6632 }
6633 })
6634 .log_err();
6635 })
6636 }
6637
6638 fn refresh_selected_text_highlights(
6639 &mut self,
6640 on_buffer_edit: bool,
6641 window: &mut Window,
6642 cx: &mut Context<Editor>,
6643 ) {
6644 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6645 else {
6646 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6647 self.quick_selection_highlight_task.take();
6648 self.debounced_selection_highlight_task.take();
6649 return;
6650 };
6651 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6652 if on_buffer_edit
6653 || self
6654 .quick_selection_highlight_task
6655 .as_ref()
6656 .map_or(true, |(prev_anchor_range, _)| {
6657 prev_anchor_range != &query_range
6658 })
6659 {
6660 let multi_buffer_visible_start = self
6661 .scroll_manager
6662 .anchor()
6663 .anchor
6664 .to_point(&multi_buffer_snapshot);
6665 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6666 multi_buffer_visible_start
6667 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6668 Bias::Left,
6669 );
6670 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6671 self.quick_selection_highlight_task = Some((
6672 query_range.clone(),
6673 self.update_selection_occurrence_highlights(
6674 query_text.clone(),
6675 query_range.clone(),
6676 multi_buffer_visible_range,
6677 false,
6678 window,
6679 cx,
6680 ),
6681 ));
6682 }
6683 if on_buffer_edit
6684 || self
6685 .debounced_selection_highlight_task
6686 .as_ref()
6687 .map_or(true, |(prev_anchor_range, _)| {
6688 prev_anchor_range != &query_range
6689 })
6690 {
6691 let multi_buffer_start = multi_buffer_snapshot
6692 .anchor_before(0)
6693 .to_point(&multi_buffer_snapshot);
6694 let multi_buffer_end = multi_buffer_snapshot
6695 .anchor_after(multi_buffer_snapshot.len())
6696 .to_point(&multi_buffer_snapshot);
6697 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6698 self.debounced_selection_highlight_task = Some((
6699 query_range.clone(),
6700 self.update_selection_occurrence_highlights(
6701 query_text,
6702 query_range,
6703 multi_buffer_full_range,
6704 true,
6705 window,
6706 cx,
6707 ),
6708 ));
6709 }
6710 }
6711
6712 pub fn refresh_inline_completion(
6713 &mut self,
6714 debounce: bool,
6715 user_requested: bool,
6716 window: &mut Window,
6717 cx: &mut Context<Self>,
6718 ) -> Option<()> {
6719 let provider = self.edit_prediction_provider()?;
6720 let cursor = self.selections.newest_anchor().head();
6721 let (buffer, cursor_buffer_position) =
6722 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6723
6724 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6725 self.discard_inline_completion(false, cx);
6726 return None;
6727 }
6728
6729 if !user_requested
6730 && (!self.should_show_edit_predictions()
6731 || !self.is_focused(window)
6732 || buffer.read(cx).is_empty())
6733 {
6734 self.discard_inline_completion(false, cx);
6735 return None;
6736 }
6737
6738 self.update_visible_inline_completion(window, cx);
6739 provider.refresh(
6740 self.project.clone(),
6741 buffer,
6742 cursor_buffer_position,
6743 debounce,
6744 cx,
6745 );
6746 Some(())
6747 }
6748
6749 fn show_edit_predictions_in_menu(&self) -> bool {
6750 match self.edit_prediction_settings {
6751 EditPredictionSettings::Disabled => false,
6752 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6753 }
6754 }
6755
6756 pub fn edit_predictions_enabled(&self) -> bool {
6757 match self.edit_prediction_settings {
6758 EditPredictionSettings::Disabled => false,
6759 EditPredictionSettings::Enabled { .. } => true,
6760 }
6761 }
6762
6763 fn edit_prediction_requires_modifier(&self) -> bool {
6764 match self.edit_prediction_settings {
6765 EditPredictionSettings::Disabled => false,
6766 EditPredictionSettings::Enabled {
6767 preview_requires_modifier,
6768 ..
6769 } => preview_requires_modifier,
6770 }
6771 }
6772
6773 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6774 if self.edit_prediction_provider.is_none() {
6775 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6776 } else {
6777 let selection = self.selections.newest_anchor();
6778 let cursor = selection.head();
6779
6780 if let Some((buffer, cursor_buffer_position)) =
6781 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6782 {
6783 self.edit_prediction_settings =
6784 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6785 }
6786 }
6787 }
6788
6789 fn edit_prediction_settings_at_position(
6790 &self,
6791 buffer: &Entity<Buffer>,
6792 buffer_position: language::Anchor,
6793 cx: &App,
6794 ) -> EditPredictionSettings {
6795 if !self.mode.is_full()
6796 || !self.show_inline_completions_override.unwrap_or(true)
6797 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6798 {
6799 return EditPredictionSettings::Disabled;
6800 }
6801
6802 let buffer = buffer.read(cx);
6803
6804 let file = buffer.file();
6805
6806 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6807 return EditPredictionSettings::Disabled;
6808 };
6809
6810 let by_provider = matches!(
6811 self.menu_inline_completions_policy,
6812 MenuInlineCompletionsPolicy::ByProvider
6813 );
6814
6815 let show_in_menu = by_provider
6816 && self
6817 .edit_prediction_provider
6818 .as_ref()
6819 .map_or(false, |provider| {
6820 provider.provider.show_completions_in_menu()
6821 });
6822
6823 let preview_requires_modifier =
6824 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6825
6826 EditPredictionSettings::Enabled {
6827 show_in_menu,
6828 preview_requires_modifier,
6829 }
6830 }
6831
6832 fn should_show_edit_predictions(&self) -> bool {
6833 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6834 }
6835
6836 pub fn edit_prediction_preview_is_active(&self) -> bool {
6837 matches!(
6838 self.edit_prediction_preview,
6839 EditPredictionPreview::Active { .. }
6840 )
6841 }
6842
6843 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6844 let cursor = self.selections.newest_anchor().head();
6845 if let Some((buffer, cursor_position)) =
6846 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6847 {
6848 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6849 } else {
6850 false
6851 }
6852 }
6853
6854 pub fn supports_minimap(&self, cx: &App) -> bool {
6855 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6856 }
6857
6858 fn edit_predictions_enabled_in_buffer(
6859 &self,
6860 buffer: &Entity<Buffer>,
6861 buffer_position: language::Anchor,
6862 cx: &App,
6863 ) -> bool {
6864 maybe!({
6865 if self.read_only(cx) {
6866 return Some(false);
6867 }
6868 let provider = self.edit_prediction_provider()?;
6869 if !provider.is_enabled(&buffer, buffer_position, cx) {
6870 return Some(false);
6871 }
6872 let buffer = buffer.read(cx);
6873 let Some(file) = buffer.file() else {
6874 return Some(true);
6875 };
6876 let settings = all_language_settings(Some(file), cx);
6877 Some(settings.edit_predictions_enabled_for_file(file, cx))
6878 })
6879 .unwrap_or(false)
6880 }
6881
6882 fn cycle_inline_completion(
6883 &mut self,
6884 direction: Direction,
6885 window: &mut Window,
6886 cx: &mut Context<Self>,
6887 ) -> Option<()> {
6888 let provider = self.edit_prediction_provider()?;
6889 let cursor = self.selections.newest_anchor().head();
6890 let (buffer, cursor_buffer_position) =
6891 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6892 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6893 return None;
6894 }
6895
6896 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6897 self.update_visible_inline_completion(window, cx);
6898
6899 Some(())
6900 }
6901
6902 pub fn show_inline_completion(
6903 &mut self,
6904 _: &ShowEditPrediction,
6905 window: &mut Window,
6906 cx: &mut Context<Self>,
6907 ) {
6908 if !self.has_active_inline_completion() {
6909 self.refresh_inline_completion(false, true, window, cx);
6910 return;
6911 }
6912
6913 self.update_visible_inline_completion(window, cx);
6914 }
6915
6916 pub fn display_cursor_names(
6917 &mut self,
6918 _: &DisplayCursorNames,
6919 window: &mut Window,
6920 cx: &mut Context<Self>,
6921 ) {
6922 self.show_cursor_names(window, cx);
6923 }
6924
6925 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6926 self.show_cursor_names = true;
6927 cx.notify();
6928 cx.spawn_in(window, async move |this, cx| {
6929 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6930 this.update(cx, |this, cx| {
6931 this.show_cursor_names = false;
6932 cx.notify()
6933 })
6934 .ok()
6935 })
6936 .detach();
6937 }
6938
6939 pub fn next_edit_prediction(
6940 &mut self,
6941 _: &NextEditPrediction,
6942 window: &mut Window,
6943 cx: &mut Context<Self>,
6944 ) {
6945 if self.has_active_inline_completion() {
6946 self.cycle_inline_completion(Direction::Next, window, cx);
6947 } else {
6948 let is_copilot_disabled = self
6949 .refresh_inline_completion(false, true, window, cx)
6950 .is_none();
6951 if is_copilot_disabled {
6952 cx.propagate();
6953 }
6954 }
6955 }
6956
6957 pub fn previous_edit_prediction(
6958 &mut self,
6959 _: &PreviousEditPrediction,
6960 window: &mut Window,
6961 cx: &mut Context<Self>,
6962 ) {
6963 if self.has_active_inline_completion() {
6964 self.cycle_inline_completion(Direction::Prev, window, cx);
6965 } else {
6966 let is_copilot_disabled = self
6967 .refresh_inline_completion(false, true, window, cx)
6968 .is_none();
6969 if is_copilot_disabled {
6970 cx.propagate();
6971 }
6972 }
6973 }
6974
6975 pub fn accept_edit_prediction(
6976 &mut self,
6977 _: &AcceptEditPrediction,
6978 window: &mut Window,
6979 cx: &mut Context<Self>,
6980 ) {
6981 if self.show_edit_predictions_in_menu() {
6982 self.hide_context_menu(window, cx);
6983 }
6984
6985 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6986 return;
6987 };
6988
6989 self.report_inline_completion_event(
6990 active_inline_completion.completion_id.clone(),
6991 true,
6992 cx,
6993 );
6994
6995 match &active_inline_completion.completion {
6996 InlineCompletion::Move { target, .. } => {
6997 let target = *target;
6998
6999 if let Some(position_map) = &self.last_position_map {
7000 if position_map
7001 .visible_row_range
7002 .contains(&target.to_display_point(&position_map.snapshot).row())
7003 || !self.edit_prediction_requires_modifier()
7004 {
7005 self.unfold_ranges(&[target..target], true, false, cx);
7006 // Note that this is also done in vim's handler of the Tab action.
7007 self.change_selections(
7008 Some(Autoscroll::newest()),
7009 window,
7010 cx,
7011 |selections| {
7012 selections.select_anchor_ranges([target..target]);
7013 },
7014 );
7015 self.clear_row_highlights::<EditPredictionPreview>();
7016
7017 self.edit_prediction_preview
7018 .set_previous_scroll_position(None);
7019 } else {
7020 self.edit_prediction_preview
7021 .set_previous_scroll_position(Some(
7022 position_map.snapshot.scroll_anchor,
7023 ));
7024
7025 self.highlight_rows::<EditPredictionPreview>(
7026 target..target,
7027 cx.theme().colors().editor_highlighted_line_background,
7028 RowHighlightOptions {
7029 autoscroll: true,
7030 ..Default::default()
7031 },
7032 cx,
7033 );
7034 self.request_autoscroll(Autoscroll::fit(), cx);
7035 }
7036 }
7037 }
7038 InlineCompletion::Edit { edits, .. } => {
7039 if let Some(provider) = self.edit_prediction_provider() {
7040 provider.accept(cx);
7041 }
7042
7043 // Store the transaction ID and selections before applying the edit
7044 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7045
7046 let snapshot = self.buffer.read(cx).snapshot(cx);
7047 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7048
7049 self.buffer.update(cx, |buffer, cx| {
7050 buffer.edit(edits.iter().cloned(), None, cx)
7051 });
7052
7053 self.change_selections(None, window, cx, |s| {
7054 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7055 });
7056
7057 let selections = self.selections.disjoint_anchors();
7058 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7059 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7060 if has_new_transaction {
7061 self.selection_history
7062 .insert_transaction(transaction_id_now, selections);
7063 }
7064 }
7065
7066 self.update_visible_inline_completion(window, cx);
7067 if self.active_inline_completion.is_none() {
7068 self.refresh_inline_completion(true, true, window, cx);
7069 }
7070
7071 cx.notify();
7072 }
7073 }
7074
7075 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7076 }
7077
7078 pub fn accept_partial_inline_completion(
7079 &mut self,
7080 _: &AcceptPartialEditPrediction,
7081 window: &mut Window,
7082 cx: &mut Context<Self>,
7083 ) {
7084 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7085 return;
7086 };
7087 if self.selections.count() != 1 {
7088 return;
7089 }
7090
7091 self.report_inline_completion_event(
7092 active_inline_completion.completion_id.clone(),
7093 true,
7094 cx,
7095 );
7096
7097 match &active_inline_completion.completion {
7098 InlineCompletion::Move { target, .. } => {
7099 let target = *target;
7100 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
7101 selections.select_anchor_ranges([target..target]);
7102 });
7103 }
7104 InlineCompletion::Edit { edits, .. } => {
7105 // Find an insertion that starts at the cursor position.
7106 let snapshot = self.buffer.read(cx).snapshot(cx);
7107 let cursor_offset = self.selections.newest::<usize>(cx).head();
7108 let insertion = edits.iter().find_map(|(range, text)| {
7109 let range = range.to_offset(&snapshot);
7110 if range.is_empty() && range.start == cursor_offset {
7111 Some(text)
7112 } else {
7113 None
7114 }
7115 });
7116
7117 if let Some(text) = insertion {
7118 let mut partial_completion = text
7119 .chars()
7120 .by_ref()
7121 .take_while(|c| c.is_alphabetic())
7122 .collect::<String>();
7123 if partial_completion.is_empty() {
7124 partial_completion = text
7125 .chars()
7126 .by_ref()
7127 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7128 .collect::<String>();
7129 }
7130
7131 cx.emit(EditorEvent::InputHandled {
7132 utf16_range_to_replace: None,
7133 text: partial_completion.clone().into(),
7134 });
7135
7136 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7137
7138 self.refresh_inline_completion(true, true, window, cx);
7139 cx.notify();
7140 } else {
7141 self.accept_edit_prediction(&Default::default(), window, cx);
7142 }
7143 }
7144 }
7145 }
7146
7147 fn discard_inline_completion(
7148 &mut self,
7149 should_report_inline_completion_event: bool,
7150 cx: &mut Context<Self>,
7151 ) -> bool {
7152 if should_report_inline_completion_event {
7153 let completion_id = self
7154 .active_inline_completion
7155 .as_ref()
7156 .and_then(|active_completion| active_completion.completion_id.clone());
7157
7158 self.report_inline_completion_event(completion_id, false, cx);
7159 }
7160
7161 if let Some(provider) = self.edit_prediction_provider() {
7162 provider.discard(cx);
7163 }
7164
7165 self.take_active_inline_completion(cx)
7166 }
7167
7168 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7169 let Some(provider) = self.edit_prediction_provider() else {
7170 return;
7171 };
7172
7173 let Some((_, buffer, _)) = self
7174 .buffer
7175 .read(cx)
7176 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7177 else {
7178 return;
7179 };
7180
7181 let extension = buffer
7182 .read(cx)
7183 .file()
7184 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7185
7186 let event_type = match accepted {
7187 true => "Edit Prediction Accepted",
7188 false => "Edit Prediction Discarded",
7189 };
7190 telemetry::event!(
7191 event_type,
7192 provider = provider.name(),
7193 prediction_id = id,
7194 suggestion_accepted = accepted,
7195 file_extension = extension,
7196 );
7197 }
7198
7199 pub fn has_active_inline_completion(&self) -> bool {
7200 self.active_inline_completion.is_some()
7201 }
7202
7203 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7204 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7205 return false;
7206 };
7207
7208 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7209 self.clear_highlights::<InlineCompletionHighlight>(cx);
7210 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7211 true
7212 }
7213
7214 /// Returns true when we're displaying the edit prediction popover below the cursor
7215 /// like we are not previewing and the LSP autocomplete menu is visible
7216 /// or we are in `when_holding_modifier` mode.
7217 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7218 if self.edit_prediction_preview_is_active()
7219 || !self.show_edit_predictions_in_menu()
7220 || !self.edit_predictions_enabled()
7221 {
7222 return false;
7223 }
7224
7225 if self.has_visible_completions_menu() {
7226 return true;
7227 }
7228
7229 has_completion && self.edit_prediction_requires_modifier()
7230 }
7231
7232 fn handle_modifiers_changed(
7233 &mut self,
7234 modifiers: Modifiers,
7235 position_map: &PositionMap,
7236 window: &mut Window,
7237 cx: &mut Context<Self>,
7238 ) {
7239 if self.show_edit_predictions_in_menu() {
7240 self.update_edit_prediction_preview(&modifiers, window, cx);
7241 }
7242
7243 self.update_selection_mode(&modifiers, position_map, window, cx);
7244
7245 let mouse_position = window.mouse_position();
7246 if !position_map.text_hitbox.is_hovered(window) {
7247 return;
7248 }
7249
7250 self.update_hovered_link(
7251 position_map.point_for_position(mouse_position),
7252 &position_map.snapshot,
7253 modifiers,
7254 window,
7255 cx,
7256 )
7257 }
7258
7259 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7260 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7261 if invert {
7262 match multi_cursor_setting {
7263 MultiCursorModifier::Alt => modifiers.alt,
7264 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7265 }
7266 } else {
7267 match multi_cursor_setting {
7268 MultiCursorModifier::Alt => modifiers.secondary(),
7269 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7270 }
7271 }
7272 }
7273
7274 fn columnar_selection_mode(
7275 modifiers: &Modifiers,
7276 cx: &mut Context<Self>,
7277 ) -> Option<ColumnarMode> {
7278 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7279 if Self::multi_cursor_modifier(false, modifiers, cx) {
7280 Some(ColumnarMode::FromMouse)
7281 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7282 Some(ColumnarMode::FromSelection)
7283 } else {
7284 None
7285 }
7286 } else {
7287 None
7288 }
7289 }
7290
7291 fn update_selection_mode(
7292 &mut self,
7293 modifiers: &Modifiers,
7294 position_map: &PositionMap,
7295 window: &mut Window,
7296 cx: &mut Context<Self>,
7297 ) {
7298 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7299 return;
7300 };
7301 if self.selections.pending.is_none() {
7302 return;
7303 }
7304
7305 let mouse_position = window.mouse_position();
7306 let point_for_position = position_map.point_for_position(mouse_position);
7307 let position = point_for_position.previous_valid;
7308
7309 self.select(
7310 SelectPhase::BeginColumnar {
7311 position,
7312 reset: false,
7313 mode,
7314 goal_column: point_for_position.exact_unclipped.column(),
7315 },
7316 window,
7317 cx,
7318 );
7319 }
7320
7321 fn update_edit_prediction_preview(
7322 &mut self,
7323 modifiers: &Modifiers,
7324 window: &mut Window,
7325 cx: &mut Context<Self>,
7326 ) {
7327 let mut modifiers_held = false;
7328 if let Some(accept_keystroke) = self
7329 .accept_edit_prediction_keybind(false, window, cx)
7330 .keystroke()
7331 {
7332 modifiers_held = modifiers_held
7333 || (&accept_keystroke.modifiers == modifiers
7334 && accept_keystroke.modifiers.modified());
7335 };
7336 if let Some(accept_partial_keystroke) = self
7337 .accept_edit_prediction_keybind(true, window, cx)
7338 .keystroke()
7339 {
7340 modifiers_held = modifiers_held
7341 || (&accept_partial_keystroke.modifiers == modifiers
7342 && accept_partial_keystroke.modifiers.modified());
7343 }
7344
7345 if modifiers_held {
7346 if matches!(
7347 self.edit_prediction_preview,
7348 EditPredictionPreview::Inactive { .. }
7349 ) {
7350 self.edit_prediction_preview = EditPredictionPreview::Active {
7351 previous_scroll_position: None,
7352 since: Instant::now(),
7353 };
7354
7355 self.update_visible_inline_completion(window, cx);
7356 cx.notify();
7357 }
7358 } else if let EditPredictionPreview::Active {
7359 previous_scroll_position,
7360 since,
7361 } = self.edit_prediction_preview
7362 {
7363 if let (Some(previous_scroll_position), Some(position_map)) =
7364 (previous_scroll_position, self.last_position_map.as_ref())
7365 {
7366 self.set_scroll_position(
7367 previous_scroll_position
7368 .scroll_position(&position_map.snapshot.display_snapshot),
7369 window,
7370 cx,
7371 );
7372 }
7373
7374 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7375 released_too_fast: since.elapsed() < Duration::from_millis(200),
7376 };
7377 self.clear_row_highlights::<EditPredictionPreview>();
7378 self.update_visible_inline_completion(window, cx);
7379 cx.notify();
7380 }
7381 }
7382
7383 fn update_visible_inline_completion(
7384 &mut self,
7385 _window: &mut Window,
7386 cx: &mut Context<Self>,
7387 ) -> Option<()> {
7388 let selection = self.selections.newest_anchor();
7389 let cursor = selection.head();
7390 let multibuffer = self.buffer.read(cx).snapshot(cx);
7391 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7392 let excerpt_id = cursor.excerpt_id;
7393
7394 let show_in_menu = self.show_edit_predictions_in_menu();
7395 let completions_menu_has_precedence = !show_in_menu
7396 && (self.context_menu.borrow().is_some()
7397 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7398
7399 if completions_menu_has_precedence
7400 || !offset_selection.is_empty()
7401 || self
7402 .active_inline_completion
7403 .as_ref()
7404 .map_or(false, |completion| {
7405 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7406 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7407 !invalidation_range.contains(&offset_selection.head())
7408 })
7409 {
7410 self.discard_inline_completion(false, cx);
7411 return None;
7412 }
7413
7414 self.take_active_inline_completion(cx);
7415 let Some(provider) = self.edit_prediction_provider() else {
7416 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7417 return None;
7418 };
7419
7420 let (buffer, cursor_buffer_position) =
7421 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7422
7423 self.edit_prediction_settings =
7424 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7425
7426 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7427
7428 if self.edit_prediction_indent_conflict {
7429 let cursor_point = cursor.to_point(&multibuffer);
7430
7431 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7432
7433 if let Some((_, indent)) = indents.iter().next() {
7434 if indent.len == cursor_point.column {
7435 self.edit_prediction_indent_conflict = false;
7436 }
7437 }
7438 }
7439
7440 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7441 let edits = inline_completion
7442 .edits
7443 .into_iter()
7444 .flat_map(|(range, new_text)| {
7445 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7446 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7447 Some((start..end, new_text))
7448 })
7449 .collect::<Vec<_>>();
7450 if edits.is_empty() {
7451 return None;
7452 }
7453
7454 let first_edit_start = edits.first().unwrap().0.start;
7455 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7456 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7457
7458 let last_edit_end = edits.last().unwrap().0.end;
7459 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7460 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7461
7462 let cursor_row = cursor.to_point(&multibuffer).row;
7463
7464 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7465
7466 let mut inlay_ids = Vec::new();
7467 let invalidation_row_range;
7468 let move_invalidation_row_range = if cursor_row < edit_start_row {
7469 Some(cursor_row..edit_end_row)
7470 } else if cursor_row > edit_end_row {
7471 Some(edit_start_row..cursor_row)
7472 } else {
7473 None
7474 };
7475 let is_move =
7476 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7477 let completion = if is_move {
7478 invalidation_row_range =
7479 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7480 let target = first_edit_start;
7481 InlineCompletion::Move { target, snapshot }
7482 } else {
7483 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7484 && !self.inline_completions_hidden_for_vim_mode;
7485
7486 if show_completions_in_buffer {
7487 if edits
7488 .iter()
7489 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7490 {
7491 let mut inlays = Vec::new();
7492 for (range, new_text) in &edits {
7493 let inlay = Inlay::inline_completion(
7494 post_inc(&mut self.next_inlay_id),
7495 range.start,
7496 new_text.as_str(),
7497 );
7498 inlay_ids.push(inlay.id);
7499 inlays.push(inlay);
7500 }
7501
7502 self.splice_inlays(&[], inlays, cx);
7503 } else {
7504 let background_color = cx.theme().status().deleted_background;
7505 let style = HighlightStyle {
7506 background_color: Some(background_color),
7507 ..Default::default()
7508 };
7509 self.highlight_text::<InlineCompletionHighlight>(
7510 edits
7511 .iter()
7512 .map(|(range, _)| (range.clone(), style))
7513 .collect(),
7514 cx,
7515 );
7516 }
7517 }
7518
7519 invalidation_row_range = edit_start_row..edit_end_row;
7520
7521 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7522 if provider.show_tab_accept_marker() {
7523 EditDisplayMode::TabAccept
7524 } else {
7525 EditDisplayMode::Inline
7526 }
7527 } else {
7528 EditDisplayMode::DiffPopover
7529 };
7530
7531 InlineCompletion::Edit {
7532 edits,
7533 edit_preview: inline_completion.edit_preview,
7534 display_mode,
7535 snapshot,
7536 }
7537 };
7538
7539 let invalidation_range = multibuffer
7540 .anchor_before(Point::new(invalidation_row_range.start, 0))
7541 ..multibuffer.anchor_after(Point::new(
7542 invalidation_row_range.end,
7543 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7544 ));
7545
7546 self.stale_inline_completion_in_menu = None;
7547 self.active_inline_completion = Some(InlineCompletionState {
7548 inlay_ids,
7549 completion,
7550 completion_id: inline_completion.id,
7551 invalidation_range,
7552 });
7553
7554 cx.notify();
7555
7556 Some(())
7557 }
7558
7559 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7560 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7561 }
7562
7563 fn clear_tasks(&mut self) {
7564 self.tasks.clear()
7565 }
7566
7567 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7568 if self.tasks.insert(key, value).is_some() {
7569 // This case should hopefully be rare, but just in case...
7570 log::error!(
7571 "multiple different run targets found on a single line, only the last target will be rendered"
7572 )
7573 }
7574 }
7575
7576 /// Get all display points of breakpoints that will be rendered within editor
7577 ///
7578 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7579 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7580 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7581 fn active_breakpoints(
7582 &self,
7583 range: Range<DisplayRow>,
7584 window: &mut Window,
7585 cx: &mut Context<Self>,
7586 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7587 let mut breakpoint_display_points = HashMap::default();
7588
7589 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7590 return breakpoint_display_points;
7591 };
7592
7593 let snapshot = self.snapshot(window, cx);
7594
7595 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7596 let Some(project) = self.project.as_ref() else {
7597 return breakpoint_display_points;
7598 };
7599
7600 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7601 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7602
7603 for (buffer_snapshot, range, excerpt_id) in
7604 multi_buffer_snapshot.range_to_buffer_ranges(range)
7605 {
7606 let Some(buffer) = project
7607 .read(cx)
7608 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7609 else {
7610 continue;
7611 };
7612 let breakpoints = breakpoint_store.read(cx).breakpoints(
7613 &buffer,
7614 Some(
7615 buffer_snapshot.anchor_before(range.start)
7616 ..buffer_snapshot.anchor_after(range.end),
7617 ),
7618 buffer_snapshot,
7619 cx,
7620 );
7621 for (breakpoint, state) in breakpoints {
7622 let multi_buffer_anchor =
7623 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7624 let position = multi_buffer_anchor
7625 .to_point(&multi_buffer_snapshot)
7626 .to_display_point(&snapshot);
7627
7628 breakpoint_display_points.insert(
7629 position.row(),
7630 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7631 );
7632 }
7633 }
7634
7635 breakpoint_display_points
7636 }
7637
7638 fn breakpoint_context_menu(
7639 &self,
7640 anchor: Anchor,
7641 window: &mut Window,
7642 cx: &mut Context<Self>,
7643 ) -> Entity<ui::ContextMenu> {
7644 let weak_editor = cx.weak_entity();
7645 let focus_handle = self.focus_handle(cx);
7646
7647 let row = self
7648 .buffer
7649 .read(cx)
7650 .snapshot(cx)
7651 .summary_for_anchor::<Point>(&anchor)
7652 .row;
7653
7654 let breakpoint = self
7655 .breakpoint_at_row(row, window, cx)
7656 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7657
7658 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7659 "Edit Log Breakpoint"
7660 } else {
7661 "Set Log Breakpoint"
7662 };
7663
7664 let condition_breakpoint_msg = if breakpoint
7665 .as_ref()
7666 .is_some_and(|bp| bp.1.condition.is_some())
7667 {
7668 "Edit Condition Breakpoint"
7669 } else {
7670 "Set Condition Breakpoint"
7671 };
7672
7673 let hit_condition_breakpoint_msg = if breakpoint
7674 .as_ref()
7675 .is_some_and(|bp| bp.1.hit_condition.is_some())
7676 {
7677 "Edit Hit Condition Breakpoint"
7678 } else {
7679 "Set Hit Condition Breakpoint"
7680 };
7681
7682 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7683 "Unset Breakpoint"
7684 } else {
7685 "Set Breakpoint"
7686 };
7687
7688 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7689
7690 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7691 BreakpointState::Enabled => Some("Disable"),
7692 BreakpointState::Disabled => Some("Enable"),
7693 });
7694
7695 let (anchor, breakpoint) =
7696 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7697
7698 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7699 menu.on_blur_subscription(Subscription::new(|| {}))
7700 .context(focus_handle)
7701 .when(run_to_cursor, |this| {
7702 let weak_editor = weak_editor.clone();
7703 this.entry("Run to cursor", None, move |window, cx| {
7704 weak_editor
7705 .update(cx, |editor, cx| {
7706 editor.change_selections(None, window, cx, |s| {
7707 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7708 });
7709 })
7710 .ok();
7711
7712 window.dispatch_action(Box::new(RunToCursor), cx);
7713 })
7714 .separator()
7715 })
7716 .when_some(toggle_state_msg, |this, msg| {
7717 this.entry(msg, None, {
7718 let weak_editor = weak_editor.clone();
7719 let breakpoint = breakpoint.clone();
7720 move |_window, cx| {
7721 weak_editor
7722 .update(cx, |this, cx| {
7723 this.edit_breakpoint_at_anchor(
7724 anchor,
7725 breakpoint.as_ref().clone(),
7726 BreakpointEditAction::InvertState,
7727 cx,
7728 );
7729 })
7730 .log_err();
7731 }
7732 })
7733 })
7734 .entry(set_breakpoint_msg, None, {
7735 let weak_editor = weak_editor.clone();
7736 let breakpoint = breakpoint.clone();
7737 move |_window, cx| {
7738 weak_editor
7739 .update(cx, |this, cx| {
7740 this.edit_breakpoint_at_anchor(
7741 anchor,
7742 breakpoint.as_ref().clone(),
7743 BreakpointEditAction::Toggle,
7744 cx,
7745 );
7746 })
7747 .log_err();
7748 }
7749 })
7750 .entry(log_breakpoint_msg, None, {
7751 let breakpoint = breakpoint.clone();
7752 let weak_editor = weak_editor.clone();
7753 move |window, cx| {
7754 weak_editor
7755 .update(cx, |this, cx| {
7756 this.add_edit_breakpoint_block(
7757 anchor,
7758 breakpoint.as_ref(),
7759 BreakpointPromptEditAction::Log,
7760 window,
7761 cx,
7762 );
7763 })
7764 .log_err();
7765 }
7766 })
7767 .entry(condition_breakpoint_msg, None, {
7768 let breakpoint = breakpoint.clone();
7769 let weak_editor = weak_editor.clone();
7770 move |window, cx| {
7771 weak_editor
7772 .update(cx, |this, cx| {
7773 this.add_edit_breakpoint_block(
7774 anchor,
7775 breakpoint.as_ref(),
7776 BreakpointPromptEditAction::Condition,
7777 window,
7778 cx,
7779 );
7780 })
7781 .log_err();
7782 }
7783 })
7784 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7785 weak_editor
7786 .update(cx, |this, cx| {
7787 this.add_edit_breakpoint_block(
7788 anchor,
7789 breakpoint.as_ref(),
7790 BreakpointPromptEditAction::HitCondition,
7791 window,
7792 cx,
7793 );
7794 })
7795 .log_err();
7796 })
7797 })
7798 }
7799
7800 fn render_breakpoint(
7801 &self,
7802 position: Anchor,
7803 row: DisplayRow,
7804 breakpoint: &Breakpoint,
7805 state: Option<BreakpointSessionState>,
7806 cx: &mut Context<Self>,
7807 ) -> IconButton {
7808 let is_rejected = state.is_some_and(|s| !s.verified);
7809 // Is it a breakpoint that shows up when hovering over gutter?
7810 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7811 (false, false),
7812 |PhantomBreakpointIndicator {
7813 is_active,
7814 display_row,
7815 collides_with_existing_breakpoint,
7816 }| {
7817 (
7818 is_active && display_row == row,
7819 collides_with_existing_breakpoint,
7820 )
7821 },
7822 );
7823
7824 let (color, icon) = {
7825 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7826 (false, false) => ui::IconName::DebugBreakpoint,
7827 (true, false) => ui::IconName::DebugLogBreakpoint,
7828 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7829 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7830 };
7831
7832 let color = if is_phantom {
7833 Color::Hint
7834 } else if is_rejected {
7835 Color::Disabled
7836 } else {
7837 Color::Debugger
7838 };
7839
7840 (color, icon)
7841 };
7842
7843 let breakpoint = Arc::from(breakpoint.clone());
7844
7845 let alt_as_text = gpui::Keystroke {
7846 modifiers: Modifiers::secondary_key(),
7847 ..Default::default()
7848 };
7849 let primary_action_text = if breakpoint.is_disabled() {
7850 "Enable breakpoint"
7851 } else if is_phantom && !collides_with_existing {
7852 "Set breakpoint"
7853 } else {
7854 "Unset breakpoint"
7855 };
7856 let focus_handle = self.focus_handle.clone();
7857
7858 let meta = if is_rejected {
7859 SharedString::from("No executable code is associated with this line.")
7860 } else if collides_with_existing && !breakpoint.is_disabled() {
7861 SharedString::from(format!(
7862 "{alt_as_text}-click to disable,\nright-click for more options."
7863 ))
7864 } else {
7865 SharedString::from("Right-click for more options.")
7866 };
7867 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7868 .icon_size(IconSize::XSmall)
7869 .size(ui::ButtonSize::None)
7870 .when(is_rejected, |this| {
7871 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7872 })
7873 .icon_color(color)
7874 .style(ButtonStyle::Transparent)
7875 .on_click(cx.listener({
7876 let breakpoint = breakpoint.clone();
7877
7878 move |editor, event: &ClickEvent, window, cx| {
7879 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7880 BreakpointEditAction::InvertState
7881 } else {
7882 BreakpointEditAction::Toggle
7883 };
7884
7885 window.focus(&editor.focus_handle(cx));
7886 editor.edit_breakpoint_at_anchor(
7887 position,
7888 breakpoint.as_ref().clone(),
7889 edit_action,
7890 cx,
7891 );
7892 }
7893 }))
7894 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7895 editor.set_breakpoint_context_menu(
7896 row,
7897 Some(position),
7898 event.down.position,
7899 window,
7900 cx,
7901 );
7902 }))
7903 .tooltip(move |window, cx| {
7904 Tooltip::with_meta_in(
7905 primary_action_text,
7906 Some(&ToggleBreakpoint),
7907 meta.clone(),
7908 &focus_handle,
7909 window,
7910 cx,
7911 )
7912 })
7913 }
7914
7915 fn build_tasks_context(
7916 project: &Entity<Project>,
7917 buffer: &Entity<Buffer>,
7918 buffer_row: u32,
7919 tasks: &Arc<RunnableTasks>,
7920 cx: &mut Context<Self>,
7921 ) -> Task<Option<task::TaskContext>> {
7922 let position = Point::new(buffer_row, tasks.column);
7923 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7924 let location = Location {
7925 buffer: buffer.clone(),
7926 range: range_start..range_start,
7927 };
7928 // Fill in the environmental variables from the tree-sitter captures
7929 let mut captured_task_variables = TaskVariables::default();
7930 for (capture_name, value) in tasks.extra_variables.clone() {
7931 captured_task_variables.insert(
7932 task::VariableName::Custom(capture_name.into()),
7933 value.clone(),
7934 );
7935 }
7936 project.update(cx, |project, cx| {
7937 project.task_store().update(cx, |task_store, cx| {
7938 task_store.task_context_for_location(captured_task_variables, location, cx)
7939 })
7940 })
7941 }
7942
7943 pub fn spawn_nearest_task(
7944 &mut self,
7945 action: &SpawnNearestTask,
7946 window: &mut Window,
7947 cx: &mut Context<Self>,
7948 ) {
7949 let Some((workspace, _)) = self.workspace.clone() else {
7950 return;
7951 };
7952 let Some(project) = self.project.clone() else {
7953 return;
7954 };
7955
7956 // Try to find a closest, enclosing node using tree-sitter that has a
7957 // task
7958 let Some((buffer, buffer_row, tasks)) = self
7959 .find_enclosing_node_task(cx)
7960 // Or find the task that's closest in row-distance.
7961 .or_else(|| self.find_closest_task(cx))
7962 else {
7963 return;
7964 };
7965
7966 let reveal_strategy = action.reveal;
7967 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7968 cx.spawn_in(window, async move |_, cx| {
7969 let context = task_context.await?;
7970 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7971
7972 let resolved = &mut resolved_task.resolved;
7973 resolved.reveal = reveal_strategy;
7974
7975 workspace
7976 .update_in(cx, |workspace, window, cx| {
7977 workspace.schedule_resolved_task(
7978 task_source_kind,
7979 resolved_task,
7980 false,
7981 window,
7982 cx,
7983 );
7984 })
7985 .ok()
7986 })
7987 .detach();
7988 }
7989
7990 fn find_closest_task(
7991 &mut self,
7992 cx: &mut Context<Self>,
7993 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7994 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7995
7996 let ((buffer_id, row), tasks) = self
7997 .tasks
7998 .iter()
7999 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8000
8001 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8002 let tasks = Arc::new(tasks.to_owned());
8003 Some((buffer, *row, tasks))
8004 }
8005
8006 fn find_enclosing_node_task(
8007 &mut self,
8008 cx: &mut Context<Self>,
8009 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8010 let snapshot = self.buffer.read(cx).snapshot(cx);
8011 let offset = self.selections.newest::<usize>(cx).head();
8012 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8013 let buffer_id = excerpt.buffer().remote_id();
8014
8015 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8016 let mut cursor = layer.node().walk();
8017
8018 while cursor.goto_first_child_for_byte(offset).is_some() {
8019 if cursor.node().end_byte() == offset {
8020 cursor.goto_next_sibling();
8021 }
8022 }
8023
8024 // Ascend to the smallest ancestor that contains the range and has a task.
8025 loop {
8026 let node = cursor.node();
8027 let node_range = node.byte_range();
8028 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8029
8030 // Check if this node contains our offset
8031 if node_range.start <= offset && node_range.end >= offset {
8032 // If it contains offset, check for task
8033 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8034 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8035 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8036 }
8037 }
8038
8039 if !cursor.goto_parent() {
8040 break;
8041 }
8042 }
8043 None
8044 }
8045
8046 fn render_run_indicator(
8047 &self,
8048 _style: &EditorStyle,
8049 is_active: bool,
8050 row: DisplayRow,
8051 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8052 cx: &mut Context<Self>,
8053 ) -> IconButton {
8054 let color = Color::Muted;
8055 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8056
8057 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
8058 .shape(ui::IconButtonShape::Square)
8059 .icon_size(IconSize::XSmall)
8060 .icon_color(color)
8061 .toggle_state(is_active)
8062 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8063 let quick_launch = e.down.button == MouseButton::Left;
8064 window.focus(&editor.focus_handle(cx));
8065 editor.toggle_code_actions(
8066 &ToggleCodeActions {
8067 deployed_from: Some(CodeActionSource::RunMenu(row)),
8068 quick_launch,
8069 },
8070 window,
8071 cx,
8072 );
8073 }))
8074 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8075 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
8076 }))
8077 }
8078
8079 pub fn context_menu_visible(&self) -> bool {
8080 !self.edit_prediction_preview_is_active()
8081 && self
8082 .context_menu
8083 .borrow()
8084 .as_ref()
8085 .map_or(false, |menu| menu.visible())
8086 }
8087
8088 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8089 self.context_menu
8090 .borrow()
8091 .as_ref()
8092 .map(|menu| menu.origin())
8093 }
8094
8095 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8096 self.context_menu_options = Some(options);
8097 }
8098
8099 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8100 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8101
8102 fn render_edit_prediction_popover(
8103 &mut self,
8104 text_bounds: &Bounds<Pixels>,
8105 content_origin: gpui::Point<Pixels>,
8106 right_margin: Pixels,
8107 editor_snapshot: &EditorSnapshot,
8108 visible_row_range: Range<DisplayRow>,
8109 scroll_top: f32,
8110 scroll_bottom: f32,
8111 line_layouts: &[LineWithInvisibles],
8112 line_height: Pixels,
8113 scroll_pixel_position: gpui::Point<Pixels>,
8114 newest_selection_head: Option<DisplayPoint>,
8115 editor_width: Pixels,
8116 style: &EditorStyle,
8117 window: &mut Window,
8118 cx: &mut App,
8119 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8120 if self.mode().is_minimap() {
8121 return None;
8122 }
8123 let active_inline_completion = self.active_inline_completion.as_ref()?;
8124
8125 if self.edit_prediction_visible_in_cursor_popover(true) {
8126 return None;
8127 }
8128
8129 match &active_inline_completion.completion {
8130 InlineCompletion::Move { target, .. } => {
8131 let target_display_point = target.to_display_point(editor_snapshot);
8132
8133 if self.edit_prediction_requires_modifier() {
8134 if !self.edit_prediction_preview_is_active() {
8135 return None;
8136 }
8137
8138 self.render_edit_prediction_modifier_jump_popover(
8139 text_bounds,
8140 content_origin,
8141 visible_row_range,
8142 line_layouts,
8143 line_height,
8144 scroll_pixel_position,
8145 newest_selection_head,
8146 target_display_point,
8147 window,
8148 cx,
8149 )
8150 } else {
8151 self.render_edit_prediction_eager_jump_popover(
8152 text_bounds,
8153 content_origin,
8154 editor_snapshot,
8155 visible_row_range,
8156 scroll_top,
8157 scroll_bottom,
8158 line_height,
8159 scroll_pixel_position,
8160 target_display_point,
8161 editor_width,
8162 window,
8163 cx,
8164 )
8165 }
8166 }
8167 InlineCompletion::Edit {
8168 display_mode: EditDisplayMode::Inline,
8169 ..
8170 } => None,
8171 InlineCompletion::Edit {
8172 display_mode: EditDisplayMode::TabAccept,
8173 edits,
8174 ..
8175 } => {
8176 let range = &edits.first()?.0;
8177 let target_display_point = range.end.to_display_point(editor_snapshot);
8178
8179 self.render_edit_prediction_end_of_line_popover(
8180 "Accept",
8181 editor_snapshot,
8182 visible_row_range,
8183 target_display_point,
8184 line_height,
8185 scroll_pixel_position,
8186 content_origin,
8187 editor_width,
8188 window,
8189 cx,
8190 )
8191 }
8192 InlineCompletion::Edit {
8193 edits,
8194 edit_preview,
8195 display_mode: EditDisplayMode::DiffPopover,
8196 snapshot,
8197 } => self.render_edit_prediction_diff_popover(
8198 text_bounds,
8199 content_origin,
8200 right_margin,
8201 editor_snapshot,
8202 visible_row_range,
8203 line_layouts,
8204 line_height,
8205 scroll_pixel_position,
8206 newest_selection_head,
8207 editor_width,
8208 style,
8209 edits,
8210 edit_preview,
8211 snapshot,
8212 window,
8213 cx,
8214 ),
8215 }
8216 }
8217
8218 fn render_edit_prediction_modifier_jump_popover(
8219 &mut self,
8220 text_bounds: &Bounds<Pixels>,
8221 content_origin: gpui::Point<Pixels>,
8222 visible_row_range: Range<DisplayRow>,
8223 line_layouts: &[LineWithInvisibles],
8224 line_height: Pixels,
8225 scroll_pixel_position: gpui::Point<Pixels>,
8226 newest_selection_head: Option<DisplayPoint>,
8227 target_display_point: DisplayPoint,
8228 window: &mut Window,
8229 cx: &mut App,
8230 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8231 let scrolled_content_origin =
8232 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8233
8234 const SCROLL_PADDING_Y: Pixels = px(12.);
8235
8236 if target_display_point.row() < visible_row_range.start {
8237 return self.render_edit_prediction_scroll_popover(
8238 |_| SCROLL_PADDING_Y,
8239 IconName::ArrowUp,
8240 visible_row_range,
8241 line_layouts,
8242 newest_selection_head,
8243 scrolled_content_origin,
8244 window,
8245 cx,
8246 );
8247 } else if target_display_point.row() >= visible_row_range.end {
8248 return self.render_edit_prediction_scroll_popover(
8249 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8250 IconName::ArrowDown,
8251 visible_row_range,
8252 line_layouts,
8253 newest_selection_head,
8254 scrolled_content_origin,
8255 window,
8256 cx,
8257 );
8258 }
8259
8260 const POLE_WIDTH: Pixels = px(2.);
8261
8262 let line_layout =
8263 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8264 let target_column = target_display_point.column() as usize;
8265
8266 let target_x = line_layout.x_for_index(target_column);
8267 let target_y =
8268 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8269
8270 let flag_on_right = target_x < text_bounds.size.width / 2.;
8271
8272 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8273 border_color.l += 0.001;
8274
8275 let mut element = v_flex()
8276 .items_end()
8277 .when(flag_on_right, |el| el.items_start())
8278 .child(if flag_on_right {
8279 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8280 .rounded_bl(px(0.))
8281 .rounded_tl(px(0.))
8282 .border_l_2()
8283 .border_color(border_color)
8284 } else {
8285 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8286 .rounded_br(px(0.))
8287 .rounded_tr(px(0.))
8288 .border_r_2()
8289 .border_color(border_color)
8290 })
8291 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8292 .into_any();
8293
8294 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8295
8296 let mut origin = scrolled_content_origin + point(target_x, target_y)
8297 - point(
8298 if flag_on_right {
8299 POLE_WIDTH
8300 } else {
8301 size.width - POLE_WIDTH
8302 },
8303 size.height - line_height,
8304 );
8305
8306 origin.x = origin.x.max(content_origin.x);
8307
8308 element.prepaint_at(origin, window, cx);
8309
8310 Some((element, origin))
8311 }
8312
8313 fn render_edit_prediction_scroll_popover(
8314 &mut self,
8315 to_y: impl Fn(Size<Pixels>) -> Pixels,
8316 scroll_icon: IconName,
8317 visible_row_range: Range<DisplayRow>,
8318 line_layouts: &[LineWithInvisibles],
8319 newest_selection_head: Option<DisplayPoint>,
8320 scrolled_content_origin: gpui::Point<Pixels>,
8321 window: &mut Window,
8322 cx: &mut App,
8323 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8324 let mut element = self
8325 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8326 .into_any();
8327
8328 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8329
8330 let cursor = newest_selection_head?;
8331 let cursor_row_layout =
8332 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8333 let cursor_column = cursor.column() as usize;
8334
8335 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8336
8337 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8338
8339 element.prepaint_at(origin, window, cx);
8340 Some((element, origin))
8341 }
8342
8343 fn render_edit_prediction_eager_jump_popover(
8344 &mut self,
8345 text_bounds: &Bounds<Pixels>,
8346 content_origin: gpui::Point<Pixels>,
8347 editor_snapshot: &EditorSnapshot,
8348 visible_row_range: Range<DisplayRow>,
8349 scroll_top: f32,
8350 scroll_bottom: f32,
8351 line_height: Pixels,
8352 scroll_pixel_position: gpui::Point<Pixels>,
8353 target_display_point: DisplayPoint,
8354 editor_width: Pixels,
8355 window: &mut Window,
8356 cx: &mut App,
8357 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8358 if target_display_point.row().as_f32() < scroll_top {
8359 let mut element = self
8360 .render_edit_prediction_line_popover(
8361 "Jump to Edit",
8362 Some(IconName::ArrowUp),
8363 window,
8364 cx,
8365 )?
8366 .into_any();
8367
8368 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8369 let offset = point(
8370 (text_bounds.size.width - size.width) / 2.,
8371 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8372 );
8373
8374 let origin = text_bounds.origin + offset;
8375 element.prepaint_at(origin, window, cx);
8376 Some((element, origin))
8377 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8378 let mut element = self
8379 .render_edit_prediction_line_popover(
8380 "Jump to Edit",
8381 Some(IconName::ArrowDown),
8382 window,
8383 cx,
8384 )?
8385 .into_any();
8386
8387 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8388 let offset = point(
8389 (text_bounds.size.width - size.width) / 2.,
8390 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8391 );
8392
8393 let origin = text_bounds.origin + offset;
8394 element.prepaint_at(origin, window, cx);
8395 Some((element, origin))
8396 } else {
8397 self.render_edit_prediction_end_of_line_popover(
8398 "Jump to Edit",
8399 editor_snapshot,
8400 visible_row_range,
8401 target_display_point,
8402 line_height,
8403 scroll_pixel_position,
8404 content_origin,
8405 editor_width,
8406 window,
8407 cx,
8408 )
8409 }
8410 }
8411
8412 fn render_edit_prediction_end_of_line_popover(
8413 self: &mut Editor,
8414 label: &'static str,
8415 editor_snapshot: &EditorSnapshot,
8416 visible_row_range: Range<DisplayRow>,
8417 target_display_point: DisplayPoint,
8418 line_height: Pixels,
8419 scroll_pixel_position: gpui::Point<Pixels>,
8420 content_origin: gpui::Point<Pixels>,
8421 editor_width: Pixels,
8422 window: &mut Window,
8423 cx: &mut App,
8424 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8425 let target_line_end = DisplayPoint::new(
8426 target_display_point.row(),
8427 editor_snapshot.line_len(target_display_point.row()),
8428 );
8429
8430 let mut element = self
8431 .render_edit_prediction_line_popover(label, None, window, cx)?
8432 .into_any();
8433
8434 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8435
8436 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8437
8438 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8439 let mut origin = start_point
8440 + line_origin
8441 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8442 origin.x = origin.x.max(content_origin.x);
8443
8444 let max_x = content_origin.x + editor_width - size.width;
8445
8446 if origin.x > max_x {
8447 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8448
8449 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8450 origin.y += offset;
8451 IconName::ArrowUp
8452 } else {
8453 origin.y -= offset;
8454 IconName::ArrowDown
8455 };
8456
8457 element = self
8458 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8459 .into_any();
8460
8461 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8462
8463 origin.x = content_origin.x + editor_width - size.width - px(2.);
8464 }
8465
8466 element.prepaint_at(origin, window, cx);
8467 Some((element, origin))
8468 }
8469
8470 fn render_edit_prediction_diff_popover(
8471 self: &Editor,
8472 text_bounds: &Bounds<Pixels>,
8473 content_origin: gpui::Point<Pixels>,
8474 right_margin: Pixels,
8475 editor_snapshot: &EditorSnapshot,
8476 visible_row_range: Range<DisplayRow>,
8477 line_layouts: &[LineWithInvisibles],
8478 line_height: Pixels,
8479 scroll_pixel_position: gpui::Point<Pixels>,
8480 newest_selection_head: Option<DisplayPoint>,
8481 editor_width: Pixels,
8482 style: &EditorStyle,
8483 edits: &Vec<(Range<Anchor>, String)>,
8484 edit_preview: &Option<language::EditPreview>,
8485 snapshot: &language::BufferSnapshot,
8486 window: &mut Window,
8487 cx: &mut App,
8488 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8489 let edit_start = edits
8490 .first()
8491 .unwrap()
8492 .0
8493 .start
8494 .to_display_point(editor_snapshot);
8495 let edit_end = edits
8496 .last()
8497 .unwrap()
8498 .0
8499 .end
8500 .to_display_point(editor_snapshot);
8501
8502 let is_visible = visible_row_range.contains(&edit_start.row())
8503 || visible_row_range.contains(&edit_end.row());
8504 if !is_visible {
8505 return None;
8506 }
8507
8508 let highlighted_edits =
8509 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8510
8511 let styled_text = highlighted_edits.to_styled_text(&style.text);
8512 let line_count = highlighted_edits.text.lines().count();
8513
8514 const BORDER_WIDTH: Pixels = px(1.);
8515
8516 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8517 let has_keybind = keybind.is_some();
8518
8519 let mut element = h_flex()
8520 .items_start()
8521 .child(
8522 h_flex()
8523 .bg(cx.theme().colors().editor_background)
8524 .border(BORDER_WIDTH)
8525 .shadow_sm()
8526 .border_color(cx.theme().colors().border)
8527 .rounded_l_lg()
8528 .when(line_count > 1, |el| el.rounded_br_lg())
8529 .pr_1()
8530 .child(styled_text),
8531 )
8532 .child(
8533 h_flex()
8534 .h(line_height + BORDER_WIDTH * 2.)
8535 .px_1p5()
8536 .gap_1()
8537 // Workaround: For some reason, there's a gap if we don't do this
8538 .ml(-BORDER_WIDTH)
8539 .shadow(vec![gpui::BoxShadow {
8540 color: gpui::black().opacity(0.05),
8541 offset: point(px(1.), px(1.)),
8542 blur_radius: px(2.),
8543 spread_radius: px(0.),
8544 }])
8545 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8546 .border(BORDER_WIDTH)
8547 .border_color(cx.theme().colors().border)
8548 .rounded_r_lg()
8549 .id("edit_prediction_diff_popover_keybind")
8550 .when(!has_keybind, |el| {
8551 let status_colors = cx.theme().status();
8552
8553 el.bg(status_colors.error_background)
8554 .border_color(status_colors.error.opacity(0.6))
8555 .child(Icon::new(IconName::Info).color(Color::Error))
8556 .cursor_default()
8557 .hoverable_tooltip(move |_window, cx| {
8558 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8559 })
8560 })
8561 .children(keybind),
8562 )
8563 .into_any();
8564
8565 let longest_row =
8566 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8567 let longest_line_width = if visible_row_range.contains(&longest_row) {
8568 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8569 } else {
8570 layout_line(
8571 longest_row,
8572 editor_snapshot,
8573 style,
8574 editor_width,
8575 |_| false,
8576 window,
8577 cx,
8578 )
8579 .width
8580 };
8581
8582 let viewport_bounds =
8583 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8584 right: -right_margin,
8585 ..Default::default()
8586 });
8587
8588 let x_after_longest =
8589 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8590 - scroll_pixel_position.x;
8591
8592 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8593
8594 // Fully visible if it can be displayed within the window (allow overlapping other
8595 // panes). However, this is only allowed if the popover starts within text_bounds.
8596 let can_position_to_the_right = x_after_longest < text_bounds.right()
8597 && x_after_longest + element_bounds.width < viewport_bounds.right();
8598
8599 let mut origin = if can_position_to_the_right {
8600 point(
8601 x_after_longest,
8602 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8603 - scroll_pixel_position.y,
8604 )
8605 } else {
8606 let cursor_row = newest_selection_head.map(|head| head.row());
8607 let above_edit = edit_start
8608 .row()
8609 .0
8610 .checked_sub(line_count as u32)
8611 .map(DisplayRow);
8612 let below_edit = Some(edit_end.row() + 1);
8613 let above_cursor =
8614 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8615 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8616
8617 // Place the edit popover adjacent to the edit if there is a location
8618 // available that is onscreen and does not obscure the cursor. Otherwise,
8619 // place it adjacent to the cursor.
8620 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8621 .into_iter()
8622 .flatten()
8623 .find(|&start_row| {
8624 let end_row = start_row + line_count as u32;
8625 visible_row_range.contains(&start_row)
8626 && visible_row_range.contains(&end_row)
8627 && cursor_row.map_or(true, |cursor_row| {
8628 !((start_row..end_row).contains(&cursor_row))
8629 })
8630 })?;
8631
8632 content_origin
8633 + point(
8634 -scroll_pixel_position.x,
8635 row_target.as_f32() * line_height - scroll_pixel_position.y,
8636 )
8637 };
8638
8639 origin.x -= BORDER_WIDTH;
8640
8641 window.defer_draw(element, origin, 1);
8642
8643 // Do not return an element, since it will already be drawn due to defer_draw.
8644 None
8645 }
8646
8647 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8648 px(30.)
8649 }
8650
8651 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8652 if self.read_only(cx) {
8653 cx.theme().players().read_only()
8654 } else {
8655 self.style.as_ref().unwrap().local_player
8656 }
8657 }
8658
8659 fn render_edit_prediction_accept_keybind(
8660 &self,
8661 window: &mut Window,
8662 cx: &App,
8663 ) -> Option<AnyElement> {
8664 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8665 let accept_keystroke = accept_binding.keystroke()?;
8666
8667 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8668
8669 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8670 Color::Accent
8671 } else {
8672 Color::Muted
8673 };
8674
8675 h_flex()
8676 .px_0p5()
8677 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8678 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8679 .text_size(TextSize::XSmall.rems(cx))
8680 .child(h_flex().children(ui::render_modifiers(
8681 &accept_keystroke.modifiers,
8682 PlatformStyle::platform(),
8683 Some(modifiers_color),
8684 Some(IconSize::XSmall.rems().into()),
8685 true,
8686 )))
8687 .when(is_platform_style_mac, |parent| {
8688 parent.child(accept_keystroke.key.clone())
8689 })
8690 .when(!is_platform_style_mac, |parent| {
8691 parent.child(
8692 Key::new(
8693 util::capitalize(&accept_keystroke.key),
8694 Some(Color::Default),
8695 )
8696 .size(Some(IconSize::XSmall.rems().into())),
8697 )
8698 })
8699 .into_any()
8700 .into()
8701 }
8702
8703 fn render_edit_prediction_line_popover(
8704 &self,
8705 label: impl Into<SharedString>,
8706 icon: Option<IconName>,
8707 window: &mut Window,
8708 cx: &App,
8709 ) -> Option<Stateful<Div>> {
8710 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8711
8712 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8713 let has_keybind = keybind.is_some();
8714
8715 let result = h_flex()
8716 .id("ep-line-popover")
8717 .py_0p5()
8718 .pl_1()
8719 .pr(padding_right)
8720 .gap_1()
8721 .rounded_md()
8722 .border_1()
8723 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8724 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8725 .shadow_sm()
8726 .when(!has_keybind, |el| {
8727 let status_colors = cx.theme().status();
8728
8729 el.bg(status_colors.error_background)
8730 .border_color(status_colors.error.opacity(0.6))
8731 .pl_2()
8732 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8733 .cursor_default()
8734 .hoverable_tooltip(move |_window, cx| {
8735 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8736 })
8737 })
8738 .children(keybind)
8739 .child(
8740 Label::new(label)
8741 .size(LabelSize::Small)
8742 .when(!has_keybind, |el| {
8743 el.color(cx.theme().status().error.into()).strikethrough()
8744 }),
8745 )
8746 .when(!has_keybind, |el| {
8747 el.child(
8748 h_flex().ml_1().child(
8749 Icon::new(IconName::Info)
8750 .size(IconSize::Small)
8751 .color(cx.theme().status().error.into()),
8752 ),
8753 )
8754 })
8755 .when_some(icon, |element, icon| {
8756 element.child(
8757 div()
8758 .mt(px(1.5))
8759 .child(Icon::new(icon).size(IconSize::Small)),
8760 )
8761 });
8762
8763 Some(result)
8764 }
8765
8766 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8767 let accent_color = cx.theme().colors().text_accent;
8768 let editor_bg_color = cx.theme().colors().editor_background;
8769 editor_bg_color.blend(accent_color.opacity(0.1))
8770 }
8771
8772 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8773 let accent_color = cx.theme().colors().text_accent;
8774 let editor_bg_color = cx.theme().colors().editor_background;
8775 editor_bg_color.blend(accent_color.opacity(0.6))
8776 }
8777
8778 fn render_edit_prediction_cursor_popover(
8779 &self,
8780 min_width: Pixels,
8781 max_width: Pixels,
8782 cursor_point: Point,
8783 style: &EditorStyle,
8784 accept_keystroke: Option<&gpui::Keystroke>,
8785 _window: &Window,
8786 cx: &mut Context<Editor>,
8787 ) -> Option<AnyElement> {
8788 let provider = self.edit_prediction_provider.as_ref()?;
8789
8790 if provider.provider.needs_terms_acceptance(cx) {
8791 return Some(
8792 h_flex()
8793 .min_w(min_width)
8794 .flex_1()
8795 .px_2()
8796 .py_1()
8797 .gap_3()
8798 .elevation_2(cx)
8799 .hover(|style| style.bg(cx.theme().colors().element_hover))
8800 .id("accept-terms")
8801 .cursor_pointer()
8802 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8803 .on_click(cx.listener(|this, _event, window, cx| {
8804 cx.stop_propagation();
8805 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8806 window.dispatch_action(
8807 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8808 cx,
8809 );
8810 }))
8811 .child(
8812 h_flex()
8813 .flex_1()
8814 .gap_2()
8815 .child(Icon::new(IconName::ZedPredict))
8816 .child(Label::new("Accept Terms of Service"))
8817 .child(div().w_full())
8818 .child(
8819 Icon::new(IconName::ArrowUpRight)
8820 .color(Color::Muted)
8821 .size(IconSize::Small),
8822 )
8823 .into_any_element(),
8824 )
8825 .into_any(),
8826 );
8827 }
8828
8829 let is_refreshing = provider.provider.is_refreshing(cx);
8830
8831 fn pending_completion_container() -> Div {
8832 h_flex()
8833 .h_full()
8834 .flex_1()
8835 .gap_2()
8836 .child(Icon::new(IconName::ZedPredict))
8837 }
8838
8839 let completion = match &self.active_inline_completion {
8840 Some(prediction) => {
8841 if !self.has_visible_completions_menu() {
8842 const RADIUS: Pixels = px(6.);
8843 const BORDER_WIDTH: Pixels = px(1.);
8844
8845 return Some(
8846 h_flex()
8847 .elevation_2(cx)
8848 .border(BORDER_WIDTH)
8849 .border_color(cx.theme().colors().border)
8850 .when(accept_keystroke.is_none(), |el| {
8851 el.border_color(cx.theme().status().error)
8852 })
8853 .rounded(RADIUS)
8854 .rounded_tl(px(0.))
8855 .overflow_hidden()
8856 .child(div().px_1p5().child(match &prediction.completion {
8857 InlineCompletion::Move { target, snapshot } => {
8858 use text::ToPoint as _;
8859 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8860 {
8861 Icon::new(IconName::ZedPredictDown)
8862 } else {
8863 Icon::new(IconName::ZedPredictUp)
8864 }
8865 }
8866 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8867 }))
8868 .child(
8869 h_flex()
8870 .gap_1()
8871 .py_1()
8872 .px_2()
8873 .rounded_r(RADIUS - BORDER_WIDTH)
8874 .border_l_1()
8875 .border_color(cx.theme().colors().border)
8876 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8877 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8878 el.child(
8879 Label::new("Hold")
8880 .size(LabelSize::Small)
8881 .when(accept_keystroke.is_none(), |el| {
8882 el.strikethrough()
8883 })
8884 .line_height_style(LineHeightStyle::UiLabel),
8885 )
8886 })
8887 .id("edit_prediction_cursor_popover_keybind")
8888 .when(accept_keystroke.is_none(), |el| {
8889 let status_colors = cx.theme().status();
8890
8891 el.bg(status_colors.error_background)
8892 .border_color(status_colors.error.opacity(0.6))
8893 .child(Icon::new(IconName::Info).color(Color::Error))
8894 .cursor_default()
8895 .hoverable_tooltip(move |_window, cx| {
8896 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8897 .into()
8898 })
8899 })
8900 .when_some(
8901 accept_keystroke.as_ref(),
8902 |el, accept_keystroke| {
8903 el.child(h_flex().children(ui::render_modifiers(
8904 &accept_keystroke.modifiers,
8905 PlatformStyle::platform(),
8906 Some(Color::Default),
8907 Some(IconSize::XSmall.rems().into()),
8908 false,
8909 )))
8910 },
8911 ),
8912 )
8913 .into_any(),
8914 );
8915 }
8916
8917 self.render_edit_prediction_cursor_popover_preview(
8918 prediction,
8919 cursor_point,
8920 style,
8921 cx,
8922 )?
8923 }
8924
8925 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8926 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8927 stale_completion,
8928 cursor_point,
8929 style,
8930 cx,
8931 )?,
8932
8933 None => {
8934 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8935 }
8936 },
8937
8938 None => pending_completion_container().child(Label::new("No Prediction")),
8939 };
8940
8941 let completion = if is_refreshing {
8942 completion
8943 .with_animation(
8944 "loading-completion",
8945 Animation::new(Duration::from_secs(2))
8946 .repeat()
8947 .with_easing(pulsating_between(0.4, 0.8)),
8948 |label, delta| label.opacity(delta),
8949 )
8950 .into_any_element()
8951 } else {
8952 completion.into_any_element()
8953 };
8954
8955 let has_completion = self.active_inline_completion.is_some();
8956
8957 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8958 Some(
8959 h_flex()
8960 .min_w(min_width)
8961 .max_w(max_width)
8962 .flex_1()
8963 .elevation_2(cx)
8964 .border_color(cx.theme().colors().border)
8965 .child(
8966 div()
8967 .flex_1()
8968 .py_1()
8969 .px_2()
8970 .overflow_hidden()
8971 .child(completion),
8972 )
8973 .when_some(accept_keystroke, |el, accept_keystroke| {
8974 if !accept_keystroke.modifiers.modified() {
8975 return el;
8976 }
8977
8978 el.child(
8979 h_flex()
8980 .h_full()
8981 .border_l_1()
8982 .rounded_r_lg()
8983 .border_color(cx.theme().colors().border)
8984 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8985 .gap_1()
8986 .py_1()
8987 .px_2()
8988 .child(
8989 h_flex()
8990 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8991 .when(is_platform_style_mac, |parent| parent.gap_1())
8992 .child(h_flex().children(ui::render_modifiers(
8993 &accept_keystroke.modifiers,
8994 PlatformStyle::platform(),
8995 Some(if !has_completion {
8996 Color::Muted
8997 } else {
8998 Color::Default
8999 }),
9000 None,
9001 false,
9002 ))),
9003 )
9004 .child(Label::new("Preview").into_any_element())
9005 .opacity(if has_completion { 1.0 } else { 0.4 }),
9006 )
9007 })
9008 .into_any(),
9009 )
9010 }
9011
9012 fn render_edit_prediction_cursor_popover_preview(
9013 &self,
9014 completion: &InlineCompletionState,
9015 cursor_point: Point,
9016 style: &EditorStyle,
9017 cx: &mut Context<Editor>,
9018 ) -> Option<Div> {
9019 use text::ToPoint as _;
9020
9021 fn render_relative_row_jump(
9022 prefix: impl Into<String>,
9023 current_row: u32,
9024 target_row: u32,
9025 ) -> Div {
9026 let (row_diff, arrow) = if target_row < current_row {
9027 (current_row - target_row, IconName::ArrowUp)
9028 } else {
9029 (target_row - current_row, IconName::ArrowDown)
9030 };
9031
9032 h_flex()
9033 .child(
9034 Label::new(format!("{}{}", prefix.into(), row_diff))
9035 .color(Color::Muted)
9036 .size(LabelSize::Small),
9037 )
9038 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9039 }
9040
9041 match &completion.completion {
9042 InlineCompletion::Move {
9043 target, snapshot, ..
9044 } => Some(
9045 h_flex()
9046 .px_2()
9047 .gap_2()
9048 .flex_1()
9049 .child(
9050 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9051 Icon::new(IconName::ZedPredictDown)
9052 } else {
9053 Icon::new(IconName::ZedPredictUp)
9054 },
9055 )
9056 .child(Label::new("Jump to Edit")),
9057 ),
9058
9059 InlineCompletion::Edit {
9060 edits,
9061 edit_preview,
9062 snapshot,
9063 display_mode: _,
9064 } => {
9065 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9066
9067 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
9068 &snapshot,
9069 &edits,
9070 edit_preview.as_ref()?,
9071 true,
9072 cx,
9073 )
9074 .first_line_preview();
9075
9076 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9077 .with_default_highlights(&style.text, highlighted_edits.highlights);
9078
9079 let preview = h_flex()
9080 .gap_1()
9081 .min_w_16()
9082 .child(styled_text)
9083 .when(has_more_lines, |parent| parent.child("…"));
9084
9085 let left = if first_edit_row != cursor_point.row {
9086 render_relative_row_jump("", cursor_point.row, first_edit_row)
9087 .into_any_element()
9088 } else {
9089 Icon::new(IconName::ZedPredict).into_any_element()
9090 };
9091
9092 Some(
9093 h_flex()
9094 .h_full()
9095 .flex_1()
9096 .gap_2()
9097 .pr_1()
9098 .overflow_x_hidden()
9099 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9100 .child(left)
9101 .child(preview),
9102 )
9103 }
9104 }
9105 }
9106
9107 pub fn render_context_menu(
9108 &self,
9109 style: &EditorStyle,
9110 max_height_in_lines: u32,
9111 window: &mut Window,
9112 cx: &mut Context<Editor>,
9113 ) -> Option<AnyElement> {
9114 let menu = self.context_menu.borrow();
9115 let menu = menu.as_ref()?;
9116 if !menu.visible() {
9117 return None;
9118 };
9119 Some(menu.render(style, max_height_in_lines, window, cx))
9120 }
9121
9122 fn render_context_menu_aside(
9123 &mut self,
9124 max_size: Size<Pixels>,
9125 window: &mut Window,
9126 cx: &mut Context<Editor>,
9127 ) -> Option<AnyElement> {
9128 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9129 if menu.visible() {
9130 menu.render_aside(max_size, window, cx)
9131 } else {
9132 None
9133 }
9134 })
9135 }
9136
9137 fn hide_context_menu(
9138 &mut self,
9139 window: &mut Window,
9140 cx: &mut Context<Self>,
9141 ) -> Option<CodeContextMenu> {
9142 cx.notify();
9143 self.completion_tasks.clear();
9144 let context_menu = self.context_menu.borrow_mut().take();
9145 self.stale_inline_completion_in_menu.take();
9146 self.update_visible_inline_completion(window, cx);
9147 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9148 if let Some(completion_provider) = &self.completion_provider {
9149 completion_provider.selection_changed(None, window, cx);
9150 }
9151 }
9152 context_menu
9153 }
9154
9155 fn show_snippet_choices(
9156 &mut self,
9157 choices: &Vec<String>,
9158 selection: Range<Anchor>,
9159 cx: &mut Context<Self>,
9160 ) {
9161 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9162 (Some(a), Some(b)) if a == b => a,
9163 _ => {
9164 log::error!("expected anchor range to have matching buffer IDs");
9165 return;
9166 }
9167 };
9168 let multi_buffer = self.buffer().read(cx);
9169 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9170 return;
9171 };
9172
9173 let id = post_inc(&mut self.next_completion_id);
9174 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9175 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9176 CompletionsMenu::new_snippet_choices(
9177 id,
9178 true,
9179 choices,
9180 selection,
9181 buffer,
9182 snippet_sort_order,
9183 ),
9184 ));
9185 }
9186
9187 pub fn insert_snippet(
9188 &mut self,
9189 insertion_ranges: &[Range<usize>],
9190 snippet: Snippet,
9191 window: &mut Window,
9192 cx: &mut Context<Self>,
9193 ) -> Result<()> {
9194 struct Tabstop<T> {
9195 is_end_tabstop: bool,
9196 ranges: Vec<Range<T>>,
9197 choices: Option<Vec<String>>,
9198 }
9199
9200 let tabstops = self.buffer.update(cx, |buffer, cx| {
9201 let snippet_text: Arc<str> = snippet.text.clone().into();
9202 let edits = insertion_ranges
9203 .iter()
9204 .cloned()
9205 .map(|range| (range, snippet_text.clone()));
9206 let autoindent_mode = AutoindentMode::Block {
9207 original_indent_columns: Vec::new(),
9208 };
9209 buffer.edit(edits, Some(autoindent_mode), cx);
9210
9211 let snapshot = &*buffer.read(cx);
9212 let snippet = &snippet;
9213 snippet
9214 .tabstops
9215 .iter()
9216 .map(|tabstop| {
9217 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9218 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9219 });
9220 let mut tabstop_ranges = tabstop
9221 .ranges
9222 .iter()
9223 .flat_map(|tabstop_range| {
9224 let mut delta = 0_isize;
9225 insertion_ranges.iter().map(move |insertion_range| {
9226 let insertion_start = insertion_range.start as isize + delta;
9227 delta +=
9228 snippet.text.len() as isize - insertion_range.len() as isize;
9229
9230 let start = ((insertion_start + tabstop_range.start) as usize)
9231 .min(snapshot.len());
9232 let end = ((insertion_start + tabstop_range.end) as usize)
9233 .min(snapshot.len());
9234 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9235 })
9236 })
9237 .collect::<Vec<_>>();
9238 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9239
9240 Tabstop {
9241 is_end_tabstop,
9242 ranges: tabstop_ranges,
9243 choices: tabstop.choices.clone(),
9244 }
9245 })
9246 .collect::<Vec<_>>()
9247 });
9248 if let Some(tabstop) = tabstops.first() {
9249 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9250 // Reverse order so that the first range is the newest created selection.
9251 // Completions will use it and autoscroll will prioritize it.
9252 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9253 });
9254
9255 if let Some(choices) = &tabstop.choices {
9256 if let Some(selection) = tabstop.ranges.first() {
9257 self.show_snippet_choices(choices, selection.clone(), cx)
9258 }
9259 }
9260
9261 // If we're already at the last tabstop and it's at the end of the snippet,
9262 // we're done, we don't need to keep the state around.
9263 if !tabstop.is_end_tabstop {
9264 let choices = tabstops
9265 .iter()
9266 .map(|tabstop| tabstop.choices.clone())
9267 .collect();
9268
9269 let ranges = tabstops
9270 .into_iter()
9271 .map(|tabstop| tabstop.ranges)
9272 .collect::<Vec<_>>();
9273
9274 self.snippet_stack.push(SnippetState {
9275 active_index: 0,
9276 ranges,
9277 choices,
9278 });
9279 }
9280
9281 // Check whether the just-entered snippet ends with an auto-closable bracket.
9282 if self.autoclose_regions.is_empty() {
9283 let snapshot = self.buffer.read(cx).snapshot(cx);
9284 for selection in &mut self.selections.all::<Point>(cx) {
9285 let selection_head = selection.head();
9286 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9287 continue;
9288 };
9289
9290 let mut bracket_pair = None;
9291 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9292 let prev_chars = snapshot
9293 .reversed_chars_at(selection_head)
9294 .collect::<String>();
9295 for (pair, enabled) in scope.brackets() {
9296 if enabled
9297 && pair.close
9298 && prev_chars.starts_with(pair.start.as_str())
9299 && next_chars.starts_with(pair.end.as_str())
9300 {
9301 bracket_pair = Some(pair.clone());
9302 break;
9303 }
9304 }
9305 if let Some(pair) = bracket_pair {
9306 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9307 let autoclose_enabled =
9308 self.use_autoclose && snapshot_settings.use_autoclose;
9309 if autoclose_enabled {
9310 let start = snapshot.anchor_after(selection_head);
9311 let end = snapshot.anchor_after(selection_head);
9312 self.autoclose_regions.push(AutocloseRegion {
9313 selection_id: selection.id,
9314 range: start..end,
9315 pair,
9316 });
9317 }
9318 }
9319 }
9320 }
9321 }
9322 Ok(())
9323 }
9324
9325 pub fn move_to_next_snippet_tabstop(
9326 &mut self,
9327 window: &mut Window,
9328 cx: &mut Context<Self>,
9329 ) -> bool {
9330 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9331 }
9332
9333 pub fn move_to_prev_snippet_tabstop(
9334 &mut self,
9335 window: &mut Window,
9336 cx: &mut Context<Self>,
9337 ) -> bool {
9338 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9339 }
9340
9341 pub fn move_to_snippet_tabstop(
9342 &mut self,
9343 bias: Bias,
9344 window: &mut Window,
9345 cx: &mut Context<Self>,
9346 ) -> bool {
9347 if let Some(mut snippet) = self.snippet_stack.pop() {
9348 match bias {
9349 Bias::Left => {
9350 if snippet.active_index > 0 {
9351 snippet.active_index -= 1;
9352 } else {
9353 self.snippet_stack.push(snippet);
9354 return false;
9355 }
9356 }
9357 Bias::Right => {
9358 if snippet.active_index + 1 < snippet.ranges.len() {
9359 snippet.active_index += 1;
9360 } else {
9361 self.snippet_stack.push(snippet);
9362 return false;
9363 }
9364 }
9365 }
9366 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9367 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9368 // Reverse order so that the first range is the newest created selection.
9369 // Completions will use it and autoscroll will prioritize it.
9370 s.select_ranges(current_ranges.iter().rev().cloned())
9371 });
9372
9373 if let Some(choices) = &snippet.choices[snippet.active_index] {
9374 if let Some(selection) = current_ranges.first() {
9375 self.show_snippet_choices(&choices, selection.clone(), cx);
9376 }
9377 }
9378
9379 // If snippet state is not at the last tabstop, push it back on the stack
9380 if snippet.active_index + 1 < snippet.ranges.len() {
9381 self.snippet_stack.push(snippet);
9382 }
9383 return true;
9384 }
9385 }
9386
9387 false
9388 }
9389
9390 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9391 self.transact(window, cx, |this, window, cx| {
9392 this.select_all(&SelectAll, window, cx);
9393 this.insert("", window, cx);
9394 });
9395 }
9396
9397 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9398 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9399 self.transact(window, cx, |this, window, cx| {
9400 this.select_autoclose_pair(window, cx);
9401 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9402 if !this.linked_edit_ranges.is_empty() {
9403 let selections = this.selections.all::<MultiBufferPoint>(cx);
9404 let snapshot = this.buffer.read(cx).snapshot(cx);
9405
9406 for selection in selections.iter() {
9407 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9408 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9409 if selection_start.buffer_id != selection_end.buffer_id {
9410 continue;
9411 }
9412 if let Some(ranges) =
9413 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9414 {
9415 for (buffer, entries) in ranges {
9416 linked_ranges.entry(buffer).or_default().extend(entries);
9417 }
9418 }
9419 }
9420 }
9421
9422 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9423 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9424 for selection in &mut selections {
9425 if selection.is_empty() {
9426 let old_head = selection.head();
9427 let mut new_head =
9428 movement::left(&display_map, old_head.to_display_point(&display_map))
9429 .to_point(&display_map);
9430 if let Some((buffer, line_buffer_range)) = display_map
9431 .buffer_snapshot
9432 .buffer_line_for_row(MultiBufferRow(old_head.row))
9433 {
9434 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9435 let indent_len = match indent_size.kind {
9436 IndentKind::Space => {
9437 buffer.settings_at(line_buffer_range.start, cx).tab_size
9438 }
9439 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9440 };
9441 if old_head.column <= indent_size.len && old_head.column > 0 {
9442 let indent_len = indent_len.get();
9443 new_head = cmp::min(
9444 new_head,
9445 MultiBufferPoint::new(
9446 old_head.row,
9447 ((old_head.column - 1) / indent_len) * indent_len,
9448 ),
9449 );
9450 }
9451 }
9452
9453 selection.set_head(new_head, SelectionGoal::None);
9454 }
9455 }
9456
9457 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9458 s.select(selections)
9459 });
9460 this.insert("", window, cx);
9461 let empty_str: Arc<str> = Arc::from("");
9462 for (buffer, edits) in linked_ranges {
9463 let snapshot = buffer.read(cx).snapshot();
9464 use text::ToPoint as TP;
9465
9466 let edits = edits
9467 .into_iter()
9468 .map(|range| {
9469 let end_point = TP::to_point(&range.end, &snapshot);
9470 let mut start_point = TP::to_point(&range.start, &snapshot);
9471
9472 if end_point == start_point {
9473 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9474 .saturating_sub(1);
9475 start_point =
9476 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9477 };
9478
9479 (start_point..end_point, empty_str.clone())
9480 })
9481 .sorted_by_key(|(range, _)| range.start)
9482 .collect::<Vec<_>>();
9483 buffer.update(cx, |this, cx| {
9484 this.edit(edits, None, cx);
9485 })
9486 }
9487 this.refresh_inline_completion(true, false, window, cx);
9488 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9489 });
9490 }
9491
9492 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9493 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9494 self.transact(window, cx, |this, window, cx| {
9495 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9496 s.move_with(|map, selection| {
9497 if selection.is_empty() {
9498 let cursor = movement::right(map, selection.head());
9499 selection.end = cursor;
9500 selection.reversed = true;
9501 selection.goal = SelectionGoal::None;
9502 }
9503 })
9504 });
9505 this.insert("", window, cx);
9506 this.refresh_inline_completion(true, false, window, cx);
9507 });
9508 }
9509
9510 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9511 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9512 if self.move_to_prev_snippet_tabstop(window, cx) {
9513 return;
9514 }
9515 self.outdent(&Outdent, window, cx);
9516 }
9517
9518 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9519 if self.move_to_next_snippet_tabstop(window, cx) {
9520 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9521 return;
9522 }
9523 if self.read_only(cx) {
9524 return;
9525 }
9526 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9527 let mut selections = self.selections.all_adjusted(cx);
9528 let buffer = self.buffer.read(cx);
9529 let snapshot = buffer.snapshot(cx);
9530 let rows_iter = selections.iter().map(|s| s.head().row);
9531 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9532
9533 let has_some_cursor_in_whitespace = selections
9534 .iter()
9535 .filter(|selection| selection.is_empty())
9536 .any(|selection| {
9537 let cursor = selection.head();
9538 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9539 cursor.column < current_indent.len
9540 });
9541
9542 let mut edits = Vec::new();
9543 let mut prev_edited_row = 0;
9544 let mut row_delta = 0;
9545 for selection in &mut selections {
9546 if selection.start.row != prev_edited_row {
9547 row_delta = 0;
9548 }
9549 prev_edited_row = selection.end.row;
9550
9551 // If the selection is non-empty, then increase the indentation of the selected lines.
9552 if !selection.is_empty() {
9553 row_delta =
9554 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9555 continue;
9556 }
9557
9558 let cursor = selection.head();
9559 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9560 if let Some(suggested_indent) =
9561 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9562 {
9563 // Don't do anything if already at suggested indent
9564 // and there is any other cursor which is not
9565 if has_some_cursor_in_whitespace
9566 && cursor.column == current_indent.len
9567 && current_indent.len == suggested_indent.len
9568 {
9569 continue;
9570 }
9571
9572 // Adjust line and move cursor to suggested indent
9573 // if cursor is not at suggested indent
9574 if cursor.column < suggested_indent.len
9575 && cursor.column <= current_indent.len
9576 && current_indent.len <= suggested_indent.len
9577 {
9578 selection.start = Point::new(cursor.row, suggested_indent.len);
9579 selection.end = selection.start;
9580 if row_delta == 0 {
9581 edits.extend(Buffer::edit_for_indent_size_adjustment(
9582 cursor.row,
9583 current_indent,
9584 suggested_indent,
9585 ));
9586 row_delta = suggested_indent.len - current_indent.len;
9587 }
9588 continue;
9589 }
9590
9591 // If current indent is more than suggested indent
9592 // only move cursor to current indent and skip indent
9593 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9594 selection.start = Point::new(cursor.row, current_indent.len);
9595 selection.end = selection.start;
9596 continue;
9597 }
9598 }
9599
9600 // Otherwise, insert a hard or soft tab.
9601 let settings = buffer.language_settings_at(cursor, cx);
9602 let tab_size = if settings.hard_tabs {
9603 IndentSize::tab()
9604 } else {
9605 let tab_size = settings.tab_size.get();
9606 let indent_remainder = snapshot
9607 .text_for_range(Point::new(cursor.row, 0)..cursor)
9608 .flat_map(str::chars)
9609 .fold(row_delta % tab_size, |counter: u32, c| {
9610 if c == '\t' {
9611 0
9612 } else {
9613 (counter + 1) % tab_size
9614 }
9615 });
9616
9617 let chars_to_next_tab_stop = tab_size - indent_remainder;
9618 IndentSize::spaces(chars_to_next_tab_stop)
9619 };
9620 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9621 selection.end = selection.start;
9622 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9623 row_delta += tab_size.len;
9624 }
9625
9626 self.transact(window, cx, |this, window, cx| {
9627 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9628 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9629 s.select(selections)
9630 });
9631 this.refresh_inline_completion(true, false, window, cx);
9632 });
9633 }
9634
9635 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9636 if self.read_only(cx) {
9637 return;
9638 }
9639 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9640 let mut selections = self.selections.all::<Point>(cx);
9641 let mut prev_edited_row = 0;
9642 let mut row_delta = 0;
9643 let mut edits = Vec::new();
9644 let buffer = self.buffer.read(cx);
9645 let snapshot = buffer.snapshot(cx);
9646 for selection in &mut selections {
9647 if selection.start.row != prev_edited_row {
9648 row_delta = 0;
9649 }
9650 prev_edited_row = selection.end.row;
9651
9652 row_delta =
9653 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9654 }
9655
9656 self.transact(window, cx, |this, window, cx| {
9657 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9658 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9659 s.select(selections)
9660 });
9661 });
9662 }
9663
9664 fn indent_selection(
9665 buffer: &MultiBuffer,
9666 snapshot: &MultiBufferSnapshot,
9667 selection: &mut Selection<Point>,
9668 edits: &mut Vec<(Range<Point>, String)>,
9669 delta_for_start_row: u32,
9670 cx: &App,
9671 ) -> u32 {
9672 let settings = buffer.language_settings_at(selection.start, cx);
9673 let tab_size = settings.tab_size.get();
9674 let indent_kind = if settings.hard_tabs {
9675 IndentKind::Tab
9676 } else {
9677 IndentKind::Space
9678 };
9679 let mut start_row = selection.start.row;
9680 let mut end_row = selection.end.row + 1;
9681
9682 // If a selection ends at the beginning of a line, don't indent
9683 // that last line.
9684 if selection.end.column == 0 && selection.end.row > selection.start.row {
9685 end_row -= 1;
9686 }
9687
9688 // Avoid re-indenting a row that has already been indented by a
9689 // previous selection, but still update this selection's column
9690 // to reflect that indentation.
9691 if delta_for_start_row > 0 {
9692 start_row += 1;
9693 selection.start.column += delta_for_start_row;
9694 if selection.end.row == selection.start.row {
9695 selection.end.column += delta_for_start_row;
9696 }
9697 }
9698
9699 let mut delta_for_end_row = 0;
9700 let has_multiple_rows = start_row + 1 != end_row;
9701 for row in start_row..end_row {
9702 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9703 let indent_delta = match (current_indent.kind, indent_kind) {
9704 (IndentKind::Space, IndentKind::Space) => {
9705 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9706 IndentSize::spaces(columns_to_next_tab_stop)
9707 }
9708 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9709 (_, IndentKind::Tab) => IndentSize::tab(),
9710 };
9711
9712 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9713 0
9714 } else {
9715 selection.start.column
9716 };
9717 let row_start = Point::new(row, start);
9718 edits.push((
9719 row_start..row_start,
9720 indent_delta.chars().collect::<String>(),
9721 ));
9722
9723 // Update this selection's endpoints to reflect the indentation.
9724 if row == selection.start.row {
9725 selection.start.column += indent_delta.len;
9726 }
9727 if row == selection.end.row {
9728 selection.end.column += indent_delta.len;
9729 delta_for_end_row = indent_delta.len;
9730 }
9731 }
9732
9733 if selection.start.row == selection.end.row {
9734 delta_for_start_row + delta_for_end_row
9735 } else {
9736 delta_for_end_row
9737 }
9738 }
9739
9740 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9741 if self.read_only(cx) {
9742 return;
9743 }
9744 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9745 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9746 let selections = self.selections.all::<Point>(cx);
9747 let mut deletion_ranges = Vec::new();
9748 let mut last_outdent = None;
9749 {
9750 let buffer = self.buffer.read(cx);
9751 let snapshot = buffer.snapshot(cx);
9752 for selection in &selections {
9753 let settings = buffer.language_settings_at(selection.start, cx);
9754 let tab_size = settings.tab_size.get();
9755 let mut rows = selection.spanned_rows(false, &display_map);
9756
9757 // Avoid re-outdenting a row that has already been outdented by a
9758 // previous selection.
9759 if let Some(last_row) = last_outdent {
9760 if last_row == rows.start {
9761 rows.start = rows.start.next_row();
9762 }
9763 }
9764 let has_multiple_rows = rows.len() > 1;
9765 for row in rows.iter_rows() {
9766 let indent_size = snapshot.indent_size_for_line(row);
9767 if indent_size.len > 0 {
9768 let deletion_len = match indent_size.kind {
9769 IndentKind::Space => {
9770 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9771 if columns_to_prev_tab_stop == 0 {
9772 tab_size
9773 } else {
9774 columns_to_prev_tab_stop
9775 }
9776 }
9777 IndentKind::Tab => 1,
9778 };
9779 let start = if has_multiple_rows
9780 || deletion_len > selection.start.column
9781 || indent_size.len < selection.start.column
9782 {
9783 0
9784 } else {
9785 selection.start.column - deletion_len
9786 };
9787 deletion_ranges.push(
9788 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9789 );
9790 last_outdent = Some(row);
9791 }
9792 }
9793 }
9794 }
9795
9796 self.transact(window, cx, |this, window, cx| {
9797 this.buffer.update(cx, |buffer, cx| {
9798 let empty_str: Arc<str> = Arc::default();
9799 buffer.edit(
9800 deletion_ranges
9801 .into_iter()
9802 .map(|range| (range, empty_str.clone())),
9803 None,
9804 cx,
9805 );
9806 });
9807 let selections = this.selections.all::<usize>(cx);
9808 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9809 s.select(selections)
9810 });
9811 });
9812 }
9813
9814 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9815 if self.read_only(cx) {
9816 return;
9817 }
9818 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9819 let selections = self
9820 .selections
9821 .all::<usize>(cx)
9822 .into_iter()
9823 .map(|s| s.range());
9824
9825 self.transact(window, cx, |this, window, cx| {
9826 this.buffer.update(cx, |buffer, cx| {
9827 buffer.autoindent_ranges(selections, cx);
9828 });
9829 let selections = this.selections.all::<usize>(cx);
9830 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9831 s.select(selections)
9832 });
9833 });
9834 }
9835
9836 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9837 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9838 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9839 let selections = self.selections.all::<Point>(cx);
9840
9841 let mut new_cursors = Vec::new();
9842 let mut edit_ranges = Vec::new();
9843 let mut selections = selections.iter().peekable();
9844 while let Some(selection) = selections.next() {
9845 let mut rows = selection.spanned_rows(false, &display_map);
9846 let goal_display_column = selection.head().to_display_point(&display_map).column();
9847
9848 // Accumulate contiguous regions of rows that we want to delete.
9849 while let Some(next_selection) = selections.peek() {
9850 let next_rows = next_selection.spanned_rows(false, &display_map);
9851 if next_rows.start <= rows.end {
9852 rows.end = next_rows.end;
9853 selections.next().unwrap();
9854 } else {
9855 break;
9856 }
9857 }
9858
9859 let buffer = &display_map.buffer_snapshot;
9860 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9861 let edit_end;
9862 let cursor_buffer_row;
9863 if buffer.max_point().row >= rows.end.0 {
9864 // If there's a line after the range, delete the \n from the end of the row range
9865 // and position the cursor on the next line.
9866 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9867 cursor_buffer_row = rows.end;
9868 } else {
9869 // If there isn't a line after the range, delete the \n from the line before the
9870 // start of the row range and position the cursor there.
9871 edit_start = edit_start.saturating_sub(1);
9872 edit_end = buffer.len();
9873 cursor_buffer_row = rows.start.previous_row();
9874 }
9875
9876 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9877 *cursor.column_mut() =
9878 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9879
9880 new_cursors.push((
9881 selection.id,
9882 buffer.anchor_after(cursor.to_point(&display_map)),
9883 ));
9884 edit_ranges.push(edit_start..edit_end);
9885 }
9886
9887 self.transact(window, cx, |this, window, cx| {
9888 let buffer = this.buffer.update(cx, |buffer, cx| {
9889 let empty_str: Arc<str> = Arc::default();
9890 buffer.edit(
9891 edit_ranges
9892 .into_iter()
9893 .map(|range| (range, empty_str.clone())),
9894 None,
9895 cx,
9896 );
9897 buffer.snapshot(cx)
9898 });
9899 let new_selections = new_cursors
9900 .into_iter()
9901 .map(|(id, cursor)| {
9902 let cursor = cursor.to_point(&buffer);
9903 Selection {
9904 id,
9905 start: cursor,
9906 end: cursor,
9907 reversed: false,
9908 goal: SelectionGoal::None,
9909 }
9910 })
9911 .collect();
9912
9913 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9914 s.select(new_selections);
9915 });
9916 });
9917 }
9918
9919 pub fn join_lines_impl(
9920 &mut self,
9921 insert_whitespace: bool,
9922 window: &mut Window,
9923 cx: &mut Context<Self>,
9924 ) {
9925 if self.read_only(cx) {
9926 return;
9927 }
9928 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9929 for selection in self.selections.all::<Point>(cx) {
9930 let start = MultiBufferRow(selection.start.row);
9931 // Treat single line selections as if they include the next line. Otherwise this action
9932 // would do nothing for single line selections individual cursors.
9933 let end = if selection.start.row == selection.end.row {
9934 MultiBufferRow(selection.start.row + 1)
9935 } else {
9936 MultiBufferRow(selection.end.row)
9937 };
9938
9939 if let Some(last_row_range) = row_ranges.last_mut() {
9940 if start <= last_row_range.end {
9941 last_row_range.end = end;
9942 continue;
9943 }
9944 }
9945 row_ranges.push(start..end);
9946 }
9947
9948 let snapshot = self.buffer.read(cx).snapshot(cx);
9949 let mut cursor_positions = Vec::new();
9950 for row_range in &row_ranges {
9951 let anchor = snapshot.anchor_before(Point::new(
9952 row_range.end.previous_row().0,
9953 snapshot.line_len(row_range.end.previous_row()),
9954 ));
9955 cursor_positions.push(anchor..anchor);
9956 }
9957
9958 self.transact(window, cx, |this, window, cx| {
9959 for row_range in row_ranges.into_iter().rev() {
9960 for row in row_range.iter_rows().rev() {
9961 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9962 let next_line_row = row.next_row();
9963 let indent = snapshot.indent_size_for_line(next_line_row);
9964 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9965
9966 let replace =
9967 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9968 " "
9969 } else {
9970 ""
9971 };
9972
9973 this.buffer.update(cx, |buffer, cx| {
9974 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9975 });
9976 }
9977 }
9978
9979 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9980 s.select_anchor_ranges(cursor_positions)
9981 });
9982 });
9983 }
9984
9985 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9986 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9987 self.join_lines_impl(true, window, cx);
9988 }
9989
9990 pub fn sort_lines_case_sensitive(
9991 &mut self,
9992 _: &SortLinesCaseSensitive,
9993 window: &mut Window,
9994 cx: &mut Context<Self>,
9995 ) {
9996 self.manipulate_lines(window, cx, |lines| lines.sort())
9997 }
9998
9999 pub fn sort_lines_case_insensitive(
10000 &mut self,
10001 _: &SortLinesCaseInsensitive,
10002 window: &mut Window,
10003 cx: &mut Context<Self>,
10004 ) {
10005 self.manipulate_lines(window, cx, |lines| {
10006 lines.sort_by_key(|line| line.to_lowercase())
10007 })
10008 }
10009
10010 pub fn unique_lines_case_insensitive(
10011 &mut self,
10012 _: &UniqueLinesCaseInsensitive,
10013 window: &mut Window,
10014 cx: &mut Context<Self>,
10015 ) {
10016 self.manipulate_lines(window, cx, |lines| {
10017 let mut seen = HashSet::default();
10018 lines.retain(|line| seen.insert(line.to_lowercase()));
10019 })
10020 }
10021
10022 pub fn unique_lines_case_sensitive(
10023 &mut self,
10024 _: &UniqueLinesCaseSensitive,
10025 window: &mut Window,
10026 cx: &mut Context<Self>,
10027 ) {
10028 self.manipulate_lines(window, cx, |lines| {
10029 let mut seen = HashSet::default();
10030 lines.retain(|line| seen.insert(*line));
10031 })
10032 }
10033
10034 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10035 let Some(project) = self.project.clone() else {
10036 return;
10037 };
10038 self.reload(project, window, cx)
10039 .detach_and_notify_err(window, cx);
10040 }
10041
10042 pub fn restore_file(
10043 &mut self,
10044 _: &::git::RestoreFile,
10045 window: &mut Window,
10046 cx: &mut Context<Self>,
10047 ) {
10048 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10049 let mut buffer_ids = HashSet::default();
10050 let snapshot = self.buffer().read(cx).snapshot(cx);
10051 for selection in self.selections.all::<usize>(cx) {
10052 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10053 }
10054
10055 let buffer = self.buffer().read(cx);
10056 let ranges = buffer_ids
10057 .into_iter()
10058 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10059 .collect::<Vec<_>>();
10060
10061 self.restore_hunks_in_ranges(ranges, window, cx);
10062 }
10063
10064 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10065 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10066 let selections = self
10067 .selections
10068 .all(cx)
10069 .into_iter()
10070 .map(|s| s.range())
10071 .collect();
10072 self.restore_hunks_in_ranges(selections, window, cx);
10073 }
10074
10075 pub fn restore_hunks_in_ranges(
10076 &mut self,
10077 ranges: Vec<Range<Point>>,
10078 window: &mut Window,
10079 cx: &mut Context<Editor>,
10080 ) {
10081 let mut revert_changes = HashMap::default();
10082 let chunk_by = self
10083 .snapshot(window, cx)
10084 .hunks_for_ranges(ranges)
10085 .into_iter()
10086 .chunk_by(|hunk| hunk.buffer_id);
10087 for (buffer_id, hunks) in &chunk_by {
10088 let hunks = hunks.collect::<Vec<_>>();
10089 for hunk in &hunks {
10090 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10091 }
10092 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10093 }
10094 drop(chunk_by);
10095 if !revert_changes.is_empty() {
10096 self.transact(window, cx, |editor, window, cx| {
10097 editor.restore(revert_changes, window, cx);
10098 });
10099 }
10100 }
10101
10102 pub fn open_active_item_in_terminal(
10103 &mut self,
10104 _: &OpenInTerminal,
10105 window: &mut Window,
10106 cx: &mut Context<Self>,
10107 ) {
10108 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10109 let project_path = buffer.read(cx).project_path(cx)?;
10110 let project = self.project.as_ref()?.read(cx);
10111 let entry = project.entry_for_path(&project_path, cx)?;
10112 let parent = match &entry.canonical_path {
10113 Some(canonical_path) => canonical_path.to_path_buf(),
10114 None => project.absolute_path(&project_path, cx)?,
10115 }
10116 .parent()?
10117 .to_path_buf();
10118 Some(parent)
10119 }) {
10120 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10121 }
10122 }
10123
10124 fn set_breakpoint_context_menu(
10125 &mut self,
10126 display_row: DisplayRow,
10127 position: Option<Anchor>,
10128 clicked_point: gpui::Point<Pixels>,
10129 window: &mut Window,
10130 cx: &mut Context<Self>,
10131 ) {
10132 if !cx.has_flag::<DebuggerFeatureFlag>() {
10133 return;
10134 }
10135 let source = self
10136 .buffer
10137 .read(cx)
10138 .snapshot(cx)
10139 .anchor_before(Point::new(display_row.0, 0u32));
10140
10141 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10142
10143 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10144 self,
10145 source,
10146 clicked_point,
10147 context_menu,
10148 window,
10149 cx,
10150 );
10151 }
10152
10153 fn add_edit_breakpoint_block(
10154 &mut self,
10155 anchor: Anchor,
10156 breakpoint: &Breakpoint,
10157 edit_action: BreakpointPromptEditAction,
10158 window: &mut Window,
10159 cx: &mut Context<Self>,
10160 ) {
10161 let weak_editor = cx.weak_entity();
10162 let bp_prompt = cx.new(|cx| {
10163 BreakpointPromptEditor::new(
10164 weak_editor,
10165 anchor,
10166 breakpoint.clone(),
10167 edit_action,
10168 window,
10169 cx,
10170 )
10171 });
10172
10173 let height = bp_prompt.update(cx, |this, cx| {
10174 this.prompt
10175 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10176 });
10177 let cloned_prompt = bp_prompt.clone();
10178 let blocks = vec![BlockProperties {
10179 style: BlockStyle::Sticky,
10180 placement: BlockPlacement::Above(anchor),
10181 height: Some(height),
10182 render: Arc::new(move |cx| {
10183 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10184 cloned_prompt.clone().into_any_element()
10185 }),
10186 priority: 0,
10187 render_in_minimap: true,
10188 }];
10189
10190 let focus_handle = bp_prompt.focus_handle(cx);
10191 window.focus(&focus_handle);
10192
10193 let block_ids = self.insert_blocks(blocks, None, cx);
10194 bp_prompt.update(cx, |prompt, _| {
10195 prompt.add_block_ids(block_ids);
10196 });
10197 }
10198
10199 pub(crate) fn breakpoint_at_row(
10200 &self,
10201 row: u32,
10202 window: &mut Window,
10203 cx: &mut Context<Self>,
10204 ) -> Option<(Anchor, Breakpoint)> {
10205 let snapshot = self.snapshot(window, cx);
10206 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10207
10208 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10209 }
10210
10211 pub(crate) fn breakpoint_at_anchor(
10212 &self,
10213 breakpoint_position: Anchor,
10214 snapshot: &EditorSnapshot,
10215 cx: &mut Context<Self>,
10216 ) -> Option<(Anchor, Breakpoint)> {
10217 let project = self.project.clone()?;
10218
10219 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10220 snapshot
10221 .buffer_snapshot
10222 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10223 })?;
10224
10225 let enclosing_excerpt = breakpoint_position.excerpt_id;
10226 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10227 let buffer_snapshot = buffer.read(cx).snapshot();
10228
10229 let row = buffer_snapshot
10230 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10231 .row;
10232
10233 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10234 let anchor_end = snapshot
10235 .buffer_snapshot
10236 .anchor_after(Point::new(row, line_len));
10237
10238 let bp = self
10239 .breakpoint_store
10240 .as_ref()?
10241 .read_with(cx, |breakpoint_store, cx| {
10242 breakpoint_store
10243 .breakpoints(
10244 &buffer,
10245 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10246 &buffer_snapshot,
10247 cx,
10248 )
10249 .next()
10250 .and_then(|(bp, _)| {
10251 let breakpoint_row = buffer_snapshot
10252 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10253 .row;
10254
10255 if breakpoint_row == row {
10256 snapshot
10257 .buffer_snapshot
10258 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10259 .map(|position| (position, bp.bp.clone()))
10260 } else {
10261 None
10262 }
10263 })
10264 });
10265 bp
10266 }
10267
10268 pub fn edit_log_breakpoint(
10269 &mut self,
10270 _: &EditLogBreakpoint,
10271 window: &mut Window,
10272 cx: &mut Context<Self>,
10273 ) {
10274 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10275 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10276 message: None,
10277 state: BreakpointState::Enabled,
10278 condition: None,
10279 hit_condition: None,
10280 });
10281
10282 self.add_edit_breakpoint_block(
10283 anchor,
10284 &breakpoint,
10285 BreakpointPromptEditAction::Log,
10286 window,
10287 cx,
10288 );
10289 }
10290 }
10291
10292 fn breakpoints_at_cursors(
10293 &self,
10294 window: &mut Window,
10295 cx: &mut Context<Self>,
10296 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10297 let snapshot = self.snapshot(window, cx);
10298 let cursors = self
10299 .selections
10300 .disjoint_anchors()
10301 .into_iter()
10302 .map(|selection| {
10303 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10304
10305 let breakpoint_position = self
10306 .breakpoint_at_row(cursor_position.row, window, cx)
10307 .map(|bp| bp.0)
10308 .unwrap_or_else(|| {
10309 snapshot
10310 .display_snapshot
10311 .buffer_snapshot
10312 .anchor_after(Point::new(cursor_position.row, 0))
10313 });
10314
10315 let breakpoint = self
10316 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10317 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10318
10319 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10320 })
10321 // 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.
10322 .collect::<HashMap<Anchor, _>>();
10323
10324 cursors.into_iter().collect()
10325 }
10326
10327 pub fn enable_breakpoint(
10328 &mut self,
10329 _: &crate::actions::EnableBreakpoint,
10330 window: &mut Window,
10331 cx: &mut Context<Self>,
10332 ) {
10333 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10334 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10335 continue;
10336 };
10337 self.edit_breakpoint_at_anchor(
10338 anchor,
10339 breakpoint,
10340 BreakpointEditAction::InvertState,
10341 cx,
10342 );
10343 }
10344 }
10345
10346 pub fn disable_breakpoint(
10347 &mut self,
10348 _: &crate::actions::DisableBreakpoint,
10349 window: &mut Window,
10350 cx: &mut Context<Self>,
10351 ) {
10352 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10353 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10354 continue;
10355 };
10356 self.edit_breakpoint_at_anchor(
10357 anchor,
10358 breakpoint,
10359 BreakpointEditAction::InvertState,
10360 cx,
10361 );
10362 }
10363 }
10364
10365 pub fn toggle_breakpoint(
10366 &mut self,
10367 _: &crate::actions::ToggleBreakpoint,
10368 window: &mut Window,
10369 cx: &mut Context<Self>,
10370 ) {
10371 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10372 if let Some(breakpoint) = breakpoint {
10373 self.edit_breakpoint_at_anchor(
10374 anchor,
10375 breakpoint,
10376 BreakpointEditAction::Toggle,
10377 cx,
10378 );
10379 } else {
10380 self.edit_breakpoint_at_anchor(
10381 anchor,
10382 Breakpoint::new_standard(),
10383 BreakpointEditAction::Toggle,
10384 cx,
10385 );
10386 }
10387 }
10388 }
10389
10390 pub fn edit_breakpoint_at_anchor(
10391 &mut self,
10392 breakpoint_position: Anchor,
10393 breakpoint: Breakpoint,
10394 edit_action: BreakpointEditAction,
10395 cx: &mut Context<Self>,
10396 ) {
10397 let Some(breakpoint_store) = &self.breakpoint_store else {
10398 return;
10399 };
10400
10401 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10402 if breakpoint_position == Anchor::min() {
10403 self.buffer()
10404 .read(cx)
10405 .excerpt_buffer_ids()
10406 .into_iter()
10407 .next()
10408 } else {
10409 None
10410 }
10411 }) else {
10412 return;
10413 };
10414
10415 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10416 return;
10417 };
10418
10419 breakpoint_store.update(cx, |breakpoint_store, cx| {
10420 breakpoint_store.toggle_breakpoint(
10421 buffer,
10422 BreakpointWithPosition {
10423 position: breakpoint_position.text_anchor,
10424 bp: breakpoint,
10425 },
10426 edit_action,
10427 cx,
10428 );
10429 });
10430
10431 cx.notify();
10432 }
10433
10434 #[cfg(any(test, feature = "test-support"))]
10435 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10436 self.breakpoint_store.clone()
10437 }
10438
10439 pub fn prepare_restore_change(
10440 &self,
10441 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10442 hunk: &MultiBufferDiffHunk,
10443 cx: &mut App,
10444 ) -> Option<()> {
10445 if hunk.is_created_file() {
10446 return None;
10447 }
10448 let buffer = self.buffer.read(cx);
10449 let diff = buffer.diff_for(hunk.buffer_id)?;
10450 let buffer = buffer.buffer(hunk.buffer_id)?;
10451 let buffer = buffer.read(cx);
10452 let original_text = diff
10453 .read(cx)
10454 .base_text()
10455 .as_rope()
10456 .slice(hunk.diff_base_byte_range.clone());
10457 let buffer_snapshot = buffer.snapshot();
10458 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10459 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10460 probe
10461 .0
10462 .start
10463 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10464 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10465 }) {
10466 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10467 Some(())
10468 } else {
10469 None
10470 }
10471 }
10472
10473 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10474 self.manipulate_lines(window, cx, |lines| lines.reverse())
10475 }
10476
10477 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10478 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10479 }
10480
10481 fn manipulate_lines<Fn>(
10482 &mut self,
10483 window: &mut Window,
10484 cx: &mut Context<Self>,
10485 mut callback: Fn,
10486 ) where
10487 Fn: FnMut(&mut Vec<&str>),
10488 {
10489 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10490
10491 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10492 let buffer = self.buffer.read(cx).snapshot(cx);
10493
10494 let mut edits = Vec::new();
10495
10496 let selections = self.selections.all::<Point>(cx);
10497 let mut selections = selections.iter().peekable();
10498 let mut contiguous_row_selections = Vec::new();
10499 let mut new_selections = Vec::new();
10500 let mut added_lines = 0;
10501 let mut removed_lines = 0;
10502
10503 while let Some(selection) = selections.next() {
10504 let (start_row, end_row) = consume_contiguous_rows(
10505 &mut contiguous_row_selections,
10506 selection,
10507 &display_map,
10508 &mut selections,
10509 );
10510
10511 let start_point = Point::new(start_row.0, 0);
10512 let end_point = Point::new(
10513 end_row.previous_row().0,
10514 buffer.line_len(end_row.previous_row()),
10515 );
10516 let text = buffer
10517 .text_for_range(start_point..end_point)
10518 .collect::<String>();
10519
10520 let mut lines = text.split('\n').collect_vec();
10521
10522 let lines_before = lines.len();
10523 callback(&mut lines);
10524 let lines_after = lines.len();
10525
10526 edits.push((start_point..end_point, lines.join("\n")));
10527
10528 // Selections must change based on added and removed line count
10529 let start_row =
10530 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10531 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10532 new_selections.push(Selection {
10533 id: selection.id,
10534 start: start_row,
10535 end: end_row,
10536 goal: SelectionGoal::None,
10537 reversed: selection.reversed,
10538 });
10539
10540 if lines_after > lines_before {
10541 added_lines += lines_after - lines_before;
10542 } else if lines_before > lines_after {
10543 removed_lines += lines_before - lines_after;
10544 }
10545 }
10546
10547 self.transact(window, cx, |this, window, cx| {
10548 let buffer = this.buffer.update(cx, |buffer, cx| {
10549 buffer.edit(edits, None, cx);
10550 buffer.snapshot(cx)
10551 });
10552
10553 // Recalculate offsets on newly edited buffer
10554 let new_selections = new_selections
10555 .iter()
10556 .map(|s| {
10557 let start_point = Point::new(s.start.0, 0);
10558 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10559 Selection {
10560 id: s.id,
10561 start: buffer.point_to_offset(start_point),
10562 end: buffer.point_to_offset(end_point),
10563 goal: s.goal,
10564 reversed: s.reversed,
10565 }
10566 })
10567 .collect();
10568
10569 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10570 s.select(new_selections);
10571 });
10572
10573 this.request_autoscroll(Autoscroll::fit(), cx);
10574 });
10575 }
10576
10577 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10578 self.manipulate_text(window, cx, |text| {
10579 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10580 if has_upper_case_characters {
10581 text.to_lowercase()
10582 } else {
10583 text.to_uppercase()
10584 }
10585 })
10586 }
10587
10588 pub fn convert_to_upper_case(
10589 &mut self,
10590 _: &ConvertToUpperCase,
10591 window: &mut Window,
10592 cx: &mut Context<Self>,
10593 ) {
10594 self.manipulate_text(window, cx, |text| text.to_uppercase())
10595 }
10596
10597 pub fn convert_to_lower_case(
10598 &mut self,
10599 _: &ConvertToLowerCase,
10600 window: &mut Window,
10601 cx: &mut Context<Self>,
10602 ) {
10603 self.manipulate_text(window, cx, |text| text.to_lowercase())
10604 }
10605
10606 pub fn convert_to_title_case(
10607 &mut self,
10608 _: &ConvertToTitleCase,
10609 window: &mut Window,
10610 cx: &mut Context<Self>,
10611 ) {
10612 self.manipulate_text(window, cx, |text| {
10613 text.split('\n')
10614 .map(|line| line.to_case(Case::Title))
10615 .join("\n")
10616 })
10617 }
10618
10619 pub fn convert_to_snake_case(
10620 &mut self,
10621 _: &ConvertToSnakeCase,
10622 window: &mut Window,
10623 cx: &mut Context<Self>,
10624 ) {
10625 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10626 }
10627
10628 pub fn convert_to_kebab_case(
10629 &mut self,
10630 _: &ConvertToKebabCase,
10631 window: &mut Window,
10632 cx: &mut Context<Self>,
10633 ) {
10634 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10635 }
10636
10637 pub fn convert_to_upper_camel_case(
10638 &mut self,
10639 _: &ConvertToUpperCamelCase,
10640 window: &mut Window,
10641 cx: &mut Context<Self>,
10642 ) {
10643 self.manipulate_text(window, cx, |text| {
10644 text.split('\n')
10645 .map(|line| line.to_case(Case::UpperCamel))
10646 .join("\n")
10647 })
10648 }
10649
10650 pub fn convert_to_lower_camel_case(
10651 &mut self,
10652 _: &ConvertToLowerCamelCase,
10653 window: &mut Window,
10654 cx: &mut Context<Self>,
10655 ) {
10656 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10657 }
10658
10659 pub fn convert_to_opposite_case(
10660 &mut self,
10661 _: &ConvertToOppositeCase,
10662 window: &mut Window,
10663 cx: &mut Context<Self>,
10664 ) {
10665 self.manipulate_text(window, cx, |text| {
10666 text.chars()
10667 .fold(String::with_capacity(text.len()), |mut t, c| {
10668 if c.is_uppercase() {
10669 t.extend(c.to_lowercase());
10670 } else {
10671 t.extend(c.to_uppercase());
10672 }
10673 t
10674 })
10675 })
10676 }
10677
10678 pub fn convert_to_rot13(
10679 &mut self,
10680 _: &ConvertToRot13,
10681 window: &mut Window,
10682 cx: &mut Context<Self>,
10683 ) {
10684 self.manipulate_text(window, cx, |text| {
10685 text.chars()
10686 .map(|c| match c {
10687 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10688 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10689 _ => c,
10690 })
10691 .collect()
10692 })
10693 }
10694
10695 pub fn convert_to_rot47(
10696 &mut self,
10697 _: &ConvertToRot47,
10698 window: &mut Window,
10699 cx: &mut Context<Self>,
10700 ) {
10701 self.manipulate_text(window, cx, |text| {
10702 text.chars()
10703 .map(|c| {
10704 let code_point = c as u32;
10705 if code_point >= 33 && code_point <= 126 {
10706 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10707 }
10708 c
10709 })
10710 .collect()
10711 })
10712 }
10713
10714 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10715 where
10716 Fn: FnMut(&str) -> String,
10717 {
10718 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10719 let buffer = self.buffer.read(cx).snapshot(cx);
10720
10721 let mut new_selections = Vec::new();
10722 let mut edits = Vec::new();
10723 let mut selection_adjustment = 0i32;
10724
10725 for selection in self.selections.all::<usize>(cx) {
10726 let selection_is_empty = selection.is_empty();
10727
10728 let (start, end) = if selection_is_empty {
10729 let word_range = movement::surrounding_word(
10730 &display_map,
10731 selection.start.to_display_point(&display_map),
10732 );
10733 let start = word_range.start.to_offset(&display_map, Bias::Left);
10734 let end = word_range.end.to_offset(&display_map, Bias::Left);
10735 (start, end)
10736 } else {
10737 (selection.start, selection.end)
10738 };
10739
10740 let text = buffer.text_for_range(start..end).collect::<String>();
10741 let old_length = text.len() as i32;
10742 let text = callback(&text);
10743
10744 new_selections.push(Selection {
10745 start: (start as i32 - selection_adjustment) as usize,
10746 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10747 goal: SelectionGoal::None,
10748 ..selection
10749 });
10750
10751 selection_adjustment += old_length - text.len() as i32;
10752
10753 edits.push((start..end, text));
10754 }
10755
10756 self.transact(window, cx, |this, window, cx| {
10757 this.buffer.update(cx, |buffer, cx| {
10758 buffer.edit(edits, None, cx);
10759 });
10760
10761 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10762 s.select(new_selections);
10763 });
10764
10765 this.request_autoscroll(Autoscroll::fit(), cx);
10766 });
10767 }
10768
10769 pub fn move_selection_on_drop(
10770 &mut self,
10771 selection: &Selection<Anchor>,
10772 target: DisplayPoint,
10773 is_cut: bool,
10774 window: &mut Window,
10775 cx: &mut Context<Self>,
10776 ) {
10777 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10778 let buffer = &display_map.buffer_snapshot;
10779 let mut edits = Vec::new();
10780 let insert_point = display_map
10781 .clip_point(target, Bias::Left)
10782 .to_point(&display_map);
10783 let text = buffer
10784 .text_for_range(selection.start..selection.end)
10785 .collect::<String>();
10786 if is_cut {
10787 edits.push(((selection.start..selection.end), String::new()));
10788 }
10789 let insert_anchor = buffer.anchor_before(insert_point);
10790 edits.push(((insert_anchor..insert_anchor), text));
10791 let last_edit_start = insert_anchor.bias_left(buffer);
10792 let last_edit_end = insert_anchor.bias_right(buffer);
10793 self.transact(window, cx, |this, window, cx| {
10794 this.buffer.update(cx, |buffer, cx| {
10795 buffer.edit(edits, None, cx);
10796 });
10797 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10798 s.select_anchor_ranges([last_edit_start..last_edit_end]);
10799 });
10800 });
10801 }
10802
10803 pub fn clear_selection_drag_state(&mut self) {
10804 self.selection_drag_state = SelectionDragState::None;
10805 }
10806
10807 pub fn duplicate(
10808 &mut self,
10809 upwards: bool,
10810 whole_lines: bool,
10811 window: &mut Window,
10812 cx: &mut Context<Self>,
10813 ) {
10814 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10815
10816 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10817 let buffer = &display_map.buffer_snapshot;
10818 let selections = self.selections.all::<Point>(cx);
10819
10820 let mut edits = Vec::new();
10821 let mut selections_iter = selections.iter().peekable();
10822 while let Some(selection) = selections_iter.next() {
10823 let mut rows = selection.spanned_rows(false, &display_map);
10824 // duplicate line-wise
10825 if whole_lines || selection.start == selection.end {
10826 // Avoid duplicating the same lines twice.
10827 while let Some(next_selection) = selections_iter.peek() {
10828 let next_rows = next_selection.spanned_rows(false, &display_map);
10829 if next_rows.start < rows.end {
10830 rows.end = next_rows.end;
10831 selections_iter.next().unwrap();
10832 } else {
10833 break;
10834 }
10835 }
10836
10837 // Copy the text from the selected row region and splice it either at the start
10838 // or end of the region.
10839 let start = Point::new(rows.start.0, 0);
10840 let end = Point::new(
10841 rows.end.previous_row().0,
10842 buffer.line_len(rows.end.previous_row()),
10843 );
10844 let text = buffer
10845 .text_for_range(start..end)
10846 .chain(Some("\n"))
10847 .collect::<String>();
10848 let insert_location = if upwards {
10849 Point::new(rows.end.0, 0)
10850 } else {
10851 start
10852 };
10853 edits.push((insert_location..insert_location, text));
10854 } else {
10855 // duplicate character-wise
10856 let start = selection.start;
10857 let end = selection.end;
10858 let text = buffer.text_for_range(start..end).collect::<String>();
10859 edits.push((selection.end..selection.end, text));
10860 }
10861 }
10862
10863 self.transact(window, cx, |this, _, cx| {
10864 this.buffer.update(cx, |buffer, cx| {
10865 buffer.edit(edits, None, cx);
10866 });
10867
10868 this.request_autoscroll(Autoscroll::fit(), cx);
10869 });
10870 }
10871
10872 pub fn duplicate_line_up(
10873 &mut self,
10874 _: &DuplicateLineUp,
10875 window: &mut Window,
10876 cx: &mut Context<Self>,
10877 ) {
10878 self.duplicate(true, true, window, cx);
10879 }
10880
10881 pub fn duplicate_line_down(
10882 &mut self,
10883 _: &DuplicateLineDown,
10884 window: &mut Window,
10885 cx: &mut Context<Self>,
10886 ) {
10887 self.duplicate(false, true, window, cx);
10888 }
10889
10890 pub fn duplicate_selection(
10891 &mut self,
10892 _: &DuplicateSelection,
10893 window: &mut Window,
10894 cx: &mut Context<Self>,
10895 ) {
10896 self.duplicate(false, false, window, cx);
10897 }
10898
10899 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10900 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10901
10902 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10903 let buffer = self.buffer.read(cx).snapshot(cx);
10904
10905 let mut edits = Vec::new();
10906 let mut unfold_ranges = Vec::new();
10907 let mut refold_creases = Vec::new();
10908
10909 let selections = self.selections.all::<Point>(cx);
10910 let mut selections = selections.iter().peekable();
10911 let mut contiguous_row_selections = Vec::new();
10912 let mut new_selections = Vec::new();
10913
10914 while let Some(selection) = selections.next() {
10915 // Find all the selections that span a contiguous row range
10916 let (start_row, end_row) = consume_contiguous_rows(
10917 &mut contiguous_row_selections,
10918 selection,
10919 &display_map,
10920 &mut selections,
10921 );
10922
10923 // Move the text spanned by the row range to be before the line preceding the row range
10924 if start_row.0 > 0 {
10925 let range_to_move = Point::new(
10926 start_row.previous_row().0,
10927 buffer.line_len(start_row.previous_row()),
10928 )
10929 ..Point::new(
10930 end_row.previous_row().0,
10931 buffer.line_len(end_row.previous_row()),
10932 );
10933 let insertion_point = display_map
10934 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10935 .0;
10936
10937 // Don't move lines across excerpts
10938 if buffer
10939 .excerpt_containing(insertion_point..range_to_move.end)
10940 .is_some()
10941 {
10942 let text = buffer
10943 .text_for_range(range_to_move.clone())
10944 .flat_map(|s| s.chars())
10945 .skip(1)
10946 .chain(['\n'])
10947 .collect::<String>();
10948
10949 edits.push((
10950 buffer.anchor_after(range_to_move.start)
10951 ..buffer.anchor_before(range_to_move.end),
10952 String::new(),
10953 ));
10954 let insertion_anchor = buffer.anchor_after(insertion_point);
10955 edits.push((insertion_anchor..insertion_anchor, text));
10956
10957 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10958
10959 // Move selections up
10960 new_selections.extend(contiguous_row_selections.drain(..).map(
10961 |mut selection| {
10962 selection.start.row -= row_delta;
10963 selection.end.row -= row_delta;
10964 selection
10965 },
10966 ));
10967
10968 // Move folds up
10969 unfold_ranges.push(range_to_move.clone());
10970 for fold in display_map.folds_in_range(
10971 buffer.anchor_before(range_to_move.start)
10972 ..buffer.anchor_after(range_to_move.end),
10973 ) {
10974 let mut start = fold.range.start.to_point(&buffer);
10975 let mut end = fold.range.end.to_point(&buffer);
10976 start.row -= row_delta;
10977 end.row -= row_delta;
10978 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10979 }
10980 }
10981 }
10982
10983 // If we didn't move line(s), preserve the existing selections
10984 new_selections.append(&mut contiguous_row_selections);
10985 }
10986
10987 self.transact(window, cx, |this, window, cx| {
10988 this.unfold_ranges(&unfold_ranges, true, true, cx);
10989 this.buffer.update(cx, |buffer, cx| {
10990 for (range, text) in edits {
10991 buffer.edit([(range, text)], None, cx);
10992 }
10993 });
10994 this.fold_creases(refold_creases, true, window, cx);
10995 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10996 s.select(new_selections);
10997 })
10998 });
10999 }
11000
11001 pub fn move_line_down(
11002 &mut self,
11003 _: &MoveLineDown,
11004 window: &mut Window,
11005 cx: &mut Context<Self>,
11006 ) {
11007 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11008
11009 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11010 let buffer = self.buffer.read(cx).snapshot(cx);
11011
11012 let mut edits = Vec::new();
11013 let mut unfold_ranges = Vec::new();
11014 let mut refold_creases = Vec::new();
11015
11016 let selections = self.selections.all::<Point>(cx);
11017 let mut selections = selections.iter().peekable();
11018 let mut contiguous_row_selections = Vec::new();
11019 let mut new_selections = Vec::new();
11020
11021 while let Some(selection) = selections.next() {
11022 // Find all the selections that span a contiguous row range
11023 let (start_row, end_row) = consume_contiguous_rows(
11024 &mut contiguous_row_selections,
11025 selection,
11026 &display_map,
11027 &mut selections,
11028 );
11029
11030 // Move the text spanned by the row range to be after the last line of the row range
11031 if end_row.0 <= buffer.max_point().row {
11032 let range_to_move =
11033 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11034 let insertion_point = display_map
11035 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11036 .0;
11037
11038 // Don't move lines across excerpt boundaries
11039 if buffer
11040 .excerpt_containing(range_to_move.start..insertion_point)
11041 .is_some()
11042 {
11043 let mut text = String::from("\n");
11044 text.extend(buffer.text_for_range(range_to_move.clone()));
11045 text.pop(); // Drop trailing newline
11046 edits.push((
11047 buffer.anchor_after(range_to_move.start)
11048 ..buffer.anchor_before(range_to_move.end),
11049 String::new(),
11050 ));
11051 let insertion_anchor = buffer.anchor_after(insertion_point);
11052 edits.push((insertion_anchor..insertion_anchor, text));
11053
11054 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11055
11056 // Move selections down
11057 new_selections.extend(contiguous_row_selections.drain(..).map(
11058 |mut selection| {
11059 selection.start.row += row_delta;
11060 selection.end.row += row_delta;
11061 selection
11062 },
11063 ));
11064
11065 // Move folds down
11066 unfold_ranges.push(range_to_move.clone());
11067 for fold in display_map.folds_in_range(
11068 buffer.anchor_before(range_to_move.start)
11069 ..buffer.anchor_after(range_to_move.end),
11070 ) {
11071 let mut start = fold.range.start.to_point(&buffer);
11072 let mut end = fold.range.end.to_point(&buffer);
11073 start.row += row_delta;
11074 end.row += row_delta;
11075 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11076 }
11077 }
11078 }
11079
11080 // If we didn't move line(s), preserve the existing selections
11081 new_selections.append(&mut contiguous_row_selections);
11082 }
11083
11084 self.transact(window, cx, |this, window, cx| {
11085 this.unfold_ranges(&unfold_ranges, true, true, cx);
11086 this.buffer.update(cx, |buffer, cx| {
11087 for (range, text) in edits {
11088 buffer.edit([(range, text)], None, cx);
11089 }
11090 });
11091 this.fold_creases(refold_creases, true, window, cx);
11092 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11093 s.select(new_selections)
11094 });
11095 });
11096 }
11097
11098 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11099 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11100 let text_layout_details = &self.text_layout_details(window);
11101 self.transact(window, cx, |this, window, cx| {
11102 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11103 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11104 s.move_with(|display_map, selection| {
11105 if !selection.is_empty() {
11106 return;
11107 }
11108
11109 let mut head = selection.head();
11110 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11111 if head.column() == display_map.line_len(head.row()) {
11112 transpose_offset = display_map
11113 .buffer_snapshot
11114 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11115 }
11116
11117 if transpose_offset == 0 {
11118 return;
11119 }
11120
11121 *head.column_mut() += 1;
11122 head = display_map.clip_point(head, Bias::Right);
11123 let goal = SelectionGoal::HorizontalPosition(
11124 display_map
11125 .x_for_display_point(head, text_layout_details)
11126 .into(),
11127 );
11128 selection.collapse_to(head, goal);
11129
11130 let transpose_start = display_map
11131 .buffer_snapshot
11132 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11133 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11134 let transpose_end = display_map
11135 .buffer_snapshot
11136 .clip_offset(transpose_offset + 1, Bias::Right);
11137 if let Some(ch) =
11138 display_map.buffer_snapshot.chars_at(transpose_start).next()
11139 {
11140 edits.push((transpose_start..transpose_offset, String::new()));
11141 edits.push((transpose_end..transpose_end, ch.to_string()));
11142 }
11143 }
11144 });
11145 edits
11146 });
11147 this.buffer
11148 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11149 let selections = this.selections.all::<usize>(cx);
11150 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11151 s.select(selections);
11152 });
11153 });
11154 }
11155
11156 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11157 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11158 self.rewrap_impl(RewrapOptions::default(), cx)
11159 }
11160
11161 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11162 let buffer = self.buffer.read(cx).snapshot(cx);
11163 let selections = self.selections.all::<Point>(cx);
11164
11165 // Shrink and split selections to respect paragraph boundaries.
11166 let ranges = selections.into_iter().flat_map(|selection| {
11167 let language_settings = buffer.language_settings_at(selection.head(), cx);
11168 let language_scope = buffer.language_scope_at(selection.head());
11169
11170 let Some(start_row) = (selection.start.row..=selection.end.row)
11171 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11172 else {
11173 return vec![];
11174 };
11175 let Some(end_row) = (selection.start.row..=selection.end.row)
11176 .rev()
11177 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11178 else {
11179 return vec![];
11180 };
11181
11182 let mut row = start_row;
11183 let mut ranges = Vec::new();
11184 while let Some(blank_row) =
11185 (row..end_row).find(|row| buffer.is_line_blank(MultiBufferRow(*row)))
11186 {
11187 let next_paragraph_start = (blank_row + 1..=end_row)
11188 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11189 .unwrap();
11190 ranges.push((
11191 language_settings.clone(),
11192 language_scope.clone(),
11193 Point::new(row, 0)..Point::new(blank_row - 1, 0),
11194 ));
11195 row = next_paragraph_start;
11196 }
11197 ranges.push((
11198 language_settings.clone(),
11199 language_scope.clone(),
11200 Point::new(row, 0)..Point::new(end_row, 0),
11201 ));
11202
11203 ranges
11204 });
11205
11206 let mut edits = Vec::new();
11207 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11208
11209 for (language_settings, language_scope, range) in ranges {
11210 let mut start_row = range.start.row;
11211 let mut end_row = range.end.row;
11212
11213 // Skip selections that overlap with a range that has already been rewrapped.
11214 let selection_range = start_row..end_row;
11215 if rewrapped_row_ranges
11216 .iter()
11217 .any(|range| range.overlaps(&selection_range))
11218 {
11219 continue;
11220 }
11221
11222 let tab_size = language_settings.tab_size;
11223
11224 // Since not all lines in the selection may be at the same indent
11225 // level, choose the indent size that is the most common between all
11226 // of the lines.
11227 //
11228 // If there is a tie, we use the deepest indent.
11229 let (indent_size, indent_end) = {
11230 let mut indent_size_occurrences = HashMap::default();
11231 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
11232
11233 for row in start_row..=end_row {
11234 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11235 rows_by_indent_size.entry(indent).or_default().push(row);
11236 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
11237 }
11238
11239 let indent_size = indent_size_occurrences
11240 .into_iter()
11241 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
11242 .map(|(indent, _)| indent)
11243 .unwrap_or_default();
11244 let row = rows_by_indent_size[&indent_size][0];
11245 let indent_end = Point::new(row, indent_size.len);
11246
11247 (indent_size, indent_end)
11248 };
11249
11250 let mut line_prefix = indent_size.chars().collect::<String>();
11251
11252 let mut inside_comment = false;
11253 if let Some(comment_prefix) = language_scope.and_then(|language| {
11254 language
11255 .line_comment_prefixes()
11256 .iter()
11257 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11258 .cloned()
11259 }) {
11260 line_prefix.push_str(&comment_prefix);
11261 inside_comment = true;
11262 }
11263
11264 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11265 RewrapBehavior::InComments => inside_comment,
11266 RewrapBehavior::InSelections => !range.is_empty(),
11267 RewrapBehavior::Anywhere => true,
11268 };
11269
11270 let should_rewrap = options.override_language_settings
11271 || allow_rewrap_based_on_language
11272 || self.hard_wrap.is_some();
11273 if !should_rewrap {
11274 continue;
11275 }
11276
11277 if range.is_empty() {
11278 'expand_upwards: while start_row > 0 {
11279 let prev_row = start_row - 1;
11280 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11281 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11282 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11283 {
11284 start_row = prev_row;
11285 } else {
11286 break 'expand_upwards;
11287 }
11288 }
11289
11290 'expand_downwards: while end_row < buffer.max_point().row {
11291 let next_row = end_row + 1;
11292 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11293 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11294 && !buffer.is_line_blank(MultiBufferRow(next_row))
11295 {
11296 end_row = next_row;
11297 } else {
11298 break 'expand_downwards;
11299 }
11300 }
11301 }
11302
11303 let start = Point::new(start_row, 0);
11304 let start_offset = start.to_offset(&buffer);
11305 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11306 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11307 let Some(lines_without_prefixes) = selection_text
11308 .lines()
11309 .map(|line| {
11310 line.strip_prefix(&line_prefix)
11311 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
11312 .with_context(|| {
11313 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11314 })
11315 })
11316 .collect::<Result<Vec<_>, _>>()
11317 .log_err()
11318 else {
11319 continue;
11320 };
11321
11322 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11323 buffer
11324 .language_settings_at(Point::new(start_row, 0), cx)
11325 .preferred_line_length as usize
11326 });
11327 let wrapped_text = wrap_with_prefix(
11328 line_prefix,
11329 lines_without_prefixes.join("\n"),
11330 wrap_column,
11331 tab_size,
11332 options.preserve_existing_whitespace,
11333 );
11334
11335 // TODO: should always use char-based diff while still supporting cursor behavior that
11336 // matches vim.
11337 let mut diff_options = DiffOptions::default();
11338 if options.override_language_settings {
11339 diff_options.max_word_diff_len = 0;
11340 diff_options.max_word_diff_line_count = 0;
11341 } else {
11342 diff_options.max_word_diff_len = usize::MAX;
11343 diff_options.max_word_diff_line_count = usize::MAX;
11344 }
11345
11346 for (old_range, new_text) in
11347 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11348 {
11349 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11350 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11351 edits.push((edit_start..edit_end, new_text));
11352 }
11353
11354 rewrapped_row_ranges.push(start_row..=end_row);
11355 }
11356
11357 self.buffer
11358 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11359 }
11360
11361 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11362 let mut text = String::new();
11363 let buffer = self.buffer.read(cx).snapshot(cx);
11364 let mut selections = self.selections.all::<Point>(cx);
11365 let mut clipboard_selections = Vec::with_capacity(selections.len());
11366 {
11367 let max_point = buffer.max_point();
11368 let mut is_first = true;
11369 for selection in &mut selections {
11370 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11371 if is_entire_line {
11372 selection.start = Point::new(selection.start.row, 0);
11373 if !selection.is_empty() && selection.end.column == 0 {
11374 selection.end = cmp::min(max_point, selection.end);
11375 } else {
11376 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11377 }
11378 selection.goal = SelectionGoal::None;
11379 }
11380 if is_first {
11381 is_first = false;
11382 } else {
11383 text += "\n";
11384 }
11385 let mut len = 0;
11386 for chunk in buffer.text_for_range(selection.start..selection.end) {
11387 text.push_str(chunk);
11388 len += chunk.len();
11389 }
11390 clipboard_selections.push(ClipboardSelection {
11391 len,
11392 is_entire_line,
11393 first_line_indent: buffer
11394 .indent_size_for_line(MultiBufferRow(selection.start.row))
11395 .len,
11396 });
11397 }
11398 }
11399
11400 self.transact(window, cx, |this, window, cx| {
11401 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11402 s.select(selections);
11403 });
11404 this.insert("", window, cx);
11405 });
11406 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11407 }
11408
11409 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11410 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11411 let item = self.cut_common(window, cx);
11412 cx.write_to_clipboard(item);
11413 }
11414
11415 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11416 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11417 self.change_selections(None, window, cx, |s| {
11418 s.move_with(|snapshot, sel| {
11419 if sel.is_empty() {
11420 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11421 }
11422 });
11423 });
11424 let item = self.cut_common(window, cx);
11425 cx.set_global(KillRing(item))
11426 }
11427
11428 pub fn kill_ring_yank(
11429 &mut self,
11430 _: &KillRingYank,
11431 window: &mut Window,
11432 cx: &mut Context<Self>,
11433 ) {
11434 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11435 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11436 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11437 (kill_ring.text().to_string(), kill_ring.metadata_json())
11438 } else {
11439 return;
11440 }
11441 } else {
11442 return;
11443 };
11444 self.do_paste(&text, metadata, false, window, cx);
11445 }
11446
11447 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11448 self.do_copy(true, cx);
11449 }
11450
11451 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11452 self.do_copy(false, cx);
11453 }
11454
11455 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11456 let selections = self.selections.all::<Point>(cx);
11457 let buffer = self.buffer.read(cx).read(cx);
11458 let mut text = String::new();
11459
11460 let mut clipboard_selections = Vec::with_capacity(selections.len());
11461 {
11462 let max_point = buffer.max_point();
11463 let mut is_first = true;
11464 for selection in &selections {
11465 let mut start = selection.start;
11466 let mut end = selection.end;
11467 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11468 if is_entire_line {
11469 start = Point::new(start.row, 0);
11470 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11471 }
11472
11473 let mut trimmed_selections = Vec::new();
11474 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11475 let row = MultiBufferRow(start.row);
11476 let first_indent = buffer.indent_size_for_line(row);
11477 if first_indent.len == 0 || start.column > first_indent.len {
11478 trimmed_selections.push(start..end);
11479 } else {
11480 trimmed_selections.push(
11481 Point::new(row.0, first_indent.len)
11482 ..Point::new(row.0, buffer.line_len(row)),
11483 );
11484 for row in start.row + 1..=end.row {
11485 let mut line_len = buffer.line_len(MultiBufferRow(row));
11486 if row == end.row {
11487 line_len = end.column;
11488 }
11489 if line_len == 0 {
11490 trimmed_selections
11491 .push(Point::new(row, 0)..Point::new(row, line_len));
11492 continue;
11493 }
11494 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11495 if row_indent_size.len >= first_indent.len {
11496 trimmed_selections.push(
11497 Point::new(row, first_indent.len)..Point::new(row, line_len),
11498 );
11499 } else {
11500 trimmed_selections.clear();
11501 trimmed_selections.push(start..end);
11502 break;
11503 }
11504 }
11505 }
11506 } else {
11507 trimmed_selections.push(start..end);
11508 }
11509
11510 for trimmed_range in trimmed_selections {
11511 if is_first {
11512 is_first = false;
11513 } else {
11514 text += "\n";
11515 }
11516 let mut len = 0;
11517 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11518 text.push_str(chunk);
11519 len += chunk.len();
11520 }
11521 clipboard_selections.push(ClipboardSelection {
11522 len,
11523 is_entire_line,
11524 first_line_indent: buffer
11525 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11526 .len,
11527 });
11528 }
11529 }
11530 }
11531
11532 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11533 text,
11534 clipboard_selections,
11535 ));
11536 }
11537
11538 pub fn do_paste(
11539 &mut self,
11540 text: &String,
11541 clipboard_selections: Option<Vec<ClipboardSelection>>,
11542 handle_entire_lines: bool,
11543 window: &mut Window,
11544 cx: &mut Context<Self>,
11545 ) {
11546 if self.read_only(cx) {
11547 return;
11548 }
11549
11550 let clipboard_text = Cow::Borrowed(text);
11551
11552 self.transact(window, cx, |this, window, cx| {
11553 if let Some(mut clipboard_selections) = clipboard_selections {
11554 let old_selections = this.selections.all::<usize>(cx);
11555 let all_selections_were_entire_line =
11556 clipboard_selections.iter().all(|s| s.is_entire_line);
11557 let first_selection_indent_column =
11558 clipboard_selections.first().map(|s| s.first_line_indent);
11559 if clipboard_selections.len() != old_selections.len() {
11560 clipboard_selections.drain(..);
11561 }
11562 let cursor_offset = this.selections.last::<usize>(cx).head();
11563 let mut auto_indent_on_paste = true;
11564
11565 this.buffer.update(cx, |buffer, cx| {
11566 let snapshot = buffer.read(cx);
11567 auto_indent_on_paste = snapshot
11568 .language_settings_at(cursor_offset, cx)
11569 .auto_indent_on_paste;
11570
11571 let mut start_offset = 0;
11572 let mut edits = Vec::new();
11573 let mut original_indent_columns = Vec::new();
11574 for (ix, selection) in old_selections.iter().enumerate() {
11575 let to_insert;
11576 let entire_line;
11577 let original_indent_column;
11578 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11579 let end_offset = start_offset + clipboard_selection.len;
11580 to_insert = &clipboard_text[start_offset..end_offset];
11581 entire_line = clipboard_selection.is_entire_line;
11582 start_offset = end_offset + 1;
11583 original_indent_column = Some(clipboard_selection.first_line_indent);
11584 } else {
11585 to_insert = clipboard_text.as_str();
11586 entire_line = all_selections_were_entire_line;
11587 original_indent_column = first_selection_indent_column
11588 }
11589
11590 // If the corresponding selection was empty when this slice of the
11591 // clipboard text was written, then the entire line containing the
11592 // selection was copied. If this selection is also currently empty,
11593 // then paste the line before the current line of the buffer.
11594 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11595 let column = selection.start.to_point(&snapshot).column as usize;
11596 let line_start = selection.start - column;
11597 line_start..line_start
11598 } else {
11599 selection.range()
11600 };
11601
11602 edits.push((range, to_insert));
11603 original_indent_columns.push(original_indent_column);
11604 }
11605 drop(snapshot);
11606
11607 buffer.edit(
11608 edits,
11609 if auto_indent_on_paste {
11610 Some(AutoindentMode::Block {
11611 original_indent_columns,
11612 })
11613 } else {
11614 None
11615 },
11616 cx,
11617 );
11618 });
11619
11620 let selections = this.selections.all::<usize>(cx);
11621 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11622 s.select(selections)
11623 });
11624 } else {
11625 this.insert(&clipboard_text, window, cx);
11626 }
11627 });
11628 }
11629
11630 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11631 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11632 if let Some(item) = cx.read_from_clipboard() {
11633 let entries = item.entries();
11634
11635 match entries.first() {
11636 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11637 // of all the pasted entries.
11638 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11639 .do_paste(
11640 clipboard_string.text(),
11641 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11642 true,
11643 window,
11644 cx,
11645 ),
11646 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11647 }
11648 }
11649 }
11650
11651 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11652 if self.read_only(cx) {
11653 return;
11654 }
11655
11656 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11657
11658 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11659 if let Some((selections, _)) =
11660 self.selection_history.transaction(transaction_id).cloned()
11661 {
11662 self.change_selections(None, window, cx, |s| {
11663 s.select_anchors(selections.to_vec());
11664 });
11665 } else {
11666 log::error!(
11667 "No entry in selection_history found for undo. \
11668 This may correspond to a bug where undo does not update the selection. \
11669 If this is occurring, please add details to \
11670 https://github.com/zed-industries/zed/issues/22692"
11671 );
11672 }
11673 self.request_autoscroll(Autoscroll::fit(), cx);
11674 self.unmark_text(window, cx);
11675 self.refresh_inline_completion(true, false, window, cx);
11676 cx.emit(EditorEvent::Edited { transaction_id });
11677 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11678 }
11679 }
11680
11681 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11682 if self.read_only(cx) {
11683 return;
11684 }
11685
11686 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11687
11688 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11689 if let Some((_, Some(selections))) =
11690 self.selection_history.transaction(transaction_id).cloned()
11691 {
11692 self.change_selections(None, window, cx, |s| {
11693 s.select_anchors(selections.to_vec());
11694 });
11695 } else {
11696 log::error!(
11697 "No entry in selection_history found for redo. \
11698 This may correspond to a bug where undo does not update the selection. \
11699 If this is occurring, please add details to \
11700 https://github.com/zed-industries/zed/issues/22692"
11701 );
11702 }
11703 self.request_autoscroll(Autoscroll::fit(), cx);
11704 self.unmark_text(window, cx);
11705 self.refresh_inline_completion(true, false, window, cx);
11706 cx.emit(EditorEvent::Edited { transaction_id });
11707 }
11708 }
11709
11710 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11711 self.buffer
11712 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11713 }
11714
11715 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11716 self.buffer
11717 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11718 }
11719
11720 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11721 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11722 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11723 s.move_with(|map, selection| {
11724 let cursor = if selection.is_empty() {
11725 movement::left(map, selection.start)
11726 } else {
11727 selection.start
11728 };
11729 selection.collapse_to(cursor, SelectionGoal::None);
11730 });
11731 })
11732 }
11733
11734 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11735 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11736 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11737 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11738 })
11739 }
11740
11741 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11742 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11743 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11744 s.move_with(|map, selection| {
11745 let cursor = if selection.is_empty() {
11746 movement::right(map, selection.end)
11747 } else {
11748 selection.end
11749 };
11750 selection.collapse_to(cursor, SelectionGoal::None)
11751 });
11752 })
11753 }
11754
11755 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11756 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11757 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11758 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11759 })
11760 }
11761
11762 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11763 if self.take_rename(true, window, cx).is_some() {
11764 return;
11765 }
11766
11767 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11768 cx.propagate();
11769 return;
11770 }
11771
11772 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11773
11774 let text_layout_details = &self.text_layout_details(window);
11775 let selection_count = self.selections.count();
11776 let first_selection = self.selections.first_anchor();
11777
11778 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11779 s.move_with(|map, selection| {
11780 if !selection.is_empty() {
11781 selection.goal = SelectionGoal::None;
11782 }
11783 let (cursor, goal) = movement::up(
11784 map,
11785 selection.start,
11786 selection.goal,
11787 false,
11788 text_layout_details,
11789 );
11790 selection.collapse_to(cursor, goal);
11791 });
11792 });
11793
11794 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11795 {
11796 cx.propagate();
11797 }
11798 }
11799
11800 pub fn move_up_by_lines(
11801 &mut self,
11802 action: &MoveUpByLines,
11803 window: &mut Window,
11804 cx: &mut Context<Self>,
11805 ) {
11806 if self.take_rename(true, window, cx).is_some() {
11807 return;
11808 }
11809
11810 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11811 cx.propagate();
11812 return;
11813 }
11814
11815 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11816
11817 let text_layout_details = &self.text_layout_details(window);
11818
11819 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11820 s.move_with(|map, selection| {
11821 if !selection.is_empty() {
11822 selection.goal = SelectionGoal::None;
11823 }
11824 let (cursor, goal) = movement::up_by_rows(
11825 map,
11826 selection.start,
11827 action.lines,
11828 selection.goal,
11829 false,
11830 text_layout_details,
11831 );
11832 selection.collapse_to(cursor, goal);
11833 });
11834 })
11835 }
11836
11837 pub fn move_down_by_lines(
11838 &mut self,
11839 action: &MoveDownByLines,
11840 window: &mut Window,
11841 cx: &mut Context<Self>,
11842 ) {
11843 if self.take_rename(true, window, cx).is_some() {
11844 return;
11845 }
11846
11847 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11848 cx.propagate();
11849 return;
11850 }
11851
11852 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11853
11854 let text_layout_details = &self.text_layout_details(window);
11855
11856 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11857 s.move_with(|map, selection| {
11858 if !selection.is_empty() {
11859 selection.goal = SelectionGoal::None;
11860 }
11861 let (cursor, goal) = movement::down_by_rows(
11862 map,
11863 selection.start,
11864 action.lines,
11865 selection.goal,
11866 false,
11867 text_layout_details,
11868 );
11869 selection.collapse_to(cursor, goal);
11870 });
11871 })
11872 }
11873
11874 pub fn select_down_by_lines(
11875 &mut self,
11876 action: &SelectDownByLines,
11877 window: &mut Window,
11878 cx: &mut Context<Self>,
11879 ) {
11880 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11881 let text_layout_details = &self.text_layout_details(window);
11882 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11883 s.move_heads_with(|map, head, goal| {
11884 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11885 })
11886 })
11887 }
11888
11889 pub fn select_up_by_lines(
11890 &mut self,
11891 action: &SelectUpByLines,
11892 window: &mut Window,
11893 cx: &mut Context<Self>,
11894 ) {
11895 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11896 let text_layout_details = &self.text_layout_details(window);
11897 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11898 s.move_heads_with(|map, head, goal| {
11899 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11900 })
11901 })
11902 }
11903
11904 pub fn select_page_up(
11905 &mut self,
11906 _: &SelectPageUp,
11907 window: &mut Window,
11908 cx: &mut Context<Self>,
11909 ) {
11910 let Some(row_count) = self.visible_row_count() else {
11911 return;
11912 };
11913
11914 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11915
11916 let text_layout_details = &self.text_layout_details(window);
11917
11918 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11919 s.move_heads_with(|map, head, goal| {
11920 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11921 })
11922 })
11923 }
11924
11925 pub fn move_page_up(
11926 &mut self,
11927 action: &MovePageUp,
11928 window: &mut Window,
11929 cx: &mut Context<Self>,
11930 ) {
11931 if self.take_rename(true, window, cx).is_some() {
11932 return;
11933 }
11934
11935 if self
11936 .context_menu
11937 .borrow_mut()
11938 .as_mut()
11939 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11940 .unwrap_or(false)
11941 {
11942 return;
11943 }
11944
11945 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11946 cx.propagate();
11947 return;
11948 }
11949
11950 let Some(row_count) = self.visible_row_count() else {
11951 return;
11952 };
11953
11954 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11955
11956 let autoscroll = if action.center_cursor {
11957 Autoscroll::center()
11958 } else {
11959 Autoscroll::fit()
11960 };
11961
11962 let text_layout_details = &self.text_layout_details(window);
11963
11964 self.change_selections(Some(autoscroll), window, cx, |s| {
11965 s.move_with(|map, selection| {
11966 if !selection.is_empty() {
11967 selection.goal = SelectionGoal::None;
11968 }
11969 let (cursor, goal) = movement::up_by_rows(
11970 map,
11971 selection.end,
11972 row_count,
11973 selection.goal,
11974 false,
11975 text_layout_details,
11976 );
11977 selection.collapse_to(cursor, goal);
11978 });
11979 });
11980 }
11981
11982 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11983 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11984 let text_layout_details = &self.text_layout_details(window);
11985 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11986 s.move_heads_with(|map, head, goal| {
11987 movement::up(map, head, goal, false, text_layout_details)
11988 })
11989 })
11990 }
11991
11992 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11993 self.take_rename(true, window, cx);
11994
11995 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11996 cx.propagate();
11997 return;
11998 }
11999
12000 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12001
12002 let text_layout_details = &self.text_layout_details(window);
12003 let selection_count = self.selections.count();
12004 let first_selection = self.selections.first_anchor();
12005
12006 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12007 s.move_with(|map, selection| {
12008 if !selection.is_empty() {
12009 selection.goal = SelectionGoal::None;
12010 }
12011 let (cursor, goal) = movement::down(
12012 map,
12013 selection.end,
12014 selection.goal,
12015 false,
12016 text_layout_details,
12017 );
12018 selection.collapse_to(cursor, goal);
12019 });
12020 });
12021
12022 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12023 {
12024 cx.propagate();
12025 }
12026 }
12027
12028 pub fn select_page_down(
12029 &mut self,
12030 _: &SelectPageDown,
12031 window: &mut Window,
12032 cx: &mut Context<Self>,
12033 ) {
12034 let Some(row_count) = self.visible_row_count() else {
12035 return;
12036 };
12037
12038 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12039
12040 let text_layout_details = &self.text_layout_details(window);
12041
12042 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12043 s.move_heads_with(|map, head, goal| {
12044 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12045 })
12046 })
12047 }
12048
12049 pub fn move_page_down(
12050 &mut self,
12051 action: &MovePageDown,
12052 window: &mut Window,
12053 cx: &mut Context<Self>,
12054 ) {
12055 if self.take_rename(true, window, cx).is_some() {
12056 return;
12057 }
12058
12059 if self
12060 .context_menu
12061 .borrow_mut()
12062 .as_mut()
12063 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12064 .unwrap_or(false)
12065 {
12066 return;
12067 }
12068
12069 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12070 cx.propagate();
12071 return;
12072 }
12073
12074 let Some(row_count) = self.visible_row_count() else {
12075 return;
12076 };
12077
12078 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12079
12080 let autoscroll = if action.center_cursor {
12081 Autoscroll::center()
12082 } else {
12083 Autoscroll::fit()
12084 };
12085
12086 let text_layout_details = &self.text_layout_details(window);
12087 self.change_selections(Some(autoscroll), window, cx, |s| {
12088 s.move_with(|map, selection| {
12089 if !selection.is_empty() {
12090 selection.goal = SelectionGoal::None;
12091 }
12092 let (cursor, goal) = movement::down_by_rows(
12093 map,
12094 selection.end,
12095 row_count,
12096 selection.goal,
12097 false,
12098 text_layout_details,
12099 );
12100 selection.collapse_to(cursor, goal);
12101 });
12102 });
12103 }
12104
12105 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12106 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12107 let text_layout_details = &self.text_layout_details(window);
12108 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12109 s.move_heads_with(|map, head, goal| {
12110 movement::down(map, head, goal, false, text_layout_details)
12111 })
12112 });
12113 }
12114
12115 pub fn context_menu_first(
12116 &mut self,
12117 _: &ContextMenuFirst,
12118 window: &mut Window,
12119 cx: &mut Context<Self>,
12120 ) {
12121 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12122 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12123 }
12124 }
12125
12126 pub fn context_menu_prev(
12127 &mut self,
12128 _: &ContextMenuPrevious,
12129 window: &mut Window,
12130 cx: &mut Context<Self>,
12131 ) {
12132 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12133 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12134 }
12135 }
12136
12137 pub fn context_menu_next(
12138 &mut self,
12139 _: &ContextMenuNext,
12140 window: &mut Window,
12141 cx: &mut Context<Self>,
12142 ) {
12143 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12144 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12145 }
12146 }
12147
12148 pub fn context_menu_last(
12149 &mut self,
12150 _: &ContextMenuLast,
12151 window: &mut Window,
12152 cx: &mut Context<Self>,
12153 ) {
12154 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12155 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12156 }
12157 }
12158
12159 pub fn move_to_previous_word_start(
12160 &mut self,
12161 _: &MoveToPreviousWordStart,
12162 window: &mut Window,
12163 cx: &mut Context<Self>,
12164 ) {
12165 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12166 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12167 s.move_cursors_with(|map, head, _| {
12168 (
12169 movement::previous_word_start(map, head),
12170 SelectionGoal::None,
12171 )
12172 });
12173 })
12174 }
12175
12176 pub fn move_to_previous_subword_start(
12177 &mut self,
12178 _: &MoveToPreviousSubwordStart,
12179 window: &mut Window,
12180 cx: &mut Context<Self>,
12181 ) {
12182 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12183 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12184 s.move_cursors_with(|map, head, _| {
12185 (
12186 movement::previous_subword_start(map, head),
12187 SelectionGoal::None,
12188 )
12189 });
12190 })
12191 }
12192
12193 pub fn select_to_previous_word_start(
12194 &mut self,
12195 _: &SelectToPreviousWordStart,
12196 window: &mut Window,
12197 cx: &mut Context<Self>,
12198 ) {
12199 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12200 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12201 s.move_heads_with(|map, head, _| {
12202 (
12203 movement::previous_word_start(map, head),
12204 SelectionGoal::None,
12205 )
12206 });
12207 })
12208 }
12209
12210 pub fn select_to_previous_subword_start(
12211 &mut self,
12212 _: &SelectToPreviousSubwordStart,
12213 window: &mut Window,
12214 cx: &mut Context<Self>,
12215 ) {
12216 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12217 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12218 s.move_heads_with(|map, head, _| {
12219 (
12220 movement::previous_subword_start(map, head),
12221 SelectionGoal::None,
12222 )
12223 });
12224 })
12225 }
12226
12227 pub fn delete_to_previous_word_start(
12228 &mut self,
12229 action: &DeleteToPreviousWordStart,
12230 window: &mut Window,
12231 cx: &mut Context<Self>,
12232 ) {
12233 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12234 self.transact(window, cx, |this, window, cx| {
12235 this.select_autoclose_pair(window, cx);
12236 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12237 s.move_with(|map, selection| {
12238 if selection.is_empty() {
12239 let cursor = if action.ignore_newlines {
12240 movement::previous_word_start(map, selection.head())
12241 } else {
12242 movement::previous_word_start_or_newline(map, selection.head())
12243 };
12244 selection.set_head(cursor, SelectionGoal::None);
12245 }
12246 });
12247 });
12248 this.insert("", window, cx);
12249 });
12250 }
12251
12252 pub fn delete_to_previous_subword_start(
12253 &mut self,
12254 _: &DeleteToPreviousSubwordStart,
12255 window: &mut Window,
12256 cx: &mut Context<Self>,
12257 ) {
12258 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12259 self.transact(window, cx, |this, window, cx| {
12260 this.select_autoclose_pair(window, cx);
12261 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12262 s.move_with(|map, selection| {
12263 if selection.is_empty() {
12264 let cursor = movement::previous_subword_start(map, selection.head());
12265 selection.set_head(cursor, SelectionGoal::None);
12266 }
12267 });
12268 });
12269 this.insert("", window, cx);
12270 });
12271 }
12272
12273 pub fn move_to_next_word_end(
12274 &mut self,
12275 _: &MoveToNextWordEnd,
12276 window: &mut Window,
12277 cx: &mut Context<Self>,
12278 ) {
12279 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12280 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12281 s.move_cursors_with(|map, head, _| {
12282 (movement::next_word_end(map, head), SelectionGoal::None)
12283 });
12284 })
12285 }
12286
12287 pub fn move_to_next_subword_end(
12288 &mut self,
12289 _: &MoveToNextSubwordEnd,
12290 window: &mut Window,
12291 cx: &mut Context<Self>,
12292 ) {
12293 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12294 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12295 s.move_cursors_with(|map, head, _| {
12296 (movement::next_subword_end(map, head), SelectionGoal::None)
12297 });
12298 })
12299 }
12300
12301 pub fn select_to_next_word_end(
12302 &mut self,
12303 _: &SelectToNextWordEnd,
12304 window: &mut Window,
12305 cx: &mut Context<Self>,
12306 ) {
12307 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12308 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12309 s.move_heads_with(|map, head, _| {
12310 (movement::next_word_end(map, head), SelectionGoal::None)
12311 });
12312 })
12313 }
12314
12315 pub fn select_to_next_subword_end(
12316 &mut self,
12317 _: &SelectToNextSubwordEnd,
12318 window: &mut Window,
12319 cx: &mut Context<Self>,
12320 ) {
12321 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12322 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12323 s.move_heads_with(|map, head, _| {
12324 (movement::next_subword_end(map, head), SelectionGoal::None)
12325 });
12326 })
12327 }
12328
12329 pub fn delete_to_next_word_end(
12330 &mut self,
12331 action: &DeleteToNextWordEnd,
12332 window: &mut Window,
12333 cx: &mut Context<Self>,
12334 ) {
12335 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12336 self.transact(window, cx, |this, window, cx| {
12337 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12338 s.move_with(|map, selection| {
12339 if selection.is_empty() {
12340 let cursor = if action.ignore_newlines {
12341 movement::next_word_end(map, selection.head())
12342 } else {
12343 movement::next_word_end_or_newline(map, selection.head())
12344 };
12345 selection.set_head(cursor, SelectionGoal::None);
12346 }
12347 });
12348 });
12349 this.insert("", window, cx);
12350 });
12351 }
12352
12353 pub fn delete_to_next_subword_end(
12354 &mut self,
12355 _: &DeleteToNextSubwordEnd,
12356 window: &mut Window,
12357 cx: &mut Context<Self>,
12358 ) {
12359 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12360 self.transact(window, cx, |this, window, cx| {
12361 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12362 s.move_with(|map, selection| {
12363 if selection.is_empty() {
12364 let cursor = movement::next_subword_end(map, selection.head());
12365 selection.set_head(cursor, SelectionGoal::None);
12366 }
12367 });
12368 });
12369 this.insert("", window, cx);
12370 });
12371 }
12372
12373 pub fn move_to_beginning_of_line(
12374 &mut self,
12375 action: &MoveToBeginningOfLine,
12376 window: &mut Window,
12377 cx: &mut Context<Self>,
12378 ) {
12379 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12380 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12381 s.move_cursors_with(|map, head, _| {
12382 (
12383 movement::indented_line_beginning(
12384 map,
12385 head,
12386 action.stop_at_soft_wraps,
12387 action.stop_at_indent,
12388 ),
12389 SelectionGoal::None,
12390 )
12391 });
12392 })
12393 }
12394
12395 pub fn select_to_beginning_of_line(
12396 &mut self,
12397 action: &SelectToBeginningOfLine,
12398 window: &mut Window,
12399 cx: &mut Context<Self>,
12400 ) {
12401 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12402 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12403 s.move_heads_with(|map, head, _| {
12404 (
12405 movement::indented_line_beginning(
12406 map,
12407 head,
12408 action.stop_at_soft_wraps,
12409 action.stop_at_indent,
12410 ),
12411 SelectionGoal::None,
12412 )
12413 });
12414 });
12415 }
12416
12417 pub fn delete_to_beginning_of_line(
12418 &mut self,
12419 action: &DeleteToBeginningOfLine,
12420 window: &mut Window,
12421 cx: &mut Context<Self>,
12422 ) {
12423 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12424 self.transact(window, cx, |this, window, cx| {
12425 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12426 s.move_with(|_, selection| {
12427 selection.reversed = true;
12428 });
12429 });
12430
12431 this.select_to_beginning_of_line(
12432 &SelectToBeginningOfLine {
12433 stop_at_soft_wraps: false,
12434 stop_at_indent: action.stop_at_indent,
12435 },
12436 window,
12437 cx,
12438 );
12439 this.backspace(&Backspace, window, cx);
12440 });
12441 }
12442
12443 pub fn move_to_end_of_line(
12444 &mut self,
12445 action: &MoveToEndOfLine,
12446 window: &mut Window,
12447 cx: &mut Context<Self>,
12448 ) {
12449 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12450 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12451 s.move_cursors_with(|map, head, _| {
12452 (
12453 movement::line_end(map, head, action.stop_at_soft_wraps),
12454 SelectionGoal::None,
12455 )
12456 });
12457 })
12458 }
12459
12460 pub fn select_to_end_of_line(
12461 &mut self,
12462 action: &SelectToEndOfLine,
12463 window: &mut Window,
12464 cx: &mut Context<Self>,
12465 ) {
12466 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12467 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12468 s.move_heads_with(|map, head, _| {
12469 (
12470 movement::line_end(map, head, action.stop_at_soft_wraps),
12471 SelectionGoal::None,
12472 )
12473 });
12474 })
12475 }
12476
12477 pub fn delete_to_end_of_line(
12478 &mut self,
12479 _: &DeleteToEndOfLine,
12480 window: &mut Window,
12481 cx: &mut Context<Self>,
12482 ) {
12483 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12484 self.transact(window, cx, |this, window, cx| {
12485 this.select_to_end_of_line(
12486 &SelectToEndOfLine {
12487 stop_at_soft_wraps: false,
12488 },
12489 window,
12490 cx,
12491 );
12492 this.delete(&Delete, window, cx);
12493 });
12494 }
12495
12496 pub fn cut_to_end_of_line(
12497 &mut self,
12498 _: &CutToEndOfLine,
12499 window: &mut Window,
12500 cx: &mut Context<Self>,
12501 ) {
12502 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12503 self.transact(window, cx, |this, window, cx| {
12504 this.select_to_end_of_line(
12505 &SelectToEndOfLine {
12506 stop_at_soft_wraps: false,
12507 },
12508 window,
12509 cx,
12510 );
12511 this.cut(&Cut, window, cx);
12512 });
12513 }
12514
12515 pub fn move_to_start_of_paragraph(
12516 &mut self,
12517 _: &MoveToStartOfParagraph,
12518 window: &mut Window,
12519 cx: &mut Context<Self>,
12520 ) {
12521 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12522 cx.propagate();
12523 return;
12524 }
12525 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12526 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12527 s.move_with(|map, selection| {
12528 selection.collapse_to(
12529 movement::start_of_paragraph(map, selection.head(), 1),
12530 SelectionGoal::None,
12531 )
12532 });
12533 })
12534 }
12535
12536 pub fn move_to_end_of_paragraph(
12537 &mut self,
12538 _: &MoveToEndOfParagraph,
12539 window: &mut Window,
12540 cx: &mut Context<Self>,
12541 ) {
12542 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12543 cx.propagate();
12544 return;
12545 }
12546 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12547 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12548 s.move_with(|map, selection| {
12549 selection.collapse_to(
12550 movement::end_of_paragraph(map, selection.head(), 1),
12551 SelectionGoal::None,
12552 )
12553 });
12554 })
12555 }
12556
12557 pub fn select_to_start_of_paragraph(
12558 &mut self,
12559 _: &SelectToStartOfParagraph,
12560 window: &mut Window,
12561 cx: &mut Context<Self>,
12562 ) {
12563 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12564 cx.propagate();
12565 return;
12566 }
12567 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12568 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12569 s.move_heads_with(|map, head, _| {
12570 (
12571 movement::start_of_paragraph(map, head, 1),
12572 SelectionGoal::None,
12573 )
12574 });
12575 })
12576 }
12577
12578 pub fn select_to_end_of_paragraph(
12579 &mut self,
12580 _: &SelectToEndOfParagraph,
12581 window: &mut Window,
12582 cx: &mut Context<Self>,
12583 ) {
12584 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12585 cx.propagate();
12586 return;
12587 }
12588 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12589 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12590 s.move_heads_with(|map, head, _| {
12591 (
12592 movement::end_of_paragraph(map, head, 1),
12593 SelectionGoal::None,
12594 )
12595 });
12596 })
12597 }
12598
12599 pub fn move_to_start_of_excerpt(
12600 &mut self,
12601 _: &MoveToStartOfExcerpt,
12602 window: &mut Window,
12603 cx: &mut Context<Self>,
12604 ) {
12605 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12606 cx.propagate();
12607 return;
12608 }
12609 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12610 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12611 s.move_with(|map, selection| {
12612 selection.collapse_to(
12613 movement::start_of_excerpt(
12614 map,
12615 selection.head(),
12616 workspace::searchable::Direction::Prev,
12617 ),
12618 SelectionGoal::None,
12619 )
12620 });
12621 })
12622 }
12623
12624 pub fn move_to_start_of_next_excerpt(
12625 &mut self,
12626 _: &MoveToStartOfNextExcerpt,
12627 window: &mut Window,
12628 cx: &mut Context<Self>,
12629 ) {
12630 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12631 cx.propagate();
12632 return;
12633 }
12634
12635 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12636 s.move_with(|map, selection| {
12637 selection.collapse_to(
12638 movement::start_of_excerpt(
12639 map,
12640 selection.head(),
12641 workspace::searchable::Direction::Next,
12642 ),
12643 SelectionGoal::None,
12644 )
12645 });
12646 })
12647 }
12648
12649 pub fn move_to_end_of_excerpt(
12650 &mut self,
12651 _: &MoveToEndOfExcerpt,
12652 window: &mut Window,
12653 cx: &mut Context<Self>,
12654 ) {
12655 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12656 cx.propagate();
12657 return;
12658 }
12659 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12660 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12661 s.move_with(|map, selection| {
12662 selection.collapse_to(
12663 movement::end_of_excerpt(
12664 map,
12665 selection.head(),
12666 workspace::searchable::Direction::Next,
12667 ),
12668 SelectionGoal::None,
12669 )
12670 });
12671 })
12672 }
12673
12674 pub fn move_to_end_of_previous_excerpt(
12675 &mut self,
12676 _: &MoveToEndOfPreviousExcerpt,
12677 window: &mut Window,
12678 cx: &mut Context<Self>,
12679 ) {
12680 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12681 cx.propagate();
12682 return;
12683 }
12684 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12685 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12686 s.move_with(|map, selection| {
12687 selection.collapse_to(
12688 movement::end_of_excerpt(
12689 map,
12690 selection.head(),
12691 workspace::searchable::Direction::Prev,
12692 ),
12693 SelectionGoal::None,
12694 )
12695 });
12696 })
12697 }
12698
12699 pub fn select_to_start_of_excerpt(
12700 &mut self,
12701 _: &SelectToStartOfExcerpt,
12702 window: &mut Window,
12703 cx: &mut Context<Self>,
12704 ) {
12705 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12706 cx.propagate();
12707 return;
12708 }
12709 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12710 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12711 s.move_heads_with(|map, head, _| {
12712 (
12713 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12714 SelectionGoal::None,
12715 )
12716 });
12717 })
12718 }
12719
12720 pub fn select_to_start_of_next_excerpt(
12721 &mut self,
12722 _: &SelectToStartOfNextExcerpt,
12723 window: &mut Window,
12724 cx: &mut Context<Self>,
12725 ) {
12726 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12727 cx.propagate();
12728 return;
12729 }
12730 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12731 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12732 s.move_heads_with(|map, head, _| {
12733 (
12734 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12735 SelectionGoal::None,
12736 )
12737 });
12738 })
12739 }
12740
12741 pub fn select_to_end_of_excerpt(
12742 &mut self,
12743 _: &SelectToEndOfExcerpt,
12744 window: &mut Window,
12745 cx: &mut Context<Self>,
12746 ) {
12747 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12748 cx.propagate();
12749 return;
12750 }
12751 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12752 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12753 s.move_heads_with(|map, head, _| {
12754 (
12755 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12756 SelectionGoal::None,
12757 )
12758 });
12759 })
12760 }
12761
12762 pub fn select_to_end_of_previous_excerpt(
12763 &mut self,
12764 _: &SelectToEndOfPreviousExcerpt,
12765 window: &mut Window,
12766 cx: &mut Context<Self>,
12767 ) {
12768 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12769 cx.propagate();
12770 return;
12771 }
12772 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12773 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12774 s.move_heads_with(|map, head, _| {
12775 (
12776 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12777 SelectionGoal::None,
12778 )
12779 });
12780 })
12781 }
12782
12783 pub fn move_to_beginning(
12784 &mut self,
12785 _: &MoveToBeginning,
12786 window: &mut Window,
12787 cx: &mut Context<Self>,
12788 ) {
12789 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12790 cx.propagate();
12791 return;
12792 }
12793 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12794 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12795 s.select_ranges(vec![0..0]);
12796 });
12797 }
12798
12799 pub fn select_to_beginning(
12800 &mut self,
12801 _: &SelectToBeginning,
12802 window: &mut Window,
12803 cx: &mut Context<Self>,
12804 ) {
12805 let mut selection = self.selections.last::<Point>(cx);
12806 selection.set_head(Point::zero(), SelectionGoal::None);
12807 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12808 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12809 s.select(vec![selection]);
12810 });
12811 }
12812
12813 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12814 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12815 cx.propagate();
12816 return;
12817 }
12818 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12819 let cursor = self.buffer.read(cx).read(cx).len();
12820 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12821 s.select_ranges(vec![cursor..cursor])
12822 });
12823 }
12824
12825 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12826 self.nav_history = nav_history;
12827 }
12828
12829 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12830 self.nav_history.as_ref()
12831 }
12832
12833 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12834 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12835 }
12836
12837 fn push_to_nav_history(
12838 &mut self,
12839 cursor_anchor: Anchor,
12840 new_position: Option<Point>,
12841 is_deactivate: bool,
12842 cx: &mut Context<Self>,
12843 ) {
12844 if let Some(nav_history) = self.nav_history.as_mut() {
12845 let buffer = self.buffer.read(cx).read(cx);
12846 let cursor_position = cursor_anchor.to_point(&buffer);
12847 let scroll_state = self.scroll_manager.anchor();
12848 let scroll_top_row = scroll_state.top_row(&buffer);
12849 drop(buffer);
12850
12851 if let Some(new_position) = new_position {
12852 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12853 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12854 return;
12855 }
12856 }
12857
12858 nav_history.push(
12859 Some(NavigationData {
12860 cursor_anchor,
12861 cursor_position,
12862 scroll_anchor: scroll_state,
12863 scroll_top_row,
12864 }),
12865 cx,
12866 );
12867 cx.emit(EditorEvent::PushedToNavHistory {
12868 anchor: cursor_anchor,
12869 is_deactivate,
12870 })
12871 }
12872 }
12873
12874 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12875 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12876 let buffer = self.buffer.read(cx).snapshot(cx);
12877 let mut selection = self.selections.first::<usize>(cx);
12878 selection.set_head(buffer.len(), SelectionGoal::None);
12879 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12880 s.select(vec![selection]);
12881 });
12882 }
12883
12884 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12885 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12886 let end = self.buffer.read(cx).read(cx).len();
12887 self.change_selections(None, window, cx, |s| {
12888 s.select_ranges(vec![0..end]);
12889 });
12890 }
12891
12892 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12893 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12894 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12895 let mut selections = self.selections.all::<Point>(cx);
12896 let max_point = display_map.buffer_snapshot.max_point();
12897 for selection in &mut selections {
12898 let rows = selection.spanned_rows(true, &display_map);
12899 selection.start = Point::new(rows.start.0, 0);
12900 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12901 selection.reversed = false;
12902 }
12903 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12904 s.select(selections);
12905 });
12906 }
12907
12908 pub fn split_selection_into_lines(
12909 &mut self,
12910 _: &SplitSelectionIntoLines,
12911 window: &mut Window,
12912 cx: &mut Context<Self>,
12913 ) {
12914 let selections = self
12915 .selections
12916 .all::<Point>(cx)
12917 .into_iter()
12918 .map(|selection| selection.start..selection.end)
12919 .collect::<Vec<_>>();
12920 self.unfold_ranges(&selections, true, true, cx);
12921
12922 let mut new_selection_ranges = Vec::new();
12923 {
12924 let buffer = self.buffer.read(cx).read(cx);
12925 for selection in selections {
12926 for row in selection.start.row..selection.end.row {
12927 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12928 new_selection_ranges.push(cursor..cursor);
12929 }
12930
12931 let is_multiline_selection = selection.start.row != selection.end.row;
12932 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12933 // so this action feels more ergonomic when paired with other selection operations
12934 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12935 if !should_skip_last {
12936 new_selection_ranges.push(selection.end..selection.end);
12937 }
12938 }
12939 }
12940 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12941 s.select_ranges(new_selection_ranges);
12942 });
12943 }
12944
12945 pub fn add_selection_above(
12946 &mut self,
12947 _: &AddSelectionAbove,
12948 window: &mut Window,
12949 cx: &mut Context<Self>,
12950 ) {
12951 self.add_selection(true, window, cx);
12952 }
12953
12954 pub fn add_selection_below(
12955 &mut self,
12956 _: &AddSelectionBelow,
12957 window: &mut Window,
12958 cx: &mut Context<Self>,
12959 ) {
12960 self.add_selection(false, window, cx);
12961 }
12962
12963 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12964 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12965
12966 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12967 let all_selections = self.selections.all::<Point>(cx);
12968 let text_layout_details = self.text_layout_details(window);
12969
12970 let (mut columnar_selections, new_selections_to_columnarize) = {
12971 if let Some(state) = self.add_selections_state.as_ref() {
12972 let columnar_selection_ids: HashSet<_> = state
12973 .groups
12974 .iter()
12975 .flat_map(|group| group.stack.iter())
12976 .copied()
12977 .collect();
12978
12979 all_selections
12980 .into_iter()
12981 .partition(|s| columnar_selection_ids.contains(&s.id))
12982 } else {
12983 (Vec::new(), all_selections)
12984 }
12985 };
12986
12987 let mut state = self
12988 .add_selections_state
12989 .take()
12990 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
12991
12992 for selection in new_selections_to_columnarize {
12993 let range = selection.display_range(&display_map).sorted();
12994 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12995 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12996 let positions = start_x.min(end_x)..start_x.max(end_x);
12997 let mut stack = Vec::new();
12998 for row in range.start.row().0..=range.end.row().0 {
12999 if let Some(selection) = self.selections.build_columnar_selection(
13000 &display_map,
13001 DisplayRow(row),
13002 &positions,
13003 selection.reversed,
13004 &text_layout_details,
13005 ) {
13006 stack.push(selection.id);
13007 columnar_selections.push(selection);
13008 }
13009 }
13010 if !stack.is_empty() {
13011 if above {
13012 stack.reverse();
13013 }
13014 state.groups.push(AddSelectionsGroup { above, stack });
13015 }
13016 }
13017
13018 let mut final_selections = Vec::new();
13019 let end_row = if above {
13020 DisplayRow(0)
13021 } else {
13022 display_map.max_point().row()
13023 };
13024
13025 let mut last_added_item_per_group = HashMap::default();
13026 for group in state.groups.iter_mut() {
13027 if let Some(last_id) = group.stack.last() {
13028 last_added_item_per_group.insert(*last_id, group);
13029 }
13030 }
13031
13032 for selection in columnar_selections {
13033 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13034 if above == group.above {
13035 let range = selection.display_range(&display_map).sorted();
13036 debug_assert_eq!(range.start.row(), range.end.row());
13037 let mut row = range.start.row();
13038 let positions =
13039 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13040 px(start)..px(end)
13041 } else {
13042 let start_x =
13043 display_map.x_for_display_point(range.start, &text_layout_details);
13044 let end_x =
13045 display_map.x_for_display_point(range.end, &text_layout_details);
13046 start_x.min(end_x)..start_x.max(end_x)
13047 };
13048
13049 let mut maybe_new_selection = None;
13050 while row != end_row {
13051 if above {
13052 row.0 -= 1;
13053 } else {
13054 row.0 += 1;
13055 }
13056 if let Some(new_selection) = self.selections.build_columnar_selection(
13057 &display_map,
13058 row,
13059 &positions,
13060 selection.reversed,
13061 &text_layout_details,
13062 ) {
13063 maybe_new_selection = Some(new_selection);
13064 break;
13065 }
13066 }
13067
13068 if let Some(new_selection) = maybe_new_selection {
13069 group.stack.push(new_selection.id);
13070 if above {
13071 final_selections.push(new_selection);
13072 final_selections.push(selection);
13073 } else {
13074 final_selections.push(selection);
13075 final_selections.push(new_selection);
13076 }
13077 } else {
13078 final_selections.push(selection);
13079 }
13080 } else {
13081 group.stack.pop();
13082 }
13083 } else {
13084 final_selections.push(selection);
13085 }
13086 }
13087
13088 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13089 s.select(final_selections);
13090 });
13091
13092 let final_selection_ids: HashSet<_> = self
13093 .selections
13094 .all::<Point>(cx)
13095 .iter()
13096 .map(|s| s.id)
13097 .collect();
13098 state.groups.retain_mut(|group| {
13099 // selections might get merged above so we remove invalid items from stacks
13100 group.stack.retain(|id| final_selection_ids.contains(id));
13101
13102 // single selection in stack can be treated as initial state
13103 group.stack.len() > 1
13104 });
13105
13106 if !state.groups.is_empty() {
13107 self.add_selections_state = Some(state);
13108 }
13109 }
13110
13111 fn select_match_ranges(
13112 &mut self,
13113 range: Range<usize>,
13114 reversed: bool,
13115 replace_newest: bool,
13116 auto_scroll: Option<Autoscroll>,
13117 window: &mut Window,
13118 cx: &mut Context<Editor>,
13119 ) {
13120 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
13121 self.change_selections(auto_scroll, window, cx, |s| {
13122 if replace_newest {
13123 s.delete(s.newest_anchor().id);
13124 }
13125 if reversed {
13126 s.insert_range(range.end..range.start);
13127 } else {
13128 s.insert_range(range);
13129 }
13130 });
13131 }
13132
13133 pub fn select_next_match_internal(
13134 &mut self,
13135 display_map: &DisplaySnapshot,
13136 replace_newest: bool,
13137 autoscroll: Option<Autoscroll>,
13138 window: &mut Window,
13139 cx: &mut Context<Self>,
13140 ) -> Result<()> {
13141 let buffer = &display_map.buffer_snapshot;
13142 let mut selections = self.selections.all::<usize>(cx);
13143 if let Some(mut select_next_state) = self.select_next_state.take() {
13144 let query = &select_next_state.query;
13145 if !select_next_state.done {
13146 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13147 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13148 let mut next_selected_range = None;
13149
13150 let bytes_after_last_selection =
13151 buffer.bytes_in_range(last_selection.end..buffer.len());
13152 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13153 let query_matches = query
13154 .stream_find_iter(bytes_after_last_selection)
13155 .map(|result| (last_selection.end, result))
13156 .chain(
13157 query
13158 .stream_find_iter(bytes_before_first_selection)
13159 .map(|result| (0, result)),
13160 );
13161
13162 for (start_offset, query_match) in query_matches {
13163 let query_match = query_match.unwrap(); // can only fail due to I/O
13164 let offset_range =
13165 start_offset + query_match.start()..start_offset + query_match.end();
13166 let display_range = offset_range.start.to_display_point(display_map)
13167 ..offset_range.end.to_display_point(display_map);
13168
13169 if !select_next_state.wordwise
13170 || (!movement::is_inside_word(display_map, display_range.start)
13171 && !movement::is_inside_word(display_map, display_range.end))
13172 {
13173 // TODO: This is n^2, because we might check all the selections
13174 if !selections
13175 .iter()
13176 .any(|selection| selection.range().overlaps(&offset_range))
13177 {
13178 next_selected_range = Some(offset_range);
13179 break;
13180 }
13181 }
13182 }
13183
13184 if let Some(next_selected_range) = next_selected_range {
13185 self.select_match_ranges(
13186 next_selected_range,
13187 last_selection.reversed,
13188 replace_newest,
13189 autoscroll,
13190 window,
13191 cx,
13192 );
13193 } else {
13194 select_next_state.done = true;
13195 }
13196 }
13197
13198 self.select_next_state = Some(select_next_state);
13199 } else {
13200 let mut only_carets = true;
13201 let mut same_text_selected = true;
13202 let mut selected_text = None;
13203
13204 let mut selections_iter = selections.iter().peekable();
13205 while let Some(selection) = selections_iter.next() {
13206 if selection.start != selection.end {
13207 only_carets = false;
13208 }
13209
13210 if same_text_selected {
13211 if selected_text.is_none() {
13212 selected_text =
13213 Some(buffer.text_for_range(selection.range()).collect::<String>());
13214 }
13215
13216 if let Some(next_selection) = selections_iter.peek() {
13217 if next_selection.range().len() == selection.range().len() {
13218 let next_selected_text = buffer
13219 .text_for_range(next_selection.range())
13220 .collect::<String>();
13221 if Some(next_selected_text) != selected_text {
13222 same_text_selected = false;
13223 selected_text = None;
13224 }
13225 } else {
13226 same_text_selected = false;
13227 selected_text = None;
13228 }
13229 }
13230 }
13231 }
13232
13233 if only_carets {
13234 for selection in &mut selections {
13235 let word_range = movement::surrounding_word(
13236 display_map,
13237 selection.start.to_display_point(display_map),
13238 );
13239 selection.start = word_range.start.to_offset(display_map, Bias::Left);
13240 selection.end = word_range.end.to_offset(display_map, Bias::Left);
13241 selection.goal = SelectionGoal::None;
13242 selection.reversed = false;
13243 self.select_match_ranges(
13244 selection.start..selection.end,
13245 selection.reversed,
13246 replace_newest,
13247 autoscroll,
13248 window,
13249 cx,
13250 );
13251 }
13252
13253 if selections.len() == 1 {
13254 let selection = selections
13255 .last()
13256 .expect("ensured that there's only one selection");
13257 let query = buffer
13258 .text_for_range(selection.start..selection.end)
13259 .collect::<String>();
13260 let is_empty = query.is_empty();
13261 let select_state = SelectNextState {
13262 query: AhoCorasick::new(&[query])?,
13263 wordwise: true,
13264 done: is_empty,
13265 };
13266 self.select_next_state = Some(select_state);
13267 } else {
13268 self.select_next_state = None;
13269 }
13270 } else if let Some(selected_text) = selected_text {
13271 self.select_next_state = Some(SelectNextState {
13272 query: AhoCorasick::new(&[selected_text])?,
13273 wordwise: false,
13274 done: false,
13275 });
13276 self.select_next_match_internal(
13277 display_map,
13278 replace_newest,
13279 autoscroll,
13280 window,
13281 cx,
13282 )?;
13283 }
13284 }
13285 Ok(())
13286 }
13287
13288 pub fn select_all_matches(
13289 &mut self,
13290 _action: &SelectAllMatches,
13291 window: &mut Window,
13292 cx: &mut Context<Self>,
13293 ) -> Result<()> {
13294 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13295
13296 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13297
13298 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13299 let Some(select_next_state) = self.select_next_state.as_mut() else {
13300 return Ok(());
13301 };
13302 if select_next_state.done {
13303 return Ok(());
13304 }
13305
13306 let mut new_selections = Vec::new();
13307
13308 let reversed = self.selections.oldest::<usize>(cx).reversed;
13309 let buffer = &display_map.buffer_snapshot;
13310 let query_matches = select_next_state
13311 .query
13312 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13313
13314 for query_match in query_matches.into_iter() {
13315 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13316 let offset_range = if reversed {
13317 query_match.end()..query_match.start()
13318 } else {
13319 query_match.start()..query_match.end()
13320 };
13321 let display_range = offset_range.start.to_display_point(&display_map)
13322 ..offset_range.end.to_display_point(&display_map);
13323
13324 if !select_next_state.wordwise
13325 || (!movement::is_inside_word(&display_map, display_range.start)
13326 && !movement::is_inside_word(&display_map, display_range.end))
13327 {
13328 new_selections.push(offset_range.start..offset_range.end);
13329 }
13330 }
13331
13332 select_next_state.done = true;
13333 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13334 self.change_selections(None, window, cx, |selections| {
13335 selections.select_ranges(new_selections)
13336 });
13337
13338 Ok(())
13339 }
13340
13341 pub fn select_next(
13342 &mut self,
13343 action: &SelectNext,
13344 window: &mut Window,
13345 cx: &mut Context<Self>,
13346 ) -> Result<()> {
13347 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13348 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13349 self.select_next_match_internal(
13350 &display_map,
13351 action.replace_newest,
13352 Some(Autoscroll::newest()),
13353 window,
13354 cx,
13355 )?;
13356 Ok(())
13357 }
13358
13359 pub fn select_previous(
13360 &mut self,
13361 action: &SelectPrevious,
13362 window: &mut Window,
13363 cx: &mut Context<Self>,
13364 ) -> Result<()> {
13365 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13366 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13367 let buffer = &display_map.buffer_snapshot;
13368 let mut selections = self.selections.all::<usize>(cx);
13369 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13370 let query = &select_prev_state.query;
13371 if !select_prev_state.done {
13372 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13373 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13374 let mut next_selected_range = None;
13375 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13376 let bytes_before_last_selection =
13377 buffer.reversed_bytes_in_range(0..last_selection.start);
13378 let bytes_after_first_selection =
13379 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13380 let query_matches = query
13381 .stream_find_iter(bytes_before_last_selection)
13382 .map(|result| (last_selection.start, result))
13383 .chain(
13384 query
13385 .stream_find_iter(bytes_after_first_selection)
13386 .map(|result| (buffer.len(), result)),
13387 );
13388 for (end_offset, query_match) in query_matches {
13389 let query_match = query_match.unwrap(); // can only fail due to I/O
13390 let offset_range =
13391 end_offset - query_match.end()..end_offset - query_match.start();
13392 let display_range = offset_range.start.to_display_point(&display_map)
13393 ..offset_range.end.to_display_point(&display_map);
13394
13395 if !select_prev_state.wordwise
13396 || (!movement::is_inside_word(&display_map, display_range.start)
13397 && !movement::is_inside_word(&display_map, display_range.end))
13398 {
13399 next_selected_range = Some(offset_range);
13400 break;
13401 }
13402 }
13403
13404 if let Some(next_selected_range) = next_selected_range {
13405 self.select_match_ranges(
13406 next_selected_range,
13407 last_selection.reversed,
13408 action.replace_newest,
13409 Some(Autoscroll::newest()),
13410 window,
13411 cx,
13412 );
13413 } else {
13414 select_prev_state.done = true;
13415 }
13416 }
13417
13418 self.select_prev_state = Some(select_prev_state);
13419 } else {
13420 let mut only_carets = true;
13421 let mut same_text_selected = true;
13422 let mut selected_text = None;
13423
13424 let mut selections_iter = selections.iter().peekable();
13425 while let Some(selection) = selections_iter.next() {
13426 if selection.start != selection.end {
13427 only_carets = false;
13428 }
13429
13430 if same_text_selected {
13431 if selected_text.is_none() {
13432 selected_text =
13433 Some(buffer.text_for_range(selection.range()).collect::<String>());
13434 }
13435
13436 if let Some(next_selection) = selections_iter.peek() {
13437 if next_selection.range().len() == selection.range().len() {
13438 let next_selected_text = buffer
13439 .text_for_range(next_selection.range())
13440 .collect::<String>();
13441 if Some(next_selected_text) != selected_text {
13442 same_text_selected = false;
13443 selected_text = None;
13444 }
13445 } else {
13446 same_text_selected = false;
13447 selected_text = None;
13448 }
13449 }
13450 }
13451 }
13452
13453 if only_carets {
13454 for selection in &mut selections {
13455 let word_range = movement::surrounding_word(
13456 &display_map,
13457 selection.start.to_display_point(&display_map),
13458 );
13459 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
13460 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
13461 selection.goal = SelectionGoal::None;
13462 selection.reversed = false;
13463 self.select_match_ranges(
13464 selection.start..selection.end,
13465 selection.reversed,
13466 action.replace_newest,
13467 Some(Autoscroll::newest()),
13468 window,
13469 cx,
13470 );
13471 }
13472 if selections.len() == 1 {
13473 let selection = selections
13474 .last()
13475 .expect("ensured that there's only one selection");
13476 let query = buffer
13477 .text_for_range(selection.start..selection.end)
13478 .collect::<String>();
13479 let is_empty = query.is_empty();
13480 let select_state = SelectNextState {
13481 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13482 wordwise: true,
13483 done: is_empty,
13484 };
13485 self.select_prev_state = Some(select_state);
13486 } else {
13487 self.select_prev_state = None;
13488 }
13489 } else if let Some(selected_text) = selected_text {
13490 self.select_prev_state = Some(SelectNextState {
13491 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13492 wordwise: false,
13493 done: false,
13494 });
13495 self.select_previous(action, window, cx)?;
13496 }
13497 }
13498 Ok(())
13499 }
13500
13501 pub fn find_next_match(
13502 &mut self,
13503 _: &FindNextMatch,
13504 window: &mut Window,
13505 cx: &mut Context<Self>,
13506 ) -> Result<()> {
13507 let selections = self.selections.disjoint_anchors();
13508 match selections.first() {
13509 Some(first) if selections.len() >= 2 => {
13510 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13511 s.select_ranges([first.range()]);
13512 });
13513 }
13514 _ => self.select_next(
13515 &SelectNext {
13516 replace_newest: true,
13517 },
13518 window,
13519 cx,
13520 )?,
13521 }
13522 Ok(())
13523 }
13524
13525 pub fn find_previous_match(
13526 &mut self,
13527 _: &FindPreviousMatch,
13528 window: &mut Window,
13529 cx: &mut Context<Self>,
13530 ) -> Result<()> {
13531 let selections = self.selections.disjoint_anchors();
13532 match selections.last() {
13533 Some(last) if selections.len() >= 2 => {
13534 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13535 s.select_ranges([last.range()]);
13536 });
13537 }
13538 _ => self.select_previous(
13539 &SelectPrevious {
13540 replace_newest: true,
13541 },
13542 window,
13543 cx,
13544 )?,
13545 }
13546 Ok(())
13547 }
13548
13549 pub fn toggle_comments(
13550 &mut self,
13551 action: &ToggleComments,
13552 window: &mut Window,
13553 cx: &mut Context<Self>,
13554 ) {
13555 if self.read_only(cx) {
13556 return;
13557 }
13558 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13559 let text_layout_details = &self.text_layout_details(window);
13560 self.transact(window, cx, |this, window, cx| {
13561 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13562 let mut edits = Vec::new();
13563 let mut selection_edit_ranges = Vec::new();
13564 let mut last_toggled_row = None;
13565 let snapshot = this.buffer.read(cx).read(cx);
13566 let empty_str: Arc<str> = Arc::default();
13567 let mut suffixes_inserted = Vec::new();
13568 let ignore_indent = action.ignore_indent;
13569
13570 fn comment_prefix_range(
13571 snapshot: &MultiBufferSnapshot,
13572 row: MultiBufferRow,
13573 comment_prefix: &str,
13574 comment_prefix_whitespace: &str,
13575 ignore_indent: bool,
13576 ) -> Range<Point> {
13577 let indent_size = if ignore_indent {
13578 0
13579 } else {
13580 snapshot.indent_size_for_line(row).len
13581 };
13582
13583 let start = Point::new(row.0, indent_size);
13584
13585 let mut line_bytes = snapshot
13586 .bytes_in_range(start..snapshot.max_point())
13587 .flatten()
13588 .copied();
13589
13590 // If this line currently begins with the line comment prefix, then record
13591 // the range containing the prefix.
13592 if line_bytes
13593 .by_ref()
13594 .take(comment_prefix.len())
13595 .eq(comment_prefix.bytes())
13596 {
13597 // Include any whitespace that matches the comment prefix.
13598 let matching_whitespace_len = line_bytes
13599 .zip(comment_prefix_whitespace.bytes())
13600 .take_while(|(a, b)| a == b)
13601 .count() as u32;
13602 let end = Point::new(
13603 start.row,
13604 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13605 );
13606 start..end
13607 } else {
13608 start..start
13609 }
13610 }
13611
13612 fn comment_suffix_range(
13613 snapshot: &MultiBufferSnapshot,
13614 row: MultiBufferRow,
13615 comment_suffix: &str,
13616 comment_suffix_has_leading_space: bool,
13617 ) -> Range<Point> {
13618 let end = Point::new(row.0, snapshot.line_len(row));
13619 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13620
13621 let mut line_end_bytes = snapshot
13622 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13623 .flatten()
13624 .copied();
13625
13626 let leading_space_len = if suffix_start_column > 0
13627 && line_end_bytes.next() == Some(b' ')
13628 && comment_suffix_has_leading_space
13629 {
13630 1
13631 } else {
13632 0
13633 };
13634
13635 // If this line currently begins with the line comment prefix, then record
13636 // the range containing the prefix.
13637 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13638 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13639 start..end
13640 } else {
13641 end..end
13642 }
13643 }
13644
13645 // TODO: Handle selections that cross excerpts
13646 for selection in &mut selections {
13647 let start_column = snapshot
13648 .indent_size_for_line(MultiBufferRow(selection.start.row))
13649 .len;
13650 let language = if let Some(language) =
13651 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13652 {
13653 language
13654 } else {
13655 continue;
13656 };
13657
13658 selection_edit_ranges.clear();
13659
13660 // If multiple selections contain a given row, avoid processing that
13661 // row more than once.
13662 let mut start_row = MultiBufferRow(selection.start.row);
13663 if last_toggled_row == Some(start_row) {
13664 start_row = start_row.next_row();
13665 }
13666 let end_row =
13667 if selection.end.row > selection.start.row && selection.end.column == 0 {
13668 MultiBufferRow(selection.end.row - 1)
13669 } else {
13670 MultiBufferRow(selection.end.row)
13671 };
13672 last_toggled_row = Some(end_row);
13673
13674 if start_row > end_row {
13675 continue;
13676 }
13677
13678 // If the language has line comments, toggle those.
13679 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13680
13681 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13682 if ignore_indent {
13683 full_comment_prefixes = full_comment_prefixes
13684 .into_iter()
13685 .map(|s| Arc::from(s.trim_end()))
13686 .collect();
13687 }
13688
13689 if !full_comment_prefixes.is_empty() {
13690 let first_prefix = full_comment_prefixes
13691 .first()
13692 .expect("prefixes is non-empty");
13693 let prefix_trimmed_lengths = full_comment_prefixes
13694 .iter()
13695 .map(|p| p.trim_end_matches(' ').len())
13696 .collect::<SmallVec<[usize; 4]>>();
13697
13698 let mut all_selection_lines_are_comments = true;
13699
13700 for row in start_row.0..=end_row.0 {
13701 let row = MultiBufferRow(row);
13702 if start_row < end_row && snapshot.is_line_blank(row) {
13703 continue;
13704 }
13705
13706 let prefix_range = full_comment_prefixes
13707 .iter()
13708 .zip(prefix_trimmed_lengths.iter().copied())
13709 .map(|(prefix, trimmed_prefix_len)| {
13710 comment_prefix_range(
13711 snapshot.deref(),
13712 row,
13713 &prefix[..trimmed_prefix_len],
13714 &prefix[trimmed_prefix_len..],
13715 ignore_indent,
13716 )
13717 })
13718 .max_by_key(|range| range.end.column - range.start.column)
13719 .expect("prefixes is non-empty");
13720
13721 if prefix_range.is_empty() {
13722 all_selection_lines_are_comments = false;
13723 }
13724
13725 selection_edit_ranges.push(prefix_range);
13726 }
13727
13728 if all_selection_lines_are_comments {
13729 edits.extend(
13730 selection_edit_ranges
13731 .iter()
13732 .cloned()
13733 .map(|range| (range, empty_str.clone())),
13734 );
13735 } else {
13736 let min_column = selection_edit_ranges
13737 .iter()
13738 .map(|range| range.start.column)
13739 .min()
13740 .unwrap_or(0);
13741 edits.extend(selection_edit_ranges.iter().map(|range| {
13742 let position = Point::new(range.start.row, min_column);
13743 (position..position, first_prefix.clone())
13744 }));
13745 }
13746 } else if let Some((full_comment_prefix, comment_suffix)) =
13747 language.block_comment_delimiters()
13748 {
13749 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13750 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13751 let prefix_range = comment_prefix_range(
13752 snapshot.deref(),
13753 start_row,
13754 comment_prefix,
13755 comment_prefix_whitespace,
13756 ignore_indent,
13757 );
13758 let suffix_range = comment_suffix_range(
13759 snapshot.deref(),
13760 end_row,
13761 comment_suffix.trim_start_matches(' '),
13762 comment_suffix.starts_with(' '),
13763 );
13764
13765 if prefix_range.is_empty() || suffix_range.is_empty() {
13766 edits.push((
13767 prefix_range.start..prefix_range.start,
13768 full_comment_prefix.clone(),
13769 ));
13770 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13771 suffixes_inserted.push((end_row, comment_suffix.len()));
13772 } else {
13773 edits.push((prefix_range, empty_str.clone()));
13774 edits.push((suffix_range, empty_str.clone()));
13775 }
13776 } else {
13777 continue;
13778 }
13779 }
13780
13781 drop(snapshot);
13782 this.buffer.update(cx, |buffer, cx| {
13783 buffer.edit(edits, None, cx);
13784 });
13785
13786 // Adjust selections so that they end before any comment suffixes that
13787 // were inserted.
13788 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13789 let mut selections = this.selections.all::<Point>(cx);
13790 let snapshot = this.buffer.read(cx).read(cx);
13791 for selection in &mut selections {
13792 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13793 match row.cmp(&MultiBufferRow(selection.end.row)) {
13794 Ordering::Less => {
13795 suffixes_inserted.next();
13796 continue;
13797 }
13798 Ordering::Greater => break,
13799 Ordering::Equal => {
13800 if selection.end.column == snapshot.line_len(row) {
13801 if selection.is_empty() {
13802 selection.start.column -= suffix_len as u32;
13803 }
13804 selection.end.column -= suffix_len as u32;
13805 }
13806 break;
13807 }
13808 }
13809 }
13810 }
13811
13812 drop(snapshot);
13813 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13814 s.select(selections)
13815 });
13816
13817 let selections = this.selections.all::<Point>(cx);
13818 let selections_on_single_row = selections.windows(2).all(|selections| {
13819 selections[0].start.row == selections[1].start.row
13820 && selections[0].end.row == selections[1].end.row
13821 && selections[0].start.row == selections[0].end.row
13822 });
13823 let selections_selecting = selections
13824 .iter()
13825 .any(|selection| selection.start != selection.end);
13826 let advance_downwards = action.advance_downwards
13827 && selections_on_single_row
13828 && !selections_selecting
13829 && !matches!(this.mode, EditorMode::SingleLine { .. });
13830
13831 if advance_downwards {
13832 let snapshot = this.buffer.read(cx).snapshot(cx);
13833
13834 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13835 s.move_cursors_with(|display_snapshot, display_point, _| {
13836 let mut point = display_point.to_point(display_snapshot);
13837 point.row += 1;
13838 point = snapshot.clip_point(point, Bias::Left);
13839 let display_point = point.to_display_point(display_snapshot);
13840 let goal = SelectionGoal::HorizontalPosition(
13841 display_snapshot
13842 .x_for_display_point(display_point, text_layout_details)
13843 .into(),
13844 );
13845 (display_point, goal)
13846 })
13847 });
13848 }
13849 });
13850 }
13851
13852 pub fn select_enclosing_symbol(
13853 &mut self,
13854 _: &SelectEnclosingSymbol,
13855 window: &mut Window,
13856 cx: &mut Context<Self>,
13857 ) {
13858 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13859
13860 let buffer = self.buffer.read(cx).snapshot(cx);
13861 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13862
13863 fn update_selection(
13864 selection: &Selection<usize>,
13865 buffer_snap: &MultiBufferSnapshot,
13866 ) -> Option<Selection<usize>> {
13867 let cursor = selection.head();
13868 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13869 for symbol in symbols.iter().rev() {
13870 let start = symbol.range.start.to_offset(buffer_snap);
13871 let end = symbol.range.end.to_offset(buffer_snap);
13872 let new_range = start..end;
13873 if start < selection.start || end > selection.end {
13874 return Some(Selection {
13875 id: selection.id,
13876 start: new_range.start,
13877 end: new_range.end,
13878 goal: SelectionGoal::None,
13879 reversed: selection.reversed,
13880 });
13881 }
13882 }
13883 None
13884 }
13885
13886 let mut selected_larger_symbol = false;
13887 let new_selections = old_selections
13888 .iter()
13889 .map(|selection| match update_selection(selection, &buffer) {
13890 Some(new_selection) => {
13891 if new_selection.range() != selection.range() {
13892 selected_larger_symbol = true;
13893 }
13894 new_selection
13895 }
13896 None => selection.clone(),
13897 })
13898 .collect::<Vec<_>>();
13899
13900 if selected_larger_symbol {
13901 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13902 s.select(new_selections);
13903 });
13904 }
13905 }
13906
13907 pub fn select_larger_syntax_node(
13908 &mut self,
13909 _: &SelectLargerSyntaxNode,
13910 window: &mut Window,
13911 cx: &mut Context<Self>,
13912 ) {
13913 let Some(visible_row_count) = self.visible_row_count() else {
13914 return;
13915 };
13916 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13917 if old_selections.is_empty() {
13918 return;
13919 }
13920
13921 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13922
13923 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13924 let buffer = self.buffer.read(cx).snapshot(cx);
13925
13926 let mut selected_larger_node = false;
13927 let mut new_selections = old_selections
13928 .iter()
13929 .map(|selection| {
13930 let old_range = selection.start..selection.end;
13931
13932 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13933 // manually select word at selection
13934 if ["string_content", "inline"].contains(&node.kind()) {
13935 let word_range = {
13936 let display_point = buffer
13937 .offset_to_point(old_range.start)
13938 .to_display_point(&display_map);
13939 let Range { start, end } =
13940 movement::surrounding_word(&display_map, display_point);
13941 start.to_point(&display_map).to_offset(&buffer)
13942 ..end.to_point(&display_map).to_offset(&buffer)
13943 };
13944 // ignore if word is already selected
13945 if !word_range.is_empty() && old_range != word_range {
13946 let last_word_range = {
13947 let display_point = buffer
13948 .offset_to_point(old_range.end)
13949 .to_display_point(&display_map);
13950 let Range { start, end } =
13951 movement::surrounding_word(&display_map, display_point);
13952 start.to_point(&display_map).to_offset(&buffer)
13953 ..end.to_point(&display_map).to_offset(&buffer)
13954 };
13955 // only select word if start and end point belongs to same word
13956 if word_range == last_word_range {
13957 selected_larger_node = true;
13958 return Selection {
13959 id: selection.id,
13960 start: word_range.start,
13961 end: word_range.end,
13962 goal: SelectionGoal::None,
13963 reversed: selection.reversed,
13964 };
13965 }
13966 }
13967 }
13968 }
13969
13970 let mut new_range = old_range.clone();
13971 while let Some((_node, containing_range)) =
13972 buffer.syntax_ancestor(new_range.clone())
13973 {
13974 new_range = match containing_range {
13975 MultiOrSingleBufferOffsetRange::Single(_) => break,
13976 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13977 };
13978 if !display_map.intersects_fold(new_range.start)
13979 && !display_map.intersects_fold(new_range.end)
13980 {
13981 break;
13982 }
13983 }
13984
13985 selected_larger_node |= new_range != old_range;
13986 Selection {
13987 id: selection.id,
13988 start: new_range.start,
13989 end: new_range.end,
13990 goal: SelectionGoal::None,
13991 reversed: selection.reversed,
13992 }
13993 })
13994 .collect::<Vec<_>>();
13995
13996 if !selected_larger_node {
13997 return; // don't put this call in the history
13998 }
13999
14000 // scroll based on transformation done to the last selection created by the user
14001 let (last_old, last_new) = old_selections
14002 .last()
14003 .zip(new_selections.last().cloned())
14004 .expect("old_selections isn't empty");
14005
14006 // revert selection
14007 let is_selection_reversed = {
14008 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14009 new_selections.last_mut().expect("checked above").reversed =
14010 should_newest_selection_be_reversed;
14011 should_newest_selection_be_reversed
14012 };
14013
14014 if selected_larger_node {
14015 self.select_syntax_node_history.disable_clearing = true;
14016 self.change_selections(None, window, cx, |s| {
14017 s.select(new_selections.clone());
14018 });
14019 self.select_syntax_node_history.disable_clearing = false;
14020 }
14021
14022 let start_row = last_new.start.to_display_point(&display_map).row().0;
14023 let end_row = last_new.end.to_display_point(&display_map).row().0;
14024 let selection_height = end_row - start_row + 1;
14025 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14026
14027 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14028 let scroll_behavior = if fits_on_the_screen {
14029 self.request_autoscroll(Autoscroll::fit(), cx);
14030 SelectSyntaxNodeScrollBehavior::FitSelection
14031 } else if is_selection_reversed {
14032 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14033 SelectSyntaxNodeScrollBehavior::CursorTop
14034 } else {
14035 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14036 SelectSyntaxNodeScrollBehavior::CursorBottom
14037 };
14038
14039 self.select_syntax_node_history.push((
14040 old_selections,
14041 scroll_behavior,
14042 is_selection_reversed,
14043 ));
14044 }
14045
14046 pub fn select_smaller_syntax_node(
14047 &mut self,
14048 _: &SelectSmallerSyntaxNode,
14049 window: &mut Window,
14050 cx: &mut Context<Self>,
14051 ) {
14052 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14053
14054 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14055 self.select_syntax_node_history.pop()
14056 {
14057 if let Some(selection) = selections.last_mut() {
14058 selection.reversed = is_selection_reversed;
14059 }
14060
14061 self.select_syntax_node_history.disable_clearing = true;
14062 self.change_selections(None, window, cx, |s| {
14063 s.select(selections.to_vec());
14064 });
14065 self.select_syntax_node_history.disable_clearing = false;
14066
14067 match scroll_behavior {
14068 SelectSyntaxNodeScrollBehavior::CursorTop => {
14069 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14070 }
14071 SelectSyntaxNodeScrollBehavior::FitSelection => {
14072 self.request_autoscroll(Autoscroll::fit(), cx);
14073 }
14074 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14075 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14076 }
14077 }
14078 }
14079 }
14080
14081 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14082 if !EditorSettings::get_global(cx).gutter.runnables {
14083 self.clear_tasks();
14084 return Task::ready(());
14085 }
14086 let project = self.project.as_ref().map(Entity::downgrade);
14087 let task_sources = self.lsp_task_sources(cx);
14088 let multi_buffer = self.buffer.downgrade();
14089 cx.spawn_in(window, async move |editor, cx| {
14090 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14091 let Some(project) = project.and_then(|p| p.upgrade()) else {
14092 return;
14093 };
14094 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14095 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14096 }) else {
14097 return;
14098 };
14099
14100 let hide_runnables = project
14101 .update(cx, |project, cx| {
14102 // Do not display any test indicators in non-dev server remote projects.
14103 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14104 })
14105 .unwrap_or(true);
14106 if hide_runnables {
14107 return;
14108 }
14109 let new_rows =
14110 cx.background_spawn({
14111 let snapshot = display_snapshot.clone();
14112 async move {
14113 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14114 }
14115 })
14116 .await;
14117 let Ok(lsp_tasks) =
14118 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14119 else {
14120 return;
14121 };
14122 let lsp_tasks = lsp_tasks.await;
14123
14124 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14125 lsp_tasks
14126 .into_iter()
14127 .flat_map(|(kind, tasks)| {
14128 tasks.into_iter().filter_map(move |(location, task)| {
14129 Some((kind.clone(), location?, task))
14130 })
14131 })
14132 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14133 let buffer = location.target.buffer;
14134 let buffer_snapshot = buffer.read(cx).snapshot();
14135 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14136 |(excerpt_id, snapshot, _)| {
14137 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14138 display_snapshot
14139 .buffer_snapshot
14140 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14141 } else {
14142 None
14143 }
14144 },
14145 );
14146 if let Some(offset) = offset {
14147 let task_buffer_range =
14148 location.target.range.to_point(&buffer_snapshot);
14149 let context_buffer_range =
14150 task_buffer_range.to_offset(&buffer_snapshot);
14151 let context_range = BufferOffset(context_buffer_range.start)
14152 ..BufferOffset(context_buffer_range.end);
14153
14154 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14155 .or_insert_with(|| RunnableTasks {
14156 templates: Vec::new(),
14157 offset,
14158 column: task_buffer_range.start.column,
14159 extra_variables: HashMap::default(),
14160 context_range,
14161 })
14162 .templates
14163 .push((kind, task.original_task().clone()));
14164 }
14165
14166 acc
14167 })
14168 }) else {
14169 return;
14170 };
14171
14172 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14173 buffer.language_settings(cx).tasks.prefer_lsp
14174 }) else {
14175 return;
14176 };
14177
14178 let rows = Self::runnable_rows(
14179 project,
14180 display_snapshot,
14181 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14182 new_rows,
14183 cx.clone(),
14184 )
14185 .await;
14186 editor
14187 .update(cx, |editor, _| {
14188 editor.clear_tasks();
14189 for (key, mut value) in rows {
14190 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14191 value.templates.extend(lsp_tasks.templates);
14192 }
14193
14194 editor.insert_tasks(key, value);
14195 }
14196 for (key, value) in lsp_tasks_by_rows {
14197 editor.insert_tasks(key, value);
14198 }
14199 })
14200 .ok();
14201 })
14202 }
14203 fn fetch_runnable_ranges(
14204 snapshot: &DisplaySnapshot,
14205 range: Range<Anchor>,
14206 ) -> Vec<language::RunnableRange> {
14207 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14208 }
14209
14210 fn runnable_rows(
14211 project: Entity<Project>,
14212 snapshot: DisplaySnapshot,
14213 prefer_lsp: bool,
14214 runnable_ranges: Vec<RunnableRange>,
14215 cx: AsyncWindowContext,
14216 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14217 cx.spawn(async move |cx| {
14218 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14219 for mut runnable in runnable_ranges {
14220 let Some(tasks) = cx
14221 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14222 .ok()
14223 else {
14224 continue;
14225 };
14226 let mut tasks = tasks.await;
14227
14228 if prefer_lsp {
14229 tasks.retain(|(task_kind, _)| {
14230 !matches!(task_kind, TaskSourceKind::Language { .. })
14231 });
14232 }
14233 if tasks.is_empty() {
14234 continue;
14235 }
14236
14237 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14238 let Some(row) = snapshot
14239 .buffer_snapshot
14240 .buffer_line_for_row(MultiBufferRow(point.row))
14241 .map(|(_, range)| range.start.row)
14242 else {
14243 continue;
14244 };
14245
14246 let context_range =
14247 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14248 runnable_rows.push((
14249 (runnable.buffer_id, row),
14250 RunnableTasks {
14251 templates: tasks,
14252 offset: snapshot
14253 .buffer_snapshot
14254 .anchor_before(runnable.run_range.start),
14255 context_range,
14256 column: point.column,
14257 extra_variables: runnable.extra_captures,
14258 },
14259 ));
14260 }
14261 runnable_rows
14262 })
14263 }
14264
14265 fn templates_with_tags(
14266 project: &Entity<Project>,
14267 runnable: &mut Runnable,
14268 cx: &mut App,
14269 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14270 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14271 let (worktree_id, file) = project
14272 .buffer_for_id(runnable.buffer, cx)
14273 .and_then(|buffer| buffer.read(cx).file())
14274 .map(|file| (file.worktree_id(cx), file.clone()))
14275 .unzip();
14276
14277 (
14278 project.task_store().read(cx).task_inventory().cloned(),
14279 worktree_id,
14280 file,
14281 )
14282 });
14283
14284 let tags = mem::take(&mut runnable.tags);
14285 let language = runnable.language.clone();
14286 cx.spawn(async move |cx| {
14287 let mut templates_with_tags = Vec::new();
14288 if let Some(inventory) = inventory {
14289 for RunnableTag(tag) in tags {
14290 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14291 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14292 }) else {
14293 return templates_with_tags;
14294 };
14295 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14296 move |(_, template)| {
14297 template.tags.iter().any(|source_tag| source_tag == &tag)
14298 },
14299 ));
14300 }
14301 }
14302 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14303
14304 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14305 // Strongest source wins; if we have worktree tag binding, prefer that to
14306 // global and language bindings;
14307 // if we have a global binding, prefer that to language binding.
14308 let first_mismatch = templates_with_tags
14309 .iter()
14310 .position(|(tag_source, _)| tag_source != leading_tag_source);
14311 if let Some(index) = first_mismatch {
14312 templates_with_tags.truncate(index);
14313 }
14314 }
14315
14316 templates_with_tags
14317 })
14318 }
14319
14320 pub fn move_to_enclosing_bracket(
14321 &mut self,
14322 _: &MoveToEnclosingBracket,
14323 window: &mut Window,
14324 cx: &mut Context<Self>,
14325 ) {
14326 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14327 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14328 s.move_offsets_with(|snapshot, selection| {
14329 let Some(enclosing_bracket_ranges) =
14330 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14331 else {
14332 return;
14333 };
14334
14335 let mut best_length = usize::MAX;
14336 let mut best_inside = false;
14337 let mut best_in_bracket_range = false;
14338 let mut best_destination = None;
14339 for (open, close) in enclosing_bracket_ranges {
14340 let close = close.to_inclusive();
14341 let length = close.end() - open.start;
14342 let inside = selection.start >= open.end && selection.end <= *close.start();
14343 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14344 || close.contains(&selection.head());
14345
14346 // If best is next to a bracket and current isn't, skip
14347 if !in_bracket_range && best_in_bracket_range {
14348 continue;
14349 }
14350
14351 // Prefer smaller lengths unless best is inside and current isn't
14352 if length > best_length && (best_inside || !inside) {
14353 continue;
14354 }
14355
14356 best_length = length;
14357 best_inside = inside;
14358 best_in_bracket_range = in_bracket_range;
14359 best_destination = Some(
14360 if close.contains(&selection.start) && close.contains(&selection.end) {
14361 if inside { open.end } else { open.start }
14362 } else if inside {
14363 *close.start()
14364 } else {
14365 *close.end()
14366 },
14367 );
14368 }
14369
14370 if let Some(destination) = best_destination {
14371 selection.collapse_to(destination, SelectionGoal::None);
14372 }
14373 })
14374 });
14375 }
14376
14377 pub fn undo_selection(
14378 &mut self,
14379 _: &UndoSelection,
14380 window: &mut Window,
14381 cx: &mut Context<Self>,
14382 ) {
14383 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14384 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14385 self.selection_history.mode = SelectionHistoryMode::Undoing;
14386 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14387 this.end_selection(window, cx);
14388 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14389 s.select_anchors(entry.selections.to_vec())
14390 });
14391 });
14392 self.selection_history.mode = SelectionHistoryMode::Normal;
14393
14394 self.select_next_state = entry.select_next_state;
14395 self.select_prev_state = entry.select_prev_state;
14396 self.add_selections_state = entry.add_selections_state;
14397 }
14398 }
14399
14400 pub fn redo_selection(
14401 &mut self,
14402 _: &RedoSelection,
14403 window: &mut Window,
14404 cx: &mut Context<Self>,
14405 ) {
14406 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14407 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14408 self.selection_history.mode = SelectionHistoryMode::Redoing;
14409 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14410 this.end_selection(window, cx);
14411 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14412 s.select_anchors(entry.selections.to_vec())
14413 });
14414 });
14415 self.selection_history.mode = SelectionHistoryMode::Normal;
14416
14417 self.select_next_state = entry.select_next_state;
14418 self.select_prev_state = entry.select_prev_state;
14419 self.add_selections_state = entry.add_selections_state;
14420 }
14421 }
14422
14423 pub fn expand_excerpts(
14424 &mut self,
14425 action: &ExpandExcerpts,
14426 _: &mut Window,
14427 cx: &mut Context<Self>,
14428 ) {
14429 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14430 }
14431
14432 pub fn expand_excerpts_down(
14433 &mut self,
14434 action: &ExpandExcerptsDown,
14435 _: &mut Window,
14436 cx: &mut Context<Self>,
14437 ) {
14438 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14439 }
14440
14441 pub fn expand_excerpts_up(
14442 &mut self,
14443 action: &ExpandExcerptsUp,
14444 _: &mut Window,
14445 cx: &mut Context<Self>,
14446 ) {
14447 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14448 }
14449
14450 pub fn expand_excerpts_for_direction(
14451 &mut self,
14452 lines: u32,
14453 direction: ExpandExcerptDirection,
14454
14455 cx: &mut Context<Self>,
14456 ) {
14457 let selections = self.selections.disjoint_anchors();
14458
14459 let lines = if lines == 0 {
14460 EditorSettings::get_global(cx).expand_excerpt_lines
14461 } else {
14462 lines
14463 };
14464
14465 self.buffer.update(cx, |buffer, cx| {
14466 let snapshot = buffer.snapshot(cx);
14467 let mut excerpt_ids = selections
14468 .iter()
14469 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14470 .collect::<Vec<_>>();
14471 excerpt_ids.sort();
14472 excerpt_ids.dedup();
14473 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14474 })
14475 }
14476
14477 pub fn expand_excerpt(
14478 &mut self,
14479 excerpt: ExcerptId,
14480 direction: ExpandExcerptDirection,
14481 window: &mut Window,
14482 cx: &mut Context<Self>,
14483 ) {
14484 let current_scroll_position = self.scroll_position(cx);
14485 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14486 let mut should_scroll_up = false;
14487
14488 if direction == ExpandExcerptDirection::Down {
14489 let multi_buffer = self.buffer.read(cx);
14490 let snapshot = multi_buffer.snapshot(cx);
14491 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14492 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14493 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14494 let buffer_snapshot = buffer.read(cx).snapshot();
14495 let excerpt_end_row =
14496 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14497 let last_row = buffer_snapshot.max_point().row;
14498 let lines_below = last_row.saturating_sub(excerpt_end_row);
14499 should_scroll_up = lines_below >= lines_to_expand;
14500 }
14501 }
14502 }
14503 }
14504
14505 self.buffer.update(cx, |buffer, cx| {
14506 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14507 });
14508
14509 if should_scroll_up {
14510 let new_scroll_position =
14511 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14512 self.set_scroll_position(new_scroll_position, window, cx);
14513 }
14514 }
14515
14516 pub fn go_to_singleton_buffer_point(
14517 &mut self,
14518 point: Point,
14519 window: &mut Window,
14520 cx: &mut Context<Self>,
14521 ) {
14522 self.go_to_singleton_buffer_range(point..point, window, cx);
14523 }
14524
14525 pub fn go_to_singleton_buffer_range(
14526 &mut self,
14527 range: Range<Point>,
14528 window: &mut Window,
14529 cx: &mut Context<Self>,
14530 ) {
14531 let multibuffer = self.buffer().read(cx);
14532 let Some(buffer) = multibuffer.as_singleton() else {
14533 return;
14534 };
14535 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14536 return;
14537 };
14538 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14539 return;
14540 };
14541 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14542 s.select_anchor_ranges([start..end])
14543 });
14544 }
14545
14546 pub fn go_to_diagnostic(
14547 &mut self,
14548 _: &GoToDiagnostic,
14549 window: &mut Window,
14550 cx: &mut Context<Self>,
14551 ) {
14552 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14553 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14554 }
14555
14556 pub fn go_to_prev_diagnostic(
14557 &mut self,
14558 _: &GoToPreviousDiagnostic,
14559 window: &mut Window,
14560 cx: &mut Context<Self>,
14561 ) {
14562 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14563 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14564 }
14565
14566 pub fn go_to_diagnostic_impl(
14567 &mut self,
14568 direction: Direction,
14569 window: &mut Window,
14570 cx: &mut Context<Self>,
14571 ) {
14572 let buffer = self.buffer.read(cx).snapshot(cx);
14573 let selection = self.selections.newest::<usize>(cx);
14574
14575 let mut active_group_id = None;
14576 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14577 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14578 active_group_id = Some(active_group.group_id);
14579 }
14580 }
14581
14582 fn filtered(
14583 snapshot: EditorSnapshot,
14584 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14585 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14586 diagnostics
14587 .filter(|entry| entry.range.start != entry.range.end)
14588 .filter(|entry| !entry.diagnostic.is_unnecessary)
14589 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14590 }
14591
14592 let snapshot = self.snapshot(window, cx);
14593 let before = filtered(
14594 snapshot.clone(),
14595 buffer
14596 .diagnostics_in_range(0..selection.start)
14597 .filter(|entry| entry.range.start <= selection.start),
14598 );
14599 let after = filtered(
14600 snapshot,
14601 buffer
14602 .diagnostics_in_range(selection.start..buffer.len())
14603 .filter(|entry| entry.range.start >= selection.start),
14604 );
14605
14606 let mut found: Option<DiagnosticEntry<usize>> = None;
14607 if direction == Direction::Prev {
14608 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14609 {
14610 for diagnostic in prev_diagnostics.into_iter().rev() {
14611 if diagnostic.range.start != selection.start
14612 || active_group_id
14613 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14614 {
14615 found = Some(diagnostic);
14616 break 'outer;
14617 }
14618 }
14619 }
14620 } else {
14621 for diagnostic in after.chain(before) {
14622 if diagnostic.range.start != selection.start
14623 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14624 {
14625 found = Some(diagnostic);
14626 break;
14627 }
14628 }
14629 }
14630 let Some(next_diagnostic) = found else {
14631 return;
14632 };
14633
14634 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14635 return;
14636 };
14637 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14638 s.select_ranges(vec![
14639 next_diagnostic.range.start..next_diagnostic.range.start,
14640 ])
14641 });
14642 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14643 self.refresh_inline_completion(false, true, window, cx);
14644 }
14645
14646 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14647 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14648 let snapshot = self.snapshot(window, cx);
14649 let selection = self.selections.newest::<Point>(cx);
14650 self.go_to_hunk_before_or_after_position(
14651 &snapshot,
14652 selection.head(),
14653 Direction::Next,
14654 window,
14655 cx,
14656 );
14657 }
14658
14659 pub fn go_to_hunk_before_or_after_position(
14660 &mut self,
14661 snapshot: &EditorSnapshot,
14662 position: Point,
14663 direction: Direction,
14664 window: &mut Window,
14665 cx: &mut Context<Editor>,
14666 ) {
14667 let row = if direction == Direction::Next {
14668 self.hunk_after_position(snapshot, position)
14669 .map(|hunk| hunk.row_range.start)
14670 } else {
14671 self.hunk_before_position(snapshot, position)
14672 };
14673
14674 if let Some(row) = row {
14675 let destination = Point::new(row.0, 0);
14676 let autoscroll = Autoscroll::center();
14677
14678 self.unfold_ranges(&[destination..destination], false, false, cx);
14679 self.change_selections(Some(autoscroll), window, cx, |s| {
14680 s.select_ranges([destination..destination]);
14681 });
14682 }
14683 }
14684
14685 fn hunk_after_position(
14686 &mut self,
14687 snapshot: &EditorSnapshot,
14688 position: Point,
14689 ) -> Option<MultiBufferDiffHunk> {
14690 snapshot
14691 .buffer_snapshot
14692 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14693 .find(|hunk| hunk.row_range.start.0 > position.row)
14694 .or_else(|| {
14695 snapshot
14696 .buffer_snapshot
14697 .diff_hunks_in_range(Point::zero()..position)
14698 .find(|hunk| hunk.row_range.end.0 < position.row)
14699 })
14700 }
14701
14702 fn go_to_prev_hunk(
14703 &mut self,
14704 _: &GoToPreviousHunk,
14705 window: &mut Window,
14706 cx: &mut Context<Self>,
14707 ) {
14708 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14709 let snapshot = self.snapshot(window, cx);
14710 let selection = self.selections.newest::<Point>(cx);
14711 self.go_to_hunk_before_or_after_position(
14712 &snapshot,
14713 selection.head(),
14714 Direction::Prev,
14715 window,
14716 cx,
14717 );
14718 }
14719
14720 fn hunk_before_position(
14721 &mut self,
14722 snapshot: &EditorSnapshot,
14723 position: Point,
14724 ) -> Option<MultiBufferRow> {
14725 snapshot
14726 .buffer_snapshot
14727 .diff_hunk_before(position)
14728 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14729 }
14730
14731 fn go_to_next_change(
14732 &mut self,
14733 _: &GoToNextChange,
14734 window: &mut Window,
14735 cx: &mut Context<Self>,
14736 ) {
14737 if let Some(selections) = self
14738 .change_list
14739 .next_change(1, Direction::Next)
14740 .map(|s| s.to_vec())
14741 {
14742 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14743 let map = s.display_map();
14744 s.select_display_ranges(selections.iter().map(|a| {
14745 let point = a.to_display_point(&map);
14746 point..point
14747 }))
14748 })
14749 }
14750 }
14751
14752 fn go_to_previous_change(
14753 &mut self,
14754 _: &GoToPreviousChange,
14755 window: &mut Window,
14756 cx: &mut Context<Self>,
14757 ) {
14758 if let Some(selections) = self
14759 .change_list
14760 .next_change(1, Direction::Prev)
14761 .map(|s| s.to_vec())
14762 {
14763 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14764 let map = s.display_map();
14765 s.select_display_ranges(selections.iter().map(|a| {
14766 let point = a.to_display_point(&map);
14767 point..point
14768 }))
14769 })
14770 }
14771 }
14772
14773 fn go_to_line<T: 'static>(
14774 &mut self,
14775 position: Anchor,
14776 highlight_color: Option<Hsla>,
14777 window: &mut Window,
14778 cx: &mut Context<Self>,
14779 ) {
14780 let snapshot = self.snapshot(window, cx).display_snapshot;
14781 let position = position.to_point(&snapshot.buffer_snapshot);
14782 let start = snapshot
14783 .buffer_snapshot
14784 .clip_point(Point::new(position.row, 0), Bias::Left);
14785 let end = start + Point::new(1, 0);
14786 let start = snapshot.buffer_snapshot.anchor_before(start);
14787 let end = snapshot.buffer_snapshot.anchor_before(end);
14788
14789 self.highlight_rows::<T>(
14790 start..end,
14791 highlight_color
14792 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14793 Default::default(),
14794 cx,
14795 );
14796
14797 if self.buffer.read(cx).is_singleton() {
14798 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14799 }
14800 }
14801
14802 pub fn go_to_definition(
14803 &mut self,
14804 _: &GoToDefinition,
14805 window: &mut Window,
14806 cx: &mut Context<Self>,
14807 ) -> Task<Result<Navigated>> {
14808 let definition =
14809 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14810 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14811 cx.spawn_in(window, async move |editor, cx| {
14812 if definition.await? == Navigated::Yes {
14813 return Ok(Navigated::Yes);
14814 }
14815 match fallback_strategy {
14816 GoToDefinitionFallback::None => Ok(Navigated::No),
14817 GoToDefinitionFallback::FindAllReferences => {
14818 match editor.update_in(cx, |editor, window, cx| {
14819 editor.find_all_references(&FindAllReferences, window, cx)
14820 })? {
14821 Some(references) => references.await,
14822 None => Ok(Navigated::No),
14823 }
14824 }
14825 }
14826 })
14827 }
14828
14829 pub fn go_to_declaration(
14830 &mut self,
14831 _: &GoToDeclaration,
14832 window: &mut Window,
14833 cx: &mut Context<Self>,
14834 ) -> Task<Result<Navigated>> {
14835 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14836 }
14837
14838 pub fn go_to_declaration_split(
14839 &mut self,
14840 _: &GoToDeclaration,
14841 window: &mut Window,
14842 cx: &mut Context<Self>,
14843 ) -> Task<Result<Navigated>> {
14844 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14845 }
14846
14847 pub fn go_to_implementation(
14848 &mut self,
14849 _: &GoToImplementation,
14850 window: &mut Window,
14851 cx: &mut Context<Self>,
14852 ) -> Task<Result<Navigated>> {
14853 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14854 }
14855
14856 pub fn go_to_implementation_split(
14857 &mut self,
14858 _: &GoToImplementationSplit,
14859 window: &mut Window,
14860 cx: &mut Context<Self>,
14861 ) -> Task<Result<Navigated>> {
14862 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14863 }
14864
14865 pub fn go_to_type_definition(
14866 &mut self,
14867 _: &GoToTypeDefinition,
14868 window: &mut Window,
14869 cx: &mut Context<Self>,
14870 ) -> Task<Result<Navigated>> {
14871 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14872 }
14873
14874 pub fn go_to_definition_split(
14875 &mut self,
14876 _: &GoToDefinitionSplit,
14877 window: &mut Window,
14878 cx: &mut Context<Self>,
14879 ) -> Task<Result<Navigated>> {
14880 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14881 }
14882
14883 pub fn go_to_type_definition_split(
14884 &mut self,
14885 _: &GoToTypeDefinitionSplit,
14886 window: &mut Window,
14887 cx: &mut Context<Self>,
14888 ) -> Task<Result<Navigated>> {
14889 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14890 }
14891
14892 fn go_to_definition_of_kind(
14893 &mut self,
14894 kind: GotoDefinitionKind,
14895 split: bool,
14896 window: &mut Window,
14897 cx: &mut Context<Self>,
14898 ) -> Task<Result<Navigated>> {
14899 let Some(provider) = self.semantics_provider.clone() else {
14900 return Task::ready(Ok(Navigated::No));
14901 };
14902 let head = self.selections.newest::<usize>(cx).head();
14903 let buffer = self.buffer.read(cx);
14904 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14905 text_anchor
14906 } else {
14907 return Task::ready(Ok(Navigated::No));
14908 };
14909
14910 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14911 return Task::ready(Ok(Navigated::No));
14912 };
14913
14914 cx.spawn_in(window, async move |editor, cx| {
14915 let definitions = definitions.await?;
14916 let navigated = editor
14917 .update_in(cx, |editor, window, cx| {
14918 editor.navigate_to_hover_links(
14919 Some(kind),
14920 definitions
14921 .into_iter()
14922 .filter(|location| {
14923 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14924 })
14925 .map(HoverLink::Text)
14926 .collect::<Vec<_>>(),
14927 split,
14928 window,
14929 cx,
14930 )
14931 })?
14932 .await?;
14933 anyhow::Ok(navigated)
14934 })
14935 }
14936
14937 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14938 let selection = self.selections.newest_anchor();
14939 let head = selection.head();
14940 let tail = selection.tail();
14941
14942 let Some((buffer, start_position)) =
14943 self.buffer.read(cx).text_anchor_for_position(head, cx)
14944 else {
14945 return;
14946 };
14947
14948 let end_position = if head != tail {
14949 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14950 return;
14951 };
14952 Some(pos)
14953 } else {
14954 None
14955 };
14956
14957 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14958 let url = if let Some(end_pos) = end_position {
14959 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14960 } else {
14961 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14962 };
14963
14964 if let Some(url) = url {
14965 editor.update(cx, |_, cx| {
14966 cx.open_url(&url);
14967 })
14968 } else {
14969 Ok(())
14970 }
14971 });
14972
14973 url_finder.detach();
14974 }
14975
14976 pub fn open_selected_filename(
14977 &mut self,
14978 _: &OpenSelectedFilename,
14979 window: &mut Window,
14980 cx: &mut Context<Self>,
14981 ) {
14982 let Some(workspace) = self.workspace() else {
14983 return;
14984 };
14985
14986 let position = self.selections.newest_anchor().head();
14987
14988 let Some((buffer, buffer_position)) =
14989 self.buffer.read(cx).text_anchor_for_position(position, cx)
14990 else {
14991 return;
14992 };
14993
14994 let project = self.project.clone();
14995
14996 cx.spawn_in(window, async move |_, cx| {
14997 let result = find_file(&buffer, project, buffer_position, cx).await;
14998
14999 if let Some((_, path)) = result {
15000 workspace
15001 .update_in(cx, |workspace, window, cx| {
15002 workspace.open_resolved_path(path, window, cx)
15003 })?
15004 .await?;
15005 }
15006 anyhow::Ok(())
15007 })
15008 .detach();
15009 }
15010
15011 pub(crate) fn navigate_to_hover_links(
15012 &mut self,
15013 kind: Option<GotoDefinitionKind>,
15014 mut definitions: Vec<HoverLink>,
15015 split: bool,
15016 window: &mut Window,
15017 cx: &mut Context<Editor>,
15018 ) -> Task<Result<Navigated>> {
15019 // If there is one definition, just open it directly
15020 if definitions.len() == 1 {
15021 let definition = definitions.pop().unwrap();
15022
15023 enum TargetTaskResult {
15024 Location(Option<Location>),
15025 AlreadyNavigated,
15026 }
15027
15028 let target_task = match definition {
15029 HoverLink::Text(link) => {
15030 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15031 }
15032 HoverLink::InlayHint(lsp_location, server_id) => {
15033 let computation =
15034 self.compute_target_location(lsp_location, server_id, window, cx);
15035 cx.background_spawn(async move {
15036 let location = computation.await?;
15037 Ok(TargetTaskResult::Location(location))
15038 })
15039 }
15040 HoverLink::Url(url) => {
15041 cx.open_url(&url);
15042 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15043 }
15044 HoverLink::File(path) => {
15045 if let Some(workspace) = self.workspace() {
15046 cx.spawn_in(window, async move |_, cx| {
15047 workspace
15048 .update_in(cx, |workspace, window, cx| {
15049 workspace.open_resolved_path(path, window, cx)
15050 })?
15051 .await
15052 .map(|_| TargetTaskResult::AlreadyNavigated)
15053 })
15054 } else {
15055 Task::ready(Ok(TargetTaskResult::Location(None)))
15056 }
15057 }
15058 };
15059 cx.spawn_in(window, async move |editor, cx| {
15060 let target = match target_task.await.context("target resolution task")? {
15061 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15062 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15063 TargetTaskResult::Location(Some(target)) => target,
15064 };
15065
15066 editor.update_in(cx, |editor, window, cx| {
15067 let Some(workspace) = editor.workspace() else {
15068 return Navigated::No;
15069 };
15070 let pane = workspace.read(cx).active_pane().clone();
15071
15072 let range = target.range.to_point(target.buffer.read(cx));
15073 let range = editor.range_for_match(&range);
15074 let range = collapse_multiline_range(range);
15075
15076 if !split
15077 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15078 {
15079 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15080 } else {
15081 window.defer(cx, move |window, cx| {
15082 let target_editor: Entity<Self> =
15083 workspace.update(cx, |workspace, cx| {
15084 let pane = if split {
15085 workspace.adjacent_pane(window, cx)
15086 } else {
15087 workspace.active_pane().clone()
15088 };
15089
15090 workspace.open_project_item(
15091 pane,
15092 target.buffer.clone(),
15093 true,
15094 true,
15095 window,
15096 cx,
15097 )
15098 });
15099 target_editor.update(cx, |target_editor, cx| {
15100 // When selecting a definition in a different buffer, disable the nav history
15101 // to avoid creating a history entry at the previous cursor location.
15102 pane.update(cx, |pane, _| pane.disable_history());
15103 target_editor.go_to_singleton_buffer_range(range, window, cx);
15104 pane.update(cx, |pane, _| pane.enable_history());
15105 });
15106 });
15107 }
15108 Navigated::Yes
15109 })
15110 })
15111 } else if !definitions.is_empty() {
15112 cx.spawn_in(window, async move |editor, cx| {
15113 let (title, location_tasks, workspace) = editor
15114 .update_in(cx, |editor, window, cx| {
15115 let tab_kind = match kind {
15116 Some(GotoDefinitionKind::Implementation) => "Implementations",
15117 _ => "Definitions",
15118 };
15119 let title = definitions
15120 .iter()
15121 .find_map(|definition| match definition {
15122 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15123 let buffer = origin.buffer.read(cx);
15124 format!(
15125 "{} for {}",
15126 tab_kind,
15127 buffer
15128 .text_for_range(origin.range.clone())
15129 .collect::<String>()
15130 )
15131 }),
15132 HoverLink::InlayHint(_, _) => None,
15133 HoverLink::Url(_) => None,
15134 HoverLink::File(_) => None,
15135 })
15136 .unwrap_or(tab_kind.to_string());
15137 let location_tasks = definitions
15138 .into_iter()
15139 .map(|definition| match definition {
15140 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15141 HoverLink::InlayHint(lsp_location, server_id) => editor
15142 .compute_target_location(lsp_location, server_id, window, cx),
15143 HoverLink::Url(_) => Task::ready(Ok(None)),
15144 HoverLink::File(_) => Task::ready(Ok(None)),
15145 })
15146 .collect::<Vec<_>>();
15147 (title, location_tasks, editor.workspace().clone())
15148 })
15149 .context("location tasks preparation")?;
15150
15151 let locations: Vec<Location> = future::join_all(location_tasks)
15152 .await
15153 .into_iter()
15154 .filter_map(|location| location.transpose())
15155 .collect::<Result<_>>()
15156 .context("location tasks")?;
15157
15158 if locations.is_empty() {
15159 return Ok(Navigated::No);
15160 }
15161
15162 let Some(workspace) = workspace else {
15163 return Ok(Navigated::No);
15164 };
15165
15166 let opened = workspace
15167 .update_in(cx, |workspace, window, cx| {
15168 Self::open_locations_in_multibuffer(
15169 workspace,
15170 locations,
15171 title,
15172 split,
15173 MultibufferSelectionMode::First,
15174 window,
15175 cx,
15176 )
15177 })
15178 .ok();
15179
15180 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15181 })
15182 } else {
15183 Task::ready(Ok(Navigated::No))
15184 }
15185 }
15186
15187 fn compute_target_location(
15188 &self,
15189 lsp_location: lsp::Location,
15190 server_id: LanguageServerId,
15191 window: &mut Window,
15192 cx: &mut Context<Self>,
15193 ) -> Task<anyhow::Result<Option<Location>>> {
15194 let Some(project) = self.project.clone() else {
15195 return Task::ready(Ok(None));
15196 };
15197
15198 cx.spawn_in(window, async move |editor, cx| {
15199 let location_task = editor.update(cx, |_, cx| {
15200 project.update(cx, |project, cx| {
15201 let language_server_name = project
15202 .language_server_statuses(cx)
15203 .find(|(id, _)| server_id == *id)
15204 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15205 language_server_name.map(|language_server_name| {
15206 project.open_local_buffer_via_lsp(
15207 lsp_location.uri.clone(),
15208 server_id,
15209 language_server_name,
15210 cx,
15211 )
15212 })
15213 })
15214 })?;
15215 let location = match location_task {
15216 Some(task) => Some({
15217 let target_buffer_handle = task.await.context("open local buffer")?;
15218 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15219 let target_start = target_buffer
15220 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15221 let target_end = target_buffer
15222 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15223 target_buffer.anchor_after(target_start)
15224 ..target_buffer.anchor_before(target_end)
15225 })?;
15226 Location {
15227 buffer: target_buffer_handle,
15228 range,
15229 }
15230 }),
15231 None => None,
15232 };
15233 Ok(location)
15234 })
15235 }
15236
15237 pub fn find_all_references(
15238 &mut self,
15239 _: &FindAllReferences,
15240 window: &mut Window,
15241 cx: &mut Context<Self>,
15242 ) -> Option<Task<Result<Navigated>>> {
15243 let selection = self.selections.newest::<usize>(cx);
15244 let multi_buffer = self.buffer.read(cx);
15245 let head = selection.head();
15246
15247 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15248 let head_anchor = multi_buffer_snapshot.anchor_at(
15249 head,
15250 if head < selection.tail() {
15251 Bias::Right
15252 } else {
15253 Bias::Left
15254 },
15255 );
15256
15257 match self
15258 .find_all_references_task_sources
15259 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15260 {
15261 Ok(_) => {
15262 log::info!(
15263 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15264 );
15265 return None;
15266 }
15267 Err(i) => {
15268 self.find_all_references_task_sources.insert(i, head_anchor);
15269 }
15270 }
15271
15272 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15273 let workspace = self.workspace()?;
15274 let project = workspace.read(cx).project().clone();
15275 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15276 Some(cx.spawn_in(window, async move |editor, cx| {
15277 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15278 if let Ok(i) = editor
15279 .find_all_references_task_sources
15280 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15281 {
15282 editor.find_all_references_task_sources.remove(i);
15283 }
15284 });
15285
15286 let locations = references.await?;
15287 if locations.is_empty() {
15288 return anyhow::Ok(Navigated::No);
15289 }
15290
15291 workspace.update_in(cx, |workspace, window, cx| {
15292 let title = locations
15293 .first()
15294 .as_ref()
15295 .map(|location| {
15296 let buffer = location.buffer.read(cx);
15297 format!(
15298 "References to `{}`",
15299 buffer
15300 .text_for_range(location.range.clone())
15301 .collect::<String>()
15302 )
15303 })
15304 .unwrap();
15305 Self::open_locations_in_multibuffer(
15306 workspace,
15307 locations,
15308 title,
15309 false,
15310 MultibufferSelectionMode::First,
15311 window,
15312 cx,
15313 );
15314 Navigated::Yes
15315 })
15316 }))
15317 }
15318
15319 /// Opens a multibuffer with the given project locations in it
15320 pub fn open_locations_in_multibuffer(
15321 workspace: &mut Workspace,
15322 mut locations: Vec<Location>,
15323 title: String,
15324 split: bool,
15325 multibuffer_selection_mode: MultibufferSelectionMode,
15326 window: &mut Window,
15327 cx: &mut Context<Workspace>,
15328 ) {
15329 if locations.is_empty() {
15330 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
15331 return;
15332 }
15333
15334 // If there are multiple definitions, open them in a multibuffer
15335 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15336 let mut locations = locations.into_iter().peekable();
15337 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15338 let capability = workspace.project().read(cx).capability();
15339
15340 let excerpt_buffer = cx.new(|cx| {
15341 let mut multibuffer = MultiBuffer::new(capability);
15342 while let Some(location) = locations.next() {
15343 let buffer = location.buffer.read(cx);
15344 let mut ranges_for_buffer = Vec::new();
15345 let range = location.range.to_point(buffer);
15346 ranges_for_buffer.push(range.clone());
15347
15348 while let Some(next_location) = locations.peek() {
15349 if next_location.buffer == location.buffer {
15350 ranges_for_buffer.push(next_location.range.to_point(buffer));
15351 locations.next();
15352 } else {
15353 break;
15354 }
15355 }
15356
15357 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15358 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15359 PathKey::for_buffer(&location.buffer, cx),
15360 location.buffer.clone(),
15361 ranges_for_buffer,
15362 DEFAULT_MULTIBUFFER_CONTEXT,
15363 cx,
15364 );
15365 ranges.extend(new_ranges)
15366 }
15367
15368 multibuffer.with_title(title)
15369 });
15370
15371 let editor = cx.new(|cx| {
15372 Editor::for_multibuffer(
15373 excerpt_buffer,
15374 Some(workspace.project().clone()),
15375 window,
15376 cx,
15377 )
15378 });
15379 editor.update(cx, |editor, cx| {
15380 match multibuffer_selection_mode {
15381 MultibufferSelectionMode::First => {
15382 if let Some(first_range) = ranges.first() {
15383 editor.change_selections(None, window, cx, |selections| {
15384 selections.clear_disjoint();
15385 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
15386 });
15387 }
15388 editor.highlight_background::<Self>(
15389 &ranges,
15390 |theme| theme.colors().editor_highlighted_line_background,
15391 cx,
15392 );
15393 }
15394 MultibufferSelectionMode::All => {
15395 editor.change_selections(None, window, cx, |selections| {
15396 selections.clear_disjoint();
15397 selections.select_anchor_ranges(ranges);
15398 });
15399 }
15400 }
15401 editor.register_buffers_with_language_servers(cx);
15402 });
15403
15404 let item = Box::new(editor);
15405 let item_id = item.item_id();
15406
15407 if split {
15408 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15409 } else {
15410 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15411 let (preview_item_id, preview_item_idx) =
15412 workspace.active_pane().read_with(cx, |pane, _| {
15413 (pane.preview_item_id(), pane.preview_item_idx())
15414 });
15415
15416 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15417
15418 if let Some(preview_item_id) = preview_item_id {
15419 workspace.active_pane().update(cx, |pane, cx| {
15420 pane.remove_item(preview_item_id, false, false, window, cx);
15421 });
15422 }
15423 } else {
15424 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15425 }
15426 }
15427 workspace.active_pane().update(cx, |pane, cx| {
15428 pane.set_preview_item_id(Some(item_id), cx);
15429 });
15430 }
15431
15432 pub fn rename(
15433 &mut self,
15434 _: &Rename,
15435 window: &mut Window,
15436 cx: &mut Context<Self>,
15437 ) -> Option<Task<Result<()>>> {
15438 use language::ToOffset as _;
15439
15440 let provider = self.semantics_provider.clone()?;
15441 let selection = self.selections.newest_anchor().clone();
15442 let (cursor_buffer, cursor_buffer_position) = self
15443 .buffer
15444 .read(cx)
15445 .text_anchor_for_position(selection.head(), cx)?;
15446 let (tail_buffer, cursor_buffer_position_end) = self
15447 .buffer
15448 .read(cx)
15449 .text_anchor_for_position(selection.tail(), cx)?;
15450 if tail_buffer != cursor_buffer {
15451 return None;
15452 }
15453
15454 let snapshot = cursor_buffer.read(cx).snapshot();
15455 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15456 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15457 let prepare_rename = provider
15458 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15459 .unwrap_or_else(|| Task::ready(Ok(None)));
15460 drop(snapshot);
15461
15462 Some(cx.spawn_in(window, async move |this, cx| {
15463 let rename_range = if let Some(range) = prepare_rename.await? {
15464 Some(range)
15465 } else {
15466 this.update(cx, |this, cx| {
15467 let buffer = this.buffer.read(cx).snapshot(cx);
15468 let mut buffer_highlights = this
15469 .document_highlights_for_position(selection.head(), &buffer)
15470 .filter(|highlight| {
15471 highlight.start.excerpt_id == selection.head().excerpt_id
15472 && highlight.end.excerpt_id == selection.head().excerpt_id
15473 });
15474 buffer_highlights
15475 .next()
15476 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15477 })?
15478 };
15479 if let Some(rename_range) = rename_range {
15480 this.update_in(cx, |this, window, cx| {
15481 let snapshot = cursor_buffer.read(cx).snapshot();
15482 let rename_buffer_range = rename_range.to_offset(&snapshot);
15483 let cursor_offset_in_rename_range =
15484 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15485 let cursor_offset_in_rename_range_end =
15486 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15487
15488 this.take_rename(false, window, cx);
15489 let buffer = this.buffer.read(cx).read(cx);
15490 let cursor_offset = selection.head().to_offset(&buffer);
15491 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15492 let rename_end = rename_start + rename_buffer_range.len();
15493 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15494 let mut old_highlight_id = None;
15495 let old_name: Arc<str> = buffer
15496 .chunks(rename_start..rename_end, true)
15497 .map(|chunk| {
15498 if old_highlight_id.is_none() {
15499 old_highlight_id = chunk.syntax_highlight_id;
15500 }
15501 chunk.text
15502 })
15503 .collect::<String>()
15504 .into();
15505
15506 drop(buffer);
15507
15508 // Position the selection in the rename editor so that it matches the current selection.
15509 this.show_local_selections = false;
15510 let rename_editor = cx.new(|cx| {
15511 let mut editor = Editor::single_line(window, cx);
15512 editor.buffer.update(cx, |buffer, cx| {
15513 buffer.edit([(0..0, old_name.clone())], None, cx)
15514 });
15515 let rename_selection_range = match cursor_offset_in_rename_range
15516 .cmp(&cursor_offset_in_rename_range_end)
15517 {
15518 Ordering::Equal => {
15519 editor.select_all(&SelectAll, window, cx);
15520 return editor;
15521 }
15522 Ordering::Less => {
15523 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15524 }
15525 Ordering::Greater => {
15526 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15527 }
15528 };
15529 if rename_selection_range.end > old_name.len() {
15530 editor.select_all(&SelectAll, window, cx);
15531 } else {
15532 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15533 s.select_ranges([rename_selection_range]);
15534 });
15535 }
15536 editor
15537 });
15538 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15539 if e == &EditorEvent::Focused {
15540 cx.emit(EditorEvent::FocusedIn)
15541 }
15542 })
15543 .detach();
15544
15545 let write_highlights = this
15546 .clear_background_highlights::<DocumentHighlightWrite>(cx)
15547 .unwrap_or_default();
15548 let read_highlights = this
15549 .clear_background_highlights::<DocumentHighlightRead>(cx)
15550 .unwrap_or_default();
15551 let ranges = write_highlights
15552 .iter()
15553 .chain(read_highlights.iter())
15554 .cloned()
15555 .map(|highlight| {
15556 (
15557 highlight.range,
15558 HighlightStyle {
15559 fade_out: Some(0.6),
15560 ..Default::default()
15561 },
15562 )
15563 })
15564 .collect();
15565
15566 this.highlight_text::<Rename>(ranges, cx);
15567 let rename_focus_handle = rename_editor.focus_handle(cx);
15568 window.focus(&rename_focus_handle);
15569 let block_id = this.insert_blocks(
15570 [BlockProperties {
15571 style: BlockStyle::Flex,
15572 placement: BlockPlacement::Below(range.start),
15573 height: Some(1),
15574 render: Arc::new({
15575 let rename_editor = rename_editor.clone();
15576 move |cx: &mut BlockContext| {
15577 let mut text_style = cx.editor_style.text.clone();
15578 if let Some(highlight_style) = old_highlight_id
15579 .and_then(|h| h.style(&cx.editor_style.syntax))
15580 {
15581 text_style = text_style.highlight(highlight_style);
15582 }
15583 div()
15584 .block_mouse_except_scroll()
15585 .pl(cx.anchor_x)
15586 .child(EditorElement::new(
15587 &rename_editor,
15588 EditorStyle {
15589 background: cx.theme().system().transparent,
15590 local_player: cx.editor_style.local_player,
15591 text: text_style,
15592 scrollbar_width: cx.editor_style.scrollbar_width,
15593 syntax: cx.editor_style.syntax.clone(),
15594 status: cx.editor_style.status.clone(),
15595 inlay_hints_style: HighlightStyle {
15596 font_weight: Some(FontWeight::BOLD),
15597 ..make_inlay_hints_style(cx.app)
15598 },
15599 inline_completion_styles: make_suggestion_styles(
15600 cx.app,
15601 ),
15602 ..EditorStyle::default()
15603 },
15604 ))
15605 .into_any_element()
15606 }
15607 }),
15608 priority: 0,
15609 render_in_minimap: true,
15610 }],
15611 Some(Autoscroll::fit()),
15612 cx,
15613 )[0];
15614 this.pending_rename = Some(RenameState {
15615 range,
15616 old_name,
15617 editor: rename_editor,
15618 block_id,
15619 });
15620 })?;
15621 }
15622
15623 Ok(())
15624 }))
15625 }
15626
15627 pub fn confirm_rename(
15628 &mut self,
15629 _: &ConfirmRename,
15630 window: &mut Window,
15631 cx: &mut Context<Self>,
15632 ) -> Option<Task<Result<()>>> {
15633 let rename = self.take_rename(false, window, cx)?;
15634 let workspace = self.workspace()?.downgrade();
15635 let (buffer, start) = self
15636 .buffer
15637 .read(cx)
15638 .text_anchor_for_position(rename.range.start, cx)?;
15639 let (end_buffer, _) = self
15640 .buffer
15641 .read(cx)
15642 .text_anchor_for_position(rename.range.end, cx)?;
15643 if buffer != end_buffer {
15644 return None;
15645 }
15646
15647 let old_name = rename.old_name;
15648 let new_name = rename.editor.read(cx).text(cx);
15649
15650 let rename = self.semantics_provider.as_ref()?.perform_rename(
15651 &buffer,
15652 start,
15653 new_name.clone(),
15654 cx,
15655 )?;
15656
15657 Some(cx.spawn_in(window, async move |editor, cx| {
15658 let project_transaction = rename.await?;
15659 Self::open_project_transaction(
15660 &editor,
15661 workspace,
15662 project_transaction,
15663 format!("Rename: {} → {}", old_name, new_name),
15664 cx,
15665 )
15666 .await?;
15667
15668 editor.update(cx, |editor, cx| {
15669 editor.refresh_document_highlights(cx);
15670 })?;
15671 Ok(())
15672 }))
15673 }
15674
15675 fn take_rename(
15676 &mut self,
15677 moving_cursor: bool,
15678 window: &mut Window,
15679 cx: &mut Context<Self>,
15680 ) -> Option<RenameState> {
15681 let rename = self.pending_rename.take()?;
15682 if rename.editor.focus_handle(cx).is_focused(window) {
15683 window.focus(&self.focus_handle);
15684 }
15685
15686 self.remove_blocks(
15687 [rename.block_id].into_iter().collect(),
15688 Some(Autoscroll::fit()),
15689 cx,
15690 );
15691 self.clear_highlights::<Rename>(cx);
15692 self.show_local_selections = true;
15693
15694 if moving_cursor {
15695 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15696 editor.selections.newest::<usize>(cx).head()
15697 });
15698
15699 // Update the selection to match the position of the selection inside
15700 // the rename editor.
15701 let snapshot = self.buffer.read(cx).read(cx);
15702 let rename_range = rename.range.to_offset(&snapshot);
15703 let cursor_in_editor = snapshot
15704 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15705 .min(rename_range.end);
15706 drop(snapshot);
15707
15708 self.change_selections(None, window, cx, |s| {
15709 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15710 });
15711 } else {
15712 self.refresh_document_highlights(cx);
15713 }
15714
15715 Some(rename)
15716 }
15717
15718 pub fn pending_rename(&self) -> Option<&RenameState> {
15719 self.pending_rename.as_ref()
15720 }
15721
15722 fn format(
15723 &mut self,
15724 _: &Format,
15725 window: &mut Window,
15726 cx: &mut Context<Self>,
15727 ) -> Option<Task<Result<()>>> {
15728 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15729
15730 let project = match &self.project {
15731 Some(project) => project.clone(),
15732 None => return None,
15733 };
15734
15735 Some(self.perform_format(
15736 project,
15737 FormatTrigger::Manual,
15738 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
15739 window,
15740 cx,
15741 ))
15742 }
15743
15744 fn format_selections(
15745 &mut self,
15746 _: &FormatSelections,
15747 window: &mut Window,
15748 cx: &mut Context<Self>,
15749 ) -> Option<Task<Result<()>>> {
15750 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15751
15752 let project = match &self.project {
15753 Some(project) => project.clone(),
15754 None => return None,
15755 };
15756
15757 let ranges = self
15758 .selections
15759 .all_adjusted(cx)
15760 .into_iter()
15761 .map(|selection| selection.range())
15762 .collect_vec();
15763
15764 Some(self.perform_format(
15765 project,
15766 FormatTrigger::Manual,
15767 FormatTarget::Ranges(ranges),
15768 window,
15769 cx,
15770 ))
15771 }
15772
15773 fn perform_format(
15774 &mut self,
15775 project: Entity<Project>,
15776 trigger: FormatTrigger,
15777 target: FormatTarget,
15778 window: &mut Window,
15779 cx: &mut Context<Self>,
15780 ) -> Task<Result<()>> {
15781 let buffer = self.buffer.clone();
15782 let (buffers, target) = match target {
15783 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
15784 FormatTarget::Ranges(selection_ranges) => {
15785 let multi_buffer = buffer.read(cx);
15786 let snapshot = multi_buffer.read(cx);
15787 let mut buffers = HashSet::default();
15788 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15789 BTreeMap::new();
15790 for selection_range in selection_ranges {
15791 for (buffer, buffer_range, _) in
15792 snapshot.range_to_buffer_ranges(selection_range)
15793 {
15794 let buffer_id = buffer.remote_id();
15795 let start = buffer.anchor_before(buffer_range.start);
15796 let end = buffer.anchor_after(buffer_range.end);
15797 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15798 buffer_id_to_ranges
15799 .entry(buffer_id)
15800 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15801 .or_insert_with(|| vec![start..end]);
15802 }
15803 }
15804 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15805 }
15806 };
15807
15808 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15809 let selections_prev = transaction_id_prev
15810 .and_then(|transaction_id_prev| {
15811 // default to selections as they were after the last edit, if we have them,
15812 // instead of how they are now.
15813 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15814 // will take you back to where you made the last edit, instead of staying where you scrolled
15815 self.selection_history
15816 .transaction(transaction_id_prev)
15817 .map(|t| t.0.clone())
15818 })
15819 .unwrap_or_else(|| {
15820 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15821 self.selections.disjoint_anchors()
15822 });
15823
15824 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15825 let format = project.update(cx, |project, cx| {
15826 project.format(buffers, target, true, trigger, cx)
15827 });
15828
15829 cx.spawn_in(window, async move |editor, cx| {
15830 let transaction = futures::select_biased! {
15831 transaction = format.log_err().fuse() => transaction,
15832 () = timeout => {
15833 log::warn!("timed out waiting for formatting");
15834 None
15835 }
15836 };
15837
15838 buffer
15839 .update(cx, |buffer, cx| {
15840 if let Some(transaction) = transaction {
15841 if !buffer.is_singleton() {
15842 buffer.push_transaction(&transaction.0, cx);
15843 }
15844 }
15845 cx.notify();
15846 })
15847 .ok();
15848
15849 if let Some(transaction_id_now) =
15850 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15851 {
15852 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15853 if has_new_transaction {
15854 _ = editor.update(cx, |editor, _| {
15855 editor
15856 .selection_history
15857 .insert_transaction(transaction_id_now, selections_prev);
15858 });
15859 }
15860 }
15861
15862 Ok(())
15863 })
15864 }
15865
15866 fn organize_imports(
15867 &mut self,
15868 _: &OrganizeImports,
15869 window: &mut Window,
15870 cx: &mut Context<Self>,
15871 ) -> Option<Task<Result<()>>> {
15872 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15873 let project = match &self.project {
15874 Some(project) => project.clone(),
15875 None => return None,
15876 };
15877 Some(self.perform_code_action_kind(
15878 project,
15879 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15880 window,
15881 cx,
15882 ))
15883 }
15884
15885 fn perform_code_action_kind(
15886 &mut self,
15887 project: Entity<Project>,
15888 kind: CodeActionKind,
15889 window: &mut Window,
15890 cx: &mut Context<Self>,
15891 ) -> Task<Result<()>> {
15892 let buffer = self.buffer.clone();
15893 let buffers = buffer.read(cx).all_buffers();
15894 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15895 let apply_action = project.update(cx, |project, cx| {
15896 project.apply_code_action_kind(buffers, kind, true, cx)
15897 });
15898 cx.spawn_in(window, async move |_, cx| {
15899 let transaction = futures::select_biased! {
15900 () = timeout => {
15901 log::warn!("timed out waiting for executing code action");
15902 None
15903 }
15904 transaction = apply_action.log_err().fuse() => transaction,
15905 };
15906 buffer
15907 .update(cx, |buffer, cx| {
15908 // check if we need this
15909 if let Some(transaction) = transaction {
15910 if !buffer.is_singleton() {
15911 buffer.push_transaction(&transaction.0, cx);
15912 }
15913 }
15914 cx.notify();
15915 })
15916 .ok();
15917 Ok(())
15918 })
15919 }
15920
15921 fn restart_language_server(
15922 &mut self,
15923 _: &RestartLanguageServer,
15924 _: &mut Window,
15925 cx: &mut Context<Self>,
15926 ) {
15927 if let Some(project) = self.project.clone() {
15928 self.buffer.update(cx, |multi_buffer, cx| {
15929 project.update(cx, |project, cx| {
15930 project.restart_language_servers_for_buffers(
15931 multi_buffer.all_buffers().into_iter().collect(),
15932 cx,
15933 );
15934 });
15935 })
15936 }
15937 }
15938
15939 fn stop_language_server(
15940 &mut self,
15941 _: &StopLanguageServer,
15942 _: &mut Window,
15943 cx: &mut Context<Self>,
15944 ) {
15945 if let Some(project) = self.project.clone() {
15946 self.buffer.update(cx, |multi_buffer, cx| {
15947 project.update(cx, |project, cx| {
15948 project.stop_language_servers_for_buffers(
15949 multi_buffer.all_buffers().into_iter().collect(),
15950 cx,
15951 );
15952 cx.emit(project::Event::RefreshInlayHints);
15953 });
15954 });
15955 }
15956 }
15957
15958 fn cancel_language_server_work(
15959 workspace: &mut Workspace,
15960 _: &actions::CancelLanguageServerWork,
15961 _: &mut Window,
15962 cx: &mut Context<Workspace>,
15963 ) {
15964 let project = workspace.project();
15965 let buffers = workspace
15966 .active_item(cx)
15967 .and_then(|item| item.act_as::<Editor>(cx))
15968 .map_or(HashSet::default(), |editor| {
15969 editor.read(cx).buffer.read(cx).all_buffers()
15970 });
15971 project.update(cx, |project, cx| {
15972 project.cancel_language_server_work_for_buffers(buffers, cx);
15973 });
15974 }
15975
15976 fn show_character_palette(
15977 &mut self,
15978 _: &ShowCharacterPalette,
15979 window: &mut Window,
15980 _: &mut Context<Self>,
15981 ) {
15982 window.show_character_palette();
15983 }
15984
15985 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15986 if self.mode.is_minimap() {
15987 return;
15988 }
15989
15990 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15991 let buffer = self.buffer.read(cx).snapshot(cx);
15992 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15993 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15994 let is_valid = buffer
15995 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15996 .any(|entry| {
15997 entry.diagnostic.is_primary
15998 && !entry.range.is_empty()
15999 && entry.range.start == primary_range_start
16000 && entry.diagnostic.message == active_diagnostics.active_message
16001 });
16002
16003 if !is_valid {
16004 self.dismiss_diagnostics(cx);
16005 }
16006 }
16007 }
16008
16009 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16010 match &self.active_diagnostics {
16011 ActiveDiagnostic::Group(group) => Some(group),
16012 _ => None,
16013 }
16014 }
16015
16016 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16017 self.dismiss_diagnostics(cx);
16018 self.active_diagnostics = ActiveDiagnostic::All;
16019 }
16020
16021 fn activate_diagnostics(
16022 &mut self,
16023 buffer_id: BufferId,
16024 diagnostic: DiagnosticEntry<usize>,
16025 window: &mut Window,
16026 cx: &mut Context<Self>,
16027 ) {
16028 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16029 return;
16030 }
16031 self.dismiss_diagnostics(cx);
16032 let snapshot = self.snapshot(window, cx);
16033 let buffer = self.buffer.read(cx).snapshot(cx);
16034 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16035 return;
16036 };
16037
16038 let diagnostic_group = buffer
16039 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16040 .collect::<Vec<_>>();
16041
16042 let blocks =
16043 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16044
16045 let blocks = self.display_map.update(cx, |display_map, cx| {
16046 display_map.insert_blocks(blocks, cx).into_iter().collect()
16047 });
16048 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16049 active_range: buffer.anchor_before(diagnostic.range.start)
16050 ..buffer.anchor_after(diagnostic.range.end),
16051 active_message: diagnostic.diagnostic.message.clone(),
16052 group_id: diagnostic.diagnostic.group_id,
16053 blocks,
16054 });
16055 cx.notify();
16056 }
16057
16058 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16059 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16060 return;
16061 };
16062
16063 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16064 if let ActiveDiagnostic::Group(group) = prev {
16065 self.display_map.update(cx, |display_map, cx| {
16066 display_map.remove_blocks(group.blocks, cx);
16067 });
16068 cx.notify();
16069 }
16070 }
16071
16072 /// Disable inline diagnostics rendering for this editor.
16073 pub fn disable_inline_diagnostics(&mut self) {
16074 self.inline_diagnostics_enabled = false;
16075 self.inline_diagnostics_update = Task::ready(());
16076 self.inline_diagnostics.clear();
16077 }
16078
16079 pub fn diagnostics_enabled(&self) -> bool {
16080 self.mode.is_full()
16081 }
16082
16083 pub fn inline_diagnostics_enabled(&self) -> bool {
16084 self.diagnostics_enabled() && self.inline_diagnostics_enabled
16085 }
16086
16087 pub fn show_inline_diagnostics(&self) -> bool {
16088 self.show_inline_diagnostics
16089 }
16090
16091 pub fn toggle_inline_diagnostics(
16092 &mut self,
16093 _: &ToggleInlineDiagnostics,
16094 window: &mut Window,
16095 cx: &mut Context<Editor>,
16096 ) {
16097 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16098 self.refresh_inline_diagnostics(false, window, cx);
16099 }
16100
16101 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16102 self.diagnostics_max_severity = severity;
16103 self.display_map.update(cx, |display_map, _| {
16104 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16105 });
16106 }
16107
16108 pub fn toggle_diagnostics(
16109 &mut self,
16110 _: &ToggleDiagnostics,
16111 window: &mut Window,
16112 cx: &mut Context<Editor>,
16113 ) {
16114 if !self.diagnostics_enabled() {
16115 return;
16116 }
16117
16118 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16119 EditorSettings::get_global(cx)
16120 .diagnostics_max_severity
16121 .filter(|severity| severity != &DiagnosticSeverity::Off)
16122 .unwrap_or(DiagnosticSeverity::Hint)
16123 } else {
16124 DiagnosticSeverity::Off
16125 };
16126 self.set_max_diagnostics_severity(new_severity, cx);
16127 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16128 self.active_diagnostics = ActiveDiagnostic::None;
16129 self.inline_diagnostics_update = Task::ready(());
16130 self.inline_diagnostics.clear();
16131 } else {
16132 self.refresh_inline_diagnostics(false, window, cx);
16133 }
16134
16135 cx.notify();
16136 }
16137
16138 pub fn toggle_minimap(
16139 &mut self,
16140 _: &ToggleMinimap,
16141 window: &mut Window,
16142 cx: &mut Context<Editor>,
16143 ) {
16144 if self.supports_minimap(cx) {
16145 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16146 }
16147 }
16148
16149 fn refresh_inline_diagnostics(
16150 &mut self,
16151 debounce: bool,
16152 window: &mut Window,
16153 cx: &mut Context<Self>,
16154 ) {
16155 let max_severity = ProjectSettings::get_global(cx)
16156 .diagnostics
16157 .inline
16158 .max_severity
16159 .unwrap_or(self.diagnostics_max_severity);
16160
16161 if !self.inline_diagnostics_enabled()
16162 || !self.show_inline_diagnostics
16163 || max_severity == DiagnosticSeverity::Off
16164 {
16165 self.inline_diagnostics_update = Task::ready(());
16166 self.inline_diagnostics.clear();
16167 return;
16168 }
16169
16170 let debounce_ms = ProjectSettings::get_global(cx)
16171 .diagnostics
16172 .inline
16173 .update_debounce_ms;
16174 let debounce = if debounce && debounce_ms > 0 {
16175 Some(Duration::from_millis(debounce_ms))
16176 } else {
16177 None
16178 };
16179 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16180 if let Some(debounce) = debounce {
16181 cx.background_executor().timer(debounce).await;
16182 }
16183 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16184 editor
16185 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16186 .ok()
16187 }) else {
16188 return;
16189 };
16190
16191 let new_inline_diagnostics = cx
16192 .background_spawn(async move {
16193 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16194 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16195 let message = diagnostic_entry
16196 .diagnostic
16197 .message
16198 .split_once('\n')
16199 .map(|(line, _)| line)
16200 .map(SharedString::new)
16201 .unwrap_or_else(|| {
16202 SharedString::from(diagnostic_entry.diagnostic.message)
16203 });
16204 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16205 let (Ok(i) | Err(i)) = inline_diagnostics
16206 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16207 inline_diagnostics.insert(
16208 i,
16209 (
16210 start_anchor,
16211 InlineDiagnostic {
16212 message,
16213 group_id: diagnostic_entry.diagnostic.group_id,
16214 start: diagnostic_entry.range.start.to_point(&snapshot),
16215 is_primary: diagnostic_entry.diagnostic.is_primary,
16216 severity: diagnostic_entry.diagnostic.severity,
16217 },
16218 ),
16219 );
16220 }
16221 inline_diagnostics
16222 })
16223 .await;
16224
16225 editor
16226 .update(cx, |editor, cx| {
16227 editor.inline_diagnostics = new_inline_diagnostics;
16228 cx.notify();
16229 })
16230 .ok();
16231 });
16232 }
16233
16234 fn pull_diagnostics(
16235 &mut self,
16236 buffer_id: Option<BufferId>,
16237 window: &Window,
16238 cx: &mut Context<Self>,
16239 ) -> Option<()> {
16240 if !self.mode().is_full() {
16241 return None;
16242 }
16243 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16244 .diagnostics
16245 .lsp_pull_diagnostics;
16246 if !pull_diagnostics_settings.enabled {
16247 return None;
16248 }
16249 let project = self.project.as_ref()?.downgrade();
16250 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16251 let mut buffers = self.buffer.read(cx).all_buffers();
16252 if let Some(buffer_id) = buffer_id {
16253 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16254 }
16255
16256 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16257 cx.background_executor().timer(debounce).await;
16258
16259 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16260 buffers
16261 .into_iter()
16262 .filter_map(|buffer| {
16263 project
16264 .update(cx, |project, cx| {
16265 project.lsp_store().update(cx, |lsp_store, cx| {
16266 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
16267 })
16268 })
16269 .ok()
16270 })
16271 .collect::<FuturesUnordered<_>>()
16272 }) else {
16273 return;
16274 };
16275
16276 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16277 match pull_task {
16278 Ok(()) => {
16279 if editor
16280 .update_in(cx, |editor, window, cx| {
16281 editor.update_diagnostics_state(window, cx);
16282 })
16283 .is_err()
16284 {
16285 return;
16286 }
16287 }
16288 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16289 }
16290 }
16291 });
16292
16293 Some(())
16294 }
16295
16296 pub fn set_selections_from_remote(
16297 &mut self,
16298 selections: Vec<Selection<Anchor>>,
16299 pending_selection: Option<Selection<Anchor>>,
16300 window: &mut Window,
16301 cx: &mut Context<Self>,
16302 ) {
16303 let old_cursor_position = self.selections.newest_anchor().head();
16304 self.selections.change_with(cx, |s| {
16305 s.select_anchors(selections);
16306 if let Some(pending_selection) = pending_selection {
16307 s.set_pending(pending_selection, SelectMode::Character);
16308 } else {
16309 s.clear_pending();
16310 }
16311 });
16312 self.selections_did_change(
16313 false,
16314 &old_cursor_position,
16315 SelectionEffects::default(),
16316 window,
16317 cx,
16318 );
16319 }
16320
16321 pub fn transact(
16322 &mut self,
16323 window: &mut Window,
16324 cx: &mut Context<Self>,
16325 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16326 ) -> Option<TransactionId> {
16327 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16328 this.start_transaction_at(Instant::now(), window, cx);
16329 update(this, window, cx);
16330 this.end_transaction_at(Instant::now(), cx)
16331 })
16332 }
16333
16334 pub fn start_transaction_at(
16335 &mut self,
16336 now: Instant,
16337 window: &mut Window,
16338 cx: &mut Context<Self>,
16339 ) {
16340 self.end_selection(window, cx);
16341 if let Some(tx_id) = self
16342 .buffer
16343 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16344 {
16345 self.selection_history
16346 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16347 cx.emit(EditorEvent::TransactionBegun {
16348 transaction_id: tx_id,
16349 })
16350 }
16351 }
16352
16353 pub fn end_transaction_at(
16354 &mut self,
16355 now: Instant,
16356 cx: &mut Context<Self>,
16357 ) -> Option<TransactionId> {
16358 if let Some(transaction_id) = self
16359 .buffer
16360 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16361 {
16362 if let Some((_, end_selections)) =
16363 self.selection_history.transaction_mut(transaction_id)
16364 {
16365 *end_selections = Some(self.selections.disjoint_anchors());
16366 } else {
16367 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16368 }
16369
16370 cx.emit(EditorEvent::Edited { transaction_id });
16371 Some(transaction_id)
16372 } else {
16373 None
16374 }
16375 }
16376
16377 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16378 if self.selection_mark_mode {
16379 self.change_selections(None, window, cx, |s| {
16380 s.move_with(|_, sel| {
16381 sel.collapse_to(sel.head(), SelectionGoal::None);
16382 });
16383 })
16384 }
16385 self.selection_mark_mode = true;
16386 cx.notify();
16387 }
16388
16389 pub fn swap_selection_ends(
16390 &mut self,
16391 _: &actions::SwapSelectionEnds,
16392 window: &mut Window,
16393 cx: &mut Context<Self>,
16394 ) {
16395 self.change_selections(None, window, cx, |s| {
16396 s.move_with(|_, sel| {
16397 if sel.start != sel.end {
16398 sel.reversed = !sel.reversed
16399 }
16400 });
16401 });
16402 self.request_autoscroll(Autoscroll::newest(), cx);
16403 cx.notify();
16404 }
16405
16406 pub fn toggle_fold(
16407 &mut self,
16408 _: &actions::ToggleFold,
16409 window: &mut Window,
16410 cx: &mut Context<Self>,
16411 ) {
16412 if self.is_singleton(cx) {
16413 let selection = self.selections.newest::<Point>(cx);
16414
16415 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16416 let range = if selection.is_empty() {
16417 let point = selection.head().to_display_point(&display_map);
16418 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16419 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16420 .to_point(&display_map);
16421 start..end
16422 } else {
16423 selection.range()
16424 };
16425 if display_map.folds_in_range(range).next().is_some() {
16426 self.unfold_lines(&Default::default(), window, cx)
16427 } else {
16428 self.fold(&Default::default(), window, cx)
16429 }
16430 } else {
16431 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16432 let buffer_ids: HashSet<_> = self
16433 .selections
16434 .disjoint_anchor_ranges()
16435 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16436 .collect();
16437
16438 let should_unfold = buffer_ids
16439 .iter()
16440 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16441
16442 for buffer_id in buffer_ids {
16443 if should_unfold {
16444 self.unfold_buffer(buffer_id, cx);
16445 } else {
16446 self.fold_buffer(buffer_id, cx);
16447 }
16448 }
16449 }
16450 }
16451
16452 pub fn toggle_fold_recursive(
16453 &mut self,
16454 _: &actions::ToggleFoldRecursive,
16455 window: &mut Window,
16456 cx: &mut Context<Self>,
16457 ) {
16458 let selection = self.selections.newest::<Point>(cx);
16459
16460 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16461 let range = if selection.is_empty() {
16462 let point = selection.head().to_display_point(&display_map);
16463 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16464 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16465 .to_point(&display_map);
16466 start..end
16467 } else {
16468 selection.range()
16469 };
16470 if display_map.folds_in_range(range).next().is_some() {
16471 self.unfold_recursive(&Default::default(), window, cx)
16472 } else {
16473 self.fold_recursive(&Default::default(), window, cx)
16474 }
16475 }
16476
16477 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16478 if self.is_singleton(cx) {
16479 let mut to_fold = Vec::new();
16480 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16481 let selections = self.selections.all_adjusted(cx);
16482
16483 for selection in selections {
16484 let range = selection.range().sorted();
16485 let buffer_start_row = range.start.row;
16486
16487 if range.start.row != range.end.row {
16488 let mut found = false;
16489 let mut row = range.start.row;
16490 while row <= range.end.row {
16491 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16492 {
16493 found = true;
16494 row = crease.range().end.row + 1;
16495 to_fold.push(crease);
16496 } else {
16497 row += 1
16498 }
16499 }
16500 if found {
16501 continue;
16502 }
16503 }
16504
16505 for row in (0..=range.start.row).rev() {
16506 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16507 if crease.range().end.row >= buffer_start_row {
16508 to_fold.push(crease);
16509 if row <= range.start.row {
16510 break;
16511 }
16512 }
16513 }
16514 }
16515 }
16516
16517 self.fold_creases(to_fold, true, window, cx);
16518 } else {
16519 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16520 let buffer_ids = self
16521 .selections
16522 .disjoint_anchor_ranges()
16523 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16524 .collect::<HashSet<_>>();
16525 for buffer_id in buffer_ids {
16526 self.fold_buffer(buffer_id, cx);
16527 }
16528 }
16529 }
16530
16531 fn fold_at_level(
16532 &mut self,
16533 fold_at: &FoldAtLevel,
16534 window: &mut Window,
16535 cx: &mut Context<Self>,
16536 ) {
16537 if !self.buffer.read(cx).is_singleton() {
16538 return;
16539 }
16540
16541 let fold_at_level = fold_at.0;
16542 let snapshot = self.buffer.read(cx).snapshot(cx);
16543 let mut to_fold = Vec::new();
16544 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16545
16546 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16547 while start_row < end_row {
16548 match self
16549 .snapshot(window, cx)
16550 .crease_for_buffer_row(MultiBufferRow(start_row))
16551 {
16552 Some(crease) => {
16553 let nested_start_row = crease.range().start.row + 1;
16554 let nested_end_row = crease.range().end.row;
16555
16556 if current_level < fold_at_level {
16557 stack.push((nested_start_row, nested_end_row, current_level + 1));
16558 } else if current_level == fold_at_level {
16559 to_fold.push(crease);
16560 }
16561
16562 start_row = nested_end_row + 1;
16563 }
16564 None => start_row += 1,
16565 }
16566 }
16567 }
16568
16569 self.fold_creases(to_fold, true, window, cx);
16570 }
16571
16572 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16573 if self.buffer.read(cx).is_singleton() {
16574 let mut fold_ranges = Vec::new();
16575 let snapshot = self.buffer.read(cx).snapshot(cx);
16576
16577 for row in 0..snapshot.max_row().0 {
16578 if let Some(foldable_range) = self
16579 .snapshot(window, cx)
16580 .crease_for_buffer_row(MultiBufferRow(row))
16581 {
16582 fold_ranges.push(foldable_range);
16583 }
16584 }
16585
16586 self.fold_creases(fold_ranges, true, window, cx);
16587 } else {
16588 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16589 editor
16590 .update_in(cx, |editor, _, cx| {
16591 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16592 editor.fold_buffer(buffer_id, cx);
16593 }
16594 })
16595 .ok();
16596 });
16597 }
16598 }
16599
16600 pub fn fold_function_bodies(
16601 &mut self,
16602 _: &actions::FoldFunctionBodies,
16603 window: &mut Window,
16604 cx: &mut Context<Self>,
16605 ) {
16606 let snapshot = self.buffer.read(cx).snapshot(cx);
16607
16608 let ranges = snapshot
16609 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16610 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16611 .collect::<Vec<_>>();
16612
16613 let creases = ranges
16614 .into_iter()
16615 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16616 .collect();
16617
16618 self.fold_creases(creases, true, window, cx);
16619 }
16620
16621 pub fn fold_recursive(
16622 &mut self,
16623 _: &actions::FoldRecursive,
16624 window: &mut Window,
16625 cx: &mut Context<Self>,
16626 ) {
16627 let mut to_fold = Vec::new();
16628 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16629 let selections = self.selections.all_adjusted(cx);
16630
16631 for selection in selections {
16632 let range = selection.range().sorted();
16633 let buffer_start_row = range.start.row;
16634
16635 if range.start.row != range.end.row {
16636 let mut found = false;
16637 for row in range.start.row..=range.end.row {
16638 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16639 found = true;
16640 to_fold.push(crease);
16641 }
16642 }
16643 if found {
16644 continue;
16645 }
16646 }
16647
16648 for row in (0..=range.start.row).rev() {
16649 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16650 if crease.range().end.row >= buffer_start_row {
16651 to_fold.push(crease);
16652 } else {
16653 break;
16654 }
16655 }
16656 }
16657 }
16658
16659 self.fold_creases(to_fold, true, window, cx);
16660 }
16661
16662 pub fn fold_at(
16663 &mut self,
16664 buffer_row: MultiBufferRow,
16665 window: &mut Window,
16666 cx: &mut Context<Self>,
16667 ) {
16668 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16669
16670 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16671 let autoscroll = self
16672 .selections
16673 .all::<Point>(cx)
16674 .iter()
16675 .any(|selection| crease.range().overlaps(&selection.range()));
16676
16677 self.fold_creases(vec![crease], autoscroll, window, cx);
16678 }
16679 }
16680
16681 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16682 if self.is_singleton(cx) {
16683 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16684 let buffer = &display_map.buffer_snapshot;
16685 let selections = self.selections.all::<Point>(cx);
16686 let ranges = selections
16687 .iter()
16688 .map(|s| {
16689 let range = s.display_range(&display_map).sorted();
16690 let mut start = range.start.to_point(&display_map);
16691 let mut end = range.end.to_point(&display_map);
16692 start.column = 0;
16693 end.column = buffer.line_len(MultiBufferRow(end.row));
16694 start..end
16695 })
16696 .collect::<Vec<_>>();
16697
16698 self.unfold_ranges(&ranges, true, true, cx);
16699 } else {
16700 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16701 let buffer_ids = self
16702 .selections
16703 .disjoint_anchor_ranges()
16704 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16705 .collect::<HashSet<_>>();
16706 for buffer_id in buffer_ids {
16707 self.unfold_buffer(buffer_id, cx);
16708 }
16709 }
16710 }
16711
16712 pub fn unfold_recursive(
16713 &mut self,
16714 _: &UnfoldRecursive,
16715 _window: &mut Window,
16716 cx: &mut Context<Self>,
16717 ) {
16718 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16719 let selections = self.selections.all::<Point>(cx);
16720 let ranges = selections
16721 .iter()
16722 .map(|s| {
16723 let mut range = s.display_range(&display_map).sorted();
16724 *range.start.column_mut() = 0;
16725 *range.end.column_mut() = display_map.line_len(range.end.row());
16726 let start = range.start.to_point(&display_map);
16727 let end = range.end.to_point(&display_map);
16728 start..end
16729 })
16730 .collect::<Vec<_>>();
16731
16732 self.unfold_ranges(&ranges, true, true, cx);
16733 }
16734
16735 pub fn unfold_at(
16736 &mut self,
16737 buffer_row: MultiBufferRow,
16738 _window: &mut Window,
16739 cx: &mut Context<Self>,
16740 ) {
16741 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16742
16743 let intersection_range = Point::new(buffer_row.0, 0)
16744 ..Point::new(
16745 buffer_row.0,
16746 display_map.buffer_snapshot.line_len(buffer_row),
16747 );
16748
16749 let autoscroll = self
16750 .selections
16751 .all::<Point>(cx)
16752 .iter()
16753 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16754
16755 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16756 }
16757
16758 pub fn unfold_all(
16759 &mut self,
16760 _: &actions::UnfoldAll,
16761 _window: &mut Window,
16762 cx: &mut Context<Self>,
16763 ) {
16764 if self.buffer.read(cx).is_singleton() {
16765 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16766 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16767 } else {
16768 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16769 editor
16770 .update(cx, |editor, cx| {
16771 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16772 editor.unfold_buffer(buffer_id, cx);
16773 }
16774 })
16775 .ok();
16776 });
16777 }
16778 }
16779
16780 pub fn fold_selected_ranges(
16781 &mut self,
16782 _: &FoldSelectedRanges,
16783 window: &mut Window,
16784 cx: &mut Context<Self>,
16785 ) {
16786 let selections = self.selections.all_adjusted(cx);
16787 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16788 let ranges = selections
16789 .into_iter()
16790 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16791 .collect::<Vec<_>>();
16792 self.fold_creases(ranges, true, window, cx);
16793 }
16794
16795 pub fn fold_ranges<T: ToOffset + Clone>(
16796 &mut self,
16797 ranges: Vec<Range<T>>,
16798 auto_scroll: bool,
16799 window: &mut Window,
16800 cx: &mut Context<Self>,
16801 ) {
16802 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16803 let ranges = ranges
16804 .into_iter()
16805 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16806 .collect::<Vec<_>>();
16807 self.fold_creases(ranges, auto_scroll, window, cx);
16808 }
16809
16810 pub fn fold_creases<T: ToOffset + Clone>(
16811 &mut self,
16812 creases: Vec<Crease<T>>,
16813 auto_scroll: bool,
16814 _window: &mut Window,
16815 cx: &mut Context<Self>,
16816 ) {
16817 if creases.is_empty() {
16818 return;
16819 }
16820
16821 let mut buffers_affected = HashSet::default();
16822 let multi_buffer = self.buffer().read(cx);
16823 for crease in &creases {
16824 if let Some((_, buffer, _)) =
16825 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16826 {
16827 buffers_affected.insert(buffer.read(cx).remote_id());
16828 };
16829 }
16830
16831 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16832
16833 if auto_scroll {
16834 self.request_autoscroll(Autoscroll::fit(), cx);
16835 }
16836
16837 cx.notify();
16838
16839 self.scrollbar_marker_state.dirty = true;
16840 self.folds_did_change(cx);
16841 }
16842
16843 /// Removes any folds whose ranges intersect any of the given ranges.
16844 pub fn unfold_ranges<T: ToOffset + Clone>(
16845 &mut self,
16846 ranges: &[Range<T>],
16847 inclusive: bool,
16848 auto_scroll: bool,
16849 cx: &mut Context<Self>,
16850 ) {
16851 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16852 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16853 });
16854 self.folds_did_change(cx);
16855 }
16856
16857 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16858 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16859 return;
16860 }
16861 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16862 self.display_map.update(cx, |display_map, cx| {
16863 display_map.fold_buffers([buffer_id], cx)
16864 });
16865 cx.emit(EditorEvent::BufferFoldToggled {
16866 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16867 folded: true,
16868 });
16869 cx.notify();
16870 }
16871
16872 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16873 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16874 return;
16875 }
16876 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16877 self.display_map.update(cx, |display_map, cx| {
16878 display_map.unfold_buffers([buffer_id], cx);
16879 });
16880 cx.emit(EditorEvent::BufferFoldToggled {
16881 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16882 folded: false,
16883 });
16884 cx.notify();
16885 }
16886
16887 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16888 self.display_map.read(cx).is_buffer_folded(buffer)
16889 }
16890
16891 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16892 self.display_map.read(cx).folded_buffers()
16893 }
16894
16895 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16896 self.display_map.update(cx, |display_map, cx| {
16897 display_map.disable_header_for_buffer(buffer_id, cx);
16898 });
16899 cx.notify();
16900 }
16901
16902 /// Removes any folds with the given ranges.
16903 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16904 &mut self,
16905 ranges: &[Range<T>],
16906 type_id: TypeId,
16907 auto_scroll: bool,
16908 cx: &mut Context<Self>,
16909 ) {
16910 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16911 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16912 });
16913 self.folds_did_change(cx);
16914 }
16915
16916 fn remove_folds_with<T: ToOffset + Clone>(
16917 &mut self,
16918 ranges: &[Range<T>],
16919 auto_scroll: bool,
16920 cx: &mut Context<Self>,
16921 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16922 ) {
16923 if ranges.is_empty() {
16924 return;
16925 }
16926
16927 let mut buffers_affected = HashSet::default();
16928 let multi_buffer = self.buffer().read(cx);
16929 for range in ranges {
16930 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16931 buffers_affected.insert(buffer.read(cx).remote_id());
16932 };
16933 }
16934
16935 self.display_map.update(cx, update);
16936
16937 if auto_scroll {
16938 self.request_autoscroll(Autoscroll::fit(), cx);
16939 }
16940
16941 cx.notify();
16942 self.scrollbar_marker_state.dirty = true;
16943 self.active_indent_guides_state.dirty = true;
16944 }
16945
16946 pub fn update_fold_widths(
16947 &mut self,
16948 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16949 cx: &mut Context<Self>,
16950 ) -> bool {
16951 self.display_map
16952 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16953 }
16954
16955 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16956 self.display_map.read(cx).fold_placeholder.clone()
16957 }
16958
16959 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16960 self.buffer.update(cx, |buffer, cx| {
16961 buffer.set_all_diff_hunks_expanded(cx);
16962 });
16963 }
16964
16965 pub fn expand_all_diff_hunks(
16966 &mut self,
16967 _: &ExpandAllDiffHunks,
16968 _window: &mut Window,
16969 cx: &mut Context<Self>,
16970 ) {
16971 self.buffer.update(cx, |buffer, cx| {
16972 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16973 });
16974 }
16975
16976 pub fn toggle_selected_diff_hunks(
16977 &mut self,
16978 _: &ToggleSelectedDiffHunks,
16979 _window: &mut Window,
16980 cx: &mut Context<Self>,
16981 ) {
16982 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16983 self.toggle_diff_hunks_in_ranges(ranges, cx);
16984 }
16985
16986 pub fn diff_hunks_in_ranges<'a>(
16987 &'a self,
16988 ranges: &'a [Range<Anchor>],
16989 buffer: &'a MultiBufferSnapshot,
16990 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16991 ranges.iter().flat_map(move |range| {
16992 let end_excerpt_id = range.end.excerpt_id;
16993 let range = range.to_point(buffer);
16994 let mut peek_end = range.end;
16995 if range.end.row < buffer.max_row().0 {
16996 peek_end = Point::new(range.end.row + 1, 0);
16997 }
16998 buffer
16999 .diff_hunks_in_range(range.start..peek_end)
17000 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17001 })
17002 }
17003
17004 pub fn has_stageable_diff_hunks_in_ranges(
17005 &self,
17006 ranges: &[Range<Anchor>],
17007 snapshot: &MultiBufferSnapshot,
17008 ) -> bool {
17009 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17010 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17011 }
17012
17013 pub fn toggle_staged_selected_diff_hunks(
17014 &mut self,
17015 _: &::git::ToggleStaged,
17016 _: &mut Window,
17017 cx: &mut Context<Self>,
17018 ) {
17019 let snapshot = self.buffer.read(cx).snapshot(cx);
17020 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17021 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17022 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17023 }
17024
17025 pub fn set_render_diff_hunk_controls(
17026 &mut self,
17027 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17028 cx: &mut Context<Self>,
17029 ) {
17030 self.render_diff_hunk_controls = render_diff_hunk_controls;
17031 cx.notify();
17032 }
17033
17034 pub fn stage_and_next(
17035 &mut self,
17036 _: &::git::StageAndNext,
17037 window: &mut Window,
17038 cx: &mut Context<Self>,
17039 ) {
17040 self.do_stage_or_unstage_and_next(true, window, cx);
17041 }
17042
17043 pub fn unstage_and_next(
17044 &mut self,
17045 _: &::git::UnstageAndNext,
17046 window: &mut Window,
17047 cx: &mut Context<Self>,
17048 ) {
17049 self.do_stage_or_unstage_and_next(false, window, cx);
17050 }
17051
17052 pub fn stage_or_unstage_diff_hunks(
17053 &mut self,
17054 stage: bool,
17055 ranges: Vec<Range<Anchor>>,
17056 cx: &mut Context<Self>,
17057 ) {
17058 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17059 cx.spawn(async move |this, cx| {
17060 task.await?;
17061 this.update(cx, |this, cx| {
17062 let snapshot = this.buffer.read(cx).snapshot(cx);
17063 let chunk_by = this
17064 .diff_hunks_in_ranges(&ranges, &snapshot)
17065 .chunk_by(|hunk| hunk.buffer_id);
17066 for (buffer_id, hunks) in &chunk_by {
17067 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17068 }
17069 })
17070 })
17071 .detach_and_log_err(cx);
17072 }
17073
17074 fn save_buffers_for_ranges_if_needed(
17075 &mut self,
17076 ranges: &[Range<Anchor>],
17077 cx: &mut Context<Editor>,
17078 ) -> Task<Result<()>> {
17079 let multibuffer = self.buffer.read(cx);
17080 let snapshot = multibuffer.read(cx);
17081 let buffer_ids: HashSet<_> = ranges
17082 .iter()
17083 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17084 .collect();
17085 drop(snapshot);
17086
17087 let mut buffers = HashSet::default();
17088 for buffer_id in buffer_ids {
17089 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17090 let buffer = buffer_entity.read(cx);
17091 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17092 {
17093 buffers.insert(buffer_entity);
17094 }
17095 }
17096 }
17097
17098 if let Some(project) = &self.project {
17099 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17100 } else {
17101 Task::ready(Ok(()))
17102 }
17103 }
17104
17105 fn do_stage_or_unstage_and_next(
17106 &mut self,
17107 stage: bool,
17108 window: &mut Window,
17109 cx: &mut Context<Self>,
17110 ) {
17111 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17112
17113 if ranges.iter().any(|range| range.start != range.end) {
17114 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17115 return;
17116 }
17117
17118 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17119 let snapshot = self.snapshot(window, cx);
17120 let position = self.selections.newest::<Point>(cx).head();
17121 let mut row = snapshot
17122 .buffer_snapshot
17123 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17124 .find(|hunk| hunk.row_range.start.0 > position.row)
17125 .map(|hunk| hunk.row_range.start);
17126
17127 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17128 // Outside of the project diff editor, wrap around to the beginning.
17129 if !all_diff_hunks_expanded {
17130 row = row.or_else(|| {
17131 snapshot
17132 .buffer_snapshot
17133 .diff_hunks_in_range(Point::zero()..position)
17134 .find(|hunk| hunk.row_range.end.0 < position.row)
17135 .map(|hunk| hunk.row_range.start)
17136 });
17137 }
17138
17139 if let Some(row) = row {
17140 let destination = Point::new(row.0, 0);
17141 let autoscroll = Autoscroll::center();
17142
17143 self.unfold_ranges(&[destination..destination], false, false, cx);
17144 self.change_selections(Some(autoscroll), window, cx, |s| {
17145 s.select_ranges([destination..destination]);
17146 });
17147 }
17148 }
17149
17150 fn do_stage_or_unstage(
17151 &self,
17152 stage: bool,
17153 buffer_id: BufferId,
17154 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17155 cx: &mut App,
17156 ) -> Option<()> {
17157 let project = self.project.as_ref()?;
17158 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17159 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17160 let buffer_snapshot = buffer.read(cx).snapshot();
17161 let file_exists = buffer_snapshot
17162 .file()
17163 .is_some_and(|file| file.disk_state().exists());
17164 diff.update(cx, |diff, cx| {
17165 diff.stage_or_unstage_hunks(
17166 stage,
17167 &hunks
17168 .map(|hunk| buffer_diff::DiffHunk {
17169 buffer_range: hunk.buffer_range,
17170 diff_base_byte_range: hunk.diff_base_byte_range,
17171 secondary_status: hunk.secondary_status,
17172 range: Point::zero()..Point::zero(), // unused
17173 })
17174 .collect::<Vec<_>>(),
17175 &buffer_snapshot,
17176 file_exists,
17177 cx,
17178 )
17179 });
17180 None
17181 }
17182
17183 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17184 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17185 self.buffer
17186 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17187 }
17188
17189 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17190 self.buffer.update(cx, |buffer, cx| {
17191 let ranges = vec![Anchor::min()..Anchor::max()];
17192 if !buffer.all_diff_hunks_expanded()
17193 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17194 {
17195 buffer.collapse_diff_hunks(ranges, cx);
17196 true
17197 } else {
17198 false
17199 }
17200 })
17201 }
17202
17203 fn toggle_diff_hunks_in_ranges(
17204 &mut self,
17205 ranges: Vec<Range<Anchor>>,
17206 cx: &mut Context<Editor>,
17207 ) {
17208 self.buffer.update(cx, |buffer, cx| {
17209 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17210 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17211 })
17212 }
17213
17214 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17215 self.buffer.update(cx, |buffer, cx| {
17216 let snapshot = buffer.snapshot(cx);
17217 let excerpt_id = range.end.excerpt_id;
17218 let point_range = range.to_point(&snapshot);
17219 let expand = !buffer.single_hunk_is_expanded(range, cx);
17220 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17221 })
17222 }
17223
17224 pub(crate) fn apply_all_diff_hunks(
17225 &mut self,
17226 _: &ApplyAllDiffHunks,
17227 window: &mut Window,
17228 cx: &mut Context<Self>,
17229 ) {
17230 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17231
17232 let buffers = self.buffer.read(cx).all_buffers();
17233 for branch_buffer in buffers {
17234 branch_buffer.update(cx, |branch_buffer, cx| {
17235 branch_buffer.merge_into_base(Vec::new(), cx);
17236 });
17237 }
17238
17239 if let Some(project) = self.project.clone() {
17240 self.save(
17241 SaveOptions {
17242 format: true,
17243 autosave: false,
17244 },
17245 project,
17246 window,
17247 cx,
17248 )
17249 .detach_and_log_err(cx);
17250 }
17251 }
17252
17253 pub(crate) fn apply_selected_diff_hunks(
17254 &mut self,
17255 _: &ApplyDiffHunk,
17256 window: &mut Window,
17257 cx: &mut Context<Self>,
17258 ) {
17259 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17260 let snapshot = self.snapshot(window, cx);
17261 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17262 let mut ranges_by_buffer = HashMap::default();
17263 self.transact(window, cx, |editor, _window, cx| {
17264 for hunk in hunks {
17265 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17266 ranges_by_buffer
17267 .entry(buffer.clone())
17268 .or_insert_with(Vec::new)
17269 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17270 }
17271 }
17272
17273 for (buffer, ranges) in ranges_by_buffer {
17274 buffer.update(cx, |buffer, cx| {
17275 buffer.merge_into_base(ranges, cx);
17276 });
17277 }
17278 });
17279
17280 if let Some(project) = self.project.clone() {
17281 self.save(
17282 SaveOptions {
17283 format: true,
17284 autosave: false,
17285 },
17286 project,
17287 window,
17288 cx,
17289 )
17290 .detach_and_log_err(cx);
17291 }
17292 }
17293
17294 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17295 if hovered != self.gutter_hovered {
17296 self.gutter_hovered = hovered;
17297 cx.notify();
17298 }
17299 }
17300
17301 pub fn insert_blocks(
17302 &mut self,
17303 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17304 autoscroll: Option<Autoscroll>,
17305 cx: &mut Context<Self>,
17306 ) -> Vec<CustomBlockId> {
17307 let blocks = self
17308 .display_map
17309 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17310 if let Some(autoscroll) = autoscroll {
17311 self.request_autoscroll(autoscroll, cx);
17312 }
17313 cx.notify();
17314 blocks
17315 }
17316
17317 pub fn resize_blocks(
17318 &mut self,
17319 heights: HashMap<CustomBlockId, u32>,
17320 autoscroll: Option<Autoscroll>,
17321 cx: &mut Context<Self>,
17322 ) {
17323 self.display_map
17324 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17325 if let Some(autoscroll) = autoscroll {
17326 self.request_autoscroll(autoscroll, cx);
17327 }
17328 cx.notify();
17329 }
17330
17331 pub fn replace_blocks(
17332 &mut self,
17333 renderers: HashMap<CustomBlockId, RenderBlock>,
17334 autoscroll: Option<Autoscroll>,
17335 cx: &mut Context<Self>,
17336 ) {
17337 self.display_map
17338 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17339 if let Some(autoscroll) = autoscroll {
17340 self.request_autoscroll(autoscroll, cx);
17341 }
17342 cx.notify();
17343 }
17344
17345 pub fn remove_blocks(
17346 &mut self,
17347 block_ids: HashSet<CustomBlockId>,
17348 autoscroll: Option<Autoscroll>,
17349 cx: &mut Context<Self>,
17350 ) {
17351 self.display_map.update(cx, |display_map, cx| {
17352 display_map.remove_blocks(block_ids, cx)
17353 });
17354 if let Some(autoscroll) = autoscroll {
17355 self.request_autoscroll(autoscroll, cx);
17356 }
17357 cx.notify();
17358 }
17359
17360 pub fn row_for_block(
17361 &self,
17362 block_id: CustomBlockId,
17363 cx: &mut Context<Self>,
17364 ) -> Option<DisplayRow> {
17365 self.display_map
17366 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17367 }
17368
17369 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17370 self.focused_block = Some(focused_block);
17371 }
17372
17373 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17374 self.focused_block.take()
17375 }
17376
17377 pub fn insert_creases(
17378 &mut self,
17379 creases: impl IntoIterator<Item = Crease<Anchor>>,
17380 cx: &mut Context<Self>,
17381 ) -> Vec<CreaseId> {
17382 self.display_map
17383 .update(cx, |map, cx| map.insert_creases(creases, cx))
17384 }
17385
17386 pub fn remove_creases(
17387 &mut self,
17388 ids: impl IntoIterator<Item = CreaseId>,
17389 cx: &mut Context<Self>,
17390 ) -> Vec<(CreaseId, Range<Anchor>)> {
17391 self.display_map
17392 .update(cx, |map, cx| map.remove_creases(ids, cx))
17393 }
17394
17395 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17396 self.display_map
17397 .update(cx, |map, cx| map.snapshot(cx))
17398 .longest_row()
17399 }
17400
17401 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17402 self.display_map
17403 .update(cx, |map, cx| map.snapshot(cx))
17404 .max_point()
17405 }
17406
17407 pub fn text(&self, cx: &App) -> String {
17408 self.buffer.read(cx).read(cx).text()
17409 }
17410
17411 pub fn is_empty(&self, cx: &App) -> bool {
17412 self.buffer.read(cx).read(cx).is_empty()
17413 }
17414
17415 pub fn text_option(&self, cx: &App) -> Option<String> {
17416 let text = self.text(cx);
17417 let text = text.trim();
17418
17419 if text.is_empty() {
17420 return None;
17421 }
17422
17423 Some(text.to_string())
17424 }
17425
17426 pub fn set_text(
17427 &mut self,
17428 text: impl Into<Arc<str>>,
17429 window: &mut Window,
17430 cx: &mut Context<Self>,
17431 ) {
17432 self.transact(window, cx, |this, _, cx| {
17433 this.buffer
17434 .read(cx)
17435 .as_singleton()
17436 .expect("you can only call set_text on editors for singleton buffers")
17437 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17438 });
17439 }
17440
17441 pub fn display_text(&self, cx: &mut App) -> String {
17442 self.display_map
17443 .update(cx, |map, cx| map.snapshot(cx))
17444 .text()
17445 }
17446
17447 fn create_minimap(
17448 &self,
17449 minimap_settings: MinimapSettings,
17450 window: &mut Window,
17451 cx: &mut Context<Self>,
17452 ) -> Option<Entity<Self>> {
17453 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17454 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17455 }
17456
17457 fn initialize_new_minimap(
17458 &self,
17459 minimap_settings: MinimapSettings,
17460 window: &mut Window,
17461 cx: &mut Context<Self>,
17462 ) -> Entity<Self> {
17463 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17464
17465 let mut minimap = Editor::new_internal(
17466 EditorMode::Minimap {
17467 parent: cx.weak_entity(),
17468 },
17469 self.buffer.clone(),
17470 self.project.clone(),
17471 Some(self.display_map.clone()),
17472 window,
17473 cx,
17474 );
17475 minimap.scroll_manager.clone_state(&self.scroll_manager);
17476 minimap.set_text_style_refinement(TextStyleRefinement {
17477 font_size: Some(MINIMAP_FONT_SIZE),
17478 font_weight: Some(MINIMAP_FONT_WEIGHT),
17479 ..Default::default()
17480 });
17481 minimap.update_minimap_configuration(minimap_settings, cx);
17482 cx.new(|_| minimap)
17483 }
17484
17485 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17486 let current_line_highlight = minimap_settings
17487 .current_line_highlight
17488 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17489 self.set_current_line_highlight(Some(current_line_highlight));
17490 }
17491
17492 pub fn minimap(&self) -> Option<&Entity<Self>> {
17493 self.minimap
17494 .as_ref()
17495 .filter(|_| self.minimap_visibility.visible())
17496 }
17497
17498 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17499 let mut wrap_guides = smallvec![];
17500
17501 if self.show_wrap_guides == Some(false) {
17502 return wrap_guides;
17503 }
17504
17505 let settings = self.buffer.read(cx).language_settings(cx);
17506 if settings.show_wrap_guides {
17507 match self.soft_wrap_mode(cx) {
17508 SoftWrap::Column(soft_wrap) => {
17509 wrap_guides.push((soft_wrap as usize, true));
17510 }
17511 SoftWrap::Bounded(soft_wrap) => {
17512 wrap_guides.push((soft_wrap as usize, true));
17513 }
17514 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17515 }
17516 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17517 }
17518
17519 wrap_guides
17520 }
17521
17522 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17523 let settings = self.buffer.read(cx).language_settings(cx);
17524 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17525 match mode {
17526 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17527 SoftWrap::None
17528 }
17529 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17530 language_settings::SoftWrap::PreferredLineLength => {
17531 SoftWrap::Column(settings.preferred_line_length)
17532 }
17533 language_settings::SoftWrap::Bounded => {
17534 SoftWrap::Bounded(settings.preferred_line_length)
17535 }
17536 }
17537 }
17538
17539 pub fn set_soft_wrap_mode(
17540 &mut self,
17541 mode: language_settings::SoftWrap,
17542
17543 cx: &mut Context<Self>,
17544 ) {
17545 self.soft_wrap_mode_override = Some(mode);
17546 cx.notify();
17547 }
17548
17549 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17550 self.hard_wrap = hard_wrap;
17551 cx.notify();
17552 }
17553
17554 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17555 self.text_style_refinement = Some(style);
17556 }
17557
17558 /// called by the Element so we know what style we were most recently rendered with.
17559 pub(crate) fn set_style(
17560 &mut self,
17561 style: EditorStyle,
17562 window: &mut Window,
17563 cx: &mut Context<Self>,
17564 ) {
17565 // We intentionally do not inform the display map about the minimap style
17566 // so that wrapping is not recalculated and stays consistent for the editor
17567 // and its linked minimap.
17568 if !self.mode.is_minimap() {
17569 let rem_size = window.rem_size();
17570 self.display_map.update(cx, |map, cx| {
17571 map.set_font(
17572 style.text.font(),
17573 style.text.font_size.to_pixels(rem_size),
17574 cx,
17575 )
17576 });
17577 }
17578 self.style = Some(style);
17579 }
17580
17581 pub fn style(&self) -> Option<&EditorStyle> {
17582 self.style.as_ref()
17583 }
17584
17585 // Called by the element. This method is not designed to be called outside of the editor
17586 // element's layout code because it does not notify when rewrapping is computed synchronously.
17587 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17588 self.display_map
17589 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17590 }
17591
17592 pub fn set_soft_wrap(&mut self) {
17593 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17594 }
17595
17596 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17597 if self.soft_wrap_mode_override.is_some() {
17598 self.soft_wrap_mode_override.take();
17599 } else {
17600 let soft_wrap = match self.soft_wrap_mode(cx) {
17601 SoftWrap::GitDiff => return,
17602 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17603 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17604 language_settings::SoftWrap::None
17605 }
17606 };
17607 self.soft_wrap_mode_override = Some(soft_wrap);
17608 }
17609 cx.notify();
17610 }
17611
17612 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17613 let Some(workspace) = self.workspace() else {
17614 return;
17615 };
17616 let fs = workspace.read(cx).app_state().fs.clone();
17617 let current_show = TabBarSettings::get_global(cx).show;
17618 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17619 setting.show = Some(!current_show);
17620 });
17621 }
17622
17623 pub fn toggle_indent_guides(
17624 &mut self,
17625 _: &ToggleIndentGuides,
17626 _: &mut Window,
17627 cx: &mut Context<Self>,
17628 ) {
17629 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17630 self.buffer
17631 .read(cx)
17632 .language_settings(cx)
17633 .indent_guides
17634 .enabled
17635 });
17636 self.show_indent_guides = Some(!currently_enabled);
17637 cx.notify();
17638 }
17639
17640 fn should_show_indent_guides(&self) -> Option<bool> {
17641 self.show_indent_guides
17642 }
17643
17644 pub fn toggle_line_numbers(
17645 &mut self,
17646 _: &ToggleLineNumbers,
17647 _: &mut Window,
17648 cx: &mut Context<Self>,
17649 ) {
17650 let mut editor_settings = EditorSettings::get_global(cx).clone();
17651 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17652 EditorSettings::override_global(editor_settings, cx);
17653 }
17654
17655 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17656 if let Some(show_line_numbers) = self.show_line_numbers {
17657 return show_line_numbers;
17658 }
17659 EditorSettings::get_global(cx).gutter.line_numbers
17660 }
17661
17662 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17663 self.use_relative_line_numbers
17664 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17665 }
17666
17667 pub fn toggle_relative_line_numbers(
17668 &mut self,
17669 _: &ToggleRelativeLineNumbers,
17670 _: &mut Window,
17671 cx: &mut Context<Self>,
17672 ) {
17673 let is_relative = self.should_use_relative_line_numbers(cx);
17674 self.set_relative_line_number(Some(!is_relative), cx)
17675 }
17676
17677 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17678 self.use_relative_line_numbers = is_relative;
17679 cx.notify();
17680 }
17681
17682 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17683 self.show_gutter = show_gutter;
17684 cx.notify();
17685 }
17686
17687 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17688 self.show_scrollbars = ScrollbarAxes {
17689 horizontal: show,
17690 vertical: show,
17691 };
17692 cx.notify();
17693 }
17694
17695 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17696 self.show_scrollbars.vertical = show;
17697 cx.notify();
17698 }
17699
17700 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17701 self.show_scrollbars.horizontal = show;
17702 cx.notify();
17703 }
17704
17705 pub fn set_minimap_visibility(
17706 &mut self,
17707 minimap_visibility: MinimapVisibility,
17708 window: &mut Window,
17709 cx: &mut Context<Self>,
17710 ) {
17711 if self.minimap_visibility != minimap_visibility {
17712 if minimap_visibility.visible() && self.minimap.is_none() {
17713 let minimap_settings = EditorSettings::get_global(cx).minimap;
17714 self.minimap =
17715 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17716 }
17717 self.minimap_visibility = minimap_visibility;
17718 cx.notify();
17719 }
17720 }
17721
17722 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17723 self.set_show_scrollbars(false, cx);
17724 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17725 }
17726
17727 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17728 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17729 }
17730
17731 /// Normally the text in full mode and auto height editors is padded on the
17732 /// left side by roughly half a character width for improved hit testing.
17733 ///
17734 /// Use this method to disable this for cases where this is not wanted (e.g.
17735 /// if you want to align the editor text with some other text above or below)
17736 /// or if you want to add this padding to single-line editors.
17737 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17738 self.offset_content = offset_content;
17739 cx.notify();
17740 }
17741
17742 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17743 self.show_line_numbers = Some(show_line_numbers);
17744 cx.notify();
17745 }
17746
17747 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17748 self.disable_expand_excerpt_buttons = true;
17749 cx.notify();
17750 }
17751
17752 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17753 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17754 cx.notify();
17755 }
17756
17757 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17758 self.show_code_actions = Some(show_code_actions);
17759 cx.notify();
17760 }
17761
17762 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17763 self.show_runnables = Some(show_runnables);
17764 cx.notify();
17765 }
17766
17767 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17768 self.show_breakpoints = Some(show_breakpoints);
17769 cx.notify();
17770 }
17771
17772 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17773 if self.display_map.read(cx).masked != masked {
17774 self.display_map.update(cx, |map, _| map.masked = masked);
17775 }
17776 cx.notify()
17777 }
17778
17779 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17780 self.show_wrap_guides = Some(show_wrap_guides);
17781 cx.notify();
17782 }
17783
17784 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17785 self.show_indent_guides = Some(show_indent_guides);
17786 cx.notify();
17787 }
17788
17789 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17790 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17791 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17792 if let Some(dir) = file.abs_path(cx).parent() {
17793 return Some(dir.to_owned());
17794 }
17795 }
17796
17797 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17798 return Some(project_path.path.to_path_buf());
17799 }
17800 }
17801
17802 None
17803 }
17804
17805 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17806 self.active_excerpt(cx)?
17807 .1
17808 .read(cx)
17809 .file()
17810 .and_then(|f| f.as_local())
17811 }
17812
17813 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17814 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17815 let buffer = buffer.read(cx);
17816 if let Some(project_path) = buffer.project_path(cx) {
17817 let project = self.project.as_ref()?.read(cx);
17818 project.absolute_path(&project_path, cx)
17819 } else {
17820 buffer
17821 .file()
17822 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17823 }
17824 })
17825 }
17826
17827 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17828 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17829 let project_path = buffer.read(cx).project_path(cx)?;
17830 let project = self.project.as_ref()?.read(cx);
17831 let entry = project.entry_for_path(&project_path, cx)?;
17832 let path = entry.path.to_path_buf();
17833 Some(path)
17834 })
17835 }
17836
17837 pub fn reveal_in_finder(
17838 &mut self,
17839 _: &RevealInFileManager,
17840 _window: &mut Window,
17841 cx: &mut Context<Self>,
17842 ) {
17843 if let Some(target) = self.target_file(cx) {
17844 cx.reveal_path(&target.abs_path(cx));
17845 }
17846 }
17847
17848 pub fn copy_path(
17849 &mut self,
17850 _: &zed_actions::workspace::CopyPath,
17851 _window: &mut Window,
17852 cx: &mut Context<Self>,
17853 ) {
17854 if let Some(path) = self.target_file_abs_path(cx) {
17855 if let Some(path) = path.to_str() {
17856 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17857 }
17858 }
17859 }
17860
17861 pub fn copy_relative_path(
17862 &mut self,
17863 _: &zed_actions::workspace::CopyRelativePath,
17864 _window: &mut Window,
17865 cx: &mut Context<Self>,
17866 ) {
17867 if let Some(path) = self.target_file_path(cx) {
17868 if let Some(path) = path.to_str() {
17869 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17870 }
17871 }
17872 }
17873
17874 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17875 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17876 buffer.read(cx).project_path(cx)
17877 } else {
17878 None
17879 }
17880 }
17881
17882 // Returns true if the editor handled a go-to-line request
17883 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17884 maybe!({
17885 let breakpoint_store = self.breakpoint_store.as_ref()?;
17886
17887 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17888 else {
17889 self.clear_row_highlights::<ActiveDebugLine>();
17890 return None;
17891 };
17892
17893 let position = active_stack_frame.position;
17894 let buffer_id = position.buffer_id?;
17895 let snapshot = self
17896 .project
17897 .as_ref()?
17898 .read(cx)
17899 .buffer_for_id(buffer_id, cx)?
17900 .read(cx)
17901 .snapshot();
17902
17903 let mut handled = false;
17904 for (id, ExcerptRange { context, .. }) in
17905 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17906 {
17907 if context.start.cmp(&position, &snapshot).is_ge()
17908 || context.end.cmp(&position, &snapshot).is_lt()
17909 {
17910 continue;
17911 }
17912 let snapshot = self.buffer.read(cx).snapshot(cx);
17913 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17914
17915 handled = true;
17916 self.clear_row_highlights::<ActiveDebugLine>();
17917
17918 self.go_to_line::<ActiveDebugLine>(
17919 multibuffer_anchor,
17920 Some(cx.theme().colors().editor_debugger_active_line_background),
17921 window,
17922 cx,
17923 );
17924
17925 cx.notify();
17926 }
17927
17928 handled.then_some(())
17929 })
17930 .is_some()
17931 }
17932
17933 pub fn copy_file_name_without_extension(
17934 &mut self,
17935 _: &CopyFileNameWithoutExtension,
17936 _: &mut Window,
17937 cx: &mut Context<Self>,
17938 ) {
17939 if let Some(file) = self.target_file(cx) {
17940 if let Some(file_stem) = file.path().file_stem() {
17941 if let Some(name) = file_stem.to_str() {
17942 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17943 }
17944 }
17945 }
17946 }
17947
17948 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17949 if let Some(file) = self.target_file(cx) {
17950 if let Some(file_name) = file.path().file_name() {
17951 if let Some(name) = file_name.to_str() {
17952 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17953 }
17954 }
17955 }
17956 }
17957
17958 pub fn toggle_git_blame(
17959 &mut self,
17960 _: &::git::Blame,
17961 window: &mut Window,
17962 cx: &mut Context<Self>,
17963 ) {
17964 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17965
17966 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17967 self.start_git_blame(true, window, cx);
17968 }
17969
17970 cx.notify();
17971 }
17972
17973 pub fn toggle_git_blame_inline(
17974 &mut self,
17975 _: &ToggleGitBlameInline,
17976 window: &mut Window,
17977 cx: &mut Context<Self>,
17978 ) {
17979 self.toggle_git_blame_inline_internal(true, window, cx);
17980 cx.notify();
17981 }
17982
17983 pub fn open_git_blame_commit(
17984 &mut self,
17985 _: &OpenGitBlameCommit,
17986 window: &mut Window,
17987 cx: &mut Context<Self>,
17988 ) {
17989 self.open_git_blame_commit_internal(window, cx);
17990 }
17991
17992 fn open_git_blame_commit_internal(
17993 &mut self,
17994 window: &mut Window,
17995 cx: &mut Context<Self>,
17996 ) -> Option<()> {
17997 let blame = self.blame.as_ref()?;
17998 let snapshot = self.snapshot(window, cx);
17999 let cursor = self.selections.newest::<Point>(cx).head();
18000 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18001 let blame_entry = blame
18002 .update(cx, |blame, cx| {
18003 blame
18004 .blame_for_rows(
18005 &[RowInfo {
18006 buffer_id: Some(buffer.remote_id()),
18007 buffer_row: Some(point.row),
18008 ..Default::default()
18009 }],
18010 cx,
18011 )
18012 .next()
18013 })
18014 .flatten()?;
18015 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18016 let repo = blame.read(cx).repository(cx)?;
18017 let workspace = self.workspace()?.downgrade();
18018 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18019 None
18020 }
18021
18022 pub fn git_blame_inline_enabled(&self) -> bool {
18023 self.git_blame_inline_enabled
18024 }
18025
18026 pub fn toggle_selection_menu(
18027 &mut self,
18028 _: &ToggleSelectionMenu,
18029 _: &mut Window,
18030 cx: &mut Context<Self>,
18031 ) {
18032 self.show_selection_menu = self
18033 .show_selection_menu
18034 .map(|show_selections_menu| !show_selections_menu)
18035 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18036
18037 cx.notify();
18038 }
18039
18040 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18041 self.show_selection_menu
18042 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18043 }
18044
18045 fn start_git_blame(
18046 &mut self,
18047 user_triggered: bool,
18048 window: &mut Window,
18049 cx: &mut Context<Self>,
18050 ) {
18051 if let Some(project) = self.project.as_ref() {
18052 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18053 return;
18054 };
18055
18056 if buffer.read(cx).file().is_none() {
18057 return;
18058 }
18059
18060 let focused = self.focus_handle(cx).contains_focused(window, cx);
18061
18062 let project = project.clone();
18063 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18064 self.blame_subscription =
18065 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18066 self.blame = Some(blame);
18067 }
18068 }
18069
18070 fn toggle_git_blame_inline_internal(
18071 &mut self,
18072 user_triggered: bool,
18073 window: &mut Window,
18074 cx: &mut Context<Self>,
18075 ) {
18076 if self.git_blame_inline_enabled {
18077 self.git_blame_inline_enabled = false;
18078 self.show_git_blame_inline = false;
18079 self.show_git_blame_inline_delay_task.take();
18080 } else {
18081 self.git_blame_inline_enabled = true;
18082 self.start_git_blame_inline(user_triggered, window, cx);
18083 }
18084
18085 cx.notify();
18086 }
18087
18088 fn start_git_blame_inline(
18089 &mut self,
18090 user_triggered: bool,
18091 window: &mut Window,
18092 cx: &mut Context<Self>,
18093 ) {
18094 self.start_git_blame(user_triggered, window, cx);
18095
18096 if ProjectSettings::get_global(cx)
18097 .git
18098 .inline_blame_delay()
18099 .is_some()
18100 {
18101 self.start_inline_blame_timer(window, cx);
18102 } else {
18103 self.show_git_blame_inline = true
18104 }
18105 }
18106
18107 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18108 self.blame.as_ref()
18109 }
18110
18111 pub fn show_git_blame_gutter(&self) -> bool {
18112 self.show_git_blame_gutter
18113 }
18114
18115 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18116 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18117 }
18118
18119 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18120 self.show_git_blame_inline
18121 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18122 && !self.newest_selection_head_on_empty_line(cx)
18123 && self.has_blame_entries(cx)
18124 }
18125
18126 fn has_blame_entries(&self, cx: &App) -> bool {
18127 self.blame()
18128 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18129 }
18130
18131 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18132 let cursor_anchor = self.selections.newest_anchor().head();
18133
18134 let snapshot = self.buffer.read(cx).snapshot(cx);
18135 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18136
18137 snapshot.line_len(buffer_row) == 0
18138 }
18139
18140 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18141 let buffer_and_selection = maybe!({
18142 let selection = self.selections.newest::<Point>(cx);
18143 let selection_range = selection.range();
18144
18145 let multi_buffer = self.buffer().read(cx);
18146 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18147 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18148
18149 let (buffer, range, _) = if selection.reversed {
18150 buffer_ranges.first()
18151 } else {
18152 buffer_ranges.last()
18153 }?;
18154
18155 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18156 ..text::ToPoint::to_point(&range.end, &buffer).row;
18157 Some((
18158 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18159 selection,
18160 ))
18161 });
18162
18163 let Some((buffer, selection)) = buffer_and_selection else {
18164 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18165 };
18166
18167 let Some(project) = self.project.as_ref() else {
18168 return Task::ready(Err(anyhow!("editor does not have project")));
18169 };
18170
18171 project.update(cx, |project, cx| {
18172 project.get_permalink_to_line(&buffer, selection, cx)
18173 })
18174 }
18175
18176 pub fn copy_permalink_to_line(
18177 &mut self,
18178 _: &CopyPermalinkToLine,
18179 window: &mut Window,
18180 cx: &mut Context<Self>,
18181 ) {
18182 let permalink_task = self.get_permalink_to_line(cx);
18183 let workspace = self.workspace();
18184
18185 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18186 Ok(permalink) => {
18187 cx.update(|_, cx| {
18188 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18189 })
18190 .ok();
18191 }
18192 Err(err) => {
18193 let message = format!("Failed to copy permalink: {err}");
18194
18195 anyhow::Result::<()>::Err(err).log_err();
18196
18197 if let Some(workspace) = workspace {
18198 workspace
18199 .update_in(cx, |workspace, _, cx| {
18200 struct CopyPermalinkToLine;
18201
18202 workspace.show_toast(
18203 Toast::new(
18204 NotificationId::unique::<CopyPermalinkToLine>(),
18205 message,
18206 ),
18207 cx,
18208 )
18209 })
18210 .ok();
18211 }
18212 }
18213 })
18214 .detach();
18215 }
18216
18217 pub fn copy_file_location(
18218 &mut self,
18219 _: &CopyFileLocation,
18220 _: &mut Window,
18221 cx: &mut Context<Self>,
18222 ) {
18223 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18224 if let Some(file) = self.target_file(cx) {
18225 if let Some(path) = file.path().to_str() {
18226 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18227 }
18228 }
18229 }
18230
18231 pub fn open_permalink_to_line(
18232 &mut self,
18233 _: &OpenPermalinkToLine,
18234 window: &mut Window,
18235 cx: &mut Context<Self>,
18236 ) {
18237 let permalink_task = self.get_permalink_to_line(cx);
18238 let workspace = self.workspace();
18239
18240 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18241 Ok(permalink) => {
18242 cx.update(|_, cx| {
18243 cx.open_url(permalink.as_ref());
18244 })
18245 .ok();
18246 }
18247 Err(err) => {
18248 let message = format!("Failed to open permalink: {err}");
18249
18250 anyhow::Result::<()>::Err(err).log_err();
18251
18252 if let Some(workspace) = workspace {
18253 workspace
18254 .update(cx, |workspace, cx| {
18255 struct OpenPermalinkToLine;
18256
18257 workspace.show_toast(
18258 Toast::new(
18259 NotificationId::unique::<OpenPermalinkToLine>(),
18260 message,
18261 ),
18262 cx,
18263 )
18264 })
18265 .ok();
18266 }
18267 }
18268 })
18269 .detach();
18270 }
18271
18272 pub fn insert_uuid_v4(
18273 &mut self,
18274 _: &InsertUuidV4,
18275 window: &mut Window,
18276 cx: &mut Context<Self>,
18277 ) {
18278 self.insert_uuid(UuidVersion::V4, window, cx);
18279 }
18280
18281 pub fn insert_uuid_v7(
18282 &mut self,
18283 _: &InsertUuidV7,
18284 window: &mut Window,
18285 cx: &mut Context<Self>,
18286 ) {
18287 self.insert_uuid(UuidVersion::V7, window, cx);
18288 }
18289
18290 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18291 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18292 self.transact(window, cx, |this, window, cx| {
18293 let edits = this
18294 .selections
18295 .all::<Point>(cx)
18296 .into_iter()
18297 .map(|selection| {
18298 let uuid = match version {
18299 UuidVersion::V4 => uuid::Uuid::new_v4(),
18300 UuidVersion::V7 => uuid::Uuid::now_v7(),
18301 };
18302
18303 (selection.range(), uuid.to_string())
18304 });
18305 this.edit(edits, cx);
18306 this.refresh_inline_completion(true, false, window, cx);
18307 });
18308 }
18309
18310 pub fn open_selections_in_multibuffer(
18311 &mut self,
18312 _: &OpenSelectionsInMultibuffer,
18313 window: &mut Window,
18314 cx: &mut Context<Self>,
18315 ) {
18316 let multibuffer = self.buffer.read(cx);
18317
18318 let Some(buffer) = multibuffer.as_singleton() else {
18319 return;
18320 };
18321
18322 let Some(workspace) = self.workspace() else {
18323 return;
18324 };
18325
18326 let title = multibuffer.title(cx).to_string();
18327
18328 let locations = self
18329 .selections
18330 .all_anchors(cx)
18331 .into_iter()
18332 .map(|selection| Location {
18333 buffer: buffer.clone(),
18334 range: selection.start.text_anchor..selection.end.text_anchor,
18335 })
18336 .collect::<Vec<_>>();
18337
18338 cx.spawn_in(window, async move |_, cx| {
18339 workspace.update_in(cx, |workspace, window, cx| {
18340 Self::open_locations_in_multibuffer(
18341 workspace,
18342 locations,
18343 format!("Selections for '{title}'"),
18344 false,
18345 MultibufferSelectionMode::All,
18346 window,
18347 cx,
18348 );
18349 })
18350 })
18351 .detach();
18352 }
18353
18354 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18355 /// last highlight added will be used.
18356 ///
18357 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18358 pub fn highlight_rows<T: 'static>(
18359 &mut self,
18360 range: Range<Anchor>,
18361 color: Hsla,
18362 options: RowHighlightOptions,
18363 cx: &mut Context<Self>,
18364 ) {
18365 let snapshot = self.buffer().read(cx).snapshot(cx);
18366 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18367 let ix = row_highlights.binary_search_by(|highlight| {
18368 Ordering::Equal
18369 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18370 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18371 });
18372
18373 if let Err(mut ix) = ix {
18374 let index = post_inc(&mut self.highlight_order);
18375
18376 // If this range intersects with the preceding highlight, then merge it with
18377 // the preceding highlight. Otherwise insert a new highlight.
18378 let mut merged = false;
18379 if ix > 0 {
18380 let prev_highlight = &mut row_highlights[ix - 1];
18381 if prev_highlight
18382 .range
18383 .end
18384 .cmp(&range.start, &snapshot)
18385 .is_ge()
18386 {
18387 ix -= 1;
18388 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18389 prev_highlight.range.end = range.end;
18390 }
18391 merged = true;
18392 prev_highlight.index = index;
18393 prev_highlight.color = color;
18394 prev_highlight.options = options;
18395 }
18396 }
18397
18398 if !merged {
18399 row_highlights.insert(
18400 ix,
18401 RowHighlight {
18402 range: range.clone(),
18403 index,
18404 color,
18405 options,
18406 type_id: TypeId::of::<T>(),
18407 },
18408 );
18409 }
18410
18411 // If any of the following highlights intersect with this one, merge them.
18412 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18413 let highlight = &row_highlights[ix];
18414 if next_highlight
18415 .range
18416 .start
18417 .cmp(&highlight.range.end, &snapshot)
18418 .is_le()
18419 {
18420 if next_highlight
18421 .range
18422 .end
18423 .cmp(&highlight.range.end, &snapshot)
18424 .is_gt()
18425 {
18426 row_highlights[ix].range.end = next_highlight.range.end;
18427 }
18428 row_highlights.remove(ix + 1);
18429 } else {
18430 break;
18431 }
18432 }
18433 }
18434 }
18435
18436 /// Remove any highlighted row ranges of the given type that intersect the
18437 /// given ranges.
18438 pub fn remove_highlighted_rows<T: 'static>(
18439 &mut self,
18440 ranges_to_remove: Vec<Range<Anchor>>,
18441 cx: &mut Context<Self>,
18442 ) {
18443 let snapshot = self.buffer().read(cx).snapshot(cx);
18444 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18445 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18446 row_highlights.retain(|highlight| {
18447 while let Some(range_to_remove) = ranges_to_remove.peek() {
18448 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18449 Ordering::Less | Ordering::Equal => {
18450 ranges_to_remove.next();
18451 }
18452 Ordering::Greater => {
18453 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18454 Ordering::Less | Ordering::Equal => {
18455 return false;
18456 }
18457 Ordering::Greater => break,
18458 }
18459 }
18460 }
18461 }
18462
18463 true
18464 })
18465 }
18466
18467 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18468 pub fn clear_row_highlights<T: 'static>(&mut self) {
18469 self.highlighted_rows.remove(&TypeId::of::<T>());
18470 }
18471
18472 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18473 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18474 self.highlighted_rows
18475 .get(&TypeId::of::<T>())
18476 .map_or(&[] as &[_], |vec| vec.as_slice())
18477 .iter()
18478 .map(|highlight| (highlight.range.clone(), highlight.color))
18479 }
18480
18481 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18482 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18483 /// Allows to ignore certain kinds of highlights.
18484 pub fn highlighted_display_rows(
18485 &self,
18486 window: &mut Window,
18487 cx: &mut App,
18488 ) -> BTreeMap<DisplayRow, LineHighlight> {
18489 let snapshot = self.snapshot(window, cx);
18490 let mut used_highlight_orders = HashMap::default();
18491 self.highlighted_rows
18492 .iter()
18493 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18494 .fold(
18495 BTreeMap::<DisplayRow, LineHighlight>::new(),
18496 |mut unique_rows, highlight| {
18497 let start = highlight.range.start.to_display_point(&snapshot);
18498 let end = highlight.range.end.to_display_point(&snapshot);
18499 let start_row = start.row().0;
18500 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18501 && end.column() == 0
18502 {
18503 end.row().0.saturating_sub(1)
18504 } else {
18505 end.row().0
18506 };
18507 for row in start_row..=end_row {
18508 let used_index =
18509 used_highlight_orders.entry(row).or_insert(highlight.index);
18510 if highlight.index >= *used_index {
18511 *used_index = highlight.index;
18512 unique_rows.insert(
18513 DisplayRow(row),
18514 LineHighlight {
18515 include_gutter: highlight.options.include_gutter,
18516 border: None,
18517 background: highlight.color.into(),
18518 type_id: Some(highlight.type_id),
18519 },
18520 );
18521 }
18522 }
18523 unique_rows
18524 },
18525 )
18526 }
18527
18528 pub fn highlighted_display_row_for_autoscroll(
18529 &self,
18530 snapshot: &DisplaySnapshot,
18531 ) -> Option<DisplayRow> {
18532 self.highlighted_rows
18533 .values()
18534 .flat_map(|highlighted_rows| highlighted_rows.iter())
18535 .filter_map(|highlight| {
18536 if highlight.options.autoscroll {
18537 Some(highlight.range.start.to_display_point(snapshot).row())
18538 } else {
18539 None
18540 }
18541 })
18542 .min()
18543 }
18544
18545 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18546 self.highlight_background::<SearchWithinRange>(
18547 ranges,
18548 |theme| theme.colors().editor_document_highlight_read_background,
18549 cx,
18550 )
18551 }
18552
18553 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18554 self.breadcrumb_header = Some(new_header);
18555 }
18556
18557 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18558 self.clear_background_highlights::<SearchWithinRange>(cx);
18559 }
18560
18561 pub fn highlight_background<T: 'static>(
18562 &mut self,
18563 ranges: &[Range<Anchor>],
18564 color_fetcher: fn(&Theme) -> Hsla,
18565 cx: &mut Context<Self>,
18566 ) {
18567 let highlights = ranges
18568 .iter()
18569 .map(|range| BackgroundHighlight {
18570 range: range.clone(),
18571 color_fetcher,
18572 })
18573 .collect();
18574 self.background_highlights
18575 .insert(TypeId::of::<T>(), highlights);
18576 self.scrollbar_marker_state.dirty = true;
18577 cx.notify();
18578 }
18579
18580 pub fn highlight_background_ranges<T: 'static>(
18581 &mut self,
18582 background_highlights: Vec<BackgroundHighlight>,
18583 cx: &mut Context<'_, Editor>,
18584 ) {
18585 self.background_highlights
18586 .insert(TypeId::of::<T>(), background_highlights);
18587 self.scrollbar_marker_state.dirty = true;
18588 cx.notify();
18589 }
18590
18591 pub fn clear_background_highlights<T: 'static>(
18592 &mut self,
18593 cx: &mut Context<Self>,
18594 ) -> Option<Vec<BackgroundHighlight>> {
18595 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
18596 if !text_highlights.is_empty() {
18597 self.scrollbar_marker_state.dirty = true;
18598 cx.notify();
18599 }
18600 Some(text_highlights)
18601 }
18602
18603 pub fn highlight_gutter<T: 'static>(
18604 &mut self,
18605 ranges: impl Into<Vec<Range<Anchor>>>,
18606 color_fetcher: fn(&App) -> Hsla,
18607 cx: &mut Context<Self>,
18608 ) {
18609 self.gutter_highlights
18610 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
18611 cx.notify();
18612 }
18613
18614 pub fn clear_gutter_highlights<T: 'static>(
18615 &mut self,
18616 cx: &mut Context<Self>,
18617 ) -> Option<GutterHighlight> {
18618 cx.notify();
18619 self.gutter_highlights.remove(&TypeId::of::<T>())
18620 }
18621
18622 pub fn insert_gutter_highlight<T: 'static>(
18623 &mut self,
18624 range: Range<Anchor>,
18625 color_fetcher: fn(&App) -> Hsla,
18626 cx: &mut Context<Self>,
18627 ) {
18628 let snapshot = self.buffer().read(cx).snapshot(cx);
18629 let mut highlights = self
18630 .gutter_highlights
18631 .remove(&TypeId::of::<T>())
18632 .map(|(_, highlights)| highlights)
18633 .unwrap_or_default();
18634 let ix = highlights.binary_search_by(|highlight| {
18635 Ordering::Equal
18636 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
18637 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
18638 });
18639 if let Err(ix) = ix {
18640 highlights.insert(ix, range);
18641 }
18642 self.gutter_highlights
18643 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
18644 }
18645
18646 pub fn remove_gutter_highlights<T: 'static>(
18647 &mut self,
18648 ranges_to_remove: Vec<Range<Anchor>>,
18649 cx: &mut Context<Self>,
18650 ) {
18651 let snapshot = self.buffer().read(cx).snapshot(cx);
18652 let Some((color_fetcher, mut gutter_highlights)) =
18653 self.gutter_highlights.remove(&TypeId::of::<T>())
18654 else {
18655 return;
18656 };
18657 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18658 gutter_highlights.retain(|highlight| {
18659 while let Some(range_to_remove) = ranges_to_remove.peek() {
18660 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
18661 Ordering::Less | Ordering::Equal => {
18662 ranges_to_remove.next();
18663 }
18664 Ordering::Greater => {
18665 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
18666 Ordering::Less | Ordering::Equal => {
18667 return false;
18668 }
18669 Ordering::Greater => break,
18670 }
18671 }
18672 }
18673 }
18674
18675 true
18676 });
18677 self.gutter_highlights
18678 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
18679 }
18680
18681 #[cfg(feature = "test-support")]
18682 pub fn all_text_background_highlights(
18683 &self,
18684 window: &mut Window,
18685 cx: &mut Context<Self>,
18686 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18687 let snapshot = self.snapshot(window, cx);
18688 let buffer = &snapshot.buffer_snapshot;
18689 let start = buffer.anchor_before(0);
18690 let end = buffer.anchor_after(buffer.len());
18691 let theme = cx.theme();
18692 self.background_highlights_in_range(start..end, &snapshot, theme)
18693 }
18694
18695 #[cfg(feature = "test-support")]
18696 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18697 let snapshot = self.buffer().read(cx).snapshot(cx);
18698
18699 let highlights = self
18700 .background_highlights
18701 .get(&TypeId::of::<items::BufferSearchHighlights>());
18702
18703 if let Some(highlights) = highlights {
18704 highlights
18705 .iter()
18706 .map(|highlight| {
18707 highlight.range.start.to_point(&snapshot)
18708 ..highlight.range.end.to_point(&snapshot)
18709 })
18710 .collect_vec()
18711 } else {
18712 vec![]
18713 }
18714 }
18715
18716 fn document_highlights_for_position<'a>(
18717 &'a self,
18718 position: Anchor,
18719 buffer: &'a MultiBufferSnapshot,
18720 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18721 let read_highlights = self
18722 .background_highlights
18723 .get(&TypeId::of::<DocumentHighlightRead>());
18724 let write_highlights = self
18725 .background_highlights
18726 .get(&TypeId::of::<DocumentHighlightWrite>());
18727 let left_position = position.bias_left(buffer);
18728 let right_position = position.bias_right(buffer);
18729 read_highlights
18730 .into_iter()
18731 .chain(write_highlights)
18732 .flat_map(move |highlights| {
18733 let start_ix = match highlights.binary_search_by(|probe| {
18734 let cmp = probe.range.end.cmp(&left_position, buffer);
18735 if cmp.is_ge() {
18736 Ordering::Greater
18737 } else {
18738 Ordering::Less
18739 }
18740 }) {
18741 Ok(i) | Err(i) => i,
18742 };
18743
18744 highlights[start_ix..]
18745 .iter()
18746 .take_while(move |highlight| {
18747 highlight.range.start.cmp(&right_position, buffer).is_le()
18748 })
18749 .map(|highlight| &highlight.range)
18750 })
18751 }
18752
18753 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18754 self.background_highlights
18755 .get(&TypeId::of::<T>())
18756 .map_or(false, |highlights| !highlights.is_empty())
18757 }
18758
18759 pub fn background_highlights_in_range(
18760 &self,
18761 search_range: Range<Anchor>,
18762 display_snapshot: &DisplaySnapshot,
18763 theme: &Theme,
18764 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18765 let mut results = Vec::new();
18766 for highlights in self.background_highlights.values() {
18767 let start_ix = match highlights.binary_search_by(|probe| {
18768 let cmp = probe
18769 .range
18770 .end
18771 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18772 if cmp.is_gt() {
18773 Ordering::Greater
18774 } else {
18775 Ordering::Less
18776 }
18777 }) {
18778 Ok(i) | Err(i) => i,
18779 };
18780 for highlight in &highlights[start_ix..] {
18781 if highlight
18782 .range
18783 .start
18784 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18785 .is_ge()
18786 {
18787 break;
18788 }
18789
18790 let start = highlight.range.start.to_display_point(display_snapshot);
18791 let end = highlight.range.end.to_display_point(display_snapshot);
18792 let color = (highlight.color_fetcher)(theme);
18793 results.push((start..end, color))
18794 }
18795 }
18796 results
18797 }
18798
18799 pub fn background_highlight_row_ranges<T: 'static>(
18800 &self,
18801 search_range: Range<Anchor>,
18802 display_snapshot: &DisplaySnapshot,
18803 count: usize,
18804 ) -> Vec<RangeInclusive<DisplayPoint>> {
18805 let mut results = Vec::new();
18806 let Some(highlights) = self.background_highlights.get(&TypeId::of::<T>()) else {
18807 return vec![];
18808 };
18809
18810 let start_ix = match highlights.binary_search_by(|probe| {
18811 let cmp = probe
18812 .range
18813 .end
18814 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18815 if cmp.is_gt() {
18816 Ordering::Greater
18817 } else {
18818 Ordering::Less
18819 }
18820 }) {
18821 Ok(i) | Err(i) => i,
18822 };
18823 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18824 if let (Some(start_display), Some(end_display)) = (start, end) {
18825 results.push(
18826 start_display.to_display_point(display_snapshot)
18827 ..=end_display.to_display_point(display_snapshot),
18828 );
18829 }
18830 };
18831 let mut start_row: Option<Point> = None;
18832 let mut end_row: Option<Point> = None;
18833 if highlights.len() > count {
18834 return Vec::new();
18835 }
18836 for highlight in &highlights[start_ix..] {
18837 if highlight
18838 .range
18839 .start
18840 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18841 .is_ge()
18842 {
18843 break;
18844 }
18845 let end = highlight
18846 .range
18847 .end
18848 .to_point(&display_snapshot.buffer_snapshot);
18849 if let Some(current_row) = &end_row {
18850 if end.row == current_row.row {
18851 continue;
18852 }
18853 }
18854 let start = highlight
18855 .range
18856 .start
18857 .to_point(&display_snapshot.buffer_snapshot);
18858 if start_row.is_none() {
18859 assert_eq!(end_row, None);
18860 start_row = Some(start);
18861 end_row = Some(end);
18862 continue;
18863 }
18864 if let Some(current_end) = end_row.as_mut() {
18865 if start.row > current_end.row + 1 {
18866 push_region(start_row, end_row);
18867 start_row = Some(start);
18868 end_row = Some(end);
18869 } else {
18870 // Merge two hunks.
18871 *current_end = end;
18872 }
18873 } else {
18874 unreachable!();
18875 }
18876 }
18877 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18878 push_region(start_row, end_row);
18879 results
18880 }
18881
18882 pub fn gutter_highlights_in_range(
18883 &self,
18884 search_range: Range<Anchor>,
18885 display_snapshot: &DisplaySnapshot,
18886 cx: &App,
18887 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18888 let mut results = Vec::new();
18889 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18890 let color = color_fetcher(cx);
18891 let start_ix = match ranges.binary_search_by(|probe| {
18892 let cmp = probe
18893 .end
18894 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18895 if cmp.is_gt() {
18896 Ordering::Greater
18897 } else {
18898 Ordering::Less
18899 }
18900 }) {
18901 Ok(i) | Err(i) => i,
18902 };
18903 for range in &ranges[start_ix..] {
18904 if range
18905 .start
18906 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18907 .is_ge()
18908 {
18909 break;
18910 }
18911
18912 let start = range.start.to_display_point(display_snapshot);
18913 let end = range.end.to_display_point(display_snapshot);
18914 results.push((start..end, color))
18915 }
18916 }
18917 results
18918 }
18919
18920 /// Get the text ranges corresponding to the redaction query
18921 pub fn redacted_ranges(
18922 &self,
18923 search_range: Range<Anchor>,
18924 display_snapshot: &DisplaySnapshot,
18925 cx: &App,
18926 ) -> Vec<Range<DisplayPoint>> {
18927 display_snapshot
18928 .buffer_snapshot
18929 .redacted_ranges(search_range, |file| {
18930 if let Some(file) = file {
18931 file.is_private()
18932 && EditorSettings::get(
18933 Some(SettingsLocation {
18934 worktree_id: file.worktree_id(cx),
18935 path: file.path().as_ref(),
18936 }),
18937 cx,
18938 )
18939 .redact_private_values
18940 } else {
18941 false
18942 }
18943 })
18944 .map(|range| {
18945 range.start.to_display_point(display_snapshot)
18946 ..range.end.to_display_point(display_snapshot)
18947 })
18948 .collect()
18949 }
18950
18951 pub fn highlight_text<T: 'static>(
18952 &mut self,
18953 ranges: Vec<(Range<Anchor>, HighlightStyle)>,
18954 cx: &mut Context<Self>,
18955 ) {
18956 self.display_map
18957 .update(cx, |map, _| map.highlight_text(TypeId::of::<T>(), ranges));
18958 cx.notify();
18959 }
18960
18961 pub(crate) fn highlight_inlays<T: 'static>(
18962 &mut self,
18963 highlights: Vec<InlayHighlight>,
18964 style: HighlightStyle,
18965 cx: &mut Context<Self>,
18966 ) {
18967 self.display_map.update(cx, |map, _| {
18968 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18969 });
18970 cx.notify();
18971 }
18972
18973 pub fn text_highlights<'a, T: 'static>(
18974 &'a self,
18975 cx: &'a App,
18976 ) -> Option<&'a [(Range<Anchor>, HighlightStyle)]> {
18977 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18978 }
18979
18980 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18981 let cleared = self
18982 .display_map
18983 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18984 if cleared {
18985 cx.notify();
18986 }
18987 }
18988
18989 pub fn remove_text_highlights<T: 'static>(
18990 &mut self,
18991 cx: &mut Context<Self>,
18992 ) -> Option<Vec<(Range<Anchor>, HighlightStyle)>> {
18993 self.display_map
18994 .update(cx, |map, _| map.remove_text_highlights(TypeId::of::<T>()))
18995 }
18996
18997 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18998 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18999 && self.focus_handle.is_focused(window)
19000 }
19001
19002 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19003 self.show_cursor_when_unfocused = is_enabled;
19004 cx.notify();
19005 }
19006
19007 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19008 cx.notify();
19009 }
19010
19011 fn on_debug_session_event(
19012 &mut self,
19013 _session: Entity<Session>,
19014 event: &SessionEvent,
19015 cx: &mut Context<Self>,
19016 ) {
19017 match event {
19018 SessionEvent::InvalidateInlineValue => {
19019 self.refresh_inline_values(cx);
19020 }
19021 _ => {}
19022 }
19023 }
19024
19025 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19026 let Some(project) = self.project.clone() else {
19027 return;
19028 };
19029
19030 if !self.inline_value_cache.enabled {
19031 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19032 self.splice_inlays(&inlays, Vec::new(), cx);
19033 return;
19034 }
19035
19036 let current_execution_position = self
19037 .highlighted_rows
19038 .get(&TypeId::of::<ActiveDebugLine>())
19039 .and_then(|lines| lines.last().map(|line| line.range.start));
19040
19041 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19042 let inline_values = editor
19043 .update(cx, |editor, cx| {
19044 let Some(current_execution_position) = current_execution_position else {
19045 return Some(Task::ready(Ok(Vec::new())));
19046 };
19047
19048 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19049 let snapshot = buffer.snapshot(cx);
19050
19051 let excerpt = snapshot.excerpt_containing(
19052 current_execution_position..current_execution_position,
19053 )?;
19054
19055 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19056 })?;
19057
19058 let range =
19059 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19060
19061 project.inline_values(buffer, range, cx)
19062 })
19063 .ok()
19064 .flatten()?
19065 .await
19066 .context("refreshing debugger inlays")
19067 .log_err()?;
19068
19069 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19070
19071 for (buffer_id, inline_value) in inline_values
19072 .into_iter()
19073 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19074 {
19075 buffer_inline_values
19076 .entry(buffer_id)
19077 .or_default()
19078 .push(inline_value);
19079 }
19080
19081 editor
19082 .update(cx, |editor, cx| {
19083 let snapshot = editor.buffer.read(cx).snapshot(cx);
19084 let mut new_inlays = Vec::default();
19085
19086 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19087 let buffer_id = buffer_snapshot.remote_id();
19088 buffer_inline_values
19089 .get(&buffer_id)
19090 .into_iter()
19091 .flatten()
19092 .for_each(|hint| {
19093 let inlay = Inlay::debugger(
19094 post_inc(&mut editor.next_inlay_id),
19095 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19096 hint.text(),
19097 );
19098
19099 new_inlays.push(inlay);
19100 });
19101 }
19102
19103 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19104 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19105
19106 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19107 })
19108 .ok()?;
19109 Some(())
19110 });
19111 }
19112
19113 fn on_buffer_event(
19114 &mut self,
19115 multibuffer: &Entity<MultiBuffer>,
19116 event: &multi_buffer::Event,
19117 window: &mut Window,
19118 cx: &mut Context<Self>,
19119 ) {
19120 match event {
19121 multi_buffer::Event::Edited {
19122 singleton_buffer_edited,
19123 edited_buffer,
19124 } => {
19125 self.scrollbar_marker_state.dirty = true;
19126 self.active_indent_guides_state.dirty = true;
19127 self.refresh_active_diagnostics(cx);
19128 self.refresh_code_actions(window, cx);
19129 self.refresh_selected_text_highlights(true, window, cx);
19130 refresh_matching_bracket_highlights(self, window, cx);
19131 if self.has_active_inline_completion() {
19132 self.update_visible_inline_completion(window, cx);
19133 }
19134 if let Some(project) = self.project.as_ref() {
19135 if let Some(edited_buffer) = edited_buffer {
19136 project.update(cx, |project, cx| {
19137 self.registered_buffers
19138 .entry(edited_buffer.read(cx).remote_id())
19139 .or_insert_with(|| {
19140 project
19141 .register_buffer_with_language_servers(&edited_buffer, cx)
19142 });
19143 });
19144 }
19145 }
19146 cx.emit(EditorEvent::BufferEdited);
19147 cx.emit(SearchEvent::MatchesInvalidated);
19148
19149 if let Some(buffer) = edited_buffer {
19150 self.update_lsp_data(true, None, Some(buffer.read(cx).remote_id()), window, cx);
19151 }
19152
19153 if *singleton_buffer_edited {
19154 if let Some(buffer) = edited_buffer {
19155 if buffer.read(cx).file().is_none() {
19156 cx.emit(EditorEvent::TitleChanged);
19157 }
19158 }
19159 if let Some(project) = &self.project {
19160 #[allow(clippy::mutable_key_type)]
19161 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19162 multibuffer
19163 .all_buffers()
19164 .into_iter()
19165 .filter_map(|buffer| {
19166 buffer.update(cx, |buffer, cx| {
19167 let language = buffer.language()?;
19168 let should_discard = project.update(cx, |project, cx| {
19169 project.is_local()
19170 && !project.has_language_servers_for(buffer, cx)
19171 });
19172 should_discard.not().then_some(language.clone())
19173 })
19174 })
19175 .collect::<HashSet<_>>()
19176 });
19177 if !languages_affected.is_empty() {
19178 self.refresh_inlay_hints(
19179 InlayHintRefreshReason::BufferEdited(languages_affected),
19180 cx,
19181 );
19182 }
19183 }
19184 }
19185
19186 let Some(project) = &self.project else { return };
19187 let (telemetry, is_via_ssh) = {
19188 let project = project.read(cx);
19189 let telemetry = project.client().telemetry().clone();
19190 let is_via_ssh = project.is_via_ssh();
19191 (telemetry, is_via_ssh)
19192 };
19193 refresh_linked_ranges(self, window, cx);
19194 telemetry.log_edit_event("editor", is_via_ssh);
19195 }
19196 multi_buffer::Event::ExcerptsAdded {
19197 buffer,
19198 predecessor,
19199 excerpts,
19200 } => {
19201 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19202 let buffer_id = buffer.read(cx).remote_id();
19203 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19204 if let Some(project) = &self.project {
19205 update_uncommitted_diff_for_buffer(
19206 cx.entity(),
19207 project,
19208 [buffer.clone()],
19209 self.buffer.clone(),
19210 cx,
19211 )
19212 .detach();
19213 }
19214 }
19215 self.update_lsp_data(false, None, Some(buffer_id), window, cx);
19216 cx.emit(EditorEvent::ExcerptsAdded {
19217 buffer: buffer.clone(),
19218 predecessor: *predecessor,
19219 excerpts: excerpts.clone(),
19220 });
19221 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19222 }
19223 multi_buffer::Event::ExcerptsRemoved {
19224 ids,
19225 removed_buffer_ids,
19226 } => {
19227 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19228 let buffer = self.buffer.read(cx);
19229 self.registered_buffers
19230 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19231 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19232 cx.emit(EditorEvent::ExcerptsRemoved {
19233 ids: ids.clone(),
19234 removed_buffer_ids: removed_buffer_ids.clone(),
19235 });
19236 }
19237 multi_buffer::Event::ExcerptsEdited {
19238 excerpt_ids,
19239 buffer_ids,
19240 } => {
19241 self.display_map.update(cx, |map, cx| {
19242 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19243 });
19244 cx.emit(EditorEvent::ExcerptsEdited {
19245 ids: excerpt_ids.clone(),
19246 });
19247 }
19248 multi_buffer::Event::ExcerptsExpanded { ids } => {
19249 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19250 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19251 }
19252 multi_buffer::Event::Reparsed(buffer_id) => {
19253 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19254 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19255
19256 cx.emit(EditorEvent::Reparsed(*buffer_id));
19257 }
19258 multi_buffer::Event::DiffHunksToggled => {
19259 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19260 }
19261 multi_buffer::Event::LanguageChanged(buffer_id) => {
19262 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19263 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19264 cx.emit(EditorEvent::Reparsed(*buffer_id));
19265 cx.notify();
19266 }
19267 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19268 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19269 multi_buffer::Event::FileHandleChanged
19270 | multi_buffer::Event::Reloaded
19271 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19272 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19273 multi_buffer::Event::DiagnosticsUpdated => {
19274 self.update_diagnostics_state(window, cx);
19275 }
19276 _ => {}
19277 };
19278 }
19279
19280 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19281 self.refresh_active_diagnostics(cx);
19282 self.refresh_inline_diagnostics(true, window, cx);
19283 self.scrollbar_marker_state.dirty = true;
19284 cx.notify();
19285 }
19286
19287 pub fn start_temporary_diff_override(&mut self) {
19288 self.load_diff_task.take();
19289 self.temporary_diff_override = true;
19290 }
19291
19292 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19293 self.temporary_diff_override = false;
19294 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19295 self.buffer.update(cx, |buffer, cx| {
19296 buffer.set_all_diff_hunks_collapsed(cx);
19297 });
19298
19299 if let Some(project) = self.project.clone() {
19300 self.load_diff_task = Some(
19301 update_uncommitted_diff_for_buffer(
19302 cx.entity(),
19303 &project,
19304 self.buffer.read(cx).all_buffers(),
19305 self.buffer.clone(),
19306 cx,
19307 )
19308 .shared(),
19309 );
19310 }
19311 }
19312
19313 fn on_display_map_changed(
19314 &mut self,
19315 _: Entity<DisplayMap>,
19316 _: &mut Window,
19317 cx: &mut Context<Self>,
19318 ) {
19319 cx.notify();
19320 }
19321
19322 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19323 let new_severity = if self.diagnostics_enabled() {
19324 EditorSettings::get_global(cx)
19325 .diagnostics_max_severity
19326 .unwrap_or(DiagnosticSeverity::Hint)
19327 } else {
19328 DiagnosticSeverity::Off
19329 };
19330 self.set_max_diagnostics_severity(new_severity, cx);
19331 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19332 self.update_edit_prediction_settings(cx);
19333 self.refresh_inline_completion(true, false, window, cx);
19334 self.refresh_inlay_hints(
19335 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19336 self.selections.newest_anchor().head(),
19337 &self.buffer.read(cx).snapshot(cx),
19338 cx,
19339 )),
19340 cx,
19341 );
19342
19343 let old_cursor_shape = self.cursor_shape;
19344
19345 {
19346 let editor_settings = EditorSettings::get_global(cx);
19347 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19348 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19349 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19350 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19351 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19352 }
19353
19354 if old_cursor_shape != self.cursor_shape {
19355 cx.emit(EditorEvent::CursorShapeChanged);
19356 }
19357
19358 let project_settings = ProjectSettings::get_global(cx);
19359 self.serialize_dirty_buffers =
19360 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19361
19362 if self.mode.is_full() {
19363 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19364 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19365 if self.show_inline_diagnostics != show_inline_diagnostics {
19366 self.show_inline_diagnostics = show_inline_diagnostics;
19367 self.refresh_inline_diagnostics(false, window, cx);
19368 }
19369
19370 if self.git_blame_inline_enabled != inline_blame_enabled {
19371 self.toggle_git_blame_inline_internal(false, window, cx);
19372 }
19373
19374 let minimap_settings = EditorSettings::get_global(cx).minimap;
19375 if self.minimap_visibility != MinimapVisibility::Disabled {
19376 if self.minimap_visibility.settings_visibility()
19377 != minimap_settings.minimap_enabled()
19378 {
19379 self.set_minimap_visibility(
19380 MinimapVisibility::for_mode(self.mode(), cx),
19381 window,
19382 cx,
19383 );
19384 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19385 minimap_entity.update(cx, |minimap_editor, cx| {
19386 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19387 })
19388 }
19389 }
19390 }
19391
19392 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
19393 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
19394 }) {
19395 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
19396 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
19397 }
19398 self.refresh_colors(true, None, None, window, cx);
19399 }
19400
19401 cx.notify();
19402 }
19403
19404 pub fn set_searchable(&mut self, searchable: bool) {
19405 self.searchable = searchable;
19406 }
19407
19408 pub fn searchable(&self) -> bool {
19409 self.searchable
19410 }
19411
19412 fn open_proposed_changes_editor(
19413 &mut self,
19414 _: &OpenProposedChangesEditor,
19415 window: &mut Window,
19416 cx: &mut Context<Self>,
19417 ) {
19418 let Some(workspace) = self.workspace() else {
19419 cx.propagate();
19420 return;
19421 };
19422
19423 let selections = self.selections.all::<usize>(cx);
19424 let multi_buffer = self.buffer.read(cx);
19425 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19426 let mut new_selections_by_buffer = HashMap::default();
19427 for selection in selections {
19428 for (buffer, range, _) in
19429 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19430 {
19431 let mut range = range.to_point(buffer);
19432 range.start.column = 0;
19433 range.end.column = buffer.line_len(range.end.row);
19434 new_selections_by_buffer
19435 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19436 .or_insert(Vec::new())
19437 .push(range)
19438 }
19439 }
19440
19441 let proposed_changes_buffers = new_selections_by_buffer
19442 .into_iter()
19443 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19444 .collect::<Vec<_>>();
19445 let proposed_changes_editor = cx.new(|cx| {
19446 ProposedChangesEditor::new(
19447 "Proposed changes",
19448 proposed_changes_buffers,
19449 self.project.clone(),
19450 window,
19451 cx,
19452 )
19453 });
19454
19455 window.defer(cx, move |window, cx| {
19456 workspace.update(cx, |workspace, cx| {
19457 workspace.active_pane().update(cx, |pane, cx| {
19458 pane.add_item(
19459 Box::new(proposed_changes_editor),
19460 true,
19461 true,
19462 None,
19463 window,
19464 cx,
19465 );
19466 });
19467 });
19468 });
19469 }
19470
19471 pub fn open_excerpts_in_split(
19472 &mut self,
19473 _: &OpenExcerptsSplit,
19474 window: &mut Window,
19475 cx: &mut Context<Self>,
19476 ) {
19477 self.open_excerpts_common(None, true, window, cx)
19478 }
19479
19480 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19481 self.open_excerpts_common(None, false, window, cx)
19482 }
19483
19484 fn open_excerpts_common(
19485 &mut self,
19486 jump_data: Option<JumpData>,
19487 split: bool,
19488 window: &mut Window,
19489 cx: &mut Context<Self>,
19490 ) {
19491 let Some(workspace) = self.workspace() else {
19492 cx.propagate();
19493 return;
19494 };
19495
19496 if self.buffer.read(cx).is_singleton() {
19497 cx.propagate();
19498 return;
19499 }
19500
19501 let mut new_selections_by_buffer = HashMap::default();
19502 match &jump_data {
19503 Some(JumpData::MultiBufferPoint {
19504 excerpt_id,
19505 position,
19506 anchor,
19507 line_offset_from_top,
19508 }) => {
19509 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19510 if let Some(buffer) = multi_buffer_snapshot
19511 .buffer_id_for_excerpt(*excerpt_id)
19512 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19513 {
19514 let buffer_snapshot = buffer.read(cx).snapshot();
19515 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19516 language::ToPoint::to_point(anchor, &buffer_snapshot)
19517 } else {
19518 buffer_snapshot.clip_point(*position, Bias::Left)
19519 };
19520 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19521 new_selections_by_buffer.insert(
19522 buffer,
19523 (
19524 vec![jump_to_offset..jump_to_offset],
19525 Some(*line_offset_from_top),
19526 ),
19527 );
19528 }
19529 }
19530 Some(JumpData::MultiBufferRow {
19531 row,
19532 line_offset_from_top,
19533 }) => {
19534 let point = MultiBufferPoint::new(row.0, 0);
19535 if let Some((buffer, buffer_point, _)) =
19536 self.buffer.read(cx).point_to_buffer_point(point, cx)
19537 {
19538 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
19539 new_selections_by_buffer
19540 .entry(buffer)
19541 .or_insert((Vec::new(), Some(*line_offset_from_top)))
19542 .0
19543 .push(buffer_offset..buffer_offset)
19544 }
19545 }
19546 None => {
19547 let selections = self.selections.all::<usize>(cx);
19548 let multi_buffer = self.buffer.read(cx);
19549 for selection in selections {
19550 for (snapshot, range, _, anchor) in multi_buffer
19551 .snapshot(cx)
19552 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
19553 {
19554 if let Some(anchor) = anchor {
19555 // selection is in a deleted hunk
19556 let Some(buffer_id) = anchor.buffer_id else {
19557 continue;
19558 };
19559 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
19560 continue;
19561 };
19562 let offset = text::ToOffset::to_offset(
19563 &anchor.text_anchor,
19564 &buffer_handle.read(cx).snapshot(),
19565 );
19566 let range = offset..offset;
19567 new_selections_by_buffer
19568 .entry(buffer_handle)
19569 .or_insert((Vec::new(), None))
19570 .0
19571 .push(range)
19572 } else {
19573 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
19574 else {
19575 continue;
19576 };
19577 new_selections_by_buffer
19578 .entry(buffer_handle)
19579 .or_insert((Vec::new(), None))
19580 .0
19581 .push(range)
19582 }
19583 }
19584 }
19585 }
19586 }
19587
19588 new_selections_by_buffer
19589 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19590
19591 if new_selections_by_buffer.is_empty() {
19592 return;
19593 }
19594
19595 // We defer the pane interaction because we ourselves are a workspace item
19596 // and activating a new item causes the pane to call a method on us reentrantly,
19597 // which panics if we're on the stack.
19598 window.defer(cx, move |window, cx| {
19599 workspace.update(cx, |workspace, cx| {
19600 let pane = if split {
19601 workspace.adjacent_pane(window, cx)
19602 } else {
19603 workspace.active_pane().clone()
19604 };
19605
19606 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19607 let editor = buffer
19608 .read(cx)
19609 .file()
19610 .is_none()
19611 .then(|| {
19612 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19613 // so `workspace.open_project_item` will never find them, always opening a new editor.
19614 // Instead, we try to activate the existing editor in the pane first.
19615 let (editor, pane_item_index) =
19616 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19617 let editor = item.downcast::<Editor>()?;
19618 let singleton_buffer =
19619 editor.read(cx).buffer().read(cx).as_singleton()?;
19620 if singleton_buffer == buffer {
19621 Some((editor, i))
19622 } else {
19623 None
19624 }
19625 })?;
19626 pane.update(cx, |pane, cx| {
19627 pane.activate_item(pane_item_index, true, true, window, cx)
19628 });
19629 Some(editor)
19630 })
19631 .flatten()
19632 .unwrap_or_else(|| {
19633 workspace.open_project_item::<Self>(
19634 pane.clone(),
19635 buffer,
19636 true,
19637 true,
19638 window,
19639 cx,
19640 )
19641 });
19642
19643 editor.update(cx, |editor, cx| {
19644 let autoscroll = match scroll_offset {
19645 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19646 None => Autoscroll::newest(),
19647 };
19648 let nav_history = editor.nav_history.take();
19649 editor.change_selections(Some(autoscroll), window, cx, |s| {
19650 s.select_ranges(ranges);
19651 });
19652 editor.nav_history = nav_history;
19653 });
19654 }
19655 })
19656 });
19657 }
19658
19659 // For now, don't allow opening excerpts in buffers that aren't backed by
19660 // regular project files.
19661 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19662 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19663 }
19664
19665 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19666 let snapshot = self.buffer.read(cx).read(cx);
19667 let ranges = self.text_highlights::<InputComposition>(cx)?;
19668 Some(
19669 ranges
19670 .iter()
19671 .map(move |(range, _)| {
19672 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19673 })
19674 .collect(),
19675 )
19676 }
19677
19678 fn selection_replacement_ranges(
19679 &self,
19680 range: Range<OffsetUtf16>,
19681 cx: &mut App,
19682 ) -> Vec<Range<OffsetUtf16>> {
19683 let selections = self.selections.all::<OffsetUtf16>(cx);
19684 let newest_selection = selections
19685 .iter()
19686 .max_by_key(|selection| selection.id)
19687 .unwrap();
19688 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19689 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19690 let snapshot = self.buffer.read(cx).read(cx);
19691 selections
19692 .into_iter()
19693 .map(|mut selection| {
19694 selection.start.0 =
19695 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19696 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19697 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19698 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19699 })
19700 .collect()
19701 }
19702
19703 fn report_editor_event(
19704 &self,
19705 event_type: &'static str,
19706 file_extension: Option<String>,
19707 cx: &App,
19708 ) {
19709 if cfg!(any(test, feature = "test-support")) {
19710 return;
19711 }
19712
19713 let Some(project) = &self.project else { return };
19714
19715 // If None, we are in a file without an extension
19716 let file = self
19717 .buffer
19718 .read(cx)
19719 .as_singleton()
19720 .and_then(|b| b.read(cx).file());
19721 let file_extension = file_extension.or(file
19722 .as_ref()
19723 .and_then(|file| Path::new(file.file_name(cx)).extension())
19724 .and_then(|e| e.to_str())
19725 .map(|a| a.to_string()));
19726
19727 let vim_mode = vim_enabled(cx);
19728
19729 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19730 let copilot_enabled = edit_predictions_provider
19731 == language::language_settings::EditPredictionProvider::Copilot;
19732 let copilot_enabled_for_language = self
19733 .buffer
19734 .read(cx)
19735 .language_settings(cx)
19736 .show_edit_predictions;
19737
19738 let project = project.read(cx);
19739 telemetry::event!(
19740 event_type,
19741 file_extension,
19742 vim_mode,
19743 copilot_enabled,
19744 copilot_enabled_for_language,
19745 edit_predictions_provider,
19746 is_via_ssh = project.is_via_ssh(),
19747 );
19748 }
19749
19750 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19751 /// with each line being an array of {text, highlight} objects.
19752 fn copy_highlight_json(
19753 &mut self,
19754 _: &CopyHighlightJson,
19755 window: &mut Window,
19756 cx: &mut Context<Self>,
19757 ) {
19758 #[derive(Serialize)]
19759 struct Chunk<'a> {
19760 text: String,
19761 highlight: Option<&'a str>,
19762 }
19763
19764 let snapshot = self.buffer.read(cx).snapshot(cx);
19765 let range = self
19766 .selected_text_range(false, window, cx)
19767 .and_then(|selection| {
19768 if selection.range.is_empty() {
19769 None
19770 } else {
19771 Some(selection.range)
19772 }
19773 })
19774 .unwrap_or_else(|| 0..snapshot.len());
19775
19776 let chunks = snapshot.chunks(range, true);
19777 let mut lines = Vec::new();
19778 let mut line: VecDeque<Chunk> = VecDeque::new();
19779
19780 let Some(style) = self.style.as_ref() else {
19781 return;
19782 };
19783
19784 for chunk in chunks {
19785 let highlight = chunk
19786 .syntax_highlight_id
19787 .and_then(|id| id.name(&style.syntax));
19788 let mut chunk_lines = chunk.text.split('\n').peekable();
19789 while let Some(text) = chunk_lines.next() {
19790 let mut merged_with_last_token = false;
19791 if let Some(last_token) = line.back_mut() {
19792 if last_token.highlight == highlight {
19793 last_token.text.push_str(text);
19794 merged_with_last_token = true;
19795 }
19796 }
19797
19798 if !merged_with_last_token {
19799 line.push_back(Chunk {
19800 text: text.into(),
19801 highlight,
19802 });
19803 }
19804
19805 if chunk_lines.peek().is_some() {
19806 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19807 line.pop_front();
19808 }
19809 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19810 line.pop_back();
19811 }
19812
19813 lines.push(mem::take(&mut line));
19814 }
19815 }
19816 }
19817
19818 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19819 return;
19820 };
19821 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19822 }
19823
19824 pub fn open_context_menu(
19825 &mut self,
19826 _: &OpenContextMenu,
19827 window: &mut Window,
19828 cx: &mut Context<Self>,
19829 ) {
19830 self.request_autoscroll(Autoscroll::newest(), cx);
19831 let position = self.selections.newest_display(cx).start;
19832 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19833 }
19834
19835 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19836 &self.inlay_hint_cache
19837 }
19838
19839 pub fn replay_insert_event(
19840 &mut self,
19841 text: &str,
19842 relative_utf16_range: Option<Range<isize>>,
19843 window: &mut Window,
19844 cx: &mut Context<Self>,
19845 ) {
19846 if !self.input_enabled {
19847 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19848 return;
19849 }
19850 if let Some(relative_utf16_range) = relative_utf16_range {
19851 let selections = self.selections.all::<OffsetUtf16>(cx);
19852 self.change_selections(None, window, cx, |s| {
19853 let new_ranges = selections.into_iter().map(|range| {
19854 let start = OffsetUtf16(
19855 range
19856 .head()
19857 .0
19858 .saturating_add_signed(relative_utf16_range.start),
19859 );
19860 let end = OffsetUtf16(
19861 range
19862 .head()
19863 .0
19864 .saturating_add_signed(relative_utf16_range.end),
19865 );
19866 start..end
19867 });
19868 s.select_ranges(new_ranges);
19869 });
19870 }
19871
19872 self.handle_input(text, window, cx);
19873 }
19874
19875 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19876 let Some(provider) = self.semantics_provider.as_ref() else {
19877 return false;
19878 };
19879
19880 let mut supports = false;
19881 self.buffer().update(cx, |this, cx| {
19882 this.for_each_buffer(|buffer| {
19883 supports |= provider.supports_inlay_hints(buffer, cx);
19884 });
19885 });
19886
19887 supports
19888 }
19889
19890 pub fn is_focused(&self, window: &Window) -> bool {
19891 self.focus_handle.is_focused(window)
19892 }
19893
19894 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19895 cx.emit(EditorEvent::Focused);
19896
19897 if let Some(descendant) = self
19898 .last_focused_descendant
19899 .take()
19900 .and_then(|descendant| descendant.upgrade())
19901 {
19902 window.focus(&descendant);
19903 } else {
19904 if let Some(blame) = self.blame.as_ref() {
19905 blame.update(cx, GitBlame::focus)
19906 }
19907
19908 self.blink_manager.update(cx, BlinkManager::enable);
19909 self.show_cursor_names(window, cx);
19910 self.buffer.update(cx, |buffer, cx| {
19911 buffer.finalize_last_transaction(cx);
19912 if self.leader_id.is_none() {
19913 buffer.set_active_selections(
19914 &self.selections.disjoint_anchors(),
19915 self.selections.line_mode,
19916 self.cursor_shape,
19917 cx,
19918 );
19919 }
19920 });
19921 }
19922 }
19923
19924 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19925 cx.emit(EditorEvent::FocusedIn)
19926 }
19927
19928 fn handle_focus_out(
19929 &mut self,
19930 event: FocusOutEvent,
19931 _window: &mut Window,
19932 cx: &mut Context<Self>,
19933 ) {
19934 if event.blurred != self.focus_handle {
19935 self.last_focused_descendant = Some(event.blurred);
19936 }
19937 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19938 }
19939
19940 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19941 self.blink_manager.update(cx, BlinkManager::disable);
19942 self.buffer
19943 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19944
19945 if let Some(blame) = self.blame.as_ref() {
19946 blame.update(cx, GitBlame::blur)
19947 }
19948 if !self.hover_state.focused(window, cx) {
19949 hide_hover(self, cx);
19950 }
19951 if !self
19952 .context_menu
19953 .borrow()
19954 .as_ref()
19955 .is_some_and(|context_menu| context_menu.focused(window, cx))
19956 {
19957 self.hide_context_menu(window, cx);
19958 }
19959 self.discard_inline_completion(false, cx);
19960 cx.emit(EditorEvent::Blurred);
19961 cx.notify();
19962 }
19963
19964 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19965 let mut pending: String = window
19966 .pending_input_keystrokes()
19967 .into_iter()
19968 .flatten()
19969 .filter_map(|keystroke| {
19970 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
19971 keystroke.key_char.clone()
19972 } else {
19973 None
19974 }
19975 })
19976 .collect();
19977
19978 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
19979 pending = "".to_string();
19980 }
19981
19982 let existing_pending = self.text_highlights::<PendingInput>(cx).map(|ranges| {
19983 ranges
19984 .iter()
19985 .map(|(range, _)| range.clone())
19986 .collect::<Vec<_>>()
19987 });
19988 if existing_pending.is_none() && pending.is_empty() {
19989 return;
19990 }
19991 let transaction =
19992 self.transact(window, cx, |this, window, cx| {
19993 let selections = this.selections.all::<usize>(cx);
19994 let edits = selections
19995 .iter()
19996 .map(|selection| (selection.end..selection.end, pending.clone()));
19997 this.edit(edits, cx);
19998 this.change_selections(None, window, cx, |s| {
19999 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20000 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20001 }));
20002 });
20003 if let Some(existing_ranges) = existing_pending {
20004 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20005 this.edit(edits, cx);
20006 }
20007 });
20008
20009 let snapshot = self.snapshot(window, cx);
20010 let ranges = self
20011 .selections
20012 .all::<usize>(cx)
20013 .into_iter()
20014 .map(|selection| {
20015 (
20016 snapshot.buffer_snapshot.anchor_after(selection.end)
20017 ..snapshot
20018 .buffer_snapshot
20019 .anchor_before(selection.end + pending.len()),
20020 HighlightStyle {
20021 underline: Some(UnderlineStyle {
20022 thickness: px(1.),
20023 color: None,
20024 wavy: false,
20025 }),
20026 ..Default::default()
20027 },
20028 )
20029 })
20030 .collect();
20031
20032 if pending.is_empty() {
20033 self.clear_highlights::<PendingInput>(cx);
20034 } else {
20035 self.highlight_text::<PendingInput>(ranges, cx);
20036 }
20037
20038 self.ime_transaction = self.ime_transaction.or(transaction);
20039 if let Some(transaction) = self.ime_transaction {
20040 self.buffer.update(cx, |buffer, cx| {
20041 buffer.group_until_transaction(transaction, cx);
20042 });
20043 }
20044
20045 if self.text_highlights::<PendingInput>(cx).is_none() {
20046 self.ime_transaction.take();
20047 }
20048 }
20049
20050 pub fn register_action_renderer(
20051 &mut self,
20052 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20053 ) -> Subscription {
20054 let id = self.next_editor_action_id.post_inc();
20055 self.editor_actions
20056 .borrow_mut()
20057 .insert(id, Box::new(listener));
20058
20059 let editor_actions = self.editor_actions.clone();
20060 Subscription::new(move || {
20061 editor_actions.borrow_mut().remove(&id);
20062 })
20063 }
20064
20065 pub fn register_action<A: Action>(
20066 &mut self,
20067 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20068 ) -> Subscription {
20069 let id = self.next_editor_action_id.post_inc();
20070 let listener = Arc::new(listener);
20071 self.editor_actions.borrow_mut().insert(
20072 id,
20073 Box::new(move |_, window, _| {
20074 let listener = listener.clone();
20075 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20076 let action = action.downcast_ref().unwrap();
20077 if phase == DispatchPhase::Bubble {
20078 listener(action, window, cx)
20079 }
20080 })
20081 }),
20082 );
20083
20084 let editor_actions = self.editor_actions.clone();
20085 Subscription::new(move || {
20086 editor_actions.borrow_mut().remove(&id);
20087 })
20088 }
20089
20090 pub fn file_header_size(&self) -> u32 {
20091 FILE_HEADER_HEIGHT
20092 }
20093
20094 pub fn restore(
20095 &mut self,
20096 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20097 window: &mut Window,
20098 cx: &mut Context<Self>,
20099 ) {
20100 let workspace = self.workspace();
20101 let project = self.project.as_ref();
20102 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20103 let mut tasks = Vec::new();
20104 for (buffer_id, changes) in revert_changes {
20105 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20106 buffer.update(cx, |buffer, cx| {
20107 buffer.edit(
20108 changes
20109 .into_iter()
20110 .map(|(range, text)| (range, text.to_string())),
20111 None,
20112 cx,
20113 );
20114 });
20115
20116 if let Some(project) =
20117 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20118 {
20119 project.update(cx, |project, cx| {
20120 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20121 })
20122 }
20123 }
20124 }
20125 tasks
20126 });
20127 cx.spawn_in(window, async move |_, cx| {
20128 for (buffer, task) in save_tasks {
20129 let result = task.await;
20130 if result.is_err() {
20131 let Some(path) = buffer
20132 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20133 .ok()
20134 else {
20135 continue;
20136 };
20137 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20138 let Some(task) = cx
20139 .update_window_entity(&workspace, |workspace, window, cx| {
20140 workspace
20141 .open_path_preview(path, None, false, false, false, window, cx)
20142 })
20143 .ok()
20144 else {
20145 continue;
20146 };
20147 task.await.log_err();
20148 }
20149 }
20150 }
20151 })
20152 .detach();
20153 self.change_selections(None, window, cx, |selections| selections.refresh());
20154 }
20155
20156 pub fn to_pixel_point(
20157 &self,
20158 source: multi_buffer::Anchor,
20159 editor_snapshot: &EditorSnapshot,
20160 window: &mut Window,
20161 ) -> Option<gpui::Point<Pixels>> {
20162 let source_point = source.to_display_point(editor_snapshot);
20163 self.display_to_pixel_point(source_point, editor_snapshot, window)
20164 }
20165
20166 pub fn display_to_pixel_point(
20167 &self,
20168 source: DisplayPoint,
20169 editor_snapshot: &EditorSnapshot,
20170 window: &mut Window,
20171 ) -> Option<gpui::Point<Pixels>> {
20172 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20173 let text_layout_details = self.text_layout_details(window);
20174 let scroll_top = text_layout_details
20175 .scroll_anchor
20176 .scroll_position(editor_snapshot)
20177 .y;
20178
20179 if source.row().as_f32() < scroll_top.floor() {
20180 return None;
20181 }
20182 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20183 let source_y = line_height * (source.row().as_f32() - scroll_top);
20184 Some(gpui::Point::new(source_x, source_y))
20185 }
20186
20187 pub fn has_visible_completions_menu(&self) -> bool {
20188 !self.edit_prediction_preview_is_active()
20189 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20190 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20191 })
20192 }
20193
20194 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20195 if self.mode.is_minimap() {
20196 return;
20197 }
20198 self.addons
20199 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20200 }
20201
20202 pub fn unregister_addon<T: Addon>(&mut self) {
20203 self.addons.remove(&std::any::TypeId::of::<T>());
20204 }
20205
20206 pub fn addon<T: Addon>(&self) -> Option<&T> {
20207 let type_id = std::any::TypeId::of::<T>();
20208 self.addons
20209 .get(&type_id)
20210 .and_then(|item| item.to_any().downcast_ref::<T>())
20211 }
20212
20213 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20214 let type_id = std::any::TypeId::of::<T>();
20215 self.addons
20216 .get_mut(&type_id)
20217 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20218 }
20219
20220 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
20221 let text_layout_details = self.text_layout_details(window);
20222 let style = &text_layout_details.editor_style;
20223 let font_id = window.text_system().resolve_font(&style.text.font());
20224 let font_size = style.text.font_size.to_pixels(window.rem_size());
20225 let line_height = style.text.line_height_in_pixels(window.rem_size());
20226 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20227
20228 gpui::Size::new(em_width, line_height)
20229 }
20230
20231 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20232 self.load_diff_task.clone()
20233 }
20234
20235 fn read_metadata_from_db(
20236 &mut self,
20237 item_id: u64,
20238 workspace_id: WorkspaceId,
20239 window: &mut Window,
20240 cx: &mut Context<Editor>,
20241 ) {
20242 if self.is_singleton(cx)
20243 && !self.mode.is_minimap()
20244 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20245 {
20246 let buffer_snapshot = OnceCell::new();
20247
20248 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20249 if !folds.is_empty() {
20250 let snapshot =
20251 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20252 self.fold_ranges(
20253 folds
20254 .into_iter()
20255 .map(|(start, end)| {
20256 snapshot.clip_offset(start, Bias::Left)
20257 ..snapshot.clip_offset(end, Bias::Right)
20258 })
20259 .collect(),
20260 false,
20261 window,
20262 cx,
20263 );
20264 }
20265 }
20266
20267 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20268 if !selections.is_empty() {
20269 let snapshot =
20270 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20271 // skip adding the initial selection to selection history
20272 self.selection_history.mode = SelectionHistoryMode::Skipping;
20273 self.change_selections(None, window, cx, |s| {
20274 s.select_ranges(selections.into_iter().map(|(start, end)| {
20275 snapshot.clip_offset(start, Bias::Left)
20276 ..snapshot.clip_offset(end, Bias::Right)
20277 }));
20278 });
20279 self.selection_history.mode = SelectionHistoryMode::Normal;
20280 }
20281 };
20282 }
20283
20284 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20285 }
20286
20287 fn update_lsp_data(
20288 &mut self,
20289 update_on_edit: bool,
20290 for_server_id: Option<LanguageServerId>,
20291 for_buffer: Option<BufferId>,
20292 window: &mut Window,
20293 cx: &mut Context<'_, Self>,
20294 ) {
20295 self.pull_diagnostics(for_buffer, window, cx);
20296 self.refresh_colors(update_on_edit, for_server_id, for_buffer, window, cx);
20297 }
20298}
20299
20300fn vim_enabled(cx: &App) -> bool {
20301 cx.global::<SettingsStore>()
20302 .raw_user_settings()
20303 .get("vim_mode")
20304 == Some(&serde_json::Value::Bool(true))
20305}
20306
20307fn process_completion_for_edit(
20308 completion: &Completion,
20309 intent: CompletionIntent,
20310 buffer: &Entity<Buffer>,
20311 cursor_position: &text::Anchor,
20312 cx: &mut Context<Editor>,
20313) -> CompletionEdit {
20314 let buffer = buffer.read(cx);
20315 let buffer_snapshot = buffer.snapshot();
20316 let (snippet, new_text) = if completion.is_snippet() {
20317 // Workaround for typescript language server issues so that methods don't expand within
20318 // strings and functions with type expressions. The previous point is used because the query
20319 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20320 let mut snippet_source = completion.new_text.clone();
20321 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20322 previous_point.column = previous_point.column.saturating_sub(1);
20323 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20324 if scope.prefers_label_for_snippet_in_completion() {
20325 if let Some(label) = completion.label() {
20326 if matches!(
20327 completion.kind(),
20328 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20329 ) {
20330 snippet_source = label;
20331 }
20332 }
20333 }
20334 }
20335 match Snippet::parse(&snippet_source).log_err() {
20336 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20337 None => (None, completion.new_text.clone()),
20338 }
20339 } else {
20340 (None, completion.new_text.clone())
20341 };
20342
20343 let mut range_to_replace = {
20344 let replace_range = &completion.replace_range;
20345 if let CompletionSource::Lsp {
20346 insert_range: Some(insert_range),
20347 ..
20348 } = &completion.source
20349 {
20350 debug_assert_eq!(
20351 insert_range.start, replace_range.start,
20352 "insert_range and replace_range should start at the same position"
20353 );
20354 debug_assert!(
20355 insert_range
20356 .start
20357 .cmp(&cursor_position, &buffer_snapshot)
20358 .is_le(),
20359 "insert_range should start before or at cursor position"
20360 );
20361 debug_assert!(
20362 replace_range
20363 .start
20364 .cmp(&cursor_position, &buffer_snapshot)
20365 .is_le(),
20366 "replace_range should start before or at cursor position"
20367 );
20368 debug_assert!(
20369 insert_range
20370 .end
20371 .cmp(&cursor_position, &buffer_snapshot)
20372 .is_le(),
20373 "insert_range should end before or at cursor position"
20374 );
20375
20376 let should_replace = match intent {
20377 CompletionIntent::CompleteWithInsert => false,
20378 CompletionIntent::CompleteWithReplace => true,
20379 CompletionIntent::Complete | CompletionIntent::Compose => {
20380 let insert_mode =
20381 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20382 .completions
20383 .lsp_insert_mode;
20384 match insert_mode {
20385 LspInsertMode::Insert => false,
20386 LspInsertMode::Replace => true,
20387 LspInsertMode::ReplaceSubsequence => {
20388 let mut text_to_replace = buffer.chars_for_range(
20389 buffer.anchor_before(replace_range.start)
20390 ..buffer.anchor_after(replace_range.end),
20391 );
20392 let mut current_needle = text_to_replace.next();
20393 for haystack_ch in completion.label.text.chars() {
20394 if let Some(needle_ch) = current_needle {
20395 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20396 current_needle = text_to_replace.next();
20397 }
20398 }
20399 }
20400 current_needle.is_none()
20401 }
20402 LspInsertMode::ReplaceSuffix => {
20403 if replace_range
20404 .end
20405 .cmp(&cursor_position, &buffer_snapshot)
20406 .is_gt()
20407 {
20408 let range_after_cursor = *cursor_position..replace_range.end;
20409 let text_after_cursor = buffer
20410 .text_for_range(
20411 buffer.anchor_before(range_after_cursor.start)
20412 ..buffer.anchor_after(range_after_cursor.end),
20413 )
20414 .collect::<String>()
20415 .to_ascii_lowercase();
20416 completion
20417 .label
20418 .text
20419 .to_ascii_lowercase()
20420 .ends_with(&text_after_cursor)
20421 } else {
20422 true
20423 }
20424 }
20425 }
20426 }
20427 };
20428
20429 if should_replace {
20430 replace_range.clone()
20431 } else {
20432 insert_range.clone()
20433 }
20434 } else {
20435 replace_range.clone()
20436 }
20437 };
20438
20439 if range_to_replace
20440 .end
20441 .cmp(&cursor_position, &buffer_snapshot)
20442 .is_lt()
20443 {
20444 range_to_replace.end = *cursor_position;
20445 }
20446
20447 CompletionEdit {
20448 new_text,
20449 replace_range: range_to_replace.to_offset(&buffer),
20450 snippet,
20451 }
20452}
20453
20454struct CompletionEdit {
20455 new_text: String,
20456 replace_range: Range<usize>,
20457 snippet: Option<Snippet>,
20458}
20459
20460fn insert_extra_newline_brackets(
20461 buffer: &MultiBufferSnapshot,
20462 range: Range<usize>,
20463 language: &language::LanguageScope,
20464) -> bool {
20465 let leading_whitespace_len = buffer
20466 .reversed_chars_at(range.start)
20467 .take_while(|c| c.is_whitespace() && *c != '\n')
20468 .map(|c| c.len_utf8())
20469 .sum::<usize>();
20470 let trailing_whitespace_len = buffer
20471 .chars_at(range.end)
20472 .take_while(|c| c.is_whitespace() && *c != '\n')
20473 .map(|c| c.len_utf8())
20474 .sum::<usize>();
20475 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20476
20477 language.brackets().any(|(pair, enabled)| {
20478 let pair_start = pair.start.trim_end();
20479 let pair_end = pair.end.trim_start();
20480
20481 enabled
20482 && pair.newline
20483 && buffer.contains_str_at(range.end, pair_end)
20484 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
20485 })
20486}
20487
20488fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
20489 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
20490 [(buffer, range, _)] => (*buffer, range.clone()),
20491 _ => return false,
20492 };
20493 let pair = {
20494 let mut result: Option<BracketMatch> = None;
20495
20496 for pair in buffer
20497 .all_bracket_ranges(range.clone())
20498 .filter(move |pair| {
20499 pair.open_range.start <= range.start && pair.close_range.end >= range.end
20500 })
20501 {
20502 let len = pair.close_range.end - pair.open_range.start;
20503
20504 if let Some(existing) = &result {
20505 let existing_len = existing.close_range.end - existing.open_range.start;
20506 if len > existing_len {
20507 continue;
20508 }
20509 }
20510
20511 result = Some(pair);
20512 }
20513
20514 result
20515 };
20516 let Some(pair) = pair else {
20517 return false;
20518 };
20519 pair.newline_only
20520 && buffer
20521 .chars_for_range(pair.open_range.end..range.start)
20522 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
20523 .all(|c| c.is_whitespace() && c != '\n')
20524}
20525
20526fn update_uncommitted_diff_for_buffer(
20527 editor: Entity<Editor>,
20528 project: &Entity<Project>,
20529 buffers: impl IntoIterator<Item = Entity<Buffer>>,
20530 buffer: Entity<MultiBuffer>,
20531 cx: &mut App,
20532) -> Task<()> {
20533 let mut tasks = Vec::new();
20534 project.update(cx, |project, cx| {
20535 for buffer in buffers {
20536 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
20537 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
20538 }
20539 }
20540 });
20541 cx.spawn(async move |cx| {
20542 let diffs = future::join_all(tasks).await;
20543 if editor
20544 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
20545 .unwrap_or(false)
20546 {
20547 return;
20548 }
20549
20550 buffer
20551 .update(cx, |buffer, cx| {
20552 for diff in diffs.into_iter().flatten() {
20553 buffer.add_diff(diff, cx);
20554 }
20555 })
20556 .ok();
20557 })
20558}
20559
20560fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
20561 let tab_size = tab_size.get() as usize;
20562 let mut width = offset;
20563
20564 for ch in text.chars() {
20565 width += if ch == '\t' {
20566 tab_size - (width % tab_size)
20567 } else {
20568 1
20569 };
20570 }
20571
20572 width - offset
20573}
20574
20575#[cfg(test)]
20576mod tests {
20577 use super::*;
20578
20579 #[test]
20580 fn test_string_size_with_expanded_tabs() {
20581 let nz = |val| NonZeroU32::new(val).unwrap();
20582 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
20583 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
20584 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
20585 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
20586 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
20587 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
20588 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
20589 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
20590 }
20591}
20592
20593/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
20594struct WordBreakingTokenizer<'a> {
20595 input: &'a str,
20596}
20597
20598impl<'a> WordBreakingTokenizer<'a> {
20599 fn new(input: &'a str) -> Self {
20600 Self { input }
20601 }
20602}
20603
20604fn is_char_ideographic(ch: char) -> bool {
20605 use unicode_script::Script::*;
20606 use unicode_script::UnicodeScript;
20607 matches!(ch.script(), Han | Tangut | Yi)
20608}
20609
20610fn is_grapheme_ideographic(text: &str) -> bool {
20611 text.chars().any(is_char_ideographic)
20612}
20613
20614fn is_grapheme_whitespace(text: &str) -> bool {
20615 text.chars().any(|x| x.is_whitespace())
20616}
20617
20618fn should_stay_with_preceding_ideograph(text: &str) -> bool {
20619 text.chars().next().map_or(false, |ch| {
20620 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
20621 })
20622}
20623
20624#[derive(PartialEq, Eq, Debug, Clone, Copy)]
20625enum WordBreakToken<'a> {
20626 Word { token: &'a str, grapheme_len: usize },
20627 InlineWhitespace { token: &'a str, grapheme_len: usize },
20628 Newline,
20629}
20630
20631impl<'a> Iterator for WordBreakingTokenizer<'a> {
20632 /// Yields a span, the count of graphemes in the token, and whether it was
20633 /// whitespace. Note that it also breaks at word boundaries.
20634 type Item = WordBreakToken<'a>;
20635
20636 fn next(&mut self) -> Option<Self::Item> {
20637 use unicode_segmentation::UnicodeSegmentation;
20638 if self.input.is_empty() {
20639 return None;
20640 }
20641
20642 let mut iter = self.input.graphemes(true).peekable();
20643 let mut offset = 0;
20644 let mut grapheme_len = 0;
20645 if let Some(first_grapheme) = iter.next() {
20646 let is_newline = first_grapheme == "\n";
20647 let is_whitespace = is_grapheme_whitespace(first_grapheme);
20648 offset += first_grapheme.len();
20649 grapheme_len += 1;
20650 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
20651 if let Some(grapheme) = iter.peek().copied() {
20652 if should_stay_with_preceding_ideograph(grapheme) {
20653 offset += grapheme.len();
20654 grapheme_len += 1;
20655 }
20656 }
20657 } else {
20658 let mut words = self.input[offset..].split_word_bound_indices().peekable();
20659 let mut next_word_bound = words.peek().copied();
20660 if next_word_bound.map_or(false, |(i, _)| i == 0) {
20661 next_word_bound = words.next();
20662 }
20663 while let Some(grapheme) = iter.peek().copied() {
20664 if next_word_bound.map_or(false, |(i, _)| i == offset) {
20665 break;
20666 };
20667 if is_grapheme_whitespace(grapheme) != is_whitespace
20668 || (grapheme == "\n") != is_newline
20669 {
20670 break;
20671 };
20672 offset += grapheme.len();
20673 grapheme_len += 1;
20674 iter.next();
20675 }
20676 }
20677 let token = &self.input[..offset];
20678 self.input = &self.input[offset..];
20679 if token == "\n" {
20680 Some(WordBreakToken::Newline)
20681 } else if is_whitespace {
20682 Some(WordBreakToken::InlineWhitespace {
20683 token,
20684 grapheme_len,
20685 })
20686 } else {
20687 Some(WordBreakToken::Word {
20688 token,
20689 grapheme_len,
20690 })
20691 }
20692 } else {
20693 None
20694 }
20695 }
20696}
20697
20698#[test]
20699fn test_word_breaking_tokenizer() {
20700 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
20701 ("", &[]),
20702 (" ", &[whitespace(" ", 2)]),
20703 ("Ʒ", &[word("Ʒ", 1)]),
20704 ("Ǽ", &[word("Ǽ", 1)]),
20705 ("⋑", &[word("⋑", 1)]),
20706 ("⋑⋑", &[word("⋑⋑", 2)]),
20707 (
20708 "原理,进而",
20709 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20710 ),
20711 (
20712 "hello world",
20713 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20714 ),
20715 (
20716 "hello, world",
20717 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
20718 ),
20719 (
20720 " hello world",
20721 &[
20722 whitespace(" ", 2),
20723 word("hello", 5),
20724 whitespace(" ", 1),
20725 word("world", 5),
20726 ],
20727 ),
20728 (
20729 "这是什么 \n 钢笔",
20730 &[
20731 word("这", 1),
20732 word("是", 1),
20733 word("什", 1),
20734 word("么", 1),
20735 whitespace(" ", 1),
20736 newline(),
20737 whitespace(" ", 1),
20738 word("钢", 1),
20739 word("笔", 1),
20740 ],
20741 ),
20742 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
20743 ];
20744
20745 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20746 WordBreakToken::Word {
20747 token,
20748 grapheme_len,
20749 }
20750 }
20751
20752 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20753 WordBreakToken::InlineWhitespace {
20754 token,
20755 grapheme_len,
20756 }
20757 }
20758
20759 fn newline() -> WordBreakToken<'static> {
20760 WordBreakToken::Newline
20761 }
20762
20763 for (input, result) in tests {
20764 assert_eq!(
20765 WordBreakingTokenizer::new(input)
20766 .collect::<Vec<_>>()
20767 .as_slice(),
20768 *result,
20769 );
20770 }
20771}
20772
20773fn wrap_with_prefix(
20774 line_prefix: String,
20775 unwrapped_text: String,
20776 wrap_column: usize,
20777 tab_size: NonZeroU32,
20778 preserve_existing_whitespace: bool,
20779) -> String {
20780 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
20781 let mut wrapped_text = String::new();
20782 let mut current_line = line_prefix.clone();
20783
20784 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
20785 let mut current_line_len = line_prefix_len;
20786 let mut in_whitespace = false;
20787 for token in tokenizer {
20788 let have_preceding_whitespace = in_whitespace;
20789 match token {
20790 WordBreakToken::Word {
20791 token,
20792 grapheme_len,
20793 } => {
20794 in_whitespace = false;
20795 if current_line_len + grapheme_len > wrap_column
20796 && current_line_len != line_prefix_len
20797 {
20798 wrapped_text.push_str(current_line.trim_end());
20799 wrapped_text.push('\n');
20800 current_line.truncate(line_prefix.len());
20801 current_line_len = line_prefix_len;
20802 }
20803 current_line.push_str(token);
20804 current_line_len += grapheme_len;
20805 }
20806 WordBreakToken::InlineWhitespace {
20807 mut token,
20808 mut grapheme_len,
20809 } => {
20810 in_whitespace = true;
20811 if have_preceding_whitespace && !preserve_existing_whitespace {
20812 continue;
20813 }
20814 if !preserve_existing_whitespace {
20815 token = " ";
20816 grapheme_len = 1;
20817 }
20818 if current_line_len + grapheme_len > wrap_column {
20819 wrapped_text.push_str(current_line.trim_end());
20820 wrapped_text.push('\n');
20821 current_line.truncate(line_prefix.len());
20822 current_line_len = line_prefix_len;
20823 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
20824 current_line.push_str(token);
20825 current_line_len += grapheme_len;
20826 }
20827 }
20828 WordBreakToken::Newline => {
20829 in_whitespace = true;
20830 if preserve_existing_whitespace {
20831 wrapped_text.push_str(current_line.trim_end());
20832 wrapped_text.push('\n');
20833 current_line.truncate(line_prefix.len());
20834 current_line_len = line_prefix_len;
20835 } else if have_preceding_whitespace {
20836 continue;
20837 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
20838 {
20839 wrapped_text.push_str(current_line.trim_end());
20840 wrapped_text.push('\n');
20841 current_line.truncate(line_prefix.len());
20842 current_line_len = line_prefix_len;
20843 } else if current_line_len != line_prefix_len {
20844 current_line.push(' ');
20845 current_line_len += 1;
20846 }
20847 }
20848 }
20849 }
20850
20851 if !current_line.is_empty() {
20852 wrapped_text.push_str(¤t_line);
20853 }
20854 wrapped_text
20855}
20856
20857#[test]
20858fn test_wrap_with_prefix() {
20859 assert_eq!(
20860 wrap_with_prefix(
20861 "# ".to_string(),
20862 "abcdefg".to_string(),
20863 4,
20864 NonZeroU32::new(4).unwrap(),
20865 false,
20866 ),
20867 "# abcdefg"
20868 );
20869 assert_eq!(
20870 wrap_with_prefix(
20871 "".to_string(),
20872 "\thello world".to_string(),
20873 8,
20874 NonZeroU32::new(4).unwrap(),
20875 false,
20876 ),
20877 "hello\nworld"
20878 );
20879 assert_eq!(
20880 wrap_with_prefix(
20881 "// ".to_string(),
20882 "xx \nyy zz aa bb cc".to_string(),
20883 12,
20884 NonZeroU32::new(4).unwrap(),
20885 false,
20886 ),
20887 "// xx yy zz\n// aa bb cc"
20888 );
20889 assert_eq!(
20890 wrap_with_prefix(
20891 String::new(),
20892 "这是什么 \n 钢笔".to_string(),
20893 3,
20894 NonZeroU32::new(4).unwrap(),
20895 false,
20896 ),
20897 "这是什\n么 钢\n笔"
20898 );
20899}
20900
20901pub trait CollaborationHub {
20902 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20903 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20904 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20905}
20906
20907impl CollaborationHub for Entity<Project> {
20908 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20909 self.read(cx).collaborators()
20910 }
20911
20912 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
20913 self.read(cx).user_store().read(cx).participant_indices()
20914 }
20915
20916 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
20917 let this = self.read(cx);
20918 let user_ids = this.collaborators().values().map(|c| c.user_id);
20919 this.user_store().read(cx).participant_names(user_ids, cx)
20920 }
20921}
20922
20923pub trait SemanticsProvider {
20924 fn hover(
20925 &self,
20926 buffer: &Entity<Buffer>,
20927 position: text::Anchor,
20928 cx: &mut App,
20929 ) -> Option<Task<Vec<project::Hover>>>;
20930
20931 fn inline_values(
20932 &self,
20933 buffer_handle: Entity<Buffer>,
20934 range: Range<text::Anchor>,
20935 cx: &mut App,
20936 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20937
20938 fn inlay_hints(
20939 &self,
20940 buffer_handle: Entity<Buffer>,
20941 range: Range<text::Anchor>,
20942 cx: &mut App,
20943 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20944
20945 fn resolve_inlay_hint(
20946 &self,
20947 hint: InlayHint,
20948 buffer_handle: Entity<Buffer>,
20949 server_id: LanguageServerId,
20950 cx: &mut App,
20951 ) -> Option<Task<anyhow::Result<InlayHint>>>;
20952
20953 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
20954
20955 fn document_highlights(
20956 &self,
20957 buffer: &Entity<Buffer>,
20958 position: text::Anchor,
20959 cx: &mut App,
20960 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
20961
20962 fn definitions(
20963 &self,
20964 buffer: &Entity<Buffer>,
20965 position: text::Anchor,
20966 kind: GotoDefinitionKind,
20967 cx: &mut App,
20968 ) -> Option<Task<Result<Vec<LocationLink>>>>;
20969
20970 fn range_for_rename(
20971 &self,
20972 buffer: &Entity<Buffer>,
20973 position: text::Anchor,
20974 cx: &mut App,
20975 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
20976
20977 fn perform_rename(
20978 &self,
20979 buffer: &Entity<Buffer>,
20980 position: text::Anchor,
20981 new_name: String,
20982 cx: &mut App,
20983 ) -> Option<Task<Result<ProjectTransaction>>>;
20984}
20985
20986pub trait CompletionProvider {
20987 fn completions(
20988 &self,
20989 excerpt_id: ExcerptId,
20990 buffer: &Entity<Buffer>,
20991 buffer_position: text::Anchor,
20992 trigger: CompletionContext,
20993 window: &mut Window,
20994 cx: &mut Context<Editor>,
20995 ) -> Task<Result<Vec<CompletionResponse>>>;
20996
20997 fn resolve_completions(
20998 &self,
20999 _buffer: Entity<Buffer>,
21000 _completion_indices: Vec<usize>,
21001 _completions: Rc<RefCell<Box<[Completion]>>>,
21002 _cx: &mut Context<Editor>,
21003 ) -> Task<Result<bool>> {
21004 Task::ready(Ok(false))
21005 }
21006
21007 fn apply_additional_edits_for_completion(
21008 &self,
21009 _buffer: Entity<Buffer>,
21010 _completions: Rc<RefCell<Box<[Completion]>>>,
21011 _completion_index: usize,
21012 _push_to_history: bool,
21013 _cx: &mut Context<Editor>,
21014 ) -> Task<Result<Option<language::Transaction>>> {
21015 Task::ready(Ok(None))
21016 }
21017
21018 fn is_completion_trigger(
21019 &self,
21020 buffer: &Entity<Buffer>,
21021 position: language::Anchor,
21022 text: &str,
21023 trigger_in_words: bool,
21024 menu_is_open: bool,
21025 cx: &mut Context<Editor>,
21026 ) -> bool;
21027
21028 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21029
21030 fn sort_completions(&self) -> bool {
21031 true
21032 }
21033
21034 fn filter_completions(&self) -> bool {
21035 true
21036 }
21037}
21038
21039pub trait CodeActionProvider {
21040 fn id(&self) -> Arc<str>;
21041
21042 fn code_actions(
21043 &self,
21044 buffer: &Entity<Buffer>,
21045 range: Range<text::Anchor>,
21046 window: &mut Window,
21047 cx: &mut App,
21048 ) -> Task<Result<Vec<CodeAction>>>;
21049
21050 fn apply_code_action(
21051 &self,
21052 buffer_handle: Entity<Buffer>,
21053 action: CodeAction,
21054 excerpt_id: ExcerptId,
21055 push_to_history: bool,
21056 window: &mut Window,
21057 cx: &mut App,
21058 ) -> Task<Result<ProjectTransaction>>;
21059}
21060
21061impl CodeActionProvider for Entity<Project> {
21062 fn id(&self) -> Arc<str> {
21063 "project".into()
21064 }
21065
21066 fn code_actions(
21067 &self,
21068 buffer: &Entity<Buffer>,
21069 range: Range<text::Anchor>,
21070 _window: &mut Window,
21071 cx: &mut App,
21072 ) -> Task<Result<Vec<CodeAction>>> {
21073 self.update(cx, |project, cx| {
21074 let code_lens = project.code_lens(buffer, range.clone(), cx);
21075 let code_actions = project.code_actions(buffer, range, None, cx);
21076 cx.background_spawn(async move {
21077 let (code_lens, code_actions) = join(code_lens, code_actions).await;
21078 Ok(code_lens
21079 .context("code lens fetch")?
21080 .into_iter()
21081 .chain(code_actions.context("code action fetch")?)
21082 .collect())
21083 })
21084 })
21085 }
21086
21087 fn apply_code_action(
21088 &self,
21089 buffer_handle: Entity<Buffer>,
21090 action: CodeAction,
21091 _excerpt_id: ExcerptId,
21092 push_to_history: bool,
21093 _window: &mut Window,
21094 cx: &mut App,
21095 ) -> Task<Result<ProjectTransaction>> {
21096 self.update(cx, |project, cx| {
21097 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21098 })
21099 }
21100}
21101
21102fn snippet_completions(
21103 project: &Project,
21104 buffer: &Entity<Buffer>,
21105 buffer_position: text::Anchor,
21106 cx: &mut App,
21107) -> Task<Result<CompletionResponse>> {
21108 let languages = buffer.read(cx).languages_at(buffer_position);
21109 let snippet_store = project.snippets().read(cx);
21110
21111 let scopes: Vec<_> = languages
21112 .iter()
21113 .filter_map(|language| {
21114 let language_name = language.lsp_id();
21115 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21116
21117 if snippets.is_empty() {
21118 None
21119 } else {
21120 Some((language.default_scope(), snippets))
21121 }
21122 })
21123 .collect();
21124
21125 if scopes.is_empty() {
21126 return Task::ready(Ok(CompletionResponse {
21127 completions: vec![],
21128 is_incomplete: false,
21129 }));
21130 }
21131
21132 let snapshot = buffer.read(cx).text_snapshot();
21133 let chars: String = snapshot
21134 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21135 .collect();
21136 let executor = cx.background_executor().clone();
21137
21138 cx.background_spawn(async move {
21139 let mut is_incomplete = false;
21140 let mut completions: Vec<Completion> = Vec::new();
21141 for (scope, snippets) in scopes.into_iter() {
21142 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21143 let mut last_word = chars
21144 .chars()
21145 .take_while(|c| classifier.is_word(*c))
21146 .collect::<String>();
21147 last_word = last_word.chars().rev().collect();
21148
21149 if last_word.is_empty() {
21150 return Ok(CompletionResponse {
21151 completions: vec![],
21152 is_incomplete: true,
21153 });
21154 }
21155
21156 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21157 let to_lsp = |point: &text::Anchor| {
21158 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21159 point_to_lsp(end)
21160 };
21161 let lsp_end = to_lsp(&buffer_position);
21162
21163 let candidates = snippets
21164 .iter()
21165 .enumerate()
21166 .flat_map(|(ix, snippet)| {
21167 snippet
21168 .prefix
21169 .iter()
21170 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21171 })
21172 .collect::<Vec<StringMatchCandidate>>();
21173
21174 const MAX_RESULTS: usize = 100;
21175 let mut matches = fuzzy::match_strings(
21176 &candidates,
21177 &last_word,
21178 last_word.chars().any(|c| c.is_uppercase()),
21179 MAX_RESULTS,
21180 &Default::default(),
21181 executor.clone(),
21182 )
21183 .await;
21184
21185 if matches.len() >= MAX_RESULTS {
21186 is_incomplete = true;
21187 }
21188
21189 // Remove all candidates where the query's start does not match the start of any word in the candidate
21190 if let Some(query_start) = last_word.chars().next() {
21191 matches.retain(|string_match| {
21192 split_words(&string_match.string).any(|word| {
21193 // Check that the first codepoint of the word as lowercase matches the first
21194 // codepoint of the query as lowercase
21195 word.chars()
21196 .flat_map(|codepoint| codepoint.to_lowercase())
21197 .zip(query_start.to_lowercase())
21198 .all(|(word_cp, query_cp)| word_cp == query_cp)
21199 })
21200 });
21201 }
21202
21203 let matched_strings = matches
21204 .into_iter()
21205 .map(|m| m.string)
21206 .collect::<HashSet<_>>();
21207
21208 completions.extend(snippets.iter().filter_map(|snippet| {
21209 let matching_prefix = snippet
21210 .prefix
21211 .iter()
21212 .find(|prefix| matched_strings.contains(*prefix))?;
21213 let start = as_offset - last_word.len();
21214 let start = snapshot.anchor_before(start);
21215 let range = start..buffer_position;
21216 let lsp_start = to_lsp(&start);
21217 let lsp_range = lsp::Range {
21218 start: lsp_start,
21219 end: lsp_end,
21220 };
21221 Some(Completion {
21222 replace_range: range,
21223 new_text: snippet.body.clone(),
21224 source: CompletionSource::Lsp {
21225 insert_range: None,
21226 server_id: LanguageServerId(usize::MAX),
21227 resolved: true,
21228 lsp_completion: Box::new(lsp::CompletionItem {
21229 label: snippet.prefix.first().unwrap().clone(),
21230 kind: Some(CompletionItemKind::SNIPPET),
21231 label_details: snippet.description.as_ref().map(|description| {
21232 lsp::CompletionItemLabelDetails {
21233 detail: Some(description.clone()),
21234 description: None,
21235 }
21236 }),
21237 insert_text_format: Some(InsertTextFormat::SNIPPET),
21238 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21239 lsp::InsertReplaceEdit {
21240 new_text: snippet.body.clone(),
21241 insert: lsp_range,
21242 replace: lsp_range,
21243 },
21244 )),
21245 filter_text: Some(snippet.body.clone()),
21246 sort_text: Some(char::MAX.to_string()),
21247 ..lsp::CompletionItem::default()
21248 }),
21249 lsp_defaults: None,
21250 },
21251 label: CodeLabel {
21252 text: matching_prefix.clone(),
21253 runs: Vec::new(),
21254 filter_range: 0..matching_prefix.len(),
21255 },
21256 icon_path: None,
21257 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21258 single_line: snippet.name.clone().into(),
21259 plain_text: snippet
21260 .description
21261 .clone()
21262 .map(|description| description.into()),
21263 }),
21264 insert_text_mode: None,
21265 confirm: None,
21266 })
21267 }))
21268 }
21269
21270 Ok(CompletionResponse {
21271 completions,
21272 is_incomplete,
21273 })
21274 })
21275}
21276
21277impl CompletionProvider for Entity<Project> {
21278 fn completions(
21279 &self,
21280 _excerpt_id: ExcerptId,
21281 buffer: &Entity<Buffer>,
21282 buffer_position: text::Anchor,
21283 options: CompletionContext,
21284 _window: &mut Window,
21285 cx: &mut Context<Editor>,
21286 ) -> Task<Result<Vec<CompletionResponse>>> {
21287 self.update(cx, |project, cx| {
21288 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21289 let project_completions = project.completions(buffer, buffer_position, options, cx);
21290 cx.background_spawn(async move {
21291 let mut responses = project_completions.await?;
21292 let snippets = snippets.await?;
21293 if !snippets.completions.is_empty() {
21294 responses.push(snippets);
21295 }
21296 Ok(responses)
21297 })
21298 })
21299 }
21300
21301 fn resolve_completions(
21302 &self,
21303 buffer: Entity<Buffer>,
21304 completion_indices: Vec<usize>,
21305 completions: Rc<RefCell<Box<[Completion]>>>,
21306 cx: &mut Context<Editor>,
21307 ) -> Task<Result<bool>> {
21308 self.update(cx, |project, cx| {
21309 project.lsp_store().update(cx, |lsp_store, cx| {
21310 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21311 })
21312 })
21313 }
21314
21315 fn apply_additional_edits_for_completion(
21316 &self,
21317 buffer: Entity<Buffer>,
21318 completions: Rc<RefCell<Box<[Completion]>>>,
21319 completion_index: usize,
21320 push_to_history: bool,
21321 cx: &mut Context<Editor>,
21322 ) -> Task<Result<Option<language::Transaction>>> {
21323 self.update(cx, |project, cx| {
21324 project.lsp_store().update(cx, |lsp_store, cx| {
21325 lsp_store.apply_additional_edits_for_completion(
21326 buffer,
21327 completions,
21328 completion_index,
21329 push_to_history,
21330 cx,
21331 )
21332 })
21333 })
21334 }
21335
21336 fn is_completion_trigger(
21337 &self,
21338 buffer: &Entity<Buffer>,
21339 position: language::Anchor,
21340 text: &str,
21341 trigger_in_words: bool,
21342 menu_is_open: bool,
21343 cx: &mut Context<Editor>,
21344 ) -> bool {
21345 let mut chars = text.chars();
21346 let char = if let Some(char) = chars.next() {
21347 char
21348 } else {
21349 return false;
21350 };
21351 if chars.next().is_some() {
21352 return false;
21353 }
21354
21355 let buffer = buffer.read(cx);
21356 let snapshot = buffer.snapshot();
21357 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21358 return false;
21359 }
21360 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21361 if trigger_in_words && classifier.is_word(char) {
21362 return true;
21363 }
21364
21365 buffer.completion_triggers().contains(text)
21366 }
21367}
21368
21369impl SemanticsProvider for Entity<Project> {
21370 fn hover(
21371 &self,
21372 buffer: &Entity<Buffer>,
21373 position: text::Anchor,
21374 cx: &mut App,
21375 ) -> Option<Task<Vec<project::Hover>>> {
21376 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21377 }
21378
21379 fn document_highlights(
21380 &self,
21381 buffer: &Entity<Buffer>,
21382 position: text::Anchor,
21383 cx: &mut App,
21384 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21385 Some(self.update(cx, |project, cx| {
21386 project.document_highlights(buffer, position, cx)
21387 }))
21388 }
21389
21390 fn definitions(
21391 &self,
21392 buffer: &Entity<Buffer>,
21393 position: text::Anchor,
21394 kind: GotoDefinitionKind,
21395 cx: &mut App,
21396 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21397 Some(self.update(cx, |project, cx| match kind {
21398 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
21399 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
21400 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
21401 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
21402 }))
21403 }
21404
21405 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21406 // TODO: make this work for remote projects
21407 self.update(cx, |project, cx| {
21408 if project
21409 .active_debug_session(cx)
21410 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21411 {
21412 return true;
21413 }
21414
21415 buffer.update(cx, |buffer, cx| {
21416 project.any_language_server_supports_inlay_hints(buffer, cx)
21417 })
21418 })
21419 }
21420
21421 fn inline_values(
21422 &self,
21423 buffer_handle: Entity<Buffer>,
21424
21425 range: Range<text::Anchor>,
21426 cx: &mut App,
21427 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21428 self.update(cx, |project, cx| {
21429 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21430
21431 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21432 })
21433 }
21434
21435 fn inlay_hints(
21436 &self,
21437 buffer_handle: Entity<Buffer>,
21438 range: Range<text::Anchor>,
21439 cx: &mut App,
21440 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21441 Some(self.update(cx, |project, cx| {
21442 project.inlay_hints(buffer_handle, range, cx)
21443 }))
21444 }
21445
21446 fn resolve_inlay_hint(
21447 &self,
21448 hint: InlayHint,
21449 buffer_handle: Entity<Buffer>,
21450 server_id: LanguageServerId,
21451 cx: &mut App,
21452 ) -> Option<Task<anyhow::Result<InlayHint>>> {
21453 Some(self.update(cx, |project, cx| {
21454 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
21455 }))
21456 }
21457
21458 fn range_for_rename(
21459 &self,
21460 buffer: &Entity<Buffer>,
21461 position: text::Anchor,
21462 cx: &mut App,
21463 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
21464 Some(self.update(cx, |project, cx| {
21465 let buffer = buffer.clone();
21466 let task = project.prepare_rename(buffer.clone(), position, cx);
21467 cx.spawn(async move |_, cx| {
21468 Ok(match task.await? {
21469 PrepareRenameResponse::Success(range) => Some(range),
21470 PrepareRenameResponse::InvalidPosition => None,
21471 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
21472 // Fallback on using TreeSitter info to determine identifier range
21473 buffer.read_with(cx, |buffer, _| {
21474 let snapshot = buffer.snapshot();
21475 let (range, kind) = snapshot.surrounding_word(position);
21476 if kind != Some(CharKind::Word) {
21477 return None;
21478 }
21479 Some(
21480 snapshot.anchor_before(range.start)
21481 ..snapshot.anchor_after(range.end),
21482 )
21483 })?
21484 }
21485 })
21486 })
21487 }))
21488 }
21489
21490 fn perform_rename(
21491 &self,
21492 buffer: &Entity<Buffer>,
21493 position: text::Anchor,
21494 new_name: String,
21495 cx: &mut App,
21496 ) -> Option<Task<Result<ProjectTransaction>>> {
21497 Some(self.update(cx, |project, cx| {
21498 project.perform_rename(buffer.clone(), position, new_name, cx)
21499 }))
21500 }
21501}
21502
21503fn inlay_hint_settings(
21504 location: Anchor,
21505 snapshot: &MultiBufferSnapshot,
21506 cx: &mut Context<Editor>,
21507) -> InlayHintSettings {
21508 let file = snapshot.file_at(location);
21509 let language = snapshot.language_at(location).map(|l| l.name());
21510 language_settings(language, file, cx).inlay_hints
21511}
21512
21513fn consume_contiguous_rows(
21514 contiguous_row_selections: &mut Vec<Selection<Point>>,
21515 selection: &Selection<Point>,
21516 display_map: &DisplaySnapshot,
21517 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
21518) -> (MultiBufferRow, MultiBufferRow) {
21519 contiguous_row_selections.push(selection.clone());
21520 let start_row = MultiBufferRow(selection.start.row);
21521 let mut end_row = ending_row(selection, display_map);
21522
21523 while let Some(next_selection) = selections.peek() {
21524 if next_selection.start.row <= end_row.0 {
21525 end_row = ending_row(next_selection, display_map);
21526 contiguous_row_selections.push(selections.next().unwrap().clone());
21527 } else {
21528 break;
21529 }
21530 }
21531 (start_row, end_row)
21532}
21533
21534fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
21535 if next_selection.end.column > 0 || next_selection.is_empty() {
21536 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
21537 } else {
21538 MultiBufferRow(next_selection.end.row)
21539 }
21540}
21541
21542impl EditorSnapshot {
21543 pub fn remote_selections_in_range<'a>(
21544 &'a self,
21545 range: &'a Range<Anchor>,
21546 collaboration_hub: &dyn CollaborationHub,
21547 cx: &'a App,
21548 ) -> impl 'a + Iterator<Item = RemoteSelection> {
21549 let participant_names = collaboration_hub.user_names(cx);
21550 let participant_indices = collaboration_hub.user_participant_indices(cx);
21551 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
21552 let collaborators_by_replica_id = collaborators_by_peer_id
21553 .values()
21554 .map(|collaborator| (collaborator.replica_id, collaborator))
21555 .collect::<HashMap<_, _>>();
21556 self.buffer_snapshot
21557 .selections_in_range(range, false)
21558 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
21559 if replica_id == AGENT_REPLICA_ID {
21560 Some(RemoteSelection {
21561 replica_id,
21562 selection,
21563 cursor_shape,
21564 line_mode,
21565 collaborator_id: CollaboratorId::Agent,
21566 user_name: Some("Agent".into()),
21567 color: cx.theme().players().agent(),
21568 })
21569 } else {
21570 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
21571 let participant_index = participant_indices.get(&collaborator.user_id).copied();
21572 let user_name = participant_names.get(&collaborator.user_id).cloned();
21573 Some(RemoteSelection {
21574 replica_id,
21575 selection,
21576 cursor_shape,
21577 line_mode,
21578 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
21579 user_name,
21580 color: if let Some(index) = participant_index {
21581 cx.theme().players().color_for_participant(index.0)
21582 } else {
21583 cx.theme().players().absent()
21584 },
21585 })
21586 }
21587 })
21588 }
21589
21590 pub fn hunks_for_ranges(
21591 &self,
21592 ranges: impl IntoIterator<Item = Range<Point>>,
21593 ) -> Vec<MultiBufferDiffHunk> {
21594 let mut hunks = Vec::new();
21595 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
21596 HashMap::default();
21597 for query_range in ranges {
21598 let query_rows =
21599 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
21600 for hunk in self.buffer_snapshot.diff_hunks_in_range(
21601 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
21602 ) {
21603 // Include deleted hunks that are adjacent to the query range, because
21604 // otherwise they would be missed.
21605 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
21606 if hunk.status().is_deleted() {
21607 intersects_range |= hunk.row_range.start == query_rows.end;
21608 intersects_range |= hunk.row_range.end == query_rows.start;
21609 }
21610 if intersects_range {
21611 if !processed_buffer_rows
21612 .entry(hunk.buffer_id)
21613 .or_default()
21614 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
21615 {
21616 continue;
21617 }
21618 hunks.push(hunk);
21619 }
21620 }
21621 }
21622
21623 hunks
21624 }
21625
21626 fn display_diff_hunks_for_rows<'a>(
21627 &'a self,
21628 display_rows: Range<DisplayRow>,
21629 folded_buffers: &'a HashSet<BufferId>,
21630 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
21631 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
21632 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
21633
21634 self.buffer_snapshot
21635 .diff_hunks_in_range(buffer_start..buffer_end)
21636 .filter_map(|hunk| {
21637 if folded_buffers.contains(&hunk.buffer_id) {
21638 return None;
21639 }
21640
21641 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
21642 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
21643
21644 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
21645 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
21646
21647 let display_hunk = if hunk_display_start.column() != 0 {
21648 DisplayDiffHunk::Folded {
21649 display_row: hunk_display_start.row(),
21650 }
21651 } else {
21652 let mut end_row = hunk_display_end.row();
21653 if hunk_display_end.column() > 0 {
21654 end_row.0 += 1;
21655 }
21656 let is_created_file = hunk.is_created_file();
21657 DisplayDiffHunk::Unfolded {
21658 status: hunk.status(),
21659 diff_base_byte_range: hunk.diff_base_byte_range,
21660 display_row_range: hunk_display_start.row()..end_row,
21661 multi_buffer_range: Anchor::range_in_buffer(
21662 hunk.excerpt_id,
21663 hunk.buffer_id,
21664 hunk.buffer_range,
21665 ),
21666 is_created_file,
21667 }
21668 };
21669
21670 Some(display_hunk)
21671 })
21672 }
21673
21674 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
21675 self.display_snapshot.buffer_snapshot.language_at(position)
21676 }
21677
21678 pub fn is_focused(&self) -> bool {
21679 self.is_focused
21680 }
21681
21682 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
21683 self.placeholder_text.as_ref()
21684 }
21685
21686 pub fn scroll_position(&self) -> gpui::Point<f32> {
21687 self.scroll_anchor.scroll_position(&self.display_snapshot)
21688 }
21689
21690 fn gutter_dimensions(
21691 &self,
21692 font_id: FontId,
21693 font_size: Pixels,
21694 max_line_number_width: Pixels,
21695 cx: &App,
21696 ) -> Option<GutterDimensions> {
21697 if !self.show_gutter {
21698 return None;
21699 }
21700
21701 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
21702 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
21703
21704 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
21705 matches!(
21706 ProjectSettings::get_global(cx).git.git_gutter,
21707 Some(GitGutterSetting::TrackedFiles)
21708 )
21709 });
21710 let gutter_settings = EditorSettings::get_global(cx).gutter;
21711 let show_line_numbers = self
21712 .show_line_numbers
21713 .unwrap_or(gutter_settings.line_numbers);
21714 let line_gutter_width = if show_line_numbers {
21715 // Avoid flicker-like gutter resizes when the line number gains another digit by
21716 // only resizing the gutter on files with > 10**min_line_number_digits lines.
21717 let min_width_for_number_on_gutter =
21718 ch_advance * gutter_settings.min_line_number_digits as f32;
21719 max_line_number_width.max(min_width_for_number_on_gutter)
21720 } else {
21721 0.0.into()
21722 };
21723
21724 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
21725 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
21726
21727 let git_blame_entries_width =
21728 self.git_blame_gutter_max_author_length
21729 .map(|max_author_length| {
21730 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21731 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
21732
21733 /// The number of characters to dedicate to gaps and margins.
21734 const SPACING_WIDTH: usize = 4;
21735
21736 let max_char_count = max_author_length.min(renderer.max_author_length())
21737 + ::git::SHORT_SHA_LENGTH
21738 + MAX_RELATIVE_TIMESTAMP.len()
21739 + SPACING_WIDTH;
21740
21741 ch_advance * max_char_count
21742 });
21743
21744 let is_singleton = self.buffer_snapshot.is_singleton();
21745
21746 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
21747 left_padding += if !is_singleton {
21748 ch_width * 4.0
21749 } else if show_runnables || show_breakpoints {
21750 ch_width * 3.0
21751 } else if show_git_gutter && show_line_numbers {
21752 ch_width * 2.0
21753 } else if show_git_gutter || show_line_numbers {
21754 ch_width
21755 } else {
21756 px(0.)
21757 };
21758
21759 let shows_folds = is_singleton && gutter_settings.folds;
21760
21761 let right_padding = if shows_folds && show_line_numbers {
21762 ch_width * 4.0
21763 } else if shows_folds || (!is_singleton && show_line_numbers) {
21764 ch_width * 3.0
21765 } else if show_line_numbers {
21766 ch_width
21767 } else {
21768 px(0.)
21769 };
21770
21771 Some(GutterDimensions {
21772 left_padding,
21773 right_padding,
21774 width: line_gutter_width + left_padding + right_padding,
21775 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
21776 git_blame_entries_width,
21777 })
21778 }
21779
21780 pub fn render_crease_toggle(
21781 &self,
21782 buffer_row: MultiBufferRow,
21783 row_contains_cursor: bool,
21784 editor: Entity<Editor>,
21785 window: &mut Window,
21786 cx: &mut App,
21787 ) -> Option<AnyElement> {
21788 let folded = self.is_line_folded(buffer_row);
21789 let mut is_foldable = false;
21790
21791 if let Some(crease) = self
21792 .crease_snapshot
21793 .query_row(buffer_row, &self.buffer_snapshot)
21794 {
21795 is_foldable = true;
21796 match crease {
21797 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
21798 if let Some(render_toggle) = render_toggle {
21799 let toggle_callback =
21800 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
21801 if folded {
21802 editor.update(cx, |editor, cx| {
21803 editor.fold_at(buffer_row, window, cx)
21804 });
21805 } else {
21806 editor.update(cx, |editor, cx| {
21807 editor.unfold_at(buffer_row, window, cx)
21808 });
21809 }
21810 });
21811 return Some((render_toggle)(
21812 buffer_row,
21813 folded,
21814 toggle_callback,
21815 window,
21816 cx,
21817 ));
21818 }
21819 }
21820 }
21821 }
21822
21823 is_foldable |= self.starts_indent(buffer_row);
21824
21825 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
21826 Some(
21827 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
21828 .toggle_state(folded)
21829 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
21830 if folded {
21831 this.unfold_at(buffer_row, window, cx);
21832 } else {
21833 this.fold_at(buffer_row, window, cx);
21834 }
21835 }))
21836 .into_any_element(),
21837 )
21838 } else {
21839 None
21840 }
21841 }
21842
21843 pub fn render_crease_trailer(
21844 &self,
21845 buffer_row: MultiBufferRow,
21846 window: &mut Window,
21847 cx: &mut App,
21848 ) -> Option<AnyElement> {
21849 let folded = self.is_line_folded(buffer_row);
21850 if let Crease::Inline { render_trailer, .. } = self
21851 .crease_snapshot
21852 .query_row(buffer_row, &self.buffer_snapshot)?
21853 {
21854 let render_trailer = render_trailer.as_ref()?;
21855 Some(render_trailer(buffer_row, folded, window, cx))
21856 } else {
21857 None
21858 }
21859 }
21860}
21861
21862impl Deref for EditorSnapshot {
21863 type Target = DisplaySnapshot;
21864
21865 fn deref(&self) -> &Self::Target {
21866 &self.display_snapshot
21867 }
21868}
21869
21870#[derive(Clone, Debug, PartialEq, Eq)]
21871pub enum EditorEvent {
21872 InputIgnored {
21873 text: Arc<str>,
21874 },
21875 InputHandled {
21876 utf16_range_to_replace: Option<Range<isize>>,
21877 text: Arc<str>,
21878 },
21879 ExcerptsAdded {
21880 buffer: Entity<Buffer>,
21881 predecessor: ExcerptId,
21882 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21883 },
21884 ExcerptsRemoved {
21885 ids: Vec<ExcerptId>,
21886 removed_buffer_ids: Vec<BufferId>,
21887 },
21888 BufferFoldToggled {
21889 ids: Vec<ExcerptId>,
21890 folded: bool,
21891 },
21892 ExcerptsEdited {
21893 ids: Vec<ExcerptId>,
21894 },
21895 ExcerptsExpanded {
21896 ids: Vec<ExcerptId>,
21897 },
21898 BufferEdited,
21899 Edited {
21900 transaction_id: clock::Lamport,
21901 },
21902 Reparsed(BufferId),
21903 Focused,
21904 FocusedIn,
21905 Blurred,
21906 DirtyChanged,
21907 Saved,
21908 TitleChanged,
21909 DiffBaseChanged,
21910 SelectionsChanged {
21911 local: bool,
21912 },
21913 ScrollPositionChanged {
21914 local: bool,
21915 autoscroll: bool,
21916 },
21917 Closed,
21918 TransactionUndone {
21919 transaction_id: clock::Lamport,
21920 },
21921 TransactionBegun {
21922 transaction_id: clock::Lamport,
21923 },
21924 Reloaded,
21925 CursorShapeChanged,
21926 PushedToNavHistory {
21927 anchor: Anchor,
21928 is_deactivate: bool,
21929 },
21930}
21931
21932impl EventEmitter<EditorEvent> for Editor {}
21933
21934impl Focusable for Editor {
21935 fn focus_handle(&self, _cx: &App) -> FocusHandle {
21936 self.focus_handle.clone()
21937 }
21938}
21939
21940impl Render for Editor {
21941 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21942 let settings = ThemeSettings::get_global(cx);
21943
21944 let mut text_style = match self.mode {
21945 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21946 color: cx.theme().colors().editor_foreground,
21947 font_family: settings.ui_font.family.clone(),
21948 font_features: settings.ui_font.features.clone(),
21949 font_fallbacks: settings.ui_font.fallbacks.clone(),
21950 font_size: rems(0.875).into(),
21951 font_weight: settings.ui_font.weight,
21952 line_height: relative(settings.buffer_line_height.value()),
21953 ..Default::default()
21954 },
21955 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
21956 color: cx.theme().colors().editor_foreground,
21957 font_family: settings.buffer_font.family.clone(),
21958 font_features: settings.buffer_font.features.clone(),
21959 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21960 font_size: settings.buffer_font_size(cx).into(),
21961 font_weight: settings.buffer_font.weight,
21962 line_height: relative(settings.buffer_line_height.value()),
21963 ..Default::default()
21964 },
21965 };
21966 if let Some(text_style_refinement) = &self.text_style_refinement {
21967 text_style.refine(text_style_refinement)
21968 }
21969
21970 let background = match self.mode {
21971 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
21972 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
21973 EditorMode::Full { .. } => cx.theme().colors().editor_background,
21974 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
21975 };
21976
21977 EditorElement::new(
21978 &cx.entity(),
21979 EditorStyle {
21980 background,
21981 local_player: cx.theme().players().local(),
21982 text: text_style,
21983 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
21984 syntax: cx.theme().syntax().clone(),
21985 status: cx.theme().status().clone(),
21986 inlay_hints_style: make_inlay_hints_style(cx),
21987 inline_completion_styles: make_suggestion_styles(cx),
21988 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
21989 show_underlines: !self.mode.is_minimap(),
21990 },
21991 )
21992 }
21993}
21994
21995impl EntityInputHandler for Editor {
21996 fn text_for_range(
21997 &mut self,
21998 range_utf16: Range<usize>,
21999 adjusted_range: &mut Option<Range<usize>>,
22000 _: &mut Window,
22001 cx: &mut Context<Self>,
22002 ) -> Option<String> {
22003 let snapshot = self.buffer.read(cx).read(cx);
22004 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22005 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22006 if (start.0..end.0) != range_utf16 {
22007 adjusted_range.replace(start.0..end.0);
22008 }
22009 Some(snapshot.text_for_range(start..end).collect())
22010 }
22011
22012 fn selected_text_range(
22013 &mut self,
22014 ignore_disabled_input: bool,
22015 _: &mut Window,
22016 cx: &mut Context<Self>,
22017 ) -> Option<UTF16Selection> {
22018 // Prevent the IME menu from appearing when holding down an alphabetic key
22019 // while input is disabled.
22020 if !ignore_disabled_input && !self.input_enabled {
22021 return None;
22022 }
22023
22024 let selection = self.selections.newest::<OffsetUtf16>(cx);
22025 let range = selection.range();
22026
22027 Some(UTF16Selection {
22028 range: range.start.0..range.end.0,
22029 reversed: selection.reversed,
22030 })
22031 }
22032
22033 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22034 let snapshot = self.buffer.read(cx).read(cx);
22035 let (range, _) = self.text_highlights::<InputComposition>(cx)?.first()?;
22036 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22037 }
22038
22039 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22040 self.clear_highlights::<InputComposition>(cx);
22041 self.ime_transaction.take();
22042 }
22043
22044 fn replace_text_in_range(
22045 &mut self,
22046 range_utf16: Option<Range<usize>>,
22047 text: &str,
22048 window: &mut Window,
22049 cx: &mut Context<Self>,
22050 ) {
22051 if !self.input_enabled {
22052 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22053 return;
22054 }
22055
22056 self.transact(window, cx, |this, window, cx| {
22057 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22058 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22059 Some(this.selection_replacement_ranges(range_utf16, cx))
22060 } else {
22061 this.marked_text_ranges(cx)
22062 };
22063
22064 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22065 let newest_selection_id = this.selections.newest_anchor().id;
22066 this.selections
22067 .all::<OffsetUtf16>(cx)
22068 .iter()
22069 .zip(ranges_to_replace.iter())
22070 .find_map(|(selection, range)| {
22071 if selection.id == newest_selection_id {
22072 Some(
22073 (range.start.0 as isize - selection.head().0 as isize)
22074 ..(range.end.0 as isize - selection.head().0 as isize),
22075 )
22076 } else {
22077 None
22078 }
22079 })
22080 });
22081
22082 cx.emit(EditorEvent::InputHandled {
22083 utf16_range_to_replace: range_to_replace,
22084 text: text.into(),
22085 });
22086
22087 if let Some(new_selected_ranges) = new_selected_ranges {
22088 this.change_selections(None, window, cx, |selections| {
22089 selections.select_ranges(new_selected_ranges)
22090 });
22091 this.backspace(&Default::default(), window, cx);
22092 }
22093
22094 this.handle_input(text, window, cx);
22095 });
22096
22097 if let Some(transaction) = self.ime_transaction {
22098 self.buffer.update(cx, |buffer, cx| {
22099 buffer.group_until_transaction(transaction, cx);
22100 });
22101 }
22102
22103 self.unmark_text(window, cx);
22104 }
22105
22106 fn replace_and_mark_text_in_range(
22107 &mut self,
22108 range_utf16: Option<Range<usize>>,
22109 text: &str,
22110 new_selected_range_utf16: Option<Range<usize>>,
22111 window: &mut Window,
22112 cx: &mut Context<Self>,
22113 ) {
22114 if !self.input_enabled {
22115 return;
22116 }
22117
22118 let transaction = self.transact(window, cx, |this, window, cx| {
22119 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22120 let snapshot = this.buffer.read(cx).read(cx);
22121 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22122 for marked_range in &mut marked_ranges {
22123 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22124 marked_range.start.0 += relative_range_utf16.start;
22125 marked_range.start =
22126 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22127 marked_range.end =
22128 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22129 }
22130 }
22131 Some(marked_ranges)
22132 } else if let Some(range_utf16) = range_utf16 {
22133 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22134 Some(this.selection_replacement_ranges(range_utf16, cx))
22135 } else {
22136 None
22137 };
22138
22139 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22140 let newest_selection_id = this.selections.newest_anchor().id;
22141 this.selections
22142 .all::<OffsetUtf16>(cx)
22143 .iter()
22144 .zip(ranges_to_replace.iter())
22145 .find_map(|(selection, range)| {
22146 if selection.id == newest_selection_id {
22147 Some(
22148 (range.start.0 as isize - selection.head().0 as isize)
22149 ..(range.end.0 as isize - selection.head().0 as isize),
22150 )
22151 } else {
22152 None
22153 }
22154 })
22155 });
22156
22157 cx.emit(EditorEvent::InputHandled {
22158 utf16_range_to_replace: range_to_replace,
22159 text: text.into(),
22160 });
22161
22162 if let Some(ranges) = ranges_to_replace {
22163 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
22164 }
22165
22166 let marked_ranges = {
22167 let snapshot = this.buffer.read(cx).read(cx);
22168 this.selections
22169 .disjoint_anchors()
22170 .iter()
22171 .map(|selection| {
22172 (
22173 selection.start.bias_left(&snapshot)
22174 ..selection.end.bias_right(&snapshot),
22175 HighlightStyle {
22176 underline: Some(UnderlineStyle {
22177 thickness: px(1.),
22178 color: None,
22179 wavy: false,
22180 }),
22181 ..Default::default()
22182 },
22183 )
22184 })
22185 .collect::<Vec<_>>()
22186 };
22187
22188 if text.is_empty() {
22189 this.unmark_text(window, cx);
22190 } else {
22191 this.highlight_text::<InputComposition>(marked_ranges.clone(), cx);
22192 }
22193
22194 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22195 let use_autoclose = this.use_autoclose;
22196 let use_auto_surround = this.use_auto_surround;
22197 this.set_use_autoclose(false);
22198 this.set_use_auto_surround(false);
22199 this.handle_input(text, window, cx);
22200 this.set_use_autoclose(use_autoclose);
22201 this.set_use_auto_surround(use_auto_surround);
22202
22203 if let Some(new_selected_range) = new_selected_range_utf16 {
22204 let snapshot = this.buffer.read(cx).read(cx);
22205 let new_selected_ranges = marked_ranges
22206 .into_iter()
22207 .map(|(marked_range, _)| {
22208 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22209 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22210 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22211 snapshot.clip_offset_utf16(new_start, Bias::Left)
22212 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22213 })
22214 .collect::<Vec<_>>();
22215
22216 drop(snapshot);
22217 this.change_selections(None, window, cx, |selections| {
22218 selections.select_ranges(new_selected_ranges)
22219 });
22220 }
22221 });
22222
22223 self.ime_transaction = self.ime_transaction.or(transaction);
22224 if let Some(transaction) = self.ime_transaction {
22225 self.buffer.update(cx, |buffer, cx| {
22226 buffer.group_until_transaction(transaction, cx);
22227 });
22228 }
22229
22230 if self.text_highlights::<InputComposition>(cx).is_none() {
22231 self.ime_transaction.take();
22232 }
22233 }
22234
22235 fn bounds_for_range(
22236 &mut self,
22237 range_utf16: Range<usize>,
22238 element_bounds: gpui::Bounds<Pixels>,
22239 window: &mut Window,
22240 cx: &mut Context<Self>,
22241 ) -> Option<gpui::Bounds<Pixels>> {
22242 let text_layout_details = self.text_layout_details(window);
22243 let gpui::Size {
22244 width: em_width,
22245 height: line_height,
22246 } = self.character_size(window);
22247
22248 let snapshot = self.snapshot(window, cx);
22249 let scroll_position = snapshot.scroll_position();
22250 let scroll_left = scroll_position.x * em_width;
22251
22252 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22253 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22254 + self.gutter_dimensions.width
22255 + self.gutter_dimensions.margin;
22256 let y = line_height * (start.row().as_f32() - scroll_position.y);
22257
22258 Some(Bounds {
22259 origin: element_bounds.origin + point(x, y),
22260 size: size(em_width, line_height),
22261 })
22262 }
22263
22264 fn character_index_for_point(
22265 &mut self,
22266 point: gpui::Point<Pixels>,
22267 _window: &mut Window,
22268 _cx: &mut Context<Self>,
22269 ) -> Option<usize> {
22270 let position_map = self.last_position_map.as_ref()?;
22271 if !position_map.text_hitbox.contains(&point) {
22272 return None;
22273 }
22274 let display_point = position_map.point_for_position(point).previous_valid;
22275 let anchor = position_map
22276 .snapshot
22277 .display_point_to_anchor(display_point, Bias::Left);
22278 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22279 Some(utf16_offset.0)
22280 }
22281}
22282
22283trait SelectionExt {
22284 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22285 fn spanned_rows(
22286 &self,
22287 include_end_if_at_line_start: bool,
22288 map: &DisplaySnapshot,
22289 ) -> Range<MultiBufferRow>;
22290}
22291
22292impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22293 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22294 let start = self
22295 .start
22296 .to_point(&map.buffer_snapshot)
22297 .to_display_point(map);
22298 let end = self
22299 .end
22300 .to_point(&map.buffer_snapshot)
22301 .to_display_point(map);
22302 if self.reversed {
22303 end..start
22304 } else {
22305 start..end
22306 }
22307 }
22308
22309 fn spanned_rows(
22310 &self,
22311 include_end_if_at_line_start: bool,
22312 map: &DisplaySnapshot,
22313 ) -> Range<MultiBufferRow> {
22314 let start = self.start.to_point(&map.buffer_snapshot);
22315 let mut end = self.end.to_point(&map.buffer_snapshot);
22316 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22317 end.row -= 1;
22318 }
22319
22320 let buffer_start = map.prev_line_boundary(start).0;
22321 let buffer_end = map.next_line_boundary(end).0;
22322 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22323 }
22324}
22325
22326impl<T: InvalidationRegion> InvalidationStack<T> {
22327 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22328 where
22329 S: Clone + ToOffset,
22330 {
22331 while let Some(region) = self.last() {
22332 let all_selections_inside_invalidation_ranges =
22333 if selections.len() == region.ranges().len() {
22334 selections
22335 .iter()
22336 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22337 .all(|(selection, invalidation_range)| {
22338 let head = selection.head().to_offset(buffer);
22339 invalidation_range.start <= head && invalidation_range.end >= head
22340 })
22341 } else {
22342 false
22343 };
22344
22345 if all_selections_inside_invalidation_ranges {
22346 break;
22347 } else {
22348 self.pop();
22349 }
22350 }
22351 }
22352}
22353
22354impl<T> Default for InvalidationStack<T> {
22355 fn default() -> Self {
22356 Self(Default::default())
22357 }
22358}
22359
22360impl<T> Deref for InvalidationStack<T> {
22361 type Target = Vec<T>;
22362
22363 fn deref(&self) -> &Self::Target {
22364 &self.0
22365 }
22366}
22367
22368impl<T> DerefMut for InvalidationStack<T> {
22369 fn deref_mut(&mut self) -> &mut Self::Target {
22370 &mut self.0
22371 }
22372}
22373
22374impl InvalidationRegion for SnippetState {
22375 fn ranges(&self) -> &[Range<Anchor>] {
22376 &self.ranges[self.active_index]
22377 }
22378}
22379
22380fn inline_completion_edit_text(
22381 current_snapshot: &BufferSnapshot,
22382 edits: &[(Range<Anchor>, String)],
22383 edit_preview: &EditPreview,
22384 include_deletions: bool,
22385 cx: &App,
22386) -> HighlightedText {
22387 let edits = edits
22388 .iter()
22389 .map(|(anchor, text)| {
22390 (
22391 anchor.start.text_anchor..anchor.end.text_anchor,
22392 text.clone(),
22393 )
22394 })
22395 .collect::<Vec<_>>();
22396
22397 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22398}
22399
22400pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22401 match severity {
22402 lsp::DiagnosticSeverity::ERROR => colors.error,
22403 lsp::DiagnosticSeverity::WARNING => colors.warning,
22404 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22405 lsp::DiagnosticSeverity::HINT => colors.info,
22406 _ => colors.ignored,
22407 }
22408}
22409
22410pub fn styled_runs_for_code_label<'a>(
22411 label: &'a CodeLabel,
22412 syntax_theme: &'a theme::SyntaxTheme,
22413) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22414 let fade_out = HighlightStyle {
22415 fade_out: Some(0.35),
22416 ..Default::default()
22417 };
22418
22419 let mut prev_end = label.filter_range.end;
22420 label
22421 .runs
22422 .iter()
22423 .enumerate()
22424 .flat_map(move |(ix, (range, highlight_id))| {
22425 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22426 style
22427 } else {
22428 return Default::default();
22429 };
22430 let mut muted_style = style;
22431 muted_style.highlight(fade_out);
22432
22433 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22434 if range.start >= label.filter_range.end {
22435 if range.start > prev_end {
22436 runs.push((prev_end..range.start, fade_out));
22437 }
22438 runs.push((range.clone(), muted_style));
22439 } else if range.end <= label.filter_range.end {
22440 runs.push((range.clone(), style));
22441 } else {
22442 runs.push((range.start..label.filter_range.end, style));
22443 runs.push((label.filter_range.end..range.end, muted_style));
22444 }
22445 prev_end = cmp::max(prev_end, range.end);
22446
22447 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22448 runs.push((prev_end..label.text.len(), fade_out));
22449 }
22450
22451 runs
22452 })
22453}
22454
22455pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22456 let mut prev_index = 0;
22457 let mut prev_codepoint: Option<char> = None;
22458 text.char_indices()
22459 .chain([(text.len(), '\0')])
22460 .filter_map(move |(index, codepoint)| {
22461 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22462 let is_boundary = index == text.len()
22463 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22464 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22465 if is_boundary {
22466 let chunk = &text[prev_index..index];
22467 prev_index = index;
22468 Some(chunk)
22469 } else {
22470 None
22471 }
22472 })
22473}
22474
22475pub trait RangeToAnchorExt: Sized {
22476 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22477
22478 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22479 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22480 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22481 }
22482}
22483
22484impl<T: ToOffset> RangeToAnchorExt for Range<T> {
22485 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
22486 let start_offset = self.start.to_offset(snapshot);
22487 let end_offset = self.end.to_offset(snapshot);
22488 if start_offset == end_offset {
22489 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
22490 } else {
22491 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
22492 }
22493 }
22494}
22495
22496pub trait RowExt {
22497 fn as_f32(&self) -> f32;
22498
22499 fn next_row(&self) -> Self;
22500
22501 fn previous_row(&self) -> Self;
22502
22503 fn minus(&self, other: Self) -> u32;
22504}
22505
22506impl RowExt for DisplayRow {
22507 fn as_f32(&self) -> f32 {
22508 self.0 as f32
22509 }
22510
22511 fn next_row(&self) -> Self {
22512 Self(self.0 + 1)
22513 }
22514
22515 fn previous_row(&self) -> Self {
22516 Self(self.0.saturating_sub(1))
22517 }
22518
22519 fn minus(&self, other: Self) -> u32 {
22520 self.0 - other.0
22521 }
22522}
22523
22524impl RowExt for MultiBufferRow {
22525 fn as_f32(&self) -> f32 {
22526 self.0 as f32
22527 }
22528
22529 fn next_row(&self) -> Self {
22530 Self(self.0 + 1)
22531 }
22532
22533 fn previous_row(&self) -> Self {
22534 Self(self.0.saturating_sub(1))
22535 }
22536
22537 fn minus(&self, other: Self) -> u32 {
22538 self.0 - other.0
22539 }
22540}
22541
22542trait RowRangeExt {
22543 type Row;
22544
22545 fn len(&self) -> usize;
22546
22547 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
22548}
22549
22550impl RowRangeExt for Range<MultiBufferRow> {
22551 type Row = MultiBufferRow;
22552
22553 fn len(&self) -> usize {
22554 (self.end.0 - self.start.0) as usize
22555 }
22556
22557 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
22558 (self.start.0..self.end.0).map(MultiBufferRow)
22559 }
22560}
22561
22562impl RowRangeExt for Range<DisplayRow> {
22563 type Row = DisplayRow;
22564
22565 fn len(&self) -> usize {
22566 (self.end.0 - self.start.0) as usize
22567 }
22568
22569 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
22570 (self.start.0..self.end.0).map(DisplayRow)
22571 }
22572}
22573
22574/// If select range has more than one line, we
22575/// just point the cursor to range.start.
22576fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
22577 if range.start.row == range.end.row {
22578 range
22579 } else {
22580 range.start..range.start
22581 }
22582}
22583pub struct KillRing(ClipboardItem);
22584impl Global for KillRing {}
22585
22586const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
22587
22588enum BreakpointPromptEditAction {
22589 Log,
22590 Condition,
22591 HitCondition,
22592}
22593
22594struct BreakpointPromptEditor {
22595 pub(crate) prompt: Entity<Editor>,
22596 editor: WeakEntity<Editor>,
22597 breakpoint_anchor: Anchor,
22598 breakpoint: Breakpoint,
22599 edit_action: BreakpointPromptEditAction,
22600 block_ids: HashSet<CustomBlockId>,
22601 editor_margins: Arc<Mutex<EditorMargins>>,
22602 _subscriptions: Vec<Subscription>,
22603}
22604
22605impl BreakpointPromptEditor {
22606 const MAX_LINES: u8 = 4;
22607
22608 fn new(
22609 editor: WeakEntity<Editor>,
22610 breakpoint_anchor: Anchor,
22611 breakpoint: Breakpoint,
22612 edit_action: BreakpointPromptEditAction,
22613 window: &mut Window,
22614 cx: &mut Context<Self>,
22615 ) -> Self {
22616 let base_text = match edit_action {
22617 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
22618 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
22619 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
22620 }
22621 .map(|msg| msg.to_string())
22622 .unwrap_or_default();
22623
22624 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
22625 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
22626
22627 let prompt = cx.new(|cx| {
22628 let mut prompt = Editor::new(
22629 EditorMode::AutoHeight {
22630 min_lines: 1,
22631 max_lines: Self::MAX_LINES as usize,
22632 },
22633 buffer,
22634 None,
22635 window,
22636 cx,
22637 );
22638 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
22639 prompt.set_show_cursor_when_unfocused(false, cx);
22640 prompt.set_placeholder_text(
22641 match edit_action {
22642 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
22643 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
22644 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
22645 },
22646 cx,
22647 );
22648
22649 prompt
22650 });
22651
22652 Self {
22653 prompt,
22654 editor,
22655 breakpoint_anchor,
22656 breakpoint,
22657 edit_action,
22658 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
22659 block_ids: Default::default(),
22660 _subscriptions: vec![],
22661 }
22662 }
22663
22664 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
22665 self.block_ids.extend(block_ids)
22666 }
22667
22668 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
22669 if let Some(editor) = self.editor.upgrade() {
22670 let message = self
22671 .prompt
22672 .read(cx)
22673 .buffer
22674 .read(cx)
22675 .as_singleton()
22676 .expect("A multi buffer in breakpoint prompt isn't possible")
22677 .read(cx)
22678 .as_rope()
22679 .to_string();
22680
22681 editor.update(cx, |editor, cx| {
22682 editor.edit_breakpoint_at_anchor(
22683 self.breakpoint_anchor,
22684 self.breakpoint.clone(),
22685 match self.edit_action {
22686 BreakpointPromptEditAction::Log => {
22687 BreakpointEditAction::EditLogMessage(message.into())
22688 }
22689 BreakpointPromptEditAction::Condition => {
22690 BreakpointEditAction::EditCondition(message.into())
22691 }
22692 BreakpointPromptEditAction::HitCondition => {
22693 BreakpointEditAction::EditHitCondition(message.into())
22694 }
22695 },
22696 cx,
22697 );
22698
22699 editor.remove_blocks(self.block_ids.clone(), None, cx);
22700 cx.focus_self(window);
22701 });
22702 }
22703 }
22704
22705 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
22706 self.editor
22707 .update(cx, |editor, cx| {
22708 editor.remove_blocks(self.block_ids.clone(), None, cx);
22709 window.focus(&editor.focus_handle);
22710 })
22711 .log_err();
22712 }
22713
22714 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22715 let settings = ThemeSettings::get_global(cx);
22716 let text_style = TextStyle {
22717 color: if self.prompt.read(cx).read_only(cx) {
22718 cx.theme().colors().text_disabled
22719 } else {
22720 cx.theme().colors().text
22721 },
22722 font_family: settings.buffer_font.family.clone(),
22723 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22724 font_size: settings.buffer_font_size(cx).into(),
22725 font_weight: settings.buffer_font.weight,
22726 line_height: relative(settings.buffer_line_height.value()),
22727 ..Default::default()
22728 };
22729 EditorElement::new(
22730 &self.prompt,
22731 EditorStyle {
22732 background: cx.theme().colors().editor_background,
22733 local_player: cx.theme().players().local(),
22734 text: text_style,
22735 ..Default::default()
22736 },
22737 )
22738 }
22739}
22740
22741impl Render for BreakpointPromptEditor {
22742 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22743 let editor_margins = *self.editor_margins.lock();
22744 let gutter_dimensions = editor_margins.gutter;
22745 h_flex()
22746 .key_context("Editor")
22747 .bg(cx.theme().colors().editor_background)
22748 .border_y_1()
22749 .border_color(cx.theme().status().info_border)
22750 .size_full()
22751 .py(window.line_height() / 2.5)
22752 .on_action(cx.listener(Self::confirm))
22753 .on_action(cx.listener(Self::cancel))
22754 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
22755 .child(div().flex_1().child(self.render_prompt_editor(cx)))
22756 }
22757}
22758
22759impl Focusable for BreakpointPromptEditor {
22760 fn focus_handle(&self, cx: &App) -> FocusHandle {
22761 self.prompt.focus_handle(cx)
22762 }
22763}
22764
22765fn all_edits_insertions_or_deletions(
22766 edits: &Vec<(Range<Anchor>, String)>,
22767 snapshot: &MultiBufferSnapshot,
22768) -> bool {
22769 let mut all_insertions = true;
22770 let mut all_deletions = true;
22771
22772 for (range, new_text) in edits.iter() {
22773 let range_is_empty = range.to_offset(&snapshot).is_empty();
22774 let text_is_empty = new_text.is_empty();
22775
22776 if range_is_empty != text_is_empty {
22777 if range_is_empty {
22778 all_deletions = false;
22779 } else {
22780 all_insertions = false;
22781 }
22782 } else {
22783 return false;
22784 }
22785
22786 if !all_insertions && !all_deletions {
22787 return false;
22788 }
22789 }
22790 all_insertions || all_deletions
22791}
22792
22793struct MissingEditPredictionKeybindingTooltip;
22794
22795impl Render for MissingEditPredictionKeybindingTooltip {
22796 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22797 ui::tooltip_container(window, cx, |container, _, cx| {
22798 container
22799 .flex_shrink_0()
22800 .max_w_80()
22801 .min_h(rems_from_px(124.))
22802 .justify_between()
22803 .child(
22804 v_flex()
22805 .flex_1()
22806 .text_ui_sm(cx)
22807 .child(Label::new("Conflict with Accept Keybinding"))
22808 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
22809 )
22810 .child(
22811 h_flex()
22812 .pb_1()
22813 .gap_1()
22814 .items_end()
22815 .w_full()
22816 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
22817 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
22818 }))
22819 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
22820 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
22821 })),
22822 )
22823 })
22824 }
22825}
22826
22827#[derive(Debug, Clone, Copy, PartialEq)]
22828pub struct LineHighlight {
22829 pub background: Background,
22830 pub border: Option<gpui::Hsla>,
22831 pub include_gutter: bool,
22832 pub type_id: Option<TypeId>,
22833}
22834
22835fn render_diff_hunk_controls(
22836 row: u32,
22837 status: &DiffHunkStatus,
22838 hunk_range: Range<Anchor>,
22839 is_created_file: bool,
22840 line_height: Pixels,
22841 editor: &Entity<Editor>,
22842 _window: &mut Window,
22843 cx: &mut App,
22844) -> AnyElement {
22845 h_flex()
22846 .h(line_height)
22847 .mr_1()
22848 .gap_1()
22849 .px_0p5()
22850 .pb_1()
22851 .border_x_1()
22852 .border_b_1()
22853 .border_color(cx.theme().colors().border_variant)
22854 .rounded_b_lg()
22855 .bg(cx.theme().colors().editor_background)
22856 .gap_1()
22857 .block_mouse_except_scroll()
22858 .shadow_md()
22859 .child(if status.has_secondary_hunk() {
22860 Button::new(("stage", row as u64), "Stage")
22861 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22862 .tooltip({
22863 let focus_handle = editor.focus_handle(cx);
22864 move |window, cx| {
22865 Tooltip::for_action_in(
22866 "Stage Hunk",
22867 &::git::ToggleStaged,
22868 &focus_handle,
22869 window,
22870 cx,
22871 )
22872 }
22873 })
22874 .on_click({
22875 let editor = editor.clone();
22876 move |_event, _window, cx| {
22877 editor.update(cx, |editor, cx| {
22878 editor.stage_or_unstage_diff_hunks(
22879 true,
22880 vec![hunk_range.start..hunk_range.start],
22881 cx,
22882 );
22883 });
22884 }
22885 })
22886 } else {
22887 Button::new(("unstage", row as u64), "Unstage")
22888 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22889 .tooltip({
22890 let focus_handle = editor.focus_handle(cx);
22891 move |window, cx| {
22892 Tooltip::for_action_in(
22893 "Unstage Hunk",
22894 &::git::ToggleStaged,
22895 &focus_handle,
22896 window,
22897 cx,
22898 )
22899 }
22900 })
22901 .on_click({
22902 let editor = editor.clone();
22903 move |_event, _window, cx| {
22904 editor.update(cx, |editor, cx| {
22905 editor.stage_or_unstage_diff_hunks(
22906 false,
22907 vec![hunk_range.start..hunk_range.start],
22908 cx,
22909 );
22910 });
22911 }
22912 })
22913 })
22914 .child(
22915 Button::new(("restore", row as u64), "Restore")
22916 .tooltip({
22917 let focus_handle = editor.focus_handle(cx);
22918 move |window, cx| {
22919 Tooltip::for_action_in(
22920 "Restore Hunk",
22921 &::git::Restore,
22922 &focus_handle,
22923 window,
22924 cx,
22925 )
22926 }
22927 })
22928 .on_click({
22929 let editor = editor.clone();
22930 move |_event, window, cx| {
22931 editor.update(cx, |editor, cx| {
22932 let snapshot = editor.snapshot(window, cx);
22933 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
22934 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
22935 });
22936 }
22937 })
22938 .disabled(is_created_file),
22939 )
22940 .when(
22941 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
22942 |el| {
22943 el.child(
22944 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
22945 .shape(IconButtonShape::Square)
22946 .icon_size(IconSize::Small)
22947 // .disabled(!has_multiple_hunks)
22948 .tooltip({
22949 let focus_handle = editor.focus_handle(cx);
22950 move |window, cx| {
22951 Tooltip::for_action_in(
22952 "Next Hunk",
22953 &GoToHunk,
22954 &focus_handle,
22955 window,
22956 cx,
22957 )
22958 }
22959 })
22960 .on_click({
22961 let editor = editor.clone();
22962 move |_event, window, cx| {
22963 editor.update(cx, |editor, cx| {
22964 let snapshot = editor.snapshot(window, cx);
22965 let position =
22966 hunk_range.end.to_point(&snapshot.buffer_snapshot);
22967 editor.go_to_hunk_before_or_after_position(
22968 &snapshot,
22969 position,
22970 Direction::Next,
22971 window,
22972 cx,
22973 );
22974 editor.expand_selected_diff_hunks(cx);
22975 });
22976 }
22977 }),
22978 )
22979 .child(
22980 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
22981 .shape(IconButtonShape::Square)
22982 .icon_size(IconSize::Small)
22983 // .disabled(!has_multiple_hunks)
22984 .tooltip({
22985 let focus_handle = editor.focus_handle(cx);
22986 move |window, cx| {
22987 Tooltip::for_action_in(
22988 "Previous Hunk",
22989 &GoToPreviousHunk,
22990 &focus_handle,
22991 window,
22992 cx,
22993 )
22994 }
22995 })
22996 .on_click({
22997 let editor = editor.clone();
22998 move |_event, window, cx| {
22999 editor.update(cx, |editor, cx| {
23000 let snapshot = editor.snapshot(window, cx);
23001 let point =
23002 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23003 editor.go_to_hunk_before_or_after_position(
23004 &snapshot,
23005 point,
23006 Direction::Prev,
23007 window,
23008 cx,
23009 );
23010 editor.expand_selected_diff_hunks(cx);
23011 });
23012 }
23013 }),
23014 )
23015 },
23016 )
23017 .into_any_element()
23018}