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;
18mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod editor_tests;
46#[cfg(test)]
47mod inline_completion_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
54use aho_corasick::AhoCorasick;
55use anyhow::{Context as _, Result, anyhow};
56use blink_manager::BlinkManager;
57use buffer_diff::DiffHunkStatus;
58use client::{Collaborator, ParticipantIndex};
59use clock::{AGENT_REPLICA_ID, ReplicaId};
60use collections::{BTreeMap, HashMap, HashSet, VecDeque};
61use convert_case::{Case, Casing};
62use display_map::*;
63pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
64pub use editor_settings::{
65 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, SearchSettings,
66 ShowScrollbar,
67};
68use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
69pub use editor_settings_controls::*;
70use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
71pub use element::{
72 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
73};
74use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
75use futures::{
76 FutureExt,
77 future::{self, Shared, join},
78};
79use fuzzy::StringMatchCandidate;
80
81use ::git::blame::BlameEntry;
82use ::git::{Restore, blame::ParsedCommitMessage};
83use code_context_menus::{
84 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
85 CompletionsMenu, ContextMenuOrigin,
86};
87use git::blame::{GitBlame, GlobalBlameRenderer};
88use gpui::{
89 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
90 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
91 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
92 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
93 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
94 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
95 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
96 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
97};
98use highlight_matching_bracket::refresh_matching_bracket_highlights;
99use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
100pub use hover_popover::hover_markdown_style;
101use hover_popover::{HoverState, hide_hover};
102use indent_guides::ActiveIndentGuidesState;
103use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
104pub use inline_completion::Direction;
105use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
106pub use items::MAX_TAB_TITLE_LEN;
107use itertools::Itertools;
108use language::{
109 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
110 CursorShape, DiagnosticEntry, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText,
111 IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal, TextObject,
112 TransactionId, TreeSitterOptions, WordsQuery,
113 language_settings::{
114 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
115 all_language_settings, language_settings,
116 },
117 point_from_lsp, text_diff_with_options,
118};
119use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
120use linked_editing_ranges::refresh_linked_ranges;
121use markdown::Markdown;
122use mouse_context_menu::MouseContextMenu;
123use persistence::DB;
124use project::{
125 ProjectPath,
126 debugger::{
127 breakpoint_store::{
128 BreakpointEditAction, BreakpointState, BreakpointStore, BreakpointStoreEvent,
129 },
130 session::{Session, SessionEvent},
131 },
132 project_settings::DiagnosticSeverity,
133};
134
135pub use git::blame::BlameRenderer;
136pub use proposed_changes_editor::{
137 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
138};
139use smallvec::smallvec;
140use std::{cell::OnceCell, iter::Peekable, ops::Not};
141use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
142
143pub use lsp::CompletionContext;
144use lsp::{
145 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
146 LanguageServerId, LanguageServerName,
147};
148
149use language::BufferSnapshot;
150pub use lsp_ext::lsp_tasks;
151use movement::TextLayoutDetails;
152pub use multi_buffer::{
153 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
154 RowInfo, ToOffset, ToPoint,
155};
156use multi_buffer::{
157 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
158 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
159};
160use parking_lot::Mutex;
161use project::{
162 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
163 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
164 TaskSourceKind,
165 debugger::breakpoint_store::Breakpoint,
166 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
167 project_settings::{GitGutterSetting, ProjectSettings},
168};
169use rand::prelude::*;
170use rpc::{ErrorExt, proto::*};
171use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
172use selections_collection::{
173 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
174};
175use serde::{Deserialize, Serialize};
176use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
177use smallvec::SmallVec;
178use snippet::Snippet;
179use std::sync::Arc;
180use std::{
181 any::TypeId,
182 borrow::Cow,
183 cell::RefCell,
184 cmp::{self, Ordering, Reverse},
185 mem,
186 num::NonZeroU32,
187 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
188 path::{Path, PathBuf},
189 rc::Rc,
190 time::{Duration, Instant},
191};
192pub use sum_tree::Bias;
193use sum_tree::TreeMap;
194use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
195use theme::{
196 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
197 observe_buffer_font_size_adjustment,
198};
199use ui::{
200 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
201 IconSize, Key, Tooltip, h_flex, prelude::*,
202};
203use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
204use workspace::{
205 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
206 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
207 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
208 item::{ItemHandle, PreviewTabsSettings},
209 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
210 searchable::SearchEvent,
211};
212
213use crate::hover_links::{find_url, find_url_from_range};
214use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
215
216pub const FILE_HEADER_HEIGHT: u32 = 2;
217pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
218pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
219const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
220const MAX_LINE_LEN: usize = 1024;
221const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
222const MAX_SELECTION_HISTORY_LEN: usize = 1024;
223pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
224#[doc(hidden)]
225pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
226const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
227
228pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
229pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
230pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
231
232pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
233pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
234pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
235pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
236
237pub type RenderDiffHunkControlsFn = Arc<
238 dyn Fn(
239 u32,
240 &DiffHunkStatus,
241 Range<Anchor>,
242 bool,
243 Pixels,
244 &Entity<Editor>,
245 &mut Window,
246 &mut App,
247 ) -> AnyElement,
248>;
249
250const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
251 alt: true,
252 shift: true,
253 control: false,
254 platform: false,
255 function: false,
256};
257
258struct InlineValueCache {
259 enabled: bool,
260 inlays: Vec<InlayId>,
261 refresh_task: Task<Option<()>>,
262}
263
264impl InlineValueCache {
265 fn new(enabled: bool) -> Self {
266 Self {
267 enabled,
268 inlays: Vec::new(),
269 refresh_task: Task::ready(None),
270 }
271 }
272}
273
274#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
275pub enum InlayId {
276 InlineCompletion(usize),
277 Hint(usize),
278 DebuggerValue(usize),
279}
280
281impl InlayId {
282 fn id(&self) -> usize {
283 match self {
284 Self::InlineCompletion(id) => *id,
285 Self::Hint(id) => *id,
286 Self::DebuggerValue(id) => *id,
287 }
288 }
289}
290
291pub enum ActiveDebugLine {}
292enum DocumentHighlightRead {}
293enum DocumentHighlightWrite {}
294enum InputComposition {}
295enum SelectedTextHighlight {}
296
297pub enum ConflictsOuter {}
298pub enum ConflictsOurs {}
299pub enum ConflictsTheirs {}
300pub enum ConflictsOursMarker {}
301pub enum ConflictsTheirsMarker {}
302
303#[derive(Debug, Copy, Clone, PartialEq, Eq)]
304pub enum Navigated {
305 Yes,
306 No,
307}
308
309impl Navigated {
310 pub fn from_bool(yes: bool) -> Navigated {
311 if yes { Navigated::Yes } else { Navigated::No }
312 }
313}
314
315#[derive(Debug, Clone, PartialEq, Eq)]
316enum DisplayDiffHunk {
317 Folded {
318 display_row: DisplayRow,
319 },
320 Unfolded {
321 is_created_file: bool,
322 diff_base_byte_range: Range<usize>,
323 display_row_range: Range<DisplayRow>,
324 multi_buffer_range: Range<Anchor>,
325 status: DiffHunkStatus,
326 },
327}
328
329pub enum HideMouseCursorOrigin {
330 TypingAction,
331 MovementAction,
332}
333
334pub fn init_settings(cx: &mut App) {
335 EditorSettings::register(cx);
336}
337
338pub fn init(cx: &mut App) {
339 init_settings(cx);
340
341 cx.set_global(GlobalBlameRenderer(Arc::new(())));
342
343 workspace::register_project_item::<Editor>(cx);
344 workspace::FollowableViewRegistry::register::<Editor>(cx);
345 workspace::register_serializable_item::<Editor>(cx);
346
347 cx.observe_new(
348 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
349 workspace.register_action(Editor::new_file);
350 workspace.register_action(Editor::new_file_vertical);
351 workspace.register_action(Editor::new_file_horizontal);
352 workspace.register_action(Editor::cancel_language_server_work);
353 },
354 )
355 .detach();
356
357 cx.on_action(move |_: &workspace::NewFile, cx| {
358 let app_state = workspace::AppState::global(cx);
359 if let Some(app_state) = app_state.upgrade() {
360 workspace::open_new(
361 Default::default(),
362 app_state,
363 cx,
364 |workspace, window, cx| {
365 Editor::new_file(workspace, &Default::default(), window, cx)
366 },
367 )
368 .detach();
369 }
370 });
371 cx.on_action(move |_: &workspace::NewWindow, cx| {
372 let app_state = workspace::AppState::global(cx);
373 if let Some(app_state) = app_state.upgrade() {
374 workspace::open_new(
375 Default::default(),
376 app_state,
377 cx,
378 |workspace, window, cx| {
379 cx.activate(true);
380 Editor::new_file(workspace, &Default::default(), window, cx)
381 },
382 )
383 .detach();
384 }
385 });
386}
387
388pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
389 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
390}
391
392pub trait DiagnosticRenderer {
393 fn render_group(
394 &self,
395 diagnostic_group: Vec<DiagnosticEntry<Point>>,
396 buffer_id: BufferId,
397 snapshot: EditorSnapshot,
398 editor: WeakEntity<Editor>,
399 cx: &mut App,
400 ) -> Vec<BlockProperties<Anchor>>;
401
402 fn render_hover(
403 &self,
404 diagnostic_group: Vec<DiagnosticEntry<Point>>,
405 range: Range<Point>,
406 buffer_id: BufferId,
407 cx: &mut App,
408 ) -> Option<Entity<markdown::Markdown>>;
409
410 fn open_link(
411 &self,
412 editor: &mut Editor,
413 link: SharedString,
414 window: &mut Window,
415 cx: &mut Context<Editor>,
416 );
417}
418
419pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
420
421impl GlobalDiagnosticRenderer {
422 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
423 cx.try_global::<Self>().map(|g| g.0.clone())
424 }
425}
426
427impl gpui::Global for GlobalDiagnosticRenderer {}
428pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
429 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
430}
431
432pub struct SearchWithinRange;
433
434trait InvalidationRegion {
435 fn ranges(&self) -> &[Range<Anchor>];
436}
437
438#[derive(Clone, Debug, PartialEq)]
439pub enum SelectPhase {
440 Begin {
441 position: DisplayPoint,
442 add: bool,
443 click_count: usize,
444 },
445 BeginColumnar {
446 position: DisplayPoint,
447 reset: bool,
448 goal_column: u32,
449 },
450 Extend {
451 position: DisplayPoint,
452 click_count: usize,
453 },
454 Update {
455 position: DisplayPoint,
456 goal_column: u32,
457 scroll_delta: gpui::Point<f32>,
458 },
459 End,
460}
461
462#[derive(Clone, Debug)]
463pub enum SelectMode {
464 Character,
465 Word(Range<Anchor>),
466 Line(Range<Anchor>),
467 All,
468}
469
470#[derive(Clone, PartialEq, Eq, Debug)]
471pub enum EditorMode {
472 SingleLine {
473 auto_width: bool,
474 },
475 AutoHeight {
476 max_lines: usize,
477 },
478 Full {
479 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
480 scale_ui_elements_with_buffer_font_size: bool,
481 /// When set to `true`, the editor will render a background for the active line.
482 show_active_line_background: bool,
483 /// When set to `true`, the editor's height will be determined by its content.
484 sized_by_content: bool,
485 },
486 Minimap {
487 parent: WeakEntity<Editor>,
488 },
489}
490
491impl EditorMode {
492 pub fn full() -> Self {
493 Self::Full {
494 scale_ui_elements_with_buffer_font_size: true,
495 show_active_line_background: true,
496 sized_by_content: false,
497 }
498 }
499
500 pub fn is_full(&self) -> bool {
501 matches!(self, Self::Full { .. })
502 }
503
504 fn is_minimap(&self) -> bool {
505 matches!(self, Self::Minimap { .. })
506 }
507}
508
509#[derive(Copy, Clone, Debug)]
510pub enum SoftWrap {
511 /// Prefer not to wrap at all.
512 ///
513 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
514 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
515 GitDiff,
516 /// Prefer a single line generally, unless an overly long line is encountered.
517 None,
518 /// Soft wrap lines that exceed the editor width.
519 EditorWidth,
520 /// Soft wrap lines at the preferred line length.
521 Column(u32),
522 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
523 Bounded(u32),
524}
525
526#[derive(Clone)]
527pub struct EditorStyle {
528 pub background: Hsla,
529 pub local_player: PlayerColor,
530 pub text: TextStyle,
531 pub scrollbar_width: Pixels,
532 pub syntax: Arc<SyntaxTheme>,
533 pub status: StatusColors,
534 pub inlay_hints_style: HighlightStyle,
535 pub inline_completion_styles: InlineCompletionStyles,
536 pub unnecessary_code_fade: f32,
537 pub show_underlines: bool,
538}
539
540impl Default for EditorStyle {
541 fn default() -> Self {
542 Self {
543 background: Hsla::default(),
544 local_player: PlayerColor::default(),
545 text: TextStyle::default(),
546 scrollbar_width: Pixels::default(),
547 syntax: Default::default(),
548 // HACK: Status colors don't have a real default.
549 // We should look into removing the status colors from the editor
550 // style and retrieve them directly from the theme.
551 status: StatusColors::dark(),
552 inlay_hints_style: HighlightStyle::default(),
553 inline_completion_styles: InlineCompletionStyles {
554 insertion: HighlightStyle::default(),
555 whitespace: HighlightStyle::default(),
556 },
557 unnecessary_code_fade: Default::default(),
558 show_underlines: true,
559 }
560 }
561}
562
563pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
564 let show_background = language_settings::language_settings(None, None, cx)
565 .inlay_hints
566 .show_background;
567
568 HighlightStyle {
569 color: Some(cx.theme().status().hint),
570 background_color: show_background.then(|| cx.theme().status().hint_background),
571 ..HighlightStyle::default()
572 }
573}
574
575pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
576 InlineCompletionStyles {
577 insertion: HighlightStyle {
578 color: Some(cx.theme().status().predictive),
579 ..HighlightStyle::default()
580 },
581 whitespace: HighlightStyle {
582 background_color: Some(cx.theme().status().created_background),
583 ..HighlightStyle::default()
584 },
585 }
586}
587
588type CompletionId = usize;
589
590pub(crate) enum EditDisplayMode {
591 TabAccept,
592 DiffPopover,
593 Inline,
594}
595
596enum InlineCompletion {
597 Edit {
598 edits: Vec<(Range<Anchor>, String)>,
599 edit_preview: Option<EditPreview>,
600 display_mode: EditDisplayMode,
601 snapshot: BufferSnapshot,
602 },
603 Move {
604 target: Anchor,
605 snapshot: BufferSnapshot,
606 },
607}
608
609struct InlineCompletionState {
610 inlay_ids: Vec<InlayId>,
611 completion: InlineCompletion,
612 completion_id: Option<SharedString>,
613 invalidation_range: Range<Anchor>,
614}
615
616enum EditPredictionSettings {
617 Disabled,
618 Enabled {
619 show_in_menu: bool,
620 preview_requires_modifier: bool,
621 },
622}
623
624enum InlineCompletionHighlight {}
625
626#[derive(Debug, Clone)]
627struct InlineDiagnostic {
628 message: SharedString,
629 group_id: usize,
630 is_primary: bool,
631 start: Point,
632 severity: lsp::DiagnosticSeverity,
633}
634
635pub enum MenuInlineCompletionsPolicy {
636 Never,
637 ByProvider,
638}
639
640pub enum EditPredictionPreview {
641 /// Modifier is not pressed
642 Inactive { released_too_fast: bool },
643 /// Modifier pressed
644 Active {
645 since: Instant,
646 previous_scroll_position: Option<ScrollAnchor>,
647 },
648}
649
650impl EditPredictionPreview {
651 pub fn released_too_fast(&self) -> bool {
652 match self {
653 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
654 EditPredictionPreview::Active { .. } => false,
655 }
656 }
657
658 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
659 if let EditPredictionPreview::Active {
660 previous_scroll_position,
661 ..
662 } = self
663 {
664 *previous_scroll_position = scroll_position;
665 }
666 }
667}
668
669pub struct ContextMenuOptions {
670 pub min_entries_visible: usize,
671 pub max_entries_visible: usize,
672 pub placement: Option<ContextMenuPlacement>,
673}
674
675#[derive(Debug, Clone, PartialEq, Eq)]
676pub enum ContextMenuPlacement {
677 Above,
678 Below,
679}
680
681#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
682struct EditorActionId(usize);
683
684impl EditorActionId {
685 pub fn post_inc(&mut self) -> Self {
686 let answer = self.0;
687
688 *self = Self(answer + 1);
689
690 Self(answer)
691 }
692}
693
694// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
695// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
696
697type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
698type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
699
700#[derive(Default)]
701struct ScrollbarMarkerState {
702 scrollbar_size: Size<Pixels>,
703 dirty: bool,
704 markers: Arc<[PaintQuad]>,
705 pending_refresh: Option<Task<Result<()>>>,
706}
707
708impl ScrollbarMarkerState {
709 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
710 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
711 }
712}
713
714#[derive(Clone, Copy, PartialEq, Eq)]
715pub enum MinimapVisibility {
716 Disabled,
717 Enabled(bool),
718}
719
720impl MinimapVisibility {
721 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
722 if mode.is_full() {
723 Self::Enabled(EditorSettings::get_global(cx).minimap.minimap_enabled())
724 } else {
725 Self::Disabled
726 }
727 }
728
729 fn disabled(&self) -> bool {
730 match *self {
731 Self::Disabled => true,
732 _ => false,
733 }
734 }
735
736 fn visible(&self) -> bool {
737 match *self {
738 Self::Enabled(visible) => visible,
739 _ => false,
740 }
741 }
742
743 fn toggle_visibility(&self) -> Self {
744 match *self {
745 Self::Enabled(visible) => Self::Enabled(!visible),
746 Self::Disabled => Self::Disabled,
747 }
748 }
749}
750
751#[derive(Clone, Debug)]
752struct RunnableTasks {
753 templates: Vec<(TaskSourceKind, TaskTemplate)>,
754 offset: multi_buffer::Anchor,
755 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
756 column: u32,
757 // Values of all named captures, including those starting with '_'
758 extra_variables: HashMap<String, String>,
759 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
760 context_range: Range<BufferOffset>,
761}
762
763impl RunnableTasks {
764 fn resolve<'a>(
765 &'a self,
766 cx: &'a task::TaskContext,
767 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
768 self.templates.iter().filter_map(|(kind, template)| {
769 template
770 .resolve_task(&kind.to_id_base(), cx)
771 .map(|task| (kind.clone(), task))
772 })
773 }
774}
775
776#[derive(Clone)]
777struct ResolvedTasks {
778 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
779 position: Anchor,
780}
781
782#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
783struct BufferOffset(usize);
784
785// Addons allow storing per-editor state in other crates (e.g. Vim)
786pub trait Addon: 'static {
787 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
788
789 fn render_buffer_header_controls(
790 &self,
791 _: &ExcerptInfo,
792 _: &Window,
793 _: &App,
794 ) -> Option<AnyElement> {
795 None
796 }
797
798 fn to_any(&self) -> &dyn std::any::Any;
799
800 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
801 None
802 }
803}
804
805/// A set of caret positions, registered when the editor was edited.
806pub struct ChangeList {
807 changes: Vec<Vec<Anchor>>,
808 /// Currently "selected" change.
809 position: Option<usize>,
810}
811
812impl ChangeList {
813 pub fn new() -> Self {
814 Self {
815 changes: Vec::new(),
816 position: None,
817 }
818 }
819
820 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
821 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
822 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
823 if self.changes.is_empty() {
824 return None;
825 }
826
827 let prev = self.position.unwrap_or(self.changes.len());
828 let next = if direction == Direction::Prev {
829 prev.saturating_sub(count)
830 } else {
831 (prev + count).min(self.changes.len() - 1)
832 };
833 self.position = Some(next);
834 self.changes.get(next).map(|anchors| anchors.as_slice())
835 }
836
837 /// Adds a new change to the list, resetting the change list position.
838 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
839 self.position.take();
840 if pop_state {
841 self.changes.pop();
842 }
843 self.changes.push(new_positions.clone());
844 }
845
846 pub fn last(&self) -> Option<&[Anchor]> {
847 self.changes.last().map(|anchors| anchors.as_slice())
848 }
849}
850
851#[derive(Clone)]
852struct InlineBlamePopoverState {
853 scroll_handle: ScrollHandle,
854 commit_message: Option<ParsedCommitMessage>,
855 markdown: Entity<Markdown>,
856}
857
858struct InlineBlamePopover {
859 position: gpui::Point<Pixels>,
860 show_task: Option<Task<()>>,
861 hide_task: Option<Task<()>>,
862 popover_bounds: Option<Bounds<Pixels>>,
863 popover_state: InlineBlamePopoverState,
864}
865
866/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
867/// a breakpoint on them.
868#[derive(Clone, Copy, Debug)]
869struct PhantomBreakpointIndicator {
870 display_row: DisplayRow,
871 /// There's a small debounce between hovering over the line and showing the indicator.
872 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
873 is_active: bool,
874 collides_with_existing_breakpoint: bool,
875}
876/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
877///
878/// See the [module level documentation](self) for more information.
879pub struct Editor {
880 focus_handle: FocusHandle,
881 last_focused_descendant: Option<WeakFocusHandle>,
882 /// The text buffer being edited
883 buffer: Entity<MultiBuffer>,
884 /// Map of how text in the buffer should be displayed.
885 /// Handles soft wraps, folds, fake inlay text insertions, etc.
886 pub display_map: Entity<DisplayMap>,
887 pub selections: SelectionsCollection,
888 pub scroll_manager: ScrollManager,
889 /// When inline assist editors are linked, they all render cursors because
890 /// typing enters text into each of them, even the ones that aren't focused.
891 pub(crate) show_cursor_when_unfocused: bool,
892 columnar_selection_tail: Option<Anchor>,
893 add_selections_state: Option<AddSelectionsState>,
894 select_next_state: Option<SelectNextState>,
895 select_prev_state: Option<SelectNextState>,
896 selection_history: SelectionHistory,
897 autoclose_regions: Vec<AutocloseRegion>,
898 snippet_stack: InvalidationStack<SnippetState>,
899 select_syntax_node_history: SelectSyntaxNodeHistory,
900 ime_transaction: Option<TransactionId>,
901 pub diagnostics_max_severity: DiagnosticSeverity,
902 active_diagnostics: ActiveDiagnostic,
903 show_inline_diagnostics: bool,
904 inline_diagnostics_update: Task<()>,
905 inline_diagnostics_enabled: bool,
906 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
907 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
908 hard_wrap: Option<usize>,
909
910 // TODO: make this a access method
911 pub project: Option<Entity<Project>>,
912 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
913 completion_provider: Option<Box<dyn CompletionProvider>>,
914 collaboration_hub: Option<Box<dyn CollaborationHub>>,
915 blink_manager: Entity<BlinkManager>,
916 show_cursor_names: bool,
917 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
918 pub show_local_selections: bool,
919 mode: EditorMode,
920 show_breadcrumbs: bool,
921 show_gutter: bool,
922 show_scrollbars: bool,
923 minimap_visibility: MinimapVisibility,
924 disable_expand_excerpt_buttons: bool,
925 show_line_numbers: Option<bool>,
926 use_relative_line_numbers: Option<bool>,
927 show_git_diff_gutter: Option<bool>,
928 show_code_actions: Option<bool>,
929 show_runnables: Option<bool>,
930 show_breakpoints: Option<bool>,
931 show_wrap_guides: Option<bool>,
932 show_indent_guides: Option<bool>,
933 placeholder_text: Option<Arc<str>>,
934 highlight_order: usize,
935 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
936 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
937 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
938 scrollbar_marker_state: ScrollbarMarkerState,
939 active_indent_guides_state: ActiveIndentGuidesState,
940 nav_history: Option<ItemNavHistory>,
941 context_menu: RefCell<Option<CodeContextMenu>>,
942 context_menu_options: Option<ContextMenuOptions>,
943 mouse_context_menu: Option<MouseContextMenu>,
944 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
945 inline_blame_popover: Option<InlineBlamePopover>,
946 signature_help_state: SignatureHelpState,
947 auto_signature_help: Option<bool>,
948 find_all_references_task_sources: Vec<Anchor>,
949 next_completion_id: CompletionId,
950 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
951 code_actions_task: Option<Task<Result<()>>>,
952 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
953 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
954 document_highlights_task: Option<Task<()>>,
955 linked_editing_range_task: Option<Task<Option<()>>>,
956 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
957 pending_rename: Option<RenameState>,
958 searchable: bool,
959 cursor_shape: CursorShape,
960 current_line_highlight: Option<CurrentLineHighlight>,
961 collapse_matches: bool,
962 autoindent_mode: Option<AutoindentMode>,
963 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
964 input_enabled: bool,
965 use_modal_editing: bool,
966 read_only: bool,
967 leader_id: Option<CollaboratorId>,
968 remote_id: Option<ViewId>,
969 pub hover_state: HoverState,
970 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
971 gutter_hovered: bool,
972 hovered_link_state: Option<HoveredLinkState>,
973 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
974 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
975 active_inline_completion: Option<InlineCompletionState>,
976 /// Used to prevent flickering as the user types while the menu is open
977 stale_inline_completion_in_menu: Option<InlineCompletionState>,
978 edit_prediction_settings: EditPredictionSettings,
979 inline_completions_hidden_for_vim_mode: bool,
980 show_inline_completions_override: Option<bool>,
981 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
982 edit_prediction_preview: EditPredictionPreview,
983 edit_prediction_indent_conflict: bool,
984 edit_prediction_requires_modifier_in_indent_conflict: bool,
985 inlay_hint_cache: InlayHintCache,
986 next_inlay_id: usize,
987 _subscriptions: Vec<Subscription>,
988 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
989 gutter_dimensions: GutterDimensions,
990 style: Option<EditorStyle>,
991 text_style_refinement: Option<TextStyleRefinement>,
992 next_editor_action_id: EditorActionId,
993 editor_actions:
994 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
995 use_autoclose: bool,
996 use_auto_surround: bool,
997 auto_replace_emoji_shortcode: bool,
998 jsx_tag_auto_close_enabled_in_any_buffer: bool,
999 show_git_blame_gutter: bool,
1000 show_git_blame_inline: bool,
1001 show_git_blame_inline_delay_task: Option<Task<()>>,
1002 git_blame_inline_enabled: bool,
1003 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1004 serialize_dirty_buffers: bool,
1005 show_selection_menu: Option<bool>,
1006 blame: Option<Entity<GitBlame>>,
1007 blame_subscription: Option<Subscription>,
1008 custom_context_menu: Option<
1009 Box<
1010 dyn 'static
1011 + Fn(
1012 &mut Self,
1013 DisplayPoint,
1014 &mut Window,
1015 &mut Context<Self>,
1016 ) -> Option<Entity<ui::ContextMenu>>,
1017 >,
1018 >,
1019 last_bounds: Option<Bounds<Pixels>>,
1020 last_position_map: Option<Rc<PositionMap>>,
1021 expect_bounds_change: Option<Bounds<Pixels>>,
1022 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1023 tasks_update_task: Option<Task<()>>,
1024 breakpoint_store: Option<Entity<BreakpointStore>>,
1025 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1026 in_project_search: bool,
1027 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1028 breadcrumb_header: Option<String>,
1029 focused_block: Option<FocusedBlock>,
1030 next_scroll_position: NextScrollCursorCenterTopBottom,
1031 addons: HashMap<TypeId, Box<dyn Addon>>,
1032 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1033 load_diff_task: Option<Shared<Task<()>>>,
1034 /// Whether we are temporarily displaying a diff other than git's
1035 temporary_diff_override: bool,
1036 selection_mark_mode: bool,
1037 toggle_fold_multiple_buffers: Task<()>,
1038 _scroll_cursor_center_top_bottom_task: Task<()>,
1039 serialize_selections: Task<()>,
1040 serialize_folds: Task<()>,
1041 mouse_cursor_hidden: bool,
1042 minimap: Option<Entity<Self>>,
1043 hide_mouse_mode: HideMouseMode,
1044 pub change_list: ChangeList,
1045 inline_value_cache: InlineValueCache,
1046}
1047
1048#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1049enum NextScrollCursorCenterTopBottom {
1050 #[default]
1051 Center,
1052 Top,
1053 Bottom,
1054}
1055
1056impl NextScrollCursorCenterTopBottom {
1057 fn next(&self) -> Self {
1058 match self {
1059 Self::Center => Self::Top,
1060 Self::Top => Self::Bottom,
1061 Self::Bottom => Self::Center,
1062 }
1063 }
1064}
1065
1066#[derive(Clone)]
1067pub struct EditorSnapshot {
1068 pub mode: EditorMode,
1069 show_gutter: bool,
1070 show_line_numbers: Option<bool>,
1071 show_git_diff_gutter: Option<bool>,
1072 show_runnables: Option<bool>,
1073 show_breakpoints: Option<bool>,
1074 git_blame_gutter_max_author_length: Option<usize>,
1075 pub display_snapshot: DisplaySnapshot,
1076 pub placeholder_text: Option<Arc<str>>,
1077 is_focused: bool,
1078 scroll_anchor: ScrollAnchor,
1079 ongoing_scroll: OngoingScroll,
1080 current_line_highlight: CurrentLineHighlight,
1081 gutter_hovered: bool,
1082}
1083
1084#[derive(Default, Debug, Clone, Copy)]
1085pub struct GutterDimensions {
1086 pub left_padding: Pixels,
1087 pub right_padding: Pixels,
1088 pub width: Pixels,
1089 pub margin: Pixels,
1090 pub git_blame_entries_width: Option<Pixels>,
1091}
1092
1093impl GutterDimensions {
1094 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1095 Self {
1096 margin: Self::default_gutter_margin(font_id, font_size, cx),
1097 ..Default::default()
1098 }
1099 }
1100
1101 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1102 -cx.text_system().descent(font_id, font_size)
1103 }
1104 /// The full width of the space taken up by the gutter.
1105 pub fn full_width(&self) -> Pixels {
1106 self.margin + self.width
1107 }
1108
1109 /// The width of the space reserved for the fold indicators,
1110 /// use alongside 'justify_end' and `gutter_width` to
1111 /// right align content with the line numbers
1112 pub fn fold_area_width(&self) -> Pixels {
1113 self.margin + self.right_padding
1114 }
1115}
1116
1117#[derive(Debug)]
1118pub struct RemoteSelection {
1119 pub replica_id: ReplicaId,
1120 pub selection: Selection<Anchor>,
1121 pub cursor_shape: CursorShape,
1122 pub collaborator_id: CollaboratorId,
1123 pub line_mode: bool,
1124 pub user_name: Option<SharedString>,
1125 pub color: PlayerColor,
1126}
1127
1128#[derive(Clone, Debug)]
1129struct SelectionHistoryEntry {
1130 selections: Arc<[Selection<Anchor>]>,
1131 select_next_state: Option<SelectNextState>,
1132 select_prev_state: Option<SelectNextState>,
1133 add_selections_state: Option<AddSelectionsState>,
1134}
1135
1136enum SelectionHistoryMode {
1137 Normal,
1138 Undoing,
1139 Redoing,
1140}
1141
1142#[derive(Clone, PartialEq, Eq, Hash)]
1143struct HoveredCursor {
1144 replica_id: u16,
1145 selection_id: usize,
1146}
1147
1148impl Default for SelectionHistoryMode {
1149 fn default() -> Self {
1150 Self::Normal
1151 }
1152}
1153
1154#[derive(Default)]
1155struct SelectionHistory {
1156 #[allow(clippy::type_complexity)]
1157 selections_by_transaction:
1158 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1159 mode: SelectionHistoryMode,
1160 undo_stack: VecDeque<SelectionHistoryEntry>,
1161 redo_stack: VecDeque<SelectionHistoryEntry>,
1162}
1163
1164impl SelectionHistory {
1165 fn insert_transaction(
1166 &mut self,
1167 transaction_id: TransactionId,
1168 selections: Arc<[Selection<Anchor>]>,
1169 ) {
1170 self.selections_by_transaction
1171 .insert(transaction_id, (selections, None));
1172 }
1173
1174 #[allow(clippy::type_complexity)]
1175 fn transaction(
1176 &self,
1177 transaction_id: TransactionId,
1178 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1179 self.selections_by_transaction.get(&transaction_id)
1180 }
1181
1182 #[allow(clippy::type_complexity)]
1183 fn transaction_mut(
1184 &mut self,
1185 transaction_id: TransactionId,
1186 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1187 self.selections_by_transaction.get_mut(&transaction_id)
1188 }
1189
1190 fn push(&mut self, entry: SelectionHistoryEntry) {
1191 if !entry.selections.is_empty() {
1192 match self.mode {
1193 SelectionHistoryMode::Normal => {
1194 self.push_undo(entry);
1195 self.redo_stack.clear();
1196 }
1197 SelectionHistoryMode::Undoing => self.push_redo(entry),
1198 SelectionHistoryMode::Redoing => self.push_undo(entry),
1199 }
1200 }
1201 }
1202
1203 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1204 if self
1205 .undo_stack
1206 .back()
1207 .map_or(true, |e| e.selections != entry.selections)
1208 {
1209 self.undo_stack.push_back(entry);
1210 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1211 self.undo_stack.pop_front();
1212 }
1213 }
1214 }
1215
1216 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1217 if self
1218 .redo_stack
1219 .back()
1220 .map_or(true, |e| e.selections != entry.selections)
1221 {
1222 self.redo_stack.push_back(entry);
1223 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1224 self.redo_stack.pop_front();
1225 }
1226 }
1227 }
1228}
1229
1230#[derive(Clone, Copy)]
1231pub struct RowHighlightOptions {
1232 pub autoscroll: bool,
1233 pub include_gutter: bool,
1234}
1235
1236impl Default for RowHighlightOptions {
1237 fn default() -> Self {
1238 Self {
1239 autoscroll: Default::default(),
1240 include_gutter: true,
1241 }
1242 }
1243}
1244
1245struct RowHighlight {
1246 index: usize,
1247 range: Range<Anchor>,
1248 color: Hsla,
1249 options: RowHighlightOptions,
1250 type_id: TypeId,
1251}
1252
1253#[derive(Clone, Debug)]
1254struct AddSelectionsState {
1255 above: bool,
1256 stack: Vec<usize>,
1257}
1258
1259#[derive(Clone)]
1260struct SelectNextState {
1261 query: AhoCorasick,
1262 wordwise: bool,
1263 done: bool,
1264}
1265
1266impl std::fmt::Debug for SelectNextState {
1267 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1268 f.debug_struct(std::any::type_name::<Self>())
1269 .field("wordwise", &self.wordwise)
1270 .field("done", &self.done)
1271 .finish()
1272 }
1273}
1274
1275#[derive(Debug)]
1276struct AutocloseRegion {
1277 selection_id: usize,
1278 range: Range<Anchor>,
1279 pair: BracketPair,
1280}
1281
1282#[derive(Debug)]
1283struct SnippetState {
1284 ranges: Vec<Vec<Range<Anchor>>>,
1285 active_index: usize,
1286 choices: Vec<Option<Vec<String>>>,
1287}
1288
1289#[doc(hidden)]
1290pub struct RenameState {
1291 pub range: Range<Anchor>,
1292 pub old_name: Arc<str>,
1293 pub editor: Entity<Editor>,
1294 block_id: CustomBlockId,
1295}
1296
1297struct InvalidationStack<T>(Vec<T>);
1298
1299struct RegisteredInlineCompletionProvider {
1300 provider: Arc<dyn InlineCompletionProviderHandle>,
1301 _subscription: Subscription,
1302}
1303
1304#[derive(Debug, PartialEq, Eq)]
1305pub struct ActiveDiagnosticGroup {
1306 pub active_range: Range<Anchor>,
1307 pub active_message: String,
1308 pub group_id: usize,
1309 pub blocks: HashSet<CustomBlockId>,
1310}
1311
1312#[derive(Debug, PartialEq, Eq)]
1313#[allow(clippy::large_enum_variant)]
1314pub(crate) enum ActiveDiagnostic {
1315 None,
1316 All,
1317 Group(ActiveDiagnosticGroup),
1318}
1319
1320#[derive(Serialize, Deserialize, Clone, Debug)]
1321pub struct ClipboardSelection {
1322 /// The number of bytes in this selection.
1323 pub len: usize,
1324 /// Whether this was a full-line selection.
1325 pub is_entire_line: bool,
1326 /// The indentation of the first line when this content was originally copied.
1327 pub first_line_indent: u32,
1328}
1329
1330// selections, scroll behavior, was newest selection reversed
1331type SelectSyntaxNodeHistoryState = (
1332 Box<[Selection<usize>]>,
1333 SelectSyntaxNodeScrollBehavior,
1334 bool,
1335);
1336
1337#[derive(Default)]
1338struct SelectSyntaxNodeHistory {
1339 stack: Vec<SelectSyntaxNodeHistoryState>,
1340 // disable temporarily to allow changing selections without losing the stack
1341 pub disable_clearing: bool,
1342}
1343
1344impl SelectSyntaxNodeHistory {
1345 pub fn try_clear(&mut self) {
1346 if !self.disable_clearing {
1347 self.stack.clear();
1348 }
1349 }
1350
1351 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1352 self.stack.push(selection);
1353 }
1354
1355 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1356 self.stack.pop()
1357 }
1358}
1359
1360enum SelectSyntaxNodeScrollBehavior {
1361 CursorTop,
1362 FitSelection,
1363 CursorBottom,
1364}
1365
1366#[derive(Debug)]
1367pub(crate) struct NavigationData {
1368 cursor_anchor: Anchor,
1369 cursor_position: Point,
1370 scroll_anchor: ScrollAnchor,
1371 scroll_top_row: u32,
1372}
1373
1374#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1375pub enum GotoDefinitionKind {
1376 Symbol,
1377 Declaration,
1378 Type,
1379 Implementation,
1380}
1381
1382#[derive(Debug, Clone)]
1383enum InlayHintRefreshReason {
1384 ModifiersChanged(bool),
1385 Toggle(bool),
1386 SettingsChange(InlayHintSettings),
1387 NewLinesShown,
1388 BufferEdited(HashSet<Arc<Language>>),
1389 RefreshRequested,
1390 ExcerptsRemoved(Vec<ExcerptId>),
1391}
1392
1393impl InlayHintRefreshReason {
1394 fn description(&self) -> &'static str {
1395 match self {
1396 Self::ModifiersChanged(_) => "modifiers changed",
1397 Self::Toggle(_) => "toggle",
1398 Self::SettingsChange(_) => "settings change",
1399 Self::NewLinesShown => "new lines shown",
1400 Self::BufferEdited(_) => "buffer edited",
1401 Self::RefreshRequested => "refresh requested",
1402 Self::ExcerptsRemoved(_) => "excerpts removed",
1403 }
1404 }
1405}
1406
1407pub enum FormatTarget {
1408 Buffers,
1409 Ranges(Vec<Range<MultiBufferPoint>>),
1410}
1411
1412pub(crate) struct FocusedBlock {
1413 id: BlockId,
1414 focus_handle: WeakFocusHandle,
1415}
1416
1417#[derive(Clone)]
1418enum JumpData {
1419 MultiBufferRow {
1420 row: MultiBufferRow,
1421 line_offset_from_top: u32,
1422 },
1423 MultiBufferPoint {
1424 excerpt_id: ExcerptId,
1425 position: Point,
1426 anchor: text::Anchor,
1427 line_offset_from_top: u32,
1428 },
1429}
1430
1431pub enum MultibufferSelectionMode {
1432 First,
1433 All,
1434}
1435
1436#[derive(Clone, Copy, Debug, Default)]
1437pub struct RewrapOptions {
1438 pub override_language_settings: bool,
1439 pub preserve_existing_whitespace: bool,
1440}
1441
1442impl Editor {
1443 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1444 let buffer = cx.new(|cx| Buffer::local("", cx));
1445 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1446 Self::new(
1447 EditorMode::SingleLine { auto_width: false },
1448 buffer,
1449 None,
1450 window,
1451 cx,
1452 )
1453 }
1454
1455 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1456 let buffer = cx.new(|cx| Buffer::local("", cx));
1457 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1458 Self::new(EditorMode::full(), buffer, None, window, cx)
1459 }
1460
1461 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1462 let buffer = cx.new(|cx| Buffer::local("", cx));
1463 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1464 Self::new(
1465 EditorMode::SingleLine { auto_width: true },
1466 buffer,
1467 None,
1468 window,
1469 cx,
1470 )
1471 }
1472
1473 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1474 let buffer = cx.new(|cx| Buffer::local("", cx));
1475 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1476 Self::new(
1477 EditorMode::AutoHeight { max_lines },
1478 buffer,
1479 None,
1480 window,
1481 cx,
1482 )
1483 }
1484
1485 pub fn for_buffer(
1486 buffer: Entity<Buffer>,
1487 project: Option<Entity<Project>>,
1488 window: &mut Window,
1489 cx: &mut Context<Self>,
1490 ) -> Self {
1491 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1492 Self::new(EditorMode::full(), buffer, project, window, cx)
1493 }
1494
1495 pub fn for_multibuffer(
1496 buffer: Entity<MultiBuffer>,
1497 project: Option<Entity<Project>>,
1498 window: &mut Window,
1499 cx: &mut Context<Self>,
1500 ) -> Self {
1501 Self::new(EditorMode::full(), buffer, project, window, cx)
1502 }
1503
1504 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1505 let mut clone = Self::new(
1506 self.mode.clone(),
1507 self.buffer.clone(),
1508 self.project.clone(),
1509 window,
1510 cx,
1511 );
1512 self.display_map.update(cx, |display_map, cx| {
1513 let snapshot = display_map.snapshot(cx);
1514 clone.display_map.update(cx, |display_map, cx| {
1515 display_map.set_state(&snapshot, cx);
1516 });
1517 });
1518 clone.folds_did_change(cx);
1519 clone.selections.clone_state(&self.selections);
1520 clone.scroll_manager.clone_state(&self.scroll_manager);
1521 clone.searchable = self.searchable;
1522 clone.read_only = self.read_only;
1523 clone
1524 }
1525
1526 pub fn new(
1527 mode: EditorMode,
1528 buffer: Entity<MultiBuffer>,
1529 project: Option<Entity<Project>>,
1530 window: &mut Window,
1531 cx: &mut Context<Self>,
1532 ) -> Self {
1533 Editor::new_internal(mode, buffer, project, None, window, cx)
1534 }
1535
1536 fn new_internal(
1537 mode: EditorMode,
1538 buffer: Entity<MultiBuffer>,
1539 project: Option<Entity<Project>>,
1540 display_map: Option<Entity<DisplayMap>>,
1541 window: &mut Window,
1542 cx: &mut Context<Self>,
1543 ) -> Self {
1544 debug_assert!(
1545 display_map.is_none() || mode.is_minimap(),
1546 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1547 );
1548
1549 let full_mode = mode.is_full();
1550 let diagnostics_max_severity = if full_mode {
1551 EditorSettings::get_global(cx)
1552 .diagnostics_max_severity
1553 .unwrap_or(DiagnosticSeverity::Hint)
1554 } else {
1555 DiagnosticSeverity::Off
1556 };
1557 let style = window.text_style();
1558 let font_size = style.font_size.to_pixels(window.rem_size());
1559 let editor = cx.entity().downgrade();
1560 let fold_placeholder = FoldPlaceholder {
1561 constrain_width: true,
1562 render: Arc::new(move |fold_id, fold_range, cx| {
1563 let editor = editor.clone();
1564 div()
1565 .id(fold_id)
1566 .bg(cx.theme().colors().ghost_element_background)
1567 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1568 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1569 .rounded_xs()
1570 .size_full()
1571 .cursor_pointer()
1572 .child("⋯")
1573 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1574 .on_click(move |_, _window, cx| {
1575 editor
1576 .update(cx, |editor, cx| {
1577 editor.unfold_ranges(
1578 &[fold_range.start..fold_range.end],
1579 true,
1580 false,
1581 cx,
1582 );
1583 cx.stop_propagation();
1584 })
1585 .ok();
1586 })
1587 .into_any()
1588 }),
1589 merge_adjacent: true,
1590 ..FoldPlaceholder::default()
1591 };
1592 let display_map = display_map.unwrap_or_else(|| {
1593 cx.new(|cx| {
1594 DisplayMap::new(
1595 buffer.clone(),
1596 style.font(),
1597 font_size,
1598 None,
1599 FILE_HEADER_HEIGHT,
1600 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1601 fold_placeholder,
1602 diagnostics_max_severity,
1603 cx,
1604 )
1605 })
1606 });
1607
1608 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1609
1610 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1611
1612 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1613 .then(|| language_settings::SoftWrap::None);
1614
1615 let mut project_subscriptions = Vec::new();
1616 if mode.is_full() {
1617 if let Some(project) = project.as_ref() {
1618 project_subscriptions.push(cx.subscribe_in(
1619 project,
1620 window,
1621 |editor, _, event, window, cx| match event {
1622 project::Event::RefreshCodeLens => {
1623 // we always query lens with actions, without storing them, always refreshing them
1624 }
1625 project::Event::RefreshInlayHints => {
1626 editor
1627 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1628 }
1629 project::Event::SnippetEdit(id, snippet_edits) => {
1630 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1631 let focus_handle = editor.focus_handle(cx);
1632 if focus_handle.is_focused(window) {
1633 let snapshot = buffer.read(cx).snapshot();
1634 for (range, snippet) in snippet_edits {
1635 let editor_range =
1636 language::range_from_lsp(*range).to_offset(&snapshot);
1637 editor
1638 .insert_snippet(
1639 &[editor_range],
1640 snippet.clone(),
1641 window,
1642 cx,
1643 )
1644 .ok();
1645 }
1646 }
1647 }
1648 }
1649 _ => {}
1650 },
1651 ));
1652 if let Some(task_inventory) = project
1653 .read(cx)
1654 .task_store()
1655 .read(cx)
1656 .task_inventory()
1657 .cloned()
1658 {
1659 project_subscriptions.push(cx.observe_in(
1660 &task_inventory,
1661 window,
1662 |editor, _, window, cx| {
1663 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1664 },
1665 ));
1666 };
1667
1668 project_subscriptions.push(cx.subscribe_in(
1669 &project.read(cx).breakpoint_store(),
1670 window,
1671 |editor, _, event, window, cx| match event {
1672 BreakpointStoreEvent::ClearDebugLines => {
1673 editor.clear_row_highlights::<ActiveDebugLine>();
1674 editor.refresh_inline_values(cx);
1675 }
1676 BreakpointStoreEvent::SetDebugLine => {
1677 if editor.go_to_active_debug_line(window, cx) {
1678 cx.stop_propagation();
1679 }
1680
1681 editor.refresh_inline_values(cx);
1682 }
1683 _ => {}
1684 },
1685 ));
1686 }
1687 }
1688
1689 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1690
1691 let inlay_hint_settings =
1692 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1693 let focus_handle = cx.focus_handle();
1694 cx.on_focus(&focus_handle, window, Self::handle_focus)
1695 .detach();
1696 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1697 .detach();
1698 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1699 .detach();
1700 cx.on_blur(&focus_handle, window, Self::handle_blur)
1701 .detach();
1702
1703 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1704 Some(false)
1705 } else {
1706 None
1707 };
1708
1709 let breakpoint_store = match (&mode, project.as_ref()) {
1710 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1711 _ => None,
1712 };
1713
1714 let mut code_action_providers = Vec::new();
1715 let mut load_uncommitted_diff = None;
1716 if let Some(project) = project.clone() {
1717 load_uncommitted_diff = Some(
1718 update_uncommitted_diff_for_buffer(
1719 cx.entity(),
1720 &project,
1721 buffer.read(cx).all_buffers(),
1722 buffer.clone(),
1723 cx,
1724 )
1725 .shared(),
1726 );
1727 code_action_providers.push(Rc::new(project) as Rc<_>);
1728 }
1729
1730 let mut this = Self {
1731 focus_handle,
1732 show_cursor_when_unfocused: false,
1733 last_focused_descendant: None,
1734 buffer: buffer.clone(),
1735 display_map: display_map.clone(),
1736 selections,
1737 scroll_manager: ScrollManager::new(cx),
1738 columnar_selection_tail: None,
1739 add_selections_state: None,
1740 select_next_state: None,
1741 select_prev_state: None,
1742 selection_history: SelectionHistory::default(),
1743 autoclose_regions: Vec::new(),
1744 snippet_stack: InvalidationStack::default(),
1745 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1746 ime_transaction: None,
1747 active_diagnostics: ActiveDiagnostic::None,
1748 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1749 inline_diagnostics_update: Task::ready(()),
1750 inline_diagnostics: Vec::new(),
1751 soft_wrap_mode_override,
1752 diagnostics_max_severity,
1753 hard_wrap: None,
1754 completion_provider: project.clone().map(|project| Box::new(project) as _),
1755 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1756 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1757 project,
1758 blink_manager: blink_manager.clone(),
1759 show_local_selections: true,
1760 show_scrollbars: full_mode,
1761 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1762 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1763 show_gutter: mode.is_full(),
1764 show_line_numbers: None,
1765 use_relative_line_numbers: None,
1766 disable_expand_excerpt_buttons: false,
1767 show_git_diff_gutter: None,
1768 show_code_actions: None,
1769 show_runnables: None,
1770 show_breakpoints: None,
1771 show_wrap_guides: None,
1772 show_indent_guides,
1773 placeholder_text: None,
1774 highlight_order: 0,
1775 highlighted_rows: HashMap::default(),
1776 background_highlights: TreeMap::default(),
1777 gutter_highlights: TreeMap::default(),
1778 scrollbar_marker_state: ScrollbarMarkerState::default(),
1779 active_indent_guides_state: ActiveIndentGuidesState::default(),
1780 nav_history: None,
1781 context_menu: RefCell::new(None),
1782 context_menu_options: None,
1783 mouse_context_menu: None,
1784 completion_tasks: Vec::new(),
1785 inline_blame_popover: None,
1786 signature_help_state: SignatureHelpState::default(),
1787 auto_signature_help: None,
1788 find_all_references_task_sources: Vec::new(),
1789 next_completion_id: 0,
1790 next_inlay_id: 0,
1791 code_action_providers,
1792 available_code_actions: None,
1793 code_actions_task: None,
1794 quick_selection_highlight_task: None,
1795 debounced_selection_highlight_task: None,
1796 document_highlights_task: None,
1797 linked_editing_range_task: None,
1798 pending_rename: None,
1799 searchable: true,
1800 cursor_shape: EditorSettings::get_global(cx)
1801 .cursor_shape
1802 .unwrap_or_default(),
1803 current_line_highlight: None,
1804 autoindent_mode: Some(AutoindentMode::EachLine),
1805 collapse_matches: false,
1806 workspace: None,
1807 input_enabled: true,
1808 use_modal_editing: mode.is_full(),
1809 read_only: mode.is_minimap(),
1810 use_autoclose: true,
1811 use_auto_surround: true,
1812 auto_replace_emoji_shortcode: false,
1813 jsx_tag_auto_close_enabled_in_any_buffer: false,
1814 leader_id: None,
1815 remote_id: None,
1816 hover_state: HoverState::default(),
1817 pending_mouse_down: None,
1818 hovered_link_state: None,
1819 edit_prediction_provider: None,
1820 active_inline_completion: None,
1821 stale_inline_completion_in_menu: None,
1822 edit_prediction_preview: EditPredictionPreview::Inactive {
1823 released_too_fast: false,
1824 },
1825 inline_diagnostics_enabled: mode.is_full(),
1826 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1827 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1828
1829 gutter_hovered: false,
1830 pixel_position_of_newest_cursor: None,
1831 last_bounds: None,
1832 last_position_map: None,
1833 expect_bounds_change: None,
1834 gutter_dimensions: GutterDimensions::default(),
1835 style: None,
1836 show_cursor_names: false,
1837 hovered_cursors: HashMap::default(),
1838 next_editor_action_id: EditorActionId::default(),
1839 editor_actions: Rc::default(),
1840 inline_completions_hidden_for_vim_mode: false,
1841 show_inline_completions_override: None,
1842 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1843 edit_prediction_settings: EditPredictionSettings::Disabled,
1844 edit_prediction_indent_conflict: false,
1845 edit_prediction_requires_modifier_in_indent_conflict: true,
1846 custom_context_menu: None,
1847 show_git_blame_gutter: false,
1848 show_git_blame_inline: false,
1849 show_selection_menu: None,
1850 show_git_blame_inline_delay_task: None,
1851 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1852 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1853 serialize_dirty_buffers: !mode.is_minimap()
1854 && ProjectSettings::get_global(cx)
1855 .session
1856 .restore_unsaved_buffers,
1857 blame: None,
1858 blame_subscription: None,
1859 tasks: BTreeMap::default(),
1860
1861 breakpoint_store,
1862 gutter_breakpoint_indicator: (None, None),
1863 _subscriptions: vec![
1864 cx.observe(&buffer, Self::on_buffer_changed),
1865 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1866 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1867 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1868 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1869 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1870 cx.observe_window_activation(window, |editor, window, cx| {
1871 let active = window.is_window_active();
1872 editor.blink_manager.update(cx, |blink_manager, cx| {
1873 if active {
1874 blink_manager.enable(cx);
1875 } else {
1876 blink_manager.disable(cx);
1877 }
1878 });
1879 }),
1880 ],
1881 tasks_update_task: None,
1882 linked_edit_ranges: Default::default(),
1883 in_project_search: false,
1884 previous_search_ranges: None,
1885 breadcrumb_header: None,
1886 focused_block: None,
1887 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1888 addons: HashMap::default(),
1889 registered_buffers: HashMap::default(),
1890 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1891 selection_mark_mode: false,
1892 toggle_fold_multiple_buffers: Task::ready(()),
1893 serialize_selections: Task::ready(()),
1894 serialize_folds: Task::ready(()),
1895 text_style_refinement: None,
1896 load_diff_task: load_uncommitted_diff,
1897 temporary_diff_override: false,
1898 mouse_cursor_hidden: false,
1899 minimap: None,
1900 hide_mouse_mode: EditorSettings::get_global(cx)
1901 .hide_mouse
1902 .unwrap_or_default(),
1903 change_list: ChangeList::new(),
1904 mode,
1905 };
1906 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1907 this._subscriptions
1908 .push(cx.observe(breakpoints, |_, _, cx| {
1909 cx.notify();
1910 }));
1911 }
1912 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1913 this._subscriptions.extend(project_subscriptions);
1914
1915 this._subscriptions.push(cx.subscribe_in(
1916 &cx.entity(),
1917 window,
1918 |editor, _, e: &EditorEvent, window, cx| match e {
1919 EditorEvent::ScrollPositionChanged { local, .. } => {
1920 if *local {
1921 let new_anchor = editor.scroll_manager.anchor();
1922 let snapshot = editor.snapshot(window, cx);
1923 editor.update_restoration_data(cx, move |data| {
1924 data.scroll_position = (
1925 new_anchor.top_row(&snapshot.buffer_snapshot),
1926 new_anchor.offset,
1927 );
1928 });
1929 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
1930 editor.inline_blame_popover.take();
1931 }
1932 }
1933 EditorEvent::Edited { .. } => {
1934 if !vim_enabled(cx) {
1935 let (map, selections) = editor.selections.all_adjusted_display(cx);
1936 let pop_state = editor
1937 .change_list
1938 .last()
1939 .map(|previous| {
1940 previous.len() == selections.len()
1941 && previous.iter().enumerate().all(|(ix, p)| {
1942 p.to_display_point(&map).row()
1943 == selections[ix].head().row()
1944 })
1945 })
1946 .unwrap_or(false);
1947 let new_positions = selections
1948 .into_iter()
1949 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
1950 .collect();
1951 editor
1952 .change_list
1953 .push_to_change_list(pop_state, new_positions);
1954 }
1955 }
1956 _ => (),
1957 },
1958 ));
1959
1960 if let Some(dap_store) = this
1961 .project
1962 .as_ref()
1963 .map(|project| project.read(cx).dap_store())
1964 {
1965 let weak_editor = cx.weak_entity();
1966
1967 this._subscriptions
1968 .push(
1969 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
1970 let session_entity = cx.entity();
1971 weak_editor
1972 .update(cx, |editor, cx| {
1973 editor._subscriptions.push(
1974 cx.subscribe(&session_entity, Self::on_debug_session_event),
1975 );
1976 })
1977 .ok();
1978 }),
1979 );
1980
1981 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
1982 this._subscriptions
1983 .push(cx.subscribe(&session, Self::on_debug_session_event));
1984 }
1985 }
1986
1987 this.end_selection(window, cx);
1988 this.scroll_manager.show_scrollbars(window, cx);
1989 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1990
1991 if full_mode {
1992 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1993 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1994
1995 if this.git_blame_inline_enabled {
1996 this.start_git_blame_inline(false, window, cx);
1997 }
1998
1999 this.go_to_active_debug_line(window, cx);
2000
2001 if let Some(buffer) = buffer.read(cx).as_singleton() {
2002 if let Some(project) = this.project.as_ref() {
2003 let handle = project.update(cx, |project, cx| {
2004 project.register_buffer_with_language_servers(&buffer, cx)
2005 });
2006 this.registered_buffers
2007 .insert(buffer.read(cx).remote_id(), handle);
2008 }
2009 }
2010
2011 this.minimap = this.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2012 }
2013
2014 this.report_editor_event("Editor Opened", None, cx);
2015 this
2016 }
2017
2018 pub fn deploy_mouse_context_menu(
2019 &mut self,
2020 position: gpui::Point<Pixels>,
2021 context_menu: Entity<ContextMenu>,
2022 window: &mut Window,
2023 cx: &mut Context<Self>,
2024 ) {
2025 self.mouse_context_menu = Some(MouseContextMenu::new(
2026 self,
2027 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2028 context_menu,
2029 window,
2030 cx,
2031 ));
2032 }
2033
2034 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2035 self.mouse_context_menu
2036 .as_ref()
2037 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2038 }
2039
2040 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2041 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2042 }
2043
2044 fn key_context_internal(
2045 &self,
2046 has_active_edit_prediction: bool,
2047 window: &Window,
2048 cx: &App,
2049 ) -> KeyContext {
2050 let mut key_context = KeyContext::new_with_defaults();
2051 key_context.add("Editor");
2052 let mode = match self.mode {
2053 EditorMode::SingleLine { .. } => "single_line",
2054 EditorMode::AutoHeight { .. } => "auto_height",
2055 EditorMode::Minimap { .. } => "minimap",
2056 EditorMode::Full { .. } => "full",
2057 };
2058
2059 if EditorSettings::jupyter_enabled(cx) {
2060 key_context.add("jupyter");
2061 }
2062
2063 key_context.set("mode", mode);
2064 if self.pending_rename.is_some() {
2065 key_context.add("renaming");
2066 }
2067
2068 match self.context_menu.borrow().as_ref() {
2069 Some(CodeContextMenu::Completions(_)) => {
2070 key_context.add("menu");
2071 key_context.add("showing_completions");
2072 }
2073 Some(CodeContextMenu::CodeActions(_)) => {
2074 key_context.add("menu");
2075 key_context.add("showing_code_actions")
2076 }
2077 None => {}
2078 }
2079
2080 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2081 if !self.focus_handle(cx).contains_focused(window, cx)
2082 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2083 {
2084 for addon in self.addons.values() {
2085 addon.extend_key_context(&mut key_context, cx)
2086 }
2087 }
2088
2089 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2090 if let Some(extension) = singleton_buffer
2091 .read(cx)
2092 .file()
2093 .and_then(|file| file.path().extension()?.to_str())
2094 {
2095 key_context.set("extension", extension.to_string());
2096 }
2097 } else {
2098 key_context.add("multibuffer");
2099 }
2100
2101 if has_active_edit_prediction {
2102 if self.edit_prediction_in_conflict() {
2103 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2104 } else {
2105 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2106 key_context.add("copilot_suggestion");
2107 }
2108 }
2109
2110 if self.selection_mark_mode {
2111 key_context.add("selection_mode");
2112 }
2113
2114 key_context
2115 }
2116
2117 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2118 self.mouse_cursor_hidden = match origin {
2119 HideMouseCursorOrigin::TypingAction => {
2120 matches!(
2121 self.hide_mouse_mode,
2122 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2123 )
2124 }
2125 HideMouseCursorOrigin::MovementAction => {
2126 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2127 }
2128 };
2129 }
2130
2131 pub fn edit_prediction_in_conflict(&self) -> bool {
2132 if !self.show_edit_predictions_in_menu() {
2133 return false;
2134 }
2135
2136 let showing_completions = self
2137 .context_menu
2138 .borrow()
2139 .as_ref()
2140 .map_or(false, |context| {
2141 matches!(context, CodeContextMenu::Completions(_))
2142 });
2143
2144 showing_completions
2145 || self.edit_prediction_requires_modifier()
2146 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2147 // bindings to insert tab characters.
2148 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2149 }
2150
2151 pub fn accept_edit_prediction_keybind(
2152 &self,
2153 window: &Window,
2154 cx: &App,
2155 ) -> AcceptEditPredictionBinding {
2156 let key_context = self.key_context_internal(true, window, cx);
2157 let in_conflict = self.edit_prediction_in_conflict();
2158
2159 AcceptEditPredictionBinding(
2160 window
2161 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2162 .into_iter()
2163 .filter(|binding| {
2164 !in_conflict
2165 || binding
2166 .keystrokes()
2167 .first()
2168 .map_or(false, |keystroke| keystroke.modifiers.modified())
2169 })
2170 .rev()
2171 .min_by_key(|binding| {
2172 binding
2173 .keystrokes()
2174 .first()
2175 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
2176 }),
2177 )
2178 }
2179
2180 pub fn new_file(
2181 workspace: &mut Workspace,
2182 _: &workspace::NewFile,
2183 window: &mut Window,
2184 cx: &mut Context<Workspace>,
2185 ) {
2186 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2187 "Failed to create buffer",
2188 window,
2189 cx,
2190 |e, _, _| match e.error_code() {
2191 ErrorCode::RemoteUpgradeRequired => Some(format!(
2192 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2193 e.error_tag("required").unwrap_or("the latest version")
2194 )),
2195 _ => None,
2196 },
2197 );
2198 }
2199
2200 pub fn new_in_workspace(
2201 workspace: &mut Workspace,
2202 window: &mut Window,
2203 cx: &mut Context<Workspace>,
2204 ) -> Task<Result<Entity<Editor>>> {
2205 let project = workspace.project().clone();
2206 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2207
2208 cx.spawn_in(window, async move |workspace, cx| {
2209 let buffer = create.await?;
2210 workspace.update_in(cx, |workspace, window, cx| {
2211 let editor =
2212 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2213 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2214 editor
2215 })
2216 })
2217 }
2218
2219 fn new_file_vertical(
2220 workspace: &mut Workspace,
2221 _: &workspace::NewFileSplitVertical,
2222 window: &mut Window,
2223 cx: &mut Context<Workspace>,
2224 ) {
2225 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2226 }
2227
2228 fn new_file_horizontal(
2229 workspace: &mut Workspace,
2230 _: &workspace::NewFileSplitHorizontal,
2231 window: &mut Window,
2232 cx: &mut Context<Workspace>,
2233 ) {
2234 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2235 }
2236
2237 fn new_file_in_direction(
2238 workspace: &mut Workspace,
2239 direction: SplitDirection,
2240 window: &mut Window,
2241 cx: &mut Context<Workspace>,
2242 ) {
2243 let project = workspace.project().clone();
2244 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2245
2246 cx.spawn_in(window, async move |workspace, cx| {
2247 let buffer = create.await?;
2248 workspace.update_in(cx, move |workspace, window, cx| {
2249 workspace.split_item(
2250 direction,
2251 Box::new(
2252 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2253 ),
2254 window,
2255 cx,
2256 )
2257 })?;
2258 anyhow::Ok(())
2259 })
2260 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2261 match e.error_code() {
2262 ErrorCode::RemoteUpgradeRequired => Some(format!(
2263 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2264 e.error_tag("required").unwrap_or("the latest version")
2265 )),
2266 _ => None,
2267 }
2268 });
2269 }
2270
2271 pub fn leader_id(&self) -> Option<CollaboratorId> {
2272 self.leader_id
2273 }
2274
2275 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2276 &self.buffer
2277 }
2278
2279 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2280 self.workspace.as_ref()?.0.upgrade()
2281 }
2282
2283 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2284 self.buffer().read(cx).title(cx)
2285 }
2286
2287 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2288 let git_blame_gutter_max_author_length = self
2289 .render_git_blame_gutter(cx)
2290 .then(|| {
2291 if let Some(blame) = self.blame.as_ref() {
2292 let max_author_length =
2293 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2294 Some(max_author_length)
2295 } else {
2296 None
2297 }
2298 })
2299 .flatten();
2300
2301 EditorSnapshot {
2302 mode: self.mode.clone(),
2303 show_gutter: self.show_gutter,
2304 show_line_numbers: self.show_line_numbers,
2305 show_git_diff_gutter: self.show_git_diff_gutter,
2306 show_runnables: self.show_runnables,
2307 show_breakpoints: self.show_breakpoints,
2308 git_blame_gutter_max_author_length,
2309 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2310 scroll_anchor: self.scroll_manager.anchor(),
2311 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2312 placeholder_text: self.placeholder_text.clone(),
2313 is_focused: self.focus_handle.is_focused(window),
2314 current_line_highlight: self
2315 .current_line_highlight
2316 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2317 gutter_hovered: self.gutter_hovered,
2318 }
2319 }
2320
2321 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2322 self.buffer.read(cx).language_at(point, cx)
2323 }
2324
2325 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2326 self.buffer.read(cx).read(cx).file_at(point).cloned()
2327 }
2328
2329 pub fn active_excerpt(
2330 &self,
2331 cx: &App,
2332 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2333 self.buffer
2334 .read(cx)
2335 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2336 }
2337
2338 pub fn mode(&self) -> &EditorMode {
2339 &self.mode
2340 }
2341
2342 pub fn set_mode(&mut self, mode: EditorMode) {
2343 self.mode = mode;
2344 }
2345
2346 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2347 self.collaboration_hub.as_deref()
2348 }
2349
2350 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2351 self.collaboration_hub = Some(hub);
2352 }
2353
2354 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2355 self.in_project_search = in_project_search;
2356 }
2357
2358 pub fn set_custom_context_menu(
2359 &mut self,
2360 f: impl 'static
2361 + Fn(
2362 &mut Self,
2363 DisplayPoint,
2364 &mut Window,
2365 &mut Context<Self>,
2366 ) -> Option<Entity<ui::ContextMenu>>,
2367 ) {
2368 self.custom_context_menu = Some(Box::new(f))
2369 }
2370
2371 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
2372 self.completion_provider = provider;
2373 }
2374
2375 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2376 self.semantics_provider.clone()
2377 }
2378
2379 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2380 self.semantics_provider = provider;
2381 }
2382
2383 pub fn set_edit_prediction_provider<T>(
2384 &mut self,
2385 provider: Option<Entity<T>>,
2386 window: &mut Window,
2387 cx: &mut Context<Self>,
2388 ) where
2389 T: EditPredictionProvider,
2390 {
2391 self.edit_prediction_provider =
2392 provider.map(|provider| RegisteredInlineCompletionProvider {
2393 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2394 if this.focus_handle.is_focused(window) {
2395 this.update_visible_inline_completion(window, cx);
2396 }
2397 }),
2398 provider: Arc::new(provider),
2399 });
2400 self.update_edit_prediction_settings(cx);
2401 self.refresh_inline_completion(false, false, window, cx);
2402 }
2403
2404 pub fn placeholder_text(&self) -> Option<&str> {
2405 self.placeholder_text.as_deref()
2406 }
2407
2408 pub fn set_placeholder_text(
2409 &mut self,
2410 placeholder_text: impl Into<Arc<str>>,
2411 cx: &mut Context<Self>,
2412 ) {
2413 let placeholder_text = Some(placeholder_text.into());
2414 if self.placeholder_text != placeholder_text {
2415 self.placeholder_text = placeholder_text;
2416 cx.notify();
2417 }
2418 }
2419
2420 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2421 self.cursor_shape = cursor_shape;
2422
2423 // Disrupt blink for immediate user feedback that the cursor shape has changed
2424 self.blink_manager.update(cx, BlinkManager::show_cursor);
2425
2426 cx.notify();
2427 }
2428
2429 pub fn set_current_line_highlight(
2430 &mut self,
2431 current_line_highlight: Option<CurrentLineHighlight>,
2432 ) {
2433 self.current_line_highlight = current_line_highlight;
2434 }
2435
2436 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2437 self.collapse_matches = collapse_matches;
2438 }
2439
2440 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2441 let buffers = self.buffer.read(cx).all_buffers();
2442 let Some(project) = self.project.as_ref() else {
2443 return;
2444 };
2445 project.update(cx, |project, cx| {
2446 for buffer in buffers {
2447 self.registered_buffers
2448 .entry(buffer.read(cx).remote_id())
2449 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2450 }
2451 })
2452 }
2453
2454 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2455 if self.collapse_matches {
2456 return range.start..range.start;
2457 }
2458 range.clone()
2459 }
2460
2461 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2462 if self.display_map.read(cx).clip_at_line_ends != clip {
2463 self.display_map
2464 .update(cx, |map, _| map.clip_at_line_ends = clip);
2465 }
2466 }
2467
2468 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2469 self.input_enabled = input_enabled;
2470 }
2471
2472 pub fn set_inline_completions_hidden_for_vim_mode(
2473 &mut self,
2474 hidden: bool,
2475 window: &mut Window,
2476 cx: &mut Context<Self>,
2477 ) {
2478 if hidden != self.inline_completions_hidden_for_vim_mode {
2479 self.inline_completions_hidden_for_vim_mode = hidden;
2480 if hidden {
2481 self.update_visible_inline_completion(window, cx);
2482 } else {
2483 self.refresh_inline_completion(true, false, window, cx);
2484 }
2485 }
2486 }
2487
2488 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2489 self.menu_inline_completions_policy = value;
2490 }
2491
2492 pub fn set_autoindent(&mut self, autoindent: bool) {
2493 if autoindent {
2494 self.autoindent_mode = Some(AutoindentMode::EachLine);
2495 } else {
2496 self.autoindent_mode = None;
2497 }
2498 }
2499
2500 pub fn read_only(&self, cx: &App) -> bool {
2501 self.read_only || self.buffer.read(cx).read_only()
2502 }
2503
2504 pub fn set_read_only(&mut self, read_only: bool) {
2505 self.read_only = read_only;
2506 }
2507
2508 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2509 self.use_autoclose = autoclose;
2510 }
2511
2512 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2513 self.use_auto_surround = auto_surround;
2514 }
2515
2516 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2517 self.auto_replace_emoji_shortcode = auto_replace;
2518 }
2519
2520 pub fn toggle_edit_predictions(
2521 &mut self,
2522 _: &ToggleEditPrediction,
2523 window: &mut Window,
2524 cx: &mut Context<Self>,
2525 ) {
2526 if self.show_inline_completions_override.is_some() {
2527 self.set_show_edit_predictions(None, window, cx);
2528 } else {
2529 let show_edit_predictions = !self.edit_predictions_enabled();
2530 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2531 }
2532 }
2533
2534 pub fn set_show_edit_predictions(
2535 &mut self,
2536 show_edit_predictions: Option<bool>,
2537 window: &mut Window,
2538 cx: &mut Context<Self>,
2539 ) {
2540 self.show_inline_completions_override = show_edit_predictions;
2541 self.update_edit_prediction_settings(cx);
2542
2543 if let Some(false) = show_edit_predictions {
2544 self.discard_inline_completion(false, cx);
2545 } else {
2546 self.refresh_inline_completion(false, true, window, cx);
2547 }
2548 }
2549
2550 fn inline_completions_disabled_in_scope(
2551 &self,
2552 buffer: &Entity<Buffer>,
2553 buffer_position: language::Anchor,
2554 cx: &App,
2555 ) -> bool {
2556 let snapshot = buffer.read(cx).snapshot();
2557 let settings = snapshot.settings_at(buffer_position, cx);
2558
2559 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2560 return false;
2561 };
2562
2563 scope.override_name().map_or(false, |scope_name| {
2564 settings
2565 .edit_predictions_disabled_in
2566 .iter()
2567 .any(|s| s == scope_name)
2568 })
2569 }
2570
2571 pub fn set_use_modal_editing(&mut self, to: bool) {
2572 self.use_modal_editing = to;
2573 }
2574
2575 pub fn use_modal_editing(&self) -> bool {
2576 self.use_modal_editing
2577 }
2578
2579 fn selections_did_change(
2580 &mut self,
2581 local: bool,
2582 old_cursor_position: &Anchor,
2583 show_completions: bool,
2584 window: &mut Window,
2585 cx: &mut Context<Self>,
2586 ) {
2587 window.invalidate_character_coordinates();
2588
2589 // Copy selections to primary selection buffer
2590 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2591 if local {
2592 let selections = self.selections.all::<usize>(cx);
2593 let buffer_handle = self.buffer.read(cx).read(cx);
2594
2595 let mut text = String::new();
2596 for (index, selection) in selections.iter().enumerate() {
2597 let text_for_selection = buffer_handle
2598 .text_for_range(selection.start..selection.end)
2599 .collect::<String>();
2600
2601 text.push_str(&text_for_selection);
2602 if index != selections.len() - 1 {
2603 text.push('\n');
2604 }
2605 }
2606
2607 if !text.is_empty() {
2608 cx.write_to_primary(ClipboardItem::new_string(text));
2609 }
2610 }
2611
2612 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2613 self.buffer.update(cx, |buffer, cx| {
2614 buffer.set_active_selections(
2615 &self.selections.disjoint_anchors(),
2616 self.selections.line_mode,
2617 self.cursor_shape,
2618 cx,
2619 )
2620 });
2621 }
2622 let display_map = self
2623 .display_map
2624 .update(cx, |display_map, cx| display_map.snapshot(cx));
2625 let buffer = &display_map.buffer_snapshot;
2626 self.add_selections_state = None;
2627 self.select_next_state = None;
2628 self.select_prev_state = None;
2629 self.select_syntax_node_history.try_clear();
2630 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2631 self.snippet_stack
2632 .invalidate(&self.selections.disjoint_anchors(), buffer);
2633 self.take_rename(false, window, cx);
2634
2635 let new_cursor_position = self.selections.newest_anchor().head();
2636
2637 self.push_to_nav_history(
2638 *old_cursor_position,
2639 Some(new_cursor_position.to_point(buffer)),
2640 false,
2641 cx,
2642 );
2643
2644 if local {
2645 let new_cursor_position = self.selections.newest_anchor().head();
2646 let mut context_menu = self.context_menu.borrow_mut();
2647 let completion_menu = match context_menu.as_ref() {
2648 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2649 _ => {
2650 *context_menu = None;
2651 None
2652 }
2653 };
2654 if let Some(buffer_id) = new_cursor_position.buffer_id {
2655 if !self.registered_buffers.contains_key(&buffer_id) {
2656 if let Some(project) = self.project.as_ref() {
2657 project.update(cx, |project, cx| {
2658 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2659 return;
2660 };
2661 self.registered_buffers.insert(
2662 buffer_id,
2663 project.register_buffer_with_language_servers(&buffer, cx),
2664 );
2665 })
2666 }
2667 }
2668 }
2669
2670 if let Some(completion_menu) = completion_menu {
2671 let cursor_position = new_cursor_position.to_offset(buffer);
2672 let (word_range, kind) =
2673 buffer.surrounding_word(completion_menu.initial_position, true);
2674 if kind == Some(CharKind::Word)
2675 && word_range.to_inclusive().contains(&cursor_position)
2676 {
2677 let mut completion_menu = completion_menu.clone();
2678 drop(context_menu);
2679
2680 let query = Self::completion_query(buffer, cursor_position);
2681 cx.spawn(async move |this, cx| {
2682 completion_menu
2683 .filter(query.as_deref(), cx.background_executor().clone())
2684 .await;
2685
2686 this.update(cx, |this, cx| {
2687 let mut context_menu = this.context_menu.borrow_mut();
2688 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2689 else {
2690 return;
2691 };
2692
2693 if menu.id > completion_menu.id {
2694 return;
2695 }
2696
2697 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2698 drop(context_menu);
2699 cx.notify();
2700 })
2701 })
2702 .detach();
2703
2704 if show_completions {
2705 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2706 }
2707 } else {
2708 drop(context_menu);
2709 self.hide_context_menu(window, cx);
2710 }
2711 } else {
2712 drop(context_menu);
2713 }
2714
2715 hide_hover(self, cx);
2716
2717 if old_cursor_position.to_display_point(&display_map).row()
2718 != new_cursor_position.to_display_point(&display_map).row()
2719 {
2720 self.available_code_actions.take();
2721 }
2722 self.refresh_code_actions(window, cx);
2723 self.refresh_document_highlights(cx);
2724 self.refresh_selected_text_highlights(false, window, cx);
2725 refresh_matching_bracket_highlights(self, window, cx);
2726 self.update_visible_inline_completion(window, cx);
2727 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2728 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2729 self.inline_blame_popover.take();
2730 if self.git_blame_inline_enabled {
2731 self.start_inline_blame_timer(window, cx);
2732 }
2733 }
2734
2735 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2736 cx.emit(EditorEvent::SelectionsChanged { local });
2737
2738 let selections = &self.selections.disjoint;
2739 if selections.len() == 1 {
2740 cx.emit(SearchEvent::ActiveMatchChanged)
2741 }
2742 if local {
2743 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2744 let inmemory_selections = selections
2745 .iter()
2746 .map(|s| {
2747 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2748 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2749 })
2750 .collect();
2751 self.update_restoration_data(cx, |data| {
2752 data.selections = inmemory_selections;
2753 });
2754
2755 if WorkspaceSettings::get(None, cx).restore_on_startup
2756 != RestoreOnStartupBehavior::None
2757 {
2758 if let Some(workspace_id) =
2759 self.workspace.as_ref().and_then(|workspace| workspace.1)
2760 {
2761 let snapshot = self.buffer().read(cx).snapshot(cx);
2762 let selections = selections.clone();
2763 let background_executor = cx.background_executor().clone();
2764 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2765 self.serialize_selections = cx.background_spawn(async move {
2766 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2767 let db_selections = selections
2768 .iter()
2769 .map(|selection| {
2770 (
2771 selection.start.to_offset(&snapshot),
2772 selection.end.to_offset(&snapshot),
2773 )
2774 })
2775 .collect();
2776
2777 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2778 .await
2779 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2780 .log_err();
2781 });
2782 }
2783 }
2784 }
2785 }
2786
2787 cx.notify();
2788 }
2789
2790 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2791 use text::ToOffset as _;
2792 use text::ToPoint as _;
2793
2794 if self.mode.is_minimap()
2795 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2796 {
2797 return;
2798 }
2799
2800 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2801 return;
2802 };
2803
2804 let snapshot = singleton.read(cx).snapshot();
2805 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2806 let display_snapshot = display_map.snapshot(cx);
2807
2808 display_snapshot
2809 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2810 .map(|fold| {
2811 fold.range.start.text_anchor.to_point(&snapshot)
2812 ..fold.range.end.text_anchor.to_point(&snapshot)
2813 })
2814 .collect()
2815 });
2816 self.update_restoration_data(cx, |data| {
2817 data.folds = inmemory_folds;
2818 });
2819
2820 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2821 return;
2822 };
2823 let background_executor = cx.background_executor().clone();
2824 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2825 let db_folds = self.display_map.update(cx, |display_map, cx| {
2826 display_map
2827 .snapshot(cx)
2828 .folds_in_range(0..snapshot.len())
2829 .map(|fold| {
2830 (
2831 fold.range.start.text_anchor.to_offset(&snapshot),
2832 fold.range.end.text_anchor.to_offset(&snapshot),
2833 )
2834 })
2835 .collect()
2836 });
2837 self.serialize_folds = cx.background_spawn(async move {
2838 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2839 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2840 .await
2841 .with_context(|| {
2842 format!(
2843 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2844 )
2845 })
2846 .log_err();
2847 });
2848 }
2849
2850 pub fn sync_selections(
2851 &mut self,
2852 other: Entity<Editor>,
2853 cx: &mut Context<Self>,
2854 ) -> gpui::Subscription {
2855 let other_selections = other.read(cx).selections.disjoint.to_vec();
2856 self.selections.change_with(cx, |selections| {
2857 selections.select_anchors(other_selections);
2858 });
2859
2860 let other_subscription =
2861 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2862 EditorEvent::SelectionsChanged { local: true } => {
2863 let other_selections = other.read(cx).selections.disjoint.to_vec();
2864 if other_selections.is_empty() {
2865 return;
2866 }
2867 this.selections.change_with(cx, |selections| {
2868 selections.select_anchors(other_selections);
2869 });
2870 }
2871 _ => {}
2872 });
2873
2874 let this_subscription =
2875 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2876 EditorEvent::SelectionsChanged { local: true } => {
2877 let these_selections = this.selections.disjoint.to_vec();
2878 if these_selections.is_empty() {
2879 return;
2880 }
2881 other.update(cx, |other_editor, cx| {
2882 other_editor.selections.change_with(cx, |selections| {
2883 selections.select_anchors(these_selections);
2884 })
2885 });
2886 }
2887 _ => {}
2888 });
2889
2890 Subscription::join(other_subscription, this_subscription)
2891 }
2892
2893 pub fn change_selections<R>(
2894 &mut self,
2895 autoscroll: Option<Autoscroll>,
2896 window: &mut Window,
2897 cx: &mut Context<Self>,
2898 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2899 ) -> R {
2900 self.change_selections_inner(autoscroll, true, window, cx, change)
2901 }
2902
2903 fn change_selections_inner<R>(
2904 &mut self,
2905 autoscroll: Option<Autoscroll>,
2906 request_completions: bool,
2907 window: &mut Window,
2908 cx: &mut Context<Self>,
2909 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2910 ) -> R {
2911 let old_cursor_position = self.selections.newest_anchor().head();
2912 self.push_to_selection_history();
2913
2914 let (changed, result) = self.selections.change_with(cx, change);
2915
2916 if changed {
2917 if let Some(autoscroll) = autoscroll {
2918 self.request_autoscroll(autoscroll, cx);
2919 }
2920 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2921
2922 if self.should_open_signature_help_automatically(
2923 &old_cursor_position,
2924 self.signature_help_state.backspace_pressed(),
2925 cx,
2926 ) {
2927 self.show_signature_help(&ShowSignatureHelp, window, cx);
2928 }
2929 self.signature_help_state.set_backspace_pressed(false);
2930 }
2931
2932 result
2933 }
2934
2935 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2936 where
2937 I: IntoIterator<Item = (Range<S>, T)>,
2938 S: ToOffset,
2939 T: Into<Arc<str>>,
2940 {
2941 if self.read_only(cx) {
2942 return;
2943 }
2944
2945 self.buffer
2946 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2947 }
2948
2949 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2950 where
2951 I: IntoIterator<Item = (Range<S>, T)>,
2952 S: ToOffset,
2953 T: Into<Arc<str>>,
2954 {
2955 if self.read_only(cx) {
2956 return;
2957 }
2958
2959 self.buffer.update(cx, |buffer, cx| {
2960 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2961 });
2962 }
2963
2964 pub fn edit_with_block_indent<I, S, T>(
2965 &mut self,
2966 edits: I,
2967 original_indent_columns: Vec<Option<u32>>,
2968 cx: &mut Context<Self>,
2969 ) where
2970 I: IntoIterator<Item = (Range<S>, T)>,
2971 S: ToOffset,
2972 T: Into<Arc<str>>,
2973 {
2974 if self.read_only(cx) {
2975 return;
2976 }
2977
2978 self.buffer.update(cx, |buffer, cx| {
2979 buffer.edit(
2980 edits,
2981 Some(AutoindentMode::Block {
2982 original_indent_columns,
2983 }),
2984 cx,
2985 )
2986 });
2987 }
2988
2989 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2990 self.hide_context_menu(window, cx);
2991
2992 match phase {
2993 SelectPhase::Begin {
2994 position,
2995 add,
2996 click_count,
2997 } => self.begin_selection(position, add, click_count, window, cx),
2998 SelectPhase::BeginColumnar {
2999 position,
3000 goal_column,
3001 reset,
3002 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3003 SelectPhase::Extend {
3004 position,
3005 click_count,
3006 } => self.extend_selection(position, click_count, window, cx),
3007 SelectPhase::Update {
3008 position,
3009 goal_column,
3010 scroll_delta,
3011 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3012 SelectPhase::End => self.end_selection(window, cx),
3013 }
3014 }
3015
3016 fn extend_selection(
3017 &mut self,
3018 position: DisplayPoint,
3019 click_count: usize,
3020 window: &mut Window,
3021 cx: &mut Context<Self>,
3022 ) {
3023 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3024 let tail = self.selections.newest::<usize>(cx).tail();
3025 self.begin_selection(position, false, click_count, window, cx);
3026
3027 let position = position.to_offset(&display_map, Bias::Left);
3028 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3029
3030 let mut pending_selection = self
3031 .selections
3032 .pending_anchor()
3033 .expect("extend_selection not called with pending selection");
3034 if position >= tail {
3035 pending_selection.start = tail_anchor;
3036 } else {
3037 pending_selection.end = tail_anchor;
3038 pending_selection.reversed = true;
3039 }
3040
3041 let mut pending_mode = self.selections.pending_mode().unwrap();
3042 match &mut pending_mode {
3043 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3044 _ => {}
3045 }
3046
3047 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3048
3049 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3050 s.set_pending(pending_selection, pending_mode)
3051 });
3052 }
3053
3054 fn begin_selection(
3055 &mut self,
3056 position: DisplayPoint,
3057 add: bool,
3058 click_count: usize,
3059 window: &mut Window,
3060 cx: &mut Context<Self>,
3061 ) {
3062 if !self.focus_handle.is_focused(window) {
3063 self.last_focused_descendant = None;
3064 window.focus(&self.focus_handle);
3065 }
3066
3067 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3068 let buffer = &display_map.buffer_snapshot;
3069 let position = display_map.clip_point(position, Bias::Left);
3070
3071 let start;
3072 let end;
3073 let mode;
3074 let mut auto_scroll;
3075 match click_count {
3076 1 => {
3077 start = buffer.anchor_before(position.to_point(&display_map));
3078 end = start;
3079 mode = SelectMode::Character;
3080 auto_scroll = true;
3081 }
3082 2 => {
3083 let range = movement::surrounding_word(&display_map, position);
3084 start = buffer.anchor_before(range.start.to_point(&display_map));
3085 end = buffer.anchor_before(range.end.to_point(&display_map));
3086 mode = SelectMode::Word(start..end);
3087 auto_scroll = true;
3088 }
3089 3 => {
3090 let position = display_map
3091 .clip_point(position, Bias::Left)
3092 .to_point(&display_map);
3093 let line_start = display_map.prev_line_boundary(position).0;
3094 let next_line_start = buffer.clip_point(
3095 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3096 Bias::Left,
3097 );
3098 start = buffer.anchor_before(line_start);
3099 end = buffer.anchor_before(next_line_start);
3100 mode = SelectMode::Line(start..end);
3101 auto_scroll = true;
3102 }
3103 _ => {
3104 start = buffer.anchor_before(0);
3105 end = buffer.anchor_before(buffer.len());
3106 mode = SelectMode::All;
3107 auto_scroll = false;
3108 }
3109 }
3110 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3111
3112 let point_to_delete: Option<usize> = {
3113 let selected_points: Vec<Selection<Point>> =
3114 self.selections.disjoint_in_range(start..end, cx);
3115
3116 if !add || click_count > 1 {
3117 None
3118 } else if !selected_points.is_empty() {
3119 Some(selected_points[0].id)
3120 } else {
3121 let clicked_point_already_selected =
3122 self.selections.disjoint.iter().find(|selection| {
3123 selection.start.to_point(buffer) == start.to_point(buffer)
3124 || selection.end.to_point(buffer) == end.to_point(buffer)
3125 });
3126
3127 clicked_point_already_selected.map(|selection| selection.id)
3128 }
3129 };
3130
3131 let selections_count = self.selections.count();
3132
3133 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3134 if let Some(point_to_delete) = point_to_delete {
3135 s.delete(point_to_delete);
3136
3137 if selections_count == 1 {
3138 s.set_pending_anchor_range(start..end, mode);
3139 }
3140 } else {
3141 if !add {
3142 s.clear_disjoint();
3143 }
3144
3145 s.set_pending_anchor_range(start..end, mode);
3146 }
3147 });
3148 }
3149
3150 fn begin_columnar_selection(
3151 &mut self,
3152 position: DisplayPoint,
3153 goal_column: u32,
3154 reset: bool,
3155 window: &mut Window,
3156 cx: &mut Context<Self>,
3157 ) {
3158 if !self.focus_handle.is_focused(window) {
3159 self.last_focused_descendant = None;
3160 window.focus(&self.focus_handle);
3161 }
3162
3163 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3164
3165 if reset {
3166 let pointer_position = display_map
3167 .buffer_snapshot
3168 .anchor_before(position.to_point(&display_map));
3169
3170 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3171 s.clear_disjoint();
3172 s.set_pending_anchor_range(
3173 pointer_position..pointer_position,
3174 SelectMode::Character,
3175 );
3176 });
3177 }
3178
3179 let tail = self.selections.newest::<Point>(cx).tail();
3180 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3181
3182 if !reset {
3183 self.select_columns(
3184 tail.to_display_point(&display_map),
3185 position,
3186 goal_column,
3187 &display_map,
3188 window,
3189 cx,
3190 );
3191 }
3192 }
3193
3194 fn update_selection(
3195 &mut self,
3196 position: DisplayPoint,
3197 goal_column: u32,
3198 scroll_delta: gpui::Point<f32>,
3199 window: &mut Window,
3200 cx: &mut Context<Self>,
3201 ) {
3202 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3203
3204 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3205 let tail = tail.to_display_point(&display_map);
3206 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3207 } else if let Some(mut pending) = self.selections.pending_anchor() {
3208 let buffer = self.buffer.read(cx).snapshot(cx);
3209 let head;
3210 let tail;
3211 let mode = self.selections.pending_mode().unwrap();
3212 match &mode {
3213 SelectMode::Character => {
3214 head = position.to_point(&display_map);
3215 tail = pending.tail().to_point(&buffer);
3216 }
3217 SelectMode::Word(original_range) => {
3218 let original_display_range = original_range.start.to_display_point(&display_map)
3219 ..original_range.end.to_display_point(&display_map);
3220 let original_buffer_range = original_display_range.start.to_point(&display_map)
3221 ..original_display_range.end.to_point(&display_map);
3222 if movement::is_inside_word(&display_map, position)
3223 || original_display_range.contains(&position)
3224 {
3225 let word_range = movement::surrounding_word(&display_map, position);
3226 if word_range.start < original_display_range.start {
3227 head = word_range.start.to_point(&display_map);
3228 } else {
3229 head = word_range.end.to_point(&display_map);
3230 }
3231 } else {
3232 head = position.to_point(&display_map);
3233 }
3234
3235 if head <= original_buffer_range.start {
3236 tail = original_buffer_range.end;
3237 } else {
3238 tail = original_buffer_range.start;
3239 }
3240 }
3241 SelectMode::Line(original_range) => {
3242 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3243
3244 let position = display_map
3245 .clip_point(position, Bias::Left)
3246 .to_point(&display_map);
3247 let line_start = display_map.prev_line_boundary(position).0;
3248 let next_line_start = buffer.clip_point(
3249 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3250 Bias::Left,
3251 );
3252
3253 if line_start < original_range.start {
3254 head = line_start
3255 } else {
3256 head = next_line_start
3257 }
3258
3259 if head <= original_range.start {
3260 tail = original_range.end;
3261 } else {
3262 tail = original_range.start;
3263 }
3264 }
3265 SelectMode::All => {
3266 return;
3267 }
3268 };
3269
3270 if head < tail {
3271 pending.start = buffer.anchor_before(head);
3272 pending.end = buffer.anchor_before(tail);
3273 pending.reversed = true;
3274 } else {
3275 pending.start = buffer.anchor_before(tail);
3276 pending.end = buffer.anchor_before(head);
3277 pending.reversed = false;
3278 }
3279
3280 self.change_selections(None, window, cx, |s| {
3281 s.set_pending(pending, mode);
3282 });
3283 } else {
3284 log::error!("update_selection dispatched with no pending selection");
3285 return;
3286 }
3287
3288 self.apply_scroll_delta(scroll_delta, window, cx);
3289 cx.notify();
3290 }
3291
3292 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3293 self.columnar_selection_tail.take();
3294 if self.selections.pending_anchor().is_some() {
3295 let selections = self.selections.all::<usize>(cx);
3296 self.change_selections(None, window, cx, |s| {
3297 s.select(selections);
3298 s.clear_pending();
3299 });
3300 }
3301 }
3302
3303 fn select_columns(
3304 &mut self,
3305 tail: DisplayPoint,
3306 head: DisplayPoint,
3307 goal_column: u32,
3308 display_map: &DisplaySnapshot,
3309 window: &mut Window,
3310 cx: &mut Context<Self>,
3311 ) {
3312 let start_row = cmp::min(tail.row(), head.row());
3313 let end_row = cmp::max(tail.row(), head.row());
3314 let start_column = cmp::min(tail.column(), goal_column);
3315 let end_column = cmp::max(tail.column(), goal_column);
3316 let reversed = start_column < tail.column();
3317
3318 let selection_ranges = (start_row.0..=end_row.0)
3319 .map(DisplayRow)
3320 .filter_map(|row| {
3321 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
3322 let start = display_map
3323 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3324 .to_point(display_map);
3325 let end = display_map
3326 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3327 .to_point(display_map);
3328 if reversed {
3329 Some(end..start)
3330 } else {
3331 Some(start..end)
3332 }
3333 } else {
3334 None
3335 }
3336 })
3337 .collect::<Vec<_>>();
3338
3339 self.change_selections(None, window, cx, |s| {
3340 s.select_ranges(selection_ranges);
3341 });
3342 cx.notify();
3343 }
3344
3345 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3346 self.selections
3347 .all_adjusted(cx)
3348 .iter()
3349 .any(|selection| !selection.is_empty())
3350 }
3351
3352 pub fn has_pending_nonempty_selection(&self) -> bool {
3353 let pending_nonempty_selection = match self.selections.pending_anchor() {
3354 Some(Selection { start, end, .. }) => start != end,
3355 None => false,
3356 };
3357
3358 pending_nonempty_selection
3359 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3360 }
3361
3362 pub fn has_pending_selection(&self) -> bool {
3363 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3364 }
3365
3366 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3367 self.selection_mark_mode = false;
3368
3369 if self.clear_expanded_diff_hunks(cx) {
3370 cx.notify();
3371 return;
3372 }
3373 if self.dismiss_menus_and_popups(true, window, cx) {
3374 return;
3375 }
3376
3377 if self.mode.is_full()
3378 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3379 {
3380 return;
3381 }
3382
3383 cx.propagate();
3384 }
3385
3386 pub fn dismiss_menus_and_popups(
3387 &mut self,
3388 is_user_requested: bool,
3389 window: &mut Window,
3390 cx: &mut Context<Self>,
3391 ) -> bool {
3392 if self.take_rename(false, window, cx).is_some() {
3393 return true;
3394 }
3395
3396 if hide_hover(self, cx) {
3397 return true;
3398 }
3399
3400 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3401 return true;
3402 }
3403
3404 if self.hide_context_menu(window, cx).is_some() {
3405 return true;
3406 }
3407
3408 if self.mouse_context_menu.take().is_some() {
3409 return true;
3410 }
3411
3412 if is_user_requested && self.discard_inline_completion(true, cx) {
3413 return true;
3414 }
3415
3416 if self.snippet_stack.pop().is_some() {
3417 return true;
3418 }
3419
3420 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3421 self.dismiss_diagnostics(cx);
3422 return true;
3423 }
3424
3425 false
3426 }
3427
3428 fn linked_editing_ranges_for(
3429 &self,
3430 selection: Range<text::Anchor>,
3431 cx: &App,
3432 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3433 if self.linked_edit_ranges.is_empty() {
3434 return None;
3435 }
3436 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3437 selection.end.buffer_id.and_then(|end_buffer_id| {
3438 if selection.start.buffer_id != Some(end_buffer_id) {
3439 return None;
3440 }
3441 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3442 let snapshot = buffer.read(cx).snapshot();
3443 self.linked_edit_ranges
3444 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3445 .map(|ranges| (ranges, snapshot, buffer))
3446 })?;
3447 use text::ToOffset as TO;
3448 // find offset from the start of current range to current cursor position
3449 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3450
3451 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3452 let start_difference = start_offset - start_byte_offset;
3453 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3454 let end_difference = end_offset - start_byte_offset;
3455 // Current range has associated linked ranges.
3456 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3457 for range in linked_ranges.iter() {
3458 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3459 let end_offset = start_offset + end_difference;
3460 let start_offset = start_offset + start_difference;
3461 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3462 continue;
3463 }
3464 if self.selections.disjoint_anchor_ranges().any(|s| {
3465 if s.start.buffer_id != selection.start.buffer_id
3466 || s.end.buffer_id != selection.end.buffer_id
3467 {
3468 return false;
3469 }
3470 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3471 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3472 }) {
3473 continue;
3474 }
3475 let start = buffer_snapshot.anchor_after(start_offset);
3476 let end = buffer_snapshot.anchor_after(end_offset);
3477 linked_edits
3478 .entry(buffer.clone())
3479 .or_default()
3480 .push(start..end);
3481 }
3482 Some(linked_edits)
3483 }
3484
3485 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3486 let text: Arc<str> = text.into();
3487
3488 if self.read_only(cx) {
3489 return;
3490 }
3491
3492 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3493
3494 let selections = self.selections.all_adjusted(cx);
3495 let mut bracket_inserted = false;
3496 let mut edits = Vec::new();
3497 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3498 let mut new_selections = Vec::with_capacity(selections.len());
3499 let mut new_autoclose_regions = Vec::new();
3500 let snapshot = self.buffer.read(cx).read(cx);
3501 let mut clear_linked_edit_ranges = false;
3502
3503 for (selection, autoclose_region) in
3504 self.selections_with_autoclose_regions(selections, &snapshot)
3505 {
3506 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3507 // Determine if the inserted text matches the opening or closing
3508 // bracket of any of this language's bracket pairs.
3509 let mut bracket_pair = None;
3510 let mut is_bracket_pair_start = false;
3511 let mut is_bracket_pair_end = false;
3512 if !text.is_empty() {
3513 let mut bracket_pair_matching_end = None;
3514 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3515 // and they are removing the character that triggered IME popup.
3516 for (pair, enabled) in scope.brackets() {
3517 if !pair.close && !pair.surround {
3518 continue;
3519 }
3520
3521 if enabled && pair.start.ends_with(text.as_ref()) {
3522 let prefix_len = pair.start.len() - text.len();
3523 let preceding_text_matches_prefix = prefix_len == 0
3524 || (selection.start.column >= (prefix_len as u32)
3525 && snapshot.contains_str_at(
3526 Point::new(
3527 selection.start.row,
3528 selection.start.column - (prefix_len as u32),
3529 ),
3530 &pair.start[..prefix_len],
3531 ));
3532 if preceding_text_matches_prefix {
3533 bracket_pair = Some(pair.clone());
3534 is_bracket_pair_start = true;
3535 break;
3536 }
3537 }
3538 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3539 {
3540 // take first bracket pair matching end, but don't break in case a later bracket
3541 // pair matches start
3542 bracket_pair_matching_end = Some(pair.clone());
3543 }
3544 }
3545 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3546 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3547 is_bracket_pair_end = true;
3548 }
3549 }
3550
3551 if let Some(bracket_pair) = bracket_pair {
3552 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3553 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3554 let auto_surround =
3555 self.use_auto_surround && snapshot_settings.use_auto_surround;
3556 if selection.is_empty() {
3557 if is_bracket_pair_start {
3558 // If the inserted text is a suffix of an opening bracket and the
3559 // selection is preceded by the rest of the opening bracket, then
3560 // insert the closing bracket.
3561 let following_text_allows_autoclose = snapshot
3562 .chars_at(selection.start)
3563 .next()
3564 .map_or(true, |c| scope.should_autoclose_before(c));
3565
3566 let preceding_text_allows_autoclose = selection.start.column == 0
3567 || snapshot.reversed_chars_at(selection.start).next().map_or(
3568 true,
3569 |c| {
3570 bracket_pair.start != bracket_pair.end
3571 || !snapshot
3572 .char_classifier_at(selection.start)
3573 .is_word(c)
3574 },
3575 );
3576
3577 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3578 && bracket_pair.start.len() == 1
3579 {
3580 let target = bracket_pair.start.chars().next().unwrap();
3581 let current_line_count = snapshot
3582 .reversed_chars_at(selection.start)
3583 .take_while(|&c| c != '\n')
3584 .filter(|&c| c == target)
3585 .count();
3586 current_line_count % 2 == 1
3587 } else {
3588 false
3589 };
3590
3591 if autoclose
3592 && bracket_pair.close
3593 && following_text_allows_autoclose
3594 && preceding_text_allows_autoclose
3595 && !is_closing_quote
3596 {
3597 let anchor = snapshot.anchor_before(selection.end);
3598 new_selections.push((selection.map(|_| anchor), text.len()));
3599 new_autoclose_regions.push((
3600 anchor,
3601 text.len(),
3602 selection.id,
3603 bracket_pair.clone(),
3604 ));
3605 edits.push((
3606 selection.range(),
3607 format!("{}{}", text, bracket_pair.end).into(),
3608 ));
3609 bracket_inserted = true;
3610 continue;
3611 }
3612 }
3613
3614 if let Some(region) = autoclose_region {
3615 // If the selection is followed by an auto-inserted closing bracket,
3616 // then don't insert that closing bracket again; just move the selection
3617 // past the closing bracket.
3618 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3619 && text.as_ref() == region.pair.end.as_str();
3620 if should_skip {
3621 let anchor = snapshot.anchor_after(selection.end);
3622 new_selections
3623 .push((selection.map(|_| anchor), region.pair.end.len()));
3624 continue;
3625 }
3626 }
3627
3628 let always_treat_brackets_as_autoclosed = snapshot
3629 .language_settings_at(selection.start, cx)
3630 .always_treat_brackets_as_autoclosed;
3631 if always_treat_brackets_as_autoclosed
3632 && is_bracket_pair_end
3633 && snapshot.contains_str_at(selection.end, text.as_ref())
3634 {
3635 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3636 // and the inserted text is a closing bracket and the selection is followed
3637 // by the closing bracket then move the selection past the closing bracket.
3638 let anchor = snapshot.anchor_after(selection.end);
3639 new_selections.push((selection.map(|_| anchor), text.len()));
3640 continue;
3641 }
3642 }
3643 // If an opening bracket is 1 character long and is typed while
3644 // text is selected, then surround that text with the bracket pair.
3645 else if auto_surround
3646 && bracket_pair.surround
3647 && is_bracket_pair_start
3648 && bracket_pair.start.chars().count() == 1
3649 {
3650 edits.push((selection.start..selection.start, text.clone()));
3651 edits.push((
3652 selection.end..selection.end,
3653 bracket_pair.end.as_str().into(),
3654 ));
3655 bracket_inserted = true;
3656 new_selections.push((
3657 Selection {
3658 id: selection.id,
3659 start: snapshot.anchor_after(selection.start),
3660 end: snapshot.anchor_before(selection.end),
3661 reversed: selection.reversed,
3662 goal: selection.goal,
3663 },
3664 0,
3665 ));
3666 continue;
3667 }
3668 }
3669 }
3670
3671 if self.auto_replace_emoji_shortcode
3672 && selection.is_empty()
3673 && text.as_ref().ends_with(':')
3674 {
3675 if let Some(possible_emoji_short_code) =
3676 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3677 {
3678 if !possible_emoji_short_code.is_empty() {
3679 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3680 let emoji_shortcode_start = Point::new(
3681 selection.start.row,
3682 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3683 );
3684
3685 // Remove shortcode from buffer
3686 edits.push((
3687 emoji_shortcode_start..selection.start,
3688 "".to_string().into(),
3689 ));
3690 new_selections.push((
3691 Selection {
3692 id: selection.id,
3693 start: snapshot.anchor_after(emoji_shortcode_start),
3694 end: snapshot.anchor_before(selection.start),
3695 reversed: selection.reversed,
3696 goal: selection.goal,
3697 },
3698 0,
3699 ));
3700
3701 // Insert emoji
3702 let selection_start_anchor = snapshot.anchor_after(selection.start);
3703 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3704 edits.push((selection.start..selection.end, emoji.to_string().into()));
3705
3706 continue;
3707 }
3708 }
3709 }
3710 }
3711
3712 // If not handling any auto-close operation, then just replace the selected
3713 // text with the given input and move the selection to the end of the
3714 // newly inserted text.
3715 let anchor = snapshot.anchor_after(selection.end);
3716 if !self.linked_edit_ranges.is_empty() {
3717 let start_anchor = snapshot.anchor_before(selection.start);
3718
3719 let is_word_char = text.chars().next().map_or(true, |char| {
3720 let classifier = snapshot
3721 .char_classifier_at(start_anchor.to_offset(&snapshot))
3722 .ignore_punctuation(true);
3723 classifier.is_word(char)
3724 });
3725
3726 if is_word_char {
3727 if let Some(ranges) = self
3728 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3729 {
3730 for (buffer, edits) in ranges {
3731 linked_edits
3732 .entry(buffer.clone())
3733 .or_default()
3734 .extend(edits.into_iter().map(|range| (range, text.clone())));
3735 }
3736 }
3737 } else {
3738 clear_linked_edit_ranges = true;
3739 }
3740 }
3741
3742 new_selections.push((selection.map(|_| anchor), 0));
3743 edits.push((selection.start..selection.end, text.clone()));
3744 }
3745
3746 drop(snapshot);
3747
3748 self.transact(window, cx, |this, window, cx| {
3749 if clear_linked_edit_ranges {
3750 this.linked_edit_ranges.clear();
3751 }
3752 let initial_buffer_versions =
3753 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3754
3755 this.buffer.update(cx, |buffer, cx| {
3756 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3757 });
3758 for (buffer, edits) in linked_edits {
3759 buffer.update(cx, |buffer, cx| {
3760 let snapshot = buffer.snapshot();
3761 let edits = edits
3762 .into_iter()
3763 .map(|(range, text)| {
3764 use text::ToPoint as TP;
3765 let end_point = TP::to_point(&range.end, &snapshot);
3766 let start_point = TP::to_point(&range.start, &snapshot);
3767 (start_point..end_point, text)
3768 })
3769 .sorted_by_key(|(range, _)| range.start);
3770 buffer.edit(edits, None, cx);
3771 })
3772 }
3773 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3774 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3775 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3776 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3777 .zip(new_selection_deltas)
3778 .map(|(selection, delta)| Selection {
3779 id: selection.id,
3780 start: selection.start + delta,
3781 end: selection.end + delta,
3782 reversed: selection.reversed,
3783 goal: SelectionGoal::None,
3784 })
3785 .collect::<Vec<_>>();
3786
3787 let mut i = 0;
3788 for (position, delta, selection_id, pair) in new_autoclose_regions {
3789 let position = position.to_offset(&map.buffer_snapshot) + delta;
3790 let start = map.buffer_snapshot.anchor_before(position);
3791 let end = map.buffer_snapshot.anchor_after(position);
3792 while let Some(existing_state) = this.autoclose_regions.get(i) {
3793 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3794 Ordering::Less => i += 1,
3795 Ordering::Greater => break,
3796 Ordering::Equal => {
3797 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3798 Ordering::Less => i += 1,
3799 Ordering::Equal => break,
3800 Ordering::Greater => break,
3801 }
3802 }
3803 }
3804 }
3805 this.autoclose_regions.insert(
3806 i,
3807 AutocloseRegion {
3808 selection_id,
3809 range: start..end,
3810 pair,
3811 },
3812 );
3813 }
3814
3815 let had_active_inline_completion = this.has_active_inline_completion();
3816 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3817 s.select(new_selections)
3818 });
3819
3820 if !bracket_inserted {
3821 if let Some(on_type_format_task) =
3822 this.trigger_on_type_formatting(text.to_string(), window, cx)
3823 {
3824 on_type_format_task.detach_and_log_err(cx);
3825 }
3826 }
3827
3828 let editor_settings = EditorSettings::get_global(cx);
3829 if bracket_inserted
3830 && (editor_settings.auto_signature_help
3831 || editor_settings.show_signature_help_after_edits)
3832 {
3833 this.show_signature_help(&ShowSignatureHelp, window, cx);
3834 }
3835
3836 let trigger_in_words =
3837 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3838 if this.hard_wrap.is_some() {
3839 let latest: Range<Point> = this.selections.newest(cx).range();
3840 if latest.is_empty()
3841 && this
3842 .buffer()
3843 .read(cx)
3844 .snapshot(cx)
3845 .line_len(MultiBufferRow(latest.start.row))
3846 == latest.start.column
3847 {
3848 this.rewrap_impl(
3849 RewrapOptions {
3850 override_language_settings: true,
3851 preserve_existing_whitespace: true,
3852 },
3853 cx,
3854 )
3855 }
3856 }
3857 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3858 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3859 this.refresh_inline_completion(true, false, window, cx);
3860 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3861 });
3862 }
3863
3864 fn find_possible_emoji_shortcode_at_position(
3865 snapshot: &MultiBufferSnapshot,
3866 position: Point,
3867 ) -> Option<String> {
3868 let mut chars = Vec::new();
3869 let mut found_colon = false;
3870 for char in snapshot.reversed_chars_at(position).take(100) {
3871 // Found a possible emoji shortcode in the middle of the buffer
3872 if found_colon {
3873 if char.is_whitespace() {
3874 chars.reverse();
3875 return Some(chars.iter().collect());
3876 }
3877 // If the previous character is not a whitespace, we are in the middle of a word
3878 // and we only want to complete the shortcode if the word is made up of other emojis
3879 let mut containing_word = String::new();
3880 for ch in snapshot
3881 .reversed_chars_at(position)
3882 .skip(chars.len() + 1)
3883 .take(100)
3884 {
3885 if ch.is_whitespace() {
3886 break;
3887 }
3888 containing_word.push(ch);
3889 }
3890 let containing_word = containing_word.chars().rev().collect::<String>();
3891 if util::word_consists_of_emojis(containing_word.as_str()) {
3892 chars.reverse();
3893 return Some(chars.iter().collect());
3894 }
3895 }
3896
3897 if char.is_whitespace() || !char.is_ascii() {
3898 return None;
3899 }
3900 if char == ':' {
3901 found_colon = true;
3902 } else {
3903 chars.push(char);
3904 }
3905 }
3906 // Found a possible emoji shortcode at the beginning of the buffer
3907 chars.reverse();
3908 Some(chars.iter().collect())
3909 }
3910
3911 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3912 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3913 self.transact(window, cx, |this, window, cx| {
3914 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
3915 let selections = this.selections.all::<usize>(cx);
3916 let multi_buffer = this.buffer.read(cx);
3917 let buffer = multi_buffer.snapshot(cx);
3918 selections
3919 .iter()
3920 .map(|selection| {
3921 let start_point = selection.start.to_point(&buffer);
3922 let mut indent =
3923 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3924 indent.len = cmp::min(indent.len, start_point.column);
3925 let start = selection.start;
3926 let end = selection.end;
3927 let selection_is_empty = start == end;
3928 let language_scope = buffer.language_scope_at(start);
3929 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
3930 &language_scope
3931 {
3932 let insert_extra_newline =
3933 insert_extra_newline_brackets(&buffer, start..end, language)
3934 || insert_extra_newline_tree_sitter(&buffer, start..end);
3935
3936 // Comment extension on newline is allowed only for cursor selections
3937 let comment_delimiter = maybe!({
3938 if !selection_is_empty {
3939 return None;
3940 }
3941
3942 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3943 return None;
3944 }
3945
3946 let delimiters = language.line_comment_prefixes();
3947 let max_len_of_delimiter =
3948 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3949 let (snapshot, range) =
3950 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3951
3952 let mut index_of_first_non_whitespace = 0;
3953 let comment_candidate = snapshot
3954 .chars_for_range(range)
3955 .skip_while(|c| {
3956 let should_skip = c.is_whitespace();
3957 if should_skip {
3958 index_of_first_non_whitespace += 1;
3959 }
3960 should_skip
3961 })
3962 .take(max_len_of_delimiter)
3963 .collect::<String>();
3964 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3965 comment_candidate.starts_with(comment_prefix.as_ref())
3966 })?;
3967 let cursor_is_placed_after_comment_marker =
3968 index_of_first_non_whitespace + comment_prefix.len()
3969 <= start_point.column as usize;
3970 if cursor_is_placed_after_comment_marker {
3971 Some(comment_prefix.clone())
3972 } else {
3973 None
3974 }
3975 });
3976 (comment_delimiter, insert_extra_newline)
3977 } else {
3978 (None, false)
3979 };
3980
3981 let capacity_for_delimiter = comment_delimiter
3982 .as_deref()
3983 .map(str::len)
3984 .unwrap_or_default();
3985 let mut new_text =
3986 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3987 new_text.push('\n');
3988 new_text.extend(indent.chars());
3989 if let Some(delimiter) = &comment_delimiter {
3990 new_text.push_str(delimiter);
3991 }
3992 if insert_extra_newline {
3993 new_text = new_text.repeat(2);
3994 }
3995
3996 let anchor = buffer.anchor_after(end);
3997 let new_selection = selection.map(|_| anchor);
3998 (
3999 (start..end, new_text),
4000 (insert_extra_newline, new_selection),
4001 )
4002 })
4003 .unzip()
4004 };
4005
4006 this.edit_with_autoindent(edits, cx);
4007 let buffer = this.buffer.read(cx).snapshot(cx);
4008 let new_selections = selection_fixup_info
4009 .into_iter()
4010 .map(|(extra_newline_inserted, new_selection)| {
4011 let mut cursor = new_selection.end.to_point(&buffer);
4012 if extra_newline_inserted {
4013 cursor.row -= 1;
4014 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4015 }
4016 new_selection.map(|_| cursor)
4017 })
4018 .collect();
4019
4020 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4021 s.select(new_selections)
4022 });
4023 this.refresh_inline_completion(true, false, window, cx);
4024 });
4025 }
4026
4027 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4028 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4029
4030 let buffer = self.buffer.read(cx);
4031 let snapshot = buffer.snapshot(cx);
4032
4033 let mut edits = Vec::new();
4034 let mut rows = Vec::new();
4035
4036 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4037 let cursor = selection.head();
4038 let row = cursor.row;
4039
4040 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4041
4042 let newline = "\n".to_string();
4043 edits.push((start_of_line..start_of_line, newline));
4044
4045 rows.push(row + rows_inserted as u32);
4046 }
4047
4048 self.transact(window, cx, |editor, window, cx| {
4049 editor.edit(edits, cx);
4050
4051 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4052 let mut index = 0;
4053 s.move_cursors_with(|map, _, _| {
4054 let row = rows[index];
4055 index += 1;
4056
4057 let point = Point::new(row, 0);
4058 let boundary = map.next_line_boundary(point).1;
4059 let clipped = map.clip_point(boundary, Bias::Left);
4060
4061 (clipped, SelectionGoal::None)
4062 });
4063 });
4064
4065 let mut indent_edits = Vec::new();
4066 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4067 for row in rows {
4068 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4069 for (row, indent) in indents {
4070 if indent.len == 0 {
4071 continue;
4072 }
4073
4074 let text = match indent.kind {
4075 IndentKind::Space => " ".repeat(indent.len as usize),
4076 IndentKind::Tab => "\t".repeat(indent.len as usize),
4077 };
4078 let point = Point::new(row.0, 0);
4079 indent_edits.push((point..point, text));
4080 }
4081 }
4082 editor.edit(indent_edits, cx);
4083 });
4084 }
4085
4086 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4087 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4088
4089 let buffer = self.buffer.read(cx);
4090 let snapshot = buffer.snapshot(cx);
4091
4092 let mut edits = Vec::new();
4093 let mut rows = Vec::new();
4094 let mut rows_inserted = 0;
4095
4096 for selection in self.selections.all_adjusted(cx) {
4097 let cursor = selection.head();
4098 let row = cursor.row;
4099
4100 let point = Point::new(row + 1, 0);
4101 let start_of_line = snapshot.clip_point(point, Bias::Left);
4102
4103 let newline = "\n".to_string();
4104 edits.push((start_of_line..start_of_line, newline));
4105
4106 rows_inserted += 1;
4107 rows.push(row + rows_inserted);
4108 }
4109
4110 self.transact(window, cx, |editor, window, cx| {
4111 editor.edit(edits, cx);
4112
4113 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4114 let mut index = 0;
4115 s.move_cursors_with(|map, _, _| {
4116 let row = rows[index];
4117 index += 1;
4118
4119 let point = Point::new(row, 0);
4120 let boundary = map.next_line_boundary(point).1;
4121 let clipped = map.clip_point(boundary, Bias::Left);
4122
4123 (clipped, SelectionGoal::None)
4124 });
4125 });
4126
4127 let mut indent_edits = Vec::new();
4128 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4129 for row in rows {
4130 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4131 for (row, indent) in indents {
4132 if indent.len == 0 {
4133 continue;
4134 }
4135
4136 let text = match indent.kind {
4137 IndentKind::Space => " ".repeat(indent.len as usize),
4138 IndentKind::Tab => "\t".repeat(indent.len as usize),
4139 };
4140 let point = Point::new(row.0, 0);
4141 indent_edits.push((point..point, text));
4142 }
4143 }
4144 editor.edit(indent_edits, cx);
4145 });
4146 }
4147
4148 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4149 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4150 original_indent_columns: Vec::new(),
4151 });
4152 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4153 }
4154
4155 fn insert_with_autoindent_mode(
4156 &mut self,
4157 text: &str,
4158 autoindent_mode: Option<AutoindentMode>,
4159 window: &mut Window,
4160 cx: &mut Context<Self>,
4161 ) {
4162 if self.read_only(cx) {
4163 return;
4164 }
4165
4166 let text: Arc<str> = text.into();
4167 self.transact(window, cx, |this, window, cx| {
4168 let old_selections = this.selections.all_adjusted(cx);
4169 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4170 let anchors = {
4171 let snapshot = buffer.read(cx);
4172 old_selections
4173 .iter()
4174 .map(|s| {
4175 let anchor = snapshot.anchor_after(s.head());
4176 s.map(|_| anchor)
4177 })
4178 .collect::<Vec<_>>()
4179 };
4180 buffer.edit(
4181 old_selections
4182 .iter()
4183 .map(|s| (s.start..s.end, text.clone())),
4184 autoindent_mode,
4185 cx,
4186 );
4187 anchors
4188 });
4189
4190 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4191 s.select_anchors(selection_anchors);
4192 });
4193
4194 cx.notify();
4195 });
4196 }
4197
4198 fn trigger_completion_on_input(
4199 &mut self,
4200 text: &str,
4201 trigger_in_words: bool,
4202 window: &mut Window,
4203 cx: &mut Context<Self>,
4204 ) {
4205 let ignore_completion_provider = self
4206 .context_menu
4207 .borrow()
4208 .as_ref()
4209 .map(|menu| match menu {
4210 CodeContextMenu::Completions(completions_menu) => {
4211 completions_menu.ignore_completion_provider
4212 }
4213 CodeContextMenu::CodeActions(_) => false,
4214 })
4215 .unwrap_or(false);
4216
4217 if ignore_completion_provider {
4218 self.show_word_completions(&ShowWordCompletions, window, cx);
4219 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
4220 self.show_completions(
4221 &ShowCompletions {
4222 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4223 },
4224 window,
4225 cx,
4226 );
4227 } else {
4228 self.hide_context_menu(window, cx);
4229 }
4230 }
4231
4232 fn is_completion_trigger(
4233 &self,
4234 text: &str,
4235 trigger_in_words: bool,
4236 cx: &mut Context<Self>,
4237 ) -> bool {
4238 let position = self.selections.newest_anchor().head();
4239 let multibuffer = self.buffer.read(cx);
4240 let Some(buffer) = position
4241 .buffer_id
4242 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4243 else {
4244 return false;
4245 };
4246
4247 if let Some(completion_provider) = &self.completion_provider {
4248 completion_provider.is_completion_trigger(
4249 &buffer,
4250 position.text_anchor,
4251 text,
4252 trigger_in_words,
4253 cx,
4254 )
4255 } else {
4256 false
4257 }
4258 }
4259
4260 /// If any empty selections is touching the start of its innermost containing autoclose
4261 /// region, expand it to select the brackets.
4262 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4263 let selections = self.selections.all::<usize>(cx);
4264 let buffer = self.buffer.read(cx).read(cx);
4265 let new_selections = self
4266 .selections_with_autoclose_regions(selections, &buffer)
4267 .map(|(mut selection, region)| {
4268 if !selection.is_empty() {
4269 return selection;
4270 }
4271
4272 if let Some(region) = region {
4273 let mut range = region.range.to_offset(&buffer);
4274 if selection.start == range.start && range.start >= region.pair.start.len() {
4275 range.start -= region.pair.start.len();
4276 if buffer.contains_str_at(range.start, ®ion.pair.start)
4277 && buffer.contains_str_at(range.end, ®ion.pair.end)
4278 {
4279 range.end += region.pair.end.len();
4280 selection.start = range.start;
4281 selection.end = range.end;
4282
4283 return selection;
4284 }
4285 }
4286 }
4287
4288 let always_treat_brackets_as_autoclosed = buffer
4289 .language_settings_at(selection.start, cx)
4290 .always_treat_brackets_as_autoclosed;
4291
4292 if !always_treat_brackets_as_autoclosed {
4293 return selection;
4294 }
4295
4296 if let Some(scope) = buffer.language_scope_at(selection.start) {
4297 for (pair, enabled) in scope.brackets() {
4298 if !enabled || !pair.close {
4299 continue;
4300 }
4301
4302 if buffer.contains_str_at(selection.start, &pair.end) {
4303 let pair_start_len = pair.start.len();
4304 if buffer.contains_str_at(
4305 selection.start.saturating_sub(pair_start_len),
4306 &pair.start,
4307 ) {
4308 selection.start -= pair_start_len;
4309 selection.end += pair.end.len();
4310
4311 return selection;
4312 }
4313 }
4314 }
4315 }
4316
4317 selection
4318 })
4319 .collect();
4320
4321 drop(buffer);
4322 self.change_selections(None, window, cx, |selections| {
4323 selections.select(new_selections)
4324 });
4325 }
4326
4327 /// Iterate the given selections, and for each one, find the smallest surrounding
4328 /// autoclose region. This uses the ordering of the selections and the autoclose
4329 /// regions to avoid repeated comparisons.
4330 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4331 &'a self,
4332 selections: impl IntoIterator<Item = Selection<D>>,
4333 buffer: &'a MultiBufferSnapshot,
4334 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4335 let mut i = 0;
4336 let mut regions = self.autoclose_regions.as_slice();
4337 selections.into_iter().map(move |selection| {
4338 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4339
4340 let mut enclosing = None;
4341 while let Some(pair_state) = regions.get(i) {
4342 if pair_state.range.end.to_offset(buffer) < range.start {
4343 regions = ®ions[i + 1..];
4344 i = 0;
4345 } else if pair_state.range.start.to_offset(buffer) > range.end {
4346 break;
4347 } else {
4348 if pair_state.selection_id == selection.id {
4349 enclosing = Some(pair_state);
4350 }
4351 i += 1;
4352 }
4353 }
4354
4355 (selection, enclosing)
4356 })
4357 }
4358
4359 /// Remove any autoclose regions that no longer contain their selection.
4360 fn invalidate_autoclose_regions(
4361 &mut self,
4362 mut selections: &[Selection<Anchor>],
4363 buffer: &MultiBufferSnapshot,
4364 ) {
4365 self.autoclose_regions.retain(|state| {
4366 let mut i = 0;
4367 while let Some(selection) = selections.get(i) {
4368 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4369 selections = &selections[1..];
4370 continue;
4371 }
4372 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4373 break;
4374 }
4375 if selection.id == state.selection_id {
4376 return true;
4377 } else {
4378 i += 1;
4379 }
4380 }
4381 false
4382 });
4383 }
4384
4385 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4386 let offset = position.to_offset(buffer);
4387 let (word_range, kind) = buffer.surrounding_word(offset, true);
4388 if offset > word_range.start && kind == Some(CharKind::Word) {
4389 Some(
4390 buffer
4391 .text_for_range(word_range.start..offset)
4392 .collect::<String>(),
4393 )
4394 } else {
4395 None
4396 }
4397 }
4398
4399 pub fn toggle_inline_values(
4400 &mut self,
4401 _: &ToggleInlineValues,
4402 _: &mut Window,
4403 cx: &mut Context<Self>,
4404 ) {
4405 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4406
4407 self.refresh_inline_values(cx);
4408 }
4409
4410 pub fn toggle_inlay_hints(
4411 &mut self,
4412 _: &ToggleInlayHints,
4413 _: &mut Window,
4414 cx: &mut Context<Self>,
4415 ) {
4416 self.refresh_inlay_hints(
4417 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4418 cx,
4419 );
4420 }
4421
4422 pub fn inlay_hints_enabled(&self) -> bool {
4423 self.inlay_hint_cache.enabled
4424 }
4425
4426 pub fn inline_values_enabled(&self) -> bool {
4427 self.inline_value_cache.enabled
4428 }
4429
4430 #[cfg(any(test, feature = "test-support"))]
4431 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4432 self.display_map
4433 .read(cx)
4434 .current_inlays()
4435 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4436 .cloned()
4437 .collect()
4438 }
4439
4440 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4441 if self.semantics_provider.is_none() || !self.mode.is_full() {
4442 return;
4443 }
4444
4445 let reason_description = reason.description();
4446 let ignore_debounce = matches!(
4447 reason,
4448 InlayHintRefreshReason::SettingsChange(_)
4449 | InlayHintRefreshReason::Toggle(_)
4450 | InlayHintRefreshReason::ExcerptsRemoved(_)
4451 | InlayHintRefreshReason::ModifiersChanged(_)
4452 );
4453 let (invalidate_cache, required_languages) = match reason {
4454 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4455 match self.inlay_hint_cache.modifiers_override(enabled) {
4456 Some(enabled) => {
4457 if enabled {
4458 (InvalidationStrategy::RefreshRequested, None)
4459 } else {
4460 self.splice_inlays(
4461 &self
4462 .visible_inlay_hints(cx)
4463 .iter()
4464 .map(|inlay| inlay.id)
4465 .collect::<Vec<InlayId>>(),
4466 Vec::new(),
4467 cx,
4468 );
4469 return;
4470 }
4471 }
4472 None => return,
4473 }
4474 }
4475 InlayHintRefreshReason::Toggle(enabled) => {
4476 if self.inlay_hint_cache.toggle(enabled) {
4477 if enabled {
4478 (InvalidationStrategy::RefreshRequested, None)
4479 } else {
4480 self.splice_inlays(
4481 &self
4482 .visible_inlay_hints(cx)
4483 .iter()
4484 .map(|inlay| inlay.id)
4485 .collect::<Vec<InlayId>>(),
4486 Vec::new(),
4487 cx,
4488 );
4489 return;
4490 }
4491 } else {
4492 return;
4493 }
4494 }
4495 InlayHintRefreshReason::SettingsChange(new_settings) => {
4496 match self.inlay_hint_cache.update_settings(
4497 &self.buffer,
4498 new_settings,
4499 self.visible_inlay_hints(cx),
4500 cx,
4501 ) {
4502 ControlFlow::Break(Some(InlaySplice {
4503 to_remove,
4504 to_insert,
4505 })) => {
4506 self.splice_inlays(&to_remove, to_insert, cx);
4507 return;
4508 }
4509 ControlFlow::Break(None) => return,
4510 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4511 }
4512 }
4513 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4514 if let Some(InlaySplice {
4515 to_remove,
4516 to_insert,
4517 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4518 {
4519 self.splice_inlays(&to_remove, to_insert, cx);
4520 }
4521 self.display_map.update(cx, |display_map, _| {
4522 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4523 });
4524 return;
4525 }
4526 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4527 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4528 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4529 }
4530 InlayHintRefreshReason::RefreshRequested => {
4531 (InvalidationStrategy::RefreshRequested, None)
4532 }
4533 };
4534
4535 if let Some(InlaySplice {
4536 to_remove,
4537 to_insert,
4538 }) = self.inlay_hint_cache.spawn_hint_refresh(
4539 reason_description,
4540 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4541 invalidate_cache,
4542 ignore_debounce,
4543 cx,
4544 ) {
4545 self.splice_inlays(&to_remove, to_insert, cx);
4546 }
4547 }
4548
4549 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4550 self.display_map
4551 .read(cx)
4552 .current_inlays()
4553 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4554 .cloned()
4555 .collect()
4556 }
4557
4558 pub fn excerpts_for_inlay_hints_query(
4559 &self,
4560 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4561 cx: &mut Context<Editor>,
4562 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4563 let Some(project) = self.project.as_ref() else {
4564 return HashMap::default();
4565 };
4566 let project = project.read(cx);
4567 let multi_buffer = self.buffer().read(cx);
4568 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4569 let multi_buffer_visible_start = self
4570 .scroll_manager
4571 .anchor()
4572 .anchor
4573 .to_point(&multi_buffer_snapshot);
4574 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4575 multi_buffer_visible_start
4576 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4577 Bias::Left,
4578 );
4579 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4580 multi_buffer_snapshot
4581 .range_to_buffer_ranges(multi_buffer_visible_range)
4582 .into_iter()
4583 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4584 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4585 let buffer_file = project::File::from_dyn(buffer.file())?;
4586 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4587 let worktree_entry = buffer_worktree
4588 .read(cx)
4589 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4590 if worktree_entry.is_ignored {
4591 return None;
4592 }
4593
4594 let language = buffer.language()?;
4595 if let Some(restrict_to_languages) = restrict_to_languages {
4596 if !restrict_to_languages.contains(language) {
4597 return None;
4598 }
4599 }
4600 Some((
4601 excerpt_id,
4602 (
4603 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4604 buffer.version().clone(),
4605 excerpt_visible_range,
4606 ),
4607 ))
4608 })
4609 .collect()
4610 }
4611
4612 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4613 TextLayoutDetails {
4614 text_system: window.text_system().clone(),
4615 editor_style: self.style.clone().unwrap(),
4616 rem_size: window.rem_size(),
4617 scroll_anchor: self.scroll_manager.anchor(),
4618 visible_rows: self.visible_line_count(),
4619 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4620 }
4621 }
4622
4623 pub fn splice_inlays(
4624 &self,
4625 to_remove: &[InlayId],
4626 to_insert: Vec<Inlay>,
4627 cx: &mut Context<Self>,
4628 ) {
4629 self.display_map.update(cx, |display_map, cx| {
4630 display_map.splice_inlays(to_remove, to_insert, cx)
4631 });
4632 cx.notify();
4633 }
4634
4635 fn trigger_on_type_formatting(
4636 &self,
4637 input: String,
4638 window: &mut Window,
4639 cx: &mut Context<Self>,
4640 ) -> Option<Task<Result<()>>> {
4641 if input.len() != 1 {
4642 return None;
4643 }
4644
4645 let project = self.project.as_ref()?;
4646 let position = self.selections.newest_anchor().head();
4647 let (buffer, buffer_position) = self
4648 .buffer
4649 .read(cx)
4650 .text_anchor_for_position(position, cx)?;
4651
4652 let settings = language_settings::language_settings(
4653 buffer
4654 .read(cx)
4655 .language_at(buffer_position)
4656 .map(|l| l.name()),
4657 buffer.read(cx).file(),
4658 cx,
4659 );
4660 if !settings.use_on_type_format {
4661 return None;
4662 }
4663
4664 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4665 // hence we do LSP request & edit on host side only — add formats to host's history.
4666 let push_to_lsp_host_history = true;
4667 // If this is not the host, append its history with new edits.
4668 let push_to_client_history = project.read(cx).is_via_collab();
4669
4670 let on_type_formatting = project.update(cx, |project, cx| {
4671 project.on_type_format(
4672 buffer.clone(),
4673 buffer_position,
4674 input,
4675 push_to_lsp_host_history,
4676 cx,
4677 )
4678 });
4679 Some(cx.spawn_in(window, async move |editor, cx| {
4680 if let Some(transaction) = on_type_formatting.await? {
4681 if push_to_client_history {
4682 buffer
4683 .update(cx, |buffer, _| {
4684 buffer.push_transaction(transaction, Instant::now());
4685 buffer.finalize_last_transaction();
4686 })
4687 .ok();
4688 }
4689 editor.update(cx, |editor, cx| {
4690 editor.refresh_document_highlights(cx);
4691 })?;
4692 }
4693 Ok(())
4694 }))
4695 }
4696
4697 pub fn show_word_completions(
4698 &mut self,
4699 _: &ShowWordCompletions,
4700 window: &mut Window,
4701 cx: &mut Context<Self>,
4702 ) {
4703 self.open_completions_menu(true, None, window, cx);
4704 }
4705
4706 pub fn show_completions(
4707 &mut self,
4708 options: &ShowCompletions,
4709 window: &mut Window,
4710 cx: &mut Context<Self>,
4711 ) {
4712 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4713 }
4714
4715 fn open_completions_menu(
4716 &mut self,
4717 ignore_completion_provider: bool,
4718 trigger: Option<&str>,
4719 window: &mut Window,
4720 cx: &mut Context<Self>,
4721 ) {
4722 if self.pending_rename.is_some() {
4723 return;
4724 }
4725 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4726 return;
4727 }
4728
4729 let position = self.selections.newest_anchor().head();
4730 if position.diff_base_anchor.is_some() {
4731 return;
4732 }
4733 let (buffer, buffer_position) =
4734 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4735 output
4736 } else {
4737 return;
4738 };
4739 let buffer_snapshot = buffer.read(cx).snapshot();
4740 let show_completion_documentation = buffer_snapshot
4741 .settings_at(buffer_position, cx)
4742 .show_completion_documentation;
4743
4744 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4745
4746 let trigger_kind = match trigger {
4747 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4748 CompletionTriggerKind::TRIGGER_CHARACTER
4749 }
4750 _ => CompletionTriggerKind::INVOKED,
4751 };
4752 let completion_context = CompletionContext {
4753 trigger_character: trigger.and_then(|trigger| {
4754 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4755 Some(String::from(trigger))
4756 } else {
4757 None
4758 }
4759 }),
4760 trigger_kind,
4761 };
4762
4763 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4764 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4765 let word_to_exclude = buffer_snapshot
4766 .text_for_range(old_range.clone())
4767 .collect::<String>();
4768 (
4769 buffer_snapshot.anchor_before(old_range.start)
4770 ..buffer_snapshot.anchor_after(old_range.end),
4771 Some(word_to_exclude),
4772 )
4773 } else {
4774 (buffer_position..buffer_position, None)
4775 };
4776
4777 let completion_settings = language_settings(
4778 buffer_snapshot
4779 .language_at(buffer_position)
4780 .map(|language| language.name()),
4781 buffer_snapshot.file(),
4782 cx,
4783 )
4784 .completions;
4785
4786 // The document can be large, so stay in reasonable bounds when searching for words,
4787 // otherwise completion pop-up might be slow to appear.
4788 const WORD_LOOKUP_ROWS: u32 = 5_000;
4789 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4790 let min_word_search = buffer_snapshot.clip_point(
4791 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4792 Bias::Left,
4793 );
4794 let max_word_search = buffer_snapshot.clip_point(
4795 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4796 Bias::Right,
4797 );
4798 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4799 ..buffer_snapshot.point_to_offset(max_word_search);
4800
4801 let provider = self
4802 .completion_provider
4803 .as_ref()
4804 .filter(|_| !ignore_completion_provider);
4805 let skip_digits = query
4806 .as_ref()
4807 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4808
4809 let (mut words, provided_completions) = match provider {
4810 Some(provider) => {
4811 let completions = provider.completions(
4812 position.excerpt_id,
4813 &buffer,
4814 buffer_position,
4815 completion_context,
4816 window,
4817 cx,
4818 );
4819
4820 let words = match completion_settings.words {
4821 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4822 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4823 .background_spawn(async move {
4824 buffer_snapshot.words_in_range(WordsQuery {
4825 fuzzy_contents: None,
4826 range: word_search_range,
4827 skip_digits,
4828 })
4829 }),
4830 };
4831
4832 (words, completions)
4833 }
4834 None => (
4835 cx.background_spawn(async move {
4836 buffer_snapshot.words_in_range(WordsQuery {
4837 fuzzy_contents: None,
4838 range: word_search_range,
4839 skip_digits,
4840 })
4841 }),
4842 Task::ready(Ok(None)),
4843 ),
4844 };
4845
4846 let sort_completions = provider
4847 .as_ref()
4848 .map_or(false, |provider| provider.sort_completions());
4849
4850 let filter_completions = provider
4851 .as_ref()
4852 .map_or(true, |provider| provider.filter_completions());
4853
4854 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
4855
4856 let id = post_inc(&mut self.next_completion_id);
4857 let task = cx.spawn_in(window, async move |editor, cx| {
4858 async move {
4859 editor.update(cx, |this, _| {
4860 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4861 })?;
4862
4863 let mut completions = Vec::new();
4864 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
4865 completions.extend(provided_completions);
4866 if completion_settings.words == WordsCompletionMode::Fallback {
4867 words = Task::ready(BTreeMap::default());
4868 }
4869 }
4870
4871 let mut words = words.await;
4872 if let Some(word_to_exclude) = &word_to_exclude {
4873 words.remove(word_to_exclude);
4874 }
4875 for lsp_completion in &completions {
4876 words.remove(&lsp_completion.new_text);
4877 }
4878 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
4879 replace_range: old_range.clone(),
4880 new_text: word.clone(),
4881 label: CodeLabel::plain(word, None),
4882 icon_path: None,
4883 documentation: None,
4884 source: CompletionSource::BufferWord {
4885 word_range,
4886 resolved: false,
4887 },
4888 insert_text_mode: Some(InsertTextMode::AS_IS),
4889 confirm: None,
4890 }));
4891
4892 let menu = if completions.is_empty() {
4893 None
4894 } else {
4895 let mut menu = CompletionsMenu::new(
4896 id,
4897 sort_completions,
4898 show_completion_documentation,
4899 ignore_completion_provider,
4900 position,
4901 buffer.clone(),
4902 completions.into(),
4903 snippet_sort_order,
4904 );
4905
4906 menu.filter(
4907 if filter_completions {
4908 query.as_deref()
4909 } else {
4910 None
4911 },
4912 cx.background_executor().clone(),
4913 )
4914 .await;
4915
4916 menu.visible().then_some(menu)
4917 };
4918
4919 editor.update_in(cx, |editor, window, cx| {
4920 match editor.context_menu.borrow().as_ref() {
4921 None => {}
4922 Some(CodeContextMenu::Completions(prev_menu)) => {
4923 if prev_menu.id > id {
4924 return;
4925 }
4926 }
4927 _ => return,
4928 }
4929
4930 if editor.focus_handle.is_focused(window) && menu.is_some() {
4931 let mut menu = menu.unwrap();
4932 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
4933
4934 *editor.context_menu.borrow_mut() =
4935 Some(CodeContextMenu::Completions(menu));
4936
4937 if editor.show_edit_predictions_in_menu() {
4938 editor.update_visible_inline_completion(window, cx);
4939 } else {
4940 editor.discard_inline_completion(false, cx);
4941 }
4942
4943 cx.notify();
4944 } else if editor.completion_tasks.len() <= 1 {
4945 // If there are no more completion tasks and the last menu was
4946 // empty, we should hide it.
4947 let was_hidden = editor.hide_context_menu(window, cx).is_none();
4948 // If it was already hidden and we don't show inline
4949 // completions in the menu, we should also show the
4950 // inline-completion when available.
4951 if was_hidden && editor.show_edit_predictions_in_menu() {
4952 editor.update_visible_inline_completion(window, cx);
4953 }
4954 }
4955 })?;
4956
4957 anyhow::Ok(())
4958 }
4959 .log_err()
4960 .await
4961 });
4962
4963 self.completion_tasks.push((id, task));
4964 }
4965
4966 #[cfg(feature = "test-support")]
4967 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
4968 let menu = self.context_menu.borrow();
4969 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
4970 let completions = menu.completions.borrow();
4971 Some(completions.to_vec())
4972 } else {
4973 None
4974 }
4975 }
4976
4977 pub fn confirm_completion(
4978 &mut self,
4979 action: &ConfirmCompletion,
4980 window: &mut Window,
4981 cx: &mut Context<Self>,
4982 ) -> Option<Task<Result<()>>> {
4983 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4984 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
4985 }
4986
4987 pub fn confirm_completion_insert(
4988 &mut self,
4989 _: &ConfirmCompletionInsert,
4990 window: &mut Window,
4991 cx: &mut Context<Self>,
4992 ) -> Option<Task<Result<()>>> {
4993 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4994 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
4995 }
4996
4997 pub fn confirm_completion_replace(
4998 &mut self,
4999 _: &ConfirmCompletionReplace,
5000 window: &mut Window,
5001 cx: &mut Context<Self>,
5002 ) -> Option<Task<Result<()>>> {
5003 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5004 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5005 }
5006
5007 pub fn compose_completion(
5008 &mut self,
5009 action: &ComposeCompletion,
5010 window: &mut Window,
5011 cx: &mut Context<Self>,
5012 ) -> Option<Task<Result<()>>> {
5013 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5014 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5015 }
5016
5017 fn do_completion(
5018 &mut self,
5019 item_ix: Option<usize>,
5020 intent: CompletionIntent,
5021 window: &mut Window,
5022 cx: &mut Context<Editor>,
5023 ) -> Option<Task<Result<()>>> {
5024 use language::ToOffset as _;
5025
5026 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5027 else {
5028 return None;
5029 };
5030
5031 let candidate_id = {
5032 let entries = completions_menu.entries.borrow();
5033 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5034 if self.show_edit_predictions_in_menu() {
5035 self.discard_inline_completion(true, cx);
5036 }
5037 mat.candidate_id
5038 };
5039
5040 let buffer_handle = completions_menu.buffer;
5041 let completion = completions_menu
5042 .completions
5043 .borrow()
5044 .get(candidate_id)?
5045 .clone();
5046 cx.stop_propagation();
5047
5048 let snapshot = self.buffer.read(cx).snapshot(cx);
5049 let newest_anchor = self.selections.newest_anchor();
5050
5051 let snippet;
5052 let new_text;
5053 if completion.is_snippet() {
5054 let mut snippet_source = completion.new_text.clone();
5055 if let Some(scope) = snapshot.language_scope_at(newest_anchor.head()) {
5056 if scope.prefers_label_for_snippet_in_completion() {
5057 if let Some(label) = completion.label() {
5058 if matches!(
5059 completion.kind(),
5060 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
5061 ) {
5062 snippet_source = label;
5063 }
5064 }
5065 }
5066 }
5067 snippet = Some(Snippet::parse(&snippet_source).log_err()?);
5068 new_text = snippet.as_ref().unwrap().text.clone();
5069 } else {
5070 snippet = None;
5071 new_text = completion.new_text.clone();
5072 };
5073
5074 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
5075 let buffer = buffer_handle.read(cx);
5076 let replace_range_multibuffer = {
5077 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5078 let multibuffer_anchor = snapshot
5079 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5080 .unwrap()
5081 ..snapshot
5082 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5083 .unwrap();
5084 multibuffer_anchor.start.to_offset(&snapshot)
5085 ..multibuffer_anchor.end.to_offset(&snapshot)
5086 };
5087 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5088 return None;
5089 }
5090
5091 let old_text = buffer
5092 .text_for_range(replace_range.clone())
5093 .collect::<String>();
5094 let lookbehind = newest_anchor
5095 .start
5096 .text_anchor
5097 .to_offset(buffer)
5098 .saturating_sub(replace_range.start);
5099 let lookahead = replace_range
5100 .end
5101 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5102 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5103 let suffix = &old_text[lookbehind.min(old_text.len())..];
5104
5105 let selections = self.selections.all::<usize>(cx);
5106 let mut ranges = Vec::new();
5107 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5108
5109 for selection in &selections {
5110 let range = if selection.id == newest_anchor.id {
5111 replace_range_multibuffer.clone()
5112 } else {
5113 let mut range = selection.range();
5114
5115 // if prefix is present, don't duplicate it
5116 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5117 range.start = range.start.saturating_sub(lookbehind);
5118
5119 // if suffix is also present, mimic the newest cursor and replace it
5120 if selection.id != newest_anchor.id
5121 && snapshot.contains_str_at(range.end, suffix)
5122 {
5123 range.end += lookahead;
5124 }
5125 }
5126 range
5127 };
5128
5129 ranges.push(range.clone());
5130
5131 if !self.linked_edit_ranges.is_empty() {
5132 let start_anchor = snapshot.anchor_before(range.start);
5133 let end_anchor = snapshot.anchor_after(range.end);
5134 if let Some(ranges) = self
5135 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5136 {
5137 for (buffer, edits) in ranges {
5138 linked_edits
5139 .entry(buffer.clone())
5140 .or_default()
5141 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5142 }
5143 }
5144 }
5145 }
5146
5147 cx.emit(EditorEvent::InputHandled {
5148 utf16_range_to_replace: None,
5149 text: new_text.clone().into(),
5150 });
5151
5152 self.transact(window, cx, |this, window, cx| {
5153 if let Some(mut snippet) = snippet {
5154 snippet.text = new_text.to_string();
5155 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5156 } else {
5157 this.buffer.update(cx, |buffer, cx| {
5158 let auto_indent = match completion.insert_text_mode {
5159 Some(InsertTextMode::AS_IS) => None,
5160 _ => this.autoindent_mode.clone(),
5161 };
5162 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5163 buffer.edit(edits, auto_indent, cx);
5164 });
5165 }
5166 for (buffer, edits) in linked_edits {
5167 buffer.update(cx, |buffer, cx| {
5168 let snapshot = buffer.snapshot();
5169 let edits = edits
5170 .into_iter()
5171 .map(|(range, text)| {
5172 use text::ToPoint as TP;
5173 let end_point = TP::to_point(&range.end, &snapshot);
5174 let start_point = TP::to_point(&range.start, &snapshot);
5175 (start_point..end_point, text)
5176 })
5177 .sorted_by_key(|(range, _)| range.start);
5178 buffer.edit(edits, None, cx);
5179 })
5180 }
5181
5182 this.refresh_inline_completion(true, false, window, cx);
5183 });
5184
5185 let show_new_completions_on_confirm = completion
5186 .confirm
5187 .as_ref()
5188 .map_or(false, |confirm| confirm(intent, window, cx));
5189 if show_new_completions_on_confirm {
5190 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5191 }
5192
5193 let provider = self.completion_provider.as_ref()?;
5194 drop(completion);
5195 let apply_edits = provider.apply_additional_edits_for_completion(
5196 buffer_handle,
5197 completions_menu.completions.clone(),
5198 candidate_id,
5199 true,
5200 cx,
5201 );
5202
5203 let editor_settings = EditorSettings::get_global(cx);
5204 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5205 // After the code completion is finished, users often want to know what signatures are needed.
5206 // so we should automatically call signature_help
5207 self.show_signature_help(&ShowSignatureHelp, window, cx);
5208 }
5209
5210 Some(cx.foreground_executor().spawn(async move {
5211 apply_edits.await?;
5212 Ok(())
5213 }))
5214 }
5215
5216 pub fn toggle_code_actions(
5217 &mut self,
5218 action: &ToggleCodeActions,
5219 window: &mut Window,
5220 cx: &mut Context<Self>,
5221 ) {
5222 let quick_launch = action.quick_launch;
5223 let mut context_menu = self.context_menu.borrow_mut();
5224 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5225 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
5226 // Toggle if we're selecting the same one
5227 *context_menu = None;
5228 cx.notify();
5229 return;
5230 } else {
5231 // Otherwise, clear it and start a new one
5232 *context_menu = None;
5233 cx.notify();
5234 }
5235 }
5236 drop(context_menu);
5237 let snapshot = self.snapshot(window, cx);
5238 let deployed_from_indicator = action.deployed_from_indicator;
5239 let mut task = self.code_actions_task.take();
5240 let action = action.clone();
5241 cx.spawn_in(window, async move |editor, cx| {
5242 while let Some(prev_task) = task {
5243 prev_task.await.log_err();
5244 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5245 }
5246
5247 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5248 if editor.focus_handle.is_focused(window) {
5249 let multibuffer_point = action
5250 .deployed_from_indicator
5251 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
5252 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
5253 let (buffer, buffer_row) = snapshot
5254 .buffer_snapshot
5255 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5256 .and_then(|(buffer_snapshot, range)| {
5257 editor
5258 .buffer
5259 .read(cx)
5260 .buffer(buffer_snapshot.remote_id())
5261 .map(|buffer| (buffer, range.start.row))
5262 })?;
5263 let (_, code_actions) = editor
5264 .available_code_actions
5265 .clone()
5266 .and_then(|(location, code_actions)| {
5267 let snapshot = location.buffer.read(cx).snapshot();
5268 let point_range = location.range.to_point(&snapshot);
5269 let point_range = point_range.start.row..=point_range.end.row;
5270 if point_range.contains(&buffer_row) {
5271 Some((location, code_actions))
5272 } else {
5273 None
5274 }
5275 })
5276 .unzip();
5277 let buffer_id = buffer.read(cx).remote_id();
5278 let tasks = editor
5279 .tasks
5280 .get(&(buffer_id, buffer_row))
5281 .map(|t| Arc::new(t.to_owned()));
5282 if tasks.is_none() && code_actions.is_none() {
5283 return None;
5284 }
5285
5286 editor.completion_tasks.clear();
5287 editor.discard_inline_completion(false, cx);
5288 let task_context =
5289 tasks
5290 .as_ref()
5291 .zip(editor.project.clone())
5292 .map(|(tasks, project)| {
5293 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5294 });
5295
5296 Some(cx.spawn_in(window, async move |editor, cx| {
5297 let task_context = match task_context {
5298 Some(task_context) => task_context.await,
5299 None => None,
5300 };
5301 let resolved_tasks =
5302 tasks
5303 .zip(task_context.clone())
5304 .map(|(tasks, task_context)| ResolvedTasks {
5305 templates: tasks.resolve(&task_context).collect(),
5306 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5307 multibuffer_point.row,
5308 tasks.column,
5309 )),
5310 });
5311 let debug_scenarios = editor.update(cx, |editor, cx| {
5312 if cx.has_flag::<DebuggerFeatureFlag>() {
5313 maybe!({
5314 let project = editor.project.as_ref()?;
5315 let dap_store = project.read(cx).dap_store();
5316 let mut scenarios = vec![];
5317 let resolved_tasks = resolved_tasks.as_ref()?;
5318 let buffer = buffer.read(cx);
5319 let language = buffer.language()?;
5320 let file = buffer.file();
5321 let debug_adapter =
5322 language_settings(language.name().into(), file, cx)
5323 .debuggers
5324 .first()
5325 .map(SharedString::from)
5326 .or_else(|| {
5327 language
5328 .config()
5329 .debuggers
5330 .first()
5331 .map(SharedString::from)
5332 })?;
5333
5334 dap_store.update(cx, |dap_store, cx| {
5335 for (_, task) in &resolved_tasks.templates {
5336 if let Some(scenario) = dap_store
5337 .debug_scenario_for_build_task(
5338 task.original_task().clone(),
5339 debug_adapter.clone().into(),
5340 task.display_label().to_owned().into(),
5341 cx,
5342 )
5343 {
5344 scenarios.push(scenario);
5345 }
5346 }
5347 });
5348 Some(scenarios)
5349 })
5350 .unwrap_or_default()
5351 } else {
5352 vec![]
5353 }
5354 })?;
5355 let spawn_straight_away = quick_launch
5356 && resolved_tasks
5357 .as_ref()
5358 .map_or(false, |tasks| tasks.templates.len() == 1)
5359 && code_actions
5360 .as_ref()
5361 .map_or(true, |actions| actions.is_empty())
5362 && debug_scenarios.is_empty();
5363 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5364 *editor.context_menu.borrow_mut() =
5365 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5366 buffer,
5367 actions: CodeActionContents::new(
5368 resolved_tasks,
5369 code_actions,
5370 debug_scenarios,
5371 task_context.unwrap_or_default(),
5372 ),
5373 selected_item: Default::default(),
5374 scroll_handle: UniformListScrollHandle::default(),
5375 deployed_from_indicator,
5376 }));
5377 if spawn_straight_away {
5378 if let Some(task) = editor.confirm_code_action(
5379 &ConfirmCodeAction { item_ix: Some(0) },
5380 window,
5381 cx,
5382 ) {
5383 cx.notify();
5384 return task;
5385 }
5386 }
5387 cx.notify();
5388 Task::ready(Ok(()))
5389 }) {
5390 task.await
5391 } else {
5392 Ok(())
5393 }
5394 }))
5395 } else {
5396 Some(Task::ready(Ok(())))
5397 }
5398 })?;
5399 if let Some(task) = spawned_test_task {
5400 task.await?;
5401 }
5402
5403 Ok::<_, anyhow::Error>(())
5404 })
5405 .detach_and_log_err(cx);
5406 }
5407
5408 pub fn confirm_code_action(
5409 &mut self,
5410 action: &ConfirmCodeAction,
5411 window: &mut Window,
5412 cx: &mut Context<Self>,
5413 ) -> Option<Task<Result<()>>> {
5414 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5415
5416 let actions_menu =
5417 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5418 menu
5419 } else {
5420 return None;
5421 };
5422
5423 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5424 let action = actions_menu.actions.get(action_ix)?;
5425 let title = action.label();
5426 let buffer = actions_menu.buffer;
5427 let workspace = self.workspace()?;
5428
5429 match action {
5430 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5431 workspace.update(cx, |workspace, cx| {
5432 workspace.schedule_resolved_task(
5433 task_source_kind,
5434 resolved_task,
5435 false,
5436 window,
5437 cx,
5438 );
5439
5440 Some(Task::ready(Ok(())))
5441 })
5442 }
5443 CodeActionsItem::CodeAction {
5444 excerpt_id,
5445 action,
5446 provider,
5447 } => {
5448 let apply_code_action =
5449 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5450 let workspace = workspace.downgrade();
5451 Some(cx.spawn_in(window, async move |editor, cx| {
5452 let project_transaction = apply_code_action.await?;
5453 Self::open_project_transaction(
5454 &editor,
5455 workspace,
5456 project_transaction,
5457 title,
5458 cx,
5459 )
5460 .await
5461 }))
5462 }
5463 CodeActionsItem::DebugScenario(scenario) => {
5464 let context = actions_menu.actions.context.clone();
5465
5466 workspace.update(cx, |workspace, cx| {
5467 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5468 });
5469 Some(Task::ready(Ok(())))
5470 }
5471 }
5472 }
5473
5474 pub async fn open_project_transaction(
5475 this: &WeakEntity<Editor>,
5476 workspace: WeakEntity<Workspace>,
5477 transaction: ProjectTransaction,
5478 title: String,
5479 cx: &mut AsyncWindowContext,
5480 ) -> Result<()> {
5481 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5482 cx.update(|_, cx| {
5483 entries.sort_unstable_by_key(|(buffer, _)| {
5484 buffer.read(cx).file().map(|f| f.path().clone())
5485 });
5486 })?;
5487
5488 // If the project transaction's edits are all contained within this editor, then
5489 // avoid opening a new editor to display them.
5490
5491 if let Some((buffer, transaction)) = entries.first() {
5492 if entries.len() == 1 {
5493 let excerpt = this.update(cx, |editor, cx| {
5494 editor
5495 .buffer()
5496 .read(cx)
5497 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5498 })?;
5499 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5500 if excerpted_buffer == *buffer {
5501 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5502 let excerpt_range = excerpt_range.to_offset(buffer);
5503 buffer
5504 .edited_ranges_for_transaction::<usize>(transaction)
5505 .all(|range| {
5506 excerpt_range.start <= range.start
5507 && excerpt_range.end >= range.end
5508 })
5509 })?;
5510
5511 if all_edits_within_excerpt {
5512 return Ok(());
5513 }
5514 }
5515 }
5516 }
5517 } else {
5518 return Ok(());
5519 }
5520
5521 let mut ranges_to_highlight = Vec::new();
5522 let excerpt_buffer = cx.new(|cx| {
5523 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5524 for (buffer_handle, transaction) in &entries {
5525 let edited_ranges = buffer_handle
5526 .read(cx)
5527 .edited_ranges_for_transaction::<Point>(transaction)
5528 .collect::<Vec<_>>();
5529 let (ranges, _) = multibuffer.set_excerpts_for_path(
5530 PathKey::for_buffer(buffer_handle, cx),
5531 buffer_handle.clone(),
5532 edited_ranges,
5533 DEFAULT_MULTIBUFFER_CONTEXT,
5534 cx,
5535 );
5536
5537 ranges_to_highlight.extend(ranges);
5538 }
5539 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5540 multibuffer
5541 })?;
5542
5543 workspace.update_in(cx, |workspace, window, cx| {
5544 let project = workspace.project().clone();
5545 let editor =
5546 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5547 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5548 editor.update(cx, |editor, cx| {
5549 editor.highlight_background::<Self>(
5550 &ranges_to_highlight,
5551 |theme| theme.editor_highlighted_line_background,
5552 cx,
5553 );
5554 });
5555 })?;
5556
5557 Ok(())
5558 }
5559
5560 pub fn clear_code_action_providers(&mut self) {
5561 self.code_action_providers.clear();
5562 self.available_code_actions.take();
5563 }
5564
5565 pub fn add_code_action_provider(
5566 &mut self,
5567 provider: Rc<dyn CodeActionProvider>,
5568 window: &mut Window,
5569 cx: &mut Context<Self>,
5570 ) {
5571 if self
5572 .code_action_providers
5573 .iter()
5574 .any(|existing_provider| existing_provider.id() == provider.id())
5575 {
5576 return;
5577 }
5578
5579 self.code_action_providers.push(provider);
5580 self.refresh_code_actions(window, cx);
5581 }
5582
5583 pub fn remove_code_action_provider(
5584 &mut self,
5585 id: Arc<str>,
5586 window: &mut Window,
5587 cx: &mut Context<Self>,
5588 ) {
5589 self.code_action_providers
5590 .retain(|provider| provider.id() != id);
5591 self.refresh_code_actions(window, cx);
5592 }
5593
5594 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5595 let newest_selection = self.selections.newest_anchor().clone();
5596 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5597 let buffer = self.buffer.read(cx);
5598 if newest_selection.head().diff_base_anchor.is_some() {
5599 return None;
5600 }
5601 let (start_buffer, start) =
5602 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5603 let (end_buffer, end) =
5604 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5605 if start_buffer != end_buffer {
5606 return None;
5607 }
5608
5609 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5610 cx.background_executor()
5611 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5612 .await;
5613
5614 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5615 let providers = this.code_action_providers.clone();
5616 let tasks = this
5617 .code_action_providers
5618 .iter()
5619 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5620 .collect::<Vec<_>>();
5621 (providers, tasks)
5622 })?;
5623
5624 let mut actions = Vec::new();
5625 for (provider, provider_actions) in
5626 providers.into_iter().zip(future::join_all(tasks).await)
5627 {
5628 if let Some(provider_actions) = provider_actions.log_err() {
5629 actions.extend(provider_actions.into_iter().map(|action| {
5630 AvailableCodeAction {
5631 excerpt_id: newest_selection.start.excerpt_id,
5632 action,
5633 provider: provider.clone(),
5634 }
5635 }));
5636 }
5637 }
5638
5639 this.update(cx, |this, cx| {
5640 this.available_code_actions = if actions.is_empty() {
5641 None
5642 } else {
5643 Some((
5644 Location {
5645 buffer: start_buffer,
5646 range: start..end,
5647 },
5648 actions.into(),
5649 ))
5650 };
5651 cx.notify();
5652 })
5653 }));
5654 None
5655 }
5656
5657 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5658 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5659 self.show_git_blame_inline = false;
5660
5661 self.show_git_blame_inline_delay_task =
5662 Some(cx.spawn_in(window, async move |this, cx| {
5663 cx.background_executor().timer(delay).await;
5664
5665 this.update(cx, |this, cx| {
5666 this.show_git_blame_inline = true;
5667 cx.notify();
5668 })
5669 .log_err();
5670 }));
5671 }
5672 }
5673
5674 fn show_blame_popover(
5675 &mut self,
5676 blame_entry: &BlameEntry,
5677 position: gpui::Point<Pixels>,
5678 cx: &mut Context<Self>,
5679 ) {
5680 if let Some(state) = &mut self.inline_blame_popover {
5681 state.hide_task.take();
5682 cx.notify();
5683 } else {
5684 let delay = EditorSettings::get_global(cx).hover_popover_delay;
5685 let show_task = cx.spawn(async move |editor, cx| {
5686 cx.background_executor()
5687 .timer(std::time::Duration::from_millis(delay))
5688 .await;
5689 editor
5690 .update(cx, |editor, cx| {
5691 if let Some(state) = &mut editor.inline_blame_popover {
5692 state.show_task = None;
5693 cx.notify();
5694 }
5695 })
5696 .ok();
5697 });
5698 let Some(blame) = self.blame.as_ref() else {
5699 return;
5700 };
5701 let blame = blame.read(cx);
5702 let details = blame.details_for_entry(&blame_entry);
5703 let markdown = cx.new(|cx| {
5704 Markdown::new(
5705 details
5706 .as_ref()
5707 .map(|message| message.message.clone())
5708 .unwrap_or_default(),
5709 None,
5710 None,
5711 cx,
5712 )
5713 });
5714 self.inline_blame_popover = Some(InlineBlamePopover {
5715 position,
5716 show_task: Some(show_task),
5717 hide_task: None,
5718 popover_bounds: None,
5719 popover_state: InlineBlamePopoverState {
5720 scroll_handle: ScrollHandle::new(),
5721 commit_message: details,
5722 markdown,
5723 },
5724 });
5725 }
5726 }
5727
5728 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
5729 if let Some(state) = &mut self.inline_blame_popover {
5730 if state.show_task.is_some() {
5731 self.inline_blame_popover.take();
5732 cx.notify();
5733 } else {
5734 let hide_task = cx.spawn(async move |editor, cx| {
5735 cx.background_executor()
5736 .timer(std::time::Duration::from_millis(100))
5737 .await;
5738 editor
5739 .update(cx, |editor, cx| {
5740 editor.inline_blame_popover.take();
5741 cx.notify();
5742 })
5743 .ok();
5744 });
5745 state.hide_task = Some(hide_task);
5746 }
5747 }
5748 }
5749
5750 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5751 if self.pending_rename.is_some() {
5752 return None;
5753 }
5754
5755 let provider = self.semantics_provider.clone()?;
5756 let buffer = self.buffer.read(cx);
5757 let newest_selection = self.selections.newest_anchor().clone();
5758 let cursor_position = newest_selection.head();
5759 let (cursor_buffer, cursor_buffer_position) =
5760 buffer.text_anchor_for_position(cursor_position, cx)?;
5761 let (tail_buffer, tail_buffer_position) =
5762 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5763 if cursor_buffer != tail_buffer {
5764 return None;
5765 }
5766
5767 let snapshot = cursor_buffer.read(cx).snapshot();
5768 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
5769 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
5770 if start_word_range != end_word_range {
5771 self.document_highlights_task.take();
5772 self.clear_background_highlights::<DocumentHighlightRead>(cx);
5773 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
5774 return None;
5775 }
5776
5777 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5778 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5779 cx.background_executor()
5780 .timer(Duration::from_millis(debounce))
5781 .await;
5782
5783 let highlights = if let Some(highlights) = cx
5784 .update(|cx| {
5785 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5786 })
5787 .ok()
5788 .flatten()
5789 {
5790 highlights.await.log_err()
5791 } else {
5792 None
5793 };
5794
5795 if let Some(highlights) = highlights {
5796 this.update(cx, |this, cx| {
5797 if this.pending_rename.is_some() {
5798 return;
5799 }
5800
5801 let buffer_id = cursor_position.buffer_id;
5802 let buffer = this.buffer.read(cx);
5803 if !buffer
5804 .text_anchor_for_position(cursor_position, cx)
5805 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5806 {
5807 return;
5808 }
5809
5810 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5811 let mut write_ranges = Vec::new();
5812 let mut read_ranges = Vec::new();
5813 for highlight in highlights {
5814 for (excerpt_id, excerpt_range) in
5815 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5816 {
5817 let start = highlight
5818 .range
5819 .start
5820 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5821 let end = highlight
5822 .range
5823 .end
5824 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5825 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5826 continue;
5827 }
5828
5829 let range = Anchor {
5830 buffer_id,
5831 excerpt_id,
5832 text_anchor: start,
5833 diff_base_anchor: None,
5834 }..Anchor {
5835 buffer_id,
5836 excerpt_id,
5837 text_anchor: end,
5838 diff_base_anchor: None,
5839 };
5840 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5841 write_ranges.push(range);
5842 } else {
5843 read_ranges.push(range);
5844 }
5845 }
5846 }
5847
5848 this.highlight_background::<DocumentHighlightRead>(
5849 &read_ranges,
5850 |theme| theme.editor_document_highlight_read_background,
5851 cx,
5852 );
5853 this.highlight_background::<DocumentHighlightWrite>(
5854 &write_ranges,
5855 |theme| theme.editor_document_highlight_write_background,
5856 cx,
5857 );
5858 cx.notify();
5859 })
5860 .log_err();
5861 }
5862 }));
5863 None
5864 }
5865
5866 fn prepare_highlight_query_from_selection(
5867 &mut self,
5868 cx: &mut Context<Editor>,
5869 ) -> Option<(String, Range<Anchor>)> {
5870 if matches!(self.mode, EditorMode::SingleLine { .. }) {
5871 return None;
5872 }
5873 if !EditorSettings::get_global(cx).selection_highlight {
5874 return None;
5875 }
5876 if self.selections.count() != 1 || self.selections.line_mode {
5877 return None;
5878 }
5879 let selection = self.selections.newest::<Point>(cx);
5880 if selection.is_empty() || selection.start.row != selection.end.row {
5881 return None;
5882 }
5883 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5884 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
5885 let query = multi_buffer_snapshot
5886 .text_for_range(selection_anchor_range.clone())
5887 .collect::<String>();
5888 if query.trim().is_empty() {
5889 return None;
5890 }
5891 Some((query, selection_anchor_range))
5892 }
5893
5894 fn update_selection_occurrence_highlights(
5895 &mut self,
5896 query_text: String,
5897 query_range: Range<Anchor>,
5898 multi_buffer_range_to_query: Range<Point>,
5899 use_debounce: bool,
5900 window: &mut Window,
5901 cx: &mut Context<Editor>,
5902 ) -> Task<()> {
5903 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5904 cx.spawn_in(window, async move |editor, cx| {
5905 if use_debounce {
5906 cx.background_executor()
5907 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
5908 .await;
5909 }
5910 let match_task = cx.background_spawn(async move {
5911 let buffer_ranges = multi_buffer_snapshot
5912 .range_to_buffer_ranges(multi_buffer_range_to_query)
5913 .into_iter()
5914 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
5915 let mut match_ranges = Vec::new();
5916 let Ok(regex) = project::search::SearchQuery::text(
5917 query_text.clone(),
5918 false,
5919 false,
5920 false,
5921 Default::default(),
5922 Default::default(),
5923 false,
5924 None,
5925 ) else {
5926 return Vec::default();
5927 };
5928 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
5929 match_ranges.extend(
5930 regex
5931 .search(&buffer_snapshot, Some(search_range.clone()))
5932 .await
5933 .into_iter()
5934 .filter_map(|match_range| {
5935 let match_start = buffer_snapshot
5936 .anchor_after(search_range.start + match_range.start);
5937 let match_end = buffer_snapshot
5938 .anchor_before(search_range.start + match_range.end);
5939 let match_anchor_range = Anchor::range_in_buffer(
5940 excerpt_id,
5941 buffer_snapshot.remote_id(),
5942 match_start..match_end,
5943 );
5944 (match_anchor_range != query_range).then_some(match_anchor_range)
5945 }),
5946 );
5947 }
5948 match_ranges
5949 });
5950 let match_ranges = match_task.await;
5951 editor
5952 .update_in(cx, |editor, _, cx| {
5953 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5954 if !match_ranges.is_empty() {
5955 editor.highlight_background::<SelectedTextHighlight>(
5956 &match_ranges,
5957 |theme| theme.editor_document_highlight_bracket_background,
5958 cx,
5959 )
5960 }
5961 })
5962 .log_err();
5963 })
5964 }
5965
5966 fn refresh_selected_text_highlights(
5967 &mut self,
5968 on_buffer_edit: bool,
5969 window: &mut Window,
5970 cx: &mut Context<Editor>,
5971 ) {
5972 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
5973 else {
5974 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5975 self.quick_selection_highlight_task.take();
5976 self.debounced_selection_highlight_task.take();
5977 return;
5978 };
5979 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5980 if on_buffer_edit
5981 || self
5982 .quick_selection_highlight_task
5983 .as_ref()
5984 .map_or(true, |(prev_anchor_range, _)| {
5985 prev_anchor_range != &query_range
5986 })
5987 {
5988 let multi_buffer_visible_start = self
5989 .scroll_manager
5990 .anchor()
5991 .anchor
5992 .to_point(&multi_buffer_snapshot);
5993 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5994 multi_buffer_visible_start
5995 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5996 Bias::Left,
5997 );
5998 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5999 self.quick_selection_highlight_task = Some((
6000 query_range.clone(),
6001 self.update_selection_occurrence_highlights(
6002 query_text.clone(),
6003 query_range.clone(),
6004 multi_buffer_visible_range,
6005 false,
6006 window,
6007 cx,
6008 ),
6009 ));
6010 }
6011 if on_buffer_edit
6012 || self
6013 .debounced_selection_highlight_task
6014 .as_ref()
6015 .map_or(true, |(prev_anchor_range, _)| {
6016 prev_anchor_range != &query_range
6017 })
6018 {
6019 let multi_buffer_start = multi_buffer_snapshot
6020 .anchor_before(0)
6021 .to_point(&multi_buffer_snapshot);
6022 let multi_buffer_end = multi_buffer_snapshot
6023 .anchor_after(multi_buffer_snapshot.len())
6024 .to_point(&multi_buffer_snapshot);
6025 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6026 self.debounced_selection_highlight_task = Some((
6027 query_range.clone(),
6028 self.update_selection_occurrence_highlights(
6029 query_text,
6030 query_range,
6031 multi_buffer_full_range,
6032 true,
6033 window,
6034 cx,
6035 ),
6036 ));
6037 }
6038 }
6039
6040 pub fn refresh_inline_completion(
6041 &mut self,
6042 debounce: bool,
6043 user_requested: bool,
6044 window: &mut Window,
6045 cx: &mut Context<Self>,
6046 ) -> Option<()> {
6047 let provider = self.edit_prediction_provider()?;
6048 let cursor = self.selections.newest_anchor().head();
6049 let (buffer, cursor_buffer_position) =
6050 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6051
6052 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6053 self.discard_inline_completion(false, cx);
6054 return None;
6055 }
6056
6057 if !user_requested
6058 && (!self.should_show_edit_predictions()
6059 || !self.is_focused(window)
6060 || buffer.read(cx).is_empty())
6061 {
6062 self.discard_inline_completion(false, cx);
6063 return None;
6064 }
6065
6066 self.update_visible_inline_completion(window, cx);
6067 provider.refresh(
6068 self.project.clone(),
6069 buffer,
6070 cursor_buffer_position,
6071 debounce,
6072 cx,
6073 );
6074 Some(())
6075 }
6076
6077 fn show_edit_predictions_in_menu(&self) -> bool {
6078 match self.edit_prediction_settings {
6079 EditPredictionSettings::Disabled => false,
6080 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6081 }
6082 }
6083
6084 pub fn edit_predictions_enabled(&self) -> bool {
6085 match self.edit_prediction_settings {
6086 EditPredictionSettings::Disabled => false,
6087 EditPredictionSettings::Enabled { .. } => true,
6088 }
6089 }
6090
6091 fn edit_prediction_requires_modifier(&self) -> bool {
6092 match self.edit_prediction_settings {
6093 EditPredictionSettings::Disabled => false,
6094 EditPredictionSettings::Enabled {
6095 preview_requires_modifier,
6096 ..
6097 } => preview_requires_modifier,
6098 }
6099 }
6100
6101 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6102 if self.edit_prediction_provider.is_none() {
6103 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6104 } else {
6105 let selection = self.selections.newest_anchor();
6106 let cursor = selection.head();
6107
6108 if let Some((buffer, cursor_buffer_position)) =
6109 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6110 {
6111 self.edit_prediction_settings =
6112 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6113 }
6114 }
6115 }
6116
6117 fn edit_prediction_settings_at_position(
6118 &self,
6119 buffer: &Entity<Buffer>,
6120 buffer_position: language::Anchor,
6121 cx: &App,
6122 ) -> EditPredictionSettings {
6123 if !self.mode.is_full()
6124 || !self.show_inline_completions_override.unwrap_or(true)
6125 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6126 {
6127 return EditPredictionSettings::Disabled;
6128 }
6129
6130 let buffer = buffer.read(cx);
6131
6132 let file = buffer.file();
6133
6134 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6135 return EditPredictionSettings::Disabled;
6136 };
6137
6138 let by_provider = matches!(
6139 self.menu_inline_completions_policy,
6140 MenuInlineCompletionsPolicy::ByProvider
6141 );
6142
6143 let show_in_menu = by_provider
6144 && self
6145 .edit_prediction_provider
6146 .as_ref()
6147 .map_or(false, |provider| {
6148 provider.provider.show_completions_in_menu()
6149 });
6150
6151 let preview_requires_modifier =
6152 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6153
6154 EditPredictionSettings::Enabled {
6155 show_in_menu,
6156 preview_requires_modifier,
6157 }
6158 }
6159
6160 fn should_show_edit_predictions(&self) -> bool {
6161 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6162 }
6163
6164 pub fn edit_prediction_preview_is_active(&self) -> bool {
6165 matches!(
6166 self.edit_prediction_preview,
6167 EditPredictionPreview::Active { .. }
6168 )
6169 }
6170
6171 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6172 let cursor = self.selections.newest_anchor().head();
6173 if let Some((buffer, cursor_position)) =
6174 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6175 {
6176 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6177 } else {
6178 false
6179 }
6180 }
6181
6182 pub fn supports_minimap(&self, cx: &App) -> bool {
6183 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6184 }
6185
6186 fn edit_predictions_enabled_in_buffer(
6187 &self,
6188 buffer: &Entity<Buffer>,
6189 buffer_position: language::Anchor,
6190 cx: &App,
6191 ) -> bool {
6192 maybe!({
6193 if self.read_only(cx) {
6194 return Some(false);
6195 }
6196 let provider = self.edit_prediction_provider()?;
6197 if !provider.is_enabled(&buffer, buffer_position, cx) {
6198 return Some(false);
6199 }
6200 let buffer = buffer.read(cx);
6201 let Some(file) = buffer.file() else {
6202 return Some(true);
6203 };
6204 let settings = all_language_settings(Some(file), cx);
6205 Some(settings.edit_predictions_enabled_for_file(file, cx))
6206 })
6207 .unwrap_or(false)
6208 }
6209
6210 fn cycle_inline_completion(
6211 &mut self,
6212 direction: Direction,
6213 window: &mut Window,
6214 cx: &mut Context<Self>,
6215 ) -> Option<()> {
6216 let provider = self.edit_prediction_provider()?;
6217 let cursor = self.selections.newest_anchor().head();
6218 let (buffer, cursor_buffer_position) =
6219 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6220 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6221 return None;
6222 }
6223
6224 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6225 self.update_visible_inline_completion(window, cx);
6226
6227 Some(())
6228 }
6229
6230 pub fn show_inline_completion(
6231 &mut self,
6232 _: &ShowEditPrediction,
6233 window: &mut Window,
6234 cx: &mut Context<Self>,
6235 ) {
6236 if !self.has_active_inline_completion() {
6237 self.refresh_inline_completion(false, true, window, cx);
6238 return;
6239 }
6240
6241 self.update_visible_inline_completion(window, cx);
6242 }
6243
6244 pub fn display_cursor_names(
6245 &mut self,
6246 _: &DisplayCursorNames,
6247 window: &mut Window,
6248 cx: &mut Context<Self>,
6249 ) {
6250 self.show_cursor_names(window, cx);
6251 }
6252
6253 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6254 self.show_cursor_names = true;
6255 cx.notify();
6256 cx.spawn_in(window, async move |this, cx| {
6257 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6258 this.update(cx, |this, cx| {
6259 this.show_cursor_names = false;
6260 cx.notify()
6261 })
6262 .ok()
6263 })
6264 .detach();
6265 }
6266
6267 pub fn next_edit_prediction(
6268 &mut self,
6269 _: &NextEditPrediction,
6270 window: &mut Window,
6271 cx: &mut Context<Self>,
6272 ) {
6273 if self.has_active_inline_completion() {
6274 self.cycle_inline_completion(Direction::Next, window, cx);
6275 } else {
6276 let is_copilot_disabled = self
6277 .refresh_inline_completion(false, true, window, cx)
6278 .is_none();
6279 if is_copilot_disabled {
6280 cx.propagate();
6281 }
6282 }
6283 }
6284
6285 pub fn previous_edit_prediction(
6286 &mut self,
6287 _: &PreviousEditPrediction,
6288 window: &mut Window,
6289 cx: &mut Context<Self>,
6290 ) {
6291 if self.has_active_inline_completion() {
6292 self.cycle_inline_completion(Direction::Prev, window, cx);
6293 } else {
6294 let is_copilot_disabled = self
6295 .refresh_inline_completion(false, true, window, cx)
6296 .is_none();
6297 if is_copilot_disabled {
6298 cx.propagate();
6299 }
6300 }
6301 }
6302
6303 pub fn accept_edit_prediction(
6304 &mut self,
6305 _: &AcceptEditPrediction,
6306 window: &mut Window,
6307 cx: &mut Context<Self>,
6308 ) {
6309 if self.show_edit_predictions_in_menu() {
6310 self.hide_context_menu(window, cx);
6311 }
6312
6313 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6314 return;
6315 };
6316
6317 self.report_inline_completion_event(
6318 active_inline_completion.completion_id.clone(),
6319 true,
6320 cx,
6321 );
6322
6323 match &active_inline_completion.completion {
6324 InlineCompletion::Move { target, .. } => {
6325 let target = *target;
6326
6327 if let Some(position_map) = &self.last_position_map {
6328 if position_map
6329 .visible_row_range
6330 .contains(&target.to_display_point(&position_map.snapshot).row())
6331 || !self.edit_prediction_requires_modifier()
6332 {
6333 self.unfold_ranges(&[target..target], true, false, cx);
6334 // Note that this is also done in vim's handler of the Tab action.
6335 self.change_selections(
6336 Some(Autoscroll::newest()),
6337 window,
6338 cx,
6339 |selections| {
6340 selections.select_anchor_ranges([target..target]);
6341 },
6342 );
6343 self.clear_row_highlights::<EditPredictionPreview>();
6344
6345 self.edit_prediction_preview
6346 .set_previous_scroll_position(None);
6347 } else {
6348 self.edit_prediction_preview
6349 .set_previous_scroll_position(Some(
6350 position_map.snapshot.scroll_anchor,
6351 ));
6352
6353 self.highlight_rows::<EditPredictionPreview>(
6354 target..target,
6355 cx.theme().colors().editor_highlighted_line_background,
6356 RowHighlightOptions {
6357 autoscroll: true,
6358 ..Default::default()
6359 },
6360 cx,
6361 );
6362 self.request_autoscroll(Autoscroll::fit(), cx);
6363 }
6364 }
6365 }
6366 InlineCompletion::Edit { edits, .. } => {
6367 if let Some(provider) = self.edit_prediction_provider() {
6368 provider.accept(cx);
6369 }
6370
6371 let snapshot = self.buffer.read(cx).snapshot(cx);
6372 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6373
6374 self.buffer.update(cx, |buffer, cx| {
6375 buffer.edit(edits.iter().cloned(), None, cx)
6376 });
6377
6378 self.change_selections(None, window, cx, |s| {
6379 s.select_anchor_ranges([last_edit_end..last_edit_end])
6380 });
6381
6382 self.update_visible_inline_completion(window, cx);
6383 if self.active_inline_completion.is_none() {
6384 self.refresh_inline_completion(true, true, window, cx);
6385 }
6386
6387 cx.notify();
6388 }
6389 }
6390
6391 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6392 }
6393
6394 pub fn accept_partial_inline_completion(
6395 &mut self,
6396 _: &AcceptPartialEditPrediction,
6397 window: &mut Window,
6398 cx: &mut Context<Self>,
6399 ) {
6400 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6401 return;
6402 };
6403 if self.selections.count() != 1 {
6404 return;
6405 }
6406
6407 self.report_inline_completion_event(
6408 active_inline_completion.completion_id.clone(),
6409 true,
6410 cx,
6411 );
6412
6413 match &active_inline_completion.completion {
6414 InlineCompletion::Move { target, .. } => {
6415 let target = *target;
6416 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6417 selections.select_anchor_ranges([target..target]);
6418 });
6419 }
6420 InlineCompletion::Edit { edits, .. } => {
6421 // Find an insertion that starts at the cursor position.
6422 let snapshot = self.buffer.read(cx).snapshot(cx);
6423 let cursor_offset = self.selections.newest::<usize>(cx).head();
6424 let insertion = edits.iter().find_map(|(range, text)| {
6425 let range = range.to_offset(&snapshot);
6426 if range.is_empty() && range.start == cursor_offset {
6427 Some(text)
6428 } else {
6429 None
6430 }
6431 });
6432
6433 if let Some(text) = insertion {
6434 let mut partial_completion = text
6435 .chars()
6436 .by_ref()
6437 .take_while(|c| c.is_alphabetic())
6438 .collect::<String>();
6439 if partial_completion.is_empty() {
6440 partial_completion = text
6441 .chars()
6442 .by_ref()
6443 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6444 .collect::<String>();
6445 }
6446
6447 cx.emit(EditorEvent::InputHandled {
6448 utf16_range_to_replace: None,
6449 text: partial_completion.clone().into(),
6450 });
6451
6452 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6453
6454 self.refresh_inline_completion(true, true, window, cx);
6455 cx.notify();
6456 } else {
6457 self.accept_edit_prediction(&Default::default(), window, cx);
6458 }
6459 }
6460 }
6461 }
6462
6463 fn discard_inline_completion(
6464 &mut self,
6465 should_report_inline_completion_event: bool,
6466 cx: &mut Context<Self>,
6467 ) -> bool {
6468 if should_report_inline_completion_event {
6469 let completion_id = self
6470 .active_inline_completion
6471 .as_ref()
6472 .and_then(|active_completion| active_completion.completion_id.clone());
6473
6474 self.report_inline_completion_event(completion_id, false, cx);
6475 }
6476
6477 if let Some(provider) = self.edit_prediction_provider() {
6478 provider.discard(cx);
6479 }
6480
6481 self.take_active_inline_completion(cx)
6482 }
6483
6484 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6485 let Some(provider) = self.edit_prediction_provider() else {
6486 return;
6487 };
6488
6489 let Some((_, buffer, _)) = self
6490 .buffer
6491 .read(cx)
6492 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6493 else {
6494 return;
6495 };
6496
6497 let extension = buffer
6498 .read(cx)
6499 .file()
6500 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6501
6502 let event_type = match accepted {
6503 true => "Edit Prediction Accepted",
6504 false => "Edit Prediction Discarded",
6505 };
6506 telemetry::event!(
6507 event_type,
6508 provider = provider.name(),
6509 prediction_id = id,
6510 suggestion_accepted = accepted,
6511 file_extension = extension,
6512 );
6513 }
6514
6515 pub fn has_active_inline_completion(&self) -> bool {
6516 self.active_inline_completion.is_some()
6517 }
6518
6519 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6520 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6521 return false;
6522 };
6523
6524 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6525 self.clear_highlights::<InlineCompletionHighlight>(cx);
6526 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6527 true
6528 }
6529
6530 /// Returns true when we're displaying the edit prediction popover below the cursor
6531 /// like we are not previewing and the LSP autocomplete menu is visible
6532 /// or we are in `when_holding_modifier` mode.
6533 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6534 if self.edit_prediction_preview_is_active()
6535 || !self.show_edit_predictions_in_menu()
6536 || !self.edit_predictions_enabled()
6537 {
6538 return false;
6539 }
6540
6541 if self.has_visible_completions_menu() {
6542 return true;
6543 }
6544
6545 has_completion && self.edit_prediction_requires_modifier()
6546 }
6547
6548 fn handle_modifiers_changed(
6549 &mut self,
6550 modifiers: Modifiers,
6551 position_map: &PositionMap,
6552 window: &mut Window,
6553 cx: &mut Context<Self>,
6554 ) {
6555 if self.show_edit_predictions_in_menu() {
6556 self.update_edit_prediction_preview(&modifiers, window, cx);
6557 }
6558
6559 self.update_selection_mode(&modifiers, position_map, window, cx);
6560
6561 let mouse_position = window.mouse_position();
6562 if !position_map.text_hitbox.is_hovered(window) {
6563 return;
6564 }
6565
6566 self.update_hovered_link(
6567 position_map.point_for_position(mouse_position),
6568 &position_map.snapshot,
6569 modifiers,
6570 window,
6571 cx,
6572 )
6573 }
6574
6575 fn update_selection_mode(
6576 &mut self,
6577 modifiers: &Modifiers,
6578 position_map: &PositionMap,
6579 window: &mut Window,
6580 cx: &mut Context<Self>,
6581 ) {
6582 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6583 return;
6584 }
6585
6586 let mouse_position = window.mouse_position();
6587 let point_for_position = position_map.point_for_position(mouse_position);
6588 let position = point_for_position.previous_valid;
6589
6590 self.select(
6591 SelectPhase::BeginColumnar {
6592 position,
6593 reset: false,
6594 goal_column: point_for_position.exact_unclipped.column(),
6595 },
6596 window,
6597 cx,
6598 );
6599 }
6600
6601 fn update_edit_prediction_preview(
6602 &mut self,
6603 modifiers: &Modifiers,
6604 window: &mut Window,
6605 cx: &mut Context<Self>,
6606 ) {
6607 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6608 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6609 return;
6610 };
6611
6612 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6613 if matches!(
6614 self.edit_prediction_preview,
6615 EditPredictionPreview::Inactive { .. }
6616 ) {
6617 self.edit_prediction_preview = EditPredictionPreview::Active {
6618 previous_scroll_position: None,
6619 since: Instant::now(),
6620 };
6621
6622 self.update_visible_inline_completion(window, cx);
6623 cx.notify();
6624 }
6625 } else if let EditPredictionPreview::Active {
6626 previous_scroll_position,
6627 since,
6628 } = self.edit_prediction_preview
6629 {
6630 if let (Some(previous_scroll_position), Some(position_map)) =
6631 (previous_scroll_position, self.last_position_map.as_ref())
6632 {
6633 self.set_scroll_position(
6634 previous_scroll_position
6635 .scroll_position(&position_map.snapshot.display_snapshot),
6636 window,
6637 cx,
6638 );
6639 }
6640
6641 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6642 released_too_fast: since.elapsed() < Duration::from_millis(200),
6643 };
6644 self.clear_row_highlights::<EditPredictionPreview>();
6645 self.update_visible_inline_completion(window, cx);
6646 cx.notify();
6647 }
6648 }
6649
6650 fn update_visible_inline_completion(
6651 &mut self,
6652 _window: &mut Window,
6653 cx: &mut Context<Self>,
6654 ) -> Option<()> {
6655 let selection = self.selections.newest_anchor();
6656 let cursor = selection.head();
6657 let multibuffer = self.buffer.read(cx).snapshot(cx);
6658 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6659 let excerpt_id = cursor.excerpt_id;
6660
6661 let show_in_menu = self.show_edit_predictions_in_menu();
6662 let completions_menu_has_precedence = !show_in_menu
6663 && (self.context_menu.borrow().is_some()
6664 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6665
6666 if completions_menu_has_precedence
6667 || !offset_selection.is_empty()
6668 || self
6669 .active_inline_completion
6670 .as_ref()
6671 .map_or(false, |completion| {
6672 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6673 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6674 !invalidation_range.contains(&offset_selection.head())
6675 })
6676 {
6677 self.discard_inline_completion(false, cx);
6678 return None;
6679 }
6680
6681 self.take_active_inline_completion(cx);
6682 let Some(provider) = self.edit_prediction_provider() else {
6683 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6684 return None;
6685 };
6686
6687 let (buffer, cursor_buffer_position) =
6688 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6689
6690 self.edit_prediction_settings =
6691 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6692
6693 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6694
6695 if self.edit_prediction_indent_conflict {
6696 let cursor_point = cursor.to_point(&multibuffer);
6697
6698 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6699
6700 if let Some((_, indent)) = indents.iter().next() {
6701 if indent.len == cursor_point.column {
6702 self.edit_prediction_indent_conflict = false;
6703 }
6704 }
6705 }
6706
6707 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6708 let edits = inline_completion
6709 .edits
6710 .into_iter()
6711 .flat_map(|(range, new_text)| {
6712 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6713 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6714 Some((start..end, new_text))
6715 })
6716 .collect::<Vec<_>>();
6717 if edits.is_empty() {
6718 return None;
6719 }
6720
6721 let first_edit_start = edits.first().unwrap().0.start;
6722 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6723 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6724
6725 let last_edit_end = edits.last().unwrap().0.end;
6726 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6727 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6728
6729 let cursor_row = cursor.to_point(&multibuffer).row;
6730
6731 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6732
6733 let mut inlay_ids = Vec::new();
6734 let invalidation_row_range;
6735 let move_invalidation_row_range = if cursor_row < edit_start_row {
6736 Some(cursor_row..edit_end_row)
6737 } else if cursor_row > edit_end_row {
6738 Some(edit_start_row..cursor_row)
6739 } else {
6740 None
6741 };
6742 let is_move =
6743 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6744 let completion = if is_move {
6745 invalidation_row_range =
6746 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6747 let target = first_edit_start;
6748 InlineCompletion::Move { target, snapshot }
6749 } else {
6750 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6751 && !self.inline_completions_hidden_for_vim_mode;
6752
6753 if show_completions_in_buffer {
6754 if edits
6755 .iter()
6756 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6757 {
6758 let mut inlays = Vec::new();
6759 for (range, new_text) in &edits {
6760 let inlay = Inlay::inline_completion(
6761 post_inc(&mut self.next_inlay_id),
6762 range.start,
6763 new_text.as_str(),
6764 );
6765 inlay_ids.push(inlay.id);
6766 inlays.push(inlay);
6767 }
6768
6769 self.splice_inlays(&[], inlays, cx);
6770 } else {
6771 let background_color = cx.theme().status().deleted_background;
6772 self.highlight_text::<InlineCompletionHighlight>(
6773 edits.iter().map(|(range, _)| range.clone()).collect(),
6774 HighlightStyle {
6775 background_color: Some(background_color),
6776 ..Default::default()
6777 },
6778 cx,
6779 );
6780 }
6781 }
6782
6783 invalidation_row_range = edit_start_row..edit_end_row;
6784
6785 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6786 if provider.show_tab_accept_marker() {
6787 EditDisplayMode::TabAccept
6788 } else {
6789 EditDisplayMode::Inline
6790 }
6791 } else {
6792 EditDisplayMode::DiffPopover
6793 };
6794
6795 InlineCompletion::Edit {
6796 edits,
6797 edit_preview: inline_completion.edit_preview,
6798 display_mode,
6799 snapshot,
6800 }
6801 };
6802
6803 let invalidation_range = multibuffer
6804 .anchor_before(Point::new(invalidation_row_range.start, 0))
6805 ..multibuffer.anchor_after(Point::new(
6806 invalidation_row_range.end,
6807 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6808 ));
6809
6810 self.stale_inline_completion_in_menu = None;
6811 self.active_inline_completion = Some(InlineCompletionState {
6812 inlay_ids,
6813 completion,
6814 completion_id: inline_completion.id,
6815 invalidation_range,
6816 });
6817
6818 cx.notify();
6819
6820 Some(())
6821 }
6822
6823 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6824 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6825 }
6826
6827 fn clear_tasks(&mut self) {
6828 self.tasks.clear()
6829 }
6830
6831 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6832 if self.tasks.insert(key, value).is_some() {
6833 // This case should hopefully be rare, but just in case...
6834 log::error!(
6835 "multiple different run targets found on a single line, only the last target will be rendered"
6836 )
6837 }
6838 }
6839
6840 /// Get all display points of breakpoints that will be rendered within editor
6841 ///
6842 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6843 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6844 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6845 fn active_breakpoints(
6846 &self,
6847 range: Range<DisplayRow>,
6848 window: &mut Window,
6849 cx: &mut Context<Self>,
6850 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
6851 let mut breakpoint_display_points = HashMap::default();
6852
6853 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
6854 return breakpoint_display_points;
6855 };
6856
6857 let snapshot = self.snapshot(window, cx);
6858
6859 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
6860 let Some(project) = self.project.as_ref() else {
6861 return breakpoint_display_points;
6862 };
6863
6864 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
6865 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
6866
6867 for (buffer_snapshot, range, excerpt_id) in
6868 multi_buffer_snapshot.range_to_buffer_ranges(range)
6869 {
6870 let Some(buffer) = project.read_with(cx, |this, cx| {
6871 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
6872 }) else {
6873 continue;
6874 };
6875 let breakpoints = breakpoint_store.read(cx).breakpoints(
6876 &buffer,
6877 Some(
6878 buffer_snapshot.anchor_before(range.start)
6879 ..buffer_snapshot.anchor_after(range.end),
6880 ),
6881 buffer_snapshot,
6882 cx,
6883 );
6884 for (anchor, breakpoint) in breakpoints {
6885 let multi_buffer_anchor =
6886 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), *anchor);
6887 let position = multi_buffer_anchor
6888 .to_point(&multi_buffer_snapshot)
6889 .to_display_point(&snapshot);
6890
6891 breakpoint_display_points
6892 .insert(position.row(), (multi_buffer_anchor, breakpoint.clone()));
6893 }
6894 }
6895
6896 breakpoint_display_points
6897 }
6898
6899 fn breakpoint_context_menu(
6900 &self,
6901 anchor: Anchor,
6902 window: &mut Window,
6903 cx: &mut Context<Self>,
6904 ) -> Entity<ui::ContextMenu> {
6905 let weak_editor = cx.weak_entity();
6906 let focus_handle = self.focus_handle(cx);
6907
6908 let row = self
6909 .buffer
6910 .read(cx)
6911 .snapshot(cx)
6912 .summary_for_anchor::<Point>(&anchor)
6913 .row;
6914
6915 let breakpoint = self
6916 .breakpoint_at_row(row, window, cx)
6917 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
6918
6919 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
6920 "Edit Log Breakpoint"
6921 } else {
6922 "Set Log Breakpoint"
6923 };
6924
6925 let condition_breakpoint_msg = if breakpoint
6926 .as_ref()
6927 .is_some_and(|bp| bp.1.condition.is_some())
6928 {
6929 "Edit Condition Breakpoint"
6930 } else {
6931 "Set Condition Breakpoint"
6932 };
6933
6934 let hit_condition_breakpoint_msg = if breakpoint
6935 .as_ref()
6936 .is_some_and(|bp| bp.1.hit_condition.is_some())
6937 {
6938 "Edit Hit Condition Breakpoint"
6939 } else {
6940 "Set Hit Condition Breakpoint"
6941 };
6942
6943 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
6944 "Unset Breakpoint"
6945 } else {
6946 "Set Breakpoint"
6947 };
6948
6949 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
6950 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
6951
6952 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
6953 BreakpointState::Enabled => Some("Disable"),
6954 BreakpointState::Disabled => Some("Enable"),
6955 });
6956
6957 let (anchor, breakpoint) =
6958 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
6959
6960 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6961 menu.on_blur_subscription(Subscription::new(|| {}))
6962 .context(focus_handle)
6963 .when(run_to_cursor, |this| {
6964 let weak_editor = weak_editor.clone();
6965 this.entry("Run to cursor", None, move |window, cx| {
6966 weak_editor
6967 .update(cx, |editor, cx| {
6968 editor.change_selections(None, window, cx, |s| {
6969 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
6970 });
6971 })
6972 .ok();
6973
6974 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
6975 })
6976 .separator()
6977 })
6978 .when_some(toggle_state_msg, |this, msg| {
6979 this.entry(msg, None, {
6980 let weak_editor = weak_editor.clone();
6981 let breakpoint = breakpoint.clone();
6982 move |_window, cx| {
6983 weak_editor
6984 .update(cx, |this, cx| {
6985 this.edit_breakpoint_at_anchor(
6986 anchor,
6987 breakpoint.as_ref().clone(),
6988 BreakpointEditAction::InvertState,
6989 cx,
6990 );
6991 })
6992 .log_err();
6993 }
6994 })
6995 })
6996 .entry(set_breakpoint_msg, None, {
6997 let weak_editor = weak_editor.clone();
6998 let breakpoint = breakpoint.clone();
6999 move |_window, cx| {
7000 weak_editor
7001 .update(cx, |this, cx| {
7002 this.edit_breakpoint_at_anchor(
7003 anchor,
7004 breakpoint.as_ref().clone(),
7005 BreakpointEditAction::Toggle,
7006 cx,
7007 );
7008 })
7009 .log_err();
7010 }
7011 })
7012 .entry(log_breakpoint_msg, None, {
7013 let breakpoint = breakpoint.clone();
7014 let weak_editor = weak_editor.clone();
7015 move |window, cx| {
7016 weak_editor
7017 .update(cx, |this, cx| {
7018 this.add_edit_breakpoint_block(
7019 anchor,
7020 breakpoint.as_ref(),
7021 BreakpointPromptEditAction::Log,
7022 window,
7023 cx,
7024 );
7025 })
7026 .log_err();
7027 }
7028 })
7029 .entry(condition_breakpoint_msg, None, {
7030 let breakpoint = breakpoint.clone();
7031 let weak_editor = weak_editor.clone();
7032 move |window, cx| {
7033 weak_editor
7034 .update(cx, |this, cx| {
7035 this.add_edit_breakpoint_block(
7036 anchor,
7037 breakpoint.as_ref(),
7038 BreakpointPromptEditAction::Condition,
7039 window,
7040 cx,
7041 );
7042 })
7043 .log_err();
7044 }
7045 })
7046 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7047 weak_editor
7048 .update(cx, |this, cx| {
7049 this.add_edit_breakpoint_block(
7050 anchor,
7051 breakpoint.as_ref(),
7052 BreakpointPromptEditAction::HitCondition,
7053 window,
7054 cx,
7055 );
7056 })
7057 .log_err();
7058 })
7059 })
7060 }
7061
7062 fn render_breakpoint(
7063 &self,
7064 position: Anchor,
7065 row: DisplayRow,
7066 breakpoint: &Breakpoint,
7067 cx: &mut Context<Self>,
7068 ) -> IconButton {
7069 // Is it a breakpoint that shows up when hovering over gutter?
7070 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7071 (false, false),
7072 |PhantomBreakpointIndicator {
7073 is_active,
7074 display_row,
7075 collides_with_existing_breakpoint,
7076 }| {
7077 (
7078 is_active && display_row == row,
7079 collides_with_existing_breakpoint,
7080 )
7081 },
7082 );
7083
7084 let (color, icon) = {
7085 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7086 (false, false) => ui::IconName::DebugBreakpoint,
7087 (true, false) => ui::IconName::DebugLogBreakpoint,
7088 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7089 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7090 };
7091
7092 let color = if is_phantom {
7093 Color::Hint
7094 } else {
7095 Color::Debugger
7096 };
7097
7098 (color, icon)
7099 };
7100
7101 let breakpoint = Arc::from(breakpoint.clone());
7102
7103 let alt_as_text = gpui::Keystroke {
7104 modifiers: Modifiers::secondary_key(),
7105 ..Default::default()
7106 };
7107 let primary_action_text = if breakpoint.is_disabled() {
7108 "enable"
7109 } else if is_phantom && !collides_with_existing {
7110 "set"
7111 } else {
7112 "unset"
7113 };
7114 let mut primary_text = format!("Click to {primary_action_text}");
7115 if collides_with_existing && !breakpoint.is_disabled() {
7116 use std::fmt::Write;
7117 write!(primary_text, ", {alt_as_text}-click to disable").ok();
7118 }
7119 let primary_text = SharedString::from(primary_text);
7120 let focus_handle = self.focus_handle.clone();
7121 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7122 .icon_size(IconSize::XSmall)
7123 .size(ui::ButtonSize::None)
7124 .icon_color(color)
7125 .style(ButtonStyle::Transparent)
7126 .on_click(cx.listener({
7127 let breakpoint = breakpoint.clone();
7128
7129 move |editor, event: &ClickEvent, window, cx| {
7130 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7131 BreakpointEditAction::InvertState
7132 } else {
7133 BreakpointEditAction::Toggle
7134 };
7135
7136 window.focus(&editor.focus_handle(cx));
7137 editor.edit_breakpoint_at_anchor(
7138 position,
7139 breakpoint.as_ref().clone(),
7140 edit_action,
7141 cx,
7142 );
7143 }
7144 }))
7145 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7146 editor.set_breakpoint_context_menu(
7147 row,
7148 Some(position),
7149 event.down.position,
7150 window,
7151 cx,
7152 );
7153 }))
7154 .tooltip(move |window, cx| {
7155 Tooltip::with_meta_in(
7156 primary_text.clone(),
7157 None,
7158 "Right-click for more options",
7159 &focus_handle,
7160 window,
7161 cx,
7162 )
7163 })
7164 }
7165
7166 fn build_tasks_context(
7167 project: &Entity<Project>,
7168 buffer: &Entity<Buffer>,
7169 buffer_row: u32,
7170 tasks: &Arc<RunnableTasks>,
7171 cx: &mut Context<Self>,
7172 ) -> Task<Option<task::TaskContext>> {
7173 let position = Point::new(buffer_row, tasks.column);
7174 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7175 let location = Location {
7176 buffer: buffer.clone(),
7177 range: range_start..range_start,
7178 };
7179 // Fill in the environmental variables from the tree-sitter captures
7180 let mut captured_task_variables = TaskVariables::default();
7181 for (capture_name, value) in tasks.extra_variables.clone() {
7182 captured_task_variables.insert(
7183 task::VariableName::Custom(capture_name.into()),
7184 value.clone(),
7185 );
7186 }
7187 project.update(cx, |project, cx| {
7188 project.task_store().update(cx, |task_store, cx| {
7189 task_store.task_context_for_location(captured_task_variables, location, cx)
7190 })
7191 })
7192 }
7193
7194 pub fn spawn_nearest_task(
7195 &mut self,
7196 action: &SpawnNearestTask,
7197 window: &mut Window,
7198 cx: &mut Context<Self>,
7199 ) {
7200 let Some((workspace, _)) = self.workspace.clone() else {
7201 return;
7202 };
7203 let Some(project) = self.project.clone() else {
7204 return;
7205 };
7206
7207 // Try to find a closest, enclosing node using tree-sitter that has a
7208 // task
7209 let Some((buffer, buffer_row, tasks)) = self
7210 .find_enclosing_node_task(cx)
7211 // Or find the task that's closest in row-distance.
7212 .or_else(|| self.find_closest_task(cx))
7213 else {
7214 return;
7215 };
7216
7217 let reveal_strategy = action.reveal;
7218 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7219 cx.spawn_in(window, async move |_, cx| {
7220 let context = task_context.await?;
7221 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7222
7223 let resolved = &mut resolved_task.resolved;
7224 resolved.reveal = reveal_strategy;
7225
7226 workspace
7227 .update_in(cx, |workspace, window, cx| {
7228 workspace.schedule_resolved_task(
7229 task_source_kind,
7230 resolved_task,
7231 false,
7232 window,
7233 cx,
7234 );
7235 })
7236 .ok()
7237 })
7238 .detach();
7239 }
7240
7241 fn find_closest_task(
7242 &mut self,
7243 cx: &mut Context<Self>,
7244 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7245 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7246
7247 let ((buffer_id, row), tasks) = self
7248 .tasks
7249 .iter()
7250 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7251
7252 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7253 let tasks = Arc::new(tasks.to_owned());
7254 Some((buffer, *row, tasks))
7255 }
7256
7257 fn find_enclosing_node_task(
7258 &mut self,
7259 cx: &mut Context<Self>,
7260 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7261 let snapshot = self.buffer.read(cx).snapshot(cx);
7262 let offset = self.selections.newest::<usize>(cx).head();
7263 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7264 let buffer_id = excerpt.buffer().remote_id();
7265
7266 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7267 let mut cursor = layer.node().walk();
7268
7269 while cursor.goto_first_child_for_byte(offset).is_some() {
7270 if cursor.node().end_byte() == offset {
7271 cursor.goto_next_sibling();
7272 }
7273 }
7274
7275 // Ascend to the smallest ancestor that contains the range and has a task.
7276 loop {
7277 let node = cursor.node();
7278 let node_range = node.byte_range();
7279 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7280
7281 // Check if this node contains our offset
7282 if node_range.start <= offset && node_range.end >= offset {
7283 // If it contains offset, check for task
7284 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7285 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7286 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7287 }
7288 }
7289
7290 if !cursor.goto_parent() {
7291 break;
7292 }
7293 }
7294 None
7295 }
7296
7297 fn render_run_indicator(
7298 &self,
7299 _style: &EditorStyle,
7300 is_active: bool,
7301 row: DisplayRow,
7302 breakpoint: Option<(Anchor, Breakpoint)>,
7303 cx: &mut Context<Self>,
7304 ) -> IconButton {
7305 let color = Color::Muted;
7306 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
7307
7308 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7309 .shape(ui::IconButtonShape::Square)
7310 .icon_size(IconSize::XSmall)
7311 .icon_color(color)
7312 .toggle_state(is_active)
7313 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7314 let quick_launch = e.down.button == MouseButton::Left;
7315 window.focus(&editor.focus_handle(cx));
7316 editor.toggle_code_actions(
7317 &ToggleCodeActions {
7318 deployed_from_indicator: Some(row),
7319 quick_launch,
7320 },
7321 window,
7322 cx,
7323 );
7324 }))
7325 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7326 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7327 }))
7328 }
7329
7330 pub fn context_menu_visible(&self) -> bool {
7331 !self.edit_prediction_preview_is_active()
7332 && self
7333 .context_menu
7334 .borrow()
7335 .as_ref()
7336 .map_or(false, |menu| menu.visible())
7337 }
7338
7339 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7340 self.context_menu
7341 .borrow()
7342 .as_ref()
7343 .map(|menu| menu.origin())
7344 }
7345
7346 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7347 self.context_menu_options = Some(options);
7348 }
7349
7350 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7351 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7352
7353 fn render_edit_prediction_popover(
7354 &mut self,
7355 text_bounds: &Bounds<Pixels>,
7356 content_origin: gpui::Point<Pixels>,
7357 right_margin: Pixels,
7358 editor_snapshot: &EditorSnapshot,
7359 visible_row_range: Range<DisplayRow>,
7360 scroll_top: f32,
7361 scroll_bottom: f32,
7362 line_layouts: &[LineWithInvisibles],
7363 line_height: Pixels,
7364 scroll_pixel_position: gpui::Point<Pixels>,
7365 newest_selection_head: Option<DisplayPoint>,
7366 editor_width: Pixels,
7367 style: &EditorStyle,
7368 window: &mut Window,
7369 cx: &mut App,
7370 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7371 if self.mode().is_minimap() {
7372 return None;
7373 }
7374 let active_inline_completion = self.active_inline_completion.as_ref()?;
7375
7376 if self.edit_prediction_visible_in_cursor_popover(true) {
7377 return None;
7378 }
7379
7380 match &active_inline_completion.completion {
7381 InlineCompletion::Move { target, .. } => {
7382 let target_display_point = target.to_display_point(editor_snapshot);
7383
7384 if self.edit_prediction_requires_modifier() {
7385 if !self.edit_prediction_preview_is_active() {
7386 return None;
7387 }
7388
7389 self.render_edit_prediction_modifier_jump_popover(
7390 text_bounds,
7391 content_origin,
7392 visible_row_range,
7393 line_layouts,
7394 line_height,
7395 scroll_pixel_position,
7396 newest_selection_head,
7397 target_display_point,
7398 window,
7399 cx,
7400 )
7401 } else {
7402 self.render_edit_prediction_eager_jump_popover(
7403 text_bounds,
7404 content_origin,
7405 editor_snapshot,
7406 visible_row_range,
7407 scroll_top,
7408 scroll_bottom,
7409 line_height,
7410 scroll_pixel_position,
7411 target_display_point,
7412 editor_width,
7413 window,
7414 cx,
7415 )
7416 }
7417 }
7418 InlineCompletion::Edit {
7419 display_mode: EditDisplayMode::Inline,
7420 ..
7421 } => None,
7422 InlineCompletion::Edit {
7423 display_mode: EditDisplayMode::TabAccept,
7424 edits,
7425 ..
7426 } => {
7427 let range = &edits.first()?.0;
7428 let target_display_point = range.end.to_display_point(editor_snapshot);
7429
7430 self.render_edit_prediction_end_of_line_popover(
7431 "Accept",
7432 editor_snapshot,
7433 visible_row_range,
7434 target_display_point,
7435 line_height,
7436 scroll_pixel_position,
7437 content_origin,
7438 editor_width,
7439 window,
7440 cx,
7441 )
7442 }
7443 InlineCompletion::Edit {
7444 edits,
7445 edit_preview,
7446 display_mode: EditDisplayMode::DiffPopover,
7447 snapshot,
7448 } => self.render_edit_prediction_diff_popover(
7449 text_bounds,
7450 content_origin,
7451 right_margin,
7452 editor_snapshot,
7453 visible_row_range,
7454 line_layouts,
7455 line_height,
7456 scroll_pixel_position,
7457 newest_selection_head,
7458 editor_width,
7459 style,
7460 edits,
7461 edit_preview,
7462 snapshot,
7463 window,
7464 cx,
7465 ),
7466 }
7467 }
7468
7469 fn render_edit_prediction_modifier_jump_popover(
7470 &mut self,
7471 text_bounds: &Bounds<Pixels>,
7472 content_origin: gpui::Point<Pixels>,
7473 visible_row_range: Range<DisplayRow>,
7474 line_layouts: &[LineWithInvisibles],
7475 line_height: Pixels,
7476 scroll_pixel_position: gpui::Point<Pixels>,
7477 newest_selection_head: Option<DisplayPoint>,
7478 target_display_point: DisplayPoint,
7479 window: &mut Window,
7480 cx: &mut App,
7481 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7482 let scrolled_content_origin =
7483 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7484
7485 const SCROLL_PADDING_Y: Pixels = px(12.);
7486
7487 if target_display_point.row() < visible_row_range.start {
7488 return self.render_edit_prediction_scroll_popover(
7489 |_| SCROLL_PADDING_Y,
7490 IconName::ArrowUp,
7491 visible_row_range,
7492 line_layouts,
7493 newest_selection_head,
7494 scrolled_content_origin,
7495 window,
7496 cx,
7497 );
7498 } else if target_display_point.row() >= visible_row_range.end {
7499 return self.render_edit_prediction_scroll_popover(
7500 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7501 IconName::ArrowDown,
7502 visible_row_range,
7503 line_layouts,
7504 newest_selection_head,
7505 scrolled_content_origin,
7506 window,
7507 cx,
7508 );
7509 }
7510
7511 const POLE_WIDTH: Pixels = px(2.);
7512
7513 let line_layout =
7514 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7515 let target_column = target_display_point.column() as usize;
7516
7517 let target_x = line_layout.x_for_index(target_column);
7518 let target_y =
7519 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7520
7521 let flag_on_right = target_x < text_bounds.size.width / 2.;
7522
7523 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7524 border_color.l += 0.001;
7525
7526 let mut element = v_flex()
7527 .items_end()
7528 .when(flag_on_right, |el| el.items_start())
7529 .child(if flag_on_right {
7530 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7531 .rounded_bl(px(0.))
7532 .rounded_tl(px(0.))
7533 .border_l_2()
7534 .border_color(border_color)
7535 } else {
7536 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7537 .rounded_br(px(0.))
7538 .rounded_tr(px(0.))
7539 .border_r_2()
7540 .border_color(border_color)
7541 })
7542 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7543 .into_any();
7544
7545 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7546
7547 let mut origin = scrolled_content_origin + point(target_x, target_y)
7548 - point(
7549 if flag_on_right {
7550 POLE_WIDTH
7551 } else {
7552 size.width - POLE_WIDTH
7553 },
7554 size.height - line_height,
7555 );
7556
7557 origin.x = origin.x.max(content_origin.x);
7558
7559 element.prepaint_at(origin, window, cx);
7560
7561 Some((element, origin))
7562 }
7563
7564 fn render_edit_prediction_scroll_popover(
7565 &mut self,
7566 to_y: impl Fn(Size<Pixels>) -> Pixels,
7567 scroll_icon: IconName,
7568 visible_row_range: Range<DisplayRow>,
7569 line_layouts: &[LineWithInvisibles],
7570 newest_selection_head: Option<DisplayPoint>,
7571 scrolled_content_origin: gpui::Point<Pixels>,
7572 window: &mut Window,
7573 cx: &mut App,
7574 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7575 let mut element = self
7576 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7577 .into_any();
7578
7579 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7580
7581 let cursor = newest_selection_head?;
7582 let cursor_row_layout =
7583 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7584 let cursor_column = cursor.column() as usize;
7585
7586 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7587
7588 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7589
7590 element.prepaint_at(origin, window, cx);
7591 Some((element, origin))
7592 }
7593
7594 fn render_edit_prediction_eager_jump_popover(
7595 &mut self,
7596 text_bounds: &Bounds<Pixels>,
7597 content_origin: gpui::Point<Pixels>,
7598 editor_snapshot: &EditorSnapshot,
7599 visible_row_range: Range<DisplayRow>,
7600 scroll_top: f32,
7601 scroll_bottom: f32,
7602 line_height: Pixels,
7603 scroll_pixel_position: gpui::Point<Pixels>,
7604 target_display_point: DisplayPoint,
7605 editor_width: Pixels,
7606 window: &mut Window,
7607 cx: &mut App,
7608 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7609 if target_display_point.row().as_f32() < scroll_top {
7610 let mut element = self
7611 .render_edit_prediction_line_popover(
7612 "Jump to Edit",
7613 Some(IconName::ArrowUp),
7614 window,
7615 cx,
7616 )?
7617 .into_any();
7618
7619 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7620 let offset = point(
7621 (text_bounds.size.width - size.width) / 2.,
7622 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7623 );
7624
7625 let origin = text_bounds.origin + offset;
7626 element.prepaint_at(origin, window, cx);
7627 Some((element, origin))
7628 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7629 let mut element = self
7630 .render_edit_prediction_line_popover(
7631 "Jump to Edit",
7632 Some(IconName::ArrowDown),
7633 window,
7634 cx,
7635 )?
7636 .into_any();
7637
7638 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7639 let offset = point(
7640 (text_bounds.size.width - size.width) / 2.,
7641 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7642 );
7643
7644 let origin = text_bounds.origin + offset;
7645 element.prepaint_at(origin, window, cx);
7646 Some((element, origin))
7647 } else {
7648 self.render_edit_prediction_end_of_line_popover(
7649 "Jump to Edit",
7650 editor_snapshot,
7651 visible_row_range,
7652 target_display_point,
7653 line_height,
7654 scroll_pixel_position,
7655 content_origin,
7656 editor_width,
7657 window,
7658 cx,
7659 )
7660 }
7661 }
7662
7663 fn render_edit_prediction_end_of_line_popover(
7664 self: &mut Editor,
7665 label: &'static str,
7666 editor_snapshot: &EditorSnapshot,
7667 visible_row_range: Range<DisplayRow>,
7668 target_display_point: DisplayPoint,
7669 line_height: Pixels,
7670 scroll_pixel_position: gpui::Point<Pixels>,
7671 content_origin: gpui::Point<Pixels>,
7672 editor_width: Pixels,
7673 window: &mut Window,
7674 cx: &mut App,
7675 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7676 let target_line_end = DisplayPoint::new(
7677 target_display_point.row(),
7678 editor_snapshot.line_len(target_display_point.row()),
7679 );
7680
7681 let mut element = self
7682 .render_edit_prediction_line_popover(label, None, window, cx)?
7683 .into_any();
7684
7685 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7686
7687 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7688
7689 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7690 let mut origin = start_point
7691 + line_origin
7692 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7693 origin.x = origin.x.max(content_origin.x);
7694
7695 let max_x = content_origin.x + editor_width - size.width;
7696
7697 if origin.x > max_x {
7698 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7699
7700 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7701 origin.y += offset;
7702 IconName::ArrowUp
7703 } else {
7704 origin.y -= offset;
7705 IconName::ArrowDown
7706 };
7707
7708 element = self
7709 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7710 .into_any();
7711
7712 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7713
7714 origin.x = content_origin.x + editor_width - size.width - px(2.);
7715 }
7716
7717 element.prepaint_at(origin, window, cx);
7718 Some((element, origin))
7719 }
7720
7721 fn render_edit_prediction_diff_popover(
7722 self: &Editor,
7723 text_bounds: &Bounds<Pixels>,
7724 content_origin: gpui::Point<Pixels>,
7725 right_margin: Pixels,
7726 editor_snapshot: &EditorSnapshot,
7727 visible_row_range: Range<DisplayRow>,
7728 line_layouts: &[LineWithInvisibles],
7729 line_height: Pixels,
7730 scroll_pixel_position: gpui::Point<Pixels>,
7731 newest_selection_head: Option<DisplayPoint>,
7732 editor_width: Pixels,
7733 style: &EditorStyle,
7734 edits: &Vec<(Range<Anchor>, String)>,
7735 edit_preview: &Option<language::EditPreview>,
7736 snapshot: &language::BufferSnapshot,
7737 window: &mut Window,
7738 cx: &mut App,
7739 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7740 let edit_start = edits
7741 .first()
7742 .unwrap()
7743 .0
7744 .start
7745 .to_display_point(editor_snapshot);
7746 let edit_end = edits
7747 .last()
7748 .unwrap()
7749 .0
7750 .end
7751 .to_display_point(editor_snapshot);
7752
7753 let is_visible = visible_row_range.contains(&edit_start.row())
7754 || visible_row_range.contains(&edit_end.row());
7755 if !is_visible {
7756 return None;
7757 }
7758
7759 let highlighted_edits =
7760 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7761
7762 let styled_text = highlighted_edits.to_styled_text(&style.text);
7763 let line_count = highlighted_edits.text.lines().count();
7764
7765 const BORDER_WIDTH: Pixels = px(1.);
7766
7767 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7768 let has_keybind = keybind.is_some();
7769
7770 let mut element = h_flex()
7771 .items_start()
7772 .child(
7773 h_flex()
7774 .bg(cx.theme().colors().editor_background)
7775 .border(BORDER_WIDTH)
7776 .shadow_sm()
7777 .border_color(cx.theme().colors().border)
7778 .rounded_l_lg()
7779 .when(line_count > 1, |el| el.rounded_br_lg())
7780 .pr_1()
7781 .child(styled_text),
7782 )
7783 .child(
7784 h_flex()
7785 .h(line_height + BORDER_WIDTH * 2.)
7786 .px_1p5()
7787 .gap_1()
7788 // Workaround: For some reason, there's a gap if we don't do this
7789 .ml(-BORDER_WIDTH)
7790 .shadow(smallvec![gpui::BoxShadow {
7791 color: gpui::black().opacity(0.05),
7792 offset: point(px(1.), px(1.)),
7793 blur_radius: px(2.),
7794 spread_radius: px(0.),
7795 }])
7796 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7797 .border(BORDER_WIDTH)
7798 .border_color(cx.theme().colors().border)
7799 .rounded_r_lg()
7800 .id("edit_prediction_diff_popover_keybind")
7801 .when(!has_keybind, |el| {
7802 let status_colors = cx.theme().status();
7803
7804 el.bg(status_colors.error_background)
7805 .border_color(status_colors.error.opacity(0.6))
7806 .child(Icon::new(IconName::Info).color(Color::Error))
7807 .cursor_default()
7808 .hoverable_tooltip(move |_window, cx| {
7809 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7810 })
7811 })
7812 .children(keybind),
7813 )
7814 .into_any();
7815
7816 let longest_row =
7817 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7818 let longest_line_width = if visible_row_range.contains(&longest_row) {
7819 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7820 } else {
7821 layout_line(
7822 longest_row,
7823 editor_snapshot,
7824 style,
7825 editor_width,
7826 |_| false,
7827 window,
7828 cx,
7829 )
7830 .width
7831 };
7832
7833 let viewport_bounds =
7834 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
7835 right: -right_margin,
7836 ..Default::default()
7837 });
7838
7839 let x_after_longest =
7840 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
7841 - scroll_pixel_position.x;
7842
7843 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7844
7845 // Fully visible if it can be displayed within the window (allow overlapping other
7846 // panes). However, this is only allowed if the popover starts within text_bounds.
7847 let can_position_to_the_right = x_after_longest < text_bounds.right()
7848 && x_after_longest + element_bounds.width < viewport_bounds.right();
7849
7850 let mut origin = if can_position_to_the_right {
7851 point(
7852 x_after_longest,
7853 text_bounds.origin.y + edit_start.row().as_f32() * line_height
7854 - scroll_pixel_position.y,
7855 )
7856 } else {
7857 let cursor_row = newest_selection_head.map(|head| head.row());
7858 let above_edit = edit_start
7859 .row()
7860 .0
7861 .checked_sub(line_count as u32)
7862 .map(DisplayRow);
7863 let below_edit = Some(edit_end.row() + 1);
7864 let above_cursor =
7865 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
7866 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
7867
7868 // Place the edit popover adjacent to the edit if there is a location
7869 // available that is onscreen and does not obscure the cursor. Otherwise,
7870 // place it adjacent to the cursor.
7871 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
7872 .into_iter()
7873 .flatten()
7874 .find(|&start_row| {
7875 let end_row = start_row + line_count as u32;
7876 visible_row_range.contains(&start_row)
7877 && visible_row_range.contains(&end_row)
7878 && cursor_row.map_or(true, |cursor_row| {
7879 !((start_row..end_row).contains(&cursor_row))
7880 })
7881 })?;
7882
7883 content_origin
7884 + point(
7885 -scroll_pixel_position.x,
7886 row_target.as_f32() * line_height - scroll_pixel_position.y,
7887 )
7888 };
7889
7890 origin.x -= BORDER_WIDTH;
7891
7892 window.defer_draw(element, origin, 1);
7893
7894 // Do not return an element, since it will already be drawn due to defer_draw.
7895 None
7896 }
7897
7898 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
7899 px(30.)
7900 }
7901
7902 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
7903 if self.read_only(cx) {
7904 cx.theme().players().read_only()
7905 } else {
7906 self.style.as_ref().unwrap().local_player
7907 }
7908 }
7909
7910 fn render_edit_prediction_accept_keybind(
7911 &self,
7912 window: &mut Window,
7913 cx: &App,
7914 ) -> Option<AnyElement> {
7915 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
7916 let accept_keystroke = accept_binding.keystroke()?;
7917
7918 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7919
7920 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
7921 Color::Accent
7922 } else {
7923 Color::Muted
7924 };
7925
7926 h_flex()
7927 .px_0p5()
7928 .when(is_platform_style_mac, |parent| parent.gap_0p5())
7929 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7930 .text_size(TextSize::XSmall.rems(cx))
7931 .child(h_flex().children(ui::render_modifiers(
7932 &accept_keystroke.modifiers,
7933 PlatformStyle::platform(),
7934 Some(modifiers_color),
7935 Some(IconSize::XSmall.rems().into()),
7936 true,
7937 )))
7938 .when(is_platform_style_mac, |parent| {
7939 parent.child(accept_keystroke.key.clone())
7940 })
7941 .when(!is_platform_style_mac, |parent| {
7942 parent.child(
7943 Key::new(
7944 util::capitalize(&accept_keystroke.key),
7945 Some(Color::Default),
7946 )
7947 .size(Some(IconSize::XSmall.rems().into())),
7948 )
7949 })
7950 .into_any()
7951 .into()
7952 }
7953
7954 fn render_edit_prediction_line_popover(
7955 &self,
7956 label: impl Into<SharedString>,
7957 icon: Option<IconName>,
7958 window: &mut Window,
7959 cx: &App,
7960 ) -> Option<Stateful<Div>> {
7961 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
7962
7963 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7964 let has_keybind = keybind.is_some();
7965
7966 let result = h_flex()
7967 .id("ep-line-popover")
7968 .py_0p5()
7969 .pl_1()
7970 .pr(padding_right)
7971 .gap_1()
7972 .rounded_md()
7973 .border_1()
7974 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7975 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7976 .shadow_sm()
7977 .when(!has_keybind, |el| {
7978 let status_colors = cx.theme().status();
7979
7980 el.bg(status_colors.error_background)
7981 .border_color(status_colors.error.opacity(0.6))
7982 .pl_2()
7983 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7984 .cursor_default()
7985 .hoverable_tooltip(move |_window, cx| {
7986 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7987 })
7988 })
7989 .children(keybind)
7990 .child(
7991 Label::new(label)
7992 .size(LabelSize::Small)
7993 .when(!has_keybind, |el| {
7994 el.color(cx.theme().status().error.into()).strikethrough()
7995 }),
7996 )
7997 .when(!has_keybind, |el| {
7998 el.child(
7999 h_flex().ml_1().child(
8000 Icon::new(IconName::Info)
8001 .size(IconSize::Small)
8002 .color(cx.theme().status().error.into()),
8003 ),
8004 )
8005 })
8006 .when_some(icon, |element, icon| {
8007 element.child(
8008 div()
8009 .mt(px(1.5))
8010 .child(Icon::new(icon).size(IconSize::Small)),
8011 )
8012 });
8013
8014 Some(result)
8015 }
8016
8017 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8018 let accent_color = cx.theme().colors().text_accent;
8019 let editor_bg_color = cx.theme().colors().editor_background;
8020 editor_bg_color.blend(accent_color.opacity(0.1))
8021 }
8022
8023 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8024 let accent_color = cx.theme().colors().text_accent;
8025 let editor_bg_color = cx.theme().colors().editor_background;
8026 editor_bg_color.blend(accent_color.opacity(0.6))
8027 }
8028
8029 fn render_edit_prediction_cursor_popover(
8030 &self,
8031 min_width: Pixels,
8032 max_width: Pixels,
8033 cursor_point: Point,
8034 style: &EditorStyle,
8035 accept_keystroke: Option<&gpui::Keystroke>,
8036 _window: &Window,
8037 cx: &mut Context<Editor>,
8038 ) -> Option<AnyElement> {
8039 let provider = self.edit_prediction_provider.as_ref()?;
8040
8041 if provider.provider.needs_terms_acceptance(cx) {
8042 return Some(
8043 h_flex()
8044 .min_w(min_width)
8045 .flex_1()
8046 .px_2()
8047 .py_1()
8048 .gap_3()
8049 .elevation_2(cx)
8050 .hover(|style| style.bg(cx.theme().colors().element_hover))
8051 .id("accept-terms")
8052 .cursor_pointer()
8053 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8054 .on_click(cx.listener(|this, _event, window, cx| {
8055 cx.stop_propagation();
8056 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8057 window.dispatch_action(
8058 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8059 cx,
8060 );
8061 }))
8062 .child(
8063 h_flex()
8064 .flex_1()
8065 .gap_2()
8066 .child(Icon::new(IconName::ZedPredict))
8067 .child(Label::new("Accept Terms of Service"))
8068 .child(div().w_full())
8069 .child(
8070 Icon::new(IconName::ArrowUpRight)
8071 .color(Color::Muted)
8072 .size(IconSize::Small),
8073 )
8074 .into_any_element(),
8075 )
8076 .into_any(),
8077 );
8078 }
8079
8080 let is_refreshing = provider.provider.is_refreshing(cx);
8081
8082 fn pending_completion_container() -> Div {
8083 h_flex()
8084 .h_full()
8085 .flex_1()
8086 .gap_2()
8087 .child(Icon::new(IconName::ZedPredict))
8088 }
8089
8090 let completion = match &self.active_inline_completion {
8091 Some(prediction) => {
8092 if !self.has_visible_completions_menu() {
8093 const RADIUS: Pixels = px(6.);
8094 const BORDER_WIDTH: Pixels = px(1.);
8095
8096 return Some(
8097 h_flex()
8098 .elevation_2(cx)
8099 .border(BORDER_WIDTH)
8100 .border_color(cx.theme().colors().border)
8101 .when(accept_keystroke.is_none(), |el| {
8102 el.border_color(cx.theme().status().error)
8103 })
8104 .rounded(RADIUS)
8105 .rounded_tl(px(0.))
8106 .overflow_hidden()
8107 .child(div().px_1p5().child(match &prediction.completion {
8108 InlineCompletion::Move { target, snapshot } => {
8109 use text::ToPoint as _;
8110 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8111 {
8112 Icon::new(IconName::ZedPredictDown)
8113 } else {
8114 Icon::new(IconName::ZedPredictUp)
8115 }
8116 }
8117 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8118 }))
8119 .child(
8120 h_flex()
8121 .gap_1()
8122 .py_1()
8123 .px_2()
8124 .rounded_r(RADIUS - BORDER_WIDTH)
8125 .border_l_1()
8126 .border_color(cx.theme().colors().border)
8127 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8128 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8129 el.child(
8130 Label::new("Hold")
8131 .size(LabelSize::Small)
8132 .when(accept_keystroke.is_none(), |el| {
8133 el.strikethrough()
8134 })
8135 .line_height_style(LineHeightStyle::UiLabel),
8136 )
8137 })
8138 .id("edit_prediction_cursor_popover_keybind")
8139 .when(accept_keystroke.is_none(), |el| {
8140 let status_colors = cx.theme().status();
8141
8142 el.bg(status_colors.error_background)
8143 .border_color(status_colors.error.opacity(0.6))
8144 .child(Icon::new(IconName::Info).color(Color::Error))
8145 .cursor_default()
8146 .hoverable_tooltip(move |_window, cx| {
8147 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8148 .into()
8149 })
8150 })
8151 .when_some(
8152 accept_keystroke.as_ref(),
8153 |el, accept_keystroke| {
8154 el.child(h_flex().children(ui::render_modifiers(
8155 &accept_keystroke.modifiers,
8156 PlatformStyle::platform(),
8157 Some(Color::Default),
8158 Some(IconSize::XSmall.rems().into()),
8159 false,
8160 )))
8161 },
8162 ),
8163 )
8164 .into_any(),
8165 );
8166 }
8167
8168 self.render_edit_prediction_cursor_popover_preview(
8169 prediction,
8170 cursor_point,
8171 style,
8172 cx,
8173 )?
8174 }
8175
8176 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8177 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8178 stale_completion,
8179 cursor_point,
8180 style,
8181 cx,
8182 )?,
8183
8184 None => {
8185 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8186 }
8187 },
8188
8189 None => pending_completion_container().child(Label::new("No Prediction")),
8190 };
8191
8192 let completion = if is_refreshing {
8193 completion
8194 .with_animation(
8195 "loading-completion",
8196 Animation::new(Duration::from_secs(2))
8197 .repeat()
8198 .with_easing(pulsating_between(0.4, 0.8)),
8199 |label, delta| label.opacity(delta),
8200 )
8201 .into_any_element()
8202 } else {
8203 completion.into_any_element()
8204 };
8205
8206 let has_completion = self.active_inline_completion.is_some();
8207
8208 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8209 Some(
8210 h_flex()
8211 .min_w(min_width)
8212 .max_w(max_width)
8213 .flex_1()
8214 .elevation_2(cx)
8215 .border_color(cx.theme().colors().border)
8216 .child(
8217 div()
8218 .flex_1()
8219 .py_1()
8220 .px_2()
8221 .overflow_hidden()
8222 .child(completion),
8223 )
8224 .when_some(accept_keystroke, |el, accept_keystroke| {
8225 if !accept_keystroke.modifiers.modified() {
8226 return el;
8227 }
8228
8229 el.child(
8230 h_flex()
8231 .h_full()
8232 .border_l_1()
8233 .rounded_r_lg()
8234 .border_color(cx.theme().colors().border)
8235 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8236 .gap_1()
8237 .py_1()
8238 .px_2()
8239 .child(
8240 h_flex()
8241 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8242 .when(is_platform_style_mac, |parent| parent.gap_1())
8243 .child(h_flex().children(ui::render_modifiers(
8244 &accept_keystroke.modifiers,
8245 PlatformStyle::platform(),
8246 Some(if !has_completion {
8247 Color::Muted
8248 } else {
8249 Color::Default
8250 }),
8251 None,
8252 false,
8253 ))),
8254 )
8255 .child(Label::new("Preview").into_any_element())
8256 .opacity(if has_completion { 1.0 } else { 0.4 }),
8257 )
8258 })
8259 .into_any(),
8260 )
8261 }
8262
8263 fn render_edit_prediction_cursor_popover_preview(
8264 &self,
8265 completion: &InlineCompletionState,
8266 cursor_point: Point,
8267 style: &EditorStyle,
8268 cx: &mut Context<Editor>,
8269 ) -> Option<Div> {
8270 use text::ToPoint as _;
8271
8272 fn render_relative_row_jump(
8273 prefix: impl Into<String>,
8274 current_row: u32,
8275 target_row: u32,
8276 ) -> Div {
8277 let (row_diff, arrow) = if target_row < current_row {
8278 (current_row - target_row, IconName::ArrowUp)
8279 } else {
8280 (target_row - current_row, IconName::ArrowDown)
8281 };
8282
8283 h_flex()
8284 .child(
8285 Label::new(format!("{}{}", prefix.into(), row_diff))
8286 .color(Color::Muted)
8287 .size(LabelSize::Small),
8288 )
8289 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8290 }
8291
8292 match &completion.completion {
8293 InlineCompletion::Move {
8294 target, snapshot, ..
8295 } => Some(
8296 h_flex()
8297 .px_2()
8298 .gap_2()
8299 .flex_1()
8300 .child(
8301 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8302 Icon::new(IconName::ZedPredictDown)
8303 } else {
8304 Icon::new(IconName::ZedPredictUp)
8305 },
8306 )
8307 .child(Label::new("Jump to Edit")),
8308 ),
8309
8310 InlineCompletion::Edit {
8311 edits,
8312 edit_preview,
8313 snapshot,
8314 display_mode: _,
8315 } => {
8316 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8317
8318 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8319 &snapshot,
8320 &edits,
8321 edit_preview.as_ref()?,
8322 true,
8323 cx,
8324 )
8325 .first_line_preview();
8326
8327 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8328 .with_default_highlights(&style.text, highlighted_edits.highlights);
8329
8330 let preview = h_flex()
8331 .gap_1()
8332 .min_w_16()
8333 .child(styled_text)
8334 .when(has_more_lines, |parent| parent.child("…"));
8335
8336 let left = if first_edit_row != cursor_point.row {
8337 render_relative_row_jump("", cursor_point.row, first_edit_row)
8338 .into_any_element()
8339 } else {
8340 Icon::new(IconName::ZedPredict).into_any_element()
8341 };
8342
8343 Some(
8344 h_flex()
8345 .h_full()
8346 .flex_1()
8347 .gap_2()
8348 .pr_1()
8349 .overflow_x_hidden()
8350 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8351 .child(left)
8352 .child(preview),
8353 )
8354 }
8355 }
8356 }
8357
8358 fn render_context_menu(
8359 &self,
8360 style: &EditorStyle,
8361 max_height_in_lines: u32,
8362 window: &mut Window,
8363 cx: &mut Context<Editor>,
8364 ) -> Option<AnyElement> {
8365 let menu = self.context_menu.borrow();
8366 let menu = menu.as_ref()?;
8367 if !menu.visible() {
8368 return None;
8369 };
8370 Some(menu.render(style, max_height_in_lines, window, cx))
8371 }
8372
8373 fn render_context_menu_aside(
8374 &mut self,
8375 max_size: Size<Pixels>,
8376 window: &mut Window,
8377 cx: &mut Context<Editor>,
8378 ) -> Option<AnyElement> {
8379 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8380 if menu.visible() {
8381 menu.render_aside(self, max_size, window, cx)
8382 } else {
8383 None
8384 }
8385 })
8386 }
8387
8388 fn hide_context_menu(
8389 &mut self,
8390 window: &mut Window,
8391 cx: &mut Context<Self>,
8392 ) -> Option<CodeContextMenu> {
8393 cx.notify();
8394 self.completion_tasks.clear();
8395 let context_menu = self.context_menu.borrow_mut().take();
8396 self.stale_inline_completion_in_menu.take();
8397 self.update_visible_inline_completion(window, cx);
8398 context_menu
8399 }
8400
8401 fn show_snippet_choices(
8402 &mut self,
8403 choices: &Vec<String>,
8404 selection: Range<Anchor>,
8405 cx: &mut Context<Self>,
8406 ) {
8407 if selection.start.buffer_id.is_none() {
8408 return;
8409 }
8410 let buffer_id = selection.start.buffer_id.unwrap();
8411 let buffer = self.buffer().read(cx).buffer(buffer_id);
8412 let id = post_inc(&mut self.next_completion_id);
8413 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8414
8415 if let Some(buffer) = buffer {
8416 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8417 CompletionsMenu::new_snippet_choices(
8418 id,
8419 true,
8420 choices,
8421 selection,
8422 buffer,
8423 snippet_sort_order,
8424 ),
8425 ));
8426 }
8427 }
8428
8429 pub fn insert_snippet(
8430 &mut self,
8431 insertion_ranges: &[Range<usize>],
8432 snippet: Snippet,
8433 window: &mut Window,
8434 cx: &mut Context<Self>,
8435 ) -> Result<()> {
8436 struct Tabstop<T> {
8437 is_end_tabstop: bool,
8438 ranges: Vec<Range<T>>,
8439 choices: Option<Vec<String>>,
8440 }
8441
8442 let tabstops = self.buffer.update(cx, |buffer, cx| {
8443 let snippet_text: Arc<str> = snippet.text.clone().into();
8444 let edits = insertion_ranges
8445 .iter()
8446 .cloned()
8447 .map(|range| (range, snippet_text.clone()));
8448 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8449
8450 let snapshot = &*buffer.read(cx);
8451 let snippet = &snippet;
8452 snippet
8453 .tabstops
8454 .iter()
8455 .map(|tabstop| {
8456 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8457 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8458 });
8459 let mut tabstop_ranges = tabstop
8460 .ranges
8461 .iter()
8462 .flat_map(|tabstop_range| {
8463 let mut delta = 0_isize;
8464 insertion_ranges.iter().map(move |insertion_range| {
8465 let insertion_start = insertion_range.start as isize + delta;
8466 delta +=
8467 snippet.text.len() as isize - insertion_range.len() as isize;
8468
8469 let start = ((insertion_start + tabstop_range.start) as usize)
8470 .min(snapshot.len());
8471 let end = ((insertion_start + tabstop_range.end) as usize)
8472 .min(snapshot.len());
8473 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8474 })
8475 })
8476 .collect::<Vec<_>>();
8477 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8478
8479 Tabstop {
8480 is_end_tabstop,
8481 ranges: tabstop_ranges,
8482 choices: tabstop.choices.clone(),
8483 }
8484 })
8485 .collect::<Vec<_>>()
8486 });
8487 if let Some(tabstop) = tabstops.first() {
8488 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8489 s.select_ranges(tabstop.ranges.iter().cloned());
8490 });
8491
8492 if let Some(choices) = &tabstop.choices {
8493 if let Some(selection) = tabstop.ranges.first() {
8494 self.show_snippet_choices(choices, selection.clone(), cx)
8495 }
8496 }
8497
8498 // If we're already at the last tabstop and it's at the end of the snippet,
8499 // we're done, we don't need to keep the state around.
8500 if !tabstop.is_end_tabstop {
8501 let choices = tabstops
8502 .iter()
8503 .map(|tabstop| tabstop.choices.clone())
8504 .collect();
8505
8506 let ranges = tabstops
8507 .into_iter()
8508 .map(|tabstop| tabstop.ranges)
8509 .collect::<Vec<_>>();
8510
8511 self.snippet_stack.push(SnippetState {
8512 active_index: 0,
8513 ranges,
8514 choices,
8515 });
8516 }
8517
8518 // Check whether the just-entered snippet ends with an auto-closable bracket.
8519 if self.autoclose_regions.is_empty() {
8520 let snapshot = self.buffer.read(cx).snapshot(cx);
8521 for selection in &mut self.selections.all::<Point>(cx) {
8522 let selection_head = selection.head();
8523 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8524 continue;
8525 };
8526
8527 let mut bracket_pair = None;
8528 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8529 let prev_chars = snapshot
8530 .reversed_chars_at(selection_head)
8531 .collect::<String>();
8532 for (pair, enabled) in scope.brackets() {
8533 if enabled
8534 && pair.close
8535 && prev_chars.starts_with(pair.start.as_str())
8536 && next_chars.starts_with(pair.end.as_str())
8537 {
8538 bracket_pair = Some(pair.clone());
8539 break;
8540 }
8541 }
8542 if let Some(pair) = bracket_pair {
8543 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8544 let autoclose_enabled =
8545 self.use_autoclose && snapshot_settings.use_autoclose;
8546 if autoclose_enabled {
8547 let start = snapshot.anchor_after(selection_head);
8548 let end = snapshot.anchor_after(selection_head);
8549 self.autoclose_regions.push(AutocloseRegion {
8550 selection_id: selection.id,
8551 range: start..end,
8552 pair,
8553 });
8554 }
8555 }
8556 }
8557 }
8558 }
8559 Ok(())
8560 }
8561
8562 pub fn move_to_next_snippet_tabstop(
8563 &mut self,
8564 window: &mut Window,
8565 cx: &mut Context<Self>,
8566 ) -> bool {
8567 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8568 }
8569
8570 pub fn move_to_prev_snippet_tabstop(
8571 &mut self,
8572 window: &mut Window,
8573 cx: &mut Context<Self>,
8574 ) -> bool {
8575 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8576 }
8577
8578 pub fn move_to_snippet_tabstop(
8579 &mut self,
8580 bias: Bias,
8581 window: &mut Window,
8582 cx: &mut Context<Self>,
8583 ) -> bool {
8584 if let Some(mut snippet) = self.snippet_stack.pop() {
8585 match bias {
8586 Bias::Left => {
8587 if snippet.active_index > 0 {
8588 snippet.active_index -= 1;
8589 } else {
8590 self.snippet_stack.push(snippet);
8591 return false;
8592 }
8593 }
8594 Bias::Right => {
8595 if snippet.active_index + 1 < snippet.ranges.len() {
8596 snippet.active_index += 1;
8597 } else {
8598 self.snippet_stack.push(snippet);
8599 return false;
8600 }
8601 }
8602 }
8603 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8604 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8605 s.select_anchor_ranges(current_ranges.iter().cloned())
8606 });
8607
8608 if let Some(choices) = &snippet.choices[snippet.active_index] {
8609 if let Some(selection) = current_ranges.first() {
8610 self.show_snippet_choices(&choices, selection.clone(), cx);
8611 }
8612 }
8613
8614 // If snippet state is not at the last tabstop, push it back on the stack
8615 if snippet.active_index + 1 < snippet.ranges.len() {
8616 self.snippet_stack.push(snippet);
8617 }
8618 return true;
8619 }
8620 }
8621
8622 false
8623 }
8624
8625 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8626 self.transact(window, cx, |this, window, cx| {
8627 this.select_all(&SelectAll, window, cx);
8628 this.insert("", window, cx);
8629 });
8630 }
8631
8632 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8633 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8634 self.transact(window, cx, |this, window, cx| {
8635 this.select_autoclose_pair(window, cx);
8636 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8637 if !this.linked_edit_ranges.is_empty() {
8638 let selections = this.selections.all::<MultiBufferPoint>(cx);
8639 let snapshot = this.buffer.read(cx).snapshot(cx);
8640
8641 for selection in selections.iter() {
8642 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8643 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8644 if selection_start.buffer_id != selection_end.buffer_id {
8645 continue;
8646 }
8647 if let Some(ranges) =
8648 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8649 {
8650 for (buffer, entries) in ranges {
8651 linked_ranges.entry(buffer).or_default().extend(entries);
8652 }
8653 }
8654 }
8655 }
8656
8657 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8658 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8659 for selection in &mut selections {
8660 if selection.is_empty() {
8661 let old_head = selection.head();
8662 let mut new_head =
8663 movement::left(&display_map, old_head.to_display_point(&display_map))
8664 .to_point(&display_map);
8665 if let Some((buffer, line_buffer_range)) = display_map
8666 .buffer_snapshot
8667 .buffer_line_for_row(MultiBufferRow(old_head.row))
8668 {
8669 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8670 let indent_len = match indent_size.kind {
8671 IndentKind::Space => {
8672 buffer.settings_at(line_buffer_range.start, cx).tab_size
8673 }
8674 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8675 };
8676 if old_head.column <= indent_size.len && old_head.column > 0 {
8677 let indent_len = indent_len.get();
8678 new_head = cmp::min(
8679 new_head,
8680 MultiBufferPoint::new(
8681 old_head.row,
8682 ((old_head.column - 1) / indent_len) * indent_len,
8683 ),
8684 );
8685 }
8686 }
8687
8688 selection.set_head(new_head, SelectionGoal::None);
8689 }
8690 }
8691
8692 this.signature_help_state.set_backspace_pressed(true);
8693 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8694 s.select(selections)
8695 });
8696 this.insert("", window, cx);
8697 let empty_str: Arc<str> = Arc::from("");
8698 for (buffer, edits) in linked_ranges {
8699 let snapshot = buffer.read(cx).snapshot();
8700 use text::ToPoint as TP;
8701
8702 let edits = edits
8703 .into_iter()
8704 .map(|range| {
8705 let end_point = TP::to_point(&range.end, &snapshot);
8706 let mut start_point = TP::to_point(&range.start, &snapshot);
8707
8708 if end_point == start_point {
8709 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8710 .saturating_sub(1);
8711 start_point =
8712 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8713 };
8714
8715 (start_point..end_point, empty_str.clone())
8716 })
8717 .sorted_by_key(|(range, _)| range.start)
8718 .collect::<Vec<_>>();
8719 buffer.update(cx, |this, cx| {
8720 this.edit(edits, None, cx);
8721 })
8722 }
8723 this.refresh_inline_completion(true, false, window, cx);
8724 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8725 });
8726 }
8727
8728 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8729 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8730 self.transact(window, cx, |this, window, cx| {
8731 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8732 s.move_with(|map, selection| {
8733 if selection.is_empty() {
8734 let cursor = movement::right(map, selection.head());
8735 selection.end = cursor;
8736 selection.reversed = true;
8737 selection.goal = SelectionGoal::None;
8738 }
8739 })
8740 });
8741 this.insert("", window, cx);
8742 this.refresh_inline_completion(true, false, window, cx);
8743 });
8744 }
8745
8746 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8747 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8748 if self.move_to_prev_snippet_tabstop(window, cx) {
8749 return;
8750 }
8751 self.outdent(&Outdent, window, cx);
8752 }
8753
8754 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8755 if self.move_to_next_snippet_tabstop(window, cx) {
8756 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8757 return;
8758 }
8759 if self.read_only(cx) {
8760 return;
8761 }
8762 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8763 let mut selections = self.selections.all_adjusted(cx);
8764 let buffer = self.buffer.read(cx);
8765 let snapshot = buffer.snapshot(cx);
8766 let rows_iter = selections.iter().map(|s| s.head().row);
8767 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8768
8769 let has_some_cursor_in_whitespace = selections
8770 .iter()
8771 .filter(|selection| selection.is_empty())
8772 .any(|selection| {
8773 let cursor = selection.head();
8774 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8775 cursor.column < current_indent.len
8776 });
8777
8778 let mut edits = Vec::new();
8779 let mut prev_edited_row = 0;
8780 let mut row_delta = 0;
8781 for selection in &mut selections {
8782 if selection.start.row != prev_edited_row {
8783 row_delta = 0;
8784 }
8785 prev_edited_row = selection.end.row;
8786
8787 // If the selection is non-empty, then increase the indentation of the selected lines.
8788 if !selection.is_empty() {
8789 row_delta =
8790 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8791 continue;
8792 }
8793
8794 let cursor = selection.head();
8795 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8796 if let Some(suggested_indent) =
8797 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8798 {
8799 // Don't do anything if already at suggested indent
8800 // and there is any other cursor which is not
8801 if has_some_cursor_in_whitespace
8802 && cursor.column == current_indent.len
8803 && current_indent.len == suggested_indent.len
8804 {
8805 continue;
8806 }
8807
8808 // Adjust line and move cursor to suggested indent
8809 // if cursor is not at suggested indent
8810 if cursor.column < suggested_indent.len
8811 && cursor.column <= current_indent.len
8812 && current_indent.len <= suggested_indent.len
8813 {
8814 selection.start = Point::new(cursor.row, suggested_indent.len);
8815 selection.end = selection.start;
8816 if row_delta == 0 {
8817 edits.extend(Buffer::edit_for_indent_size_adjustment(
8818 cursor.row,
8819 current_indent,
8820 suggested_indent,
8821 ));
8822 row_delta = suggested_indent.len - current_indent.len;
8823 }
8824 continue;
8825 }
8826
8827 // If current indent is more than suggested indent
8828 // only move cursor to current indent and skip indent
8829 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
8830 selection.start = Point::new(cursor.row, current_indent.len);
8831 selection.end = selection.start;
8832 continue;
8833 }
8834 }
8835
8836 // Otherwise, insert a hard or soft tab.
8837 let settings = buffer.language_settings_at(cursor, cx);
8838 let tab_size = if settings.hard_tabs {
8839 IndentSize::tab()
8840 } else {
8841 let tab_size = settings.tab_size.get();
8842 let indent_remainder = snapshot
8843 .text_for_range(Point::new(cursor.row, 0)..cursor)
8844 .flat_map(str::chars)
8845 .fold(row_delta % tab_size, |counter: u32, c| {
8846 if c == '\t' {
8847 0
8848 } else {
8849 (counter + 1) % tab_size
8850 }
8851 });
8852
8853 let chars_to_next_tab_stop = tab_size - indent_remainder;
8854 IndentSize::spaces(chars_to_next_tab_stop)
8855 };
8856 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
8857 selection.end = selection.start;
8858 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
8859 row_delta += tab_size.len;
8860 }
8861
8862 self.transact(window, cx, |this, window, cx| {
8863 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8864 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8865 s.select(selections)
8866 });
8867 this.refresh_inline_completion(true, false, window, cx);
8868 });
8869 }
8870
8871 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
8872 if self.read_only(cx) {
8873 return;
8874 }
8875 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8876 let mut selections = self.selections.all::<Point>(cx);
8877 let mut prev_edited_row = 0;
8878 let mut row_delta = 0;
8879 let mut edits = Vec::new();
8880 let buffer = self.buffer.read(cx);
8881 let snapshot = buffer.snapshot(cx);
8882 for selection in &mut selections {
8883 if selection.start.row != prev_edited_row {
8884 row_delta = 0;
8885 }
8886 prev_edited_row = selection.end.row;
8887
8888 row_delta =
8889 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8890 }
8891
8892 self.transact(window, cx, |this, window, cx| {
8893 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8894 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8895 s.select(selections)
8896 });
8897 });
8898 }
8899
8900 fn indent_selection(
8901 buffer: &MultiBuffer,
8902 snapshot: &MultiBufferSnapshot,
8903 selection: &mut Selection<Point>,
8904 edits: &mut Vec<(Range<Point>, String)>,
8905 delta_for_start_row: u32,
8906 cx: &App,
8907 ) -> u32 {
8908 let settings = buffer.language_settings_at(selection.start, cx);
8909 let tab_size = settings.tab_size.get();
8910 let indent_kind = if settings.hard_tabs {
8911 IndentKind::Tab
8912 } else {
8913 IndentKind::Space
8914 };
8915 let mut start_row = selection.start.row;
8916 let mut end_row = selection.end.row + 1;
8917
8918 // If a selection ends at the beginning of a line, don't indent
8919 // that last line.
8920 if selection.end.column == 0 && selection.end.row > selection.start.row {
8921 end_row -= 1;
8922 }
8923
8924 // Avoid re-indenting a row that has already been indented by a
8925 // previous selection, but still update this selection's column
8926 // to reflect that indentation.
8927 if delta_for_start_row > 0 {
8928 start_row += 1;
8929 selection.start.column += delta_for_start_row;
8930 if selection.end.row == selection.start.row {
8931 selection.end.column += delta_for_start_row;
8932 }
8933 }
8934
8935 let mut delta_for_end_row = 0;
8936 let has_multiple_rows = start_row + 1 != end_row;
8937 for row in start_row..end_row {
8938 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
8939 let indent_delta = match (current_indent.kind, indent_kind) {
8940 (IndentKind::Space, IndentKind::Space) => {
8941 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
8942 IndentSize::spaces(columns_to_next_tab_stop)
8943 }
8944 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
8945 (_, IndentKind::Tab) => IndentSize::tab(),
8946 };
8947
8948 let start = if has_multiple_rows || current_indent.len < selection.start.column {
8949 0
8950 } else {
8951 selection.start.column
8952 };
8953 let row_start = Point::new(row, start);
8954 edits.push((
8955 row_start..row_start,
8956 indent_delta.chars().collect::<String>(),
8957 ));
8958
8959 // Update this selection's endpoints to reflect the indentation.
8960 if row == selection.start.row {
8961 selection.start.column += indent_delta.len;
8962 }
8963 if row == selection.end.row {
8964 selection.end.column += indent_delta.len;
8965 delta_for_end_row = indent_delta.len;
8966 }
8967 }
8968
8969 if selection.start.row == selection.end.row {
8970 delta_for_start_row + delta_for_end_row
8971 } else {
8972 delta_for_end_row
8973 }
8974 }
8975
8976 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
8977 if self.read_only(cx) {
8978 return;
8979 }
8980 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8981 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8982 let selections = self.selections.all::<Point>(cx);
8983 let mut deletion_ranges = Vec::new();
8984 let mut last_outdent = None;
8985 {
8986 let buffer = self.buffer.read(cx);
8987 let snapshot = buffer.snapshot(cx);
8988 for selection in &selections {
8989 let settings = buffer.language_settings_at(selection.start, cx);
8990 let tab_size = settings.tab_size.get();
8991 let mut rows = selection.spanned_rows(false, &display_map);
8992
8993 // Avoid re-outdenting a row that has already been outdented by a
8994 // previous selection.
8995 if let Some(last_row) = last_outdent {
8996 if last_row == rows.start {
8997 rows.start = rows.start.next_row();
8998 }
8999 }
9000 let has_multiple_rows = rows.len() > 1;
9001 for row in rows.iter_rows() {
9002 let indent_size = snapshot.indent_size_for_line(row);
9003 if indent_size.len > 0 {
9004 let deletion_len = match indent_size.kind {
9005 IndentKind::Space => {
9006 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9007 if columns_to_prev_tab_stop == 0 {
9008 tab_size
9009 } else {
9010 columns_to_prev_tab_stop
9011 }
9012 }
9013 IndentKind::Tab => 1,
9014 };
9015 let start = if has_multiple_rows
9016 || deletion_len > selection.start.column
9017 || indent_size.len < selection.start.column
9018 {
9019 0
9020 } else {
9021 selection.start.column - deletion_len
9022 };
9023 deletion_ranges.push(
9024 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9025 );
9026 last_outdent = Some(row);
9027 }
9028 }
9029 }
9030 }
9031
9032 self.transact(window, cx, |this, window, cx| {
9033 this.buffer.update(cx, |buffer, cx| {
9034 let empty_str: Arc<str> = Arc::default();
9035 buffer.edit(
9036 deletion_ranges
9037 .into_iter()
9038 .map(|range| (range, empty_str.clone())),
9039 None,
9040 cx,
9041 );
9042 });
9043 let selections = this.selections.all::<usize>(cx);
9044 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9045 s.select(selections)
9046 });
9047 });
9048 }
9049
9050 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9051 if self.read_only(cx) {
9052 return;
9053 }
9054 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9055 let selections = self
9056 .selections
9057 .all::<usize>(cx)
9058 .into_iter()
9059 .map(|s| s.range());
9060
9061 self.transact(window, cx, |this, window, cx| {
9062 this.buffer.update(cx, |buffer, cx| {
9063 buffer.autoindent_ranges(selections, cx);
9064 });
9065 let selections = this.selections.all::<usize>(cx);
9066 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9067 s.select(selections)
9068 });
9069 });
9070 }
9071
9072 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9073 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9074 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9075 let selections = self.selections.all::<Point>(cx);
9076
9077 let mut new_cursors = Vec::new();
9078 let mut edit_ranges = Vec::new();
9079 let mut selections = selections.iter().peekable();
9080 while let Some(selection) = selections.next() {
9081 let mut rows = selection.spanned_rows(false, &display_map);
9082 let goal_display_column = selection.head().to_display_point(&display_map).column();
9083
9084 // Accumulate contiguous regions of rows that we want to delete.
9085 while let Some(next_selection) = selections.peek() {
9086 let next_rows = next_selection.spanned_rows(false, &display_map);
9087 if next_rows.start <= rows.end {
9088 rows.end = next_rows.end;
9089 selections.next().unwrap();
9090 } else {
9091 break;
9092 }
9093 }
9094
9095 let buffer = &display_map.buffer_snapshot;
9096 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9097 let edit_end;
9098 let cursor_buffer_row;
9099 if buffer.max_point().row >= rows.end.0 {
9100 // If there's a line after the range, delete the \n from the end of the row range
9101 // and position the cursor on the next line.
9102 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9103 cursor_buffer_row = rows.end;
9104 } else {
9105 // If there isn't a line after the range, delete the \n from the line before the
9106 // start of the row range and position the cursor there.
9107 edit_start = edit_start.saturating_sub(1);
9108 edit_end = buffer.len();
9109 cursor_buffer_row = rows.start.previous_row();
9110 }
9111
9112 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9113 *cursor.column_mut() =
9114 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9115
9116 new_cursors.push((
9117 selection.id,
9118 buffer.anchor_after(cursor.to_point(&display_map)),
9119 ));
9120 edit_ranges.push(edit_start..edit_end);
9121 }
9122
9123 self.transact(window, cx, |this, window, cx| {
9124 let buffer = this.buffer.update(cx, |buffer, cx| {
9125 let empty_str: Arc<str> = Arc::default();
9126 buffer.edit(
9127 edit_ranges
9128 .into_iter()
9129 .map(|range| (range, empty_str.clone())),
9130 None,
9131 cx,
9132 );
9133 buffer.snapshot(cx)
9134 });
9135 let new_selections = new_cursors
9136 .into_iter()
9137 .map(|(id, cursor)| {
9138 let cursor = cursor.to_point(&buffer);
9139 Selection {
9140 id,
9141 start: cursor,
9142 end: cursor,
9143 reversed: false,
9144 goal: SelectionGoal::None,
9145 }
9146 })
9147 .collect();
9148
9149 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9150 s.select(new_selections);
9151 });
9152 });
9153 }
9154
9155 pub fn join_lines_impl(
9156 &mut self,
9157 insert_whitespace: bool,
9158 window: &mut Window,
9159 cx: &mut Context<Self>,
9160 ) {
9161 if self.read_only(cx) {
9162 return;
9163 }
9164 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9165 for selection in self.selections.all::<Point>(cx) {
9166 let start = MultiBufferRow(selection.start.row);
9167 // Treat single line selections as if they include the next line. Otherwise this action
9168 // would do nothing for single line selections individual cursors.
9169 let end = if selection.start.row == selection.end.row {
9170 MultiBufferRow(selection.start.row + 1)
9171 } else {
9172 MultiBufferRow(selection.end.row)
9173 };
9174
9175 if let Some(last_row_range) = row_ranges.last_mut() {
9176 if start <= last_row_range.end {
9177 last_row_range.end = end;
9178 continue;
9179 }
9180 }
9181 row_ranges.push(start..end);
9182 }
9183
9184 let snapshot = self.buffer.read(cx).snapshot(cx);
9185 let mut cursor_positions = Vec::new();
9186 for row_range in &row_ranges {
9187 let anchor = snapshot.anchor_before(Point::new(
9188 row_range.end.previous_row().0,
9189 snapshot.line_len(row_range.end.previous_row()),
9190 ));
9191 cursor_positions.push(anchor..anchor);
9192 }
9193
9194 self.transact(window, cx, |this, window, cx| {
9195 for row_range in row_ranges.into_iter().rev() {
9196 for row in row_range.iter_rows().rev() {
9197 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9198 let next_line_row = row.next_row();
9199 let indent = snapshot.indent_size_for_line(next_line_row);
9200 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9201
9202 let replace =
9203 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9204 " "
9205 } else {
9206 ""
9207 };
9208
9209 this.buffer.update(cx, |buffer, cx| {
9210 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9211 });
9212 }
9213 }
9214
9215 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9216 s.select_anchor_ranges(cursor_positions)
9217 });
9218 });
9219 }
9220
9221 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9222 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9223 self.join_lines_impl(true, window, cx);
9224 }
9225
9226 pub fn sort_lines_case_sensitive(
9227 &mut self,
9228 _: &SortLinesCaseSensitive,
9229 window: &mut Window,
9230 cx: &mut Context<Self>,
9231 ) {
9232 self.manipulate_lines(window, cx, |lines| lines.sort())
9233 }
9234
9235 pub fn sort_lines_case_insensitive(
9236 &mut self,
9237 _: &SortLinesCaseInsensitive,
9238 window: &mut Window,
9239 cx: &mut Context<Self>,
9240 ) {
9241 self.manipulate_lines(window, cx, |lines| {
9242 lines.sort_by_key(|line| line.to_lowercase())
9243 })
9244 }
9245
9246 pub fn unique_lines_case_insensitive(
9247 &mut self,
9248 _: &UniqueLinesCaseInsensitive,
9249 window: &mut Window,
9250 cx: &mut Context<Self>,
9251 ) {
9252 self.manipulate_lines(window, cx, |lines| {
9253 let mut seen = HashSet::default();
9254 lines.retain(|line| seen.insert(line.to_lowercase()));
9255 })
9256 }
9257
9258 pub fn unique_lines_case_sensitive(
9259 &mut self,
9260 _: &UniqueLinesCaseSensitive,
9261 window: &mut Window,
9262 cx: &mut Context<Self>,
9263 ) {
9264 self.manipulate_lines(window, cx, |lines| {
9265 let mut seen = HashSet::default();
9266 lines.retain(|line| seen.insert(*line));
9267 })
9268 }
9269
9270 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9271 let Some(project) = self.project.clone() else {
9272 return;
9273 };
9274 self.reload(project, window, cx)
9275 .detach_and_notify_err(window, cx);
9276 }
9277
9278 pub fn restore_file(
9279 &mut self,
9280 _: &::git::RestoreFile,
9281 window: &mut Window,
9282 cx: &mut Context<Self>,
9283 ) {
9284 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9285 let mut buffer_ids = HashSet::default();
9286 let snapshot = self.buffer().read(cx).snapshot(cx);
9287 for selection in self.selections.all::<usize>(cx) {
9288 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9289 }
9290
9291 let buffer = self.buffer().read(cx);
9292 let ranges = buffer_ids
9293 .into_iter()
9294 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9295 .collect::<Vec<_>>();
9296
9297 self.restore_hunks_in_ranges(ranges, window, cx);
9298 }
9299
9300 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9301 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9302 let selections = self
9303 .selections
9304 .all(cx)
9305 .into_iter()
9306 .map(|s| s.range())
9307 .collect();
9308 self.restore_hunks_in_ranges(selections, window, cx);
9309 }
9310
9311 pub fn restore_hunks_in_ranges(
9312 &mut self,
9313 ranges: Vec<Range<Point>>,
9314 window: &mut Window,
9315 cx: &mut Context<Editor>,
9316 ) {
9317 let mut revert_changes = HashMap::default();
9318 let chunk_by = self
9319 .snapshot(window, cx)
9320 .hunks_for_ranges(ranges)
9321 .into_iter()
9322 .chunk_by(|hunk| hunk.buffer_id);
9323 for (buffer_id, hunks) in &chunk_by {
9324 let hunks = hunks.collect::<Vec<_>>();
9325 for hunk in &hunks {
9326 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9327 }
9328 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9329 }
9330 drop(chunk_by);
9331 if !revert_changes.is_empty() {
9332 self.transact(window, cx, |editor, window, cx| {
9333 editor.restore(revert_changes, window, cx);
9334 });
9335 }
9336 }
9337
9338 pub fn open_active_item_in_terminal(
9339 &mut self,
9340 _: &OpenInTerminal,
9341 window: &mut Window,
9342 cx: &mut Context<Self>,
9343 ) {
9344 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9345 let project_path = buffer.read(cx).project_path(cx)?;
9346 let project = self.project.as_ref()?.read(cx);
9347 let entry = project.entry_for_path(&project_path, cx)?;
9348 let parent = match &entry.canonical_path {
9349 Some(canonical_path) => canonical_path.to_path_buf(),
9350 None => project.absolute_path(&project_path, cx)?,
9351 }
9352 .parent()?
9353 .to_path_buf();
9354 Some(parent)
9355 }) {
9356 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9357 }
9358 }
9359
9360 fn set_breakpoint_context_menu(
9361 &mut self,
9362 display_row: DisplayRow,
9363 position: Option<Anchor>,
9364 clicked_point: gpui::Point<Pixels>,
9365 window: &mut Window,
9366 cx: &mut Context<Self>,
9367 ) {
9368 if !cx.has_flag::<DebuggerFeatureFlag>() {
9369 return;
9370 }
9371 let source = self
9372 .buffer
9373 .read(cx)
9374 .snapshot(cx)
9375 .anchor_before(Point::new(display_row.0, 0u32));
9376
9377 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9378
9379 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9380 self,
9381 source,
9382 clicked_point,
9383 context_menu,
9384 window,
9385 cx,
9386 );
9387 }
9388
9389 fn add_edit_breakpoint_block(
9390 &mut self,
9391 anchor: Anchor,
9392 breakpoint: &Breakpoint,
9393 edit_action: BreakpointPromptEditAction,
9394 window: &mut Window,
9395 cx: &mut Context<Self>,
9396 ) {
9397 let weak_editor = cx.weak_entity();
9398 let bp_prompt = cx.new(|cx| {
9399 BreakpointPromptEditor::new(
9400 weak_editor,
9401 anchor,
9402 breakpoint.clone(),
9403 edit_action,
9404 window,
9405 cx,
9406 )
9407 });
9408
9409 let height = bp_prompt.update(cx, |this, cx| {
9410 this.prompt
9411 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9412 });
9413 let cloned_prompt = bp_prompt.clone();
9414 let blocks = vec![BlockProperties {
9415 style: BlockStyle::Sticky,
9416 placement: BlockPlacement::Above(anchor),
9417 height: Some(height),
9418 render: Arc::new(move |cx| {
9419 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9420 cloned_prompt.clone().into_any_element()
9421 }),
9422 priority: 0,
9423 render_in_minimap: true,
9424 }];
9425
9426 let focus_handle = bp_prompt.focus_handle(cx);
9427 window.focus(&focus_handle);
9428
9429 let block_ids = self.insert_blocks(blocks, None, cx);
9430 bp_prompt.update(cx, |prompt, _| {
9431 prompt.add_block_ids(block_ids);
9432 });
9433 }
9434
9435 pub(crate) fn breakpoint_at_row(
9436 &self,
9437 row: u32,
9438 window: &mut Window,
9439 cx: &mut Context<Self>,
9440 ) -> Option<(Anchor, Breakpoint)> {
9441 let snapshot = self.snapshot(window, cx);
9442 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9443
9444 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9445 }
9446
9447 pub(crate) fn breakpoint_at_anchor(
9448 &self,
9449 breakpoint_position: Anchor,
9450 snapshot: &EditorSnapshot,
9451 cx: &mut Context<Self>,
9452 ) -> Option<(Anchor, Breakpoint)> {
9453 let project = self.project.clone()?;
9454
9455 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9456 snapshot
9457 .buffer_snapshot
9458 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9459 })?;
9460
9461 let enclosing_excerpt = breakpoint_position.excerpt_id;
9462 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
9463 let buffer_snapshot = buffer.read(cx).snapshot();
9464
9465 let row = buffer_snapshot
9466 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9467 .row;
9468
9469 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9470 let anchor_end = snapshot
9471 .buffer_snapshot
9472 .anchor_after(Point::new(row, line_len));
9473
9474 let bp = self
9475 .breakpoint_store
9476 .as_ref()?
9477 .read_with(cx, |breakpoint_store, cx| {
9478 breakpoint_store
9479 .breakpoints(
9480 &buffer,
9481 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9482 &buffer_snapshot,
9483 cx,
9484 )
9485 .next()
9486 .and_then(|(anchor, bp)| {
9487 let breakpoint_row = buffer_snapshot
9488 .summary_for_anchor::<text::PointUtf16>(anchor)
9489 .row;
9490
9491 if breakpoint_row == row {
9492 snapshot
9493 .buffer_snapshot
9494 .anchor_in_excerpt(enclosing_excerpt, *anchor)
9495 .map(|anchor| (anchor, bp.clone()))
9496 } else {
9497 None
9498 }
9499 })
9500 });
9501 bp
9502 }
9503
9504 pub fn edit_log_breakpoint(
9505 &mut self,
9506 _: &EditLogBreakpoint,
9507 window: &mut Window,
9508 cx: &mut Context<Self>,
9509 ) {
9510 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9511 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9512 message: None,
9513 state: BreakpointState::Enabled,
9514 condition: None,
9515 hit_condition: None,
9516 });
9517
9518 self.add_edit_breakpoint_block(
9519 anchor,
9520 &breakpoint,
9521 BreakpointPromptEditAction::Log,
9522 window,
9523 cx,
9524 );
9525 }
9526 }
9527
9528 fn breakpoints_at_cursors(
9529 &self,
9530 window: &mut Window,
9531 cx: &mut Context<Self>,
9532 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9533 let snapshot = self.snapshot(window, cx);
9534 let cursors = self
9535 .selections
9536 .disjoint_anchors()
9537 .into_iter()
9538 .map(|selection| {
9539 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9540
9541 let breakpoint_position = self
9542 .breakpoint_at_row(cursor_position.row, window, cx)
9543 .map(|bp| bp.0)
9544 .unwrap_or_else(|| {
9545 snapshot
9546 .display_snapshot
9547 .buffer_snapshot
9548 .anchor_after(Point::new(cursor_position.row, 0))
9549 });
9550
9551 let breakpoint = self
9552 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9553 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9554
9555 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9556 })
9557 // 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.
9558 .collect::<HashMap<Anchor, _>>();
9559
9560 cursors.into_iter().collect()
9561 }
9562
9563 pub fn enable_breakpoint(
9564 &mut self,
9565 _: &crate::actions::EnableBreakpoint,
9566 window: &mut Window,
9567 cx: &mut Context<Self>,
9568 ) {
9569 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9570 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9571 continue;
9572 };
9573 self.edit_breakpoint_at_anchor(
9574 anchor,
9575 breakpoint,
9576 BreakpointEditAction::InvertState,
9577 cx,
9578 );
9579 }
9580 }
9581
9582 pub fn disable_breakpoint(
9583 &mut self,
9584 _: &crate::actions::DisableBreakpoint,
9585 window: &mut Window,
9586 cx: &mut Context<Self>,
9587 ) {
9588 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9589 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9590 continue;
9591 };
9592 self.edit_breakpoint_at_anchor(
9593 anchor,
9594 breakpoint,
9595 BreakpointEditAction::InvertState,
9596 cx,
9597 );
9598 }
9599 }
9600
9601 pub fn toggle_breakpoint(
9602 &mut self,
9603 _: &crate::actions::ToggleBreakpoint,
9604 window: &mut Window,
9605 cx: &mut Context<Self>,
9606 ) {
9607 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9608 if let Some(breakpoint) = breakpoint {
9609 self.edit_breakpoint_at_anchor(
9610 anchor,
9611 breakpoint,
9612 BreakpointEditAction::Toggle,
9613 cx,
9614 );
9615 } else {
9616 self.edit_breakpoint_at_anchor(
9617 anchor,
9618 Breakpoint::new_standard(),
9619 BreakpointEditAction::Toggle,
9620 cx,
9621 );
9622 }
9623 }
9624 }
9625
9626 pub fn edit_breakpoint_at_anchor(
9627 &mut self,
9628 breakpoint_position: Anchor,
9629 breakpoint: Breakpoint,
9630 edit_action: BreakpointEditAction,
9631 cx: &mut Context<Self>,
9632 ) {
9633 let Some(breakpoint_store) = &self.breakpoint_store else {
9634 return;
9635 };
9636
9637 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9638 if breakpoint_position == Anchor::min() {
9639 self.buffer()
9640 .read(cx)
9641 .excerpt_buffer_ids()
9642 .into_iter()
9643 .next()
9644 } else {
9645 None
9646 }
9647 }) else {
9648 return;
9649 };
9650
9651 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9652 return;
9653 };
9654
9655 breakpoint_store.update(cx, |breakpoint_store, cx| {
9656 breakpoint_store.toggle_breakpoint(
9657 buffer,
9658 (breakpoint_position.text_anchor, breakpoint),
9659 edit_action,
9660 cx,
9661 );
9662 });
9663
9664 cx.notify();
9665 }
9666
9667 #[cfg(any(test, feature = "test-support"))]
9668 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9669 self.breakpoint_store.clone()
9670 }
9671
9672 pub fn prepare_restore_change(
9673 &self,
9674 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9675 hunk: &MultiBufferDiffHunk,
9676 cx: &mut App,
9677 ) -> Option<()> {
9678 if hunk.is_created_file() {
9679 return None;
9680 }
9681 let buffer = self.buffer.read(cx);
9682 let diff = buffer.diff_for(hunk.buffer_id)?;
9683 let buffer = buffer.buffer(hunk.buffer_id)?;
9684 let buffer = buffer.read(cx);
9685 let original_text = diff
9686 .read(cx)
9687 .base_text()
9688 .as_rope()
9689 .slice(hunk.diff_base_byte_range.clone());
9690 let buffer_snapshot = buffer.snapshot();
9691 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9692 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9693 probe
9694 .0
9695 .start
9696 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9697 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9698 }) {
9699 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9700 Some(())
9701 } else {
9702 None
9703 }
9704 }
9705
9706 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9707 self.manipulate_lines(window, cx, |lines| lines.reverse())
9708 }
9709
9710 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9711 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9712 }
9713
9714 fn manipulate_lines<Fn>(
9715 &mut self,
9716 window: &mut Window,
9717 cx: &mut Context<Self>,
9718 mut callback: Fn,
9719 ) where
9720 Fn: FnMut(&mut Vec<&str>),
9721 {
9722 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9723
9724 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9725 let buffer = self.buffer.read(cx).snapshot(cx);
9726
9727 let mut edits = Vec::new();
9728
9729 let selections = self.selections.all::<Point>(cx);
9730 let mut selections = selections.iter().peekable();
9731 let mut contiguous_row_selections = Vec::new();
9732 let mut new_selections = Vec::new();
9733 let mut added_lines = 0;
9734 let mut removed_lines = 0;
9735
9736 while let Some(selection) = selections.next() {
9737 let (start_row, end_row) = consume_contiguous_rows(
9738 &mut contiguous_row_selections,
9739 selection,
9740 &display_map,
9741 &mut selections,
9742 );
9743
9744 let start_point = Point::new(start_row.0, 0);
9745 let end_point = Point::new(
9746 end_row.previous_row().0,
9747 buffer.line_len(end_row.previous_row()),
9748 );
9749 let text = buffer
9750 .text_for_range(start_point..end_point)
9751 .collect::<String>();
9752
9753 let mut lines = text.split('\n').collect_vec();
9754
9755 let lines_before = lines.len();
9756 callback(&mut lines);
9757 let lines_after = lines.len();
9758
9759 edits.push((start_point..end_point, lines.join("\n")));
9760
9761 // Selections must change based on added and removed line count
9762 let start_row =
9763 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9764 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9765 new_selections.push(Selection {
9766 id: selection.id,
9767 start: start_row,
9768 end: end_row,
9769 goal: SelectionGoal::None,
9770 reversed: selection.reversed,
9771 });
9772
9773 if lines_after > lines_before {
9774 added_lines += lines_after - lines_before;
9775 } else if lines_before > lines_after {
9776 removed_lines += lines_before - lines_after;
9777 }
9778 }
9779
9780 self.transact(window, cx, |this, window, cx| {
9781 let buffer = this.buffer.update(cx, |buffer, cx| {
9782 buffer.edit(edits, None, cx);
9783 buffer.snapshot(cx)
9784 });
9785
9786 // Recalculate offsets on newly edited buffer
9787 let new_selections = new_selections
9788 .iter()
9789 .map(|s| {
9790 let start_point = Point::new(s.start.0, 0);
9791 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9792 Selection {
9793 id: s.id,
9794 start: buffer.point_to_offset(start_point),
9795 end: buffer.point_to_offset(end_point),
9796 goal: s.goal,
9797 reversed: s.reversed,
9798 }
9799 })
9800 .collect();
9801
9802 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9803 s.select(new_selections);
9804 });
9805
9806 this.request_autoscroll(Autoscroll::fit(), cx);
9807 });
9808 }
9809
9810 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
9811 self.manipulate_text(window, cx, |text| {
9812 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
9813 if has_upper_case_characters {
9814 text.to_lowercase()
9815 } else {
9816 text.to_uppercase()
9817 }
9818 })
9819 }
9820
9821 pub fn convert_to_upper_case(
9822 &mut self,
9823 _: &ConvertToUpperCase,
9824 window: &mut Window,
9825 cx: &mut Context<Self>,
9826 ) {
9827 self.manipulate_text(window, cx, |text| text.to_uppercase())
9828 }
9829
9830 pub fn convert_to_lower_case(
9831 &mut self,
9832 _: &ConvertToLowerCase,
9833 window: &mut Window,
9834 cx: &mut Context<Self>,
9835 ) {
9836 self.manipulate_text(window, cx, |text| text.to_lowercase())
9837 }
9838
9839 pub fn convert_to_title_case(
9840 &mut self,
9841 _: &ConvertToTitleCase,
9842 window: &mut Window,
9843 cx: &mut Context<Self>,
9844 ) {
9845 self.manipulate_text(window, cx, |text| {
9846 text.split('\n')
9847 .map(|line| line.to_case(Case::Title))
9848 .join("\n")
9849 })
9850 }
9851
9852 pub fn convert_to_snake_case(
9853 &mut self,
9854 _: &ConvertToSnakeCase,
9855 window: &mut Window,
9856 cx: &mut Context<Self>,
9857 ) {
9858 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
9859 }
9860
9861 pub fn convert_to_kebab_case(
9862 &mut self,
9863 _: &ConvertToKebabCase,
9864 window: &mut Window,
9865 cx: &mut Context<Self>,
9866 ) {
9867 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
9868 }
9869
9870 pub fn convert_to_upper_camel_case(
9871 &mut self,
9872 _: &ConvertToUpperCamelCase,
9873 window: &mut Window,
9874 cx: &mut Context<Self>,
9875 ) {
9876 self.manipulate_text(window, cx, |text| {
9877 text.split('\n')
9878 .map(|line| line.to_case(Case::UpperCamel))
9879 .join("\n")
9880 })
9881 }
9882
9883 pub fn convert_to_lower_camel_case(
9884 &mut self,
9885 _: &ConvertToLowerCamelCase,
9886 window: &mut Window,
9887 cx: &mut Context<Self>,
9888 ) {
9889 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
9890 }
9891
9892 pub fn convert_to_opposite_case(
9893 &mut self,
9894 _: &ConvertToOppositeCase,
9895 window: &mut Window,
9896 cx: &mut Context<Self>,
9897 ) {
9898 self.manipulate_text(window, cx, |text| {
9899 text.chars()
9900 .fold(String::with_capacity(text.len()), |mut t, c| {
9901 if c.is_uppercase() {
9902 t.extend(c.to_lowercase());
9903 } else {
9904 t.extend(c.to_uppercase());
9905 }
9906 t
9907 })
9908 })
9909 }
9910
9911 pub fn convert_to_rot13(
9912 &mut self,
9913 _: &ConvertToRot13,
9914 window: &mut Window,
9915 cx: &mut Context<Self>,
9916 ) {
9917 self.manipulate_text(window, cx, |text| {
9918 text.chars()
9919 .map(|c| match c {
9920 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
9921 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
9922 _ => c,
9923 })
9924 .collect()
9925 })
9926 }
9927
9928 pub fn convert_to_rot47(
9929 &mut self,
9930 _: &ConvertToRot47,
9931 window: &mut Window,
9932 cx: &mut Context<Self>,
9933 ) {
9934 self.manipulate_text(window, cx, |text| {
9935 text.chars()
9936 .map(|c| {
9937 let code_point = c as u32;
9938 if code_point >= 33 && code_point <= 126 {
9939 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
9940 }
9941 c
9942 })
9943 .collect()
9944 })
9945 }
9946
9947 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
9948 where
9949 Fn: FnMut(&str) -> String,
9950 {
9951 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9952 let buffer = self.buffer.read(cx).snapshot(cx);
9953
9954 let mut new_selections = Vec::new();
9955 let mut edits = Vec::new();
9956 let mut selection_adjustment = 0i32;
9957
9958 for selection in self.selections.all::<usize>(cx) {
9959 let selection_is_empty = selection.is_empty();
9960
9961 let (start, end) = if selection_is_empty {
9962 let word_range = movement::surrounding_word(
9963 &display_map,
9964 selection.start.to_display_point(&display_map),
9965 );
9966 let start = word_range.start.to_offset(&display_map, Bias::Left);
9967 let end = word_range.end.to_offset(&display_map, Bias::Left);
9968 (start, end)
9969 } else {
9970 (selection.start, selection.end)
9971 };
9972
9973 let text = buffer.text_for_range(start..end).collect::<String>();
9974 let old_length = text.len() as i32;
9975 let text = callback(&text);
9976
9977 new_selections.push(Selection {
9978 start: (start as i32 - selection_adjustment) as usize,
9979 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
9980 goal: SelectionGoal::None,
9981 ..selection
9982 });
9983
9984 selection_adjustment += old_length - text.len() as i32;
9985
9986 edits.push((start..end, text));
9987 }
9988
9989 self.transact(window, cx, |this, window, cx| {
9990 this.buffer.update(cx, |buffer, cx| {
9991 buffer.edit(edits, None, cx);
9992 });
9993
9994 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9995 s.select(new_selections);
9996 });
9997
9998 this.request_autoscroll(Autoscroll::fit(), cx);
9999 });
10000 }
10001
10002 pub fn duplicate(
10003 &mut self,
10004 upwards: bool,
10005 whole_lines: bool,
10006 window: &mut Window,
10007 cx: &mut Context<Self>,
10008 ) {
10009 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10010
10011 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10012 let buffer = &display_map.buffer_snapshot;
10013 let selections = self.selections.all::<Point>(cx);
10014
10015 let mut edits = Vec::new();
10016 let mut selections_iter = selections.iter().peekable();
10017 while let Some(selection) = selections_iter.next() {
10018 let mut rows = selection.spanned_rows(false, &display_map);
10019 // duplicate line-wise
10020 if whole_lines || selection.start == selection.end {
10021 // Avoid duplicating the same lines twice.
10022 while let Some(next_selection) = selections_iter.peek() {
10023 let next_rows = next_selection.spanned_rows(false, &display_map);
10024 if next_rows.start < rows.end {
10025 rows.end = next_rows.end;
10026 selections_iter.next().unwrap();
10027 } else {
10028 break;
10029 }
10030 }
10031
10032 // Copy the text from the selected row region and splice it either at the start
10033 // or end of the region.
10034 let start = Point::new(rows.start.0, 0);
10035 let end = Point::new(
10036 rows.end.previous_row().0,
10037 buffer.line_len(rows.end.previous_row()),
10038 );
10039 let text = buffer
10040 .text_for_range(start..end)
10041 .chain(Some("\n"))
10042 .collect::<String>();
10043 let insert_location = if upwards {
10044 Point::new(rows.end.0, 0)
10045 } else {
10046 start
10047 };
10048 edits.push((insert_location..insert_location, text));
10049 } else {
10050 // duplicate character-wise
10051 let start = selection.start;
10052 let end = selection.end;
10053 let text = buffer.text_for_range(start..end).collect::<String>();
10054 edits.push((selection.end..selection.end, text));
10055 }
10056 }
10057
10058 self.transact(window, cx, |this, _, cx| {
10059 this.buffer.update(cx, |buffer, cx| {
10060 buffer.edit(edits, None, cx);
10061 });
10062
10063 this.request_autoscroll(Autoscroll::fit(), cx);
10064 });
10065 }
10066
10067 pub fn duplicate_line_up(
10068 &mut self,
10069 _: &DuplicateLineUp,
10070 window: &mut Window,
10071 cx: &mut Context<Self>,
10072 ) {
10073 self.duplicate(true, true, window, cx);
10074 }
10075
10076 pub fn duplicate_line_down(
10077 &mut self,
10078 _: &DuplicateLineDown,
10079 window: &mut Window,
10080 cx: &mut Context<Self>,
10081 ) {
10082 self.duplicate(false, true, window, cx);
10083 }
10084
10085 pub fn duplicate_selection(
10086 &mut self,
10087 _: &DuplicateSelection,
10088 window: &mut Window,
10089 cx: &mut Context<Self>,
10090 ) {
10091 self.duplicate(false, false, window, cx);
10092 }
10093
10094 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10095 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10096
10097 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10098 let buffer = self.buffer.read(cx).snapshot(cx);
10099
10100 let mut edits = Vec::new();
10101 let mut unfold_ranges = Vec::new();
10102 let mut refold_creases = Vec::new();
10103
10104 let selections = self.selections.all::<Point>(cx);
10105 let mut selections = selections.iter().peekable();
10106 let mut contiguous_row_selections = Vec::new();
10107 let mut new_selections = Vec::new();
10108
10109 while let Some(selection) = selections.next() {
10110 // Find all the selections that span a contiguous row range
10111 let (start_row, end_row) = consume_contiguous_rows(
10112 &mut contiguous_row_selections,
10113 selection,
10114 &display_map,
10115 &mut selections,
10116 );
10117
10118 // Move the text spanned by the row range to be before the line preceding the row range
10119 if start_row.0 > 0 {
10120 let range_to_move = Point::new(
10121 start_row.previous_row().0,
10122 buffer.line_len(start_row.previous_row()),
10123 )
10124 ..Point::new(
10125 end_row.previous_row().0,
10126 buffer.line_len(end_row.previous_row()),
10127 );
10128 let insertion_point = display_map
10129 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10130 .0;
10131
10132 // Don't move lines across excerpts
10133 if buffer
10134 .excerpt_containing(insertion_point..range_to_move.end)
10135 .is_some()
10136 {
10137 let text = buffer
10138 .text_for_range(range_to_move.clone())
10139 .flat_map(|s| s.chars())
10140 .skip(1)
10141 .chain(['\n'])
10142 .collect::<String>();
10143
10144 edits.push((
10145 buffer.anchor_after(range_to_move.start)
10146 ..buffer.anchor_before(range_to_move.end),
10147 String::new(),
10148 ));
10149 let insertion_anchor = buffer.anchor_after(insertion_point);
10150 edits.push((insertion_anchor..insertion_anchor, text));
10151
10152 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10153
10154 // Move selections up
10155 new_selections.extend(contiguous_row_selections.drain(..).map(
10156 |mut selection| {
10157 selection.start.row -= row_delta;
10158 selection.end.row -= row_delta;
10159 selection
10160 },
10161 ));
10162
10163 // Move folds up
10164 unfold_ranges.push(range_to_move.clone());
10165 for fold in display_map.folds_in_range(
10166 buffer.anchor_before(range_to_move.start)
10167 ..buffer.anchor_after(range_to_move.end),
10168 ) {
10169 let mut start = fold.range.start.to_point(&buffer);
10170 let mut end = fold.range.end.to_point(&buffer);
10171 start.row -= row_delta;
10172 end.row -= row_delta;
10173 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10174 }
10175 }
10176 }
10177
10178 // If we didn't move line(s), preserve the existing selections
10179 new_selections.append(&mut contiguous_row_selections);
10180 }
10181
10182 self.transact(window, cx, |this, window, cx| {
10183 this.unfold_ranges(&unfold_ranges, true, true, cx);
10184 this.buffer.update(cx, |buffer, cx| {
10185 for (range, text) in edits {
10186 buffer.edit([(range, text)], None, cx);
10187 }
10188 });
10189 this.fold_creases(refold_creases, true, window, cx);
10190 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10191 s.select(new_selections);
10192 })
10193 });
10194 }
10195
10196 pub fn move_line_down(
10197 &mut self,
10198 _: &MoveLineDown,
10199 window: &mut Window,
10200 cx: &mut Context<Self>,
10201 ) {
10202 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10203
10204 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10205 let buffer = self.buffer.read(cx).snapshot(cx);
10206
10207 let mut edits = Vec::new();
10208 let mut unfold_ranges = Vec::new();
10209 let mut refold_creases = Vec::new();
10210
10211 let selections = self.selections.all::<Point>(cx);
10212 let mut selections = selections.iter().peekable();
10213 let mut contiguous_row_selections = Vec::new();
10214 let mut new_selections = Vec::new();
10215
10216 while let Some(selection) = selections.next() {
10217 // Find all the selections that span a contiguous row range
10218 let (start_row, end_row) = consume_contiguous_rows(
10219 &mut contiguous_row_selections,
10220 selection,
10221 &display_map,
10222 &mut selections,
10223 );
10224
10225 // Move the text spanned by the row range to be after the last line of the row range
10226 if end_row.0 <= buffer.max_point().row {
10227 let range_to_move =
10228 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10229 let insertion_point = display_map
10230 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10231 .0;
10232
10233 // Don't move lines across excerpt boundaries
10234 if buffer
10235 .excerpt_containing(range_to_move.start..insertion_point)
10236 .is_some()
10237 {
10238 let mut text = String::from("\n");
10239 text.extend(buffer.text_for_range(range_to_move.clone()));
10240 text.pop(); // Drop trailing newline
10241 edits.push((
10242 buffer.anchor_after(range_to_move.start)
10243 ..buffer.anchor_before(range_to_move.end),
10244 String::new(),
10245 ));
10246 let insertion_anchor = buffer.anchor_after(insertion_point);
10247 edits.push((insertion_anchor..insertion_anchor, text));
10248
10249 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10250
10251 // Move selections down
10252 new_selections.extend(contiguous_row_selections.drain(..).map(
10253 |mut selection| {
10254 selection.start.row += row_delta;
10255 selection.end.row += row_delta;
10256 selection
10257 },
10258 ));
10259
10260 // Move folds down
10261 unfold_ranges.push(range_to_move.clone());
10262 for fold in display_map.folds_in_range(
10263 buffer.anchor_before(range_to_move.start)
10264 ..buffer.anchor_after(range_to_move.end),
10265 ) {
10266 let mut start = fold.range.start.to_point(&buffer);
10267 let mut end = fold.range.end.to_point(&buffer);
10268 start.row += row_delta;
10269 end.row += row_delta;
10270 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10271 }
10272 }
10273 }
10274
10275 // If we didn't move line(s), preserve the existing selections
10276 new_selections.append(&mut contiguous_row_selections);
10277 }
10278
10279 self.transact(window, cx, |this, window, cx| {
10280 this.unfold_ranges(&unfold_ranges, true, true, cx);
10281 this.buffer.update(cx, |buffer, cx| {
10282 for (range, text) in edits {
10283 buffer.edit([(range, text)], None, cx);
10284 }
10285 });
10286 this.fold_creases(refold_creases, true, window, cx);
10287 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10288 s.select(new_selections)
10289 });
10290 });
10291 }
10292
10293 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10294 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10295 let text_layout_details = &self.text_layout_details(window);
10296 self.transact(window, cx, |this, window, cx| {
10297 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10298 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10299 s.move_with(|display_map, selection| {
10300 if !selection.is_empty() {
10301 return;
10302 }
10303
10304 let mut head = selection.head();
10305 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10306 if head.column() == display_map.line_len(head.row()) {
10307 transpose_offset = display_map
10308 .buffer_snapshot
10309 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10310 }
10311
10312 if transpose_offset == 0 {
10313 return;
10314 }
10315
10316 *head.column_mut() += 1;
10317 head = display_map.clip_point(head, Bias::Right);
10318 let goal = SelectionGoal::HorizontalPosition(
10319 display_map
10320 .x_for_display_point(head, text_layout_details)
10321 .into(),
10322 );
10323 selection.collapse_to(head, goal);
10324
10325 let transpose_start = display_map
10326 .buffer_snapshot
10327 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10328 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10329 let transpose_end = display_map
10330 .buffer_snapshot
10331 .clip_offset(transpose_offset + 1, Bias::Right);
10332 if let Some(ch) =
10333 display_map.buffer_snapshot.chars_at(transpose_start).next()
10334 {
10335 edits.push((transpose_start..transpose_offset, String::new()));
10336 edits.push((transpose_end..transpose_end, ch.to_string()));
10337 }
10338 }
10339 });
10340 edits
10341 });
10342 this.buffer
10343 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10344 let selections = this.selections.all::<usize>(cx);
10345 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10346 s.select(selections);
10347 });
10348 });
10349 }
10350
10351 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10352 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10353 self.rewrap_impl(RewrapOptions::default(), cx)
10354 }
10355
10356 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10357 let buffer = self.buffer.read(cx).snapshot(cx);
10358 let selections = self.selections.all::<Point>(cx);
10359 let mut selections = selections.iter().peekable();
10360
10361 let mut edits = Vec::new();
10362 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10363
10364 while let Some(selection) = selections.next() {
10365 let mut start_row = selection.start.row;
10366 let mut end_row = selection.end.row;
10367
10368 // Skip selections that overlap with a range that has already been rewrapped.
10369 let selection_range = start_row..end_row;
10370 if rewrapped_row_ranges
10371 .iter()
10372 .any(|range| range.overlaps(&selection_range))
10373 {
10374 continue;
10375 }
10376
10377 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10378
10379 // Since not all lines in the selection may be at the same indent
10380 // level, choose the indent size that is the most common between all
10381 // of the lines.
10382 //
10383 // If there is a tie, we use the deepest indent.
10384 let (indent_size, indent_end) = {
10385 let mut indent_size_occurrences = HashMap::default();
10386 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10387
10388 for row in start_row..=end_row {
10389 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10390 rows_by_indent_size.entry(indent).or_default().push(row);
10391 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10392 }
10393
10394 let indent_size = indent_size_occurrences
10395 .into_iter()
10396 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10397 .map(|(indent, _)| indent)
10398 .unwrap_or_default();
10399 let row = rows_by_indent_size[&indent_size][0];
10400 let indent_end = Point::new(row, indent_size.len);
10401
10402 (indent_size, indent_end)
10403 };
10404
10405 let mut line_prefix = indent_size.chars().collect::<String>();
10406
10407 let mut inside_comment = false;
10408 if let Some(comment_prefix) =
10409 buffer
10410 .language_scope_at(selection.head())
10411 .and_then(|language| {
10412 language
10413 .line_comment_prefixes()
10414 .iter()
10415 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10416 .cloned()
10417 })
10418 {
10419 line_prefix.push_str(&comment_prefix);
10420 inside_comment = true;
10421 }
10422
10423 let language_settings = buffer.language_settings_at(selection.head(), cx);
10424 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10425 RewrapBehavior::InComments => inside_comment,
10426 RewrapBehavior::InSelections => !selection.is_empty(),
10427 RewrapBehavior::Anywhere => true,
10428 };
10429
10430 let should_rewrap = options.override_language_settings
10431 || allow_rewrap_based_on_language
10432 || self.hard_wrap.is_some();
10433 if !should_rewrap {
10434 continue;
10435 }
10436
10437 if selection.is_empty() {
10438 'expand_upwards: while start_row > 0 {
10439 let prev_row = start_row - 1;
10440 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10441 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10442 {
10443 start_row = prev_row;
10444 } else {
10445 break 'expand_upwards;
10446 }
10447 }
10448
10449 'expand_downwards: while end_row < buffer.max_point().row {
10450 let next_row = end_row + 1;
10451 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10452 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10453 {
10454 end_row = next_row;
10455 } else {
10456 break 'expand_downwards;
10457 }
10458 }
10459 }
10460
10461 let start = Point::new(start_row, 0);
10462 let start_offset = start.to_offset(&buffer);
10463 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10464 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10465 let Some(lines_without_prefixes) = selection_text
10466 .lines()
10467 .map(|line| {
10468 line.strip_prefix(&line_prefix)
10469 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10470 .ok_or_else(|| {
10471 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
10472 })
10473 })
10474 .collect::<Result<Vec<_>, _>>()
10475 .log_err()
10476 else {
10477 continue;
10478 };
10479
10480 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10481 buffer
10482 .language_settings_at(Point::new(start_row, 0), cx)
10483 .preferred_line_length as usize
10484 });
10485 let wrapped_text = wrap_with_prefix(
10486 line_prefix,
10487 lines_without_prefixes.join("\n"),
10488 wrap_column,
10489 tab_size,
10490 options.preserve_existing_whitespace,
10491 );
10492
10493 // TODO: should always use char-based diff while still supporting cursor behavior that
10494 // matches vim.
10495 let mut diff_options = DiffOptions::default();
10496 if options.override_language_settings {
10497 diff_options.max_word_diff_len = 0;
10498 diff_options.max_word_diff_line_count = 0;
10499 } else {
10500 diff_options.max_word_diff_len = usize::MAX;
10501 diff_options.max_word_diff_line_count = usize::MAX;
10502 }
10503
10504 for (old_range, new_text) in
10505 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10506 {
10507 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10508 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10509 edits.push((edit_start..edit_end, new_text));
10510 }
10511
10512 rewrapped_row_ranges.push(start_row..=end_row);
10513 }
10514
10515 self.buffer
10516 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10517 }
10518
10519 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10520 let mut text = String::new();
10521 let buffer = self.buffer.read(cx).snapshot(cx);
10522 let mut selections = self.selections.all::<Point>(cx);
10523 let mut clipboard_selections = Vec::with_capacity(selections.len());
10524 {
10525 let max_point = buffer.max_point();
10526 let mut is_first = true;
10527 for selection in &mut selections {
10528 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10529 if is_entire_line {
10530 selection.start = Point::new(selection.start.row, 0);
10531 if !selection.is_empty() && selection.end.column == 0 {
10532 selection.end = cmp::min(max_point, selection.end);
10533 } else {
10534 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10535 }
10536 selection.goal = SelectionGoal::None;
10537 }
10538 if is_first {
10539 is_first = false;
10540 } else {
10541 text += "\n";
10542 }
10543 let mut len = 0;
10544 for chunk in buffer.text_for_range(selection.start..selection.end) {
10545 text.push_str(chunk);
10546 len += chunk.len();
10547 }
10548 clipboard_selections.push(ClipboardSelection {
10549 len,
10550 is_entire_line,
10551 first_line_indent: buffer
10552 .indent_size_for_line(MultiBufferRow(selection.start.row))
10553 .len,
10554 });
10555 }
10556 }
10557
10558 self.transact(window, cx, |this, window, cx| {
10559 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10560 s.select(selections);
10561 });
10562 this.insert("", window, cx);
10563 });
10564 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10565 }
10566
10567 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10568 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10569 let item = self.cut_common(window, cx);
10570 cx.write_to_clipboard(item);
10571 }
10572
10573 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10574 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10575 self.change_selections(None, window, cx, |s| {
10576 s.move_with(|snapshot, sel| {
10577 if sel.is_empty() {
10578 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10579 }
10580 });
10581 });
10582 let item = self.cut_common(window, cx);
10583 cx.set_global(KillRing(item))
10584 }
10585
10586 pub fn kill_ring_yank(
10587 &mut self,
10588 _: &KillRingYank,
10589 window: &mut Window,
10590 cx: &mut Context<Self>,
10591 ) {
10592 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10593 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10594 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10595 (kill_ring.text().to_string(), kill_ring.metadata_json())
10596 } else {
10597 return;
10598 }
10599 } else {
10600 return;
10601 };
10602 self.do_paste(&text, metadata, false, window, cx);
10603 }
10604
10605 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10606 self.do_copy(true, cx);
10607 }
10608
10609 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10610 self.do_copy(false, cx);
10611 }
10612
10613 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10614 let selections = self.selections.all::<Point>(cx);
10615 let buffer = self.buffer.read(cx).read(cx);
10616 let mut text = String::new();
10617
10618 let mut clipboard_selections = Vec::with_capacity(selections.len());
10619 {
10620 let max_point = buffer.max_point();
10621 let mut is_first = true;
10622 for selection in &selections {
10623 let mut start = selection.start;
10624 let mut end = selection.end;
10625 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10626 if is_entire_line {
10627 start = Point::new(start.row, 0);
10628 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10629 }
10630
10631 let mut trimmed_selections = Vec::new();
10632 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10633 let row = MultiBufferRow(start.row);
10634 let first_indent = buffer.indent_size_for_line(row);
10635 if first_indent.len == 0 || start.column > first_indent.len {
10636 trimmed_selections.push(start..end);
10637 } else {
10638 trimmed_selections.push(
10639 Point::new(row.0, first_indent.len)
10640 ..Point::new(row.0, buffer.line_len(row)),
10641 );
10642 for row in start.row + 1..=end.row {
10643 let mut line_len = buffer.line_len(MultiBufferRow(row));
10644 if row == end.row {
10645 line_len = end.column;
10646 }
10647 if line_len == 0 {
10648 trimmed_selections
10649 .push(Point::new(row, 0)..Point::new(row, line_len));
10650 continue;
10651 }
10652 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10653 if row_indent_size.len >= first_indent.len {
10654 trimmed_selections.push(
10655 Point::new(row, first_indent.len)..Point::new(row, line_len),
10656 );
10657 } else {
10658 trimmed_selections.clear();
10659 trimmed_selections.push(start..end);
10660 break;
10661 }
10662 }
10663 }
10664 } else {
10665 trimmed_selections.push(start..end);
10666 }
10667
10668 for trimmed_range in trimmed_selections {
10669 if is_first {
10670 is_first = false;
10671 } else {
10672 text += "\n";
10673 }
10674 let mut len = 0;
10675 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10676 text.push_str(chunk);
10677 len += chunk.len();
10678 }
10679 clipboard_selections.push(ClipboardSelection {
10680 len,
10681 is_entire_line,
10682 first_line_indent: buffer
10683 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10684 .len,
10685 });
10686 }
10687 }
10688 }
10689
10690 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10691 text,
10692 clipboard_selections,
10693 ));
10694 }
10695
10696 pub fn do_paste(
10697 &mut self,
10698 text: &String,
10699 clipboard_selections: Option<Vec<ClipboardSelection>>,
10700 handle_entire_lines: bool,
10701 window: &mut Window,
10702 cx: &mut Context<Self>,
10703 ) {
10704 if self.read_only(cx) {
10705 return;
10706 }
10707
10708 let clipboard_text = Cow::Borrowed(text);
10709
10710 self.transact(window, cx, |this, window, cx| {
10711 if let Some(mut clipboard_selections) = clipboard_selections {
10712 let old_selections = this.selections.all::<usize>(cx);
10713 let all_selections_were_entire_line =
10714 clipboard_selections.iter().all(|s| s.is_entire_line);
10715 let first_selection_indent_column =
10716 clipboard_selections.first().map(|s| s.first_line_indent);
10717 if clipboard_selections.len() != old_selections.len() {
10718 clipboard_selections.drain(..);
10719 }
10720 let cursor_offset = this.selections.last::<usize>(cx).head();
10721 let mut auto_indent_on_paste = true;
10722
10723 this.buffer.update(cx, |buffer, cx| {
10724 let snapshot = buffer.read(cx);
10725 auto_indent_on_paste = snapshot
10726 .language_settings_at(cursor_offset, cx)
10727 .auto_indent_on_paste;
10728
10729 let mut start_offset = 0;
10730 let mut edits = Vec::new();
10731 let mut original_indent_columns = Vec::new();
10732 for (ix, selection) in old_selections.iter().enumerate() {
10733 let to_insert;
10734 let entire_line;
10735 let original_indent_column;
10736 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10737 let end_offset = start_offset + clipboard_selection.len;
10738 to_insert = &clipboard_text[start_offset..end_offset];
10739 entire_line = clipboard_selection.is_entire_line;
10740 start_offset = end_offset + 1;
10741 original_indent_column = Some(clipboard_selection.first_line_indent);
10742 } else {
10743 to_insert = clipboard_text.as_str();
10744 entire_line = all_selections_were_entire_line;
10745 original_indent_column = first_selection_indent_column
10746 }
10747
10748 // If the corresponding selection was empty when this slice of the
10749 // clipboard text was written, then the entire line containing the
10750 // selection was copied. If this selection is also currently empty,
10751 // then paste the line before the current line of the buffer.
10752 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10753 let column = selection.start.to_point(&snapshot).column as usize;
10754 let line_start = selection.start - column;
10755 line_start..line_start
10756 } else {
10757 selection.range()
10758 };
10759
10760 edits.push((range, to_insert));
10761 original_indent_columns.push(original_indent_column);
10762 }
10763 drop(snapshot);
10764
10765 buffer.edit(
10766 edits,
10767 if auto_indent_on_paste {
10768 Some(AutoindentMode::Block {
10769 original_indent_columns,
10770 })
10771 } else {
10772 None
10773 },
10774 cx,
10775 );
10776 });
10777
10778 let selections = this.selections.all::<usize>(cx);
10779 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10780 s.select(selections)
10781 });
10782 } else {
10783 this.insert(&clipboard_text, window, cx);
10784 }
10785 });
10786 }
10787
10788 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10789 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10790 if let Some(item) = cx.read_from_clipboard() {
10791 let entries = item.entries();
10792
10793 match entries.first() {
10794 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10795 // of all the pasted entries.
10796 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10797 .do_paste(
10798 clipboard_string.text(),
10799 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10800 true,
10801 window,
10802 cx,
10803 ),
10804 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10805 }
10806 }
10807 }
10808
10809 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10810 if self.read_only(cx) {
10811 return;
10812 }
10813
10814 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10815
10816 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10817 if let Some((selections, _)) =
10818 self.selection_history.transaction(transaction_id).cloned()
10819 {
10820 self.change_selections(None, window, cx, |s| {
10821 s.select_anchors(selections.to_vec());
10822 });
10823 } else {
10824 log::error!(
10825 "No entry in selection_history found for undo. \
10826 This may correspond to a bug where undo does not update the selection. \
10827 If this is occurring, please add details to \
10828 https://github.com/zed-industries/zed/issues/22692"
10829 );
10830 }
10831 self.request_autoscroll(Autoscroll::fit(), cx);
10832 self.unmark_text(window, cx);
10833 self.refresh_inline_completion(true, false, window, cx);
10834 cx.emit(EditorEvent::Edited { transaction_id });
10835 cx.emit(EditorEvent::TransactionUndone { transaction_id });
10836 }
10837 }
10838
10839 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
10840 if self.read_only(cx) {
10841 return;
10842 }
10843
10844 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10845
10846 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
10847 if let Some((_, Some(selections))) =
10848 self.selection_history.transaction(transaction_id).cloned()
10849 {
10850 self.change_selections(None, window, cx, |s| {
10851 s.select_anchors(selections.to_vec());
10852 });
10853 } else {
10854 log::error!(
10855 "No entry in selection_history found for redo. \
10856 This may correspond to a bug where undo does not update the selection. \
10857 If this is occurring, please add details to \
10858 https://github.com/zed-industries/zed/issues/22692"
10859 );
10860 }
10861 self.request_autoscroll(Autoscroll::fit(), cx);
10862 self.unmark_text(window, cx);
10863 self.refresh_inline_completion(true, false, window, cx);
10864 cx.emit(EditorEvent::Edited { transaction_id });
10865 }
10866 }
10867
10868 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
10869 self.buffer
10870 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
10871 }
10872
10873 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
10874 self.buffer
10875 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
10876 }
10877
10878 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
10879 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10880 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10881 s.move_with(|map, selection| {
10882 let cursor = if selection.is_empty() {
10883 movement::left(map, selection.start)
10884 } else {
10885 selection.start
10886 };
10887 selection.collapse_to(cursor, SelectionGoal::None);
10888 });
10889 })
10890 }
10891
10892 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
10893 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10894 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10895 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
10896 })
10897 }
10898
10899 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
10900 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10901 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10902 s.move_with(|map, selection| {
10903 let cursor = if selection.is_empty() {
10904 movement::right(map, selection.end)
10905 } else {
10906 selection.end
10907 };
10908 selection.collapse_to(cursor, SelectionGoal::None)
10909 });
10910 })
10911 }
10912
10913 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
10914 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10915 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10916 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
10917 })
10918 }
10919
10920 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
10921 if self.take_rename(true, window, cx).is_some() {
10922 return;
10923 }
10924
10925 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10926 cx.propagate();
10927 return;
10928 }
10929
10930 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10931
10932 let text_layout_details = &self.text_layout_details(window);
10933 let selection_count = self.selections.count();
10934 let first_selection = self.selections.first_anchor();
10935
10936 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10937 s.move_with(|map, selection| {
10938 if !selection.is_empty() {
10939 selection.goal = SelectionGoal::None;
10940 }
10941 let (cursor, goal) = movement::up(
10942 map,
10943 selection.start,
10944 selection.goal,
10945 false,
10946 text_layout_details,
10947 );
10948 selection.collapse_to(cursor, goal);
10949 });
10950 });
10951
10952 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10953 {
10954 cx.propagate();
10955 }
10956 }
10957
10958 pub fn move_up_by_lines(
10959 &mut self,
10960 action: &MoveUpByLines,
10961 window: &mut Window,
10962 cx: &mut Context<Self>,
10963 ) {
10964 if self.take_rename(true, window, cx).is_some() {
10965 return;
10966 }
10967
10968 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10969 cx.propagate();
10970 return;
10971 }
10972
10973 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10974
10975 let text_layout_details = &self.text_layout_details(window);
10976
10977 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10978 s.move_with(|map, selection| {
10979 if !selection.is_empty() {
10980 selection.goal = SelectionGoal::None;
10981 }
10982 let (cursor, goal) = movement::up_by_rows(
10983 map,
10984 selection.start,
10985 action.lines,
10986 selection.goal,
10987 false,
10988 text_layout_details,
10989 );
10990 selection.collapse_to(cursor, goal);
10991 });
10992 })
10993 }
10994
10995 pub fn move_down_by_lines(
10996 &mut self,
10997 action: &MoveDownByLines,
10998 window: &mut Window,
10999 cx: &mut Context<Self>,
11000 ) {
11001 if self.take_rename(true, window, cx).is_some() {
11002 return;
11003 }
11004
11005 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11006 cx.propagate();
11007 return;
11008 }
11009
11010 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11011
11012 let text_layout_details = &self.text_layout_details(window);
11013
11014 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11015 s.move_with(|map, selection| {
11016 if !selection.is_empty() {
11017 selection.goal = SelectionGoal::None;
11018 }
11019 let (cursor, goal) = movement::down_by_rows(
11020 map,
11021 selection.start,
11022 action.lines,
11023 selection.goal,
11024 false,
11025 text_layout_details,
11026 );
11027 selection.collapse_to(cursor, goal);
11028 });
11029 })
11030 }
11031
11032 pub fn select_down_by_lines(
11033 &mut self,
11034 action: &SelectDownByLines,
11035 window: &mut Window,
11036 cx: &mut Context<Self>,
11037 ) {
11038 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11039 let text_layout_details = &self.text_layout_details(window);
11040 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11041 s.move_heads_with(|map, head, goal| {
11042 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11043 })
11044 })
11045 }
11046
11047 pub fn select_up_by_lines(
11048 &mut self,
11049 action: &SelectUpByLines,
11050 window: &mut Window,
11051 cx: &mut Context<Self>,
11052 ) {
11053 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11054 let text_layout_details = &self.text_layout_details(window);
11055 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11056 s.move_heads_with(|map, head, goal| {
11057 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11058 })
11059 })
11060 }
11061
11062 pub fn select_page_up(
11063 &mut self,
11064 _: &SelectPageUp,
11065 window: &mut Window,
11066 cx: &mut Context<Self>,
11067 ) {
11068 let Some(row_count) = self.visible_row_count() else {
11069 return;
11070 };
11071
11072 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11073
11074 let text_layout_details = &self.text_layout_details(window);
11075
11076 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11077 s.move_heads_with(|map, head, goal| {
11078 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11079 })
11080 })
11081 }
11082
11083 pub fn move_page_up(
11084 &mut self,
11085 action: &MovePageUp,
11086 window: &mut Window,
11087 cx: &mut Context<Self>,
11088 ) {
11089 if self.take_rename(true, window, cx).is_some() {
11090 return;
11091 }
11092
11093 if self
11094 .context_menu
11095 .borrow_mut()
11096 .as_mut()
11097 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
11098 .unwrap_or(false)
11099 {
11100 return;
11101 }
11102
11103 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11104 cx.propagate();
11105 return;
11106 }
11107
11108 let Some(row_count) = self.visible_row_count() else {
11109 return;
11110 };
11111
11112 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11113
11114 let autoscroll = if action.center_cursor {
11115 Autoscroll::center()
11116 } else {
11117 Autoscroll::fit()
11118 };
11119
11120 let text_layout_details = &self.text_layout_details(window);
11121
11122 self.change_selections(Some(autoscroll), window, cx, |s| {
11123 s.move_with(|map, selection| {
11124 if !selection.is_empty() {
11125 selection.goal = SelectionGoal::None;
11126 }
11127 let (cursor, goal) = movement::up_by_rows(
11128 map,
11129 selection.end,
11130 row_count,
11131 selection.goal,
11132 false,
11133 text_layout_details,
11134 );
11135 selection.collapse_to(cursor, goal);
11136 });
11137 });
11138 }
11139
11140 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11141 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11142 let text_layout_details = &self.text_layout_details(window);
11143 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11144 s.move_heads_with(|map, head, goal| {
11145 movement::up(map, head, goal, false, text_layout_details)
11146 })
11147 })
11148 }
11149
11150 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11151 self.take_rename(true, window, cx);
11152
11153 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11154 cx.propagate();
11155 return;
11156 }
11157
11158 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11159
11160 let text_layout_details = &self.text_layout_details(window);
11161 let selection_count = self.selections.count();
11162 let first_selection = self.selections.first_anchor();
11163
11164 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11165 s.move_with(|map, selection| {
11166 if !selection.is_empty() {
11167 selection.goal = SelectionGoal::None;
11168 }
11169 let (cursor, goal) = movement::down(
11170 map,
11171 selection.end,
11172 selection.goal,
11173 false,
11174 text_layout_details,
11175 );
11176 selection.collapse_to(cursor, goal);
11177 });
11178 });
11179
11180 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11181 {
11182 cx.propagate();
11183 }
11184 }
11185
11186 pub fn select_page_down(
11187 &mut self,
11188 _: &SelectPageDown,
11189 window: &mut Window,
11190 cx: &mut Context<Self>,
11191 ) {
11192 let Some(row_count) = self.visible_row_count() else {
11193 return;
11194 };
11195
11196 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11197
11198 let text_layout_details = &self.text_layout_details(window);
11199
11200 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11201 s.move_heads_with(|map, head, goal| {
11202 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11203 })
11204 })
11205 }
11206
11207 pub fn move_page_down(
11208 &mut self,
11209 action: &MovePageDown,
11210 window: &mut Window,
11211 cx: &mut Context<Self>,
11212 ) {
11213 if self.take_rename(true, window, cx).is_some() {
11214 return;
11215 }
11216
11217 if self
11218 .context_menu
11219 .borrow_mut()
11220 .as_mut()
11221 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
11222 .unwrap_or(false)
11223 {
11224 return;
11225 }
11226
11227 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11228 cx.propagate();
11229 return;
11230 }
11231
11232 let Some(row_count) = self.visible_row_count() else {
11233 return;
11234 };
11235
11236 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11237
11238 let autoscroll = if action.center_cursor {
11239 Autoscroll::center()
11240 } else {
11241 Autoscroll::fit()
11242 };
11243
11244 let text_layout_details = &self.text_layout_details(window);
11245 self.change_selections(Some(autoscroll), window, cx, |s| {
11246 s.move_with(|map, selection| {
11247 if !selection.is_empty() {
11248 selection.goal = SelectionGoal::None;
11249 }
11250 let (cursor, goal) = movement::down_by_rows(
11251 map,
11252 selection.end,
11253 row_count,
11254 selection.goal,
11255 false,
11256 text_layout_details,
11257 );
11258 selection.collapse_to(cursor, goal);
11259 });
11260 });
11261 }
11262
11263 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11264 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11265 let text_layout_details = &self.text_layout_details(window);
11266 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11267 s.move_heads_with(|map, head, goal| {
11268 movement::down(map, head, goal, false, text_layout_details)
11269 })
11270 });
11271 }
11272
11273 pub fn context_menu_first(
11274 &mut self,
11275 _: &ContextMenuFirst,
11276 _window: &mut Window,
11277 cx: &mut Context<Self>,
11278 ) {
11279 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11280 context_menu.select_first(self.completion_provider.as_deref(), cx);
11281 }
11282 }
11283
11284 pub fn context_menu_prev(
11285 &mut self,
11286 _: &ContextMenuPrevious,
11287 _window: &mut Window,
11288 cx: &mut Context<Self>,
11289 ) {
11290 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11291 context_menu.select_prev(self.completion_provider.as_deref(), cx);
11292 }
11293 }
11294
11295 pub fn context_menu_next(
11296 &mut self,
11297 _: &ContextMenuNext,
11298 _window: &mut Window,
11299 cx: &mut Context<Self>,
11300 ) {
11301 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11302 context_menu.select_next(self.completion_provider.as_deref(), cx);
11303 }
11304 }
11305
11306 pub fn context_menu_last(
11307 &mut self,
11308 _: &ContextMenuLast,
11309 _window: &mut Window,
11310 cx: &mut Context<Self>,
11311 ) {
11312 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11313 context_menu.select_last(self.completion_provider.as_deref(), cx);
11314 }
11315 }
11316
11317 pub fn move_to_previous_word_start(
11318 &mut self,
11319 _: &MoveToPreviousWordStart,
11320 window: &mut Window,
11321 cx: &mut Context<Self>,
11322 ) {
11323 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11324 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11325 s.move_cursors_with(|map, head, _| {
11326 (
11327 movement::previous_word_start(map, head),
11328 SelectionGoal::None,
11329 )
11330 });
11331 })
11332 }
11333
11334 pub fn move_to_previous_subword_start(
11335 &mut self,
11336 _: &MoveToPreviousSubwordStart,
11337 window: &mut Window,
11338 cx: &mut Context<Self>,
11339 ) {
11340 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11341 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11342 s.move_cursors_with(|map, head, _| {
11343 (
11344 movement::previous_subword_start(map, head),
11345 SelectionGoal::None,
11346 )
11347 });
11348 })
11349 }
11350
11351 pub fn select_to_previous_word_start(
11352 &mut self,
11353 _: &SelectToPreviousWordStart,
11354 window: &mut Window,
11355 cx: &mut Context<Self>,
11356 ) {
11357 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11358 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11359 s.move_heads_with(|map, head, _| {
11360 (
11361 movement::previous_word_start(map, head),
11362 SelectionGoal::None,
11363 )
11364 });
11365 })
11366 }
11367
11368 pub fn select_to_previous_subword_start(
11369 &mut self,
11370 _: &SelectToPreviousSubwordStart,
11371 window: &mut Window,
11372 cx: &mut Context<Self>,
11373 ) {
11374 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11375 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11376 s.move_heads_with(|map, head, _| {
11377 (
11378 movement::previous_subword_start(map, head),
11379 SelectionGoal::None,
11380 )
11381 });
11382 })
11383 }
11384
11385 pub fn delete_to_previous_word_start(
11386 &mut self,
11387 action: &DeleteToPreviousWordStart,
11388 window: &mut Window,
11389 cx: &mut Context<Self>,
11390 ) {
11391 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11392 self.transact(window, cx, |this, window, cx| {
11393 this.select_autoclose_pair(window, cx);
11394 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11395 s.move_with(|map, selection| {
11396 if selection.is_empty() {
11397 let cursor = if action.ignore_newlines {
11398 movement::previous_word_start(map, selection.head())
11399 } else {
11400 movement::previous_word_start_or_newline(map, selection.head())
11401 };
11402 selection.set_head(cursor, SelectionGoal::None);
11403 }
11404 });
11405 });
11406 this.insert("", window, cx);
11407 });
11408 }
11409
11410 pub fn delete_to_previous_subword_start(
11411 &mut self,
11412 _: &DeleteToPreviousSubwordStart,
11413 window: &mut Window,
11414 cx: &mut Context<Self>,
11415 ) {
11416 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11417 self.transact(window, cx, |this, window, cx| {
11418 this.select_autoclose_pair(window, cx);
11419 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11420 s.move_with(|map, selection| {
11421 if selection.is_empty() {
11422 let cursor = movement::previous_subword_start(map, selection.head());
11423 selection.set_head(cursor, SelectionGoal::None);
11424 }
11425 });
11426 });
11427 this.insert("", window, cx);
11428 });
11429 }
11430
11431 pub fn move_to_next_word_end(
11432 &mut self,
11433 _: &MoveToNextWordEnd,
11434 window: &mut Window,
11435 cx: &mut Context<Self>,
11436 ) {
11437 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11438 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11439 s.move_cursors_with(|map, head, _| {
11440 (movement::next_word_end(map, head), SelectionGoal::None)
11441 });
11442 })
11443 }
11444
11445 pub fn move_to_next_subword_end(
11446 &mut self,
11447 _: &MoveToNextSubwordEnd,
11448 window: &mut Window,
11449 cx: &mut Context<Self>,
11450 ) {
11451 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11452 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11453 s.move_cursors_with(|map, head, _| {
11454 (movement::next_subword_end(map, head), SelectionGoal::None)
11455 });
11456 })
11457 }
11458
11459 pub fn select_to_next_word_end(
11460 &mut self,
11461 _: &SelectToNextWordEnd,
11462 window: &mut Window,
11463 cx: &mut Context<Self>,
11464 ) {
11465 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11466 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11467 s.move_heads_with(|map, head, _| {
11468 (movement::next_word_end(map, head), SelectionGoal::None)
11469 });
11470 })
11471 }
11472
11473 pub fn select_to_next_subword_end(
11474 &mut self,
11475 _: &SelectToNextSubwordEnd,
11476 window: &mut Window,
11477 cx: &mut Context<Self>,
11478 ) {
11479 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11480 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11481 s.move_heads_with(|map, head, _| {
11482 (movement::next_subword_end(map, head), SelectionGoal::None)
11483 });
11484 })
11485 }
11486
11487 pub fn delete_to_next_word_end(
11488 &mut self,
11489 action: &DeleteToNextWordEnd,
11490 window: &mut Window,
11491 cx: &mut Context<Self>,
11492 ) {
11493 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11494 self.transact(window, cx, |this, window, cx| {
11495 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11496 s.move_with(|map, selection| {
11497 if selection.is_empty() {
11498 let cursor = if action.ignore_newlines {
11499 movement::next_word_end(map, selection.head())
11500 } else {
11501 movement::next_word_end_or_newline(map, selection.head())
11502 };
11503 selection.set_head(cursor, SelectionGoal::None);
11504 }
11505 });
11506 });
11507 this.insert("", window, cx);
11508 });
11509 }
11510
11511 pub fn delete_to_next_subword_end(
11512 &mut self,
11513 _: &DeleteToNextSubwordEnd,
11514 window: &mut Window,
11515 cx: &mut Context<Self>,
11516 ) {
11517 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11518 self.transact(window, cx, |this, window, cx| {
11519 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11520 s.move_with(|map, selection| {
11521 if selection.is_empty() {
11522 let cursor = movement::next_subword_end(map, selection.head());
11523 selection.set_head(cursor, SelectionGoal::None);
11524 }
11525 });
11526 });
11527 this.insert("", window, cx);
11528 });
11529 }
11530
11531 pub fn move_to_beginning_of_line(
11532 &mut self,
11533 action: &MoveToBeginningOfLine,
11534 window: &mut Window,
11535 cx: &mut Context<Self>,
11536 ) {
11537 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11538 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11539 s.move_cursors_with(|map, head, _| {
11540 (
11541 movement::indented_line_beginning(
11542 map,
11543 head,
11544 action.stop_at_soft_wraps,
11545 action.stop_at_indent,
11546 ),
11547 SelectionGoal::None,
11548 )
11549 });
11550 })
11551 }
11552
11553 pub fn select_to_beginning_of_line(
11554 &mut self,
11555 action: &SelectToBeginningOfLine,
11556 window: &mut Window,
11557 cx: &mut Context<Self>,
11558 ) {
11559 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11560 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11561 s.move_heads_with(|map, head, _| {
11562 (
11563 movement::indented_line_beginning(
11564 map,
11565 head,
11566 action.stop_at_soft_wraps,
11567 action.stop_at_indent,
11568 ),
11569 SelectionGoal::None,
11570 )
11571 });
11572 });
11573 }
11574
11575 pub fn delete_to_beginning_of_line(
11576 &mut self,
11577 action: &DeleteToBeginningOfLine,
11578 window: &mut Window,
11579 cx: &mut Context<Self>,
11580 ) {
11581 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11582 self.transact(window, cx, |this, window, cx| {
11583 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11584 s.move_with(|_, selection| {
11585 selection.reversed = true;
11586 });
11587 });
11588
11589 this.select_to_beginning_of_line(
11590 &SelectToBeginningOfLine {
11591 stop_at_soft_wraps: false,
11592 stop_at_indent: action.stop_at_indent,
11593 },
11594 window,
11595 cx,
11596 );
11597 this.backspace(&Backspace, window, cx);
11598 });
11599 }
11600
11601 pub fn move_to_end_of_line(
11602 &mut self,
11603 action: &MoveToEndOfLine,
11604 window: &mut Window,
11605 cx: &mut Context<Self>,
11606 ) {
11607 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11608 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11609 s.move_cursors_with(|map, head, _| {
11610 (
11611 movement::line_end(map, head, action.stop_at_soft_wraps),
11612 SelectionGoal::None,
11613 )
11614 });
11615 })
11616 }
11617
11618 pub fn select_to_end_of_line(
11619 &mut self,
11620 action: &SelectToEndOfLine,
11621 window: &mut Window,
11622 cx: &mut Context<Self>,
11623 ) {
11624 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11625 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11626 s.move_heads_with(|map, head, _| {
11627 (
11628 movement::line_end(map, head, action.stop_at_soft_wraps),
11629 SelectionGoal::None,
11630 )
11631 });
11632 })
11633 }
11634
11635 pub fn delete_to_end_of_line(
11636 &mut self,
11637 _: &DeleteToEndOfLine,
11638 window: &mut Window,
11639 cx: &mut Context<Self>,
11640 ) {
11641 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11642 self.transact(window, cx, |this, window, cx| {
11643 this.select_to_end_of_line(
11644 &SelectToEndOfLine {
11645 stop_at_soft_wraps: false,
11646 },
11647 window,
11648 cx,
11649 );
11650 this.delete(&Delete, window, cx);
11651 });
11652 }
11653
11654 pub fn cut_to_end_of_line(
11655 &mut self,
11656 _: &CutToEndOfLine,
11657 window: &mut Window,
11658 cx: &mut Context<Self>,
11659 ) {
11660 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11661 self.transact(window, cx, |this, window, cx| {
11662 this.select_to_end_of_line(
11663 &SelectToEndOfLine {
11664 stop_at_soft_wraps: false,
11665 },
11666 window,
11667 cx,
11668 );
11669 this.cut(&Cut, window, cx);
11670 });
11671 }
11672
11673 pub fn move_to_start_of_paragraph(
11674 &mut self,
11675 _: &MoveToStartOfParagraph,
11676 window: &mut Window,
11677 cx: &mut Context<Self>,
11678 ) {
11679 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11680 cx.propagate();
11681 return;
11682 }
11683 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11684 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11685 s.move_with(|map, selection| {
11686 selection.collapse_to(
11687 movement::start_of_paragraph(map, selection.head(), 1),
11688 SelectionGoal::None,
11689 )
11690 });
11691 })
11692 }
11693
11694 pub fn move_to_end_of_paragraph(
11695 &mut self,
11696 _: &MoveToEndOfParagraph,
11697 window: &mut Window,
11698 cx: &mut Context<Self>,
11699 ) {
11700 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11701 cx.propagate();
11702 return;
11703 }
11704 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11705 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11706 s.move_with(|map, selection| {
11707 selection.collapse_to(
11708 movement::end_of_paragraph(map, selection.head(), 1),
11709 SelectionGoal::None,
11710 )
11711 });
11712 })
11713 }
11714
11715 pub fn select_to_start_of_paragraph(
11716 &mut self,
11717 _: &SelectToStartOfParagraph,
11718 window: &mut Window,
11719 cx: &mut Context<Self>,
11720 ) {
11721 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11722 cx.propagate();
11723 return;
11724 }
11725 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11726 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11727 s.move_heads_with(|map, head, _| {
11728 (
11729 movement::start_of_paragraph(map, head, 1),
11730 SelectionGoal::None,
11731 )
11732 });
11733 })
11734 }
11735
11736 pub fn select_to_end_of_paragraph(
11737 &mut self,
11738 _: &SelectToEndOfParagraph,
11739 window: &mut Window,
11740 cx: &mut Context<Self>,
11741 ) {
11742 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11743 cx.propagate();
11744 return;
11745 }
11746 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11747 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11748 s.move_heads_with(|map, head, _| {
11749 (
11750 movement::end_of_paragraph(map, head, 1),
11751 SelectionGoal::None,
11752 )
11753 });
11754 })
11755 }
11756
11757 pub fn move_to_start_of_excerpt(
11758 &mut self,
11759 _: &MoveToStartOfExcerpt,
11760 window: &mut Window,
11761 cx: &mut Context<Self>,
11762 ) {
11763 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11764 cx.propagate();
11765 return;
11766 }
11767 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11768 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11769 s.move_with(|map, selection| {
11770 selection.collapse_to(
11771 movement::start_of_excerpt(
11772 map,
11773 selection.head(),
11774 workspace::searchable::Direction::Prev,
11775 ),
11776 SelectionGoal::None,
11777 )
11778 });
11779 })
11780 }
11781
11782 pub fn move_to_start_of_next_excerpt(
11783 &mut self,
11784 _: &MoveToStartOfNextExcerpt,
11785 window: &mut Window,
11786 cx: &mut Context<Self>,
11787 ) {
11788 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11789 cx.propagate();
11790 return;
11791 }
11792
11793 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11794 s.move_with(|map, selection| {
11795 selection.collapse_to(
11796 movement::start_of_excerpt(
11797 map,
11798 selection.head(),
11799 workspace::searchable::Direction::Next,
11800 ),
11801 SelectionGoal::None,
11802 )
11803 });
11804 })
11805 }
11806
11807 pub fn move_to_end_of_excerpt(
11808 &mut self,
11809 _: &MoveToEndOfExcerpt,
11810 window: &mut Window,
11811 cx: &mut Context<Self>,
11812 ) {
11813 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11814 cx.propagate();
11815 return;
11816 }
11817 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11818 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11819 s.move_with(|map, selection| {
11820 selection.collapse_to(
11821 movement::end_of_excerpt(
11822 map,
11823 selection.head(),
11824 workspace::searchable::Direction::Next,
11825 ),
11826 SelectionGoal::None,
11827 )
11828 });
11829 })
11830 }
11831
11832 pub fn move_to_end_of_previous_excerpt(
11833 &mut self,
11834 _: &MoveToEndOfPreviousExcerpt,
11835 window: &mut Window,
11836 cx: &mut Context<Self>,
11837 ) {
11838 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11839 cx.propagate();
11840 return;
11841 }
11842 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11843 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11844 s.move_with(|map, selection| {
11845 selection.collapse_to(
11846 movement::end_of_excerpt(
11847 map,
11848 selection.head(),
11849 workspace::searchable::Direction::Prev,
11850 ),
11851 SelectionGoal::None,
11852 )
11853 });
11854 })
11855 }
11856
11857 pub fn select_to_start_of_excerpt(
11858 &mut self,
11859 _: &SelectToStartOfExcerpt,
11860 window: &mut Window,
11861 cx: &mut Context<Self>,
11862 ) {
11863 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11864 cx.propagate();
11865 return;
11866 }
11867 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11868 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11869 s.move_heads_with(|map, head, _| {
11870 (
11871 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11872 SelectionGoal::None,
11873 )
11874 });
11875 })
11876 }
11877
11878 pub fn select_to_start_of_next_excerpt(
11879 &mut self,
11880 _: &SelectToStartOfNextExcerpt,
11881 window: &mut Window,
11882 cx: &mut Context<Self>,
11883 ) {
11884 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11885 cx.propagate();
11886 return;
11887 }
11888 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11889 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11890 s.move_heads_with(|map, head, _| {
11891 (
11892 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
11893 SelectionGoal::None,
11894 )
11895 });
11896 })
11897 }
11898
11899 pub fn select_to_end_of_excerpt(
11900 &mut self,
11901 _: &SelectToEndOfExcerpt,
11902 window: &mut Window,
11903 cx: &mut Context<Self>,
11904 ) {
11905 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11906 cx.propagate();
11907 return;
11908 }
11909 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11910 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11911 s.move_heads_with(|map, head, _| {
11912 (
11913 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
11914 SelectionGoal::None,
11915 )
11916 });
11917 })
11918 }
11919
11920 pub fn select_to_end_of_previous_excerpt(
11921 &mut self,
11922 _: &SelectToEndOfPreviousExcerpt,
11923 window: &mut Window,
11924 cx: &mut Context<Self>,
11925 ) {
11926 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11927 cx.propagate();
11928 return;
11929 }
11930 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11931 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11932 s.move_heads_with(|map, head, _| {
11933 (
11934 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11935 SelectionGoal::None,
11936 )
11937 });
11938 })
11939 }
11940
11941 pub fn move_to_beginning(
11942 &mut self,
11943 _: &MoveToBeginning,
11944 window: &mut Window,
11945 cx: &mut Context<Self>,
11946 ) {
11947 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11948 cx.propagate();
11949 return;
11950 }
11951 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11952 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11953 s.select_ranges(vec![0..0]);
11954 });
11955 }
11956
11957 pub fn select_to_beginning(
11958 &mut self,
11959 _: &SelectToBeginning,
11960 window: &mut Window,
11961 cx: &mut Context<Self>,
11962 ) {
11963 let mut selection = self.selections.last::<Point>(cx);
11964 selection.set_head(Point::zero(), SelectionGoal::None);
11965 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11966 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11967 s.select(vec![selection]);
11968 });
11969 }
11970
11971 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
11972 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11973 cx.propagate();
11974 return;
11975 }
11976 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11977 let cursor = self.buffer.read(cx).read(cx).len();
11978 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11979 s.select_ranges(vec![cursor..cursor])
11980 });
11981 }
11982
11983 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
11984 self.nav_history = nav_history;
11985 }
11986
11987 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
11988 self.nav_history.as_ref()
11989 }
11990
11991 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
11992 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
11993 }
11994
11995 fn push_to_nav_history(
11996 &mut self,
11997 cursor_anchor: Anchor,
11998 new_position: Option<Point>,
11999 is_deactivate: bool,
12000 cx: &mut Context<Self>,
12001 ) {
12002 if let Some(nav_history) = self.nav_history.as_mut() {
12003 let buffer = self.buffer.read(cx).read(cx);
12004 let cursor_position = cursor_anchor.to_point(&buffer);
12005 let scroll_state = self.scroll_manager.anchor();
12006 let scroll_top_row = scroll_state.top_row(&buffer);
12007 drop(buffer);
12008
12009 if let Some(new_position) = new_position {
12010 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12011 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12012 return;
12013 }
12014 }
12015
12016 nav_history.push(
12017 Some(NavigationData {
12018 cursor_anchor,
12019 cursor_position,
12020 scroll_anchor: scroll_state,
12021 scroll_top_row,
12022 }),
12023 cx,
12024 );
12025 cx.emit(EditorEvent::PushedToNavHistory {
12026 anchor: cursor_anchor,
12027 is_deactivate,
12028 })
12029 }
12030 }
12031
12032 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12033 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12034 let buffer = self.buffer.read(cx).snapshot(cx);
12035 let mut selection = self.selections.first::<usize>(cx);
12036 selection.set_head(buffer.len(), SelectionGoal::None);
12037 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12038 s.select(vec![selection]);
12039 });
12040 }
12041
12042 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12043 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12044 let end = self.buffer.read(cx).read(cx).len();
12045 self.change_selections(None, window, cx, |s| {
12046 s.select_ranges(vec![0..end]);
12047 });
12048 }
12049
12050 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12051 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12052 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12053 let mut selections = self.selections.all::<Point>(cx);
12054 let max_point = display_map.buffer_snapshot.max_point();
12055 for selection in &mut selections {
12056 let rows = selection.spanned_rows(true, &display_map);
12057 selection.start = Point::new(rows.start.0, 0);
12058 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12059 selection.reversed = false;
12060 }
12061 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12062 s.select(selections);
12063 });
12064 }
12065
12066 pub fn split_selection_into_lines(
12067 &mut self,
12068 _: &SplitSelectionIntoLines,
12069 window: &mut Window,
12070 cx: &mut Context<Self>,
12071 ) {
12072 let selections = self
12073 .selections
12074 .all::<Point>(cx)
12075 .into_iter()
12076 .map(|selection| selection.start..selection.end)
12077 .collect::<Vec<_>>();
12078 self.unfold_ranges(&selections, true, true, cx);
12079
12080 let mut new_selection_ranges = Vec::new();
12081 {
12082 let buffer = self.buffer.read(cx).read(cx);
12083 for selection in selections {
12084 for row in selection.start.row..selection.end.row {
12085 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12086 new_selection_ranges.push(cursor..cursor);
12087 }
12088
12089 let is_multiline_selection = selection.start.row != selection.end.row;
12090 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12091 // so this action feels more ergonomic when paired with other selection operations
12092 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12093 if !should_skip_last {
12094 new_selection_ranges.push(selection.end..selection.end);
12095 }
12096 }
12097 }
12098 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12099 s.select_ranges(new_selection_ranges);
12100 });
12101 }
12102
12103 pub fn add_selection_above(
12104 &mut self,
12105 _: &AddSelectionAbove,
12106 window: &mut Window,
12107 cx: &mut Context<Self>,
12108 ) {
12109 self.add_selection(true, window, cx);
12110 }
12111
12112 pub fn add_selection_below(
12113 &mut self,
12114 _: &AddSelectionBelow,
12115 window: &mut Window,
12116 cx: &mut Context<Self>,
12117 ) {
12118 self.add_selection(false, window, cx);
12119 }
12120
12121 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12122 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12123
12124 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12125 let mut selections = self.selections.all::<Point>(cx);
12126 let text_layout_details = self.text_layout_details(window);
12127 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12128 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12129 let range = oldest_selection.display_range(&display_map).sorted();
12130
12131 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12132 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12133 let positions = start_x.min(end_x)..start_x.max(end_x);
12134
12135 selections.clear();
12136 let mut stack = Vec::new();
12137 for row in range.start.row().0..=range.end.row().0 {
12138 if let Some(selection) = self.selections.build_columnar_selection(
12139 &display_map,
12140 DisplayRow(row),
12141 &positions,
12142 oldest_selection.reversed,
12143 &text_layout_details,
12144 ) {
12145 stack.push(selection.id);
12146 selections.push(selection);
12147 }
12148 }
12149
12150 if above {
12151 stack.reverse();
12152 }
12153
12154 AddSelectionsState { above, stack }
12155 });
12156
12157 let last_added_selection = *state.stack.last().unwrap();
12158 let mut new_selections = Vec::new();
12159 if above == state.above {
12160 let end_row = if above {
12161 DisplayRow(0)
12162 } else {
12163 display_map.max_point().row()
12164 };
12165
12166 'outer: for selection in selections {
12167 if selection.id == last_added_selection {
12168 let range = selection.display_range(&display_map).sorted();
12169 debug_assert_eq!(range.start.row(), range.end.row());
12170 let mut row = range.start.row();
12171 let positions =
12172 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12173 px(start)..px(end)
12174 } else {
12175 let start_x =
12176 display_map.x_for_display_point(range.start, &text_layout_details);
12177 let end_x =
12178 display_map.x_for_display_point(range.end, &text_layout_details);
12179 start_x.min(end_x)..start_x.max(end_x)
12180 };
12181
12182 while row != end_row {
12183 if above {
12184 row.0 -= 1;
12185 } else {
12186 row.0 += 1;
12187 }
12188
12189 if let Some(new_selection) = self.selections.build_columnar_selection(
12190 &display_map,
12191 row,
12192 &positions,
12193 selection.reversed,
12194 &text_layout_details,
12195 ) {
12196 state.stack.push(new_selection.id);
12197 if above {
12198 new_selections.push(new_selection);
12199 new_selections.push(selection);
12200 } else {
12201 new_selections.push(selection);
12202 new_selections.push(new_selection);
12203 }
12204
12205 continue 'outer;
12206 }
12207 }
12208 }
12209
12210 new_selections.push(selection);
12211 }
12212 } else {
12213 new_selections = selections;
12214 new_selections.retain(|s| s.id != last_added_selection);
12215 state.stack.pop();
12216 }
12217
12218 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12219 s.select(new_selections);
12220 });
12221 if state.stack.len() > 1 {
12222 self.add_selections_state = Some(state);
12223 }
12224 }
12225
12226 fn select_match_ranges(
12227 &mut self,
12228 range: Range<usize>,
12229 reversed: bool,
12230 replace_newest: bool,
12231 auto_scroll: Option<Autoscroll>,
12232 window: &mut Window,
12233 cx: &mut Context<Editor>,
12234 ) {
12235 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12236 self.change_selections(auto_scroll, window, cx, |s| {
12237 if replace_newest {
12238 s.delete(s.newest_anchor().id);
12239 }
12240 if reversed {
12241 s.insert_range(range.end..range.start);
12242 } else {
12243 s.insert_range(range);
12244 }
12245 });
12246 }
12247
12248 pub fn select_next_match_internal(
12249 &mut self,
12250 display_map: &DisplaySnapshot,
12251 replace_newest: bool,
12252 autoscroll: Option<Autoscroll>,
12253 window: &mut Window,
12254 cx: &mut Context<Self>,
12255 ) -> Result<()> {
12256 let buffer = &display_map.buffer_snapshot;
12257 let mut selections = self.selections.all::<usize>(cx);
12258 if let Some(mut select_next_state) = self.select_next_state.take() {
12259 let query = &select_next_state.query;
12260 if !select_next_state.done {
12261 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12262 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12263 let mut next_selected_range = None;
12264
12265 let bytes_after_last_selection =
12266 buffer.bytes_in_range(last_selection.end..buffer.len());
12267 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12268 let query_matches = query
12269 .stream_find_iter(bytes_after_last_selection)
12270 .map(|result| (last_selection.end, result))
12271 .chain(
12272 query
12273 .stream_find_iter(bytes_before_first_selection)
12274 .map(|result| (0, result)),
12275 );
12276
12277 for (start_offset, query_match) in query_matches {
12278 let query_match = query_match.unwrap(); // can only fail due to I/O
12279 let offset_range =
12280 start_offset + query_match.start()..start_offset + query_match.end();
12281 let display_range = offset_range.start.to_display_point(display_map)
12282 ..offset_range.end.to_display_point(display_map);
12283
12284 if !select_next_state.wordwise
12285 || (!movement::is_inside_word(display_map, display_range.start)
12286 && !movement::is_inside_word(display_map, display_range.end))
12287 {
12288 // TODO: This is n^2, because we might check all the selections
12289 if !selections
12290 .iter()
12291 .any(|selection| selection.range().overlaps(&offset_range))
12292 {
12293 next_selected_range = Some(offset_range);
12294 break;
12295 }
12296 }
12297 }
12298
12299 if let Some(next_selected_range) = next_selected_range {
12300 self.select_match_ranges(
12301 next_selected_range,
12302 last_selection.reversed,
12303 replace_newest,
12304 autoscroll,
12305 window,
12306 cx,
12307 );
12308 } else {
12309 select_next_state.done = true;
12310 }
12311 }
12312
12313 self.select_next_state = Some(select_next_state);
12314 } else {
12315 let mut only_carets = true;
12316 let mut same_text_selected = true;
12317 let mut selected_text = None;
12318
12319 let mut selections_iter = selections.iter().peekable();
12320 while let Some(selection) = selections_iter.next() {
12321 if selection.start != selection.end {
12322 only_carets = false;
12323 }
12324
12325 if same_text_selected {
12326 if selected_text.is_none() {
12327 selected_text =
12328 Some(buffer.text_for_range(selection.range()).collect::<String>());
12329 }
12330
12331 if let Some(next_selection) = selections_iter.peek() {
12332 if next_selection.range().len() == selection.range().len() {
12333 let next_selected_text = buffer
12334 .text_for_range(next_selection.range())
12335 .collect::<String>();
12336 if Some(next_selected_text) != selected_text {
12337 same_text_selected = false;
12338 selected_text = None;
12339 }
12340 } else {
12341 same_text_selected = false;
12342 selected_text = None;
12343 }
12344 }
12345 }
12346 }
12347
12348 if only_carets {
12349 for selection in &mut selections {
12350 let word_range = movement::surrounding_word(
12351 display_map,
12352 selection.start.to_display_point(display_map),
12353 );
12354 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12355 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12356 selection.goal = SelectionGoal::None;
12357 selection.reversed = false;
12358 self.select_match_ranges(
12359 selection.start..selection.end,
12360 selection.reversed,
12361 replace_newest,
12362 autoscroll,
12363 window,
12364 cx,
12365 );
12366 }
12367
12368 if selections.len() == 1 {
12369 let selection = selections
12370 .last()
12371 .expect("ensured that there's only one selection");
12372 let query = buffer
12373 .text_for_range(selection.start..selection.end)
12374 .collect::<String>();
12375 let is_empty = query.is_empty();
12376 let select_state = SelectNextState {
12377 query: AhoCorasick::new(&[query])?,
12378 wordwise: true,
12379 done: is_empty,
12380 };
12381 self.select_next_state = Some(select_state);
12382 } else {
12383 self.select_next_state = None;
12384 }
12385 } else if let Some(selected_text) = selected_text {
12386 self.select_next_state = Some(SelectNextState {
12387 query: AhoCorasick::new(&[selected_text])?,
12388 wordwise: false,
12389 done: false,
12390 });
12391 self.select_next_match_internal(
12392 display_map,
12393 replace_newest,
12394 autoscroll,
12395 window,
12396 cx,
12397 )?;
12398 }
12399 }
12400 Ok(())
12401 }
12402
12403 pub fn select_all_matches(
12404 &mut self,
12405 _action: &SelectAllMatches,
12406 window: &mut Window,
12407 cx: &mut Context<Self>,
12408 ) -> Result<()> {
12409 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12410
12411 self.push_to_selection_history();
12412 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12413
12414 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12415 let Some(select_next_state) = self.select_next_state.as_mut() else {
12416 return Ok(());
12417 };
12418 if select_next_state.done {
12419 return Ok(());
12420 }
12421
12422 let mut new_selections = Vec::new();
12423
12424 let reversed = self.selections.oldest::<usize>(cx).reversed;
12425 let buffer = &display_map.buffer_snapshot;
12426 let query_matches = select_next_state
12427 .query
12428 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12429
12430 for query_match in query_matches.into_iter() {
12431 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12432 let offset_range = if reversed {
12433 query_match.end()..query_match.start()
12434 } else {
12435 query_match.start()..query_match.end()
12436 };
12437 let display_range = offset_range.start.to_display_point(&display_map)
12438 ..offset_range.end.to_display_point(&display_map);
12439
12440 if !select_next_state.wordwise
12441 || (!movement::is_inside_word(&display_map, display_range.start)
12442 && !movement::is_inside_word(&display_map, display_range.end))
12443 {
12444 new_selections.push(offset_range.start..offset_range.end);
12445 }
12446 }
12447
12448 select_next_state.done = true;
12449 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12450 self.change_selections(None, window, cx, |selections| {
12451 selections.select_ranges(new_selections)
12452 });
12453
12454 Ok(())
12455 }
12456
12457 pub fn select_next(
12458 &mut self,
12459 action: &SelectNext,
12460 window: &mut Window,
12461 cx: &mut Context<Self>,
12462 ) -> Result<()> {
12463 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12464 self.push_to_selection_history();
12465 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12466 self.select_next_match_internal(
12467 &display_map,
12468 action.replace_newest,
12469 Some(Autoscroll::newest()),
12470 window,
12471 cx,
12472 )?;
12473 Ok(())
12474 }
12475
12476 pub fn select_previous(
12477 &mut self,
12478 action: &SelectPrevious,
12479 window: &mut Window,
12480 cx: &mut Context<Self>,
12481 ) -> Result<()> {
12482 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12483 self.push_to_selection_history();
12484 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12485 let buffer = &display_map.buffer_snapshot;
12486 let mut selections = self.selections.all::<usize>(cx);
12487 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12488 let query = &select_prev_state.query;
12489 if !select_prev_state.done {
12490 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12491 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12492 let mut next_selected_range = None;
12493 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12494 let bytes_before_last_selection =
12495 buffer.reversed_bytes_in_range(0..last_selection.start);
12496 let bytes_after_first_selection =
12497 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12498 let query_matches = query
12499 .stream_find_iter(bytes_before_last_selection)
12500 .map(|result| (last_selection.start, result))
12501 .chain(
12502 query
12503 .stream_find_iter(bytes_after_first_selection)
12504 .map(|result| (buffer.len(), result)),
12505 );
12506 for (end_offset, query_match) in query_matches {
12507 let query_match = query_match.unwrap(); // can only fail due to I/O
12508 let offset_range =
12509 end_offset - query_match.end()..end_offset - query_match.start();
12510 let display_range = offset_range.start.to_display_point(&display_map)
12511 ..offset_range.end.to_display_point(&display_map);
12512
12513 if !select_prev_state.wordwise
12514 || (!movement::is_inside_word(&display_map, display_range.start)
12515 && !movement::is_inside_word(&display_map, display_range.end))
12516 {
12517 next_selected_range = Some(offset_range);
12518 break;
12519 }
12520 }
12521
12522 if let Some(next_selected_range) = next_selected_range {
12523 self.select_match_ranges(
12524 next_selected_range,
12525 last_selection.reversed,
12526 action.replace_newest,
12527 Some(Autoscroll::newest()),
12528 window,
12529 cx,
12530 );
12531 } else {
12532 select_prev_state.done = true;
12533 }
12534 }
12535
12536 self.select_prev_state = Some(select_prev_state);
12537 } else {
12538 let mut only_carets = true;
12539 let mut same_text_selected = true;
12540 let mut selected_text = None;
12541
12542 let mut selections_iter = selections.iter().peekable();
12543 while let Some(selection) = selections_iter.next() {
12544 if selection.start != selection.end {
12545 only_carets = false;
12546 }
12547
12548 if same_text_selected {
12549 if selected_text.is_none() {
12550 selected_text =
12551 Some(buffer.text_for_range(selection.range()).collect::<String>());
12552 }
12553
12554 if let Some(next_selection) = selections_iter.peek() {
12555 if next_selection.range().len() == selection.range().len() {
12556 let next_selected_text = buffer
12557 .text_for_range(next_selection.range())
12558 .collect::<String>();
12559 if Some(next_selected_text) != selected_text {
12560 same_text_selected = false;
12561 selected_text = None;
12562 }
12563 } else {
12564 same_text_selected = false;
12565 selected_text = None;
12566 }
12567 }
12568 }
12569 }
12570
12571 if only_carets {
12572 for selection in &mut selections {
12573 let word_range = movement::surrounding_word(
12574 &display_map,
12575 selection.start.to_display_point(&display_map),
12576 );
12577 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12578 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12579 selection.goal = SelectionGoal::None;
12580 selection.reversed = false;
12581 self.select_match_ranges(
12582 selection.start..selection.end,
12583 selection.reversed,
12584 action.replace_newest,
12585 Some(Autoscroll::newest()),
12586 window,
12587 cx,
12588 );
12589 }
12590 if selections.len() == 1 {
12591 let selection = selections
12592 .last()
12593 .expect("ensured that there's only one selection");
12594 let query = buffer
12595 .text_for_range(selection.start..selection.end)
12596 .collect::<String>();
12597 let is_empty = query.is_empty();
12598 let select_state = SelectNextState {
12599 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12600 wordwise: true,
12601 done: is_empty,
12602 };
12603 self.select_prev_state = Some(select_state);
12604 } else {
12605 self.select_prev_state = None;
12606 }
12607 } else if let Some(selected_text) = selected_text {
12608 self.select_prev_state = Some(SelectNextState {
12609 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12610 wordwise: false,
12611 done: false,
12612 });
12613 self.select_previous(action, window, cx)?;
12614 }
12615 }
12616 Ok(())
12617 }
12618
12619 pub fn find_next_match(
12620 &mut self,
12621 _: &FindNextMatch,
12622 window: &mut Window,
12623 cx: &mut Context<Self>,
12624 ) -> Result<()> {
12625 let selections = self.selections.disjoint_anchors();
12626 match selections.first() {
12627 Some(first) if selections.len() >= 2 => {
12628 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12629 s.select_ranges([first.range()]);
12630 });
12631 }
12632 _ => self.select_next(
12633 &SelectNext {
12634 replace_newest: true,
12635 },
12636 window,
12637 cx,
12638 )?,
12639 }
12640 Ok(())
12641 }
12642
12643 pub fn find_previous_match(
12644 &mut self,
12645 _: &FindPreviousMatch,
12646 window: &mut Window,
12647 cx: &mut Context<Self>,
12648 ) -> Result<()> {
12649 let selections = self.selections.disjoint_anchors();
12650 match selections.last() {
12651 Some(last) if selections.len() >= 2 => {
12652 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12653 s.select_ranges([last.range()]);
12654 });
12655 }
12656 _ => self.select_previous(
12657 &SelectPrevious {
12658 replace_newest: true,
12659 },
12660 window,
12661 cx,
12662 )?,
12663 }
12664 Ok(())
12665 }
12666
12667 pub fn toggle_comments(
12668 &mut self,
12669 action: &ToggleComments,
12670 window: &mut Window,
12671 cx: &mut Context<Self>,
12672 ) {
12673 if self.read_only(cx) {
12674 return;
12675 }
12676 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12677 let text_layout_details = &self.text_layout_details(window);
12678 self.transact(window, cx, |this, window, cx| {
12679 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12680 let mut edits = Vec::new();
12681 let mut selection_edit_ranges = Vec::new();
12682 let mut last_toggled_row = None;
12683 let snapshot = this.buffer.read(cx).read(cx);
12684 let empty_str: Arc<str> = Arc::default();
12685 let mut suffixes_inserted = Vec::new();
12686 let ignore_indent = action.ignore_indent;
12687
12688 fn comment_prefix_range(
12689 snapshot: &MultiBufferSnapshot,
12690 row: MultiBufferRow,
12691 comment_prefix: &str,
12692 comment_prefix_whitespace: &str,
12693 ignore_indent: bool,
12694 ) -> Range<Point> {
12695 let indent_size = if ignore_indent {
12696 0
12697 } else {
12698 snapshot.indent_size_for_line(row).len
12699 };
12700
12701 let start = Point::new(row.0, indent_size);
12702
12703 let mut line_bytes = snapshot
12704 .bytes_in_range(start..snapshot.max_point())
12705 .flatten()
12706 .copied();
12707
12708 // If this line currently begins with the line comment prefix, then record
12709 // the range containing the prefix.
12710 if line_bytes
12711 .by_ref()
12712 .take(comment_prefix.len())
12713 .eq(comment_prefix.bytes())
12714 {
12715 // Include any whitespace that matches the comment prefix.
12716 let matching_whitespace_len = line_bytes
12717 .zip(comment_prefix_whitespace.bytes())
12718 .take_while(|(a, b)| a == b)
12719 .count() as u32;
12720 let end = Point::new(
12721 start.row,
12722 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12723 );
12724 start..end
12725 } else {
12726 start..start
12727 }
12728 }
12729
12730 fn comment_suffix_range(
12731 snapshot: &MultiBufferSnapshot,
12732 row: MultiBufferRow,
12733 comment_suffix: &str,
12734 comment_suffix_has_leading_space: bool,
12735 ) -> Range<Point> {
12736 let end = Point::new(row.0, snapshot.line_len(row));
12737 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12738
12739 let mut line_end_bytes = snapshot
12740 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12741 .flatten()
12742 .copied();
12743
12744 let leading_space_len = if suffix_start_column > 0
12745 && line_end_bytes.next() == Some(b' ')
12746 && comment_suffix_has_leading_space
12747 {
12748 1
12749 } else {
12750 0
12751 };
12752
12753 // If this line currently begins with the line comment prefix, then record
12754 // the range containing the prefix.
12755 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12756 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12757 start..end
12758 } else {
12759 end..end
12760 }
12761 }
12762
12763 // TODO: Handle selections that cross excerpts
12764 for selection in &mut selections {
12765 let start_column = snapshot
12766 .indent_size_for_line(MultiBufferRow(selection.start.row))
12767 .len;
12768 let language = if let Some(language) =
12769 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12770 {
12771 language
12772 } else {
12773 continue;
12774 };
12775
12776 selection_edit_ranges.clear();
12777
12778 // If multiple selections contain a given row, avoid processing that
12779 // row more than once.
12780 let mut start_row = MultiBufferRow(selection.start.row);
12781 if last_toggled_row == Some(start_row) {
12782 start_row = start_row.next_row();
12783 }
12784 let end_row =
12785 if selection.end.row > selection.start.row && selection.end.column == 0 {
12786 MultiBufferRow(selection.end.row - 1)
12787 } else {
12788 MultiBufferRow(selection.end.row)
12789 };
12790 last_toggled_row = Some(end_row);
12791
12792 if start_row > end_row {
12793 continue;
12794 }
12795
12796 // If the language has line comments, toggle those.
12797 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12798
12799 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12800 if ignore_indent {
12801 full_comment_prefixes = full_comment_prefixes
12802 .into_iter()
12803 .map(|s| Arc::from(s.trim_end()))
12804 .collect();
12805 }
12806
12807 if !full_comment_prefixes.is_empty() {
12808 let first_prefix = full_comment_prefixes
12809 .first()
12810 .expect("prefixes is non-empty");
12811 let prefix_trimmed_lengths = full_comment_prefixes
12812 .iter()
12813 .map(|p| p.trim_end_matches(' ').len())
12814 .collect::<SmallVec<[usize; 4]>>();
12815
12816 let mut all_selection_lines_are_comments = true;
12817
12818 for row in start_row.0..=end_row.0 {
12819 let row = MultiBufferRow(row);
12820 if start_row < end_row && snapshot.is_line_blank(row) {
12821 continue;
12822 }
12823
12824 let prefix_range = full_comment_prefixes
12825 .iter()
12826 .zip(prefix_trimmed_lengths.iter().copied())
12827 .map(|(prefix, trimmed_prefix_len)| {
12828 comment_prefix_range(
12829 snapshot.deref(),
12830 row,
12831 &prefix[..trimmed_prefix_len],
12832 &prefix[trimmed_prefix_len..],
12833 ignore_indent,
12834 )
12835 })
12836 .max_by_key(|range| range.end.column - range.start.column)
12837 .expect("prefixes is non-empty");
12838
12839 if prefix_range.is_empty() {
12840 all_selection_lines_are_comments = false;
12841 }
12842
12843 selection_edit_ranges.push(prefix_range);
12844 }
12845
12846 if all_selection_lines_are_comments {
12847 edits.extend(
12848 selection_edit_ranges
12849 .iter()
12850 .cloned()
12851 .map(|range| (range, empty_str.clone())),
12852 );
12853 } else {
12854 let min_column = selection_edit_ranges
12855 .iter()
12856 .map(|range| range.start.column)
12857 .min()
12858 .unwrap_or(0);
12859 edits.extend(selection_edit_ranges.iter().map(|range| {
12860 let position = Point::new(range.start.row, min_column);
12861 (position..position, first_prefix.clone())
12862 }));
12863 }
12864 } else if let Some((full_comment_prefix, comment_suffix)) =
12865 language.block_comment_delimiters()
12866 {
12867 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
12868 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
12869 let prefix_range = comment_prefix_range(
12870 snapshot.deref(),
12871 start_row,
12872 comment_prefix,
12873 comment_prefix_whitespace,
12874 ignore_indent,
12875 );
12876 let suffix_range = comment_suffix_range(
12877 snapshot.deref(),
12878 end_row,
12879 comment_suffix.trim_start_matches(' '),
12880 comment_suffix.starts_with(' '),
12881 );
12882
12883 if prefix_range.is_empty() || suffix_range.is_empty() {
12884 edits.push((
12885 prefix_range.start..prefix_range.start,
12886 full_comment_prefix.clone(),
12887 ));
12888 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
12889 suffixes_inserted.push((end_row, comment_suffix.len()));
12890 } else {
12891 edits.push((prefix_range, empty_str.clone()));
12892 edits.push((suffix_range, empty_str.clone()));
12893 }
12894 } else {
12895 continue;
12896 }
12897 }
12898
12899 drop(snapshot);
12900 this.buffer.update(cx, |buffer, cx| {
12901 buffer.edit(edits, None, cx);
12902 });
12903
12904 // Adjust selections so that they end before any comment suffixes that
12905 // were inserted.
12906 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
12907 let mut selections = this.selections.all::<Point>(cx);
12908 let snapshot = this.buffer.read(cx).read(cx);
12909 for selection in &mut selections {
12910 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
12911 match row.cmp(&MultiBufferRow(selection.end.row)) {
12912 Ordering::Less => {
12913 suffixes_inserted.next();
12914 continue;
12915 }
12916 Ordering::Greater => break,
12917 Ordering::Equal => {
12918 if selection.end.column == snapshot.line_len(row) {
12919 if selection.is_empty() {
12920 selection.start.column -= suffix_len as u32;
12921 }
12922 selection.end.column -= suffix_len as u32;
12923 }
12924 break;
12925 }
12926 }
12927 }
12928 }
12929
12930 drop(snapshot);
12931 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12932 s.select(selections)
12933 });
12934
12935 let selections = this.selections.all::<Point>(cx);
12936 let selections_on_single_row = selections.windows(2).all(|selections| {
12937 selections[0].start.row == selections[1].start.row
12938 && selections[0].end.row == selections[1].end.row
12939 && selections[0].start.row == selections[0].end.row
12940 });
12941 let selections_selecting = selections
12942 .iter()
12943 .any(|selection| selection.start != selection.end);
12944 let advance_downwards = action.advance_downwards
12945 && selections_on_single_row
12946 && !selections_selecting
12947 && !matches!(this.mode, EditorMode::SingleLine { .. });
12948
12949 if advance_downwards {
12950 let snapshot = this.buffer.read(cx).snapshot(cx);
12951
12952 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12953 s.move_cursors_with(|display_snapshot, display_point, _| {
12954 let mut point = display_point.to_point(display_snapshot);
12955 point.row += 1;
12956 point = snapshot.clip_point(point, Bias::Left);
12957 let display_point = point.to_display_point(display_snapshot);
12958 let goal = SelectionGoal::HorizontalPosition(
12959 display_snapshot
12960 .x_for_display_point(display_point, text_layout_details)
12961 .into(),
12962 );
12963 (display_point, goal)
12964 })
12965 });
12966 }
12967 });
12968 }
12969
12970 pub fn select_enclosing_symbol(
12971 &mut self,
12972 _: &SelectEnclosingSymbol,
12973 window: &mut Window,
12974 cx: &mut Context<Self>,
12975 ) {
12976 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12977
12978 let buffer = self.buffer.read(cx).snapshot(cx);
12979 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
12980
12981 fn update_selection(
12982 selection: &Selection<usize>,
12983 buffer_snap: &MultiBufferSnapshot,
12984 ) -> Option<Selection<usize>> {
12985 let cursor = selection.head();
12986 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
12987 for symbol in symbols.iter().rev() {
12988 let start = symbol.range.start.to_offset(buffer_snap);
12989 let end = symbol.range.end.to_offset(buffer_snap);
12990 let new_range = start..end;
12991 if start < selection.start || end > selection.end {
12992 return Some(Selection {
12993 id: selection.id,
12994 start: new_range.start,
12995 end: new_range.end,
12996 goal: SelectionGoal::None,
12997 reversed: selection.reversed,
12998 });
12999 }
13000 }
13001 None
13002 }
13003
13004 let mut selected_larger_symbol = false;
13005 let new_selections = old_selections
13006 .iter()
13007 .map(|selection| match update_selection(selection, &buffer) {
13008 Some(new_selection) => {
13009 if new_selection.range() != selection.range() {
13010 selected_larger_symbol = true;
13011 }
13012 new_selection
13013 }
13014 None => selection.clone(),
13015 })
13016 .collect::<Vec<_>>();
13017
13018 if selected_larger_symbol {
13019 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13020 s.select(new_selections);
13021 });
13022 }
13023 }
13024
13025 pub fn select_larger_syntax_node(
13026 &mut self,
13027 _: &SelectLargerSyntaxNode,
13028 window: &mut Window,
13029 cx: &mut Context<Self>,
13030 ) {
13031 let Some(visible_row_count) = self.visible_row_count() else {
13032 return;
13033 };
13034 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13035 if old_selections.is_empty() {
13036 return;
13037 }
13038
13039 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13040
13041 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13042 let buffer = self.buffer.read(cx).snapshot(cx);
13043
13044 let mut selected_larger_node = false;
13045 let mut new_selections = old_selections
13046 .iter()
13047 .map(|selection| {
13048 let old_range = selection.start..selection.end;
13049
13050 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13051 // manually select word at selection
13052 if ["string_content", "inline"].contains(&node.kind()) {
13053 let word_range = {
13054 let display_point = buffer
13055 .offset_to_point(old_range.start)
13056 .to_display_point(&display_map);
13057 let Range { start, end } =
13058 movement::surrounding_word(&display_map, display_point);
13059 start.to_point(&display_map).to_offset(&buffer)
13060 ..end.to_point(&display_map).to_offset(&buffer)
13061 };
13062 // ignore if word is already selected
13063 if !word_range.is_empty() && old_range != word_range {
13064 let last_word_range = {
13065 let display_point = buffer
13066 .offset_to_point(old_range.end)
13067 .to_display_point(&display_map);
13068 let Range { start, end } =
13069 movement::surrounding_word(&display_map, display_point);
13070 start.to_point(&display_map).to_offset(&buffer)
13071 ..end.to_point(&display_map).to_offset(&buffer)
13072 };
13073 // only select word if start and end point belongs to same word
13074 if word_range == last_word_range {
13075 selected_larger_node = true;
13076 return Selection {
13077 id: selection.id,
13078 start: word_range.start,
13079 end: word_range.end,
13080 goal: SelectionGoal::None,
13081 reversed: selection.reversed,
13082 };
13083 }
13084 }
13085 }
13086 }
13087
13088 let mut new_range = old_range.clone();
13089 while let Some((_node, containing_range)) =
13090 buffer.syntax_ancestor(new_range.clone())
13091 {
13092 new_range = match containing_range {
13093 MultiOrSingleBufferOffsetRange::Single(_) => break,
13094 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13095 };
13096 if !display_map.intersects_fold(new_range.start)
13097 && !display_map.intersects_fold(new_range.end)
13098 {
13099 break;
13100 }
13101 }
13102
13103 selected_larger_node |= new_range != old_range;
13104 Selection {
13105 id: selection.id,
13106 start: new_range.start,
13107 end: new_range.end,
13108 goal: SelectionGoal::None,
13109 reversed: selection.reversed,
13110 }
13111 })
13112 .collect::<Vec<_>>();
13113
13114 if !selected_larger_node {
13115 return; // don't put this call in the history
13116 }
13117
13118 // scroll based on transformation done to the last selection created by the user
13119 let (last_old, last_new) = old_selections
13120 .last()
13121 .zip(new_selections.last().cloned())
13122 .expect("old_selections isn't empty");
13123
13124 // revert selection
13125 let is_selection_reversed = {
13126 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13127 new_selections.last_mut().expect("checked above").reversed =
13128 should_newest_selection_be_reversed;
13129 should_newest_selection_be_reversed
13130 };
13131
13132 if selected_larger_node {
13133 self.select_syntax_node_history.disable_clearing = true;
13134 self.change_selections(None, window, cx, |s| {
13135 s.select(new_selections.clone());
13136 });
13137 self.select_syntax_node_history.disable_clearing = false;
13138 }
13139
13140 let start_row = last_new.start.to_display_point(&display_map).row().0;
13141 let end_row = last_new.end.to_display_point(&display_map).row().0;
13142 let selection_height = end_row - start_row + 1;
13143 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13144
13145 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13146 let scroll_behavior = if fits_on_the_screen {
13147 self.request_autoscroll(Autoscroll::fit(), cx);
13148 SelectSyntaxNodeScrollBehavior::FitSelection
13149 } else if is_selection_reversed {
13150 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13151 SelectSyntaxNodeScrollBehavior::CursorTop
13152 } else {
13153 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13154 SelectSyntaxNodeScrollBehavior::CursorBottom
13155 };
13156
13157 self.select_syntax_node_history.push((
13158 old_selections,
13159 scroll_behavior,
13160 is_selection_reversed,
13161 ));
13162 }
13163
13164 pub fn select_smaller_syntax_node(
13165 &mut self,
13166 _: &SelectSmallerSyntaxNode,
13167 window: &mut Window,
13168 cx: &mut Context<Self>,
13169 ) {
13170 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13171
13172 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13173 self.select_syntax_node_history.pop()
13174 {
13175 if let Some(selection) = selections.last_mut() {
13176 selection.reversed = is_selection_reversed;
13177 }
13178
13179 self.select_syntax_node_history.disable_clearing = true;
13180 self.change_selections(None, window, cx, |s| {
13181 s.select(selections.to_vec());
13182 });
13183 self.select_syntax_node_history.disable_clearing = false;
13184
13185 match scroll_behavior {
13186 SelectSyntaxNodeScrollBehavior::CursorTop => {
13187 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13188 }
13189 SelectSyntaxNodeScrollBehavior::FitSelection => {
13190 self.request_autoscroll(Autoscroll::fit(), cx);
13191 }
13192 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13193 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13194 }
13195 }
13196 }
13197 }
13198
13199 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13200 if !EditorSettings::get_global(cx).gutter.runnables {
13201 self.clear_tasks();
13202 return Task::ready(());
13203 }
13204 let project = self.project.as_ref().map(Entity::downgrade);
13205 let task_sources = self.lsp_task_sources(cx);
13206 cx.spawn_in(window, async move |editor, cx| {
13207 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13208 let Some(project) = project.and_then(|p| p.upgrade()) else {
13209 return;
13210 };
13211 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13212 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13213 }) else {
13214 return;
13215 };
13216
13217 let hide_runnables = project
13218 .update(cx, |project, cx| {
13219 // Do not display any test indicators in non-dev server remote projects.
13220 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13221 })
13222 .unwrap_or(true);
13223 if hide_runnables {
13224 return;
13225 }
13226 let new_rows =
13227 cx.background_spawn({
13228 let snapshot = display_snapshot.clone();
13229 async move {
13230 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13231 }
13232 })
13233 .await;
13234 let Ok(lsp_tasks) =
13235 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13236 else {
13237 return;
13238 };
13239 let lsp_tasks = lsp_tasks.await;
13240
13241 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13242 lsp_tasks
13243 .into_iter()
13244 .flat_map(|(kind, tasks)| {
13245 tasks.into_iter().filter_map(move |(location, task)| {
13246 Some((kind.clone(), location?, task))
13247 })
13248 })
13249 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13250 let buffer = location.target.buffer;
13251 let buffer_snapshot = buffer.read(cx).snapshot();
13252 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13253 |(excerpt_id, snapshot, _)| {
13254 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13255 display_snapshot
13256 .buffer_snapshot
13257 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13258 } else {
13259 None
13260 }
13261 },
13262 );
13263 if let Some(offset) = offset {
13264 let task_buffer_range =
13265 location.target.range.to_point(&buffer_snapshot);
13266 let context_buffer_range =
13267 task_buffer_range.to_offset(&buffer_snapshot);
13268 let context_range = BufferOffset(context_buffer_range.start)
13269 ..BufferOffset(context_buffer_range.end);
13270
13271 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13272 .or_insert_with(|| RunnableTasks {
13273 templates: Vec::new(),
13274 offset,
13275 column: task_buffer_range.start.column,
13276 extra_variables: HashMap::default(),
13277 context_range,
13278 })
13279 .templates
13280 .push((kind, task.original_task().clone()));
13281 }
13282
13283 acc
13284 })
13285 }) else {
13286 return;
13287 };
13288
13289 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
13290 editor
13291 .update(cx, |editor, _| {
13292 editor.clear_tasks();
13293 for (key, mut value) in rows {
13294 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13295 value.templates.extend(lsp_tasks.templates);
13296 }
13297
13298 editor.insert_tasks(key, value);
13299 }
13300 for (key, value) in lsp_tasks_by_rows {
13301 editor.insert_tasks(key, value);
13302 }
13303 })
13304 .ok();
13305 })
13306 }
13307 fn fetch_runnable_ranges(
13308 snapshot: &DisplaySnapshot,
13309 range: Range<Anchor>,
13310 ) -> Vec<language::RunnableRange> {
13311 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13312 }
13313
13314 fn runnable_rows(
13315 project: Entity<Project>,
13316 snapshot: DisplaySnapshot,
13317 runnable_ranges: Vec<RunnableRange>,
13318 mut cx: AsyncWindowContext,
13319 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13320 runnable_ranges
13321 .into_iter()
13322 .filter_map(|mut runnable| {
13323 let tasks = cx
13324 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13325 .ok()?;
13326 if tasks.is_empty() {
13327 return None;
13328 }
13329
13330 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13331
13332 let row = snapshot
13333 .buffer_snapshot
13334 .buffer_line_for_row(MultiBufferRow(point.row))?
13335 .1
13336 .start
13337 .row;
13338
13339 let context_range =
13340 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13341 Some((
13342 (runnable.buffer_id, row),
13343 RunnableTasks {
13344 templates: tasks,
13345 offset: snapshot
13346 .buffer_snapshot
13347 .anchor_before(runnable.run_range.start),
13348 context_range,
13349 column: point.column,
13350 extra_variables: runnable.extra_captures,
13351 },
13352 ))
13353 })
13354 .collect()
13355 }
13356
13357 fn templates_with_tags(
13358 project: &Entity<Project>,
13359 runnable: &mut Runnable,
13360 cx: &mut App,
13361 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13362 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13363 let (worktree_id, file) = project
13364 .buffer_for_id(runnable.buffer, cx)
13365 .and_then(|buffer| buffer.read(cx).file())
13366 .map(|file| (file.worktree_id(cx), file.clone()))
13367 .unzip();
13368
13369 (
13370 project.task_store().read(cx).task_inventory().cloned(),
13371 worktree_id,
13372 file,
13373 )
13374 });
13375
13376 let mut templates_with_tags = mem::take(&mut runnable.tags)
13377 .into_iter()
13378 .flat_map(|RunnableTag(tag)| {
13379 inventory
13380 .as_ref()
13381 .into_iter()
13382 .flat_map(|inventory| {
13383 inventory.read(cx).list_tasks(
13384 file.clone(),
13385 Some(runnable.language.clone()),
13386 worktree_id,
13387 cx,
13388 )
13389 })
13390 .filter(move |(_, template)| {
13391 template.tags.iter().any(|source_tag| source_tag == &tag)
13392 })
13393 })
13394 .sorted_by_key(|(kind, _)| kind.to_owned())
13395 .collect::<Vec<_>>();
13396 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13397 // Strongest source wins; if we have worktree tag binding, prefer that to
13398 // global and language bindings;
13399 // if we have a global binding, prefer that to language binding.
13400 let first_mismatch = templates_with_tags
13401 .iter()
13402 .position(|(tag_source, _)| tag_source != leading_tag_source);
13403 if let Some(index) = first_mismatch {
13404 templates_with_tags.truncate(index);
13405 }
13406 }
13407
13408 templates_with_tags
13409 }
13410
13411 pub fn move_to_enclosing_bracket(
13412 &mut self,
13413 _: &MoveToEnclosingBracket,
13414 window: &mut Window,
13415 cx: &mut Context<Self>,
13416 ) {
13417 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13418 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13419 s.move_offsets_with(|snapshot, selection| {
13420 let Some(enclosing_bracket_ranges) =
13421 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13422 else {
13423 return;
13424 };
13425
13426 let mut best_length = usize::MAX;
13427 let mut best_inside = false;
13428 let mut best_in_bracket_range = false;
13429 let mut best_destination = None;
13430 for (open, close) in enclosing_bracket_ranges {
13431 let close = close.to_inclusive();
13432 let length = close.end() - open.start;
13433 let inside = selection.start >= open.end && selection.end <= *close.start();
13434 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13435 || close.contains(&selection.head());
13436
13437 // If best is next to a bracket and current isn't, skip
13438 if !in_bracket_range && best_in_bracket_range {
13439 continue;
13440 }
13441
13442 // Prefer smaller lengths unless best is inside and current isn't
13443 if length > best_length && (best_inside || !inside) {
13444 continue;
13445 }
13446
13447 best_length = length;
13448 best_inside = inside;
13449 best_in_bracket_range = in_bracket_range;
13450 best_destination = Some(
13451 if close.contains(&selection.start) && close.contains(&selection.end) {
13452 if inside { open.end } else { open.start }
13453 } else if inside {
13454 *close.start()
13455 } else {
13456 *close.end()
13457 },
13458 );
13459 }
13460
13461 if let Some(destination) = best_destination {
13462 selection.collapse_to(destination, SelectionGoal::None);
13463 }
13464 })
13465 });
13466 }
13467
13468 pub fn undo_selection(
13469 &mut self,
13470 _: &UndoSelection,
13471 window: &mut Window,
13472 cx: &mut Context<Self>,
13473 ) {
13474 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13475 self.end_selection(window, cx);
13476 self.selection_history.mode = SelectionHistoryMode::Undoing;
13477 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13478 self.change_selections(None, window, cx, |s| {
13479 s.select_anchors(entry.selections.to_vec())
13480 });
13481 self.select_next_state = entry.select_next_state;
13482 self.select_prev_state = entry.select_prev_state;
13483 self.add_selections_state = entry.add_selections_state;
13484 self.request_autoscroll(Autoscroll::newest(), cx);
13485 }
13486 self.selection_history.mode = SelectionHistoryMode::Normal;
13487 }
13488
13489 pub fn redo_selection(
13490 &mut self,
13491 _: &RedoSelection,
13492 window: &mut Window,
13493 cx: &mut Context<Self>,
13494 ) {
13495 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13496 self.end_selection(window, cx);
13497 self.selection_history.mode = SelectionHistoryMode::Redoing;
13498 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13499 self.change_selections(None, window, cx, |s| {
13500 s.select_anchors(entry.selections.to_vec())
13501 });
13502 self.select_next_state = entry.select_next_state;
13503 self.select_prev_state = entry.select_prev_state;
13504 self.add_selections_state = entry.add_selections_state;
13505 self.request_autoscroll(Autoscroll::newest(), cx);
13506 }
13507 self.selection_history.mode = SelectionHistoryMode::Normal;
13508 }
13509
13510 pub fn expand_excerpts(
13511 &mut self,
13512 action: &ExpandExcerpts,
13513 _: &mut Window,
13514 cx: &mut Context<Self>,
13515 ) {
13516 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13517 }
13518
13519 pub fn expand_excerpts_down(
13520 &mut self,
13521 action: &ExpandExcerptsDown,
13522 _: &mut Window,
13523 cx: &mut Context<Self>,
13524 ) {
13525 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13526 }
13527
13528 pub fn expand_excerpts_up(
13529 &mut self,
13530 action: &ExpandExcerptsUp,
13531 _: &mut Window,
13532 cx: &mut Context<Self>,
13533 ) {
13534 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13535 }
13536
13537 pub fn expand_excerpts_for_direction(
13538 &mut self,
13539 lines: u32,
13540 direction: ExpandExcerptDirection,
13541
13542 cx: &mut Context<Self>,
13543 ) {
13544 let selections = self.selections.disjoint_anchors();
13545
13546 let lines = if lines == 0 {
13547 EditorSettings::get_global(cx).expand_excerpt_lines
13548 } else {
13549 lines
13550 };
13551
13552 self.buffer.update(cx, |buffer, cx| {
13553 let snapshot = buffer.snapshot(cx);
13554 let mut excerpt_ids = selections
13555 .iter()
13556 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13557 .collect::<Vec<_>>();
13558 excerpt_ids.sort();
13559 excerpt_ids.dedup();
13560 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13561 })
13562 }
13563
13564 pub fn expand_excerpt(
13565 &mut self,
13566 excerpt: ExcerptId,
13567 direction: ExpandExcerptDirection,
13568 window: &mut Window,
13569 cx: &mut Context<Self>,
13570 ) {
13571 let current_scroll_position = self.scroll_position(cx);
13572 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13573 let mut should_scroll_up = false;
13574
13575 if direction == ExpandExcerptDirection::Down {
13576 let multi_buffer = self.buffer.read(cx);
13577 let snapshot = multi_buffer.snapshot(cx);
13578 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13579 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13580 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13581 let buffer_snapshot = buffer.read(cx).snapshot();
13582 let excerpt_end_row =
13583 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13584 let last_row = buffer_snapshot.max_point().row;
13585 let lines_below = last_row.saturating_sub(excerpt_end_row);
13586 should_scroll_up = lines_below >= lines_to_expand;
13587 }
13588 }
13589 }
13590 }
13591
13592 self.buffer.update(cx, |buffer, cx| {
13593 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13594 });
13595
13596 if should_scroll_up {
13597 let new_scroll_position =
13598 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13599 self.set_scroll_position(new_scroll_position, window, cx);
13600 }
13601 }
13602
13603 pub fn go_to_singleton_buffer_point(
13604 &mut self,
13605 point: Point,
13606 window: &mut Window,
13607 cx: &mut Context<Self>,
13608 ) {
13609 self.go_to_singleton_buffer_range(point..point, window, cx);
13610 }
13611
13612 pub fn go_to_singleton_buffer_range(
13613 &mut self,
13614 range: Range<Point>,
13615 window: &mut Window,
13616 cx: &mut Context<Self>,
13617 ) {
13618 let multibuffer = self.buffer().read(cx);
13619 let Some(buffer) = multibuffer.as_singleton() else {
13620 return;
13621 };
13622 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13623 return;
13624 };
13625 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13626 return;
13627 };
13628 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13629 s.select_anchor_ranges([start..end])
13630 });
13631 }
13632
13633 pub fn go_to_diagnostic(
13634 &mut self,
13635 _: &GoToDiagnostic,
13636 window: &mut Window,
13637 cx: &mut Context<Self>,
13638 ) {
13639 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13640 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13641 }
13642
13643 pub fn go_to_prev_diagnostic(
13644 &mut self,
13645 _: &GoToPreviousDiagnostic,
13646 window: &mut Window,
13647 cx: &mut Context<Self>,
13648 ) {
13649 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13650 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13651 }
13652
13653 pub fn go_to_diagnostic_impl(
13654 &mut self,
13655 direction: Direction,
13656 window: &mut Window,
13657 cx: &mut Context<Self>,
13658 ) {
13659 let buffer = self.buffer.read(cx).snapshot(cx);
13660 let selection = self.selections.newest::<usize>(cx);
13661
13662 let mut active_group_id = None;
13663 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13664 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13665 active_group_id = Some(active_group.group_id);
13666 }
13667 }
13668
13669 fn filtered(
13670 snapshot: EditorSnapshot,
13671 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13672 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13673 diagnostics
13674 .filter(|entry| entry.range.start != entry.range.end)
13675 .filter(|entry| !entry.diagnostic.is_unnecessary)
13676 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13677 }
13678
13679 let snapshot = self.snapshot(window, cx);
13680 let before = filtered(
13681 snapshot.clone(),
13682 buffer
13683 .diagnostics_in_range(0..selection.start)
13684 .filter(|entry| entry.range.start <= selection.start),
13685 );
13686 let after = filtered(
13687 snapshot,
13688 buffer
13689 .diagnostics_in_range(selection.start..buffer.len())
13690 .filter(|entry| entry.range.start >= selection.start),
13691 );
13692
13693 let mut found: Option<DiagnosticEntry<usize>> = None;
13694 if direction == Direction::Prev {
13695 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13696 {
13697 for diagnostic in prev_diagnostics.into_iter().rev() {
13698 if diagnostic.range.start != selection.start
13699 || active_group_id
13700 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13701 {
13702 found = Some(diagnostic);
13703 break 'outer;
13704 }
13705 }
13706 }
13707 } else {
13708 for diagnostic in after.chain(before) {
13709 if diagnostic.range.start != selection.start
13710 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13711 {
13712 found = Some(diagnostic);
13713 break;
13714 }
13715 }
13716 }
13717 let Some(next_diagnostic) = found else {
13718 return;
13719 };
13720
13721 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13722 return;
13723 };
13724 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13725 s.select_ranges(vec![
13726 next_diagnostic.range.start..next_diagnostic.range.start,
13727 ])
13728 });
13729 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13730 self.refresh_inline_completion(false, true, window, cx);
13731 }
13732
13733 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13734 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13735 let snapshot = self.snapshot(window, cx);
13736 let selection = self.selections.newest::<Point>(cx);
13737 self.go_to_hunk_before_or_after_position(
13738 &snapshot,
13739 selection.head(),
13740 Direction::Next,
13741 window,
13742 cx,
13743 );
13744 }
13745
13746 pub fn go_to_hunk_before_or_after_position(
13747 &mut self,
13748 snapshot: &EditorSnapshot,
13749 position: Point,
13750 direction: Direction,
13751 window: &mut Window,
13752 cx: &mut Context<Editor>,
13753 ) {
13754 let row = if direction == Direction::Next {
13755 self.hunk_after_position(snapshot, position)
13756 .map(|hunk| hunk.row_range.start)
13757 } else {
13758 self.hunk_before_position(snapshot, position)
13759 };
13760
13761 if let Some(row) = row {
13762 let destination = Point::new(row.0, 0);
13763 let autoscroll = Autoscroll::center();
13764
13765 self.unfold_ranges(&[destination..destination], false, false, cx);
13766 self.change_selections(Some(autoscroll), window, cx, |s| {
13767 s.select_ranges([destination..destination]);
13768 });
13769 }
13770 }
13771
13772 fn hunk_after_position(
13773 &mut self,
13774 snapshot: &EditorSnapshot,
13775 position: Point,
13776 ) -> Option<MultiBufferDiffHunk> {
13777 snapshot
13778 .buffer_snapshot
13779 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13780 .find(|hunk| hunk.row_range.start.0 > position.row)
13781 .or_else(|| {
13782 snapshot
13783 .buffer_snapshot
13784 .diff_hunks_in_range(Point::zero()..position)
13785 .find(|hunk| hunk.row_range.end.0 < position.row)
13786 })
13787 }
13788
13789 fn go_to_prev_hunk(
13790 &mut self,
13791 _: &GoToPreviousHunk,
13792 window: &mut Window,
13793 cx: &mut Context<Self>,
13794 ) {
13795 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13796 let snapshot = self.snapshot(window, cx);
13797 let selection = self.selections.newest::<Point>(cx);
13798 self.go_to_hunk_before_or_after_position(
13799 &snapshot,
13800 selection.head(),
13801 Direction::Prev,
13802 window,
13803 cx,
13804 );
13805 }
13806
13807 fn hunk_before_position(
13808 &mut self,
13809 snapshot: &EditorSnapshot,
13810 position: Point,
13811 ) -> Option<MultiBufferRow> {
13812 snapshot
13813 .buffer_snapshot
13814 .diff_hunk_before(position)
13815 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
13816 }
13817
13818 fn go_to_next_change(
13819 &mut self,
13820 _: &GoToNextChange,
13821 window: &mut Window,
13822 cx: &mut Context<Self>,
13823 ) {
13824 if let Some(selections) = self
13825 .change_list
13826 .next_change(1, Direction::Next)
13827 .map(|s| s.to_vec())
13828 {
13829 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13830 let map = s.display_map();
13831 s.select_display_ranges(selections.iter().map(|a| {
13832 let point = a.to_display_point(&map);
13833 point..point
13834 }))
13835 })
13836 }
13837 }
13838
13839 fn go_to_previous_change(
13840 &mut self,
13841 _: &GoToPreviousChange,
13842 window: &mut Window,
13843 cx: &mut Context<Self>,
13844 ) {
13845 if let Some(selections) = self
13846 .change_list
13847 .next_change(1, Direction::Prev)
13848 .map(|s| s.to_vec())
13849 {
13850 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13851 let map = s.display_map();
13852 s.select_display_ranges(selections.iter().map(|a| {
13853 let point = a.to_display_point(&map);
13854 point..point
13855 }))
13856 })
13857 }
13858 }
13859
13860 fn go_to_line<T: 'static>(
13861 &mut self,
13862 position: Anchor,
13863 highlight_color: Option<Hsla>,
13864 window: &mut Window,
13865 cx: &mut Context<Self>,
13866 ) {
13867 let snapshot = self.snapshot(window, cx).display_snapshot;
13868 let position = position.to_point(&snapshot.buffer_snapshot);
13869 let start = snapshot
13870 .buffer_snapshot
13871 .clip_point(Point::new(position.row, 0), Bias::Left);
13872 let end = start + Point::new(1, 0);
13873 let start = snapshot.buffer_snapshot.anchor_before(start);
13874 let end = snapshot.buffer_snapshot.anchor_before(end);
13875
13876 self.highlight_rows::<T>(
13877 start..end,
13878 highlight_color
13879 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
13880 Default::default(),
13881 cx,
13882 );
13883 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
13884 }
13885
13886 pub fn go_to_definition(
13887 &mut self,
13888 _: &GoToDefinition,
13889 window: &mut Window,
13890 cx: &mut Context<Self>,
13891 ) -> Task<Result<Navigated>> {
13892 let definition =
13893 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
13894 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
13895 cx.spawn_in(window, async move |editor, cx| {
13896 if definition.await? == Navigated::Yes {
13897 return Ok(Navigated::Yes);
13898 }
13899 match fallback_strategy {
13900 GoToDefinitionFallback::None => Ok(Navigated::No),
13901 GoToDefinitionFallback::FindAllReferences => {
13902 match editor.update_in(cx, |editor, window, cx| {
13903 editor.find_all_references(&FindAllReferences, window, cx)
13904 })? {
13905 Some(references) => references.await,
13906 None => Ok(Navigated::No),
13907 }
13908 }
13909 }
13910 })
13911 }
13912
13913 pub fn go_to_declaration(
13914 &mut self,
13915 _: &GoToDeclaration,
13916 window: &mut Window,
13917 cx: &mut Context<Self>,
13918 ) -> Task<Result<Navigated>> {
13919 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
13920 }
13921
13922 pub fn go_to_declaration_split(
13923 &mut self,
13924 _: &GoToDeclaration,
13925 window: &mut Window,
13926 cx: &mut Context<Self>,
13927 ) -> Task<Result<Navigated>> {
13928 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
13929 }
13930
13931 pub fn go_to_implementation(
13932 &mut self,
13933 _: &GoToImplementation,
13934 window: &mut Window,
13935 cx: &mut Context<Self>,
13936 ) -> Task<Result<Navigated>> {
13937 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
13938 }
13939
13940 pub fn go_to_implementation_split(
13941 &mut self,
13942 _: &GoToImplementationSplit,
13943 window: &mut Window,
13944 cx: &mut Context<Self>,
13945 ) -> Task<Result<Navigated>> {
13946 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
13947 }
13948
13949 pub fn go_to_type_definition(
13950 &mut self,
13951 _: &GoToTypeDefinition,
13952 window: &mut Window,
13953 cx: &mut Context<Self>,
13954 ) -> Task<Result<Navigated>> {
13955 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
13956 }
13957
13958 pub fn go_to_definition_split(
13959 &mut self,
13960 _: &GoToDefinitionSplit,
13961 window: &mut Window,
13962 cx: &mut Context<Self>,
13963 ) -> Task<Result<Navigated>> {
13964 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
13965 }
13966
13967 pub fn go_to_type_definition_split(
13968 &mut self,
13969 _: &GoToTypeDefinitionSplit,
13970 window: &mut Window,
13971 cx: &mut Context<Self>,
13972 ) -> Task<Result<Navigated>> {
13973 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
13974 }
13975
13976 fn go_to_definition_of_kind(
13977 &mut self,
13978 kind: GotoDefinitionKind,
13979 split: bool,
13980 window: &mut Window,
13981 cx: &mut Context<Self>,
13982 ) -> Task<Result<Navigated>> {
13983 let Some(provider) = self.semantics_provider.clone() else {
13984 return Task::ready(Ok(Navigated::No));
13985 };
13986 let head = self.selections.newest::<usize>(cx).head();
13987 let buffer = self.buffer.read(cx);
13988 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
13989 text_anchor
13990 } else {
13991 return Task::ready(Ok(Navigated::No));
13992 };
13993
13994 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
13995 return Task::ready(Ok(Navigated::No));
13996 };
13997
13998 cx.spawn_in(window, async move |editor, cx| {
13999 let definitions = definitions.await?;
14000 let navigated = editor
14001 .update_in(cx, |editor, window, cx| {
14002 editor.navigate_to_hover_links(
14003 Some(kind),
14004 definitions
14005 .into_iter()
14006 .filter(|location| {
14007 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14008 })
14009 .map(HoverLink::Text)
14010 .collect::<Vec<_>>(),
14011 split,
14012 window,
14013 cx,
14014 )
14015 })?
14016 .await?;
14017 anyhow::Ok(navigated)
14018 })
14019 }
14020
14021 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14022 let selection = self.selections.newest_anchor();
14023 let head = selection.head();
14024 let tail = selection.tail();
14025
14026 let Some((buffer, start_position)) =
14027 self.buffer.read(cx).text_anchor_for_position(head, cx)
14028 else {
14029 return;
14030 };
14031
14032 let end_position = if head != tail {
14033 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14034 return;
14035 };
14036 Some(pos)
14037 } else {
14038 None
14039 };
14040
14041 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14042 let url = if let Some(end_pos) = end_position {
14043 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14044 } else {
14045 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14046 };
14047
14048 if let Some(url) = url {
14049 editor.update(cx, |_, cx| {
14050 cx.open_url(&url);
14051 })
14052 } else {
14053 Ok(())
14054 }
14055 });
14056
14057 url_finder.detach();
14058 }
14059
14060 pub fn open_selected_filename(
14061 &mut self,
14062 _: &OpenSelectedFilename,
14063 window: &mut Window,
14064 cx: &mut Context<Self>,
14065 ) {
14066 let Some(workspace) = self.workspace() else {
14067 return;
14068 };
14069
14070 let position = self.selections.newest_anchor().head();
14071
14072 let Some((buffer, buffer_position)) =
14073 self.buffer.read(cx).text_anchor_for_position(position, cx)
14074 else {
14075 return;
14076 };
14077
14078 let project = self.project.clone();
14079
14080 cx.spawn_in(window, async move |_, cx| {
14081 let result = find_file(&buffer, project, buffer_position, cx).await;
14082
14083 if let Some((_, path)) = result {
14084 workspace
14085 .update_in(cx, |workspace, window, cx| {
14086 workspace.open_resolved_path(path, window, cx)
14087 })?
14088 .await?;
14089 }
14090 anyhow::Ok(())
14091 })
14092 .detach();
14093 }
14094
14095 pub(crate) fn navigate_to_hover_links(
14096 &mut self,
14097 kind: Option<GotoDefinitionKind>,
14098 mut definitions: Vec<HoverLink>,
14099 split: bool,
14100 window: &mut Window,
14101 cx: &mut Context<Editor>,
14102 ) -> Task<Result<Navigated>> {
14103 // If there is one definition, just open it directly
14104 if definitions.len() == 1 {
14105 let definition = definitions.pop().unwrap();
14106
14107 enum TargetTaskResult {
14108 Location(Option<Location>),
14109 AlreadyNavigated,
14110 }
14111
14112 let target_task = match definition {
14113 HoverLink::Text(link) => {
14114 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14115 }
14116 HoverLink::InlayHint(lsp_location, server_id) => {
14117 let computation =
14118 self.compute_target_location(lsp_location, server_id, window, cx);
14119 cx.background_spawn(async move {
14120 let location = computation.await?;
14121 Ok(TargetTaskResult::Location(location))
14122 })
14123 }
14124 HoverLink::Url(url) => {
14125 cx.open_url(&url);
14126 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14127 }
14128 HoverLink::File(path) => {
14129 if let Some(workspace) = self.workspace() {
14130 cx.spawn_in(window, async move |_, cx| {
14131 workspace
14132 .update_in(cx, |workspace, window, cx| {
14133 workspace.open_resolved_path(path, window, cx)
14134 })?
14135 .await
14136 .map(|_| TargetTaskResult::AlreadyNavigated)
14137 })
14138 } else {
14139 Task::ready(Ok(TargetTaskResult::Location(None)))
14140 }
14141 }
14142 };
14143 cx.spawn_in(window, async move |editor, cx| {
14144 let target = match target_task.await.context("target resolution task")? {
14145 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14146 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14147 TargetTaskResult::Location(Some(target)) => target,
14148 };
14149
14150 editor.update_in(cx, |editor, window, cx| {
14151 let Some(workspace) = editor.workspace() else {
14152 return Navigated::No;
14153 };
14154 let pane = workspace.read(cx).active_pane().clone();
14155
14156 let range = target.range.to_point(target.buffer.read(cx));
14157 let range = editor.range_for_match(&range);
14158 let range = collapse_multiline_range(range);
14159
14160 if !split
14161 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14162 {
14163 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14164 } else {
14165 window.defer(cx, move |window, cx| {
14166 let target_editor: Entity<Self> =
14167 workspace.update(cx, |workspace, cx| {
14168 let pane = if split {
14169 workspace.adjacent_pane(window, cx)
14170 } else {
14171 workspace.active_pane().clone()
14172 };
14173
14174 workspace.open_project_item(
14175 pane,
14176 target.buffer.clone(),
14177 true,
14178 true,
14179 window,
14180 cx,
14181 )
14182 });
14183 target_editor.update(cx, |target_editor, cx| {
14184 // When selecting a definition in a different buffer, disable the nav history
14185 // to avoid creating a history entry at the previous cursor location.
14186 pane.update(cx, |pane, _| pane.disable_history());
14187 target_editor.go_to_singleton_buffer_range(range, window, cx);
14188 pane.update(cx, |pane, _| pane.enable_history());
14189 });
14190 });
14191 }
14192 Navigated::Yes
14193 })
14194 })
14195 } else if !definitions.is_empty() {
14196 cx.spawn_in(window, async move |editor, cx| {
14197 let (title, location_tasks, workspace) = editor
14198 .update_in(cx, |editor, window, cx| {
14199 let tab_kind = match kind {
14200 Some(GotoDefinitionKind::Implementation) => "Implementations",
14201 _ => "Definitions",
14202 };
14203 let title = definitions
14204 .iter()
14205 .find_map(|definition| match definition {
14206 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14207 let buffer = origin.buffer.read(cx);
14208 format!(
14209 "{} for {}",
14210 tab_kind,
14211 buffer
14212 .text_for_range(origin.range.clone())
14213 .collect::<String>()
14214 )
14215 }),
14216 HoverLink::InlayHint(_, _) => None,
14217 HoverLink::Url(_) => None,
14218 HoverLink::File(_) => None,
14219 })
14220 .unwrap_or(tab_kind.to_string());
14221 let location_tasks = definitions
14222 .into_iter()
14223 .map(|definition| match definition {
14224 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14225 HoverLink::InlayHint(lsp_location, server_id) => editor
14226 .compute_target_location(lsp_location, server_id, window, cx),
14227 HoverLink::Url(_) => Task::ready(Ok(None)),
14228 HoverLink::File(_) => Task::ready(Ok(None)),
14229 })
14230 .collect::<Vec<_>>();
14231 (title, location_tasks, editor.workspace().clone())
14232 })
14233 .context("location tasks preparation")?;
14234
14235 let locations = future::join_all(location_tasks)
14236 .await
14237 .into_iter()
14238 .filter_map(|location| location.transpose())
14239 .collect::<Result<_>>()
14240 .context("location tasks")?;
14241
14242 let Some(workspace) = workspace else {
14243 return Ok(Navigated::No);
14244 };
14245 let opened = workspace
14246 .update_in(cx, |workspace, window, cx| {
14247 Self::open_locations_in_multibuffer(
14248 workspace,
14249 locations,
14250 title,
14251 split,
14252 MultibufferSelectionMode::First,
14253 window,
14254 cx,
14255 )
14256 })
14257 .ok();
14258
14259 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14260 })
14261 } else {
14262 Task::ready(Ok(Navigated::No))
14263 }
14264 }
14265
14266 fn compute_target_location(
14267 &self,
14268 lsp_location: lsp::Location,
14269 server_id: LanguageServerId,
14270 window: &mut Window,
14271 cx: &mut Context<Self>,
14272 ) -> Task<anyhow::Result<Option<Location>>> {
14273 let Some(project) = self.project.clone() else {
14274 return Task::ready(Ok(None));
14275 };
14276
14277 cx.spawn_in(window, async move |editor, cx| {
14278 let location_task = editor.update(cx, |_, cx| {
14279 project.update(cx, |project, cx| {
14280 let language_server_name = project
14281 .language_server_statuses(cx)
14282 .find(|(id, _)| server_id == *id)
14283 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14284 language_server_name.map(|language_server_name| {
14285 project.open_local_buffer_via_lsp(
14286 lsp_location.uri.clone(),
14287 server_id,
14288 language_server_name,
14289 cx,
14290 )
14291 })
14292 })
14293 })?;
14294 let location = match location_task {
14295 Some(task) => Some({
14296 let target_buffer_handle = task.await.context("open local buffer")?;
14297 let range = target_buffer_handle.update(cx, |target_buffer, _| {
14298 let target_start = target_buffer
14299 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14300 let target_end = target_buffer
14301 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14302 target_buffer.anchor_after(target_start)
14303 ..target_buffer.anchor_before(target_end)
14304 })?;
14305 Location {
14306 buffer: target_buffer_handle,
14307 range,
14308 }
14309 }),
14310 None => None,
14311 };
14312 Ok(location)
14313 })
14314 }
14315
14316 pub fn find_all_references(
14317 &mut self,
14318 _: &FindAllReferences,
14319 window: &mut Window,
14320 cx: &mut Context<Self>,
14321 ) -> Option<Task<Result<Navigated>>> {
14322 let selection = self.selections.newest::<usize>(cx);
14323 let multi_buffer = self.buffer.read(cx);
14324 let head = selection.head();
14325
14326 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14327 let head_anchor = multi_buffer_snapshot.anchor_at(
14328 head,
14329 if head < selection.tail() {
14330 Bias::Right
14331 } else {
14332 Bias::Left
14333 },
14334 );
14335
14336 match self
14337 .find_all_references_task_sources
14338 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14339 {
14340 Ok(_) => {
14341 log::info!(
14342 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14343 );
14344 return None;
14345 }
14346 Err(i) => {
14347 self.find_all_references_task_sources.insert(i, head_anchor);
14348 }
14349 }
14350
14351 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14352 let workspace = self.workspace()?;
14353 let project = workspace.read(cx).project().clone();
14354 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14355 Some(cx.spawn_in(window, async move |editor, cx| {
14356 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14357 if let Ok(i) = editor
14358 .find_all_references_task_sources
14359 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14360 {
14361 editor.find_all_references_task_sources.remove(i);
14362 }
14363 });
14364
14365 let locations = references.await?;
14366 if locations.is_empty() {
14367 return anyhow::Ok(Navigated::No);
14368 }
14369
14370 workspace.update_in(cx, |workspace, window, cx| {
14371 let title = locations
14372 .first()
14373 .as_ref()
14374 .map(|location| {
14375 let buffer = location.buffer.read(cx);
14376 format!(
14377 "References to `{}`",
14378 buffer
14379 .text_for_range(location.range.clone())
14380 .collect::<String>()
14381 )
14382 })
14383 .unwrap();
14384 Self::open_locations_in_multibuffer(
14385 workspace,
14386 locations,
14387 title,
14388 false,
14389 MultibufferSelectionMode::First,
14390 window,
14391 cx,
14392 );
14393 Navigated::Yes
14394 })
14395 }))
14396 }
14397
14398 /// Opens a multibuffer with the given project locations in it
14399 pub fn open_locations_in_multibuffer(
14400 workspace: &mut Workspace,
14401 mut locations: Vec<Location>,
14402 title: String,
14403 split: bool,
14404 multibuffer_selection_mode: MultibufferSelectionMode,
14405 window: &mut Window,
14406 cx: &mut Context<Workspace>,
14407 ) {
14408 // If there are multiple definitions, open them in a multibuffer
14409 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14410 let mut locations = locations.into_iter().peekable();
14411 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14412 let capability = workspace.project().read(cx).capability();
14413
14414 let excerpt_buffer = cx.new(|cx| {
14415 let mut multibuffer = MultiBuffer::new(capability);
14416 while let Some(location) = locations.next() {
14417 let buffer = location.buffer.read(cx);
14418 let mut ranges_for_buffer = Vec::new();
14419 let range = location.range.to_point(buffer);
14420 ranges_for_buffer.push(range.clone());
14421
14422 while let Some(next_location) = locations.peek() {
14423 if next_location.buffer == location.buffer {
14424 ranges_for_buffer.push(next_location.range.to_point(buffer));
14425 locations.next();
14426 } else {
14427 break;
14428 }
14429 }
14430
14431 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14432 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14433 PathKey::for_buffer(&location.buffer, cx),
14434 location.buffer.clone(),
14435 ranges_for_buffer,
14436 DEFAULT_MULTIBUFFER_CONTEXT,
14437 cx,
14438 );
14439 ranges.extend(new_ranges)
14440 }
14441
14442 multibuffer.with_title(title)
14443 });
14444
14445 let editor = cx.new(|cx| {
14446 Editor::for_multibuffer(
14447 excerpt_buffer,
14448 Some(workspace.project().clone()),
14449 window,
14450 cx,
14451 )
14452 });
14453 editor.update(cx, |editor, cx| {
14454 match multibuffer_selection_mode {
14455 MultibufferSelectionMode::First => {
14456 if let Some(first_range) = ranges.first() {
14457 editor.change_selections(None, window, cx, |selections| {
14458 selections.clear_disjoint();
14459 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14460 });
14461 }
14462 editor.highlight_background::<Self>(
14463 &ranges,
14464 |theme| theme.editor_highlighted_line_background,
14465 cx,
14466 );
14467 }
14468 MultibufferSelectionMode::All => {
14469 editor.change_selections(None, window, cx, |selections| {
14470 selections.clear_disjoint();
14471 selections.select_anchor_ranges(ranges);
14472 });
14473 }
14474 }
14475 editor.register_buffers_with_language_servers(cx);
14476 });
14477
14478 let item = Box::new(editor);
14479 let item_id = item.item_id();
14480
14481 if split {
14482 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14483 } else {
14484 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14485 let (preview_item_id, preview_item_idx) =
14486 workspace.active_pane().update(cx, |pane, _| {
14487 (pane.preview_item_id(), pane.preview_item_idx())
14488 });
14489
14490 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14491
14492 if let Some(preview_item_id) = preview_item_id {
14493 workspace.active_pane().update(cx, |pane, cx| {
14494 pane.remove_item(preview_item_id, false, false, window, cx);
14495 });
14496 }
14497 } else {
14498 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14499 }
14500 }
14501 workspace.active_pane().update(cx, |pane, cx| {
14502 pane.set_preview_item_id(Some(item_id), cx);
14503 });
14504 }
14505
14506 pub fn rename(
14507 &mut self,
14508 _: &Rename,
14509 window: &mut Window,
14510 cx: &mut Context<Self>,
14511 ) -> Option<Task<Result<()>>> {
14512 use language::ToOffset as _;
14513
14514 let provider = self.semantics_provider.clone()?;
14515 let selection = self.selections.newest_anchor().clone();
14516 let (cursor_buffer, cursor_buffer_position) = self
14517 .buffer
14518 .read(cx)
14519 .text_anchor_for_position(selection.head(), cx)?;
14520 let (tail_buffer, cursor_buffer_position_end) = self
14521 .buffer
14522 .read(cx)
14523 .text_anchor_for_position(selection.tail(), cx)?;
14524 if tail_buffer != cursor_buffer {
14525 return None;
14526 }
14527
14528 let snapshot = cursor_buffer.read(cx).snapshot();
14529 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14530 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14531 let prepare_rename = provider
14532 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14533 .unwrap_or_else(|| Task::ready(Ok(None)));
14534 drop(snapshot);
14535
14536 Some(cx.spawn_in(window, async move |this, cx| {
14537 let rename_range = if let Some(range) = prepare_rename.await? {
14538 Some(range)
14539 } else {
14540 this.update(cx, |this, cx| {
14541 let buffer = this.buffer.read(cx).snapshot(cx);
14542 let mut buffer_highlights = this
14543 .document_highlights_for_position(selection.head(), &buffer)
14544 .filter(|highlight| {
14545 highlight.start.excerpt_id == selection.head().excerpt_id
14546 && highlight.end.excerpt_id == selection.head().excerpt_id
14547 });
14548 buffer_highlights
14549 .next()
14550 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14551 })?
14552 };
14553 if let Some(rename_range) = rename_range {
14554 this.update_in(cx, |this, window, cx| {
14555 let snapshot = cursor_buffer.read(cx).snapshot();
14556 let rename_buffer_range = rename_range.to_offset(&snapshot);
14557 let cursor_offset_in_rename_range =
14558 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14559 let cursor_offset_in_rename_range_end =
14560 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14561
14562 this.take_rename(false, window, cx);
14563 let buffer = this.buffer.read(cx).read(cx);
14564 let cursor_offset = selection.head().to_offset(&buffer);
14565 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14566 let rename_end = rename_start + rename_buffer_range.len();
14567 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14568 let mut old_highlight_id = None;
14569 let old_name: Arc<str> = buffer
14570 .chunks(rename_start..rename_end, true)
14571 .map(|chunk| {
14572 if old_highlight_id.is_none() {
14573 old_highlight_id = chunk.syntax_highlight_id;
14574 }
14575 chunk.text
14576 })
14577 .collect::<String>()
14578 .into();
14579
14580 drop(buffer);
14581
14582 // Position the selection in the rename editor so that it matches the current selection.
14583 this.show_local_selections = false;
14584 let rename_editor = cx.new(|cx| {
14585 let mut editor = Editor::single_line(window, cx);
14586 editor.buffer.update(cx, |buffer, cx| {
14587 buffer.edit([(0..0, old_name.clone())], None, cx)
14588 });
14589 let rename_selection_range = match cursor_offset_in_rename_range
14590 .cmp(&cursor_offset_in_rename_range_end)
14591 {
14592 Ordering::Equal => {
14593 editor.select_all(&SelectAll, window, cx);
14594 return editor;
14595 }
14596 Ordering::Less => {
14597 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14598 }
14599 Ordering::Greater => {
14600 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14601 }
14602 };
14603 if rename_selection_range.end > old_name.len() {
14604 editor.select_all(&SelectAll, window, cx);
14605 } else {
14606 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14607 s.select_ranges([rename_selection_range]);
14608 });
14609 }
14610 editor
14611 });
14612 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14613 if e == &EditorEvent::Focused {
14614 cx.emit(EditorEvent::FocusedIn)
14615 }
14616 })
14617 .detach();
14618
14619 let write_highlights =
14620 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14621 let read_highlights =
14622 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14623 let ranges = write_highlights
14624 .iter()
14625 .flat_map(|(_, ranges)| ranges.iter())
14626 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14627 .cloned()
14628 .collect();
14629
14630 this.highlight_text::<Rename>(
14631 ranges,
14632 HighlightStyle {
14633 fade_out: Some(0.6),
14634 ..Default::default()
14635 },
14636 cx,
14637 );
14638 let rename_focus_handle = rename_editor.focus_handle(cx);
14639 window.focus(&rename_focus_handle);
14640 let block_id = this.insert_blocks(
14641 [BlockProperties {
14642 style: BlockStyle::Flex,
14643 placement: BlockPlacement::Below(range.start),
14644 height: Some(1),
14645 render: Arc::new({
14646 let rename_editor = rename_editor.clone();
14647 move |cx: &mut BlockContext| {
14648 let mut text_style = cx.editor_style.text.clone();
14649 if let Some(highlight_style) = old_highlight_id
14650 .and_then(|h| h.style(&cx.editor_style.syntax))
14651 {
14652 text_style = text_style.highlight(highlight_style);
14653 }
14654 div()
14655 .block_mouse_down()
14656 .pl(cx.anchor_x)
14657 .child(EditorElement::new(
14658 &rename_editor,
14659 EditorStyle {
14660 background: cx.theme().system().transparent,
14661 local_player: cx.editor_style.local_player,
14662 text: text_style,
14663 scrollbar_width: cx.editor_style.scrollbar_width,
14664 syntax: cx.editor_style.syntax.clone(),
14665 status: cx.editor_style.status.clone(),
14666 inlay_hints_style: HighlightStyle {
14667 font_weight: Some(FontWeight::BOLD),
14668 ..make_inlay_hints_style(cx.app)
14669 },
14670 inline_completion_styles: make_suggestion_styles(
14671 cx.app,
14672 ),
14673 ..EditorStyle::default()
14674 },
14675 ))
14676 .into_any_element()
14677 }
14678 }),
14679 priority: 0,
14680 render_in_minimap: true,
14681 }],
14682 Some(Autoscroll::fit()),
14683 cx,
14684 )[0];
14685 this.pending_rename = Some(RenameState {
14686 range,
14687 old_name,
14688 editor: rename_editor,
14689 block_id,
14690 });
14691 })?;
14692 }
14693
14694 Ok(())
14695 }))
14696 }
14697
14698 pub fn confirm_rename(
14699 &mut self,
14700 _: &ConfirmRename,
14701 window: &mut Window,
14702 cx: &mut Context<Self>,
14703 ) -> Option<Task<Result<()>>> {
14704 let rename = self.take_rename(false, window, cx)?;
14705 let workspace = self.workspace()?.downgrade();
14706 let (buffer, start) = self
14707 .buffer
14708 .read(cx)
14709 .text_anchor_for_position(rename.range.start, cx)?;
14710 let (end_buffer, _) = self
14711 .buffer
14712 .read(cx)
14713 .text_anchor_for_position(rename.range.end, cx)?;
14714 if buffer != end_buffer {
14715 return None;
14716 }
14717
14718 let old_name = rename.old_name;
14719 let new_name = rename.editor.read(cx).text(cx);
14720
14721 let rename = self.semantics_provider.as_ref()?.perform_rename(
14722 &buffer,
14723 start,
14724 new_name.clone(),
14725 cx,
14726 )?;
14727
14728 Some(cx.spawn_in(window, async move |editor, cx| {
14729 let project_transaction = rename.await?;
14730 Self::open_project_transaction(
14731 &editor,
14732 workspace,
14733 project_transaction,
14734 format!("Rename: {} → {}", old_name, new_name),
14735 cx,
14736 )
14737 .await?;
14738
14739 editor.update(cx, |editor, cx| {
14740 editor.refresh_document_highlights(cx);
14741 })?;
14742 Ok(())
14743 }))
14744 }
14745
14746 fn take_rename(
14747 &mut self,
14748 moving_cursor: bool,
14749 window: &mut Window,
14750 cx: &mut Context<Self>,
14751 ) -> Option<RenameState> {
14752 let rename = self.pending_rename.take()?;
14753 if rename.editor.focus_handle(cx).is_focused(window) {
14754 window.focus(&self.focus_handle);
14755 }
14756
14757 self.remove_blocks(
14758 [rename.block_id].into_iter().collect(),
14759 Some(Autoscroll::fit()),
14760 cx,
14761 );
14762 self.clear_highlights::<Rename>(cx);
14763 self.show_local_selections = true;
14764
14765 if moving_cursor {
14766 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14767 editor.selections.newest::<usize>(cx).head()
14768 });
14769
14770 // Update the selection to match the position of the selection inside
14771 // the rename editor.
14772 let snapshot = self.buffer.read(cx).read(cx);
14773 let rename_range = rename.range.to_offset(&snapshot);
14774 let cursor_in_editor = snapshot
14775 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14776 .min(rename_range.end);
14777 drop(snapshot);
14778
14779 self.change_selections(None, window, cx, |s| {
14780 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14781 });
14782 } else {
14783 self.refresh_document_highlights(cx);
14784 }
14785
14786 Some(rename)
14787 }
14788
14789 pub fn pending_rename(&self) -> Option<&RenameState> {
14790 self.pending_rename.as_ref()
14791 }
14792
14793 fn format(
14794 &mut self,
14795 _: &Format,
14796 window: &mut Window,
14797 cx: &mut Context<Self>,
14798 ) -> Option<Task<Result<()>>> {
14799 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14800
14801 let project = match &self.project {
14802 Some(project) => project.clone(),
14803 None => return None,
14804 };
14805
14806 Some(self.perform_format(
14807 project,
14808 FormatTrigger::Manual,
14809 FormatTarget::Buffers,
14810 window,
14811 cx,
14812 ))
14813 }
14814
14815 fn format_selections(
14816 &mut self,
14817 _: &FormatSelections,
14818 window: &mut Window,
14819 cx: &mut Context<Self>,
14820 ) -> Option<Task<Result<()>>> {
14821 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14822
14823 let project = match &self.project {
14824 Some(project) => project.clone(),
14825 None => return None,
14826 };
14827
14828 let ranges = self
14829 .selections
14830 .all_adjusted(cx)
14831 .into_iter()
14832 .map(|selection| selection.range())
14833 .collect_vec();
14834
14835 Some(self.perform_format(
14836 project,
14837 FormatTrigger::Manual,
14838 FormatTarget::Ranges(ranges),
14839 window,
14840 cx,
14841 ))
14842 }
14843
14844 fn perform_format(
14845 &mut self,
14846 project: Entity<Project>,
14847 trigger: FormatTrigger,
14848 target: FormatTarget,
14849 window: &mut Window,
14850 cx: &mut Context<Self>,
14851 ) -> Task<Result<()>> {
14852 let buffer = self.buffer.clone();
14853 let (buffers, target) = match target {
14854 FormatTarget::Buffers => {
14855 let mut buffers = buffer.read(cx).all_buffers();
14856 if trigger == FormatTrigger::Save {
14857 buffers.retain(|buffer| buffer.read(cx).is_dirty());
14858 }
14859 (buffers, LspFormatTarget::Buffers)
14860 }
14861 FormatTarget::Ranges(selection_ranges) => {
14862 let multi_buffer = buffer.read(cx);
14863 let snapshot = multi_buffer.read(cx);
14864 let mut buffers = HashSet::default();
14865 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
14866 BTreeMap::new();
14867 for selection_range in selection_ranges {
14868 for (buffer, buffer_range, _) in
14869 snapshot.range_to_buffer_ranges(selection_range)
14870 {
14871 let buffer_id = buffer.remote_id();
14872 let start = buffer.anchor_before(buffer_range.start);
14873 let end = buffer.anchor_after(buffer_range.end);
14874 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
14875 buffer_id_to_ranges
14876 .entry(buffer_id)
14877 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
14878 .or_insert_with(|| vec![start..end]);
14879 }
14880 }
14881 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
14882 }
14883 };
14884
14885 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
14886 let selections_prev = transaction_id_prev
14887 .and_then(|transaction_id_prev| {
14888 // default to selections as they were after the last edit, if we have them,
14889 // instead of how they are now.
14890 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
14891 // will take you back to where you made the last edit, instead of staying where you scrolled
14892 self.selection_history
14893 .transaction(transaction_id_prev)
14894 .map(|t| t.0.clone())
14895 })
14896 .unwrap_or_else(|| {
14897 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
14898 self.selections.disjoint_anchors()
14899 });
14900
14901 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
14902 let format = project.update(cx, |project, cx| {
14903 project.format(buffers, target, true, trigger, cx)
14904 });
14905
14906 cx.spawn_in(window, async move |editor, cx| {
14907 let transaction = futures::select_biased! {
14908 transaction = format.log_err().fuse() => transaction,
14909 () = timeout => {
14910 log::warn!("timed out waiting for formatting");
14911 None
14912 }
14913 };
14914
14915 buffer
14916 .update(cx, |buffer, cx| {
14917 if let Some(transaction) = transaction {
14918 if !buffer.is_singleton() {
14919 buffer.push_transaction(&transaction.0, cx);
14920 }
14921 }
14922 cx.notify();
14923 })
14924 .ok();
14925
14926 if let Some(transaction_id_now) =
14927 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
14928 {
14929 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
14930 if has_new_transaction {
14931 _ = editor.update(cx, |editor, _| {
14932 editor
14933 .selection_history
14934 .insert_transaction(transaction_id_now, selections_prev);
14935 });
14936 }
14937 }
14938
14939 Ok(())
14940 })
14941 }
14942
14943 fn organize_imports(
14944 &mut self,
14945 _: &OrganizeImports,
14946 window: &mut Window,
14947 cx: &mut Context<Self>,
14948 ) -> Option<Task<Result<()>>> {
14949 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14950 let project = match &self.project {
14951 Some(project) => project.clone(),
14952 None => return None,
14953 };
14954 Some(self.perform_code_action_kind(
14955 project,
14956 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
14957 window,
14958 cx,
14959 ))
14960 }
14961
14962 fn perform_code_action_kind(
14963 &mut self,
14964 project: Entity<Project>,
14965 kind: CodeActionKind,
14966 window: &mut Window,
14967 cx: &mut Context<Self>,
14968 ) -> Task<Result<()>> {
14969 let buffer = self.buffer.clone();
14970 let buffers = buffer.read(cx).all_buffers();
14971 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
14972 let apply_action = project.update(cx, |project, cx| {
14973 project.apply_code_action_kind(buffers, kind, true, cx)
14974 });
14975 cx.spawn_in(window, async move |_, cx| {
14976 let transaction = futures::select_biased! {
14977 () = timeout => {
14978 log::warn!("timed out waiting for executing code action");
14979 None
14980 }
14981 transaction = apply_action.log_err().fuse() => transaction,
14982 };
14983 buffer
14984 .update(cx, |buffer, cx| {
14985 // check if we need this
14986 if let Some(transaction) = transaction {
14987 if !buffer.is_singleton() {
14988 buffer.push_transaction(&transaction.0, cx);
14989 }
14990 }
14991 cx.notify();
14992 })
14993 .ok();
14994 Ok(())
14995 })
14996 }
14997
14998 fn restart_language_server(
14999 &mut self,
15000 _: &RestartLanguageServer,
15001 _: &mut Window,
15002 cx: &mut Context<Self>,
15003 ) {
15004 if let Some(project) = self.project.clone() {
15005 self.buffer.update(cx, |multi_buffer, cx| {
15006 project.update(cx, |project, cx| {
15007 project.restart_language_servers_for_buffers(
15008 multi_buffer.all_buffers().into_iter().collect(),
15009 cx,
15010 );
15011 });
15012 })
15013 }
15014 }
15015
15016 fn stop_language_server(
15017 &mut self,
15018 _: &StopLanguageServer,
15019 _: &mut Window,
15020 cx: &mut Context<Self>,
15021 ) {
15022 if let Some(project) = self.project.clone() {
15023 self.buffer.update(cx, |multi_buffer, cx| {
15024 project.update(cx, |project, cx| {
15025 project.stop_language_servers_for_buffers(
15026 multi_buffer.all_buffers().into_iter().collect(),
15027 cx,
15028 );
15029 cx.emit(project::Event::RefreshInlayHints);
15030 });
15031 });
15032 }
15033 }
15034
15035 fn cancel_language_server_work(
15036 workspace: &mut Workspace,
15037 _: &actions::CancelLanguageServerWork,
15038 _: &mut Window,
15039 cx: &mut Context<Workspace>,
15040 ) {
15041 let project = workspace.project();
15042 let buffers = workspace
15043 .active_item(cx)
15044 .and_then(|item| item.act_as::<Editor>(cx))
15045 .map_or(HashSet::default(), |editor| {
15046 editor.read(cx).buffer.read(cx).all_buffers()
15047 });
15048 project.update(cx, |project, cx| {
15049 project.cancel_language_server_work_for_buffers(buffers, cx);
15050 });
15051 }
15052
15053 fn show_character_palette(
15054 &mut self,
15055 _: &ShowCharacterPalette,
15056 window: &mut Window,
15057 _: &mut Context<Self>,
15058 ) {
15059 window.show_character_palette();
15060 }
15061
15062 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15063 if self.mode.is_minimap() {
15064 return;
15065 }
15066
15067 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15068 let buffer = self.buffer.read(cx).snapshot(cx);
15069 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15070 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15071 let is_valid = buffer
15072 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15073 .any(|entry| {
15074 entry.diagnostic.is_primary
15075 && !entry.range.is_empty()
15076 && entry.range.start == primary_range_start
15077 && entry.diagnostic.message == active_diagnostics.active_message
15078 });
15079
15080 if !is_valid {
15081 self.dismiss_diagnostics(cx);
15082 }
15083 }
15084 }
15085
15086 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15087 match &self.active_diagnostics {
15088 ActiveDiagnostic::Group(group) => Some(group),
15089 _ => None,
15090 }
15091 }
15092
15093 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15094 self.dismiss_diagnostics(cx);
15095 self.active_diagnostics = ActiveDiagnostic::All;
15096 }
15097
15098 fn activate_diagnostics(
15099 &mut self,
15100 buffer_id: BufferId,
15101 diagnostic: DiagnosticEntry<usize>,
15102 window: &mut Window,
15103 cx: &mut Context<Self>,
15104 ) {
15105 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15106 return;
15107 }
15108 self.dismiss_diagnostics(cx);
15109 let snapshot = self.snapshot(window, cx);
15110 let buffer = self.buffer.read(cx).snapshot(cx);
15111 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15112 return;
15113 };
15114
15115 let diagnostic_group = buffer
15116 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15117 .collect::<Vec<_>>();
15118
15119 let blocks =
15120 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15121
15122 let blocks = self.display_map.update(cx, |display_map, cx| {
15123 display_map.insert_blocks(blocks, cx).into_iter().collect()
15124 });
15125 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15126 active_range: buffer.anchor_before(diagnostic.range.start)
15127 ..buffer.anchor_after(diagnostic.range.end),
15128 active_message: diagnostic.diagnostic.message.clone(),
15129 group_id: diagnostic.diagnostic.group_id,
15130 blocks,
15131 });
15132 cx.notify();
15133 }
15134
15135 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15136 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15137 return;
15138 };
15139
15140 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15141 if let ActiveDiagnostic::Group(group) = prev {
15142 self.display_map.update(cx, |display_map, cx| {
15143 display_map.remove_blocks(group.blocks, cx);
15144 });
15145 cx.notify();
15146 }
15147 }
15148
15149 /// Disable inline diagnostics rendering for this editor.
15150 pub fn disable_inline_diagnostics(&mut self) {
15151 self.inline_diagnostics_enabled = false;
15152 self.inline_diagnostics_update = Task::ready(());
15153 self.inline_diagnostics.clear();
15154 }
15155
15156 pub fn diagnostics_enabled(&self) -> bool {
15157 self.mode.is_full()
15158 }
15159
15160 pub fn inline_diagnostics_enabled(&self) -> bool {
15161 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15162 }
15163
15164 pub fn show_inline_diagnostics(&self) -> bool {
15165 self.show_inline_diagnostics
15166 }
15167
15168 pub fn toggle_inline_diagnostics(
15169 &mut self,
15170 _: &ToggleInlineDiagnostics,
15171 window: &mut Window,
15172 cx: &mut Context<Editor>,
15173 ) {
15174 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15175 self.refresh_inline_diagnostics(false, window, cx);
15176 }
15177
15178 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15179 self.diagnostics_max_severity = severity;
15180 self.display_map.update(cx, |display_map, _| {
15181 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15182 });
15183 }
15184
15185 pub fn toggle_diagnostics(
15186 &mut self,
15187 _: &ToggleDiagnostics,
15188 window: &mut Window,
15189 cx: &mut Context<Editor>,
15190 ) {
15191 if !self.diagnostics_enabled() {
15192 return;
15193 }
15194
15195 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15196 EditorSettings::get_global(cx)
15197 .diagnostics_max_severity
15198 .filter(|severity| severity != &DiagnosticSeverity::Off)
15199 .unwrap_or(DiagnosticSeverity::Hint)
15200 } else {
15201 DiagnosticSeverity::Off
15202 };
15203 self.set_max_diagnostics_severity(new_severity, cx);
15204 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15205 self.active_diagnostics = ActiveDiagnostic::None;
15206 self.inline_diagnostics_update = Task::ready(());
15207 self.inline_diagnostics.clear();
15208 } else {
15209 self.refresh_inline_diagnostics(false, window, cx);
15210 }
15211
15212 cx.notify();
15213 }
15214
15215 pub fn toggle_minimap(
15216 &mut self,
15217 _: &ToggleMinimap,
15218 window: &mut Window,
15219 cx: &mut Context<Editor>,
15220 ) {
15221 if self.supports_minimap(cx) {
15222 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15223 }
15224 }
15225
15226 fn refresh_inline_diagnostics(
15227 &mut self,
15228 debounce: bool,
15229 window: &mut Window,
15230 cx: &mut Context<Self>,
15231 ) {
15232 let max_severity = ProjectSettings::get_global(cx)
15233 .diagnostics
15234 .inline
15235 .max_severity
15236 .unwrap_or(self.diagnostics_max_severity);
15237
15238 if self.mode.is_minimap()
15239 || !self.inline_diagnostics_enabled()
15240 || !self.show_inline_diagnostics
15241 || max_severity == DiagnosticSeverity::Off
15242 {
15243 self.inline_diagnostics_update = Task::ready(());
15244 self.inline_diagnostics.clear();
15245 return;
15246 }
15247
15248 let debounce_ms = ProjectSettings::get_global(cx)
15249 .diagnostics
15250 .inline
15251 .update_debounce_ms;
15252 let debounce = if debounce && debounce_ms > 0 {
15253 Some(Duration::from_millis(debounce_ms))
15254 } else {
15255 None
15256 };
15257 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15258 let editor = editor.upgrade().unwrap();
15259
15260 if let Some(debounce) = debounce {
15261 cx.background_executor().timer(debounce).await;
15262 }
15263 let Some(snapshot) = editor
15264 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15265 .ok()
15266 else {
15267 return;
15268 };
15269
15270 let new_inline_diagnostics = cx
15271 .background_spawn(async move {
15272 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15273 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15274 let message = diagnostic_entry
15275 .diagnostic
15276 .message
15277 .split_once('\n')
15278 .map(|(line, _)| line)
15279 .map(SharedString::new)
15280 .unwrap_or_else(|| {
15281 SharedString::from(diagnostic_entry.diagnostic.message)
15282 });
15283 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15284 let (Ok(i) | Err(i)) = inline_diagnostics
15285 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15286 inline_diagnostics.insert(
15287 i,
15288 (
15289 start_anchor,
15290 InlineDiagnostic {
15291 message,
15292 group_id: diagnostic_entry.diagnostic.group_id,
15293 start: diagnostic_entry.range.start.to_point(&snapshot),
15294 is_primary: diagnostic_entry.diagnostic.is_primary,
15295 severity: diagnostic_entry.diagnostic.severity,
15296 },
15297 ),
15298 );
15299 }
15300 inline_diagnostics
15301 })
15302 .await;
15303
15304 editor
15305 .update(cx, |editor, cx| {
15306 editor.inline_diagnostics = new_inline_diagnostics;
15307 cx.notify();
15308 })
15309 .ok();
15310 });
15311 }
15312
15313 pub fn set_selections_from_remote(
15314 &mut self,
15315 selections: Vec<Selection<Anchor>>,
15316 pending_selection: Option<Selection<Anchor>>,
15317 window: &mut Window,
15318 cx: &mut Context<Self>,
15319 ) {
15320 let old_cursor_position = self.selections.newest_anchor().head();
15321 self.selections.change_with(cx, |s| {
15322 s.select_anchors(selections);
15323 if let Some(pending_selection) = pending_selection {
15324 s.set_pending(pending_selection, SelectMode::Character);
15325 } else {
15326 s.clear_pending();
15327 }
15328 });
15329 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15330 }
15331
15332 fn push_to_selection_history(&mut self) {
15333 self.selection_history.push(SelectionHistoryEntry {
15334 selections: self.selections.disjoint_anchors(),
15335 select_next_state: self.select_next_state.clone(),
15336 select_prev_state: self.select_prev_state.clone(),
15337 add_selections_state: self.add_selections_state.clone(),
15338 });
15339 }
15340
15341 pub fn transact(
15342 &mut self,
15343 window: &mut Window,
15344 cx: &mut Context<Self>,
15345 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15346 ) -> Option<TransactionId> {
15347 self.start_transaction_at(Instant::now(), window, cx);
15348 update(self, window, cx);
15349 self.end_transaction_at(Instant::now(), cx)
15350 }
15351
15352 pub fn start_transaction_at(
15353 &mut self,
15354 now: Instant,
15355 window: &mut Window,
15356 cx: &mut Context<Self>,
15357 ) {
15358 self.end_selection(window, cx);
15359 if let Some(tx_id) = self
15360 .buffer
15361 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15362 {
15363 self.selection_history
15364 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15365 cx.emit(EditorEvent::TransactionBegun {
15366 transaction_id: tx_id,
15367 })
15368 }
15369 }
15370
15371 pub fn end_transaction_at(
15372 &mut self,
15373 now: Instant,
15374 cx: &mut Context<Self>,
15375 ) -> Option<TransactionId> {
15376 if let Some(transaction_id) = self
15377 .buffer
15378 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15379 {
15380 if let Some((_, end_selections)) =
15381 self.selection_history.transaction_mut(transaction_id)
15382 {
15383 *end_selections = Some(self.selections.disjoint_anchors());
15384 } else {
15385 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15386 }
15387
15388 cx.emit(EditorEvent::Edited { transaction_id });
15389 Some(transaction_id)
15390 } else {
15391 None
15392 }
15393 }
15394
15395 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15396 if self.selection_mark_mode {
15397 self.change_selections(None, window, cx, |s| {
15398 s.move_with(|_, sel| {
15399 sel.collapse_to(sel.head(), SelectionGoal::None);
15400 });
15401 })
15402 }
15403 self.selection_mark_mode = true;
15404 cx.notify();
15405 }
15406
15407 pub fn swap_selection_ends(
15408 &mut self,
15409 _: &actions::SwapSelectionEnds,
15410 window: &mut Window,
15411 cx: &mut Context<Self>,
15412 ) {
15413 self.change_selections(None, window, cx, |s| {
15414 s.move_with(|_, sel| {
15415 if sel.start != sel.end {
15416 sel.reversed = !sel.reversed
15417 }
15418 });
15419 });
15420 self.request_autoscroll(Autoscroll::newest(), cx);
15421 cx.notify();
15422 }
15423
15424 pub fn toggle_fold(
15425 &mut self,
15426 _: &actions::ToggleFold,
15427 window: &mut Window,
15428 cx: &mut Context<Self>,
15429 ) {
15430 if self.is_singleton(cx) {
15431 let selection = self.selections.newest::<Point>(cx);
15432
15433 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15434 let range = if selection.is_empty() {
15435 let point = selection.head().to_display_point(&display_map);
15436 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15437 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15438 .to_point(&display_map);
15439 start..end
15440 } else {
15441 selection.range()
15442 };
15443 if display_map.folds_in_range(range).next().is_some() {
15444 self.unfold_lines(&Default::default(), window, cx)
15445 } else {
15446 self.fold(&Default::default(), window, cx)
15447 }
15448 } else {
15449 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15450 let buffer_ids: HashSet<_> = self
15451 .selections
15452 .disjoint_anchor_ranges()
15453 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15454 .collect();
15455
15456 let should_unfold = buffer_ids
15457 .iter()
15458 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15459
15460 for buffer_id in buffer_ids {
15461 if should_unfold {
15462 self.unfold_buffer(buffer_id, cx);
15463 } else {
15464 self.fold_buffer(buffer_id, cx);
15465 }
15466 }
15467 }
15468 }
15469
15470 pub fn toggle_fold_recursive(
15471 &mut self,
15472 _: &actions::ToggleFoldRecursive,
15473 window: &mut Window,
15474 cx: &mut Context<Self>,
15475 ) {
15476 let selection = self.selections.newest::<Point>(cx);
15477
15478 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15479 let range = if selection.is_empty() {
15480 let point = selection.head().to_display_point(&display_map);
15481 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15482 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15483 .to_point(&display_map);
15484 start..end
15485 } else {
15486 selection.range()
15487 };
15488 if display_map.folds_in_range(range).next().is_some() {
15489 self.unfold_recursive(&Default::default(), window, cx)
15490 } else {
15491 self.fold_recursive(&Default::default(), window, cx)
15492 }
15493 }
15494
15495 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15496 if self.is_singleton(cx) {
15497 let mut to_fold = Vec::new();
15498 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15499 let selections = self.selections.all_adjusted(cx);
15500
15501 for selection in selections {
15502 let range = selection.range().sorted();
15503 let buffer_start_row = range.start.row;
15504
15505 if range.start.row != range.end.row {
15506 let mut found = false;
15507 let mut row = range.start.row;
15508 while row <= range.end.row {
15509 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15510 {
15511 found = true;
15512 row = crease.range().end.row + 1;
15513 to_fold.push(crease);
15514 } else {
15515 row += 1
15516 }
15517 }
15518 if found {
15519 continue;
15520 }
15521 }
15522
15523 for row in (0..=range.start.row).rev() {
15524 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15525 if crease.range().end.row >= buffer_start_row {
15526 to_fold.push(crease);
15527 if row <= range.start.row {
15528 break;
15529 }
15530 }
15531 }
15532 }
15533 }
15534
15535 self.fold_creases(to_fold, true, window, cx);
15536 } else {
15537 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15538 let buffer_ids = self
15539 .selections
15540 .disjoint_anchor_ranges()
15541 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15542 .collect::<HashSet<_>>();
15543 for buffer_id in buffer_ids {
15544 self.fold_buffer(buffer_id, cx);
15545 }
15546 }
15547 }
15548
15549 fn fold_at_level(
15550 &mut self,
15551 fold_at: &FoldAtLevel,
15552 window: &mut Window,
15553 cx: &mut Context<Self>,
15554 ) {
15555 if !self.buffer.read(cx).is_singleton() {
15556 return;
15557 }
15558
15559 let fold_at_level = fold_at.0;
15560 let snapshot = self.buffer.read(cx).snapshot(cx);
15561 let mut to_fold = Vec::new();
15562 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15563
15564 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15565 while start_row < end_row {
15566 match self
15567 .snapshot(window, cx)
15568 .crease_for_buffer_row(MultiBufferRow(start_row))
15569 {
15570 Some(crease) => {
15571 let nested_start_row = crease.range().start.row + 1;
15572 let nested_end_row = crease.range().end.row;
15573
15574 if current_level < fold_at_level {
15575 stack.push((nested_start_row, nested_end_row, current_level + 1));
15576 } else if current_level == fold_at_level {
15577 to_fold.push(crease);
15578 }
15579
15580 start_row = nested_end_row + 1;
15581 }
15582 None => start_row += 1,
15583 }
15584 }
15585 }
15586
15587 self.fold_creases(to_fold, true, window, cx);
15588 }
15589
15590 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15591 if self.buffer.read(cx).is_singleton() {
15592 let mut fold_ranges = Vec::new();
15593 let snapshot = self.buffer.read(cx).snapshot(cx);
15594
15595 for row in 0..snapshot.max_row().0 {
15596 if let Some(foldable_range) = self
15597 .snapshot(window, cx)
15598 .crease_for_buffer_row(MultiBufferRow(row))
15599 {
15600 fold_ranges.push(foldable_range);
15601 }
15602 }
15603
15604 self.fold_creases(fold_ranges, true, window, cx);
15605 } else {
15606 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15607 editor
15608 .update_in(cx, |editor, _, cx| {
15609 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15610 editor.fold_buffer(buffer_id, cx);
15611 }
15612 })
15613 .ok();
15614 });
15615 }
15616 }
15617
15618 pub fn fold_function_bodies(
15619 &mut self,
15620 _: &actions::FoldFunctionBodies,
15621 window: &mut Window,
15622 cx: &mut Context<Self>,
15623 ) {
15624 let snapshot = self.buffer.read(cx).snapshot(cx);
15625
15626 let ranges = snapshot
15627 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15628 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15629 .collect::<Vec<_>>();
15630
15631 let creases = ranges
15632 .into_iter()
15633 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15634 .collect();
15635
15636 self.fold_creases(creases, true, window, cx);
15637 }
15638
15639 pub fn fold_recursive(
15640 &mut self,
15641 _: &actions::FoldRecursive,
15642 window: &mut Window,
15643 cx: &mut Context<Self>,
15644 ) {
15645 let mut to_fold = Vec::new();
15646 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15647 let selections = self.selections.all_adjusted(cx);
15648
15649 for selection in selections {
15650 let range = selection.range().sorted();
15651 let buffer_start_row = range.start.row;
15652
15653 if range.start.row != range.end.row {
15654 let mut found = false;
15655 for row in range.start.row..=range.end.row {
15656 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15657 found = true;
15658 to_fold.push(crease);
15659 }
15660 }
15661 if found {
15662 continue;
15663 }
15664 }
15665
15666 for row in (0..=range.start.row).rev() {
15667 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15668 if crease.range().end.row >= buffer_start_row {
15669 to_fold.push(crease);
15670 } else {
15671 break;
15672 }
15673 }
15674 }
15675 }
15676
15677 self.fold_creases(to_fold, true, window, cx);
15678 }
15679
15680 pub fn fold_at(
15681 &mut self,
15682 buffer_row: MultiBufferRow,
15683 window: &mut Window,
15684 cx: &mut Context<Self>,
15685 ) {
15686 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15687
15688 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15689 let autoscroll = self
15690 .selections
15691 .all::<Point>(cx)
15692 .iter()
15693 .any(|selection| crease.range().overlaps(&selection.range()));
15694
15695 self.fold_creases(vec![crease], autoscroll, window, cx);
15696 }
15697 }
15698
15699 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15700 if self.is_singleton(cx) {
15701 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15702 let buffer = &display_map.buffer_snapshot;
15703 let selections = self.selections.all::<Point>(cx);
15704 let ranges = selections
15705 .iter()
15706 .map(|s| {
15707 let range = s.display_range(&display_map).sorted();
15708 let mut start = range.start.to_point(&display_map);
15709 let mut end = range.end.to_point(&display_map);
15710 start.column = 0;
15711 end.column = buffer.line_len(MultiBufferRow(end.row));
15712 start..end
15713 })
15714 .collect::<Vec<_>>();
15715
15716 self.unfold_ranges(&ranges, true, true, cx);
15717 } else {
15718 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15719 let buffer_ids = self
15720 .selections
15721 .disjoint_anchor_ranges()
15722 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15723 .collect::<HashSet<_>>();
15724 for buffer_id in buffer_ids {
15725 self.unfold_buffer(buffer_id, cx);
15726 }
15727 }
15728 }
15729
15730 pub fn unfold_recursive(
15731 &mut self,
15732 _: &UnfoldRecursive,
15733 _window: &mut Window,
15734 cx: &mut Context<Self>,
15735 ) {
15736 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15737 let selections = self.selections.all::<Point>(cx);
15738 let ranges = selections
15739 .iter()
15740 .map(|s| {
15741 let mut range = s.display_range(&display_map).sorted();
15742 *range.start.column_mut() = 0;
15743 *range.end.column_mut() = display_map.line_len(range.end.row());
15744 let start = range.start.to_point(&display_map);
15745 let end = range.end.to_point(&display_map);
15746 start..end
15747 })
15748 .collect::<Vec<_>>();
15749
15750 self.unfold_ranges(&ranges, true, true, cx);
15751 }
15752
15753 pub fn unfold_at(
15754 &mut self,
15755 buffer_row: MultiBufferRow,
15756 _window: &mut Window,
15757 cx: &mut Context<Self>,
15758 ) {
15759 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15760
15761 let intersection_range = Point::new(buffer_row.0, 0)
15762 ..Point::new(
15763 buffer_row.0,
15764 display_map.buffer_snapshot.line_len(buffer_row),
15765 );
15766
15767 let autoscroll = self
15768 .selections
15769 .all::<Point>(cx)
15770 .iter()
15771 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15772
15773 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15774 }
15775
15776 pub fn unfold_all(
15777 &mut self,
15778 _: &actions::UnfoldAll,
15779 _window: &mut Window,
15780 cx: &mut Context<Self>,
15781 ) {
15782 if self.buffer.read(cx).is_singleton() {
15783 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15784 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15785 } else {
15786 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15787 editor
15788 .update(cx, |editor, cx| {
15789 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15790 editor.unfold_buffer(buffer_id, cx);
15791 }
15792 })
15793 .ok();
15794 });
15795 }
15796 }
15797
15798 pub fn fold_selected_ranges(
15799 &mut self,
15800 _: &FoldSelectedRanges,
15801 window: &mut Window,
15802 cx: &mut Context<Self>,
15803 ) {
15804 let selections = self.selections.all_adjusted(cx);
15805 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15806 let ranges = selections
15807 .into_iter()
15808 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
15809 .collect::<Vec<_>>();
15810 self.fold_creases(ranges, true, window, cx);
15811 }
15812
15813 pub fn fold_ranges<T: ToOffset + Clone>(
15814 &mut self,
15815 ranges: Vec<Range<T>>,
15816 auto_scroll: bool,
15817 window: &mut Window,
15818 cx: &mut Context<Self>,
15819 ) {
15820 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15821 let ranges = ranges
15822 .into_iter()
15823 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
15824 .collect::<Vec<_>>();
15825 self.fold_creases(ranges, auto_scroll, window, cx);
15826 }
15827
15828 pub fn fold_creases<T: ToOffset + Clone>(
15829 &mut self,
15830 creases: Vec<Crease<T>>,
15831 auto_scroll: bool,
15832 _window: &mut Window,
15833 cx: &mut Context<Self>,
15834 ) {
15835 if creases.is_empty() {
15836 return;
15837 }
15838
15839 let mut buffers_affected = HashSet::default();
15840 let multi_buffer = self.buffer().read(cx);
15841 for crease in &creases {
15842 if let Some((_, buffer, _)) =
15843 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
15844 {
15845 buffers_affected.insert(buffer.read(cx).remote_id());
15846 };
15847 }
15848
15849 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
15850
15851 if auto_scroll {
15852 self.request_autoscroll(Autoscroll::fit(), cx);
15853 }
15854
15855 cx.notify();
15856
15857 self.scrollbar_marker_state.dirty = true;
15858 self.folds_did_change(cx);
15859 }
15860
15861 /// Removes any folds whose ranges intersect any of the given ranges.
15862 pub fn unfold_ranges<T: ToOffset + Clone>(
15863 &mut self,
15864 ranges: &[Range<T>],
15865 inclusive: bool,
15866 auto_scroll: bool,
15867 cx: &mut Context<Self>,
15868 ) {
15869 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15870 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
15871 });
15872 self.folds_did_change(cx);
15873 }
15874
15875 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15876 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
15877 return;
15878 }
15879 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15880 self.display_map.update(cx, |display_map, cx| {
15881 display_map.fold_buffers([buffer_id], cx)
15882 });
15883 cx.emit(EditorEvent::BufferFoldToggled {
15884 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
15885 folded: true,
15886 });
15887 cx.notify();
15888 }
15889
15890 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15891 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
15892 return;
15893 }
15894 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15895 self.display_map.update(cx, |display_map, cx| {
15896 display_map.unfold_buffers([buffer_id], cx);
15897 });
15898 cx.emit(EditorEvent::BufferFoldToggled {
15899 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
15900 folded: false,
15901 });
15902 cx.notify();
15903 }
15904
15905 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
15906 self.display_map.read(cx).is_buffer_folded(buffer)
15907 }
15908
15909 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
15910 self.display_map.read(cx).folded_buffers()
15911 }
15912
15913 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15914 self.display_map.update(cx, |display_map, cx| {
15915 display_map.disable_header_for_buffer(buffer_id, cx);
15916 });
15917 cx.notify();
15918 }
15919
15920 /// Removes any folds with the given ranges.
15921 pub fn remove_folds_with_type<T: ToOffset + Clone>(
15922 &mut self,
15923 ranges: &[Range<T>],
15924 type_id: TypeId,
15925 auto_scroll: bool,
15926 cx: &mut Context<Self>,
15927 ) {
15928 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15929 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
15930 });
15931 self.folds_did_change(cx);
15932 }
15933
15934 fn remove_folds_with<T: ToOffset + Clone>(
15935 &mut self,
15936 ranges: &[Range<T>],
15937 auto_scroll: bool,
15938 cx: &mut Context<Self>,
15939 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
15940 ) {
15941 if ranges.is_empty() {
15942 return;
15943 }
15944
15945 let mut buffers_affected = HashSet::default();
15946 let multi_buffer = self.buffer().read(cx);
15947 for range in ranges {
15948 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
15949 buffers_affected.insert(buffer.read(cx).remote_id());
15950 };
15951 }
15952
15953 self.display_map.update(cx, update);
15954
15955 if auto_scroll {
15956 self.request_autoscroll(Autoscroll::fit(), cx);
15957 }
15958
15959 cx.notify();
15960 self.scrollbar_marker_state.dirty = true;
15961 self.active_indent_guides_state.dirty = true;
15962 }
15963
15964 pub fn update_fold_widths(
15965 &mut self,
15966 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
15967 cx: &mut Context<Self>,
15968 ) -> bool {
15969 self.display_map
15970 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
15971 }
15972
15973 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
15974 self.display_map.read(cx).fold_placeholder.clone()
15975 }
15976
15977 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
15978 self.buffer.update(cx, |buffer, cx| {
15979 buffer.set_all_diff_hunks_expanded(cx);
15980 });
15981 }
15982
15983 pub fn expand_all_diff_hunks(
15984 &mut self,
15985 _: &ExpandAllDiffHunks,
15986 _window: &mut Window,
15987 cx: &mut Context<Self>,
15988 ) {
15989 self.buffer.update(cx, |buffer, cx| {
15990 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
15991 });
15992 }
15993
15994 pub fn toggle_selected_diff_hunks(
15995 &mut self,
15996 _: &ToggleSelectedDiffHunks,
15997 _window: &mut Window,
15998 cx: &mut Context<Self>,
15999 ) {
16000 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16001 self.toggle_diff_hunks_in_ranges(ranges, cx);
16002 }
16003
16004 pub fn diff_hunks_in_ranges<'a>(
16005 &'a self,
16006 ranges: &'a [Range<Anchor>],
16007 buffer: &'a MultiBufferSnapshot,
16008 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16009 ranges.iter().flat_map(move |range| {
16010 let end_excerpt_id = range.end.excerpt_id;
16011 let range = range.to_point(buffer);
16012 let mut peek_end = range.end;
16013 if range.end.row < buffer.max_row().0 {
16014 peek_end = Point::new(range.end.row + 1, 0);
16015 }
16016 buffer
16017 .diff_hunks_in_range(range.start..peek_end)
16018 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16019 })
16020 }
16021
16022 pub fn has_stageable_diff_hunks_in_ranges(
16023 &self,
16024 ranges: &[Range<Anchor>],
16025 snapshot: &MultiBufferSnapshot,
16026 ) -> bool {
16027 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16028 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16029 }
16030
16031 pub fn toggle_staged_selected_diff_hunks(
16032 &mut self,
16033 _: &::git::ToggleStaged,
16034 _: &mut Window,
16035 cx: &mut Context<Self>,
16036 ) {
16037 let snapshot = self.buffer.read(cx).snapshot(cx);
16038 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16039 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16040 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16041 }
16042
16043 pub fn set_render_diff_hunk_controls(
16044 &mut self,
16045 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16046 cx: &mut Context<Self>,
16047 ) {
16048 self.render_diff_hunk_controls = render_diff_hunk_controls;
16049 cx.notify();
16050 }
16051
16052 pub fn stage_and_next(
16053 &mut self,
16054 _: &::git::StageAndNext,
16055 window: &mut Window,
16056 cx: &mut Context<Self>,
16057 ) {
16058 self.do_stage_or_unstage_and_next(true, window, cx);
16059 }
16060
16061 pub fn unstage_and_next(
16062 &mut self,
16063 _: &::git::UnstageAndNext,
16064 window: &mut Window,
16065 cx: &mut Context<Self>,
16066 ) {
16067 self.do_stage_or_unstage_and_next(false, window, cx);
16068 }
16069
16070 pub fn stage_or_unstage_diff_hunks(
16071 &mut self,
16072 stage: bool,
16073 ranges: Vec<Range<Anchor>>,
16074 cx: &mut Context<Self>,
16075 ) {
16076 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16077 cx.spawn(async move |this, cx| {
16078 task.await?;
16079 this.update(cx, |this, cx| {
16080 let snapshot = this.buffer.read(cx).snapshot(cx);
16081 let chunk_by = this
16082 .diff_hunks_in_ranges(&ranges, &snapshot)
16083 .chunk_by(|hunk| hunk.buffer_id);
16084 for (buffer_id, hunks) in &chunk_by {
16085 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16086 }
16087 })
16088 })
16089 .detach_and_log_err(cx);
16090 }
16091
16092 fn save_buffers_for_ranges_if_needed(
16093 &mut self,
16094 ranges: &[Range<Anchor>],
16095 cx: &mut Context<Editor>,
16096 ) -> Task<Result<()>> {
16097 let multibuffer = self.buffer.read(cx);
16098 let snapshot = multibuffer.read(cx);
16099 let buffer_ids: HashSet<_> = ranges
16100 .iter()
16101 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16102 .collect();
16103 drop(snapshot);
16104
16105 let mut buffers = HashSet::default();
16106 for buffer_id in buffer_ids {
16107 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16108 let buffer = buffer_entity.read(cx);
16109 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16110 {
16111 buffers.insert(buffer_entity);
16112 }
16113 }
16114 }
16115
16116 if let Some(project) = &self.project {
16117 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16118 } else {
16119 Task::ready(Ok(()))
16120 }
16121 }
16122
16123 fn do_stage_or_unstage_and_next(
16124 &mut self,
16125 stage: bool,
16126 window: &mut Window,
16127 cx: &mut Context<Self>,
16128 ) {
16129 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16130
16131 if ranges.iter().any(|range| range.start != range.end) {
16132 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16133 return;
16134 }
16135
16136 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16137 let snapshot = self.snapshot(window, cx);
16138 let position = self.selections.newest::<Point>(cx).head();
16139 let mut row = snapshot
16140 .buffer_snapshot
16141 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16142 .find(|hunk| hunk.row_range.start.0 > position.row)
16143 .map(|hunk| hunk.row_range.start);
16144
16145 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16146 // Outside of the project diff editor, wrap around to the beginning.
16147 if !all_diff_hunks_expanded {
16148 row = row.or_else(|| {
16149 snapshot
16150 .buffer_snapshot
16151 .diff_hunks_in_range(Point::zero()..position)
16152 .find(|hunk| hunk.row_range.end.0 < position.row)
16153 .map(|hunk| hunk.row_range.start)
16154 });
16155 }
16156
16157 if let Some(row) = row {
16158 let destination = Point::new(row.0, 0);
16159 let autoscroll = Autoscroll::center();
16160
16161 self.unfold_ranges(&[destination..destination], false, false, cx);
16162 self.change_selections(Some(autoscroll), window, cx, |s| {
16163 s.select_ranges([destination..destination]);
16164 });
16165 }
16166 }
16167
16168 fn do_stage_or_unstage(
16169 &self,
16170 stage: bool,
16171 buffer_id: BufferId,
16172 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16173 cx: &mut App,
16174 ) -> Option<()> {
16175 let project = self.project.as_ref()?;
16176 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16177 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16178 let buffer_snapshot = buffer.read(cx).snapshot();
16179 let file_exists = buffer_snapshot
16180 .file()
16181 .is_some_and(|file| file.disk_state().exists());
16182 diff.update(cx, |diff, cx| {
16183 diff.stage_or_unstage_hunks(
16184 stage,
16185 &hunks
16186 .map(|hunk| buffer_diff::DiffHunk {
16187 buffer_range: hunk.buffer_range,
16188 diff_base_byte_range: hunk.diff_base_byte_range,
16189 secondary_status: hunk.secondary_status,
16190 range: Point::zero()..Point::zero(), // unused
16191 })
16192 .collect::<Vec<_>>(),
16193 &buffer_snapshot,
16194 file_exists,
16195 cx,
16196 )
16197 });
16198 None
16199 }
16200
16201 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16202 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16203 self.buffer
16204 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16205 }
16206
16207 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16208 self.buffer.update(cx, |buffer, cx| {
16209 let ranges = vec![Anchor::min()..Anchor::max()];
16210 if !buffer.all_diff_hunks_expanded()
16211 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16212 {
16213 buffer.collapse_diff_hunks(ranges, cx);
16214 true
16215 } else {
16216 false
16217 }
16218 })
16219 }
16220
16221 fn toggle_diff_hunks_in_ranges(
16222 &mut self,
16223 ranges: Vec<Range<Anchor>>,
16224 cx: &mut Context<Editor>,
16225 ) {
16226 self.buffer.update(cx, |buffer, cx| {
16227 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16228 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16229 })
16230 }
16231
16232 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16233 self.buffer.update(cx, |buffer, cx| {
16234 let snapshot = buffer.snapshot(cx);
16235 let excerpt_id = range.end.excerpt_id;
16236 let point_range = range.to_point(&snapshot);
16237 let expand = !buffer.single_hunk_is_expanded(range, cx);
16238 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16239 })
16240 }
16241
16242 pub(crate) fn apply_all_diff_hunks(
16243 &mut self,
16244 _: &ApplyAllDiffHunks,
16245 window: &mut Window,
16246 cx: &mut Context<Self>,
16247 ) {
16248 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16249
16250 let buffers = self.buffer.read(cx).all_buffers();
16251 for branch_buffer in buffers {
16252 branch_buffer.update(cx, |branch_buffer, cx| {
16253 branch_buffer.merge_into_base(Vec::new(), cx);
16254 });
16255 }
16256
16257 if let Some(project) = self.project.clone() {
16258 self.save(true, project, window, cx).detach_and_log_err(cx);
16259 }
16260 }
16261
16262 pub(crate) fn apply_selected_diff_hunks(
16263 &mut self,
16264 _: &ApplyDiffHunk,
16265 window: &mut Window,
16266 cx: &mut Context<Self>,
16267 ) {
16268 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16269 let snapshot = self.snapshot(window, cx);
16270 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16271 let mut ranges_by_buffer = HashMap::default();
16272 self.transact(window, cx, |editor, _window, cx| {
16273 for hunk in hunks {
16274 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16275 ranges_by_buffer
16276 .entry(buffer.clone())
16277 .or_insert_with(Vec::new)
16278 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16279 }
16280 }
16281
16282 for (buffer, ranges) in ranges_by_buffer {
16283 buffer.update(cx, |buffer, cx| {
16284 buffer.merge_into_base(ranges, cx);
16285 });
16286 }
16287 });
16288
16289 if let Some(project) = self.project.clone() {
16290 self.save(true, project, window, cx).detach_and_log_err(cx);
16291 }
16292 }
16293
16294 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16295 if hovered != self.gutter_hovered {
16296 self.gutter_hovered = hovered;
16297 cx.notify();
16298 }
16299 }
16300
16301 pub fn insert_blocks(
16302 &mut self,
16303 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16304 autoscroll: Option<Autoscroll>,
16305 cx: &mut Context<Self>,
16306 ) -> Vec<CustomBlockId> {
16307 let blocks = self
16308 .display_map
16309 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16310 if let Some(autoscroll) = autoscroll {
16311 self.request_autoscroll(autoscroll, cx);
16312 }
16313 cx.notify();
16314 blocks
16315 }
16316
16317 pub fn resize_blocks(
16318 &mut self,
16319 heights: HashMap<CustomBlockId, u32>,
16320 autoscroll: Option<Autoscroll>,
16321 cx: &mut Context<Self>,
16322 ) {
16323 self.display_map
16324 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16325 if let Some(autoscroll) = autoscroll {
16326 self.request_autoscroll(autoscroll, cx);
16327 }
16328 cx.notify();
16329 }
16330
16331 pub fn replace_blocks(
16332 &mut self,
16333 renderers: HashMap<CustomBlockId, RenderBlock>,
16334 autoscroll: Option<Autoscroll>,
16335 cx: &mut Context<Self>,
16336 ) {
16337 self.display_map
16338 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16339 if let Some(autoscroll) = autoscroll {
16340 self.request_autoscroll(autoscroll, cx);
16341 }
16342 cx.notify();
16343 }
16344
16345 pub fn remove_blocks(
16346 &mut self,
16347 block_ids: HashSet<CustomBlockId>,
16348 autoscroll: Option<Autoscroll>,
16349 cx: &mut Context<Self>,
16350 ) {
16351 self.display_map.update(cx, |display_map, cx| {
16352 display_map.remove_blocks(block_ids, cx)
16353 });
16354 if let Some(autoscroll) = autoscroll {
16355 self.request_autoscroll(autoscroll, cx);
16356 }
16357 cx.notify();
16358 }
16359
16360 pub fn row_for_block(
16361 &self,
16362 block_id: CustomBlockId,
16363 cx: &mut Context<Self>,
16364 ) -> Option<DisplayRow> {
16365 self.display_map
16366 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16367 }
16368
16369 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16370 self.focused_block = Some(focused_block);
16371 }
16372
16373 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16374 self.focused_block.take()
16375 }
16376
16377 pub fn insert_creases(
16378 &mut self,
16379 creases: impl IntoIterator<Item = Crease<Anchor>>,
16380 cx: &mut Context<Self>,
16381 ) -> Vec<CreaseId> {
16382 self.display_map
16383 .update(cx, |map, cx| map.insert_creases(creases, cx))
16384 }
16385
16386 pub fn remove_creases(
16387 &mut self,
16388 ids: impl IntoIterator<Item = CreaseId>,
16389 cx: &mut Context<Self>,
16390 ) -> Vec<(CreaseId, Range<Anchor>)> {
16391 self.display_map
16392 .update(cx, |map, cx| map.remove_creases(ids, cx))
16393 }
16394
16395 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16396 self.display_map
16397 .update(cx, |map, cx| map.snapshot(cx))
16398 .longest_row()
16399 }
16400
16401 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16402 self.display_map
16403 .update(cx, |map, cx| map.snapshot(cx))
16404 .max_point()
16405 }
16406
16407 pub fn text(&self, cx: &App) -> String {
16408 self.buffer.read(cx).read(cx).text()
16409 }
16410
16411 pub fn is_empty(&self, cx: &App) -> bool {
16412 self.buffer.read(cx).read(cx).is_empty()
16413 }
16414
16415 pub fn text_option(&self, cx: &App) -> Option<String> {
16416 let text = self.text(cx);
16417 let text = text.trim();
16418
16419 if text.is_empty() {
16420 return None;
16421 }
16422
16423 Some(text.to_string())
16424 }
16425
16426 pub fn set_text(
16427 &mut self,
16428 text: impl Into<Arc<str>>,
16429 window: &mut Window,
16430 cx: &mut Context<Self>,
16431 ) {
16432 self.transact(window, cx, |this, _, cx| {
16433 this.buffer
16434 .read(cx)
16435 .as_singleton()
16436 .expect("you can only call set_text on editors for singleton buffers")
16437 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16438 });
16439 }
16440
16441 pub fn display_text(&self, cx: &mut App) -> String {
16442 self.display_map
16443 .update(cx, |map, cx| map.snapshot(cx))
16444 .text()
16445 }
16446
16447 fn create_minimap(
16448 &self,
16449 minimap_settings: MinimapSettings,
16450 window: &mut Window,
16451 cx: &mut Context<Self>,
16452 ) -> Option<Entity<Self>> {
16453 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
16454 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
16455 }
16456
16457 fn initialize_new_minimap(
16458 &self,
16459 minimap_settings: MinimapSettings,
16460 window: &mut Window,
16461 cx: &mut Context<Self>,
16462 ) -> Entity<Self> {
16463 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
16464
16465 let mut minimap = Editor::new_internal(
16466 EditorMode::Minimap {
16467 parent: cx.weak_entity(),
16468 },
16469 self.buffer.clone(),
16470 self.project.clone(),
16471 Some(self.display_map.clone()),
16472 window,
16473 cx,
16474 );
16475 minimap.scroll_manager.clone_state(&self.scroll_manager);
16476 minimap.set_text_style_refinement(TextStyleRefinement {
16477 font_size: Some(MINIMAP_FONT_SIZE),
16478 font_weight: Some(MINIMAP_FONT_WEIGHT),
16479 ..Default::default()
16480 });
16481 minimap.update_minimap_configuration(minimap_settings, cx);
16482 cx.new(|_| minimap)
16483 }
16484
16485 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
16486 let current_line_highlight = minimap_settings
16487 .current_line_highlight
16488 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
16489 self.set_current_line_highlight(Some(current_line_highlight));
16490 }
16491
16492 pub fn minimap(&self) -> Option<&Entity<Self>> {
16493 self.minimap
16494 .as_ref()
16495 .filter(|_| self.minimap_visibility.visible())
16496 }
16497
16498 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16499 let mut wrap_guides = smallvec::smallvec![];
16500
16501 if self.show_wrap_guides == Some(false) {
16502 return wrap_guides;
16503 }
16504
16505 let settings = self.buffer.read(cx).language_settings(cx);
16506 if settings.show_wrap_guides {
16507 match self.soft_wrap_mode(cx) {
16508 SoftWrap::Column(soft_wrap) => {
16509 wrap_guides.push((soft_wrap as usize, true));
16510 }
16511 SoftWrap::Bounded(soft_wrap) => {
16512 wrap_guides.push((soft_wrap as usize, true));
16513 }
16514 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16515 }
16516 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16517 }
16518
16519 wrap_guides
16520 }
16521
16522 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16523 let settings = self.buffer.read(cx).language_settings(cx);
16524 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16525 match mode {
16526 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16527 SoftWrap::None
16528 }
16529 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16530 language_settings::SoftWrap::PreferredLineLength => {
16531 SoftWrap::Column(settings.preferred_line_length)
16532 }
16533 language_settings::SoftWrap::Bounded => {
16534 SoftWrap::Bounded(settings.preferred_line_length)
16535 }
16536 }
16537 }
16538
16539 pub fn set_soft_wrap_mode(
16540 &mut self,
16541 mode: language_settings::SoftWrap,
16542
16543 cx: &mut Context<Self>,
16544 ) {
16545 self.soft_wrap_mode_override = Some(mode);
16546 cx.notify();
16547 }
16548
16549 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16550 self.hard_wrap = hard_wrap;
16551 cx.notify();
16552 }
16553
16554 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16555 self.text_style_refinement = Some(style);
16556 }
16557
16558 /// called by the Element so we know what style we were most recently rendered with.
16559 pub(crate) fn set_style(
16560 &mut self,
16561 style: EditorStyle,
16562 window: &mut Window,
16563 cx: &mut Context<Self>,
16564 ) {
16565 // We intentionally do not inform the display map about the minimap style
16566 // so that wrapping is not recalculated and stays consistent for the editor
16567 // and its linked minimap.
16568 if !self.mode.is_minimap() {
16569 let rem_size = window.rem_size();
16570 self.display_map.update(cx, |map, cx| {
16571 map.set_font(
16572 style.text.font(),
16573 style.text.font_size.to_pixels(rem_size),
16574 cx,
16575 )
16576 });
16577 }
16578 self.style = Some(style);
16579 }
16580
16581 pub fn style(&self) -> Option<&EditorStyle> {
16582 self.style.as_ref()
16583 }
16584
16585 // Called by the element. This method is not designed to be called outside of the editor
16586 // element's layout code because it does not notify when rewrapping is computed synchronously.
16587 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16588 self.display_map
16589 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16590 }
16591
16592 pub fn set_soft_wrap(&mut self) {
16593 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16594 }
16595
16596 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16597 if self.soft_wrap_mode_override.is_some() {
16598 self.soft_wrap_mode_override.take();
16599 } else {
16600 let soft_wrap = match self.soft_wrap_mode(cx) {
16601 SoftWrap::GitDiff => return,
16602 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16603 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16604 language_settings::SoftWrap::None
16605 }
16606 };
16607 self.soft_wrap_mode_override = Some(soft_wrap);
16608 }
16609 cx.notify();
16610 }
16611
16612 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16613 let Some(workspace) = self.workspace() else {
16614 return;
16615 };
16616 let fs = workspace.read(cx).app_state().fs.clone();
16617 let current_show = TabBarSettings::get_global(cx).show;
16618 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16619 setting.show = Some(!current_show);
16620 });
16621 }
16622
16623 pub fn toggle_indent_guides(
16624 &mut self,
16625 _: &ToggleIndentGuides,
16626 _: &mut Window,
16627 cx: &mut Context<Self>,
16628 ) {
16629 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16630 self.buffer
16631 .read(cx)
16632 .language_settings(cx)
16633 .indent_guides
16634 .enabled
16635 });
16636 self.show_indent_guides = Some(!currently_enabled);
16637 cx.notify();
16638 }
16639
16640 fn should_show_indent_guides(&self) -> Option<bool> {
16641 self.show_indent_guides
16642 }
16643
16644 pub fn toggle_line_numbers(
16645 &mut self,
16646 _: &ToggleLineNumbers,
16647 _: &mut Window,
16648 cx: &mut Context<Self>,
16649 ) {
16650 let mut editor_settings = EditorSettings::get_global(cx).clone();
16651 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16652 EditorSettings::override_global(editor_settings, cx);
16653 }
16654
16655 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16656 if let Some(show_line_numbers) = self.show_line_numbers {
16657 return show_line_numbers;
16658 }
16659 EditorSettings::get_global(cx).gutter.line_numbers
16660 }
16661
16662 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16663 self.use_relative_line_numbers
16664 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16665 }
16666
16667 pub fn toggle_relative_line_numbers(
16668 &mut self,
16669 _: &ToggleRelativeLineNumbers,
16670 _: &mut Window,
16671 cx: &mut Context<Self>,
16672 ) {
16673 let is_relative = self.should_use_relative_line_numbers(cx);
16674 self.set_relative_line_number(Some(!is_relative), cx)
16675 }
16676
16677 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16678 self.use_relative_line_numbers = is_relative;
16679 cx.notify();
16680 }
16681
16682 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16683 self.show_gutter = show_gutter;
16684 cx.notify();
16685 }
16686
16687 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
16688 self.show_scrollbars = show_scrollbars;
16689 cx.notify();
16690 }
16691
16692 pub fn set_minimap_visibility(
16693 &mut self,
16694 minimap_visibility: MinimapVisibility,
16695 window: &mut Window,
16696 cx: &mut Context<Self>,
16697 ) {
16698 if self.minimap_visibility != minimap_visibility {
16699 if minimap_visibility.visible() && self.minimap.is_none() {
16700 let minimap_settings = EditorSettings::get_global(cx).minimap;
16701 self.minimap =
16702 self.create_minimap(minimap_settings.with_show_override(), window, cx);
16703 }
16704 self.minimap_visibility = minimap_visibility;
16705 cx.notify();
16706 }
16707 }
16708
16709 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16710 self.set_show_scrollbars(false, cx);
16711 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
16712 }
16713
16714 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
16715 self.show_line_numbers = Some(show_line_numbers);
16716 cx.notify();
16717 }
16718
16719 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
16720 self.disable_expand_excerpt_buttons = true;
16721 cx.notify();
16722 }
16723
16724 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
16725 self.show_git_diff_gutter = Some(show_git_diff_gutter);
16726 cx.notify();
16727 }
16728
16729 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
16730 self.show_code_actions = Some(show_code_actions);
16731 cx.notify();
16732 }
16733
16734 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
16735 self.show_runnables = Some(show_runnables);
16736 cx.notify();
16737 }
16738
16739 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
16740 self.show_breakpoints = Some(show_breakpoints);
16741 cx.notify();
16742 }
16743
16744 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
16745 if self.display_map.read(cx).masked != masked {
16746 self.display_map.update(cx, |map, _| map.masked = masked);
16747 }
16748 cx.notify()
16749 }
16750
16751 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
16752 self.show_wrap_guides = Some(show_wrap_guides);
16753 cx.notify();
16754 }
16755
16756 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
16757 self.show_indent_guides = Some(show_indent_guides);
16758 cx.notify();
16759 }
16760
16761 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
16762 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
16763 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
16764 if let Some(dir) = file.abs_path(cx).parent() {
16765 return Some(dir.to_owned());
16766 }
16767 }
16768
16769 if let Some(project_path) = buffer.read(cx).project_path(cx) {
16770 return Some(project_path.path.to_path_buf());
16771 }
16772 }
16773
16774 None
16775 }
16776
16777 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
16778 self.active_excerpt(cx)?
16779 .1
16780 .read(cx)
16781 .file()
16782 .and_then(|f| f.as_local())
16783 }
16784
16785 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16786 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16787 let buffer = buffer.read(cx);
16788 if let Some(project_path) = buffer.project_path(cx) {
16789 let project = self.project.as_ref()?.read(cx);
16790 project.absolute_path(&project_path, cx)
16791 } else {
16792 buffer
16793 .file()
16794 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
16795 }
16796 })
16797 }
16798
16799 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16800 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16801 let project_path = buffer.read(cx).project_path(cx)?;
16802 let project = self.project.as_ref()?.read(cx);
16803 let entry = project.entry_for_path(&project_path, cx)?;
16804 let path = entry.path.to_path_buf();
16805 Some(path)
16806 })
16807 }
16808
16809 pub fn reveal_in_finder(
16810 &mut self,
16811 _: &RevealInFileManager,
16812 _window: &mut Window,
16813 cx: &mut Context<Self>,
16814 ) {
16815 if let Some(target) = self.target_file(cx) {
16816 cx.reveal_path(&target.abs_path(cx));
16817 }
16818 }
16819
16820 pub fn copy_path(
16821 &mut self,
16822 _: &zed_actions::workspace::CopyPath,
16823 _window: &mut Window,
16824 cx: &mut Context<Self>,
16825 ) {
16826 if let Some(path) = self.target_file_abs_path(cx) {
16827 if let Some(path) = path.to_str() {
16828 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16829 }
16830 }
16831 }
16832
16833 pub fn copy_relative_path(
16834 &mut self,
16835 _: &zed_actions::workspace::CopyRelativePath,
16836 _window: &mut Window,
16837 cx: &mut Context<Self>,
16838 ) {
16839 if let Some(path) = self.target_file_path(cx) {
16840 if let Some(path) = path.to_str() {
16841 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16842 }
16843 }
16844 }
16845
16846 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
16847 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
16848 buffer.read(cx).project_path(cx)
16849 } else {
16850 None
16851 }
16852 }
16853
16854 // Returns true if the editor handled a go-to-line request
16855 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
16856 maybe!({
16857 let breakpoint_store = self.breakpoint_store.as_ref()?;
16858
16859 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
16860 else {
16861 self.clear_row_highlights::<ActiveDebugLine>();
16862 return None;
16863 };
16864
16865 let position = active_stack_frame.position;
16866 let buffer_id = position.buffer_id?;
16867 let snapshot = self
16868 .project
16869 .as_ref()?
16870 .read(cx)
16871 .buffer_for_id(buffer_id, cx)?
16872 .read(cx)
16873 .snapshot();
16874
16875 let mut handled = false;
16876 for (id, ExcerptRange { context, .. }) in
16877 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
16878 {
16879 if context.start.cmp(&position, &snapshot).is_ge()
16880 || context.end.cmp(&position, &snapshot).is_lt()
16881 {
16882 continue;
16883 }
16884 let snapshot = self.buffer.read(cx).snapshot(cx);
16885 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
16886
16887 handled = true;
16888 self.clear_row_highlights::<ActiveDebugLine>();
16889 self.go_to_line::<ActiveDebugLine>(
16890 multibuffer_anchor,
16891 Some(cx.theme().colors().editor_debugger_active_line_background),
16892 window,
16893 cx,
16894 );
16895
16896 cx.notify();
16897 }
16898
16899 handled.then_some(())
16900 })
16901 .is_some()
16902 }
16903
16904 pub fn copy_file_name_without_extension(
16905 &mut self,
16906 _: &CopyFileNameWithoutExtension,
16907 _: &mut Window,
16908 cx: &mut Context<Self>,
16909 ) {
16910 if let Some(file) = self.target_file(cx) {
16911 if let Some(file_stem) = file.path().file_stem() {
16912 if let Some(name) = file_stem.to_str() {
16913 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16914 }
16915 }
16916 }
16917 }
16918
16919 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
16920 if let Some(file) = self.target_file(cx) {
16921 if let Some(file_name) = file.path().file_name() {
16922 if let Some(name) = file_name.to_str() {
16923 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16924 }
16925 }
16926 }
16927 }
16928
16929 pub fn toggle_git_blame(
16930 &mut self,
16931 _: &::git::Blame,
16932 window: &mut Window,
16933 cx: &mut Context<Self>,
16934 ) {
16935 self.show_git_blame_gutter = !self.show_git_blame_gutter;
16936
16937 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
16938 self.start_git_blame(true, window, cx);
16939 }
16940
16941 cx.notify();
16942 }
16943
16944 pub fn toggle_git_blame_inline(
16945 &mut self,
16946 _: &ToggleGitBlameInline,
16947 window: &mut Window,
16948 cx: &mut Context<Self>,
16949 ) {
16950 self.toggle_git_blame_inline_internal(true, window, cx);
16951 cx.notify();
16952 }
16953
16954 pub fn open_git_blame_commit(
16955 &mut self,
16956 _: &OpenGitBlameCommit,
16957 window: &mut Window,
16958 cx: &mut Context<Self>,
16959 ) {
16960 self.open_git_blame_commit_internal(window, cx);
16961 }
16962
16963 fn open_git_blame_commit_internal(
16964 &mut self,
16965 window: &mut Window,
16966 cx: &mut Context<Self>,
16967 ) -> Option<()> {
16968 let blame = self.blame.as_ref()?;
16969 let snapshot = self.snapshot(window, cx);
16970 let cursor = self.selections.newest::<Point>(cx).head();
16971 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
16972 let blame_entry = blame
16973 .update(cx, |blame, cx| {
16974 blame
16975 .blame_for_rows(
16976 &[RowInfo {
16977 buffer_id: Some(buffer.remote_id()),
16978 buffer_row: Some(point.row),
16979 ..Default::default()
16980 }],
16981 cx,
16982 )
16983 .next()
16984 })
16985 .flatten()?;
16986 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
16987 let repo = blame.read(cx).repository(cx)?;
16988 let workspace = self.workspace()?.downgrade();
16989 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
16990 None
16991 }
16992
16993 pub fn git_blame_inline_enabled(&self) -> bool {
16994 self.git_blame_inline_enabled
16995 }
16996
16997 pub fn toggle_selection_menu(
16998 &mut self,
16999 _: &ToggleSelectionMenu,
17000 _: &mut Window,
17001 cx: &mut Context<Self>,
17002 ) {
17003 self.show_selection_menu = self
17004 .show_selection_menu
17005 .map(|show_selections_menu| !show_selections_menu)
17006 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17007
17008 cx.notify();
17009 }
17010
17011 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17012 self.show_selection_menu
17013 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17014 }
17015
17016 fn start_git_blame(
17017 &mut self,
17018 user_triggered: bool,
17019 window: &mut Window,
17020 cx: &mut Context<Self>,
17021 ) {
17022 if let Some(project) = self.project.as_ref() {
17023 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17024 return;
17025 };
17026
17027 if buffer.read(cx).file().is_none() {
17028 return;
17029 }
17030
17031 let focused = self.focus_handle(cx).contains_focused(window, cx);
17032
17033 let project = project.clone();
17034 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17035 self.blame_subscription =
17036 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17037 self.blame = Some(blame);
17038 }
17039 }
17040
17041 fn toggle_git_blame_inline_internal(
17042 &mut self,
17043 user_triggered: bool,
17044 window: &mut Window,
17045 cx: &mut Context<Self>,
17046 ) {
17047 if self.git_blame_inline_enabled {
17048 self.git_blame_inline_enabled = false;
17049 self.show_git_blame_inline = false;
17050 self.show_git_blame_inline_delay_task.take();
17051 } else {
17052 self.git_blame_inline_enabled = true;
17053 self.start_git_blame_inline(user_triggered, window, cx);
17054 }
17055
17056 cx.notify();
17057 }
17058
17059 fn start_git_blame_inline(
17060 &mut self,
17061 user_triggered: bool,
17062 window: &mut Window,
17063 cx: &mut Context<Self>,
17064 ) {
17065 self.start_git_blame(user_triggered, window, cx);
17066
17067 if ProjectSettings::get_global(cx)
17068 .git
17069 .inline_blame_delay()
17070 .is_some()
17071 {
17072 self.start_inline_blame_timer(window, cx);
17073 } else {
17074 self.show_git_blame_inline = true
17075 }
17076 }
17077
17078 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17079 self.blame.as_ref()
17080 }
17081
17082 pub fn show_git_blame_gutter(&self) -> bool {
17083 self.show_git_blame_gutter
17084 }
17085
17086 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17087 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17088 }
17089
17090 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17091 self.show_git_blame_inline
17092 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17093 && !self.newest_selection_head_on_empty_line(cx)
17094 && self.has_blame_entries(cx)
17095 }
17096
17097 fn has_blame_entries(&self, cx: &App) -> bool {
17098 self.blame()
17099 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17100 }
17101
17102 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17103 let cursor_anchor = self.selections.newest_anchor().head();
17104
17105 let snapshot = self.buffer.read(cx).snapshot(cx);
17106 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17107
17108 snapshot.line_len(buffer_row) == 0
17109 }
17110
17111 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17112 let buffer_and_selection = maybe!({
17113 let selection = self.selections.newest::<Point>(cx);
17114 let selection_range = selection.range();
17115
17116 let multi_buffer = self.buffer().read(cx);
17117 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17118 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17119
17120 let (buffer, range, _) = if selection.reversed {
17121 buffer_ranges.first()
17122 } else {
17123 buffer_ranges.last()
17124 }?;
17125
17126 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17127 ..text::ToPoint::to_point(&range.end, &buffer).row;
17128 Some((
17129 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17130 selection,
17131 ))
17132 });
17133
17134 let Some((buffer, selection)) = buffer_and_selection else {
17135 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17136 };
17137
17138 let Some(project) = self.project.as_ref() else {
17139 return Task::ready(Err(anyhow!("editor does not have project")));
17140 };
17141
17142 project.update(cx, |project, cx| {
17143 project.get_permalink_to_line(&buffer, selection, cx)
17144 })
17145 }
17146
17147 pub fn copy_permalink_to_line(
17148 &mut self,
17149 _: &CopyPermalinkToLine,
17150 window: &mut Window,
17151 cx: &mut Context<Self>,
17152 ) {
17153 let permalink_task = self.get_permalink_to_line(cx);
17154 let workspace = self.workspace();
17155
17156 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17157 Ok(permalink) => {
17158 cx.update(|_, cx| {
17159 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17160 })
17161 .ok();
17162 }
17163 Err(err) => {
17164 let message = format!("Failed to copy permalink: {err}");
17165
17166 Err::<(), anyhow::Error>(err).log_err();
17167
17168 if let Some(workspace) = workspace {
17169 workspace
17170 .update_in(cx, |workspace, _, cx| {
17171 struct CopyPermalinkToLine;
17172
17173 workspace.show_toast(
17174 Toast::new(
17175 NotificationId::unique::<CopyPermalinkToLine>(),
17176 message,
17177 ),
17178 cx,
17179 )
17180 })
17181 .ok();
17182 }
17183 }
17184 })
17185 .detach();
17186 }
17187
17188 pub fn copy_file_location(
17189 &mut self,
17190 _: &CopyFileLocation,
17191 _: &mut Window,
17192 cx: &mut Context<Self>,
17193 ) {
17194 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17195 if let Some(file) = self.target_file(cx) {
17196 if let Some(path) = file.path().to_str() {
17197 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17198 }
17199 }
17200 }
17201
17202 pub fn open_permalink_to_line(
17203 &mut self,
17204 _: &OpenPermalinkToLine,
17205 window: &mut Window,
17206 cx: &mut Context<Self>,
17207 ) {
17208 let permalink_task = self.get_permalink_to_line(cx);
17209 let workspace = self.workspace();
17210
17211 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17212 Ok(permalink) => {
17213 cx.update(|_, cx| {
17214 cx.open_url(permalink.as_ref());
17215 })
17216 .ok();
17217 }
17218 Err(err) => {
17219 let message = format!("Failed to open permalink: {err}");
17220
17221 Err::<(), anyhow::Error>(err).log_err();
17222
17223 if let Some(workspace) = workspace {
17224 workspace
17225 .update(cx, |workspace, cx| {
17226 struct OpenPermalinkToLine;
17227
17228 workspace.show_toast(
17229 Toast::new(
17230 NotificationId::unique::<OpenPermalinkToLine>(),
17231 message,
17232 ),
17233 cx,
17234 )
17235 })
17236 .ok();
17237 }
17238 }
17239 })
17240 .detach();
17241 }
17242
17243 pub fn insert_uuid_v4(
17244 &mut self,
17245 _: &InsertUuidV4,
17246 window: &mut Window,
17247 cx: &mut Context<Self>,
17248 ) {
17249 self.insert_uuid(UuidVersion::V4, window, cx);
17250 }
17251
17252 pub fn insert_uuid_v7(
17253 &mut self,
17254 _: &InsertUuidV7,
17255 window: &mut Window,
17256 cx: &mut Context<Self>,
17257 ) {
17258 self.insert_uuid(UuidVersion::V7, window, cx);
17259 }
17260
17261 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17262 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17263 self.transact(window, cx, |this, window, cx| {
17264 let edits = this
17265 .selections
17266 .all::<Point>(cx)
17267 .into_iter()
17268 .map(|selection| {
17269 let uuid = match version {
17270 UuidVersion::V4 => uuid::Uuid::new_v4(),
17271 UuidVersion::V7 => uuid::Uuid::now_v7(),
17272 };
17273
17274 (selection.range(), uuid.to_string())
17275 });
17276 this.edit(edits, cx);
17277 this.refresh_inline_completion(true, false, window, cx);
17278 });
17279 }
17280
17281 pub fn open_selections_in_multibuffer(
17282 &mut self,
17283 _: &OpenSelectionsInMultibuffer,
17284 window: &mut Window,
17285 cx: &mut Context<Self>,
17286 ) {
17287 let multibuffer = self.buffer.read(cx);
17288
17289 let Some(buffer) = multibuffer.as_singleton() else {
17290 return;
17291 };
17292
17293 let Some(workspace) = self.workspace() else {
17294 return;
17295 };
17296
17297 let locations = self
17298 .selections
17299 .disjoint_anchors()
17300 .iter()
17301 .map(|range| Location {
17302 buffer: buffer.clone(),
17303 range: range.start.text_anchor..range.end.text_anchor,
17304 })
17305 .collect::<Vec<_>>();
17306
17307 let title = multibuffer.title(cx).to_string();
17308
17309 cx.spawn_in(window, async move |_, cx| {
17310 workspace.update_in(cx, |workspace, window, cx| {
17311 Self::open_locations_in_multibuffer(
17312 workspace,
17313 locations,
17314 format!("Selections for '{title}'"),
17315 false,
17316 MultibufferSelectionMode::All,
17317 window,
17318 cx,
17319 );
17320 })
17321 })
17322 .detach();
17323 }
17324
17325 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17326 /// last highlight added will be used.
17327 ///
17328 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17329 pub fn highlight_rows<T: 'static>(
17330 &mut self,
17331 range: Range<Anchor>,
17332 color: Hsla,
17333 options: RowHighlightOptions,
17334 cx: &mut Context<Self>,
17335 ) {
17336 let snapshot = self.buffer().read(cx).snapshot(cx);
17337 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17338 let ix = row_highlights.binary_search_by(|highlight| {
17339 Ordering::Equal
17340 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17341 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17342 });
17343
17344 if let Err(mut ix) = ix {
17345 let index = post_inc(&mut self.highlight_order);
17346
17347 // If this range intersects with the preceding highlight, then merge it with
17348 // the preceding highlight. Otherwise insert a new highlight.
17349 let mut merged = false;
17350 if ix > 0 {
17351 let prev_highlight = &mut row_highlights[ix - 1];
17352 if prev_highlight
17353 .range
17354 .end
17355 .cmp(&range.start, &snapshot)
17356 .is_ge()
17357 {
17358 ix -= 1;
17359 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17360 prev_highlight.range.end = range.end;
17361 }
17362 merged = true;
17363 prev_highlight.index = index;
17364 prev_highlight.color = color;
17365 prev_highlight.options = options;
17366 }
17367 }
17368
17369 if !merged {
17370 row_highlights.insert(
17371 ix,
17372 RowHighlight {
17373 range: range.clone(),
17374 index,
17375 color,
17376 options,
17377 type_id: TypeId::of::<T>(),
17378 },
17379 );
17380 }
17381
17382 // If any of the following highlights intersect with this one, merge them.
17383 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17384 let highlight = &row_highlights[ix];
17385 if next_highlight
17386 .range
17387 .start
17388 .cmp(&highlight.range.end, &snapshot)
17389 .is_le()
17390 {
17391 if next_highlight
17392 .range
17393 .end
17394 .cmp(&highlight.range.end, &snapshot)
17395 .is_gt()
17396 {
17397 row_highlights[ix].range.end = next_highlight.range.end;
17398 }
17399 row_highlights.remove(ix + 1);
17400 } else {
17401 break;
17402 }
17403 }
17404 }
17405 }
17406
17407 /// Remove any highlighted row ranges of the given type that intersect the
17408 /// given ranges.
17409 pub fn remove_highlighted_rows<T: 'static>(
17410 &mut self,
17411 ranges_to_remove: Vec<Range<Anchor>>,
17412 cx: &mut Context<Self>,
17413 ) {
17414 let snapshot = self.buffer().read(cx).snapshot(cx);
17415 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17416 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17417 row_highlights.retain(|highlight| {
17418 while let Some(range_to_remove) = ranges_to_remove.peek() {
17419 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17420 Ordering::Less | Ordering::Equal => {
17421 ranges_to_remove.next();
17422 }
17423 Ordering::Greater => {
17424 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17425 Ordering::Less | Ordering::Equal => {
17426 return false;
17427 }
17428 Ordering::Greater => break,
17429 }
17430 }
17431 }
17432 }
17433
17434 true
17435 })
17436 }
17437
17438 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17439 pub fn clear_row_highlights<T: 'static>(&mut self) {
17440 self.highlighted_rows.remove(&TypeId::of::<T>());
17441 }
17442
17443 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17444 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17445 self.highlighted_rows
17446 .get(&TypeId::of::<T>())
17447 .map_or(&[] as &[_], |vec| vec.as_slice())
17448 .iter()
17449 .map(|highlight| (highlight.range.clone(), highlight.color))
17450 }
17451
17452 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17453 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17454 /// Allows to ignore certain kinds of highlights.
17455 pub fn highlighted_display_rows(
17456 &self,
17457 window: &mut Window,
17458 cx: &mut App,
17459 ) -> BTreeMap<DisplayRow, LineHighlight> {
17460 let snapshot = self.snapshot(window, cx);
17461 let mut used_highlight_orders = HashMap::default();
17462 self.highlighted_rows
17463 .iter()
17464 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17465 .fold(
17466 BTreeMap::<DisplayRow, LineHighlight>::new(),
17467 |mut unique_rows, highlight| {
17468 let start = highlight.range.start.to_display_point(&snapshot);
17469 let end = highlight.range.end.to_display_point(&snapshot);
17470 let start_row = start.row().0;
17471 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17472 && end.column() == 0
17473 {
17474 end.row().0.saturating_sub(1)
17475 } else {
17476 end.row().0
17477 };
17478 for row in start_row..=end_row {
17479 let used_index =
17480 used_highlight_orders.entry(row).or_insert(highlight.index);
17481 if highlight.index >= *used_index {
17482 *used_index = highlight.index;
17483 unique_rows.insert(
17484 DisplayRow(row),
17485 LineHighlight {
17486 include_gutter: highlight.options.include_gutter,
17487 border: None,
17488 background: highlight.color.into(),
17489 type_id: Some(highlight.type_id),
17490 },
17491 );
17492 }
17493 }
17494 unique_rows
17495 },
17496 )
17497 }
17498
17499 pub fn highlighted_display_row_for_autoscroll(
17500 &self,
17501 snapshot: &DisplaySnapshot,
17502 ) -> Option<DisplayRow> {
17503 self.highlighted_rows
17504 .values()
17505 .flat_map(|highlighted_rows| highlighted_rows.iter())
17506 .filter_map(|highlight| {
17507 if highlight.options.autoscroll {
17508 Some(highlight.range.start.to_display_point(snapshot).row())
17509 } else {
17510 None
17511 }
17512 })
17513 .min()
17514 }
17515
17516 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17517 self.highlight_background::<SearchWithinRange>(
17518 ranges,
17519 |colors| colors.editor_document_highlight_read_background,
17520 cx,
17521 )
17522 }
17523
17524 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17525 self.breadcrumb_header = Some(new_header);
17526 }
17527
17528 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17529 self.clear_background_highlights::<SearchWithinRange>(cx);
17530 }
17531
17532 pub fn highlight_background<T: 'static>(
17533 &mut self,
17534 ranges: &[Range<Anchor>],
17535 color_fetcher: fn(&ThemeColors) -> Hsla,
17536 cx: &mut Context<Self>,
17537 ) {
17538 self.background_highlights
17539 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17540 self.scrollbar_marker_state.dirty = true;
17541 cx.notify();
17542 }
17543
17544 pub fn clear_background_highlights<T: 'static>(
17545 &mut self,
17546 cx: &mut Context<Self>,
17547 ) -> Option<BackgroundHighlight> {
17548 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17549 if !text_highlights.1.is_empty() {
17550 self.scrollbar_marker_state.dirty = true;
17551 cx.notify();
17552 }
17553 Some(text_highlights)
17554 }
17555
17556 pub fn highlight_gutter<T: 'static>(
17557 &mut self,
17558 ranges: &[Range<Anchor>],
17559 color_fetcher: fn(&App) -> Hsla,
17560 cx: &mut Context<Self>,
17561 ) {
17562 self.gutter_highlights
17563 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17564 cx.notify();
17565 }
17566
17567 pub fn clear_gutter_highlights<T: 'static>(
17568 &mut self,
17569 cx: &mut Context<Self>,
17570 ) -> Option<GutterHighlight> {
17571 cx.notify();
17572 self.gutter_highlights.remove(&TypeId::of::<T>())
17573 }
17574
17575 #[cfg(feature = "test-support")]
17576 pub fn all_text_background_highlights(
17577 &self,
17578 window: &mut Window,
17579 cx: &mut Context<Self>,
17580 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17581 let snapshot = self.snapshot(window, cx);
17582 let buffer = &snapshot.buffer_snapshot;
17583 let start = buffer.anchor_before(0);
17584 let end = buffer.anchor_after(buffer.len());
17585 let theme = cx.theme().colors();
17586 self.background_highlights_in_range(start..end, &snapshot, theme)
17587 }
17588
17589 #[cfg(feature = "test-support")]
17590 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17591 let snapshot = self.buffer().read(cx).snapshot(cx);
17592
17593 let highlights = self
17594 .background_highlights
17595 .get(&TypeId::of::<items::BufferSearchHighlights>());
17596
17597 if let Some((_color, ranges)) = highlights {
17598 ranges
17599 .iter()
17600 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17601 .collect_vec()
17602 } else {
17603 vec![]
17604 }
17605 }
17606
17607 fn document_highlights_for_position<'a>(
17608 &'a self,
17609 position: Anchor,
17610 buffer: &'a MultiBufferSnapshot,
17611 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17612 let read_highlights = self
17613 .background_highlights
17614 .get(&TypeId::of::<DocumentHighlightRead>())
17615 .map(|h| &h.1);
17616 let write_highlights = self
17617 .background_highlights
17618 .get(&TypeId::of::<DocumentHighlightWrite>())
17619 .map(|h| &h.1);
17620 let left_position = position.bias_left(buffer);
17621 let right_position = position.bias_right(buffer);
17622 read_highlights
17623 .into_iter()
17624 .chain(write_highlights)
17625 .flat_map(move |ranges| {
17626 let start_ix = match ranges.binary_search_by(|probe| {
17627 let cmp = probe.end.cmp(&left_position, buffer);
17628 if cmp.is_ge() {
17629 Ordering::Greater
17630 } else {
17631 Ordering::Less
17632 }
17633 }) {
17634 Ok(i) | Err(i) => i,
17635 };
17636
17637 ranges[start_ix..]
17638 .iter()
17639 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17640 })
17641 }
17642
17643 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17644 self.background_highlights
17645 .get(&TypeId::of::<T>())
17646 .map_or(false, |(_, highlights)| !highlights.is_empty())
17647 }
17648
17649 pub fn background_highlights_in_range(
17650 &self,
17651 search_range: Range<Anchor>,
17652 display_snapshot: &DisplaySnapshot,
17653 theme: &ThemeColors,
17654 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17655 let mut results = Vec::new();
17656 for (color_fetcher, ranges) in self.background_highlights.values() {
17657 let color = color_fetcher(theme);
17658 let start_ix = match ranges.binary_search_by(|probe| {
17659 let cmp = probe
17660 .end
17661 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17662 if cmp.is_gt() {
17663 Ordering::Greater
17664 } else {
17665 Ordering::Less
17666 }
17667 }) {
17668 Ok(i) | Err(i) => i,
17669 };
17670 for range in &ranges[start_ix..] {
17671 if range
17672 .start
17673 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17674 .is_ge()
17675 {
17676 break;
17677 }
17678
17679 let start = range.start.to_display_point(display_snapshot);
17680 let end = range.end.to_display_point(display_snapshot);
17681 results.push((start..end, color))
17682 }
17683 }
17684 results
17685 }
17686
17687 pub fn background_highlight_row_ranges<T: 'static>(
17688 &self,
17689 search_range: Range<Anchor>,
17690 display_snapshot: &DisplaySnapshot,
17691 count: usize,
17692 ) -> Vec<RangeInclusive<DisplayPoint>> {
17693 let mut results = Vec::new();
17694 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
17695 return vec![];
17696 };
17697
17698 let start_ix = match ranges.binary_search_by(|probe| {
17699 let cmp = probe
17700 .end
17701 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17702 if cmp.is_gt() {
17703 Ordering::Greater
17704 } else {
17705 Ordering::Less
17706 }
17707 }) {
17708 Ok(i) | Err(i) => i,
17709 };
17710 let mut push_region = |start: Option<Point>, end: Option<Point>| {
17711 if let (Some(start_display), Some(end_display)) = (start, end) {
17712 results.push(
17713 start_display.to_display_point(display_snapshot)
17714 ..=end_display.to_display_point(display_snapshot),
17715 );
17716 }
17717 };
17718 let mut start_row: Option<Point> = None;
17719 let mut end_row: Option<Point> = None;
17720 if ranges.len() > count {
17721 return Vec::new();
17722 }
17723 for range in &ranges[start_ix..] {
17724 if range
17725 .start
17726 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17727 .is_ge()
17728 {
17729 break;
17730 }
17731 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
17732 if let Some(current_row) = &end_row {
17733 if end.row == current_row.row {
17734 continue;
17735 }
17736 }
17737 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
17738 if start_row.is_none() {
17739 assert_eq!(end_row, None);
17740 start_row = Some(start);
17741 end_row = Some(end);
17742 continue;
17743 }
17744 if let Some(current_end) = end_row.as_mut() {
17745 if start.row > current_end.row + 1 {
17746 push_region(start_row, end_row);
17747 start_row = Some(start);
17748 end_row = Some(end);
17749 } else {
17750 // Merge two hunks.
17751 *current_end = end;
17752 }
17753 } else {
17754 unreachable!();
17755 }
17756 }
17757 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
17758 push_region(start_row, end_row);
17759 results
17760 }
17761
17762 pub fn gutter_highlights_in_range(
17763 &self,
17764 search_range: Range<Anchor>,
17765 display_snapshot: &DisplaySnapshot,
17766 cx: &App,
17767 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17768 let mut results = Vec::new();
17769 for (color_fetcher, ranges) in self.gutter_highlights.values() {
17770 let color = color_fetcher(cx);
17771 let start_ix = match ranges.binary_search_by(|probe| {
17772 let cmp = probe
17773 .end
17774 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17775 if cmp.is_gt() {
17776 Ordering::Greater
17777 } else {
17778 Ordering::Less
17779 }
17780 }) {
17781 Ok(i) | Err(i) => i,
17782 };
17783 for range in &ranges[start_ix..] {
17784 if range
17785 .start
17786 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17787 .is_ge()
17788 {
17789 break;
17790 }
17791
17792 let start = range.start.to_display_point(display_snapshot);
17793 let end = range.end.to_display_point(display_snapshot);
17794 results.push((start..end, color))
17795 }
17796 }
17797 results
17798 }
17799
17800 /// Get the text ranges corresponding to the redaction query
17801 pub fn redacted_ranges(
17802 &self,
17803 search_range: Range<Anchor>,
17804 display_snapshot: &DisplaySnapshot,
17805 cx: &App,
17806 ) -> Vec<Range<DisplayPoint>> {
17807 display_snapshot
17808 .buffer_snapshot
17809 .redacted_ranges(search_range, |file| {
17810 if let Some(file) = file {
17811 file.is_private()
17812 && EditorSettings::get(
17813 Some(SettingsLocation {
17814 worktree_id: file.worktree_id(cx),
17815 path: file.path().as_ref(),
17816 }),
17817 cx,
17818 )
17819 .redact_private_values
17820 } else {
17821 false
17822 }
17823 })
17824 .map(|range| {
17825 range.start.to_display_point(display_snapshot)
17826 ..range.end.to_display_point(display_snapshot)
17827 })
17828 .collect()
17829 }
17830
17831 pub fn highlight_text<T: 'static>(
17832 &mut self,
17833 ranges: Vec<Range<Anchor>>,
17834 style: HighlightStyle,
17835 cx: &mut Context<Self>,
17836 ) {
17837 self.display_map.update(cx, |map, _| {
17838 map.highlight_text(TypeId::of::<T>(), ranges, style)
17839 });
17840 cx.notify();
17841 }
17842
17843 pub(crate) fn highlight_inlays<T: 'static>(
17844 &mut self,
17845 highlights: Vec<InlayHighlight>,
17846 style: HighlightStyle,
17847 cx: &mut Context<Self>,
17848 ) {
17849 self.display_map.update(cx, |map, _| {
17850 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
17851 });
17852 cx.notify();
17853 }
17854
17855 pub fn text_highlights<'a, T: 'static>(
17856 &'a self,
17857 cx: &'a App,
17858 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
17859 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
17860 }
17861
17862 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
17863 let cleared = self
17864 .display_map
17865 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
17866 if cleared {
17867 cx.notify();
17868 }
17869 }
17870
17871 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
17872 (self.read_only(cx) || self.blink_manager.read(cx).visible())
17873 && self.focus_handle.is_focused(window)
17874 }
17875
17876 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
17877 self.show_cursor_when_unfocused = is_enabled;
17878 cx.notify();
17879 }
17880
17881 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
17882 cx.notify();
17883 }
17884
17885 fn on_debug_session_event(
17886 &mut self,
17887 _session: Entity<Session>,
17888 event: &SessionEvent,
17889 cx: &mut Context<Self>,
17890 ) {
17891 match event {
17892 SessionEvent::InvalidateInlineValue => {
17893 self.refresh_inline_values(cx);
17894 }
17895 _ => {}
17896 }
17897 }
17898
17899 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
17900 let Some(project) = self.project.clone() else {
17901 return;
17902 };
17903 let Some(buffer) = self.buffer.read(cx).as_singleton() else {
17904 return;
17905 };
17906 if !self.inline_value_cache.enabled {
17907 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
17908 self.splice_inlays(&inlays, Vec::new(), cx);
17909 return;
17910 }
17911
17912 let current_execution_position = self
17913 .highlighted_rows
17914 .get(&TypeId::of::<ActiveDebugLine>())
17915 .and_then(|lines| lines.last().map(|line| line.range.start));
17916
17917 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
17918 let snapshot = editor
17919 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17920 .ok()?;
17921
17922 let inline_values = editor
17923 .update(cx, |_, cx| {
17924 let Some(current_execution_position) = current_execution_position else {
17925 return Some(Task::ready(Ok(Vec::new())));
17926 };
17927
17928 // todo(debugger) when introducing multi buffer inline values check execution position's buffer id to make sure the text
17929 // anchor is in the same buffer
17930 let range =
17931 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
17932 project.inline_values(buffer, range, cx)
17933 })
17934 .ok()
17935 .flatten()?
17936 .await
17937 .context("refreshing debugger inlays")
17938 .log_err()?;
17939
17940 let (excerpt_id, buffer_id) = snapshot
17941 .excerpts()
17942 .next()
17943 .map(|excerpt| (excerpt.0, excerpt.1.remote_id()))?;
17944 editor
17945 .update(cx, |editor, cx| {
17946 let new_inlays = inline_values
17947 .into_iter()
17948 .map(|debugger_value| {
17949 Inlay::debugger_hint(
17950 post_inc(&mut editor.next_inlay_id),
17951 Anchor::in_buffer(excerpt_id, buffer_id, debugger_value.position),
17952 debugger_value.text(),
17953 )
17954 })
17955 .collect::<Vec<_>>();
17956 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
17957 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
17958
17959 editor.splice_inlays(&inlay_ids, new_inlays, cx);
17960 })
17961 .ok()?;
17962 Some(())
17963 });
17964 }
17965
17966 fn on_buffer_event(
17967 &mut self,
17968 multibuffer: &Entity<MultiBuffer>,
17969 event: &multi_buffer::Event,
17970 window: &mut Window,
17971 cx: &mut Context<Self>,
17972 ) {
17973 match event {
17974 multi_buffer::Event::Edited {
17975 singleton_buffer_edited,
17976 edited_buffer: buffer_edited,
17977 } => {
17978 self.scrollbar_marker_state.dirty = true;
17979 self.active_indent_guides_state.dirty = true;
17980 self.refresh_active_diagnostics(cx);
17981 self.refresh_code_actions(window, cx);
17982 self.refresh_selected_text_highlights(true, window, cx);
17983 refresh_matching_bracket_highlights(self, window, cx);
17984 if self.has_active_inline_completion() {
17985 self.update_visible_inline_completion(window, cx);
17986 }
17987 if let Some(buffer) = buffer_edited {
17988 let buffer_id = buffer.read(cx).remote_id();
17989 if !self.registered_buffers.contains_key(&buffer_id) {
17990 if let Some(project) = self.project.as_ref() {
17991 project.update(cx, |project, cx| {
17992 self.registered_buffers.insert(
17993 buffer_id,
17994 project.register_buffer_with_language_servers(&buffer, cx),
17995 );
17996 })
17997 }
17998 }
17999 }
18000 cx.emit(EditorEvent::BufferEdited);
18001 cx.emit(SearchEvent::MatchesInvalidated);
18002 if *singleton_buffer_edited {
18003 if let Some(project) = &self.project {
18004 #[allow(clippy::mutable_key_type)]
18005 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18006 multibuffer
18007 .all_buffers()
18008 .into_iter()
18009 .filter_map(|buffer| {
18010 buffer.update(cx, |buffer, cx| {
18011 let language = buffer.language()?;
18012 let should_discard = project.update(cx, |project, cx| {
18013 project.is_local()
18014 && !project.has_language_servers_for(buffer, cx)
18015 });
18016 should_discard.not().then_some(language.clone())
18017 })
18018 })
18019 .collect::<HashSet<_>>()
18020 });
18021 if !languages_affected.is_empty() {
18022 self.refresh_inlay_hints(
18023 InlayHintRefreshReason::BufferEdited(languages_affected),
18024 cx,
18025 );
18026 }
18027 }
18028 }
18029
18030 let Some(project) = &self.project else { return };
18031 let (telemetry, is_via_ssh) = {
18032 let project = project.read(cx);
18033 let telemetry = project.client().telemetry().clone();
18034 let is_via_ssh = project.is_via_ssh();
18035 (telemetry, is_via_ssh)
18036 };
18037 refresh_linked_ranges(self, window, cx);
18038 telemetry.log_edit_event("editor", is_via_ssh);
18039 }
18040 multi_buffer::Event::ExcerptsAdded {
18041 buffer,
18042 predecessor,
18043 excerpts,
18044 } => {
18045 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18046 let buffer_id = buffer.read(cx).remote_id();
18047 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18048 if let Some(project) = &self.project {
18049 update_uncommitted_diff_for_buffer(
18050 cx.entity(),
18051 project,
18052 [buffer.clone()],
18053 self.buffer.clone(),
18054 cx,
18055 )
18056 .detach();
18057 }
18058 }
18059 cx.emit(EditorEvent::ExcerptsAdded {
18060 buffer: buffer.clone(),
18061 predecessor: *predecessor,
18062 excerpts: excerpts.clone(),
18063 });
18064 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18065 }
18066 multi_buffer::Event::ExcerptsRemoved {
18067 ids,
18068 removed_buffer_ids,
18069 } => {
18070 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18071 let buffer = self.buffer.read(cx);
18072 self.registered_buffers
18073 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18074 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18075 cx.emit(EditorEvent::ExcerptsRemoved {
18076 ids: ids.clone(),
18077 removed_buffer_ids: removed_buffer_ids.clone(),
18078 })
18079 }
18080 multi_buffer::Event::ExcerptsEdited {
18081 excerpt_ids,
18082 buffer_ids,
18083 } => {
18084 self.display_map.update(cx, |map, cx| {
18085 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18086 });
18087 cx.emit(EditorEvent::ExcerptsEdited {
18088 ids: excerpt_ids.clone(),
18089 })
18090 }
18091 multi_buffer::Event::ExcerptsExpanded { ids } => {
18092 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18093 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18094 }
18095 multi_buffer::Event::Reparsed(buffer_id) => {
18096 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18097 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18098
18099 cx.emit(EditorEvent::Reparsed(*buffer_id));
18100 }
18101 multi_buffer::Event::DiffHunksToggled => {
18102 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18103 }
18104 multi_buffer::Event::LanguageChanged(buffer_id) => {
18105 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18106 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18107 cx.emit(EditorEvent::Reparsed(*buffer_id));
18108 cx.notify();
18109 }
18110 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18111 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18112 multi_buffer::Event::FileHandleChanged
18113 | multi_buffer::Event::Reloaded
18114 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18115 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18116 multi_buffer::Event::DiagnosticsUpdated => {
18117 self.refresh_active_diagnostics(cx);
18118 self.refresh_inline_diagnostics(true, window, cx);
18119 self.scrollbar_marker_state.dirty = true;
18120 cx.notify();
18121 }
18122 _ => {}
18123 };
18124 }
18125
18126 pub fn start_temporary_diff_override(&mut self) {
18127 self.load_diff_task.take();
18128 self.temporary_diff_override = true;
18129 }
18130
18131 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18132 self.temporary_diff_override = false;
18133 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18134 self.buffer.update(cx, |buffer, cx| {
18135 buffer.set_all_diff_hunks_collapsed(cx);
18136 });
18137
18138 if let Some(project) = self.project.clone() {
18139 self.load_diff_task = Some(
18140 update_uncommitted_diff_for_buffer(
18141 cx.entity(),
18142 &project,
18143 self.buffer.read(cx).all_buffers(),
18144 self.buffer.clone(),
18145 cx,
18146 )
18147 .shared(),
18148 );
18149 }
18150 }
18151
18152 fn on_display_map_changed(
18153 &mut self,
18154 _: Entity<DisplayMap>,
18155 _: &mut Window,
18156 cx: &mut Context<Self>,
18157 ) {
18158 cx.notify();
18159 }
18160
18161 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18162 let new_severity = if self.diagnostics_enabled() {
18163 EditorSettings::get_global(cx)
18164 .diagnostics_max_severity
18165 .unwrap_or(DiagnosticSeverity::Hint)
18166 } else {
18167 DiagnosticSeverity::Off
18168 };
18169 self.set_max_diagnostics_severity(new_severity, cx);
18170 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18171 self.update_edit_prediction_settings(cx);
18172 self.refresh_inline_completion(true, false, window, cx);
18173 self.refresh_inlay_hints(
18174 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18175 self.selections.newest_anchor().head(),
18176 &self.buffer.read(cx).snapshot(cx),
18177 cx,
18178 )),
18179 cx,
18180 );
18181
18182 let old_cursor_shape = self.cursor_shape;
18183
18184 {
18185 let editor_settings = EditorSettings::get_global(cx);
18186 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18187 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18188 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18189 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18190 }
18191
18192 if old_cursor_shape != self.cursor_shape {
18193 cx.emit(EditorEvent::CursorShapeChanged);
18194 }
18195
18196 let project_settings = ProjectSettings::get_global(cx);
18197 self.serialize_dirty_buffers =
18198 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18199
18200 if self.mode.is_full() {
18201 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18202 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18203 if self.show_inline_diagnostics != show_inline_diagnostics {
18204 self.show_inline_diagnostics = show_inline_diagnostics;
18205 self.refresh_inline_diagnostics(false, window, cx);
18206 }
18207
18208 if self.git_blame_inline_enabled != inline_blame_enabled {
18209 self.toggle_git_blame_inline_internal(false, window, cx);
18210 }
18211
18212 let minimap_settings = EditorSettings::get_global(cx).minimap;
18213 if self.minimap_visibility.visible() != minimap_settings.minimap_enabled() {
18214 self.set_minimap_visibility(
18215 self.minimap_visibility.toggle_visibility(),
18216 window,
18217 cx,
18218 );
18219 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18220 minimap_entity.update(cx, |minimap_editor, cx| {
18221 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18222 })
18223 }
18224 }
18225
18226 cx.notify();
18227 }
18228
18229 pub fn set_searchable(&mut self, searchable: bool) {
18230 self.searchable = searchable;
18231 }
18232
18233 pub fn searchable(&self) -> bool {
18234 self.searchable
18235 }
18236
18237 fn open_proposed_changes_editor(
18238 &mut self,
18239 _: &OpenProposedChangesEditor,
18240 window: &mut Window,
18241 cx: &mut Context<Self>,
18242 ) {
18243 let Some(workspace) = self.workspace() else {
18244 cx.propagate();
18245 return;
18246 };
18247
18248 let selections = self.selections.all::<usize>(cx);
18249 let multi_buffer = self.buffer.read(cx);
18250 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18251 let mut new_selections_by_buffer = HashMap::default();
18252 for selection in selections {
18253 for (buffer, range, _) in
18254 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18255 {
18256 let mut range = range.to_point(buffer);
18257 range.start.column = 0;
18258 range.end.column = buffer.line_len(range.end.row);
18259 new_selections_by_buffer
18260 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18261 .or_insert(Vec::new())
18262 .push(range)
18263 }
18264 }
18265
18266 let proposed_changes_buffers = new_selections_by_buffer
18267 .into_iter()
18268 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18269 .collect::<Vec<_>>();
18270 let proposed_changes_editor = cx.new(|cx| {
18271 ProposedChangesEditor::new(
18272 "Proposed changes",
18273 proposed_changes_buffers,
18274 self.project.clone(),
18275 window,
18276 cx,
18277 )
18278 });
18279
18280 window.defer(cx, move |window, cx| {
18281 workspace.update(cx, |workspace, cx| {
18282 workspace.active_pane().update(cx, |pane, cx| {
18283 pane.add_item(
18284 Box::new(proposed_changes_editor),
18285 true,
18286 true,
18287 None,
18288 window,
18289 cx,
18290 );
18291 });
18292 });
18293 });
18294 }
18295
18296 pub fn open_excerpts_in_split(
18297 &mut self,
18298 _: &OpenExcerptsSplit,
18299 window: &mut Window,
18300 cx: &mut Context<Self>,
18301 ) {
18302 self.open_excerpts_common(None, true, window, cx)
18303 }
18304
18305 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18306 self.open_excerpts_common(None, false, window, cx)
18307 }
18308
18309 fn open_excerpts_common(
18310 &mut self,
18311 jump_data: Option<JumpData>,
18312 split: bool,
18313 window: &mut Window,
18314 cx: &mut Context<Self>,
18315 ) {
18316 let Some(workspace) = self.workspace() else {
18317 cx.propagate();
18318 return;
18319 };
18320
18321 if self.buffer.read(cx).is_singleton() {
18322 cx.propagate();
18323 return;
18324 }
18325
18326 let mut new_selections_by_buffer = HashMap::default();
18327 match &jump_data {
18328 Some(JumpData::MultiBufferPoint {
18329 excerpt_id,
18330 position,
18331 anchor,
18332 line_offset_from_top,
18333 }) => {
18334 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18335 if let Some(buffer) = multi_buffer_snapshot
18336 .buffer_id_for_excerpt(*excerpt_id)
18337 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18338 {
18339 let buffer_snapshot = buffer.read(cx).snapshot();
18340 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18341 language::ToPoint::to_point(anchor, &buffer_snapshot)
18342 } else {
18343 buffer_snapshot.clip_point(*position, Bias::Left)
18344 };
18345 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18346 new_selections_by_buffer.insert(
18347 buffer,
18348 (
18349 vec![jump_to_offset..jump_to_offset],
18350 Some(*line_offset_from_top),
18351 ),
18352 );
18353 }
18354 }
18355 Some(JumpData::MultiBufferRow {
18356 row,
18357 line_offset_from_top,
18358 }) => {
18359 let point = MultiBufferPoint::new(row.0, 0);
18360 if let Some((buffer, buffer_point, _)) =
18361 self.buffer.read(cx).point_to_buffer_point(point, cx)
18362 {
18363 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18364 new_selections_by_buffer
18365 .entry(buffer)
18366 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18367 .0
18368 .push(buffer_offset..buffer_offset)
18369 }
18370 }
18371 None => {
18372 let selections = self.selections.all::<usize>(cx);
18373 let multi_buffer = self.buffer.read(cx);
18374 for selection in selections {
18375 for (snapshot, range, _, anchor) in multi_buffer
18376 .snapshot(cx)
18377 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18378 {
18379 if let Some(anchor) = anchor {
18380 // selection is in a deleted hunk
18381 let Some(buffer_id) = anchor.buffer_id else {
18382 continue;
18383 };
18384 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18385 continue;
18386 };
18387 let offset = text::ToOffset::to_offset(
18388 &anchor.text_anchor,
18389 &buffer_handle.read(cx).snapshot(),
18390 );
18391 let range = offset..offset;
18392 new_selections_by_buffer
18393 .entry(buffer_handle)
18394 .or_insert((Vec::new(), None))
18395 .0
18396 .push(range)
18397 } else {
18398 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18399 else {
18400 continue;
18401 };
18402 new_selections_by_buffer
18403 .entry(buffer_handle)
18404 .or_insert((Vec::new(), None))
18405 .0
18406 .push(range)
18407 }
18408 }
18409 }
18410 }
18411 }
18412
18413 new_selections_by_buffer
18414 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18415
18416 if new_selections_by_buffer.is_empty() {
18417 return;
18418 }
18419
18420 // We defer the pane interaction because we ourselves are a workspace item
18421 // and activating a new item causes the pane to call a method on us reentrantly,
18422 // which panics if we're on the stack.
18423 window.defer(cx, move |window, cx| {
18424 workspace.update(cx, |workspace, cx| {
18425 let pane = if split {
18426 workspace.adjacent_pane(window, cx)
18427 } else {
18428 workspace.active_pane().clone()
18429 };
18430
18431 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18432 let editor = buffer
18433 .read(cx)
18434 .file()
18435 .is_none()
18436 .then(|| {
18437 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18438 // so `workspace.open_project_item` will never find them, always opening a new editor.
18439 // Instead, we try to activate the existing editor in the pane first.
18440 let (editor, pane_item_index) =
18441 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18442 let editor = item.downcast::<Editor>()?;
18443 let singleton_buffer =
18444 editor.read(cx).buffer().read(cx).as_singleton()?;
18445 if singleton_buffer == buffer {
18446 Some((editor, i))
18447 } else {
18448 None
18449 }
18450 })?;
18451 pane.update(cx, |pane, cx| {
18452 pane.activate_item(pane_item_index, true, true, window, cx)
18453 });
18454 Some(editor)
18455 })
18456 .flatten()
18457 .unwrap_or_else(|| {
18458 workspace.open_project_item::<Self>(
18459 pane.clone(),
18460 buffer,
18461 true,
18462 true,
18463 window,
18464 cx,
18465 )
18466 });
18467
18468 editor.update(cx, |editor, cx| {
18469 let autoscroll = match scroll_offset {
18470 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18471 None => Autoscroll::newest(),
18472 };
18473 let nav_history = editor.nav_history.take();
18474 editor.change_selections(Some(autoscroll), window, cx, |s| {
18475 s.select_ranges(ranges);
18476 });
18477 editor.nav_history = nav_history;
18478 });
18479 }
18480 })
18481 });
18482 }
18483
18484 // For now, don't allow opening excerpts in buffers that aren't backed by
18485 // regular project files.
18486 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18487 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18488 }
18489
18490 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18491 let snapshot = self.buffer.read(cx).read(cx);
18492 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18493 Some(
18494 ranges
18495 .iter()
18496 .map(move |range| {
18497 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18498 })
18499 .collect(),
18500 )
18501 }
18502
18503 fn selection_replacement_ranges(
18504 &self,
18505 range: Range<OffsetUtf16>,
18506 cx: &mut App,
18507 ) -> Vec<Range<OffsetUtf16>> {
18508 let selections = self.selections.all::<OffsetUtf16>(cx);
18509 let newest_selection = selections
18510 .iter()
18511 .max_by_key(|selection| selection.id)
18512 .unwrap();
18513 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
18514 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
18515 let snapshot = self.buffer.read(cx).read(cx);
18516 selections
18517 .into_iter()
18518 .map(|mut selection| {
18519 selection.start.0 =
18520 (selection.start.0 as isize).saturating_add(start_delta) as usize;
18521 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
18522 snapshot.clip_offset_utf16(selection.start, Bias::Left)
18523 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
18524 })
18525 .collect()
18526 }
18527
18528 fn report_editor_event(
18529 &self,
18530 event_type: &'static str,
18531 file_extension: Option<String>,
18532 cx: &App,
18533 ) {
18534 if cfg!(any(test, feature = "test-support")) {
18535 return;
18536 }
18537
18538 let Some(project) = &self.project else { return };
18539
18540 // If None, we are in a file without an extension
18541 let file = self
18542 .buffer
18543 .read(cx)
18544 .as_singleton()
18545 .and_then(|b| b.read(cx).file());
18546 let file_extension = file_extension.or(file
18547 .as_ref()
18548 .and_then(|file| Path::new(file.file_name(cx)).extension())
18549 .and_then(|e| e.to_str())
18550 .map(|a| a.to_string()));
18551
18552 let vim_mode = vim_enabled(cx);
18553
18554 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
18555 let copilot_enabled = edit_predictions_provider
18556 == language::language_settings::EditPredictionProvider::Copilot;
18557 let copilot_enabled_for_language = self
18558 .buffer
18559 .read(cx)
18560 .language_settings(cx)
18561 .show_edit_predictions;
18562
18563 let project = project.read(cx);
18564 telemetry::event!(
18565 event_type,
18566 file_extension,
18567 vim_mode,
18568 copilot_enabled,
18569 copilot_enabled_for_language,
18570 edit_predictions_provider,
18571 is_via_ssh = project.is_via_ssh(),
18572 );
18573 }
18574
18575 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
18576 /// with each line being an array of {text, highlight} objects.
18577 fn copy_highlight_json(
18578 &mut self,
18579 _: &CopyHighlightJson,
18580 window: &mut Window,
18581 cx: &mut Context<Self>,
18582 ) {
18583 #[derive(Serialize)]
18584 struct Chunk<'a> {
18585 text: String,
18586 highlight: Option<&'a str>,
18587 }
18588
18589 let snapshot = self.buffer.read(cx).snapshot(cx);
18590 let range = self
18591 .selected_text_range(false, window, cx)
18592 .and_then(|selection| {
18593 if selection.range.is_empty() {
18594 None
18595 } else {
18596 Some(selection.range)
18597 }
18598 })
18599 .unwrap_or_else(|| 0..snapshot.len());
18600
18601 let chunks = snapshot.chunks(range, true);
18602 let mut lines = Vec::new();
18603 let mut line: VecDeque<Chunk> = VecDeque::new();
18604
18605 let Some(style) = self.style.as_ref() else {
18606 return;
18607 };
18608
18609 for chunk in chunks {
18610 let highlight = chunk
18611 .syntax_highlight_id
18612 .and_then(|id| id.name(&style.syntax));
18613 let mut chunk_lines = chunk.text.split('\n').peekable();
18614 while let Some(text) = chunk_lines.next() {
18615 let mut merged_with_last_token = false;
18616 if let Some(last_token) = line.back_mut() {
18617 if last_token.highlight == highlight {
18618 last_token.text.push_str(text);
18619 merged_with_last_token = true;
18620 }
18621 }
18622
18623 if !merged_with_last_token {
18624 line.push_back(Chunk {
18625 text: text.into(),
18626 highlight,
18627 });
18628 }
18629
18630 if chunk_lines.peek().is_some() {
18631 if line.len() > 1 && line.front().unwrap().text.is_empty() {
18632 line.pop_front();
18633 }
18634 if line.len() > 1 && line.back().unwrap().text.is_empty() {
18635 line.pop_back();
18636 }
18637
18638 lines.push(mem::take(&mut line));
18639 }
18640 }
18641 }
18642
18643 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
18644 return;
18645 };
18646 cx.write_to_clipboard(ClipboardItem::new_string(lines));
18647 }
18648
18649 pub fn open_context_menu(
18650 &mut self,
18651 _: &OpenContextMenu,
18652 window: &mut Window,
18653 cx: &mut Context<Self>,
18654 ) {
18655 self.request_autoscroll(Autoscroll::newest(), cx);
18656 let position = self.selections.newest_display(cx).start;
18657 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
18658 }
18659
18660 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
18661 &self.inlay_hint_cache
18662 }
18663
18664 pub fn replay_insert_event(
18665 &mut self,
18666 text: &str,
18667 relative_utf16_range: Option<Range<isize>>,
18668 window: &mut Window,
18669 cx: &mut Context<Self>,
18670 ) {
18671 if !self.input_enabled {
18672 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18673 return;
18674 }
18675 if let Some(relative_utf16_range) = relative_utf16_range {
18676 let selections = self.selections.all::<OffsetUtf16>(cx);
18677 self.change_selections(None, window, cx, |s| {
18678 let new_ranges = selections.into_iter().map(|range| {
18679 let start = OffsetUtf16(
18680 range
18681 .head()
18682 .0
18683 .saturating_add_signed(relative_utf16_range.start),
18684 );
18685 let end = OffsetUtf16(
18686 range
18687 .head()
18688 .0
18689 .saturating_add_signed(relative_utf16_range.end),
18690 );
18691 start..end
18692 });
18693 s.select_ranges(new_ranges);
18694 });
18695 }
18696
18697 self.handle_input(text, window, cx);
18698 }
18699
18700 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
18701 let Some(provider) = self.semantics_provider.as_ref() else {
18702 return false;
18703 };
18704
18705 let mut supports = false;
18706 self.buffer().update(cx, |this, cx| {
18707 this.for_each_buffer(|buffer| {
18708 supports |= provider.supports_inlay_hints(buffer, cx);
18709 });
18710 });
18711
18712 supports
18713 }
18714
18715 pub fn is_focused(&self, window: &Window) -> bool {
18716 self.focus_handle.is_focused(window)
18717 }
18718
18719 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18720 cx.emit(EditorEvent::Focused);
18721
18722 if let Some(descendant) = self
18723 .last_focused_descendant
18724 .take()
18725 .and_then(|descendant| descendant.upgrade())
18726 {
18727 window.focus(&descendant);
18728 } else {
18729 if let Some(blame) = self.blame.as_ref() {
18730 blame.update(cx, GitBlame::focus)
18731 }
18732
18733 self.blink_manager.update(cx, BlinkManager::enable);
18734 self.show_cursor_names(window, cx);
18735 self.buffer.update(cx, |buffer, cx| {
18736 buffer.finalize_last_transaction(cx);
18737 if self.leader_id.is_none() {
18738 buffer.set_active_selections(
18739 &self.selections.disjoint_anchors(),
18740 self.selections.line_mode,
18741 self.cursor_shape,
18742 cx,
18743 );
18744 }
18745 });
18746 }
18747 }
18748
18749 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18750 cx.emit(EditorEvent::FocusedIn)
18751 }
18752
18753 fn handle_focus_out(
18754 &mut self,
18755 event: FocusOutEvent,
18756 _window: &mut Window,
18757 cx: &mut Context<Self>,
18758 ) {
18759 if event.blurred != self.focus_handle {
18760 self.last_focused_descendant = Some(event.blurred);
18761 }
18762 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
18763 }
18764
18765 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18766 self.blink_manager.update(cx, BlinkManager::disable);
18767 self.buffer
18768 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
18769
18770 if let Some(blame) = self.blame.as_ref() {
18771 blame.update(cx, GitBlame::blur)
18772 }
18773 if !self.hover_state.focused(window, cx) {
18774 hide_hover(self, cx);
18775 }
18776 if !self
18777 .context_menu
18778 .borrow()
18779 .as_ref()
18780 .is_some_and(|context_menu| context_menu.focused(window, cx))
18781 {
18782 self.hide_context_menu(window, cx);
18783 }
18784 self.discard_inline_completion(false, cx);
18785 cx.emit(EditorEvent::Blurred);
18786 cx.notify();
18787 }
18788
18789 pub fn register_action<A: Action>(
18790 &mut self,
18791 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
18792 ) -> Subscription {
18793 let id = self.next_editor_action_id.post_inc();
18794 let listener = Arc::new(listener);
18795 self.editor_actions.borrow_mut().insert(
18796 id,
18797 Box::new(move |window, _| {
18798 let listener = listener.clone();
18799 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
18800 let action = action.downcast_ref().unwrap();
18801 if phase == DispatchPhase::Bubble {
18802 listener(action, window, cx)
18803 }
18804 })
18805 }),
18806 );
18807
18808 let editor_actions = self.editor_actions.clone();
18809 Subscription::new(move || {
18810 editor_actions.borrow_mut().remove(&id);
18811 })
18812 }
18813
18814 pub fn file_header_size(&self) -> u32 {
18815 FILE_HEADER_HEIGHT
18816 }
18817
18818 pub fn restore(
18819 &mut self,
18820 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
18821 window: &mut Window,
18822 cx: &mut Context<Self>,
18823 ) {
18824 let workspace = self.workspace();
18825 let project = self.project.as_ref();
18826 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
18827 let mut tasks = Vec::new();
18828 for (buffer_id, changes) in revert_changes {
18829 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
18830 buffer.update(cx, |buffer, cx| {
18831 buffer.edit(
18832 changes
18833 .into_iter()
18834 .map(|(range, text)| (range, text.to_string())),
18835 None,
18836 cx,
18837 );
18838 });
18839
18840 if let Some(project) =
18841 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
18842 {
18843 project.update(cx, |project, cx| {
18844 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
18845 })
18846 }
18847 }
18848 }
18849 tasks
18850 });
18851 cx.spawn_in(window, async move |_, cx| {
18852 for (buffer, task) in save_tasks {
18853 let result = task.await;
18854 if result.is_err() {
18855 let Some(path) = buffer
18856 .read_with(cx, |buffer, cx| buffer.project_path(cx))
18857 .ok()
18858 else {
18859 continue;
18860 };
18861 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
18862 let Some(task) = cx
18863 .update_window_entity(&workspace, |workspace, window, cx| {
18864 workspace
18865 .open_path_preview(path, None, false, false, false, window, cx)
18866 })
18867 .ok()
18868 else {
18869 continue;
18870 };
18871 task.await.log_err();
18872 }
18873 }
18874 }
18875 })
18876 .detach();
18877 self.change_selections(None, window, cx, |selections| selections.refresh());
18878 }
18879
18880 pub fn to_pixel_point(
18881 &self,
18882 source: multi_buffer::Anchor,
18883 editor_snapshot: &EditorSnapshot,
18884 window: &mut Window,
18885 ) -> Option<gpui::Point<Pixels>> {
18886 let source_point = source.to_display_point(editor_snapshot);
18887 self.display_to_pixel_point(source_point, editor_snapshot, window)
18888 }
18889
18890 pub fn display_to_pixel_point(
18891 &self,
18892 source: DisplayPoint,
18893 editor_snapshot: &EditorSnapshot,
18894 window: &mut Window,
18895 ) -> Option<gpui::Point<Pixels>> {
18896 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
18897 let text_layout_details = self.text_layout_details(window);
18898 let scroll_top = text_layout_details
18899 .scroll_anchor
18900 .scroll_position(editor_snapshot)
18901 .y;
18902
18903 if source.row().as_f32() < scroll_top.floor() {
18904 return None;
18905 }
18906 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
18907 let source_y = line_height * (source.row().as_f32() - scroll_top);
18908 Some(gpui::Point::new(source_x, source_y))
18909 }
18910
18911 pub fn has_visible_completions_menu(&self) -> bool {
18912 !self.edit_prediction_preview_is_active()
18913 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
18914 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
18915 })
18916 }
18917
18918 pub fn register_addon<T: Addon>(&mut self, instance: T) {
18919 if self.mode.is_minimap() {
18920 return;
18921 }
18922 self.addons
18923 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
18924 }
18925
18926 pub fn unregister_addon<T: Addon>(&mut self) {
18927 self.addons.remove(&std::any::TypeId::of::<T>());
18928 }
18929
18930 pub fn addon<T: Addon>(&self) -> Option<&T> {
18931 let type_id = std::any::TypeId::of::<T>();
18932 self.addons
18933 .get(&type_id)
18934 .and_then(|item| item.to_any().downcast_ref::<T>())
18935 }
18936
18937 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
18938 let type_id = std::any::TypeId::of::<T>();
18939 self.addons
18940 .get_mut(&type_id)
18941 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
18942 }
18943
18944 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
18945 let text_layout_details = self.text_layout_details(window);
18946 let style = &text_layout_details.editor_style;
18947 let font_id = window.text_system().resolve_font(&style.text.font());
18948 let font_size = style.text.font_size.to_pixels(window.rem_size());
18949 let line_height = style.text.line_height_in_pixels(window.rem_size());
18950 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
18951
18952 gpui::Size::new(em_width, line_height)
18953 }
18954
18955 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
18956 self.load_diff_task.clone()
18957 }
18958
18959 fn read_metadata_from_db(
18960 &mut self,
18961 item_id: u64,
18962 workspace_id: WorkspaceId,
18963 window: &mut Window,
18964 cx: &mut Context<Editor>,
18965 ) {
18966 if self.is_singleton(cx)
18967 && !self.mode.is_minimap()
18968 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
18969 {
18970 let buffer_snapshot = OnceCell::new();
18971
18972 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
18973 if !folds.is_empty() {
18974 let snapshot =
18975 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18976 self.fold_ranges(
18977 folds
18978 .into_iter()
18979 .map(|(start, end)| {
18980 snapshot.clip_offset(start, Bias::Left)
18981 ..snapshot.clip_offset(end, Bias::Right)
18982 })
18983 .collect(),
18984 false,
18985 window,
18986 cx,
18987 );
18988 }
18989 }
18990
18991 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
18992 if !selections.is_empty() {
18993 let snapshot =
18994 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18995 self.change_selections(None, window, cx, |s| {
18996 s.select_ranges(selections.into_iter().map(|(start, end)| {
18997 snapshot.clip_offset(start, Bias::Left)
18998 ..snapshot.clip_offset(end, Bias::Right)
18999 }));
19000 });
19001 }
19002 };
19003 }
19004
19005 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
19006 }
19007}
19008
19009fn vim_enabled(cx: &App) -> bool {
19010 cx.global::<SettingsStore>()
19011 .raw_user_settings()
19012 .get("vim_mode")
19013 == Some(&serde_json::Value::Bool(true))
19014}
19015
19016// Consider user intent and default settings
19017fn choose_completion_range(
19018 completion: &Completion,
19019 intent: CompletionIntent,
19020 buffer: &Entity<Buffer>,
19021 cx: &mut Context<Editor>,
19022) -> Range<usize> {
19023 fn should_replace(
19024 completion: &Completion,
19025 insert_range: &Range<text::Anchor>,
19026 intent: CompletionIntent,
19027 completion_mode_setting: LspInsertMode,
19028 buffer: &Buffer,
19029 ) -> bool {
19030 // specific actions take precedence over settings
19031 match intent {
19032 CompletionIntent::CompleteWithInsert => return false,
19033 CompletionIntent::CompleteWithReplace => return true,
19034 CompletionIntent::Complete | CompletionIntent::Compose => {}
19035 }
19036
19037 match completion_mode_setting {
19038 LspInsertMode::Insert => false,
19039 LspInsertMode::Replace => true,
19040 LspInsertMode::ReplaceSubsequence => {
19041 let mut text_to_replace = buffer.chars_for_range(
19042 buffer.anchor_before(completion.replace_range.start)
19043 ..buffer.anchor_after(completion.replace_range.end),
19044 );
19045 let mut completion_text = completion.new_text.chars();
19046
19047 // is `text_to_replace` a subsequence of `completion_text`
19048 text_to_replace
19049 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
19050 }
19051 LspInsertMode::ReplaceSuffix => {
19052 let range_after_cursor = insert_range.end..completion.replace_range.end;
19053
19054 let text_after_cursor = buffer
19055 .text_for_range(
19056 buffer.anchor_before(range_after_cursor.start)
19057 ..buffer.anchor_after(range_after_cursor.end),
19058 )
19059 .collect::<String>();
19060 completion.new_text.ends_with(&text_after_cursor)
19061 }
19062 }
19063 }
19064
19065 let buffer = buffer.read(cx);
19066
19067 if let CompletionSource::Lsp {
19068 insert_range: Some(insert_range),
19069 ..
19070 } = &completion.source
19071 {
19072 let completion_mode_setting =
19073 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
19074 .completions
19075 .lsp_insert_mode;
19076
19077 if !should_replace(
19078 completion,
19079 &insert_range,
19080 intent,
19081 completion_mode_setting,
19082 buffer,
19083 ) {
19084 return insert_range.to_offset(buffer);
19085 }
19086 }
19087
19088 completion.replace_range.to_offset(buffer)
19089}
19090
19091fn insert_extra_newline_brackets(
19092 buffer: &MultiBufferSnapshot,
19093 range: Range<usize>,
19094 language: &language::LanguageScope,
19095) -> bool {
19096 let leading_whitespace_len = buffer
19097 .reversed_chars_at(range.start)
19098 .take_while(|c| c.is_whitespace() && *c != '\n')
19099 .map(|c| c.len_utf8())
19100 .sum::<usize>();
19101 let trailing_whitespace_len = buffer
19102 .chars_at(range.end)
19103 .take_while(|c| c.is_whitespace() && *c != '\n')
19104 .map(|c| c.len_utf8())
19105 .sum::<usize>();
19106 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
19107
19108 language.brackets().any(|(pair, enabled)| {
19109 let pair_start = pair.start.trim_end();
19110 let pair_end = pair.end.trim_start();
19111
19112 enabled
19113 && pair.newline
19114 && buffer.contains_str_at(range.end, pair_end)
19115 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
19116 })
19117}
19118
19119fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
19120 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
19121 [(buffer, range, _)] => (*buffer, range.clone()),
19122 _ => return false,
19123 };
19124 let pair = {
19125 let mut result: Option<BracketMatch> = None;
19126
19127 for pair in buffer
19128 .all_bracket_ranges(range.clone())
19129 .filter(move |pair| {
19130 pair.open_range.start <= range.start && pair.close_range.end >= range.end
19131 })
19132 {
19133 let len = pair.close_range.end - pair.open_range.start;
19134
19135 if let Some(existing) = &result {
19136 let existing_len = existing.close_range.end - existing.open_range.start;
19137 if len > existing_len {
19138 continue;
19139 }
19140 }
19141
19142 result = Some(pair);
19143 }
19144
19145 result
19146 };
19147 let Some(pair) = pair else {
19148 return false;
19149 };
19150 pair.newline_only
19151 && buffer
19152 .chars_for_range(pair.open_range.end..range.start)
19153 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
19154 .all(|c| c.is_whitespace() && c != '\n')
19155}
19156
19157fn update_uncommitted_diff_for_buffer(
19158 editor: Entity<Editor>,
19159 project: &Entity<Project>,
19160 buffers: impl IntoIterator<Item = Entity<Buffer>>,
19161 buffer: Entity<MultiBuffer>,
19162 cx: &mut App,
19163) -> Task<()> {
19164 let mut tasks = Vec::new();
19165 project.update(cx, |project, cx| {
19166 for buffer in buffers {
19167 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
19168 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
19169 }
19170 }
19171 });
19172 cx.spawn(async move |cx| {
19173 let diffs = future::join_all(tasks).await;
19174 if editor
19175 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19176 .unwrap_or(false)
19177 {
19178 return;
19179 }
19180
19181 buffer
19182 .update(cx, |buffer, cx| {
19183 for diff in diffs.into_iter().flatten() {
19184 buffer.add_diff(diff, cx);
19185 }
19186 })
19187 .ok();
19188 })
19189}
19190
19191fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
19192 let tab_size = tab_size.get() as usize;
19193 let mut width = offset;
19194
19195 for ch in text.chars() {
19196 width += if ch == '\t' {
19197 tab_size - (width % tab_size)
19198 } else {
19199 1
19200 };
19201 }
19202
19203 width - offset
19204}
19205
19206#[cfg(test)]
19207mod tests {
19208 use super::*;
19209
19210 #[test]
19211 fn test_string_size_with_expanded_tabs() {
19212 let nz = |val| NonZeroU32::new(val).unwrap();
19213 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
19214 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
19215 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
19216 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
19217 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
19218 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
19219 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
19220 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
19221 }
19222}
19223
19224/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
19225struct WordBreakingTokenizer<'a> {
19226 input: &'a str,
19227}
19228
19229impl<'a> WordBreakingTokenizer<'a> {
19230 fn new(input: &'a str) -> Self {
19231 Self { input }
19232 }
19233}
19234
19235fn is_char_ideographic(ch: char) -> bool {
19236 use unicode_script::Script::*;
19237 use unicode_script::UnicodeScript;
19238 matches!(ch.script(), Han | Tangut | Yi)
19239}
19240
19241fn is_grapheme_ideographic(text: &str) -> bool {
19242 text.chars().any(is_char_ideographic)
19243}
19244
19245fn is_grapheme_whitespace(text: &str) -> bool {
19246 text.chars().any(|x| x.is_whitespace())
19247}
19248
19249fn should_stay_with_preceding_ideograph(text: &str) -> bool {
19250 text.chars().next().map_or(false, |ch| {
19251 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
19252 })
19253}
19254
19255#[derive(PartialEq, Eq, Debug, Clone, Copy)]
19256enum WordBreakToken<'a> {
19257 Word { token: &'a str, grapheme_len: usize },
19258 InlineWhitespace { token: &'a str, grapheme_len: usize },
19259 Newline,
19260}
19261
19262impl<'a> Iterator for WordBreakingTokenizer<'a> {
19263 /// Yields a span, the count of graphemes in the token, and whether it was
19264 /// whitespace. Note that it also breaks at word boundaries.
19265 type Item = WordBreakToken<'a>;
19266
19267 fn next(&mut self) -> Option<Self::Item> {
19268 use unicode_segmentation::UnicodeSegmentation;
19269 if self.input.is_empty() {
19270 return None;
19271 }
19272
19273 let mut iter = self.input.graphemes(true).peekable();
19274 let mut offset = 0;
19275 let mut grapheme_len = 0;
19276 if let Some(first_grapheme) = iter.next() {
19277 let is_newline = first_grapheme == "\n";
19278 let is_whitespace = is_grapheme_whitespace(first_grapheme);
19279 offset += first_grapheme.len();
19280 grapheme_len += 1;
19281 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
19282 if let Some(grapheme) = iter.peek().copied() {
19283 if should_stay_with_preceding_ideograph(grapheme) {
19284 offset += grapheme.len();
19285 grapheme_len += 1;
19286 }
19287 }
19288 } else {
19289 let mut words = self.input[offset..].split_word_bound_indices().peekable();
19290 let mut next_word_bound = words.peek().copied();
19291 if next_word_bound.map_or(false, |(i, _)| i == 0) {
19292 next_word_bound = words.next();
19293 }
19294 while let Some(grapheme) = iter.peek().copied() {
19295 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19296 break;
19297 };
19298 if is_grapheme_whitespace(grapheme) != is_whitespace
19299 || (grapheme == "\n") != is_newline
19300 {
19301 break;
19302 };
19303 offset += grapheme.len();
19304 grapheme_len += 1;
19305 iter.next();
19306 }
19307 }
19308 let token = &self.input[..offset];
19309 self.input = &self.input[offset..];
19310 if token == "\n" {
19311 Some(WordBreakToken::Newline)
19312 } else if is_whitespace {
19313 Some(WordBreakToken::InlineWhitespace {
19314 token,
19315 grapheme_len,
19316 })
19317 } else {
19318 Some(WordBreakToken::Word {
19319 token,
19320 grapheme_len,
19321 })
19322 }
19323 } else {
19324 None
19325 }
19326 }
19327}
19328
19329#[test]
19330fn test_word_breaking_tokenizer() {
19331 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
19332 ("", &[]),
19333 (" ", &[whitespace(" ", 2)]),
19334 ("Ʒ", &[word("Ʒ", 1)]),
19335 ("Ǽ", &[word("Ǽ", 1)]),
19336 ("⋑", &[word("⋑", 1)]),
19337 ("⋑⋑", &[word("⋑⋑", 2)]),
19338 (
19339 "原理,进而",
19340 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
19341 ),
19342 (
19343 "hello world",
19344 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
19345 ),
19346 (
19347 "hello, world",
19348 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
19349 ),
19350 (
19351 " hello world",
19352 &[
19353 whitespace(" ", 2),
19354 word("hello", 5),
19355 whitespace(" ", 1),
19356 word("world", 5),
19357 ],
19358 ),
19359 (
19360 "这是什么 \n 钢笔",
19361 &[
19362 word("这", 1),
19363 word("是", 1),
19364 word("什", 1),
19365 word("么", 1),
19366 whitespace(" ", 1),
19367 newline(),
19368 whitespace(" ", 1),
19369 word("钢", 1),
19370 word("笔", 1),
19371 ],
19372 ),
19373 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
19374 ];
19375
19376 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19377 WordBreakToken::Word {
19378 token,
19379 grapheme_len,
19380 }
19381 }
19382
19383 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19384 WordBreakToken::InlineWhitespace {
19385 token,
19386 grapheme_len,
19387 }
19388 }
19389
19390 fn newline() -> WordBreakToken<'static> {
19391 WordBreakToken::Newline
19392 }
19393
19394 for (input, result) in tests {
19395 assert_eq!(
19396 WordBreakingTokenizer::new(input)
19397 .collect::<Vec<_>>()
19398 .as_slice(),
19399 *result,
19400 );
19401 }
19402}
19403
19404fn wrap_with_prefix(
19405 line_prefix: String,
19406 unwrapped_text: String,
19407 wrap_column: usize,
19408 tab_size: NonZeroU32,
19409 preserve_existing_whitespace: bool,
19410) -> String {
19411 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
19412 let mut wrapped_text = String::new();
19413 let mut current_line = line_prefix.clone();
19414
19415 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
19416 let mut current_line_len = line_prefix_len;
19417 let mut in_whitespace = false;
19418 for token in tokenizer {
19419 let have_preceding_whitespace = in_whitespace;
19420 match token {
19421 WordBreakToken::Word {
19422 token,
19423 grapheme_len,
19424 } => {
19425 in_whitespace = false;
19426 if current_line_len + grapheme_len > wrap_column
19427 && current_line_len != line_prefix_len
19428 {
19429 wrapped_text.push_str(current_line.trim_end());
19430 wrapped_text.push('\n');
19431 current_line.truncate(line_prefix.len());
19432 current_line_len = line_prefix_len;
19433 }
19434 current_line.push_str(token);
19435 current_line_len += grapheme_len;
19436 }
19437 WordBreakToken::InlineWhitespace {
19438 mut token,
19439 mut grapheme_len,
19440 } => {
19441 in_whitespace = true;
19442 if have_preceding_whitespace && !preserve_existing_whitespace {
19443 continue;
19444 }
19445 if !preserve_existing_whitespace {
19446 token = " ";
19447 grapheme_len = 1;
19448 }
19449 if current_line_len + grapheme_len > wrap_column {
19450 wrapped_text.push_str(current_line.trim_end());
19451 wrapped_text.push('\n');
19452 current_line.truncate(line_prefix.len());
19453 current_line_len = line_prefix_len;
19454 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
19455 current_line.push_str(token);
19456 current_line_len += grapheme_len;
19457 }
19458 }
19459 WordBreakToken::Newline => {
19460 in_whitespace = true;
19461 if preserve_existing_whitespace {
19462 wrapped_text.push_str(current_line.trim_end());
19463 wrapped_text.push('\n');
19464 current_line.truncate(line_prefix.len());
19465 current_line_len = line_prefix_len;
19466 } else if have_preceding_whitespace {
19467 continue;
19468 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
19469 {
19470 wrapped_text.push_str(current_line.trim_end());
19471 wrapped_text.push('\n');
19472 current_line.truncate(line_prefix.len());
19473 current_line_len = line_prefix_len;
19474 } else if current_line_len != line_prefix_len {
19475 current_line.push(' ');
19476 current_line_len += 1;
19477 }
19478 }
19479 }
19480 }
19481
19482 if !current_line.is_empty() {
19483 wrapped_text.push_str(¤t_line);
19484 }
19485 wrapped_text
19486}
19487
19488#[test]
19489fn test_wrap_with_prefix() {
19490 assert_eq!(
19491 wrap_with_prefix(
19492 "# ".to_string(),
19493 "abcdefg".to_string(),
19494 4,
19495 NonZeroU32::new(4).unwrap(),
19496 false,
19497 ),
19498 "# abcdefg"
19499 );
19500 assert_eq!(
19501 wrap_with_prefix(
19502 "".to_string(),
19503 "\thello world".to_string(),
19504 8,
19505 NonZeroU32::new(4).unwrap(),
19506 false,
19507 ),
19508 "hello\nworld"
19509 );
19510 assert_eq!(
19511 wrap_with_prefix(
19512 "// ".to_string(),
19513 "xx \nyy zz aa bb cc".to_string(),
19514 12,
19515 NonZeroU32::new(4).unwrap(),
19516 false,
19517 ),
19518 "// xx yy zz\n// aa bb cc"
19519 );
19520 assert_eq!(
19521 wrap_with_prefix(
19522 String::new(),
19523 "这是什么 \n 钢笔".to_string(),
19524 3,
19525 NonZeroU32::new(4).unwrap(),
19526 false,
19527 ),
19528 "这是什\n么 钢\n笔"
19529 );
19530}
19531
19532pub trait CollaborationHub {
19533 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
19534 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
19535 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
19536}
19537
19538impl CollaborationHub for Entity<Project> {
19539 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
19540 self.read(cx).collaborators()
19541 }
19542
19543 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
19544 self.read(cx).user_store().read(cx).participant_indices()
19545 }
19546
19547 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
19548 let this = self.read(cx);
19549 let user_ids = this.collaborators().values().map(|c| c.user_id);
19550 this.user_store().read_with(cx, |user_store, cx| {
19551 user_store.participant_names(user_ids, cx)
19552 })
19553 }
19554}
19555
19556pub trait SemanticsProvider {
19557 fn hover(
19558 &self,
19559 buffer: &Entity<Buffer>,
19560 position: text::Anchor,
19561 cx: &mut App,
19562 ) -> Option<Task<Vec<project::Hover>>>;
19563
19564 fn inline_values(
19565 &self,
19566 buffer_handle: Entity<Buffer>,
19567 range: Range<text::Anchor>,
19568 cx: &mut App,
19569 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19570
19571 fn inlay_hints(
19572 &self,
19573 buffer_handle: Entity<Buffer>,
19574 range: Range<text::Anchor>,
19575 cx: &mut App,
19576 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19577
19578 fn resolve_inlay_hint(
19579 &self,
19580 hint: InlayHint,
19581 buffer_handle: Entity<Buffer>,
19582 server_id: LanguageServerId,
19583 cx: &mut App,
19584 ) -> Option<Task<anyhow::Result<InlayHint>>>;
19585
19586 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
19587
19588 fn document_highlights(
19589 &self,
19590 buffer: &Entity<Buffer>,
19591 position: text::Anchor,
19592 cx: &mut App,
19593 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
19594
19595 fn definitions(
19596 &self,
19597 buffer: &Entity<Buffer>,
19598 position: text::Anchor,
19599 kind: GotoDefinitionKind,
19600 cx: &mut App,
19601 ) -> Option<Task<Result<Vec<LocationLink>>>>;
19602
19603 fn range_for_rename(
19604 &self,
19605 buffer: &Entity<Buffer>,
19606 position: text::Anchor,
19607 cx: &mut App,
19608 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
19609
19610 fn perform_rename(
19611 &self,
19612 buffer: &Entity<Buffer>,
19613 position: text::Anchor,
19614 new_name: String,
19615 cx: &mut App,
19616 ) -> Option<Task<Result<ProjectTransaction>>>;
19617}
19618
19619pub trait CompletionProvider {
19620 fn completions(
19621 &self,
19622 excerpt_id: ExcerptId,
19623 buffer: &Entity<Buffer>,
19624 buffer_position: text::Anchor,
19625 trigger: CompletionContext,
19626 window: &mut Window,
19627 cx: &mut Context<Editor>,
19628 ) -> Task<Result<Option<Vec<Completion>>>>;
19629
19630 fn resolve_completions(
19631 &self,
19632 buffer: Entity<Buffer>,
19633 completion_indices: Vec<usize>,
19634 completions: Rc<RefCell<Box<[Completion]>>>,
19635 cx: &mut Context<Editor>,
19636 ) -> Task<Result<bool>>;
19637
19638 fn apply_additional_edits_for_completion(
19639 &self,
19640 _buffer: Entity<Buffer>,
19641 _completions: Rc<RefCell<Box<[Completion]>>>,
19642 _completion_index: usize,
19643 _push_to_history: bool,
19644 _cx: &mut Context<Editor>,
19645 ) -> Task<Result<Option<language::Transaction>>> {
19646 Task::ready(Ok(None))
19647 }
19648
19649 fn is_completion_trigger(
19650 &self,
19651 buffer: &Entity<Buffer>,
19652 position: language::Anchor,
19653 text: &str,
19654 trigger_in_words: bool,
19655 cx: &mut Context<Editor>,
19656 ) -> bool;
19657
19658 fn sort_completions(&self) -> bool {
19659 true
19660 }
19661
19662 fn filter_completions(&self) -> bool {
19663 true
19664 }
19665}
19666
19667pub trait CodeActionProvider {
19668 fn id(&self) -> Arc<str>;
19669
19670 fn code_actions(
19671 &self,
19672 buffer: &Entity<Buffer>,
19673 range: Range<text::Anchor>,
19674 window: &mut Window,
19675 cx: &mut App,
19676 ) -> Task<Result<Vec<CodeAction>>>;
19677
19678 fn apply_code_action(
19679 &self,
19680 buffer_handle: Entity<Buffer>,
19681 action: CodeAction,
19682 excerpt_id: ExcerptId,
19683 push_to_history: bool,
19684 window: &mut Window,
19685 cx: &mut App,
19686 ) -> Task<Result<ProjectTransaction>>;
19687}
19688
19689impl CodeActionProvider for Entity<Project> {
19690 fn id(&self) -> Arc<str> {
19691 "project".into()
19692 }
19693
19694 fn code_actions(
19695 &self,
19696 buffer: &Entity<Buffer>,
19697 range: Range<text::Anchor>,
19698 _window: &mut Window,
19699 cx: &mut App,
19700 ) -> Task<Result<Vec<CodeAction>>> {
19701 self.update(cx, |project, cx| {
19702 let code_lens = project.code_lens(buffer, range.clone(), cx);
19703 let code_actions = project.code_actions(buffer, range, None, cx);
19704 cx.background_spawn(async move {
19705 let (code_lens, code_actions) = join(code_lens, code_actions).await;
19706 Ok(code_lens
19707 .context("code lens fetch")?
19708 .into_iter()
19709 .chain(code_actions.context("code action fetch")?)
19710 .collect())
19711 })
19712 })
19713 }
19714
19715 fn apply_code_action(
19716 &self,
19717 buffer_handle: Entity<Buffer>,
19718 action: CodeAction,
19719 _excerpt_id: ExcerptId,
19720 push_to_history: bool,
19721 _window: &mut Window,
19722 cx: &mut App,
19723 ) -> Task<Result<ProjectTransaction>> {
19724 self.update(cx, |project, cx| {
19725 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19726 })
19727 }
19728}
19729
19730fn snippet_completions(
19731 project: &Project,
19732 buffer: &Entity<Buffer>,
19733 buffer_position: text::Anchor,
19734 cx: &mut App,
19735) -> Task<Result<Vec<Completion>>> {
19736 let languages = buffer.read(cx).languages_at(buffer_position);
19737 let snippet_store = project.snippets().read(cx);
19738
19739 let scopes: Vec<_> = languages
19740 .iter()
19741 .filter_map(|language| {
19742 let language_name = language.lsp_id();
19743 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19744
19745 if snippets.is_empty() {
19746 None
19747 } else {
19748 Some((language.default_scope(), snippets))
19749 }
19750 })
19751 .collect();
19752
19753 if scopes.is_empty() {
19754 return Task::ready(Ok(vec![]));
19755 }
19756
19757 let snapshot = buffer.read(cx).text_snapshot();
19758 let chars: String = snapshot
19759 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19760 .collect();
19761 let executor = cx.background_executor().clone();
19762
19763 cx.background_spawn(async move {
19764 let mut all_results: Vec<Completion> = Vec::new();
19765 for (scope, snippets) in scopes.into_iter() {
19766 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19767 let mut last_word = chars
19768 .chars()
19769 .take_while(|c| classifier.is_word(*c))
19770 .collect::<String>();
19771 last_word = last_word.chars().rev().collect();
19772
19773 if last_word.is_empty() {
19774 return Ok(vec![]);
19775 }
19776
19777 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
19778 let to_lsp = |point: &text::Anchor| {
19779 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
19780 point_to_lsp(end)
19781 };
19782 let lsp_end = to_lsp(&buffer_position);
19783
19784 let candidates = snippets
19785 .iter()
19786 .enumerate()
19787 .flat_map(|(ix, snippet)| {
19788 snippet
19789 .prefix
19790 .iter()
19791 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
19792 })
19793 .collect::<Vec<StringMatchCandidate>>();
19794
19795 let mut matches = fuzzy::match_strings(
19796 &candidates,
19797 &last_word,
19798 last_word.chars().any(|c| c.is_uppercase()),
19799 100,
19800 &Default::default(),
19801 executor.clone(),
19802 )
19803 .await;
19804
19805 // Remove all candidates where the query's start does not match the start of any word in the candidate
19806 if let Some(query_start) = last_word.chars().next() {
19807 matches.retain(|string_match| {
19808 split_words(&string_match.string).any(|word| {
19809 // Check that the first codepoint of the word as lowercase matches the first
19810 // codepoint of the query as lowercase
19811 word.chars()
19812 .flat_map(|codepoint| codepoint.to_lowercase())
19813 .zip(query_start.to_lowercase())
19814 .all(|(word_cp, query_cp)| word_cp == query_cp)
19815 })
19816 });
19817 }
19818
19819 let matched_strings = matches
19820 .into_iter()
19821 .map(|m| m.string)
19822 .collect::<HashSet<_>>();
19823
19824 let mut result: Vec<Completion> = snippets
19825 .iter()
19826 .filter_map(|snippet| {
19827 let matching_prefix = snippet
19828 .prefix
19829 .iter()
19830 .find(|prefix| matched_strings.contains(*prefix))?;
19831 let start = as_offset - last_word.len();
19832 let start = snapshot.anchor_before(start);
19833 let range = start..buffer_position;
19834 let lsp_start = to_lsp(&start);
19835 let lsp_range = lsp::Range {
19836 start: lsp_start,
19837 end: lsp_end,
19838 };
19839 Some(Completion {
19840 replace_range: range,
19841 new_text: snippet.body.clone(),
19842 source: CompletionSource::Lsp {
19843 insert_range: None,
19844 server_id: LanguageServerId(usize::MAX),
19845 resolved: true,
19846 lsp_completion: Box::new(lsp::CompletionItem {
19847 label: snippet.prefix.first().unwrap().clone(),
19848 kind: Some(CompletionItemKind::SNIPPET),
19849 label_details: snippet.description.as_ref().map(|description| {
19850 lsp::CompletionItemLabelDetails {
19851 detail: Some(description.clone()),
19852 description: None,
19853 }
19854 }),
19855 insert_text_format: Some(InsertTextFormat::SNIPPET),
19856 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19857 lsp::InsertReplaceEdit {
19858 new_text: snippet.body.clone(),
19859 insert: lsp_range,
19860 replace: lsp_range,
19861 },
19862 )),
19863 filter_text: Some(snippet.body.clone()),
19864 sort_text: Some(char::MAX.to_string()),
19865 ..lsp::CompletionItem::default()
19866 }),
19867 lsp_defaults: None,
19868 },
19869 label: CodeLabel {
19870 text: matching_prefix.clone(),
19871 runs: Vec::new(),
19872 filter_range: 0..matching_prefix.len(),
19873 },
19874 icon_path: None,
19875 documentation: Some(
19876 CompletionDocumentation::SingleLineAndMultiLinePlainText {
19877 single_line: snippet.name.clone().into(),
19878 plain_text: snippet
19879 .description
19880 .clone()
19881 .map(|description| description.into()),
19882 },
19883 ),
19884 insert_text_mode: None,
19885 confirm: None,
19886 })
19887 })
19888 .collect();
19889
19890 all_results.append(&mut result);
19891 }
19892
19893 Ok(all_results)
19894 })
19895}
19896
19897impl CompletionProvider for Entity<Project> {
19898 fn completions(
19899 &self,
19900 _excerpt_id: ExcerptId,
19901 buffer: &Entity<Buffer>,
19902 buffer_position: text::Anchor,
19903 options: CompletionContext,
19904 _window: &mut Window,
19905 cx: &mut Context<Editor>,
19906 ) -> Task<Result<Option<Vec<Completion>>>> {
19907 self.update(cx, |project, cx| {
19908 let snippets = snippet_completions(project, buffer, buffer_position, cx);
19909 let project_completions = project.completions(buffer, buffer_position, options, cx);
19910 cx.background_spawn(async move {
19911 let snippets_completions = snippets.await?;
19912 match project_completions.await? {
19913 Some(mut completions) => {
19914 completions.extend(snippets_completions);
19915 Ok(Some(completions))
19916 }
19917 None => {
19918 if snippets_completions.is_empty() {
19919 Ok(None)
19920 } else {
19921 Ok(Some(snippets_completions))
19922 }
19923 }
19924 }
19925 })
19926 })
19927 }
19928
19929 fn resolve_completions(
19930 &self,
19931 buffer: Entity<Buffer>,
19932 completion_indices: Vec<usize>,
19933 completions: Rc<RefCell<Box<[Completion]>>>,
19934 cx: &mut Context<Editor>,
19935 ) -> Task<Result<bool>> {
19936 self.update(cx, |project, cx| {
19937 project.lsp_store().update(cx, |lsp_store, cx| {
19938 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
19939 })
19940 })
19941 }
19942
19943 fn apply_additional_edits_for_completion(
19944 &self,
19945 buffer: Entity<Buffer>,
19946 completions: Rc<RefCell<Box<[Completion]>>>,
19947 completion_index: usize,
19948 push_to_history: bool,
19949 cx: &mut Context<Editor>,
19950 ) -> Task<Result<Option<language::Transaction>>> {
19951 self.update(cx, |project, cx| {
19952 project.lsp_store().update(cx, |lsp_store, cx| {
19953 lsp_store.apply_additional_edits_for_completion(
19954 buffer,
19955 completions,
19956 completion_index,
19957 push_to_history,
19958 cx,
19959 )
19960 })
19961 })
19962 }
19963
19964 fn is_completion_trigger(
19965 &self,
19966 buffer: &Entity<Buffer>,
19967 position: language::Anchor,
19968 text: &str,
19969 trigger_in_words: bool,
19970 cx: &mut Context<Editor>,
19971 ) -> bool {
19972 let mut chars = text.chars();
19973 let char = if let Some(char) = chars.next() {
19974 char
19975 } else {
19976 return false;
19977 };
19978 if chars.next().is_some() {
19979 return false;
19980 }
19981
19982 let buffer = buffer.read(cx);
19983 let snapshot = buffer.snapshot();
19984 if !snapshot.settings_at(position, cx).show_completions_on_input {
19985 return false;
19986 }
19987 let classifier = snapshot.char_classifier_at(position).for_completion(true);
19988 if trigger_in_words && classifier.is_word(char) {
19989 return true;
19990 }
19991
19992 buffer.completion_triggers().contains(text)
19993 }
19994}
19995
19996impl SemanticsProvider for Entity<Project> {
19997 fn hover(
19998 &self,
19999 buffer: &Entity<Buffer>,
20000 position: text::Anchor,
20001 cx: &mut App,
20002 ) -> Option<Task<Vec<project::Hover>>> {
20003 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
20004 }
20005
20006 fn document_highlights(
20007 &self,
20008 buffer: &Entity<Buffer>,
20009 position: text::Anchor,
20010 cx: &mut App,
20011 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
20012 Some(self.update(cx, |project, cx| {
20013 project.document_highlights(buffer, position, cx)
20014 }))
20015 }
20016
20017 fn definitions(
20018 &self,
20019 buffer: &Entity<Buffer>,
20020 position: text::Anchor,
20021 kind: GotoDefinitionKind,
20022 cx: &mut App,
20023 ) -> Option<Task<Result<Vec<LocationLink>>>> {
20024 Some(self.update(cx, |project, cx| match kind {
20025 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
20026 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
20027 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
20028 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
20029 }))
20030 }
20031
20032 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
20033 // TODO: make this work for remote projects
20034 self.update(cx, |project, cx| {
20035 if project
20036 .active_debug_session(cx)
20037 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
20038 {
20039 return true;
20040 }
20041
20042 buffer.update(cx, |buffer, cx| {
20043 project.any_language_server_supports_inlay_hints(buffer, cx)
20044 })
20045 })
20046 }
20047
20048 fn inline_values(
20049 &self,
20050 buffer_handle: Entity<Buffer>,
20051 range: Range<text::Anchor>,
20052 cx: &mut App,
20053 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20054 self.update(cx, |project, cx| {
20055 let (session, active_stack_frame) = project.active_debug_session(cx)?;
20056
20057 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
20058 })
20059 }
20060
20061 fn inlay_hints(
20062 &self,
20063 buffer_handle: Entity<Buffer>,
20064 range: Range<text::Anchor>,
20065 cx: &mut App,
20066 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20067 Some(self.update(cx, |project, cx| {
20068 project.inlay_hints(buffer_handle, range, cx)
20069 }))
20070 }
20071
20072 fn resolve_inlay_hint(
20073 &self,
20074 hint: InlayHint,
20075 buffer_handle: Entity<Buffer>,
20076 server_id: LanguageServerId,
20077 cx: &mut App,
20078 ) -> Option<Task<anyhow::Result<InlayHint>>> {
20079 Some(self.update(cx, |project, cx| {
20080 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
20081 }))
20082 }
20083
20084 fn range_for_rename(
20085 &self,
20086 buffer: &Entity<Buffer>,
20087 position: text::Anchor,
20088 cx: &mut App,
20089 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
20090 Some(self.update(cx, |project, cx| {
20091 let buffer = buffer.clone();
20092 let task = project.prepare_rename(buffer.clone(), position, cx);
20093 cx.spawn(async move |_, cx| {
20094 Ok(match task.await? {
20095 PrepareRenameResponse::Success(range) => Some(range),
20096 PrepareRenameResponse::InvalidPosition => None,
20097 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
20098 // Fallback on using TreeSitter info to determine identifier range
20099 buffer.update(cx, |buffer, _| {
20100 let snapshot = buffer.snapshot();
20101 let (range, kind) = snapshot.surrounding_word(position);
20102 if kind != Some(CharKind::Word) {
20103 return None;
20104 }
20105 Some(
20106 snapshot.anchor_before(range.start)
20107 ..snapshot.anchor_after(range.end),
20108 )
20109 })?
20110 }
20111 })
20112 })
20113 }))
20114 }
20115
20116 fn perform_rename(
20117 &self,
20118 buffer: &Entity<Buffer>,
20119 position: text::Anchor,
20120 new_name: String,
20121 cx: &mut App,
20122 ) -> Option<Task<Result<ProjectTransaction>>> {
20123 Some(self.update(cx, |project, cx| {
20124 project.perform_rename(buffer.clone(), position, new_name, cx)
20125 }))
20126 }
20127}
20128
20129fn inlay_hint_settings(
20130 location: Anchor,
20131 snapshot: &MultiBufferSnapshot,
20132 cx: &mut Context<Editor>,
20133) -> InlayHintSettings {
20134 let file = snapshot.file_at(location);
20135 let language = snapshot.language_at(location).map(|l| l.name());
20136 language_settings(language, file, cx).inlay_hints
20137}
20138
20139fn consume_contiguous_rows(
20140 contiguous_row_selections: &mut Vec<Selection<Point>>,
20141 selection: &Selection<Point>,
20142 display_map: &DisplaySnapshot,
20143 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
20144) -> (MultiBufferRow, MultiBufferRow) {
20145 contiguous_row_selections.push(selection.clone());
20146 let start_row = MultiBufferRow(selection.start.row);
20147 let mut end_row = ending_row(selection, display_map);
20148
20149 while let Some(next_selection) = selections.peek() {
20150 if next_selection.start.row <= end_row.0 {
20151 end_row = ending_row(next_selection, display_map);
20152 contiguous_row_selections.push(selections.next().unwrap().clone());
20153 } else {
20154 break;
20155 }
20156 }
20157 (start_row, end_row)
20158}
20159
20160fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
20161 if next_selection.end.column > 0 || next_selection.is_empty() {
20162 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
20163 } else {
20164 MultiBufferRow(next_selection.end.row)
20165 }
20166}
20167
20168impl EditorSnapshot {
20169 pub fn remote_selections_in_range<'a>(
20170 &'a self,
20171 range: &'a Range<Anchor>,
20172 collaboration_hub: &dyn CollaborationHub,
20173 cx: &'a App,
20174 ) -> impl 'a + Iterator<Item = RemoteSelection> {
20175 let participant_names = collaboration_hub.user_names(cx);
20176 let participant_indices = collaboration_hub.user_participant_indices(cx);
20177 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20178 let collaborators_by_replica_id = collaborators_by_peer_id
20179 .iter()
20180 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
20181 .collect::<HashMap<_, _>>();
20182 self.buffer_snapshot
20183 .selections_in_range(range, false)
20184 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20185 if replica_id == AGENT_REPLICA_ID {
20186 Some(RemoteSelection {
20187 replica_id,
20188 selection,
20189 cursor_shape,
20190 line_mode,
20191 collaborator_id: CollaboratorId::Agent,
20192 user_name: Some("Agent".into()),
20193 color: cx.theme().players().agent(),
20194 })
20195 } else {
20196 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20197 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20198 let user_name = participant_names.get(&collaborator.user_id).cloned();
20199 Some(RemoteSelection {
20200 replica_id,
20201 selection,
20202 cursor_shape,
20203 line_mode,
20204 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20205 user_name,
20206 color: if let Some(index) = participant_index {
20207 cx.theme().players().color_for_participant(index.0)
20208 } else {
20209 cx.theme().players().absent()
20210 },
20211 })
20212 }
20213 })
20214 }
20215
20216 pub fn hunks_for_ranges(
20217 &self,
20218 ranges: impl IntoIterator<Item = Range<Point>>,
20219 ) -> Vec<MultiBufferDiffHunk> {
20220 let mut hunks = Vec::new();
20221 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20222 HashMap::default();
20223 for query_range in ranges {
20224 let query_rows =
20225 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20226 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20227 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20228 ) {
20229 // Include deleted hunks that are adjacent to the query range, because
20230 // otherwise they would be missed.
20231 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20232 if hunk.status().is_deleted() {
20233 intersects_range |= hunk.row_range.start == query_rows.end;
20234 intersects_range |= hunk.row_range.end == query_rows.start;
20235 }
20236 if intersects_range {
20237 if !processed_buffer_rows
20238 .entry(hunk.buffer_id)
20239 .or_default()
20240 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20241 {
20242 continue;
20243 }
20244 hunks.push(hunk);
20245 }
20246 }
20247 }
20248
20249 hunks
20250 }
20251
20252 fn display_diff_hunks_for_rows<'a>(
20253 &'a self,
20254 display_rows: Range<DisplayRow>,
20255 folded_buffers: &'a HashSet<BufferId>,
20256 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20257 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20258 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20259
20260 self.buffer_snapshot
20261 .diff_hunks_in_range(buffer_start..buffer_end)
20262 .filter_map(|hunk| {
20263 if folded_buffers.contains(&hunk.buffer_id) {
20264 return None;
20265 }
20266
20267 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20268 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20269
20270 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20271 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20272
20273 let display_hunk = if hunk_display_start.column() != 0 {
20274 DisplayDiffHunk::Folded {
20275 display_row: hunk_display_start.row(),
20276 }
20277 } else {
20278 let mut end_row = hunk_display_end.row();
20279 if hunk_display_end.column() > 0 {
20280 end_row.0 += 1;
20281 }
20282 let is_created_file = hunk.is_created_file();
20283 DisplayDiffHunk::Unfolded {
20284 status: hunk.status(),
20285 diff_base_byte_range: hunk.diff_base_byte_range,
20286 display_row_range: hunk_display_start.row()..end_row,
20287 multi_buffer_range: Anchor::range_in_buffer(
20288 hunk.excerpt_id,
20289 hunk.buffer_id,
20290 hunk.buffer_range,
20291 ),
20292 is_created_file,
20293 }
20294 };
20295
20296 Some(display_hunk)
20297 })
20298 }
20299
20300 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20301 self.display_snapshot.buffer_snapshot.language_at(position)
20302 }
20303
20304 pub fn is_focused(&self) -> bool {
20305 self.is_focused
20306 }
20307
20308 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20309 self.placeholder_text.as_ref()
20310 }
20311
20312 pub fn scroll_position(&self) -> gpui::Point<f32> {
20313 self.scroll_anchor.scroll_position(&self.display_snapshot)
20314 }
20315
20316 fn gutter_dimensions(
20317 &self,
20318 font_id: FontId,
20319 font_size: Pixels,
20320 max_line_number_width: Pixels,
20321 cx: &App,
20322 ) -> Option<GutterDimensions> {
20323 if !self.show_gutter {
20324 return None;
20325 }
20326
20327 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20328 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20329
20330 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20331 matches!(
20332 ProjectSettings::get_global(cx).git.git_gutter,
20333 Some(GitGutterSetting::TrackedFiles)
20334 )
20335 });
20336 let gutter_settings = EditorSettings::get_global(cx).gutter;
20337 let show_line_numbers = self
20338 .show_line_numbers
20339 .unwrap_or(gutter_settings.line_numbers);
20340 let line_gutter_width = if show_line_numbers {
20341 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20342 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20343 max_line_number_width.max(min_width_for_number_on_gutter)
20344 } else {
20345 0.0.into()
20346 };
20347
20348 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20349 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20350
20351 let git_blame_entries_width =
20352 self.git_blame_gutter_max_author_length
20353 .map(|max_author_length| {
20354 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20355 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20356
20357 /// The number of characters to dedicate to gaps and margins.
20358 const SPACING_WIDTH: usize = 4;
20359
20360 let max_char_count = max_author_length.min(renderer.max_author_length())
20361 + ::git::SHORT_SHA_LENGTH
20362 + MAX_RELATIVE_TIMESTAMP.len()
20363 + SPACING_WIDTH;
20364
20365 em_advance * max_char_count
20366 });
20367
20368 let is_singleton = self.buffer_snapshot.is_singleton();
20369
20370 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
20371 left_padding += if !is_singleton {
20372 em_width * 4.0
20373 } else if show_runnables || show_breakpoints {
20374 em_width * 3.0
20375 } else if show_git_gutter && show_line_numbers {
20376 em_width * 2.0
20377 } else if show_git_gutter || show_line_numbers {
20378 em_width
20379 } else {
20380 px(0.)
20381 };
20382
20383 let shows_folds = is_singleton && gutter_settings.folds;
20384
20385 let right_padding = if shows_folds && show_line_numbers {
20386 em_width * 4.0
20387 } else if shows_folds || (!is_singleton && show_line_numbers) {
20388 em_width * 3.0
20389 } else if show_line_numbers {
20390 em_width
20391 } else {
20392 px(0.)
20393 };
20394
20395 Some(GutterDimensions {
20396 left_padding,
20397 right_padding,
20398 width: line_gutter_width + left_padding + right_padding,
20399 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
20400 git_blame_entries_width,
20401 })
20402 }
20403
20404 pub fn render_crease_toggle(
20405 &self,
20406 buffer_row: MultiBufferRow,
20407 row_contains_cursor: bool,
20408 editor: Entity<Editor>,
20409 window: &mut Window,
20410 cx: &mut App,
20411 ) -> Option<AnyElement> {
20412 let folded = self.is_line_folded(buffer_row);
20413 let mut is_foldable = false;
20414
20415 if let Some(crease) = self
20416 .crease_snapshot
20417 .query_row(buffer_row, &self.buffer_snapshot)
20418 {
20419 is_foldable = true;
20420 match crease {
20421 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20422 if let Some(render_toggle) = render_toggle {
20423 let toggle_callback =
20424 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20425 if folded {
20426 editor.update(cx, |editor, cx| {
20427 editor.fold_at(buffer_row, window, cx)
20428 });
20429 } else {
20430 editor.update(cx, |editor, cx| {
20431 editor.unfold_at(buffer_row, window, cx)
20432 });
20433 }
20434 });
20435 return Some((render_toggle)(
20436 buffer_row,
20437 folded,
20438 toggle_callback,
20439 window,
20440 cx,
20441 ));
20442 }
20443 }
20444 }
20445 }
20446
20447 is_foldable |= self.starts_indent(buffer_row);
20448
20449 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20450 Some(
20451 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20452 .toggle_state(folded)
20453 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20454 if folded {
20455 this.unfold_at(buffer_row, window, cx);
20456 } else {
20457 this.fold_at(buffer_row, window, cx);
20458 }
20459 }))
20460 .into_any_element(),
20461 )
20462 } else {
20463 None
20464 }
20465 }
20466
20467 pub fn render_crease_trailer(
20468 &self,
20469 buffer_row: MultiBufferRow,
20470 window: &mut Window,
20471 cx: &mut App,
20472 ) -> Option<AnyElement> {
20473 let folded = self.is_line_folded(buffer_row);
20474 if let Crease::Inline { render_trailer, .. } = self
20475 .crease_snapshot
20476 .query_row(buffer_row, &self.buffer_snapshot)?
20477 {
20478 let render_trailer = render_trailer.as_ref()?;
20479 Some(render_trailer(buffer_row, folded, window, cx))
20480 } else {
20481 None
20482 }
20483 }
20484}
20485
20486impl Deref for EditorSnapshot {
20487 type Target = DisplaySnapshot;
20488
20489 fn deref(&self) -> &Self::Target {
20490 &self.display_snapshot
20491 }
20492}
20493
20494#[derive(Clone, Debug, PartialEq, Eq)]
20495pub enum EditorEvent {
20496 InputIgnored {
20497 text: Arc<str>,
20498 },
20499 InputHandled {
20500 utf16_range_to_replace: Option<Range<isize>>,
20501 text: Arc<str>,
20502 },
20503 ExcerptsAdded {
20504 buffer: Entity<Buffer>,
20505 predecessor: ExcerptId,
20506 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
20507 },
20508 ExcerptsRemoved {
20509 ids: Vec<ExcerptId>,
20510 removed_buffer_ids: Vec<BufferId>,
20511 },
20512 BufferFoldToggled {
20513 ids: Vec<ExcerptId>,
20514 folded: bool,
20515 },
20516 ExcerptsEdited {
20517 ids: Vec<ExcerptId>,
20518 },
20519 ExcerptsExpanded {
20520 ids: Vec<ExcerptId>,
20521 },
20522 BufferEdited,
20523 Edited {
20524 transaction_id: clock::Lamport,
20525 },
20526 Reparsed(BufferId),
20527 Focused,
20528 FocusedIn,
20529 Blurred,
20530 DirtyChanged,
20531 Saved,
20532 TitleChanged,
20533 DiffBaseChanged,
20534 SelectionsChanged {
20535 local: bool,
20536 },
20537 ScrollPositionChanged {
20538 local: bool,
20539 autoscroll: bool,
20540 },
20541 Closed,
20542 TransactionUndone {
20543 transaction_id: clock::Lamport,
20544 },
20545 TransactionBegun {
20546 transaction_id: clock::Lamport,
20547 },
20548 Reloaded,
20549 CursorShapeChanged,
20550 PushedToNavHistory {
20551 anchor: Anchor,
20552 is_deactivate: bool,
20553 },
20554}
20555
20556impl EventEmitter<EditorEvent> for Editor {}
20557
20558impl Focusable for Editor {
20559 fn focus_handle(&self, _cx: &App) -> FocusHandle {
20560 self.focus_handle.clone()
20561 }
20562}
20563
20564impl Render for Editor {
20565 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20566 let settings = ThemeSettings::get_global(cx);
20567
20568 let mut text_style = match self.mode {
20569 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
20570 color: cx.theme().colors().editor_foreground,
20571 font_family: settings.ui_font.family.clone(),
20572 font_features: settings.ui_font.features.clone(),
20573 font_fallbacks: settings.ui_font.fallbacks.clone(),
20574 font_size: rems(0.875).into(),
20575 font_weight: settings.ui_font.weight,
20576 line_height: relative(settings.buffer_line_height.value()),
20577 ..Default::default()
20578 },
20579 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
20580 color: cx.theme().colors().editor_foreground,
20581 font_family: settings.buffer_font.family.clone(),
20582 font_features: settings.buffer_font.features.clone(),
20583 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20584 font_size: settings.buffer_font_size(cx).into(),
20585 font_weight: settings.buffer_font.weight,
20586 line_height: relative(settings.buffer_line_height.value()),
20587 ..Default::default()
20588 },
20589 };
20590 if let Some(text_style_refinement) = &self.text_style_refinement {
20591 text_style.refine(text_style_refinement)
20592 }
20593
20594 let background = match self.mode {
20595 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
20596 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
20597 EditorMode::Full { .. } => cx.theme().colors().editor_background,
20598 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
20599 };
20600
20601 EditorElement::new(
20602 &cx.entity(),
20603 EditorStyle {
20604 background,
20605 local_player: cx.theme().players().local(),
20606 text: text_style,
20607 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
20608 syntax: cx.theme().syntax().clone(),
20609 status: cx.theme().status().clone(),
20610 inlay_hints_style: make_inlay_hints_style(cx),
20611 inline_completion_styles: make_suggestion_styles(cx),
20612 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
20613 show_underlines: !self.mode.is_minimap(),
20614 },
20615 )
20616 }
20617}
20618
20619impl EntityInputHandler for Editor {
20620 fn text_for_range(
20621 &mut self,
20622 range_utf16: Range<usize>,
20623 adjusted_range: &mut Option<Range<usize>>,
20624 _: &mut Window,
20625 cx: &mut Context<Self>,
20626 ) -> Option<String> {
20627 let snapshot = self.buffer.read(cx).read(cx);
20628 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
20629 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
20630 if (start.0..end.0) != range_utf16 {
20631 adjusted_range.replace(start.0..end.0);
20632 }
20633 Some(snapshot.text_for_range(start..end).collect())
20634 }
20635
20636 fn selected_text_range(
20637 &mut self,
20638 ignore_disabled_input: bool,
20639 _: &mut Window,
20640 cx: &mut Context<Self>,
20641 ) -> Option<UTF16Selection> {
20642 // Prevent the IME menu from appearing when holding down an alphabetic key
20643 // while input is disabled.
20644 if !ignore_disabled_input && !self.input_enabled {
20645 return None;
20646 }
20647
20648 let selection = self.selections.newest::<OffsetUtf16>(cx);
20649 let range = selection.range();
20650
20651 Some(UTF16Selection {
20652 range: range.start.0..range.end.0,
20653 reversed: selection.reversed,
20654 })
20655 }
20656
20657 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
20658 let snapshot = self.buffer.read(cx).read(cx);
20659 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
20660 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
20661 }
20662
20663 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20664 self.clear_highlights::<InputComposition>(cx);
20665 self.ime_transaction.take();
20666 }
20667
20668 fn replace_text_in_range(
20669 &mut self,
20670 range_utf16: Option<Range<usize>>,
20671 text: &str,
20672 window: &mut Window,
20673 cx: &mut Context<Self>,
20674 ) {
20675 if !self.input_enabled {
20676 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20677 return;
20678 }
20679
20680 self.transact(window, cx, |this, window, cx| {
20681 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
20682 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20683 Some(this.selection_replacement_ranges(range_utf16, cx))
20684 } else {
20685 this.marked_text_ranges(cx)
20686 };
20687
20688 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
20689 let newest_selection_id = this.selections.newest_anchor().id;
20690 this.selections
20691 .all::<OffsetUtf16>(cx)
20692 .iter()
20693 .zip(ranges_to_replace.iter())
20694 .find_map(|(selection, range)| {
20695 if selection.id == newest_selection_id {
20696 Some(
20697 (range.start.0 as isize - selection.head().0 as isize)
20698 ..(range.end.0 as isize - selection.head().0 as isize),
20699 )
20700 } else {
20701 None
20702 }
20703 })
20704 });
20705
20706 cx.emit(EditorEvent::InputHandled {
20707 utf16_range_to_replace: range_to_replace,
20708 text: text.into(),
20709 });
20710
20711 if let Some(new_selected_ranges) = new_selected_ranges {
20712 this.change_selections(None, window, cx, |selections| {
20713 selections.select_ranges(new_selected_ranges)
20714 });
20715 this.backspace(&Default::default(), window, cx);
20716 }
20717
20718 this.handle_input(text, window, cx);
20719 });
20720
20721 if let Some(transaction) = self.ime_transaction {
20722 self.buffer.update(cx, |buffer, cx| {
20723 buffer.group_until_transaction(transaction, cx);
20724 });
20725 }
20726
20727 self.unmark_text(window, cx);
20728 }
20729
20730 fn replace_and_mark_text_in_range(
20731 &mut self,
20732 range_utf16: Option<Range<usize>>,
20733 text: &str,
20734 new_selected_range_utf16: Option<Range<usize>>,
20735 window: &mut Window,
20736 cx: &mut Context<Self>,
20737 ) {
20738 if !self.input_enabled {
20739 return;
20740 }
20741
20742 let transaction = self.transact(window, cx, |this, window, cx| {
20743 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
20744 let snapshot = this.buffer.read(cx).read(cx);
20745 if let Some(relative_range_utf16) = range_utf16.as_ref() {
20746 for marked_range in &mut marked_ranges {
20747 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
20748 marked_range.start.0 += relative_range_utf16.start;
20749 marked_range.start =
20750 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
20751 marked_range.end =
20752 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
20753 }
20754 }
20755 Some(marked_ranges)
20756 } else if let Some(range_utf16) = range_utf16 {
20757 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20758 Some(this.selection_replacement_ranges(range_utf16, cx))
20759 } else {
20760 None
20761 };
20762
20763 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
20764 let newest_selection_id = this.selections.newest_anchor().id;
20765 this.selections
20766 .all::<OffsetUtf16>(cx)
20767 .iter()
20768 .zip(ranges_to_replace.iter())
20769 .find_map(|(selection, range)| {
20770 if selection.id == newest_selection_id {
20771 Some(
20772 (range.start.0 as isize - selection.head().0 as isize)
20773 ..(range.end.0 as isize - selection.head().0 as isize),
20774 )
20775 } else {
20776 None
20777 }
20778 })
20779 });
20780
20781 cx.emit(EditorEvent::InputHandled {
20782 utf16_range_to_replace: range_to_replace,
20783 text: text.into(),
20784 });
20785
20786 if let Some(ranges) = ranges_to_replace {
20787 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
20788 }
20789
20790 let marked_ranges = {
20791 let snapshot = this.buffer.read(cx).read(cx);
20792 this.selections
20793 .disjoint_anchors()
20794 .iter()
20795 .map(|selection| {
20796 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
20797 })
20798 .collect::<Vec<_>>()
20799 };
20800
20801 if text.is_empty() {
20802 this.unmark_text(window, cx);
20803 } else {
20804 this.highlight_text::<InputComposition>(
20805 marked_ranges.clone(),
20806 HighlightStyle {
20807 underline: Some(UnderlineStyle {
20808 thickness: px(1.),
20809 color: None,
20810 wavy: false,
20811 }),
20812 ..Default::default()
20813 },
20814 cx,
20815 );
20816 }
20817
20818 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
20819 let use_autoclose = this.use_autoclose;
20820 let use_auto_surround = this.use_auto_surround;
20821 this.set_use_autoclose(false);
20822 this.set_use_auto_surround(false);
20823 this.handle_input(text, window, cx);
20824 this.set_use_autoclose(use_autoclose);
20825 this.set_use_auto_surround(use_auto_surround);
20826
20827 if let Some(new_selected_range) = new_selected_range_utf16 {
20828 let snapshot = this.buffer.read(cx).read(cx);
20829 let new_selected_ranges = marked_ranges
20830 .into_iter()
20831 .map(|marked_range| {
20832 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
20833 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
20834 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
20835 snapshot.clip_offset_utf16(new_start, Bias::Left)
20836 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
20837 })
20838 .collect::<Vec<_>>();
20839
20840 drop(snapshot);
20841 this.change_selections(None, window, cx, |selections| {
20842 selections.select_ranges(new_selected_ranges)
20843 });
20844 }
20845 });
20846
20847 self.ime_transaction = self.ime_transaction.or(transaction);
20848 if let Some(transaction) = self.ime_transaction {
20849 self.buffer.update(cx, |buffer, cx| {
20850 buffer.group_until_transaction(transaction, cx);
20851 });
20852 }
20853
20854 if self.text_highlights::<InputComposition>(cx).is_none() {
20855 self.ime_transaction.take();
20856 }
20857 }
20858
20859 fn bounds_for_range(
20860 &mut self,
20861 range_utf16: Range<usize>,
20862 element_bounds: gpui::Bounds<Pixels>,
20863 window: &mut Window,
20864 cx: &mut Context<Self>,
20865 ) -> Option<gpui::Bounds<Pixels>> {
20866 let text_layout_details = self.text_layout_details(window);
20867 let gpui::Size {
20868 width: em_width,
20869 height: line_height,
20870 } = self.character_size(window);
20871
20872 let snapshot = self.snapshot(window, cx);
20873 let scroll_position = snapshot.scroll_position();
20874 let scroll_left = scroll_position.x * em_width;
20875
20876 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
20877 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
20878 + self.gutter_dimensions.width
20879 + self.gutter_dimensions.margin;
20880 let y = line_height * (start.row().as_f32() - scroll_position.y);
20881
20882 Some(Bounds {
20883 origin: element_bounds.origin + point(x, y),
20884 size: size(em_width, line_height),
20885 })
20886 }
20887
20888 fn character_index_for_point(
20889 &mut self,
20890 point: gpui::Point<Pixels>,
20891 _window: &mut Window,
20892 _cx: &mut Context<Self>,
20893 ) -> Option<usize> {
20894 let position_map = self.last_position_map.as_ref()?;
20895 if !position_map.text_hitbox.contains(&point) {
20896 return None;
20897 }
20898 let display_point = position_map.point_for_position(point).previous_valid;
20899 let anchor = position_map
20900 .snapshot
20901 .display_point_to_anchor(display_point, Bias::Left);
20902 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
20903 Some(utf16_offset.0)
20904 }
20905}
20906
20907trait SelectionExt {
20908 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
20909 fn spanned_rows(
20910 &self,
20911 include_end_if_at_line_start: bool,
20912 map: &DisplaySnapshot,
20913 ) -> Range<MultiBufferRow>;
20914}
20915
20916impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
20917 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
20918 let start = self
20919 .start
20920 .to_point(&map.buffer_snapshot)
20921 .to_display_point(map);
20922 let end = self
20923 .end
20924 .to_point(&map.buffer_snapshot)
20925 .to_display_point(map);
20926 if self.reversed {
20927 end..start
20928 } else {
20929 start..end
20930 }
20931 }
20932
20933 fn spanned_rows(
20934 &self,
20935 include_end_if_at_line_start: bool,
20936 map: &DisplaySnapshot,
20937 ) -> Range<MultiBufferRow> {
20938 let start = self.start.to_point(&map.buffer_snapshot);
20939 let mut end = self.end.to_point(&map.buffer_snapshot);
20940 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
20941 end.row -= 1;
20942 }
20943
20944 let buffer_start = map.prev_line_boundary(start).0;
20945 let buffer_end = map.next_line_boundary(end).0;
20946 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
20947 }
20948}
20949
20950impl<T: InvalidationRegion> InvalidationStack<T> {
20951 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
20952 where
20953 S: Clone + ToOffset,
20954 {
20955 while let Some(region) = self.last() {
20956 let all_selections_inside_invalidation_ranges =
20957 if selections.len() == region.ranges().len() {
20958 selections
20959 .iter()
20960 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
20961 .all(|(selection, invalidation_range)| {
20962 let head = selection.head().to_offset(buffer);
20963 invalidation_range.start <= head && invalidation_range.end >= head
20964 })
20965 } else {
20966 false
20967 };
20968
20969 if all_selections_inside_invalidation_ranges {
20970 break;
20971 } else {
20972 self.pop();
20973 }
20974 }
20975 }
20976}
20977
20978impl<T> Default for InvalidationStack<T> {
20979 fn default() -> Self {
20980 Self(Default::default())
20981 }
20982}
20983
20984impl<T> Deref for InvalidationStack<T> {
20985 type Target = Vec<T>;
20986
20987 fn deref(&self) -> &Self::Target {
20988 &self.0
20989 }
20990}
20991
20992impl<T> DerefMut for InvalidationStack<T> {
20993 fn deref_mut(&mut self) -> &mut Self::Target {
20994 &mut self.0
20995 }
20996}
20997
20998impl InvalidationRegion for SnippetState {
20999 fn ranges(&self) -> &[Range<Anchor>] {
21000 &self.ranges[self.active_index]
21001 }
21002}
21003
21004fn inline_completion_edit_text(
21005 current_snapshot: &BufferSnapshot,
21006 edits: &[(Range<Anchor>, String)],
21007 edit_preview: &EditPreview,
21008 include_deletions: bool,
21009 cx: &App,
21010) -> HighlightedText {
21011 let edits = edits
21012 .iter()
21013 .map(|(anchor, text)| {
21014 (
21015 anchor.start.text_anchor..anchor.end.text_anchor,
21016 text.clone(),
21017 )
21018 })
21019 .collect::<Vec<_>>();
21020
21021 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
21022}
21023
21024pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
21025 match severity {
21026 lsp::DiagnosticSeverity::ERROR => colors.error,
21027 lsp::DiagnosticSeverity::WARNING => colors.warning,
21028 lsp::DiagnosticSeverity::INFORMATION => colors.info,
21029 lsp::DiagnosticSeverity::HINT => colors.info,
21030 _ => colors.ignored,
21031 }
21032}
21033
21034pub fn styled_runs_for_code_label<'a>(
21035 label: &'a CodeLabel,
21036 syntax_theme: &'a theme::SyntaxTheme,
21037) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
21038 let fade_out = HighlightStyle {
21039 fade_out: Some(0.35),
21040 ..Default::default()
21041 };
21042
21043 let mut prev_end = label.filter_range.end;
21044 label
21045 .runs
21046 .iter()
21047 .enumerate()
21048 .flat_map(move |(ix, (range, highlight_id))| {
21049 let style = if let Some(style) = highlight_id.style(syntax_theme) {
21050 style
21051 } else {
21052 return Default::default();
21053 };
21054 let mut muted_style = style;
21055 muted_style.highlight(fade_out);
21056
21057 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
21058 if range.start >= label.filter_range.end {
21059 if range.start > prev_end {
21060 runs.push((prev_end..range.start, fade_out));
21061 }
21062 runs.push((range.clone(), muted_style));
21063 } else if range.end <= label.filter_range.end {
21064 runs.push((range.clone(), style));
21065 } else {
21066 runs.push((range.start..label.filter_range.end, style));
21067 runs.push((label.filter_range.end..range.end, muted_style));
21068 }
21069 prev_end = cmp::max(prev_end, range.end);
21070
21071 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
21072 runs.push((prev_end..label.text.len(), fade_out));
21073 }
21074
21075 runs
21076 })
21077}
21078
21079pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
21080 let mut prev_index = 0;
21081 let mut prev_codepoint: Option<char> = None;
21082 text.char_indices()
21083 .chain([(text.len(), '\0')])
21084 .filter_map(move |(index, codepoint)| {
21085 let prev_codepoint = prev_codepoint.replace(codepoint)?;
21086 let is_boundary = index == text.len()
21087 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
21088 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
21089 if is_boundary {
21090 let chunk = &text[prev_index..index];
21091 prev_index = index;
21092 Some(chunk)
21093 } else {
21094 None
21095 }
21096 })
21097}
21098
21099pub trait RangeToAnchorExt: Sized {
21100 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
21101
21102 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
21103 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
21104 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
21105 }
21106}
21107
21108impl<T: ToOffset> RangeToAnchorExt for Range<T> {
21109 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
21110 let start_offset = self.start.to_offset(snapshot);
21111 let end_offset = self.end.to_offset(snapshot);
21112 if start_offset == end_offset {
21113 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
21114 } else {
21115 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
21116 }
21117 }
21118}
21119
21120pub trait RowExt {
21121 fn as_f32(&self) -> f32;
21122
21123 fn next_row(&self) -> Self;
21124
21125 fn previous_row(&self) -> Self;
21126
21127 fn minus(&self, other: Self) -> u32;
21128}
21129
21130impl RowExt for DisplayRow {
21131 fn as_f32(&self) -> f32 {
21132 self.0 as f32
21133 }
21134
21135 fn next_row(&self) -> Self {
21136 Self(self.0 + 1)
21137 }
21138
21139 fn previous_row(&self) -> Self {
21140 Self(self.0.saturating_sub(1))
21141 }
21142
21143 fn minus(&self, other: Self) -> u32 {
21144 self.0 - other.0
21145 }
21146}
21147
21148impl RowExt for MultiBufferRow {
21149 fn as_f32(&self) -> f32 {
21150 self.0 as f32
21151 }
21152
21153 fn next_row(&self) -> Self {
21154 Self(self.0 + 1)
21155 }
21156
21157 fn previous_row(&self) -> Self {
21158 Self(self.0.saturating_sub(1))
21159 }
21160
21161 fn minus(&self, other: Self) -> u32 {
21162 self.0 - other.0
21163 }
21164}
21165
21166trait RowRangeExt {
21167 type Row;
21168
21169 fn len(&self) -> usize;
21170
21171 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
21172}
21173
21174impl RowRangeExt for Range<MultiBufferRow> {
21175 type Row = MultiBufferRow;
21176
21177 fn len(&self) -> usize {
21178 (self.end.0 - self.start.0) as usize
21179 }
21180
21181 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21182 (self.start.0..self.end.0).map(MultiBufferRow)
21183 }
21184}
21185
21186impl RowRangeExt for Range<DisplayRow> {
21187 type Row = DisplayRow;
21188
21189 fn len(&self) -> usize {
21190 (self.end.0 - self.start.0) as usize
21191 }
21192
21193 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21194 (self.start.0..self.end.0).map(DisplayRow)
21195 }
21196}
21197
21198/// If select range has more than one line, we
21199/// just point the cursor to range.start.
21200fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21201 if range.start.row == range.end.row {
21202 range
21203 } else {
21204 range.start..range.start
21205 }
21206}
21207pub struct KillRing(ClipboardItem);
21208impl Global for KillRing {}
21209
21210const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21211
21212enum BreakpointPromptEditAction {
21213 Log,
21214 Condition,
21215 HitCondition,
21216}
21217
21218struct BreakpointPromptEditor {
21219 pub(crate) prompt: Entity<Editor>,
21220 editor: WeakEntity<Editor>,
21221 breakpoint_anchor: Anchor,
21222 breakpoint: Breakpoint,
21223 edit_action: BreakpointPromptEditAction,
21224 block_ids: HashSet<CustomBlockId>,
21225 editor_margins: Arc<Mutex<EditorMargins>>,
21226 _subscriptions: Vec<Subscription>,
21227}
21228
21229impl BreakpointPromptEditor {
21230 const MAX_LINES: u8 = 4;
21231
21232 fn new(
21233 editor: WeakEntity<Editor>,
21234 breakpoint_anchor: Anchor,
21235 breakpoint: Breakpoint,
21236 edit_action: BreakpointPromptEditAction,
21237 window: &mut Window,
21238 cx: &mut Context<Self>,
21239 ) -> Self {
21240 let base_text = match edit_action {
21241 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21242 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21243 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21244 }
21245 .map(|msg| msg.to_string())
21246 .unwrap_or_default();
21247
21248 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21249 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21250
21251 let prompt = cx.new(|cx| {
21252 let mut prompt = Editor::new(
21253 EditorMode::AutoHeight {
21254 max_lines: Self::MAX_LINES as usize,
21255 },
21256 buffer,
21257 None,
21258 window,
21259 cx,
21260 );
21261 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21262 prompt.set_show_cursor_when_unfocused(false, cx);
21263 prompt.set_placeholder_text(
21264 match edit_action {
21265 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21266 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21267 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21268 },
21269 cx,
21270 );
21271
21272 prompt
21273 });
21274
21275 Self {
21276 prompt,
21277 editor,
21278 breakpoint_anchor,
21279 breakpoint,
21280 edit_action,
21281 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21282 block_ids: Default::default(),
21283 _subscriptions: vec![],
21284 }
21285 }
21286
21287 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21288 self.block_ids.extend(block_ids)
21289 }
21290
21291 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21292 if let Some(editor) = self.editor.upgrade() {
21293 let message = self
21294 .prompt
21295 .read(cx)
21296 .buffer
21297 .read(cx)
21298 .as_singleton()
21299 .expect("A multi buffer in breakpoint prompt isn't possible")
21300 .read(cx)
21301 .as_rope()
21302 .to_string();
21303
21304 editor.update(cx, |editor, cx| {
21305 editor.edit_breakpoint_at_anchor(
21306 self.breakpoint_anchor,
21307 self.breakpoint.clone(),
21308 match self.edit_action {
21309 BreakpointPromptEditAction::Log => {
21310 BreakpointEditAction::EditLogMessage(message.into())
21311 }
21312 BreakpointPromptEditAction::Condition => {
21313 BreakpointEditAction::EditCondition(message.into())
21314 }
21315 BreakpointPromptEditAction::HitCondition => {
21316 BreakpointEditAction::EditHitCondition(message.into())
21317 }
21318 },
21319 cx,
21320 );
21321
21322 editor.remove_blocks(self.block_ids.clone(), None, cx);
21323 cx.focus_self(window);
21324 });
21325 }
21326 }
21327
21328 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21329 self.editor
21330 .update(cx, |editor, cx| {
21331 editor.remove_blocks(self.block_ids.clone(), None, cx);
21332 window.focus(&editor.focus_handle);
21333 })
21334 .log_err();
21335 }
21336
21337 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21338 let settings = ThemeSettings::get_global(cx);
21339 let text_style = TextStyle {
21340 color: if self.prompt.read(cx).read_only(cx) {
21341 cx.theme().colors().text_disabled
21342 } else {
21343 cx.theme().colors().text
21344 },
21345 font_family: settings.buffer_font.family.clone(),
21346 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21347 font_size: settings.buffer_font_size(cx).into(),
21348 font_weight: settings.buffer_font.weight,
21349 line_height: relative(settings.buffer_line_height.value()),
21350 ..Default::default()
21351 };
21352 EditorElement::new(
21353 &self.prompt,
21354 EditorStyle {
21355 background: cx.theme().colors().editor_background,
21356 local_player: cx.theme().players().local(),
21357 text: text_style,
21358 ..Default::default()
21359 },
21360 )
21361 }
21362}
21363
21364impl Render for BreakpointPromptEditor {
21365 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21366 let editor_margins = *self.editor_margins.lock();
21367 let gutter_dimensions = editor_margins.gutter;
21368 h_flex()
21369 .key_context("Editor")
21370 .bg(cx.theme().colors().editor_background)
21371 .border_y_1()
21372 .border_color(cx.theme().status().info_border)
21373 .size_full()
21374 .py(window.line_height() / 2.5)
21375 .on_action(cx.listener(Self::confirm))
21376 .on_action(cx.listener(Self::cancel))
21377 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21378 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21379 }
21380}
21381
21382impl Focusable for BreakpointPromptEditor {
21383 fn focus_handle(&self, cx: &App) -> FocusHandle {
21384 self.prompt.focus_handle(cx)
21385 }
21386}
21387
21388fn all_edits_insertions_or_deletions(
21389 edits: &Vec<(Range<Anchor>, String)>,
21390 snapshot: &MultiBufferSnapshot,
21391) -> bool {
21392 let mut all_insertions = true;
21393 let mut all_deletions = true;
21394
21395 for (range, new_text) in edits.iter() {
21396 let range_is_empty = range.to_offset(&snapshot).is_empty();
21397 let text_is_empty = new_text.is_empty();
21398
21399 if range_is_empty != text_is_empty {
21400 if range_is_empty {
21401 all_deletions = false;
21402 } else {
21403 all_insertions = false;
21404 }
21405 } else {
21406 return false;
21407 }
21408
21409 if !all_insertions && !all_deletions {
21410 return false;
21411 }
21412 }
21413 all_insertions || all_deletions
21414}
21415
21416struct MissingEditPredictionKeybindingTooltip;
21417
21418impl Render for MissingEditPredictionKeybindingTooltip {
21419 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21420 ui::tooltip_container(window, cx, |container, _, cx| {
21421 container
21422 .flex_shrink_0()
21423 .max_w_80()
21424 .min_h(rems_from_px(124.))
21425 .justify_between()
21426 .child(
21427 v_flex()
21428 .flex_1()
21429 .text_ui_sm(cx)
21430 .child(Label::new("Conflict with Accept Keybinding"))
21431 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21432 )
21433 .child(
21434 h_flex()
21435 .pb_1()
21436 .gap_1()
21437 .items_end()
21438 .w_full()
21439 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21440 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21441 }))
21442 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21443 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21444 })),
21445 )
21446 })
21447 }
21448}
21449
21450#[derive(Debug, Clone, Copy, PartialEq)]
21451pub struct LineHighlight {
21452 pub background: Background,
21453 pub border: Option<gpui::Hsla>,
21454 pub include_gutter: bool,
21455 pub type_id: Option<TypeId>,
21456}
21457
21458fn render_diff_hunk_controls(
21459 row: u32,
21460 status: &DiffHunkStatus,
21461 hunk_range: Range<Anchor>,
21462 is_created_file: bool,
21463 line_height: Pixels,
21464 editor: &Entity<Editor>,
21465 _window: &mut Window,
21466 cx: &mut App,
21467) -> AnyElement {
21468 h_flex()
21469 .h(line_height)
21470 .mr_1()
21471 .gap_1()
21472 .px_0p5()
21473 .pb_1()
21474 .border_x_1()
21475 .border_b_1()
21476 .border_color(cx.theme().colors().border_variant)
21477 .rounded_b_lg()
21478 .bg(cx.theme().colors().editor_background)
21479 .gap_1()
21480 .occlude()
21481 .shadow_md()
21482 .child(if status.has_secondary_hunk() {
21483 Button::new(("stage", row as u64), "Stage")
21484 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21485 .tooltip({
21486 let focus_handle = editor.focus_handle(cx);
21487 move |window, cx| {
21488 Tooltip::for_action_in(
21489 "Stage Hunk",
21490 &::git::ToggleStaged,
21491 &focus_handle,
21492 window,
21493 cx,
21494 )
21495 }
21496 })
21497 .on_click({
21498 let editor = editor.clone();
21499 move |_event, _window, cx| {
21500 editor.update(cx, |editor, cx| {
21501 editor.stage_or_unstage_diff_hunks(
21502 true,
21503 vec![hunk_range.start..hunk_range.start],
21504 cx,
21505 );
21506 });
21507 }
21508 })
21509 } else {
21510 Button::new(("unstage", row as u64), "Unstage")
21511 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21512 .tooltip({
21513 let focus_handle = editor.focus_handle(cx);
21514 move |window, cx| {
21515 Tooltip::for_action_in(
21516 "Unstage Hunk",
21517 &::git::ToggleStaged,
21518 &focus_handle,
21519 window,
21520 cx,
21521 )
21522 }
21523 })
21524 .on_click({
21525 let editor = editor.clone();
21526 move |_event, _window, cx| {
21527 editor.update(cx, |editor, cx| {
21528 editor.stage_or_unstage_diff_hunks(
21529 false,
21530 vec![hunk_range.start..hunk_range.start],
21531 cx,
21532 );
21533 });
21534 }
21535 })
21536 })
21537 .child(
21538 Button::new(("restore", row as u64), "Restore")
21539 .tooltip({
21540 let focus_handle = editor.focus_handle(cx);
21541 move |window, cx| {
21542 Tooltip::for_action_in(
21543 "Restore Hunk",
21544 &::git::Restore,
21545 &focus_handle,
21546 window,
21547 cx,
21548 )
21549 }
21550 })
21551 .on_click({
21552 let editor = editor.clone();
21553 move |_event, window, cx| {
21554 editor.update(cx, |editor, cx| {
21555 let snapshot = editor.snapshot(window, cx);
21556 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
21557 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
21558 });
21559 }
21560 })
21561 .disabled(is_created_file),
21562 )
21563 .when(
21564 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
21565 |el| {
21566 el.child(
21567 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
21568 .shape(IconButtonShape::Square)
21569 .icon_size(IconSize::Small)
21570 // .disabled(!has_multiple_hunks)
21571 .tooltip({
21572 let focus_handle = editor.focus_handle(cx);
21573 move |window, cx| {
21574 Tooltip::for_action_in(
21575 "Next Hunk",
21576 &GoToHunk,
21577 &focus_handle,
21578 window,
21579 cx,
21580 )
21581 }
21582 })
21583 .on_click({
21584 let editor = editor.clone();
21585 move |_event, window, cx| {
21586 editor.update(cx, |editor, cx| {
21587 let snapshot = editor.snapshot(window, cx);
21588 let position =
21589 hunk_range.end.to_point(&snapshot.buffer_snapshot);
21590 editor.go_to_hunk_before_or_after_position(
21591 &snapshot,
21592 position,
21593 Direction::Next,
21594 window,
21595 cx,
21596 );
21597 editor.expand_selected_diff_hunks(cx);
21598 });
21599 }
21600 }),
21601 )
21602 .child(
21603 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
21604 .shape(IconButtonShape::Square)
21605 .icon_size(IconSize::Small)
21606 // .disabled(!has_multiple_hunks)
21607 .tooltip({
21608 let focus_handle = editor.focus_handle(cx);
21609 move |window, cx| {
21610 Tooltip::for_action_in(
21611 "Previous Hunk",
21612 &GoToPreviousHunk,
21613 &focus_handle,
21614 window,
21615 cx,
21616 )
21617 }
21618 })
21619 .on_click({
21620 let editor = editor.clone();
21621 move |_event, window, cx| {
21622 editor.update(cx, |editor, cx| {
21623 let snapshot = editor.snapshot(window, cx);
21624 let point =
21625 hunk_range.start.to_point(&snapshot.buffer_snapshot);
21626 editor.go_to_hunk_before_or_after_position(
21627 &snapshot,
21628 point,
21629 Direction::Prev,
21630 window,
21631 cx,
21632 );
21633 editor.expand_selected_diff_hunks(cx);
21634 });
21635 }
21636 }),
21637 )
21638 },
21639 )
21640 .into_any_element()
21641}