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};
133
134pub use git::blame::BlameRenderer;
135pub use proposed_changes_editor::{
136 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
137};
138use smallvec::smallvec;
139use std::{cell::OnceCell, iter::Peekable};
140use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
141
142pub use lsp::CompletionContext;
143use lsp::{
144 CodeActionKind, CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity,
145 InsertTextFormat, InsertTextMode, LanguageServerId, LanguageServerName,
146};
147
148use language::BufferSnapshot;
149pub use lsp_ext::lsp_tasks;
150use movement::TextLayoutDetails;
151pub use multi_buffer::{
152 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
153 RowInfo, ToOffset, ToPoint,
154};
155use multi_buffer::{
156 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
157 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
158};
159use parking_lot::Mutex;
160use project::{
161 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
162 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
163 TaskSourceKind,
164 debugger::breakpoint_store::Breakpoint,
165 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
166 project_settings::{GitGutterSetting, ProjectSettings},
167};
168use rand::prelude::*;
169use rpc::{ErrorExt, proto::*};
170use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
171use selections_collection::{
172 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
173};
174use serde::{Deserialize, Serialize};
175use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
176use smallvec::SmallVec;
177use snippet::Snippet;
178use std::sync::Arc;
179use std::{
180 any::TypeId,
181 borrow::Cow,
182 cell::RefCell,
183 cmp::{self, Ordering, Reverse},
184 mem,
185 num::NonZeroU32,
186 ops::{ControlFlow, Deref, DerefMut, Not as _, Range, RangeInclusive},
187 path::{Path, PathBuf},
188 rc::Rc,
189 time::{Duration, Instant},
190};
191pub use sum_tree::Bias;
192use sum_tree::TreeMap;
193use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
194use theme::{
195 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
196 observe_buffer_font_size_adjustment,
197};
198use ui::{
199 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
200 IconSize, Key, Tooltip, h_flex, prelude::*,
201};
202use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
203use workspace::{
204 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
205 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
206 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
207 item::{ItemHandle, PreviewTabsSettings},
208 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
209 searchable::SearchEvent,
210};
211
212use crate::hover_links::{find_url, find_url_from_range};
213use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
214
215pub const FILE_HEADER_HEIGHT: u32 = 2;
216pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
217pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
218const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
219const MAX_LINE_LEN: usize = 1024;
220const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
221const MAX_SELECTION_HISTORY_LEN: usize = 1024;
222pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
223#[doc(hidden)]
224pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
225const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
226
227pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
228pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
229pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
230
231pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
232pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
233pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
234pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
235
236pub type RenderDiffHunkControlsFn = Arc<
237 dyn Fn(
238 u32,
239 &DiffHunkStatus,
240 Range<Anchor>,
241 bool,
242 Pixels,
243 &Entity<Editor>,
244 &mut Window,
245 &mut App,
246 ) -> AnyElement,
247>;
248
249const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
250 alt: true,
251 shift: true,
252 control: false,
253 platform: false,
254 function: false,
255};
256
257struct InlineValueCache {
258 enabled: bool,
259 inlays: Vec<InlayId>,
260 refresh_task: Task<Option<()>>,
261}
262
263impl InlineValueCache {
264 fn new(enabled: bool) -> Self {
265 Self {
266 enabled,
267 inlays: Vec::new(),
268 refresh_task: Task::ready(None),
269 }
270 }
271}
272
273#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
274pub enum InlayId {
275 InlineCompletion(usize),
276 Hint(usize),
277 DebuggerValue(usize),
278}
279
280impl InlayId {
281 fn id(&self) -> usize {
282 match self {
283 Self::InlineCompletion(id) => *id,
284 Self::Hint(id) => *id,
285 Self::DebuggerValue(id) => *id,
286 }
287 }
288}
289
290pub enum ActiveDebugLine {}
291enum DocumentHighlightRead {}
292enum DocumentHighlightWrite {}
293enum InputComposition {}
294enum SelectedTextHighlight {}
295
296pub enum ConflictsOuter {}
297pub enum ConflictsOurs {}
298pub enum ConflictsTheirs {}
299pub enum ConflictsOursMarker {}
300pub enum ConflictsTheirsMarker {}
301
302#[derive(Debug, Copy, Clone, PartialEq, Eq)]
303pub enum Navigated {
304 Yes,
305 No,
306}
307
308impl Navigated {
309 pub fn from_bool(yes: bool) -> Navigated {
310 if yes { Navigated::Yes } else { Navigated::No }
311 }
312}
313
314#[derive(Debug, Clone, PartialEq, Eq)]
315enum DisplayDiffHunk {
316 Folded {
317 display_row: DisplayRow,
318 },
319 Unfolded {
320 is_created_file: bool,
321 diff_base_byte_range: Range<usize>,
322 display_row_range: Range<DisplayRow>,
323 multi_buffer_range: Range<Anchor>,
324 status: DiffHunkStatus,
325 },
326}
327
328pub enum HideMouseCursorOrigin {
329 TypingAction,
330 MovementAction,
331}
332
333pub fn init_settings(cx: &mut App) {
334 EditorSettings::register(cx);
335}
336
337pub fn init(cx: &mut App) {
338 init_settings(cx);
339
340 cx.set_global(GlobalBlameRenderer(Arc::new(())));
341
342 workspace::register_project_item::<Editor>(cx);
343 workspace::FollowableViewRegistry::register::<Editor>(cx);
344 workspace::register_serializable_item::<Editor>(cx);
345
346 cx.observe_new(
347 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
348 workspace.register_action(Editor::new_file);
349 workspace.register_action(Editor::new_file_vertical);
350 workspace.register_action(Editor::new_file_horizontal);
351 workspace.register_action(Editor::cancel_language_server_work);
352 },
353 )
354 .detach();
355
356 cx.on_action(move |_: &workspace::NewFile, cx| {
357 let app_state = workspace::AppState::global(cx);
358 if let Some(app_state) = app_state.upgrade() {
359 workspace::open_new(
360 Default::default(),
361 app_state,
362 cx,
363 |workspace, window, cx| {
364 Editor::new_file(workspace, &Default::default(), window, cx)
365 },
366 )
367 .detach();
368 }
369 });
370 cx.on_action(move |_: &workspace::NewWindow, cx| {
371 let app_state = workspace::AppState::global(cx);
372 if let Some(app_state) = app_state.upgrade() {
373 workspace::open_new(
374 Default::default(),
375 app_state,
376 cx,
377 |workspace, window, cx| {
378 cx.activate(true);
379 Editor::new_file(workspace, &Default::default(), window, cx)
380 },
381 )
382 .detach();
383 }
384 });
385}
386
387pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
388 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
389}
390
391pub trait DiagnosticRenderer {
392 fn render_group(
393 &self,
394 diagnostic_group: Vec<DiagnosticEntry<Point>>,
395 buffer_id: BufferId,
396 snapshot: EditorSnapshot,
397 editor: WeakEntity<Editor>,
398 cx: &mut App,
399 ) -> Vec<BlockProperties<Anchor>>;
400
401 fn render_hover(
402 &self,
403 diagnostic_group: Vec<DiagnosticEntry<Point>>,
404 range: Range<Point>,
405 buffer_id: BufferId,
406 cx: &mut App,
407 ) -> Option<Entity<markdown::Markdown>>;
408
409 fn open_link(
410 &self,
411 editor: &mut Editor,
412 link: SharedString,
413 window: &mut Window,
414 cx: &mut Context<Editor>,
415 );
416}
417
418pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
419
420impl GlobalDiagnosticRenderer {
421 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
422 cx.try_global::<Self>().map(|g| g.0.clone())
423 }
424}
425
426impl gpui::Global for GlobalDiagnosticRenderer {}
427pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
428 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
429}
430
431pub struct SearchWithinRange;
432
433trait InvalidationRegion {
434 fn ranges(&self) -> &[Range<Anchor>];
435}
436
437#[derive(Clone, Debug, PartialEq)]
438pub enum SelectPhase {
439 Begin {
440 position: DisplayPoint,
441 add: bool,
442 click_count: usize,
443 },
444 BeginColumnar {
445 position: DisplayPoint,
446 reset: bool,
447 goal_column: u32,
448 },
449 Extend {
450 position: DisplayPoint,
451 click_count: usize,
452 },
453 Update {
454 position: DisplayPoint,
455 goal_column: u32,
456 scroll_delta: gpui::Point<f32>,
457 },
458 End,
459}
460
461#[derive(Clone, Debug)]
462pub enum SelectMode {
463 Character,
464 Word(Range<Anchor>),
465 Line(Range<Anchor>),
466 All,
467}
468
469#[derive(Clone, PartialEq, Eq, Debug)]
470pub enum EditorMode {
471 SingleLine {
472 auto_width: bool,
473 },
474 AutoHeight {
475 max_lines: usize,
476 },
477 Full {
478 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
479 scale_ui_elements_with_buffer_font_size: bool,
480 /// When set to `true`, the editor will render a background for the active line.
481 show_active_line_background: bool,
482 /// When set to `true`, the editor's height will be determined by its content.
483 sized_by_content: bool,
484 },
485 Minimap {
486 parent: WeakEntity<Editor>,
487 },
488}
489
490impl EditorMode {
491 pub fn full() -> Self {
492 Self::Full {
493 scale_ui_elements_with_buffer_font_size: true,
494 show_active_line_background: true,
495 sized_by_content: false,
496 }
497 }
498
499 pub fn is_full(&self) -> bool {
500 matches!(self, Self::Full { .. })
501 }
502
503 fn is_minimap(&self) -> bool {
504 matches!(self, Self::Minimap { .. })
505 }
506}
507
508#[derive(Copy, Clone, Debug)]
509pub enum SoftWrap {
510 /// Prefer not to wrap at all.
511 ///
512 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
513 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
514 GitDiff,
515 /// Prefer a single line generally, unless an overly long line is encountered.
516 None,
517 /// Soft wrap lines that exceed the editor width.
518 EditorWidth,
519 /// Soft wrap lines at the preferred line length.
520 Column(u32),
521 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
522 Bounded(u32),
523}
524
525#[derive(Clone)]
526pub struct EditorStyle {
527 pub background: Hsla,
528 pub local_player: PlayerColor,
529 pub text: TextStyle,
530 pub scrollbar_width: Pixels,
531 pub syntax: Arc<SyntaxTheme>,
532 pub status: StatusColors,
533 pub inlay_hints_style: HighlightStyle,
534 pub inline_completion_styles: InlineCompletionStyles,
535 pub unnecessary_code_fade: f32,
536 pub show_underlines: bool,
537}
538
539impl Default for EditorStyle {
540 fn default() -> Self {
541 Self {
542 background: Hsla::default(),
543 local_player: PlayerColor::default(),
544 text: TextStyle::default(),
545 scrollbar_width: Pixels::default(),
546 syntax: Default::default(),
547 // HACK: Status colors don't have a real default.
548 // We should look into removing the status colors from the editor
549 // style and retrieve them directly from the theme.
550 status: StatusColors::dark(),
551 inlay_hints_style: HighlightStyle::default(),
552 inline_completion_styles: InlineCompletionStyles {
553 insertion: HighlightStyle::default(),
554 whitespace: HighlightStyle::default(),
555 },
556 unnecessary_code_fade: Default::default(),
557 show_underlines: true,
558 }
559 }
560}
561
562pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
563 let show_background = language_settings::language_settings(None, None, cx)
564 .inlay_hints
565 .show_background;
566
567 HighlightStyle {
568 color: Some(cx.theme().status().hint),
569 background_color: show_background.then(|| cx.theme().status().hint_background),
570 ..HighlightStyle::default()
571 }
572}
573
574pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
575 InlineCompletionStyles {
576 insertion: HighlightStyle {
577 color: Some(cx.theme().status().predictive),
578 ..HighlightStyle::default()
579 },
580 whitespace: HighlightStyle {
581 background_color: Some(cx.theme().status().created_background),
582 ..HighlightStyle::default()
583 },
584 }
585}
586
587type CompletionId = usize;
588
589pub(crate) enum EditDisplayMode {
590 TabAccept,
591 DiffPopover,
592 Inline,
593}
594
595enum InlineCompletion {
596 Edit {
597 edits: Vec<(Range<Anchor>, String)>,
598 edit_preview: Option<EditPreview>,
599 display_mode: EditDisplayMode,
600 snapshot: BufferSnapshot,
601 },
602 Move {
603 target: Anchor,
604 snapshot: BufferSnapshot,
605 },
606}
607
608struct InlineCompletionState {
609 inlay_ids: Vec<InlayId>,
610 completion: InlineCompletion,
611 completion_id: Option<SharedString>,
612 invalidation_range: Range<Anchor>,
613}
614
615enum EditPredictionSettings {
616 Disabled,
617 Enabled {
618 show_in_menu: bool,
619 preview_requires_modifier: bool,
620 },
621}
622
623enum InlineCompletionHighlight {}
624
625#[derive(Debug, Clone)]
626struct InlineDiagnostic {
627 message: SharedString,
628 group_id: usize,
629 is_primary: bool,
630 start: Point,
631 severity: DiagnosticSeverity,
632}
633
634pub enum MenuInlineCompletionsPolicy {
635 Never,
636 ByProvider,
637}
638
639pub enum EditPredictionPreview {
640 /// Modifier is not pressed
641 Inactive { released_too_fast: bool },
642 /// Modifier pressed
643 Active {
644 since: Instant,
645 previous_scroll_position: Option<ScrollAnchor>,
646 },
647}
648
649impl EditPredictionPreview {
650 pub fn released_too_fast(&self) -> bool {
651 match self {
652 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
653 EditPredictionPreview::Active { .. } => false,
654 }
655 }
656
657 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
658 if let EditPredictionPreview::Active {
659 previous_scroll_position,
660 ..
661 } = self
662 {
663 *previous_scroll_position = scroll_position;
664 }
665 }
666}
667
668pub struct ContextMenuOptions {
669 pub min_entries_visible: usize,
670 pub max_entries_visible: usize,
671 pub placement: Option<ContextMenuPlacement>,
672}
673
674#[derive(Debug, Clone, PartialEq, Eq)]
675pub enum ContextMenuPlacement {
676 Above,
677 Below,
678}
679
680#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
681struct EditorActionId(usize);
682
683impl EditorActionId {
684 pub fn post_inc(&mut self) -> Self {
685 let answer = self.0;
686
687 *self = Self(answer + 1);
688
689 Self(answer)
690 }
691}
692
693// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
694// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
695
696type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
697type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
698
699#[derive(Default)]
700struct ScrollbarMarkerState {
701 scrollbar_size: Size<Pixels>,
702 dirty: bool,
703 markers: Arc<[PaintQuad]>,
704 pending_refresh: Option<Task<Result<()>>>,
705}
706
707impl ScrollbarMarkerState {
708 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
709 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
710 }
711}
712
713#[derive(Clone, Debug)]
714struct RunnableTasks {
715 templates: Vec<(TaskSourceKind, TaskTemplate)>,
716 offset: multi_buffer::Anchor,
717 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
718 column: u32,
719 // Values of all named captures, including those starting with '_'
720 extra_variables: HashMap<String, String>,
721 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
722 context_range: Range<BufferOffset>,
723}
724
725impl RunnableTasks {
726 fn resolve<'a>(
727 &'a self,
728 cx: &'a task::TaskContext,
729 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
730 self.templates.iter().filter_map(|(kind, template)| {
731 template
732 .resolve_task(&kind.to_id_base(), cx)
733 .map(|task| (kind.clone(), task))
734 })
735 }
736}
737
738#[derive(Clone)]
739struct ResolvedTasks {
740 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
741 position: Anchor,
742}
743
744#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
745struct BufferOffset(usize);
746
747// Addons allow storing per-editor state in other crates (e.g. Vim)
748pub trait Addon: 'static {
749 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
750
751 fn render_buffer_header_controls(
752 &self,
753 _: &ExcerptInfo,
754 _: &Window,
755 _: &App,
756 ) -> Option<AnyElement> {
757 None
758 }
759
760 fn to_any(&self) -> &dyn std::any::Any;
761
762 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
763 None
764 }
765}
766
767/// A set of caret positions, registered when the editor was edited.
768pub struct ChangeList {
769 changes: Vec<Vec<Anchor>>,
770 /// Currently "selected" change.
771 position: Option<usize>,
772}
773
774impl ChangeList {
775 pub fn new() -> Self {
776 Self {
777 changes: Vec::new(),
778 position: None,
779 }
780 }
781
782 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
783 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
784 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
785 if self.changes.is_empty() {
786 return None;
787 }
788
789 let prev = self.position.unwrap_or(self.changes.len());
790 let next = if direction == Direction::Prev {
791 prev.saturating_sub(count)
792 } else {
793 (prev + count).min(self.changes.len() - 1)
794 };
795 self.position = Some(next);
796 self.changes.get(next).map(|anchors| anchors.as_slice())
797 }
798
799 /// Adds a new change to the list, resetting the change list position.
800 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
801 self.position.take();
802 if pop_state {
803 self.changes.pop();
804 }
805 self.changes.push(new_positions.clone());
806 }
807
808 pub fn last(&self) -> Option<&[Anchor]> {
809 self.changes.last().map(|anchors| anchors.as_slice())
810 }
811}
812
813#[derive(Clone)]
814struct InlineBlamePopoverState {
815 scroll_handle: ScrollHandle,
816 commit_message: Option<ParsedCommitMessage>,
817 markdown: Entity<Markdown>,
818}
819
820struct InlineBlamePopover {
821 position: gpui::Point<Pixels>,
822 show_task: Option<Task<()>>,
823 hide_task: Option<Task<()>>,
824 popover_bounds: Option<Bounds<Pixels>>,
825 popover_state: InlineBlamePopoverState,
826}
827
828/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
829/// a breakpoint on them.
830#[derive(Clone, Copy, Debug)]
831struct PhantomBreakpointIndicator {
832 display_row: DisplayRow,
833 /// There's a small debounce between hovering over the line and showing the indicator.
834 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
835 is_active: bool,
836 collides_with_existing_breakpoint: bool,
837}
838/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
839///
840/// See the [module level documentation](self) for more information.
841pub struct Editor {
842 focus_handle: FocusHandle,
843 last_focused_descendant: Option<WeakFocusHandle>,
844 /// The text buffer being edited
845 buffer: Entity<MultiBuffer>,
846 /// Map of how text in the buffer should be displayed.
847 /// Handles soft wraps, folds, fake inlay text insertions, etc.
848 pub display_map: Entity<DisplayMap>,
849 pub selections: SelectionsCollection,
850 pub scroll_manager: ScrollManager,
851 /// When inline assist editors are linked, they all render cursors because
852 /// typing enters text into each of them, even the ones that aren't focused.
853 pub(crate) show_cursor_when_unfocused: bool,
854 columnar_selection_tail: Option<Anchor>,
855 add_selections_state: Option<AddSelectionsState>,
856 select_next_state: Option<SelectNextState>,
857 select_prev_state: Option<SelectNextState>,
858 selection_history: SelectionHistory,
859 autoclose_regions: Vec<AutocloseRegion>,
860 snippet_stack: InvalidationStack<SnippetState>,
861 select_syntax_node_history: SelectSyntaxNodeHistory,
862 ime_transaction: Option<TransactionId>,
863 active_diagnostics: ActiveDiagnostic,
864 show_inline_diagnostics: bool,
865 inline_diagnostics_update: Task<()>,
866 inline_diagnostics_enabled: bool,
867 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
868 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
869 hard_wrap: Option<usize>,
870
871 // TODO: make this a access method
872 pub project: Option<Entity<Project>>,
873 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
874 completion_provider: Option<Box<dyn CompletionProvider>>,
875 collaboration_hub: Option<Box<dyn CollaborationHub>>,
876 blink_manager: Entity<BlinkManager>,
877 show_cursor_names: bool,
878 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
879 pub show_local_selections: bool,
880 mode: EditorMode,
881 show_breadcrumbs: bool,
882 show_gutter: bool,
883 show_scrollbars: bool,
884 show_minimap: bool,
885 disable_expand_excerpt_buttons: bool,
886 show_line_numbers: Option<bool>,
887 use_relative_line_numbers: Option<bool>,
888 show_git_diff_gutter: Option<bool>,
889 show_code_actions: Option<bool>,
890 show_runnables: Option<bool>,
891 show_breakpoints: Option<bool>,
892 show_wrap_guides: Option<bool>,
893 show_indent_guides: Option<bool>,
894 placeholder_text: Option<Arc<str>>,
895 highlight_order: usize,
896 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
897 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
898 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
899 scrollbar_marker_state: ScrollbarMarkerState,
900 active_indent_guides_state: ActiveIndentGuidesState,
901 nav_history: Option<ItemNavHistory>,
902 context_menu: RefCell<Option<CodeContextMenu>>,
903 context_menu_options: Option<ContextMenuOptions>,
904 mouse_context_menu: Option<MouseContextMenu>,
905 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
906 inline_blame_popover: Option<InlineBlamePopover>,
907 signature_help_state: SignatureHelpState,
908 auto_signature_help: Option<bool>,
909 find_all_references_task_sources: Vec<Anchor>,
910 next_completion_id: CompletionId,
911 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
912 code_actions_task: Option<Task<Result<()>>>,
913 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
914 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
915 document_highlights_task: Option<Task<()>>,
916 linked_editing_range_task: Option<Task<Option<()>>>,
917 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
918 pending_rename: Option<RenameState>,
919 searchable: bool,
920 cursor_shape: CursorShape,
921 current_line_highlight: Option<CurrentLineHighlight>,
922 collapse_matches: bool,
923 autoindent_mode: Option<AutoindentMode>,
924 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
925 input_enabled: bool,
926 use_modal_editing: bool,
927 read_only: bool,
928 leader_id: Option<CollaboratorId>,
929 remote_id: Option<ViewId>,
930 pub hover_state: HoverState,
931 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
932 gutter_hovered: bool,
933 hovered_link_state: Option<HoveredLinkState>,
934 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
935 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
936 active_inline_completion: Option<InlineCompletionState>,
937 /// Used to prevent flickering as the user types while the menu is open
938 stale_inline_completion_in_menu: Option<InlineCompletionState>,
939 edit_prediction_settings: EditPredictionSettings,
940 inline_completions_hidden_for_vim_mode: bool,
941 show_inline_completions_override: Option<bool>,
942 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
943 edit_prediction_preview: EditPredictionPreview,
944 edit_prediction_indent_conflict: bool,
945 edit_prediction_requires_modifier_in_indent_conflict: bool,
946 inlay_hint_cache: InlayHintCache,
947 next_inlay_id: usize,
948 _subscriptions: Vec<Subscription>,
949 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
950 gutter_dimensions: GutterDimensions,
951 style: Option<EditorStyle>,
952 text_style_refinement: Option<TextStyleRefinement>,
953 next_editor_action_id: EditorActionId,
954 editor_actions:
955 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
956 use_autoclose: bool,
957 use_auto_surround: bool,
958 auto_replace_emoji_shortcode: bool,
959 jsx_tag_auto_close_enabled_in_any_buffer: bool,
960 show_git_blame_gutter: bool,
961 show_git_blame_inline: bool,
962 show_git_blame_inline_delay_task: Option<Task<()>>,
963 git_blame_inline_enabled: bool,
964 render_diff_hunk_controls: RenderDiffHunkControlsFn,
965 serialize_dirty_buffers: bool,
966 show_selection_menu: Option<bool>,
967 blame: Option<Entity<GitBlame>>,
968 blame_subscription: Option<Subscription>,
969 custom_context_menu: Option<
970 Box<
971 dyn 'static
972 + Fn(
973 &mut Self,
974 DisplayPoint,
975 &mut Window,
976 &mut Context<Self>,
977 ) -> Option<Entity<ui::ContextMenu>>,
978 >,
979 >,
980 last_bounds: Option<Bounds<Pixels>>,
981 last_position_map: Option<Rc<PositionMap>>,
982 expect_bounds_change: Option<Bounds<Pixels>>,
983 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
984 tasks_update_task: Option<Task<()>>,
985 breakpoint_store: Option<Entity<BreakpointStore>>,
986 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
987 in_project_search: bool,
988 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
989 breadcrumb_header: Option<String>,
990 focused_block: Option<FocusedBlock>,
991 next_scroll_position: NextScrollCursorCenterTopBottom,
992 addons: HashMap<TypeId, Box<dyn Addon>>,
993 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
994 load_diff_task: Option<Shared<Task<()>>>,
995 /// Whether we are temporarily displaying a diff other than git's
996 temporary_diff_override: bool,
997 selection_mark_mode: bool,
998 toggle_fold_multiple_buffers: Task<()>,
999 _scroll_cursor_center_top_bottom_task: Task<()>,
1000 serialize_selections: Task<()>,
1001 serialize_folds: Task<()>,
1002 mouse_cursor_hidden: bool,
1003 minimap: Option<Entity<Self>>,
1004 hide_mouse_mode: HideMouseMode,
1005 pub change_list: ChangeList,
1006 inline_value_cache: InlineValueCache,
1007}
1008
1009#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1010enum NextScrollCursorCenterTopBottom {
1011 #[default]
1012 Center,
1013 Top,
1014 Bottom,
1015}
1016
1017impl NextScrollCursorCenterTopBottom {
1018 fn next(&self) -> Self {
1019 match self {
1020 Self::Center => Self::Top,
1021 Self::Top => Self::Bottom,
1022 Self::Bottom => Self::Center,
1023 }
1024 }
1025}
1026
1027#[derive(Clone)]
1028pub struct EditorSnapshot {
1029 pub mode: EditorMode,
1030 show_gutter: bool,
1031 show_line_numbers: Option<bool>,
1032 show_git_diff_gutter: Option<bool>,
1033 show_runnables: Option<bool>,
1034 show_breakpoints: Option<bool>,
1035 git_blame_gutter_max_author_length: Option<usize>,
1036 pub display_snapshot: DisplaySnapshot,
1037 pub placeholder_text: Option<Arc<str>>,
1038 is_focused: bool,
1039 scroll_anchor: ScrollAnchor,
1040 ongoing_scroll: OngoingScroll,
1041 current_line_highlight: CurrentLineHighlight,
1042 gutter_hovered: bool,
1043}
1044
1045#[derive(Default, Debug, Clone, Copy)]
1046pub struct GutterDimensions {
1047 pub left_padding: Pixels,
1048 pub right_padding: Pixels,
1049 pub width: Pixels,
1050 pub margin: Pixels,
1051 pub git_blame_entries_width: Option<Pixels>,
1052}
1053
1054impl GutterDimensions {
1055 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1056 Self {
1057 margin: Self::default_gutter_margin(font_id, font_size, cx),
1058 ..Default::default()
1059 }
1060 }
1061
1062 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1063 -cx.text_system().descent(font_id, font_size)
1064 }
1065 /// The full width of the space taken up by the gutter.
1066 pub fn full_width(&self) -> Pixels {
1067 self.margin + self.width
1068 }
1069
1070 /// The width of the space reserved for the fold indicators,
1071 /// use alongside 'justify_end' and `gutter_width` to
1072 /// right align content with the line numbers
1073 pub fn fold_area_width(&self) -> Pixels {
1074 self.margin + self.right_padding
1075 }
1076}
1077
1078#[derive(Debug)]
1079pub struct RemoteSelection {
1080 pub replica_id: ReplicaId,
1081 pub selection: Selection<Anchor>,
1082 pub cursor_shape: CursorShape,
1083 pub collaborator_id: CollaboratorId,
1084 pub line_mode: bool,
1085 pub user_name: Option<SharedString>,
1086 pub color: PlayerColor,
1087}
1088
1089#[derive(Clone, Debug)]
1090struct SelectionHistoryEntry {
1091 selections: Arc<[Selection<Anchor>]>,
1092 select_next_state: Option<SelectNextState>,
1093 select_prev_state: Option<SelectNextState>,
1094 add_selections_state: Option<AddSelectionsState>,
1095}
1096
1097enum SelectionHistoryMode {
1098 Normal,
1099 Undoing,
1100 Redoing,
1101}
1102
1103#[derive(Clone, PartialEq, Eq, Hash)]
1104struct HoveredCursor {
1105 replica_id: u16,
1106 selection_id: usize,
1107}
1108
1109impl Default for SelectionHistoryMode {
1110 fn default() -> Self {
1111 Self::Normal
1112 }
1113}
1114
1115#[derive(Default)]
1116struct SelectionHistory {
1117 #[allow(clippy::type_complexity)]
1118 selections_by_transaction:
1119 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1120 mode: SelectionHistoryMode,
1121 undo_stack: VecDeque<SelectionHistoryEntry>,
1122 redo_stack: VecDeque<SelectionHistoryEntry>,
1123}
1124
1125impl SelectionHistory {
1126 fn insert_transaction(
1127 &mut self,
1128 transaction_id: TransactionId,
1129 selections: Arc<[Selection<Anchor>]>,
1130 ) {
1131 self.selections_by_transaction
1132 .insert(transaction_id, (selections, None));
1133 }
1134
1135 #[allow(clippy::type_complexity)]
1136 fn transaction(
1137 &self,
1138 transaction_id: TransactionId,
1139 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1140 self.selections_by_transaction.get(&transaction_id)
1141 }
1142
1143 #[allow(clippy::type_complexity)]
1144 fn transaction_mut(
1145 &mut self,
1146 transaction_id: TransactionId,
1147 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1148 self.selections_by_transaction.get_mut(&transaction_id)
1149 }
1150
1151 fn push(&mut self, entry: SelectionHistoryEntry) {
1152 if !entry.selections.is_empty() {
1153 match self.mode {
1154 SelectionHistoryMode::Normal => {
1155 self.push_undo(entry);
1156 self.redo_stack.clear();
1157 }
1158 SelectionHistoryMode::Undoing => self.push_redo(entry),
1159 SelectionHistoryMode::Redoing => self.push_undo(entry),
1160 }
1161 }
1162 }
1163
1164 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1165 if self
1166 .undo_stack
1167 .back()
1168 .map_or(true, |e| e.selections != entry.selections)
1169 {
1170 self.undo_stack.push_back(entry);
1171 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1172 self.undo_stack.pop_front();
1173 }
1174 }
1175 }
1176
1177 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1178 if self
1179 .redo_stack
1180 .back()
1181 .map_or(true, |e| e.selections != entry.selections)
1182 {
1183 self.redo_stack.push_back(entry);
1184 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1185 self.redo_stack.pop_front();
1186 }
1187 }
1188 }
1189}
1190
1191#[derive(Clone, Copy)]
1192pub struct RowHighlightOptions {
1193 pub autoscroll: bool,
1194 pub include_gutter: bool,
1195}
1196
1197impl Default for RowHighlightOptions {
1198 fn default() -> Self {
1199 Self {
1200 autoscroll: Default::default(),
1201 include_gutter: true,
1202 }
1203 }
1204}
1205
1206struct RowHighlight {
1207 index: usize,
1208 range: Range<Anchor>,
1209 color: Hsla,
1210 options: RowHighlightOptions,
1211 type_id: TypeId,
1212}
1213
1214#[derive(Clone, Debug)]
1215struct AddSelectionsState {
1216 above: bool,
1217 stack: Vec<usize>,
1218}
1219
1220#[derive(Clone)]
1221struct SelectNextState {
1222 query: AhoCorasick,
1223 wordwise: bool,
1224 done: bool,
1225}
1226
1227impl std::fmt::Debug for SelectNextState {
1228 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1229 f.debug_struct(std::any::type_name::<Self>())
1230 .field("wordwise", &self.wordwise)
1231 .field("done", &self.done)
1232 .finish()
1233 }
1234}
1235
1236#[derive(Debug)]
1237struct AutocloseRegion {
1238 selection_id: usize,
1239 range: Range<Anchor>,
1240 pair: BracketPair,
1241}
1242
1243#[derive(Debug)]
1244struct SnippetState {
1245 ranges: Vec<Vec<Range<Anchor>>>,
1246 active_index: usize,
1247 choices: Vec<Option<Vec<String>>>,
1248}
1249
1250#[doc(hidden)]
1251pub struct RenameState {
1252 pub range: Range<Anchor>,
1253 pub old_name: Arc<str>,
1254 pub editor: Entity<Editor>,
1255 block_id: CustomBlockId,
1256}
1257
1258struct InvalidationStack<T>(Vec<T>);
1259
1260struct RegisteredInlineCompletionProvider {
1261 provider: Arc<dyn InlineCompletionProviderHandle>,
1262 _subscription: Subscription,
1263}
1264
1265#[derive(Debug, PartialEq, Eq)]
1266pub struct ActiveDiagnosticGroup {
1267 pub active_range: Range<Anchor>,
1268 pub active_message: String,
1269 pub group_id: usize,
1270 pub blocks: HashSet<CustomBlockId>,
1271}
1272
1273#[derive(Debug, PartialEq, Eq)]
1274#[allow(clippy::large_enum_variant)]
1275pub(crate) enum ActiveDiagnostic {
1276 None,
1277 All,
1278 Group(ActiveDiagnosticGroup),
1279}
1280
1281#[derive(Serialize, Deserialize, Clone, Debug)]
1282pub struct ClipboardSelection {
1283 /// The number of bytes in this selection.
1284 pub len: usize,
1285 /// Whether this was a full-line selection.
1286 pub is_entire_line: bool,
1287 /// The indentation of the first line when this content was originally copied.
1288 pub first_line_indent: u32,
1289}
1290
1291// selections, scroll behavior, was newest selection reversed
1292type SelectSyntaxNodeHistoryState = (
1293 Box<[Selection<usize>]>,
1294 SelectSyntaxNodeScrollBehavior,
1295 bool,
1296);
1297
1298#[derive(Default)]
1299struct SelectSyntaxNodeHistory {
1300 stack: Vec<SelectSyntaxNodeHistoryState>,
1301 // disable temporarily to allow changing selections without losing the stack
1302 pub disable_clearing: bool,
1303}
1304
1305impl SelectSyntaxNodeHistory {
1306 pub fn try_clear(&mut self) {
1307 if !self.disable_clearing {
1308 self.stack.clear();
1309 }
1310 }
1311
1312 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1313 self.stack.push(selection);
1314 }
1315
1316 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1317 self.stack.pop()
1318 }
1319}
1320
1321enum SelectSyntaxNodeScrollBehavior {
1322 CursorTop,
1323 FitSelection,
1324 CursorBottom,
1325}
1326
1327#[derive(Debug)]
1328pub(crate) struct NavigationData {
1329 cursor_anchor: Anchor,
1330 cursor_position: Point,
1331 scroll_anchor: ScrollAnchor,
1332 scroll_top_row: u32,
1333}
1334
1335#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1336pub enum GotoDefinitionKind {
1337 Symbol,
1338 Declaration,
1339 Type,
1340 Implementation,
1341}
1342
1343#[derive(Debug, Clone)]
1344enum InlayHintRefreshReason {
1345 ModifiersChanged(bool),
1346 Toggle(bool),
1347 SettingsChange(InlayHintSettings),
1348 NewLinesShown,
1349 BufferEdited(HashSet<Arc<Language>>),
1350 RefreshRequested,
1351 ExcerptsRemoved(Vec<ExcerptId>),
1352}
1353
1354impl InlayHintRefreshReason {
1355 fn description(&self) -> &'static str {
1356 match self {
1357 Self::ModifiersChanged(_) => "modifiers changed",
1358 Self::Toggle(_) => "toggle",
1359 Self::SettingsChange(_) => "settings change",
1360 Self::NewLinesShown => "new lines shown",
1361 Self::BufferEdited(_) => "buffer edited",
1362 Self::RefreshRequested => "refresh requested",
1363 Self::ExcerptsRemoved(_) => "excerpts removed",
1364 }
1365 }
1366}
1367
1368pub enum FormatTarget {
1369 Buffers,
1370 Ranges(Vec<Range<MultiBufferPoint>>),
1371}
1372
1373pub(crate) struct FocusedBlock {
1374 id: BlockId,
1375 focus_handle: WeakFocusHandle,
1376}
1377
1378#[derive(Clone)]
1379enum JumpData {
1380 MultiBufferRow {
1381 row: MultiBufferRow,
1382 line_offset_from_top: u32,
1383 },
1384 MultiBufferPoint {
1385 excerpt_id: ExcerptId,
1386 position: Point,
1387 anchor: text::Anchor,
1388 line_offset_from_top: u32,
1389 },
1390}
1391
1392pub enum MultibufferSelectionMode {
1393 First,
1394 All,
1395}
1396
1397#[derive(Clone, Copy, Debug, Default)]
1398pub struct RewrapOptions {
1399 pub override_language_settings: bool,
1400 pub preserve_existing_whitespace: bool,
1401}
1402
1403impl Editor {
1404 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1405 let buffer = cx.new(|cx| Buffer::local("", cx));
1406 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1407 Self::new(
1408 EditorMode::SingleLine { auto_width: false },
1409 buffer,
1410 None,
1411 window,
1412 cx,
1413 )
1414 }
1415
1416 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1417 let buffer = cx.new(|cx| Buffer::local("", cx));
1418 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1419 Self::new(EditorMode::full(), buffer, None, window, cx)
1420 }
1421
1422 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1423 let buffer = cx.new(|cx| Buffer::local("", cx));
1424 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1425 Self::new(
1426 EditorMode::SingleLine { auto_width: true },
1427 buffer,
1428 None,
1429 window,
1430 cx,
1431 )
1432 }
1433
1434 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1435 let buffer = cx.new(|cx| Buffer::local("", cx));
1436 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1437 Self::new(
1438 EditorMode::AutoHeight { max_lines },
1439 buffer,
1440 None,
1441 window,
1442 cx,
1443 )
1444 }
1445
1446 pub fn for_buffer(
1447 buffer: Entity<Buffer>,
1448 project: Option<Entity<Project>>,
1449 window: &mut Window,
1450 cx: &mut Context<Self>,
1451 ) -> Self {
1452 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1453 Self::new(EditorMode::full(), buffer, project, window, cx)
1454 }
1455
1456 pub fn for_multibuffer(
1457 buffer: Entity<MultiBuffer>,
1458 project: Option<Entity<Project>>,
1459 window: &mut Window,
1460 cx: &mut Context<Self>,
1461 ) -> Self {
1462 Self::new(EditorMode::full(), buffer, project, window, cx)
1463 }
1464
1465 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1466 let mut clone = Self::new(
1467 self.mode.clone(),
1468 self.buffer.clone(),
1469 self.project.clone(),
1470 window,
1471 cx,
1472 );
1473 self.display_map.update(cx, |display_map, cx| {
1474 let snapshot = display_map.snapshot(cx);
1475 clone.display_map.update(cx, |display_map, cx| {
1476 display_map.set_state(&snapshot, cx);
1477 });
1478 });
1479 clone.folds_did_change(cx);
1480 clone.selections.clone_state(&self.selections);
1481 clone.scroll_manager.clone_state(&self.scroll_manager);
1482 clone.searchable = self.searchable;
1483 clone.read_only = self.read_only;
1484 clone
1485 }
1486
1487 pub fn new(
1488 mode: EditorMode,
1489 buffer: Entity<MultiBuffer>,
1490 project: Option<Entity<Project>>,
1491 window: &mut Window,
1492 cx: &mut Context<Self>,
1493 ) -> Self {
1494 Editor::new_internal(mode, buffer, project, None, window, cx)
1495 }
1496
1497 fn new_internal(
1498 mode: EditorMode,
1499 buffer: Entity<MultiBuffer>,
1500 project: Option<Entity<Project>>,
1501 display_map: Option<Entity<DisplayMap>>,
1502 window: &mut Window,
1503 cx: &mut Context<Self>,
1504 ) -> Self {
1505 debug_assert!(
1506 display_map.is_none() || mode.is_minimap(),
1507 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1508 );
1509 let style = window.text_style();
1510 let font_size = style.font_size.to_pixels(window.rem_size());
1511 let editor = cx.entity().downgrade();
1512 let fold_placeholder = FoldPlaceholder {
1513 constrain_width: true,
1514 render: Arc::new(move |fold_id, fold_range, cx| {
1515 let editor = editor.clone();
1516 div()
1517 .id(fold_id)
1518 .bg(cx.theme().colors().ghost_element_background)
1519 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1520 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1521 .rounded_xs()
1522 .size_full()
1523 .cursor_pointer()
1524 .child("⋯")
1525 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1526 .on_click(move |_, _window, cx| {
1527 editor
1528 .update(cx, |editor, cx| {
1529 editor.unfold_ranges(
1530 &[fold_range.start..fold_range.end],
1531 true,
1532 false,
1533 cx,
1534 );
1535 cx.stop_propagation();
1536 })
1537 .ok();
1538 })
1539 .into_any()
1540 }),
1541 merge_adjacent: true,
1542 ..Default::default()
1543 };
1544 let display_map = display_map.unwrap_or_else(|| {
1545 cx.new(|cx| {
1546 DisplayMap::new(
1547 buffer.clone(),
1548 style.font(),
1549 font_size,
1550 None,
1551 FILE_HEADER_HEIGHT,
1552 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1553 fold_placeholder,
1554 cx,
1555 )
1556 })
1557 });
1558
1559 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1560
1561 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1562
1563 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1564 .then(|| language_settings::SoftWrap::None);
1565
1566 let mut project_subscriptions = Vec::new();
1567 if mode.is_full() {
1568 if let Some(project) = project.as_ref() {
1569 project_subscriptions.push(cx.subscribe_in(
1570 project,
1571 window,
1572 |editor, _, event, window, cx| match event {
1573 project::Event::RefreshCodeLens => {
1574 // we always query lens with actions, without storing them, always refreshing them
1575 }
1576 project::Event::RefreshInlayHints => {
1577 editor
1578 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1579 }
1580 project::Event::SnippetEdit(id, snippet_edits) => {
1581 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1582 let focus_handle = editor.focus_handle(cx);
1583 if focus_handle.is_focused(window) {
1584 let snapshot = buffer.read(cx).snapshot();
1585 for (range, snippet) in snippet_edits {
1586 let editor_range =
1587 language::range_from_lsp(*range).to_offset(&snapshot);
1588 editor
1589 .insert_snippet(
1590 &[editor_range],
1591 snippet.clone(),
1592 window,
1593 cx,
1594 )
1595 .ok();
1596 }
1597 }
1598 }
1599 }
1600 _ => {}
1601 },
1602 ));
1603 if let Some(task_inventory) = project
1604 .read(cx)
1605 .task_store()
1606 .read(cx)
1607 .task_inventory()
1608 .cloned()
1609 {
1610 project_subscriptions.push(cx.observe_in(
1611 &task_inventory,
1612 window,
1613 |editor, _, window, cx| {
1614 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1615 },
1616 ));
1617 };
1618
1619 project_subscriptions.push(cx.subscribe_in(
1620 &project.read(cx).breakpoint_store(),
1621 window,
1622 |editor, _, event, window, cx| match event {
1623 BreakpointStoreEvent::ClearDebugLines => {
1624 editor.clear_row_highlights::<ActiveDebugLine>();
1625 editor.refresh_inline_values(cx);
1626 }
1627 BreakpointStoreEvent::SetDebugLine => {
1628 if editor.go_to_active_debug_line(window, cx) {
1629 cx.stop_propagation();
1630 }
1631
1632 editor.refresh_inline_values(cx);
1633 }
1634 _ => {}
1635 },
1636 ));
1637 }
1638 }
1639
1640 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1641
1642 let inlay_hint_settings =
1643 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1644 let focus_handle = cx.focus_handle();
1645 cx.on_focus(&focus_handle, window, Self::handle_focus)
1646 .detach();
1647 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1648 .detach();
1649 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1650 .detach();
1651 cx.on_blur(&focus_handle, window, Self::handle_blur)
1652 .detach();
1653
1654 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1655 Some(false)
1656 } else {
1657 None
1658 };
1659
1660 let breakpoint_store = match (&mode, project.as_ref()) {
1661 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1662 _ => None,
1663 };
1664
1665 let mut code_action_providers = Vec::new();
1666 let mut load_uncommitted_diff = None;
1667 if let Some(project) = project.clone() {
1668 load_uncommitted_diff = Some(
1669 update_uncommitted_diff_for_buffer(
1670 cx.entity(),
1671 &project,
1672 buffer.read(cx).all_buffers(),
1673 buffer.clone(),
1674 cx,
1675 )
1676 .shared(),
1677 );
1678 code_action_providers.push(Rc::new(project) as Rc<_>);
1679 }
1680
1681 let full_mode = mode.is_full();
1682
1683 let mut this = Self {
1684 focus_handle,
1685 show_cursor_when_unfocused: false,
1686 last_focused_descendant: None,
1687 buffer: buffer.clone(),
1688 display_map: display_map.clone(),
1689 selections,
1690 scroll_manager: ScrollManager::new(cx),
1691 columnar_selection_tail: None,
1692 add_selections_state: None,
1693 select_next_state: None,
1694 select_prev_state: None,
1695 selection_history: Default::default(),
1696 autoclose_regions: Default::default(),
1697 snippet_stack: Default::default(),
1698 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1699 ime_transaction: Default::default(),
1700 active_diagnostics: ActiveDiagnostic::None,
1701 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1702 inline_diagnostics_update: Task::ready(()),
1703 inline_diagnostics: Vec::new(),
1704 soft_wrap_mode_override,
1705 hard_wrap: None,
1706 completion_provider: project.clone().map(|project| Box::new(project) as _),
1707 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1708 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1709 project,
1710 blink_manager: blink_manager.clone(),
1711 show_local_selections: true,
1712 show_scrollbars: full_mode,
1713 show_minimap: full_mode,
1714 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1715 show_gutter: mode.is_full(),
1716 show_line_numbers: None,
1717 use_relative_line_numbers: None,
1718 disable_expand_excerpt_buttons: false,
1719 show_git_diff_gutter: None,
1720 show_code_actions: None,
1721 show_runnables: None,
1722 show_breakpoints: None,
1723 show_wrap_guides: None,
1724 show_indent_guides,
1725 placeholder_text: None,
1726 highlight_order: 0,
1727 highlighted_rows: HashMap::default(),
1728 background_highlights: Default::default(),
1729 gutter_highlights: TreeMap::default(),
1730 scrollbar_marker_state: ScrollbarMarkerState::default(),
1731 active_indent_guides_state: ActiveIndentGuidesState::default(),
1732 nav_history: None,
1733 context_menu: RefCell::new(None),
1734 context_menu_options: None,
1735 mouse_context_menu: None,
1736 completion_tasks: Default::default(),
1737 inline_blame_popover: Default::default(),
1738 signature_help_state: SignatureHelpState::default(),
1739 auto_signature_help: None,
1740 find_all_references_task_sources: Vec::new(),
1741 next_completion_id: 0,
1742 next_inlay_id: 0,
1743 code_action_providers,
1744 available_code_actions: Default::default(),
1745 code_actions_task: Default::default(),
1746 quick_selection_highlight_task: Default::default(),
1747 debounced_selection_highlight_task: Default::default(),
1748 document_highlights_task: Default::default(),
1749 linked_editing_range_task: Default::default(),
1750 pending_rename: Default::default(),
1751 searchable: true,
1752 cursor_shape: EditorSettings::get_global(cx)
1753 .cursor_shape
1754 .unwrap_or_default(),
1755 current_line_highlight: None,
1756 autoindent_mode: Some(AutoindentMode::EachLine),
1757 collapse_matches: false,
1758 workspace: None,
1759 input_enabled: true,
1760 use_modal_editing: mode.is_full(),
1761 read_only: mode.is_minimap(),
1762 use_autoclose: true,
1763 use_auto_surround: true,
1764 auto_replace_emoji_shortcode: false,
1765 jsx_tag_auto_close_enabled_in_any_buffer: false,
1766 leader_id: None,
1767 remote_id: None,
1768 hover_state: Default::default(),
1769 pending_mouse_down: None,
1770 hovered_link_state: Default::default(),
1771 edit_prediction_provider: None,
1772 active_inline_completion: None,
1773 stale_inline_completion_in_menu: None,
1774 edit_prediction_preview: EditPredictionPreview::Inactive {
1775 released_too_fast: false,
1776 },
1777 inline_diagnostics_enabled: mode.is_full(),
1778 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1779 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1780
1781 gutter_hovered: false,
1782 pixel_position_of_newest_cursor: None,
1783 last_bounds: None,
1784 last_position_map: None,
1785 expect_bounds_change: None,
1786 gutter_dimensions: GutterDimensions::default(),
1787 style: None,
1788 show_cursor_names: false,
1789 hovered_cursors: Default::default(),
1790 next_editor_action_id: EditorActionId::default(),
1791 editor_actions: Rc::default(),
1792 inline_completions_hidden_for_vim_mode: false,
1793 show_inline_completions_override: None,
1794 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1795 edit_prediction_settings: EditPredictionSettings::Disabled,
1796 edit_prediction_indent_conflict: false,
1797 edit_prediction_requires_modifier_in_indent_conflict: true,
1798 custom_context_menu: None,
1799 show_git_blame_gutter: false,
1800 show_git_blame_inline: false,
1801 show_selection_menu: None,
1802 show_git_blame_inline_delay_task: None,
1803 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1804 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1805 serialize_dirty_buffers: !mode.is_minimap()
1806 && ProjectSettings::get_global(cx)
1807 .session
1808 .restore_unsaved_buffers,
1809 blame: None,
1810 blame_subscription: None,
1811 tasks: Default::default(),
1812
1813 breakpoint_store,
1814 gutter_breakpoint_indicator: (None, None),
1815 _subscriptions: vec![
1816 cx.observe(&buffer, Self::on_buffer_changed),
1817 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1818 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1819 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1820 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1821 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1822 cx.observe_window_activation(window, |editor, window, cx| {
1823 let active = window.is_window_active();
1824 editor.blink_manager.update(cx, |blink_manager, cx| {
1825 if active {
1826 blink_manager.enable(cx);
1827 } else {
1828 blink_manager.disable(cx);
1829 }
1830 });
1831 }),
1832 ],
1833 tasks_update_task: None,
1834 linked_edit_ranges: Default::default(),
1835 in_project_search: false,
1836 previous_search_ranges: None,
1837 breadcrumb_header: None,
1838 focused_block: None,
1839 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1840 addons: HashMap::default(),
1841 registered_buffers: HashMap::default(),
1842 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1843 selection_mark_mode: false,
1844 toggle_fold_multiple_buffers: Task::ready(()),
1845 serialize_selections: Task::ready(()),
1846 serialize_folds: Task::ready(()),
1847 text_style_refinement: None,
1848 load_diff_task: load_uncommitted_diff,
1849 temporary_diff_override: false,
1850 mouse_cursor_hidden: false,
1851 minimap: None,
1852 hide_mouse_mode: EditorSettings::get_global(cx)
1853 .hide_mouse
1854 .unwrap_or_default(),
1855 change_list: ChangeList::new(),
1856 mode,
1857 };
1858 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1859 this._subscriptions
1860 .push(cx.observe(breakpoints, |_, _, cx| {
1861 cx.notify();
1862 }));
1863 }
1864 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1865 this._subscriptions.extend(project_subscriptions);
1866
1867 this._subscriptions.push(cx.subscribe_in(
1868 &cx.entity(),
1869 window,
1870 |editor, _, e: &EditorEvent, window, cx| match e {
1871 EditorEvent::ScrollPositionChanged { local, .. } => {
1872 if *local {
1873 let new_anchor = editor.scroll_manager.anchor();
1874 let snapshot = editor.snapshot(window, cx);
1875 editor.update_restoration_data(cx, move |data| {
1876 data.scroll_position = (
1877 new_anchor.top_row(&snapshot.buffer_snapshot),
1878 new_anchor.offset,
1879 );
1880 });
1881 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
1882 editor.inline_blame_popover.take();
1883 }
1884 }
1885 EditorEvent::Edited { .. } => {
1886 if !vim_enabled(cx) {
1887 let (map, selections) = editor.selections.all_adjusted_display(cx);
1888 let pop_state = editor
1889 .change_list
1890 .last()
1891 .map(|previous| {
1892 previous.len() == selections.len()
1893 && previous.iter().enumerate().all(|(ix, p)| {
1894 p.to_display_point(&map).row()
1895 == selections[ix].head().row()
1896 })
1897 })
1898 .unwrap_or(false);
1899 let new_positions = selections
1900 .into_iter()
1901 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
1902 .collect();
1903 editor
1904 .change_list
1905 .push_to_change_list(pop_state, new_positions);
1906 }
1907 }
1908 _ => (),
1909 },
1910 ));
1911
1912 if let Some(dap_store) = this
1913 .project
1914 .as_ref()
1915 .map(|project| project.read(cx).dap_store())
1916 {
1917 let weak_editor = cx.weak_entity();
1918
1919 this._subscriptions
1920 .push(
1921 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
1922 let session_entity = cx.entity();
1923 weak_editor
1924 .update(cx, |editor, cx| {
1925 editor._subscriptions.push(
1926 cx.subscribe(&session_entity, Self::on_debug_session_event),
1927 );
1928 })
1929 .ok();
1930 }),
1931 );
1932
1933 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
1934 this._subscriptions
1935 .push(cx.subscribe(&session, Self::on_debug_session_event));
1936 }
1937 }
1938
1939 this.end_selection(window, cx);
1940 this.scroll_manager.show_scrollbars(window, cx);
1941 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1942
1943 if full_mode {
1944 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1945 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1946
1947 if this.git_blame_inline_enabled {
1948 this.start_git_blame_inline(false, window, cx);
1949 }
1950
1951 this.go_to_active_debug_line(window, cx);
1952
1953 if let Some(buffer) = buffer.read(cx).as_singleton() {
1954 if let Some(project) = this.project.as_ref() {
1955 let handle = project.update(cx, |project, cx| {
1956 project.register_buffer_with_language_servers(&buffer, cx)
1957 });
1958 this.registered_buffers
1959 .insert(buffer.read(cx).remote_id(), handle);
1960 }
1961 }
1962
1963 this.minimap = this.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
1964 }
1965
1966 this.report_editor_event("Editor Opened", None, cx);
1967 this
1968 }
1969
1970 pub fn deploy_mouse_context_menu(
1971 &mut self,
1972 position: gpui::Point<Pixels>,
1973 context_menu: Entity<ContextMenu>,
1974 window: &mut Window,
1975 cx: &mut Context<Self>,
1976 ) {
1977 self.mouse_context_menu = Some(MouseContextMenu::new(
1978 self,
1979 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
1980 context_menu,
1981 window,
1982 cx,
1983 ));
1984 }
1985
1986 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
1987 self.mouse_context_menu
1988 .as_ref()
1989 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
1990 }
1991
1992 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
1993 self.key_context_internal(self.has_active_inline_completion(), window, cx)
1994 }
1995
1996 fn key_context_internal(
1997 &self,
1998 has_active_edit_prediction: bool,
1999 window: &Window,
2000 cx: &App,
2001 ) -> KeyContext {
2002 let mut key_context = KeyContext::new_with_defaults();
2003 key_context.add("Editor");
2004 let mode = match self.mode {
2005 EditorMode::SingleLine { .. } => "single_line",
2006 EditorMode::AutoHeight { .. } => "auto_height",
2007 EditorMode::Minimap { .. } => "minimap",
2008 EditorMode::Full { .. } => "full",
2009 };
2010
2011 if EditorSettings::jupyter_enabled(cx) {
2012 key_context.add("jupyter");
2013 }
2014
2015 key_context.set("mode", mode);
2016 if self.pending_rename.is_some() {
2017 key_context.add("renaming");
2018 }
2019
2020 match self.context_menu.borrow().as_ref() {
2021 Some(CodeContextMenu::Completions(_)) => {
2022 key_context.add("menu");
2023 key_context.add("showing_completions");
2024 }
2025 Some(CodeContextMenu::CodeActions(_)) => {
2026 key_context.add("menu");
2027 key_context.add("showing_code_actions")
2028 }
2029 None => {}
2030 }
2031
2032 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2033 if !self.focus_handle(cx).contains_focused(window, cx)
2034 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2035 {
2036 for addon in self.addons.values() {
2037 addon.extend_key_context(&mut key_context, cx)
2038 }
2039 }
2040
2041 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2042 if let Some(extension) = singleton_buffer
2043 .read(cx)
2044 .file()
2045 .and_then(|file| file.path().extension()?.to_str())
2046 {
2047 key_context.set("extension", extension.to_string());
2048 }
2049 } else {
2050 key_context.add("multibuffer");
2051 }
2052
2053 if has_active_edit_prediction {
2054 if self.edit_prediction_in_conflict() {
2055 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2056 } else {
2057 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2058 key_context.add("copilot_suggestion");
2059 }
2060 }
2061
2062 if self.selection_mark_mode {
2063 key_context.add("selection_mode");
2064 }
2065
2066 key_context
2067 }
2068
2069 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2070 self.mouse_cursor_hidden = match origin {
2071 HideMouseCursorOrigin::TypingAction => {
2072 matches!(
2073 self.hide_mouse_mode,
2074 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2075 )
2076 }
2077 HideMouseCursorOrigin::MovementAction => {
2078 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2079 }
2080 };
2081 }
2082
2083 pub fn edit_prediction_in_conflict(&self) -> bool {
2084 if !self.show_edit_predictions_in_menu() {
2085 return false;
2086 }
2087
2088 let showing_completions = self
2089 .context_menu
2090 .borrow()
2091 .as_ref()
2092 .map_or(false, |context| {
2093 matches!(context, CodeContextMenu::Completions(_))
2094 });
2095
2096 showing_completions
2097 || self.edit_prediction_requires_modifier()
2098 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2099 // bindings to insert tab characters.
2100 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2101 }
2102
2103 pub fn accept_edit_prediction_keybind(
2104 &self,
2105 window: &Window,
2106 cx: &App,
2107 ) -> AcceptEditPredictionBinding {
2108 let key_context = self.key_context_internal(true, window, cx);
2109 let in_conflict = self.edit_prediction_in_conflict();
2110
2111 AcceptEditPredictionBinding(
2112 window
2113 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2114 .into_iter()
2115 .filter(|binding| {
2116 !in_conflict
2117 || binding
2118 .keystrokes()
2119 .first()
2120 .map_or(false, |keystroke| keystroke.modifiers.modified())
2121 })
2122 .rev()
2123 .min_by_key(|binding| {
2124 binding
2125 .keystrokes()
2126 .first()
2127 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
2128 }),
2129 )
2130 }
2131
2132 pub fn new_file(
2133 workspace: &mut Workspace,
2134 _: &workspace::NewFile,
2135 window: &mut Window,
2136 cx: &mut Context<Workspace>,
2137 ) {
2138 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2139 "Failed to create buffer",
2140 window,
2141 cx,
2142 |e, _, _| match e.error_code() {
2143 ErrorCode::RemoteUpgradeRequired => Some(format!(
2144 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2145 e.error_tag("required").unwrap_or("the latest version")
2146 )),
2147 _ => None,
2148 },
2149 );
2150 }
2151
2152 pub fn new_in_workspace(
2153 workspace: &mut Workspace,
2154 window: &mut Window,
2155 cx: &mut Context<Workspace>,
2156 ) -> Task<Result<Entity<Editor>>> {
2157 let project = workspace.project().clone();
2158 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2159
2160 cx.spawn_in(window, async move |workspace, cx| {
2161 let buffer = create.await?;
2162 workspace.update_in(cx, |workspace, window, cx| {
2163 let editor =
2164 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2165 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2166 editor
2167 })
2168 })
2169 }
2170
2171 fn new_file_vertical(
2172 workspace: &mut Workspace,
2173 _: &workspace::NewFileSplitVertical,
2174 window: &mut Window,
2175 cx: &mut Context<Workspace>,
2176 ) {
2177 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2178 }
2179
2180 fn new_file_horizontal(
2181 workspace: &mut Workspace,
2182 _: &workspace::NewFileSplitHorizontal,
2183 window: &mut Window,
2184 cx: &mut Context<Workspace>,
2185 ) {
2186 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2187 }
2188
2189 fn new_file_in_direction(
2190 workspace: &mut Workspace,
2191 direction: SplitDirection,
2192 window: &mut Window,
2193 cx: &mut Context<Workspace>,
2194 ) {
2195 let project = workspace.project().clone();
2196 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2197
2198 cx.spawn_in(window, async move |workspace, cx| {
2199 let buffer = create.await?;
2200 workspace.update_in(cx, move |workspace, window, cx| {
2201 workspace.split_item(
2202 direction,
2203 Box::new(
2204 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2205 ),
2206 window,
2207 cx,
2208 )
2209 })?;
2210 anyhow::Ok(())
2211 })
2212 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2213 match e.error_code() {
2214 ErrorCode::RemoteUpgradeRequired => Some(format!(
2215 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2216 e.error_tag("required").unwrap_or("the latest version")
2217 )),
2218 _ => None,
2219 }
2220 });
2221 }
2222
2223 pub fn leader_id(&self) -> Option<CollaboratorId> {
2224 self.leader_id
2225 }
2226
2227 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2228 &self.buffer
2229 }
2230
2231 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2232 self.workspace.as_ref()?.0.upgrade()
2233 }
2234
2235 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2236 self.buffer().read(cx).title(cx)
2237 }
2238
2239 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2240 let git_blame_gutter_max_author_length = self
2241 .render_git_blame_gutter(cx)
2242 .then(|| {
2243 if let Some(blame) = self.blame.as_ref() {
2244 let max_author_length =
2245 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2246 Some(max_author_length)
2247 } else {
2248 None
2249 }
2250 })
2251 .flatten();
2252
2253 EditorSnapshot {
2254 mode: self.mode.clone(),
2255 show_gutter: self.show_gutter,
2256 show_line_numbers: self.show_line_numbers,
2257 show_git_diff_gutter: self.show_git_diff_gutter,
2258 show_runnables: self.show_runnables,
2259 show_breakpoints: self.show_breakpoints,
2260 git_blame_gutter_max_author_length,
2261 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2262 scroll_anchor: self.scroll_manager.anchor(),
2263 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2264 placeholder_text: self.placeholder_text.clone(),
2265 is_focused: self.focus_handle.is_focused(window),
2266 current_line_highlight: self
2267 .current_line_highlight
2268 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2269 gutter_hovered: self.gutter_hovered,
2270 }
2271 }
2272
2273 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2274 self.buffer.read(cx).language_at(point, cx)
2275 }
2276
2277 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2278 self.buffer.read(cx).read(cx).file_at(point).cloned()
2279 }
2280
2281 pub fn active_excerpt(
2282 &self,
2283 cx: &App,
2284 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2285 self.buffer
2286 .read(cx)
2287 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2288 }
2289
2290 pub fn mode(&self) -> &EditorMode {
2291 &self.mode
2292 }
2293
2294 pub fn set_mode(&mut self, mode: EditorMode) {
2295 self.mode = mode;
2296 }
2297
2298 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2299 self.collaboration_hub.as_deref()
2300 }
2301
2302 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2303 self.collaboration_hub = Some(hub);
2304 }
2305
2306 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2307 self.in_project_search = in_project_search;
2308 }
2309
2310 pub fn set_custom_context_menu(
2311 &mut self,
2312 f: impl 'static
2313 + Fn(
2314 &mut Self,
2315 DisplayPoint,
2316 &mut Window,
2317 &mut Context<Self>,
2318 ) -> Option<Entity<ui::ContextMenu>>,
2319 ) {
2320 self.custom_context_menu = Some(Box::new(f))
2321 }
2322
2323 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
2324 self.completion_provider = provider;
2325 }
2326
2327 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2328 self.semantics_provider.clone()
2329 }
2330
2331 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2332 self.semantics_provider = provider;
2333 }
2334
2335 pub fn set_edit_prediction_provider<T>(
2336 &mut self,
2337 provider: Option<Entity<T>>,
2338 window: &mut Window,
2339 cx: &mut Context<Self>,
2340 ) where
2341 T: EditPredictionProvider,
2342 {
2343 self.edit_prediction_provider =
2344 provider.map(|provider| RegisteredInlineCompletionProvider {
2345 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2346 if this.focus_handle.is_focused(window) {
2347 this.update_visible_inline_completion(window, cx);
2348 }
2349 }),
2350 provider: Arc::new(provider),
2351 });
2352 self.update_edit_prediction_settings(cx);
2353 self.refresh_inline_completion(false, false, window, cx);
2354 }
2355
2356 pub fn placeholder_text(&self) -> Option<&str> {
2357 self.placeholder_text.as_deref()
2358 }
2359
2360 pub fn set_placeholder_text(
2361 &mut self,
2362 placeholder_text: impl Into<Arc<str>>,
2363 cx: &mut Context<Self>,
2364 ) {
2365 let placeholder_text = Some(placeholder_text.into());
2366 if self.placeholder_text != placeholder_text {
2367 self.placeholder_text = placeholder_text;
2368 cx.notify();
2369 }
2370 }
2371
2372 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2373 self.cursor_shape = cursor_shape;
2374
2375 // Disrupt blink for immediate user feedback that the cursor shape has changed
2376 self.blink_manager.update(cx, BlinkManager::show_cursor);
2377
2378 cx.notify();
2379 }
2380
2381 pub fn set_current_line_highlight(
2382 &mut self,
2383 current_line_highlight: Option<CurrentLineHighlight>,
2384 ) {
2385 self.current_line_highlight = current_line_highlight;
2386 }
2387
2388 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2389 self.collapse_matches = collapse_matches;
2390 }
2391
2392 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2393 let buffers = self.buffer.read(cx).all_buffers();
2394 let Some(project) = self.project.as_ref() else {
2395 return;
2396 };
2397 project.update(cx, |project, cx| {
2398 for buffer in buffers {
2399 self.registered_buffers
2400 .entry(buffer.read(cx).remote_id())
2401 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2402 }
2403 })
2404 }
2405
2406 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2407 if self.collapse_matches {
2408 return range.start..range.start;
2409 }
2410 range.clone()
2411 }
2412
2413 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2414 if self.display_map.read(cx).clip_at_line_ends != clip {
2415 self.display_map
2416 .update(cx, |map, _| map.clip_at_line_ends = clip);
2417 }
2418 }
2419
2420 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2421 self.input_enabled = input_enabled;
2422 }
2423
2424 pub fn set_inline_completions_hidden_for_vim_mode(
2425 &mut self,
2426 hidden: bool,
2427 window: &mut Window,
2428 cx: &mut Context<Self>,
2429 ) {
2430 if hidden != self.inline_completions_hidden_for_vim_mode {
2431 self.inline_completions_hidden_for_vim_mode = hidden;
2432 if hidden {
2433 self.update_visible_inline_completion(window, cx);
2434 } else {
2435 self.refresh_inline_completion(true, false, window, cx);
2436 }
2437 }
2438 }
2439
2440 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2441 self.menu_inline_completions_policy = value;
2442 }
2443
2444 pub fn set_autoindent(&mut self, autoindent: bool) {
2445 if autoindent {
2446 self.autoindent_mode = Some(AutoindentMode::EachLine);
2447 } else {
2448 self.autoindent_mode = None;
2449 }
2450 }
2451
2452 pub fn read_only(&self, cx: &App) -> bool {
2453 self.read_only || self.buffer.read(cx).read_only()
2454 }
2455
2456 pub fn set_read_only(&mut self, read_only: bool) {
2457 self.read_only = read_only;
2458 }
2459
2460 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2461 self.use_autoclose = autoclose;
2462 }
2463
2464 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2465 self.use_auto_surround = auto_surround;
2466 }
2467
2468 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2469 self.auto_replace_emoji_shortcode = auto_replace;
2470 }
2471
2472 pub fn toggle_edit_predictions(
2473 &mut self,
2474 _: &ToggleEditPrediction,
2475 window: &mut Window,
2476 cx: &mut Context<Self>,
2477 ) {
2478 if self.show_inline_completions_override.is_some() {
2479 self.set_show_edit_predictions(None, window, cx);
2480 } else {
2481 let show_edit_predictions = !self.edit_predictions_enabled();
2482 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2483 }
2484 }
2485
2486 pub fn set_show_edit_predictions(
2487 &mut self,
2488 show_edit_predictions: Option<bool>,
2489 window: &mut Window,
2490 cx: &mut Context<Self>,
2491 ) {
2492 self.show_inline_completions_override = show_edit_predictions;
2493 self.update_edit_prediction_settings(cx);
2494
2495 if let Some(false) = show_edit_predictions {
2496 self.discard_inline_completion(false, cx);
2497 } else {
2498 self.refresh_inline_completion(false, true, window, cx);
2499 }
2500 }
2501
2502 fn inline_completions_disabled_in_scope(
2503 &self,
2504 buffer: &Entity<Buffer>,
2505 buffer_position: language::Anchor,
2506 cx: &App,
2507 ) -> bool {
2508 let snapshot = buffer.read(cx).snapshot();
2509 let settings = snapshot.settings_at(buffer_position, cx);
2510
2511 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2512 return false;
2513 };
2514
2515 scope.override_name().map_or(false, |scope_name| {
2516 settings
2517 .edit_predictions_disabled_in
2518 .iter()
2519 .any(|s| s == scope_name)
2520 })
2521 }
2522
2523 pub fn set_use_modal_editing(&mut self, to: bool) {
2524 self.use_modal_editing = to;
2525 }
2526
2527 pub fn use_modal_editing(&self) -> bool {
2528 self.use_modal_editing
2529 }
2530
2531 fn selections_did_change(
2532 &mut self,
2533 local: bool,
2534 old_cursor_position: &Anchor,
2535 show_completions: bool,
2536 window: &mut Window,
2537 cx: &mut Context<Self>,
2538 ) {
2539 window.invalidate_character_coordinates();
2540
2541 // Copy selections to primary selection buffer
2542 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2543 if local {
2544 let selections = self.selections.all::<usize>(cx);
2545 let buffer_handle = self.buffer.read(cx).read(cx);
2546
2547 let mut text = String::new();
2548 for (index, selection) in selections.iter().enumerate() {
2549 let text_for_selection = buffer_handle
2550 .text_for_range(selection.start..selection.end)
2551 .collect::<String>();
2552
2553 text.push_str(&text_for_selection);
2554 if index != selections.len() - 1 {
2555 text.push('\n');
2556 }
2557 }
2558
2559 if !text.is_empty() {
2560 cx.write_to_primary(ClipboardItem::new_string(text));
2561 }
2562 }
2563
2564 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2565 self.buffer.update(cx, |buffer, cx| {
2566 buffer.set_active_selections(
2567 &self.selections.disjoint_anchors(),
2568 self.selections.line_mode,
2569 self.cursor_shape,
2570 cx,
2571 )
2572 });
2573 }
2574 let display_map = self
2575 .display_map
2576 .update(cx, |display_map, cx| display_map.snapshot(cx));
2577 let buffer = &display_map.buffer_snapshot;
2578 self.add_selections_state = None;
2579 self.select_next_state = None;
2580 self.select_prev_state = None;
2581 self.select_syntax_node_history.try_clear();
2582 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2583 self.snippet_stack
2584 .invalidate(&self.selections.disjoint_anchors(), buffer);
2585 self.take_rename(false, window, cx);
2586
2587 let new_cursor_position = self.selections.newest_anchor().head();
2588
2589 self.push_to_nav_history(
2590 *old_cursor_position,
2591 Some(new_cursor_position.to_point(buffer)),
2592 false,
2593 cx,
2594 );
2595
2596 if local {
2597 let new_cursor_position = self.selections.newest_anchor().head();
2598 let mut context_menu = self.context_menu.borrow_mut();
2599 let completion_menu = match context_menu.as_ref() {
2600 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2601 _ => {
2602 *context_menu = None;
2603 None
2604 }
2605 };
2606 if let Some(buffer_id) = new_cursor_position.buffer_id {
2607 if !self.registered_buffers.contains_key(&buffer_id) {
2608 if let Some(project) = self.project.as_ref() {
2609 project.update(cx, |project, cx| {
2610 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2611 return;
2612 };
2613 self.registered_buffers.insert(
2614 buffer_id,
2615 project.register_buffer_with_language_servers(&buffer, cx),
2616 );
2617 })
2618 }
2619 }
2620 }
2621
2622 if let Some(completion_menu) = completion_menu {
2623 let cursor_position = new_cursor_position.to_offset(buffer);
2624 let (word_range, kind) =
2625 buffer.surrounding_word(completion_menu.initial_position, true);
2626 if kind == Some(CharKind::Word)
2627 && word_range.to_inclusive().contains(&cursor_position)
2628 {
2629 let mut completion_menu = completion_menu.clone();
2630 drop(context_menu);
2631
2632 let query = Self::completion_query(buffer, cursor_position);
2633 cx.spawn(async move |this, cx| {
2634 completion_menu
2635 .filter(query.as_deref(), cx.background_executor().clone())
2636 .await;
2637
2638 this.update(cx, |this, cx| {
2639 let mut context_menu = this.context_menu.borrow_mut();
2640 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2641 else {
2642 return;
2643 };
2644
2645 if menu.id > completion_menu.id {
2646 return;
2647 }
2648
2649 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2650 drop(context_menu);
2651 cx.notify();
2652 })
2653 })
2654 .detach();
2655
2656 if show_completions {
2657 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2658 }
2659 } else {
2660 drop(context_menu);
2661 self.hide_context_menu(window, cx);
2662 }
2663 } else {
2664 drop(context_menu);
2665 }
2666
2667 hide_hover(self, cx);
2668
2669 if old_cursor_position.to_display_point(&display_map).row()
2670 != new_cursor_position.to_display_point(&display_map).row()
2671 {
2672 self.available_code_actions.take();
2673 }
2674 self.refresh_code_actions(window, cx);
2675 self.refresh_document_highlights(cx);
2676 self.refresh_selected_text_highlights(false, window, cx);
2677 refresh_matching_bracket_highlights(self, window, cx);
2678 self.update_visible_inline_completion(window, cx);
2679 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2680 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2681 self.inline_blame_popover.take();
2682 if self.git_blame_inline_enabled {
2683 self.start_inline_blame_timer(window, cx);
2684 }
2685 }
2686
2687 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2688 cx.emit(EditorEvent::SelectionsChanged { local });
2689
2690 let selections = &self.selections.disjoint;
2691 if selections.len() == 1 {
2692 cx.emit(SearchEvent::ActiveMatchChanged)
2693 }
2694 if local {
2695 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2696 let inmemory_selections = selections
2697 .iter()
2698 .map(|s| {
2699 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2700 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2701 })
2702 .collect();
2703 self.update_restoration_data(cx, |data| {
2704 data.selections = inmemory_selections;
2705 });
2706
2707 if WorkspaceSettings::get(None, cx).restore_on_startup
2708 != RestoreOnStartupBehavior::None
2709 {
2710 if let Some(workspace_id) =
2711 self.workspace.as_ref().and_then(|workspace| workspace.1)
2712 {
2713 let snapshot = self.buffer().read(cx).snapshot(cx);
2714 let selections = selections.clone();
2715 let background_executor = cx.background_executor().clone();
2716 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2717 self.serialize_selections = cx.background_spawn(async move {
2718 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2719 let db_selections = selections
2720 .iter()
2721 .map(|selection| {
2722 (
2723 selection.start.to_offset(&snapshot),
2724 selection.end.to_offset(&snapshot),
2725 )
2726 })
2727 .collect();
2728
2729 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2730 .await
2731 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2732 .log_err();
2733 });
2734 }
2735 }
2736 }
2737 }
2738
2739 cx.notify();
2740 }
2741
2742 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2743 use text::ToOffset as _;
2744 use text::ToPoint as _;
2745
2746 if self.mode.is_minimap()
2747 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2748 {
2749 return;
2750 }
2751
2752 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2753 return;
2754 };
2755
2756 let snapshot = singleton.read(cx).snapshot();
2757 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2758 let display_snapshot = display_map.snapshot(cx);
2759
2760 display_snapshot
2761 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2762 .map(|fold| {
2763 fold.range.start.text_anchor.to_point(&snapshot)
2764 ..fold.range.end.text_anchor.to_point(&snapshot)
2765 })
2766 .collect()
2767 });
2768 self.update_restoration_data(cx, |data| {
2769 data.folds = inmemory_folds;
2770 });
2771
2772 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2773 return;
2774 };
2775 let background_executor = cx.background_executor().clone();
2776 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2777 let db_folds = self.display_map.update(cx, |display_map, cx| {
2778 display_map
2779 .snapshot(cx)
2780 .folds_in_range(0..snapshot.len())
2781 .map(|fold| {
2782 (
2783 fold.range.start.text_anchor.to_offset(&snapshot),
2784 fold.range.end.text_anchor.to_offset(&snapshot),
2785 )
2786 })
2787 .collect()
2788 });
2789 self.serialize_folds = cx.background_spawn(async move {
2790 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2791 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2792 .await
2793 .with_context(|| {
2794 format!(
2795 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2796 )
2797 })
2798 .log_err();
2799 });
2800 }
2801
2802 pub fn sync_selections(
2803 &mut self,
2804 other: Entity<Editor>,
2805 cx: &mut Context<Self>,
2806 ) -> gpui::Subscription {
2807 let other_selections = other.read(cx).selections.disjoint.to_vec();
2808 self.selections.change_with(cx, |selections| {
2809 selections.select_anchors(other_selections);
2810 });
2811
2812 let other_subscription =
2813 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2814 EditorEvent::SelectionsChanged { local: true } => {
2815 let other_selections = other.read(cx).selections.disjoint.to_vec();
2816 if other_selections.is_empty() {
2817 return;
2818 }
2819 this.selections.change_with(cx, |selections| {
2820 selections.select_anchors(other_selections);
2821 });
2822 }
2823 _ => {}
2824 });
2825
2826 let this_subscription =
2827 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2828 EditorEvent::SelectionsChanged { local: true } => {
2829 let these_selections = this.selections.disjoint.to_vec();
2830 if these_selections.is_empty() {
2831 return;
2832 }
2833 other.update(cx, |other_editor, cx| {
2834 other_editor.selections.change_with(cx, |selections| {
2835 selections.select_anchors(these_selections);
2836 })
2837 });
2838 }
2839 _ => {}
2840 });
2841
2842 Subscription::join(other_subscription, this_subscription)
2843 }
2844
2845 pub fn change_selections<R>(
2846 &mut self,
2847 autoscroll: Option<Autoscroll>,
2848 window: &mut Window,
2849 cx: &mut Context<Self>,
2850 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2851 ) -> R {
2852 self.change_selections_inner(autoscroll, true, window, cx, change)
2853 }
2854
2855 fn change_selections_inner<R>(
2856 &mut self,
2857 autoscroll: Option<Autoscroll>,
2858 request_completions: bool,
2859 window: &mut Window,
2860 cx: &mut Context<Self>,
2861 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2862 ) -> R {
2863 let old_cursor_position = self.selections.newest_anchor().head();
2864 self.push_to_selection_history();
2865
2866 let (changed, result) = self.selections.change_with(cx, change);
2867
2868 if changed {
2869 if let Some(autoscroll) = autoscroll {
2870 self.request_autoscroll(autoscroll, cx);
2871 }
2872 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2873
2874 if self.should_open_signature_help_automatically(
2875 &old_cursor_position,
2876 self.signature_help_state.backspace_pressed(),
2877 cx,
2878 ) {
2879 self.show_signature_help(&ShowSignatureHelp, window, cx);
2880 }
2881 self.signature_help_state.set_backspace_pressed(false);
2882 }
2883
2884 result
2885 }
2886
2887 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2888 where
2889 I: IntoIterator<Item = (Range<S>, T)>,
2890 S: ToOffset,
2891 T: Into<Arc<str>>,
2892 {
2893 if self.read_only(cx) {
2894 return;
2895 }
2896
2897 self.buffer
2898 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2899 }
2900
2901 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2902 where
2903 I: IntoIterator<Item = (Range<S>, T)>,
2904 S: ToOffset,
2905 T: Into<Arc<str>>,
2906 {
2907 if self.read_only(cx) {
2908 return;
2909 }
2910
2911 self.buffer.update(cx, |buffer, cx| {
2912 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2913 });
2914 }
2915
2916 pub fn edit_with_block_indent<I, S, T>(
2917 &mut self,
2918 edits: I,
2919 original_indent_columns: Vec<Option<u32>>,
2920 cx: &mut Context<Self>,
2921 ) where
2922 I: IntoIterator<Item = (Range<S>, T)>,
2923 S: ToOffset,
2924 T: Into<Arc<str>>,
2925 {
2926 if self.read_only(cx) {
2927 return;
2928 }
2929
2930 self.buffer.update(cx, |buffer, cx| {
2931 buffer.edit(
2932 edits,
2933 Some(AutoindentMode::Block {
2934 original_indent_columns,
2935 }),
2936 cx,
2937 )
2938 });
2939 }
2940
2941 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2942 self.hide_context_menu(window, cx);
2943
2944 match phase {
2945 SelectPhase::Begin {
2946 position,
2947 add,
2948 click_count,
2949 } => self.begin_selection(position, add, click_count, window, cx),
2950 SelectPhase::BeginColumnar {
2951 position,
2952 goal_column,
2953 reset,
2954 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
2955 SelectPhase::Extend {
2956 position,
2957 click_count,
2958 } => self.extend_selection(position, click_count, window, cx),
2959 SelectPhase::Update {
2960 position,
2961 goal_column,
2962 scroll_delta,
2963 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
2964 SelectPhase::End => self.end_selection(window, cx),
2965 }
2966 }
2967
2968 fn extend_selection(
2969 &mut self,
2970 position: DisplayPoint,
2971 click_count: usize,
2972 window: &mut Window,
2973 cx: &mut Context<Self>,
2974 ) {
2975 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2976 let tail = self.selections.newest::<usize>(cx).tail();
2977 self.begin_selection(position, false, click_count, window, cx);
2978
2979 let position = position.to_offset(&display_map, Bias::Left);
2980 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
2981
2982 let mut pending_selection = self
2983 .selections
2984 .pending_anchor()
2985 .expect("extend_selection not called with pending selection");
2986 if position >= tail {
2987 pending_selection.start = tail_anchor;
2988 } else {
2989 pending_selection.end = tail_anchor;
2990 pending_selection.reversed = true;
2991 }
2992
2993 let mut pending_mode = self.selections.pending_mode().unwrap();
2994 match &mut pending_mode {
2995 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
2996 _ => {}
2997 }
2998
2999 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3000
3001 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3002 s.set_pending(pending_selection, pending_mode)
3003 });
3004 }
3005
3006 fn begin_selection(
3007 &mut self,
3008 position: DisplayPoint,
3009 add: bool,
3010 click_count: usize,
3011 window: &mut Window,
3012 cx: &mut Context<Self>,
3013 ) {
3014 if !self.focus_handle.is_focused(window) {
3015 self.last_focused_descendant = None;
3016 window.focus(&self.focus_handle);
3017 }
3018
3019 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3020 let buffer = &display_map.buffer_snapshot;
3021 let position = display_map.clip_point(position, Bias::Left);
3022
3023 let start;
3024 let end;
3025 let mode;
3026 let mut auto_scroll;
3027 match click_count {
3028 1 => {
3029 start = buffer.anchor_before(position.to_point(&display_map));
3030 end = start;
3031 mode = SelectMode::Character;
3032 auto_scroll = true;
3033 }
3034 2 => {
3035 let range = movement::surrounding_word(&display_map, position);
3036 start = buffer.anchor_before(range.start.to_point(&display_map));
3037 end = buffer.anchor_before(range.end.to_point(&display_map));
3038 mode = SelectMode::Word(start..end);
3039 auto_scroll = true;
3040 }
3041 3 => {
3042 let position = display_map
3043 .clip_point(position, Bias::Left)
3044 .to_point(&display_map);
3045 let line_start = display_map.prev_line_boundary(position).0;
3046 let next_line_start = buffer.clip_point(
3047 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3048 Bias::Left,
3049 );
3050 start = buffer.anchor_before(line_start);
3051 end = buffer.anchor_before(next_line_start);
3052 mode = SelectMode::Line(start..end);
3053 auto_scroll = true;
3054 }
3055 _ => {
3056 start = buffer.anchor_before(0);
3057 end = buffer.anchor_before(buffer.len());
3058 mode = SelectMode::All;
3059 auto_scroll = false;
3060 }
3061 }
3062 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3063
3064 let point_to_delete: Option<usize> = {
3065 let selected_points: Vec<Selection<Point>> =
3066 self.selections.disjoint_in_range(start..end, cx);
3067
3068 if !add || click_count > 1 {
3069 None
3070 } else if !selected_points.is_empty() {
3071 Some(selected_points[0].id)
3072 } else {
3073 let clicked_point_already_selected =
3074 self.selections.disjoint.iter().find(|selection| {
3075 selection.start.to_point(buffer) == start.to_point(buffer)
3076 || selection.end.to_point(buffer) == end.to_point(buffer)
3077 });
3078
3079 clicked_point_already_selected.map(|selection| selection.id)
3080 }
3081 };
3082
3083 let selections_count = self.selections.count();
3084
3085 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3086 if let Some(point_to_delete) = point_to_delete {
3087 s.delete(point_to_delete);
3088
3089 if selections_count == 1 {
3090 s.set_pending_anchor_range(start..end, mode);
3091 }
3092 } else {
3093 if !add {
3094 s.clear_disjoint();
3095 }
3096
3097 s.set_pending_anchor_range(start..end, mode);
3098 }
3099 });
3100 }
3101
3102 fn begin_columnar_selection(
3103 &mut self,
3104 position: DisplayPoint,
3105 goal_column: u32,
3106 reset: bool,
3107 window: &mut Window,
3108 cx: &mut Context<Self>,
3109 ) {
3110 if !self.focus_handle.is_focused(window) {
3111 self.last_focused_descendant = None;
3112 window.focus(&self.focus_handle);
3113 }
3114
3115 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3116
3117 if reset {
3118 let pointer_position = display_map
3119 .buffer_snapshot
3120 .anchor_before(position.to_point(&display_map));
3121
3122 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3123 s.clear_disjoint();
3124 s.set_pending_anchor_range(
3125 pointer_position..pointer_position,
3126 SelectMode::Character,
3127 );
3128 });
3129 }
3130
3131 let tail = self.selections.newest::<Point>(cx).tail();
3132 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3133
3134 if !reset {
3135 self.select_columns(
3136 tail.to_display_point(&display_map),
3137 position,
3138 goal_column,
3139 &display_map,
3140 window,
3141 cx,
3142 );
3143 }
3144 }
3145
3146 fn update_selection(
3147 &mut self,
3148 position: DisplayPoint,
3149 goal_column: u32,
3150 scroll_delta: gpui::Point<f32>,
3151 window: &mut Window,
3152 cx: &mut Context<Self>,
3153 ) {
3154 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3155
3156 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3157 let tail = tail.to_display_point(&display_map);
3158 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3159 } else if let Some(mut pending) = self.selections.pending_anchor() {
3160 let buffer = self.buffer.read(cx).snapshot(cx);
3161 let head;
3162 let tail;
3163 let mode = self.selections.pending_mode().unwrap();
3164 match &mode {
3165 SelectMode::Character => {
3166 head = position.to_point(&display_map);
3167 tail = pending.tail().to_point(&buffer);
3168 }
3169 SelectMode::Word(original_range) => {
3170 let original_display_range = original_range.start.to_display_point(&display_map)
3171 ..original_range.end.to_display_point(&display_map);
3172 let original_buffer_range = original_display_range.start.to_point(&display_map)
3173 ..original_display_range.end.to_point(&display_map);
3174 if movement::is_inside_word(&display_map, position)
3175 || original_display_range.contains(&position)
3176 {
3177 let word_range = movement::surrounding_word(&display_map, position);
3178 if word_range.start < original_display_range.start {
3179 head = word_range.start.to_point(&display_map);
3180 } else {
3181 head = word_range.end.to_point(&display_map);
3182 }
3183 } else {
3184 head = position.to_point(&display_map);
3185 }
3186
3187 if head <= original_buffer_range.start {
3188 tail = original_buffer_range.end;
3189 } else {
3190 tail = original_buffer_range.start;
3191 }
3192 }
3193 SelectMode::Line(original_range) => {
3194 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3195
3196 let position = display_map
3197 .clip_point(position, Bias::Left)
3198 .to_point(&display_map);
3199 let line_start = display_map.prev_line_boundary(position).0;
3200 let next_line_start = buffer.clip_point(
3201 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3202 Bias::Left,
3203 );
3204
3205 if line_start < original_range.start {
3206 head = line_start
3207 } else {
3208 head = next_line_start
3209 }
3210
3211 if head <= original_range.start {
3212 tail = original_range.end;
3213 } else {
3214 tail = original_range.start;
3215 }
3216 }
3217 SelectMode::All => {
3218 return;
3219 }
3220 };
3221
3222 if head < tail {
3223 pending.start = buffer.anchor_before(head);
3224 pending.end = buffer.anchor_before(tail);
3225 pending.reversed = true;
3226 } else {
3227 pending.start = buffer.anchor_before(tail);
3228 pending.end = buffer.anchor_before(head);
3229 pending.reversed = false;
3230 }
3231
3232 self.change_selections(None, window, cx, |s| {
3233 s.set_pending(pending, mode);
3234 });
3235 } else {
3236 log::error!("update_selection dispatched with no pending selection");
3237 return;
3238 }
3239
3240 self.apply_scroll_delta(scroll_delta, window, cx);
3241 cx.notify();
3242 }
3243
3244 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3245 self.columnar_selection_tail.take();
3246 if self.selections.pending_anchor().is_some() {
3247 let selections = self.selections.all::<usize>(cx);
3248 self.change_selections(None, window, cx, |s| {
3249 s.select(selections);
3250 s.clear_pending();
3251 });
3252 }
3253 }
3254
3255 fn select_columns(
3256 &mut self,
3257 tail: DisplayPoint,
3258 head: DisplayPoint,
3259 goal_column: u32,
3260 display_map: &DisplaySnapshot,
3261 window: &mut Window,
3262 cx: &mut Context<Self>,
3263 ) {
3264 let start_row = cmp::min(tail.row(), head.row());
3265 let end_row = cmp::max(tail.row(), head.row());
3266 let start_column = cmp::min(tail.column(), goal_column);
3267 let end_column = cmp::max(tail.column(), goal_column);
3268 let reversed = start_column < tail.column();
3269
3270 let selection_ranges = (start_row.0..=end_row.0)
3271 .map(DisplayRow)
3272 .filter_map(|row| {
3273 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
3274 let start = display_map
3275 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3276 .to_point(display_map);
3277 let end = display_map
3278 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3279 .to_point(display_map);
3280 if reversed {
3281 Some(end..start)
3282 } else {
3283 Some(start..end)
3284 }
3285 } else {
3286 None
3287 }
3288 })
3289 .collect::<Vec<_>>();
3290
3291 self.change_selections(None, window, cx, |s| {
3292 s.select_ranges(selection_ranges);
3293 });
3294 cx.notify();
3295 }
3296
3297 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3298 self.selections
3299 .all_adjusted(cx)
3300 .iter()
3301 .any(|selection| !selection.is_empty())
3302 }
3303
3304 pub fn has_pending_nonempty_selection(&self) -> bool {
3305 let pending_nonempty_selection = match self.selections.pending_anchor() {
3306 Some(Selection { start, end, .. }) => start != end,
3307 None => false,
3308 };
3309
3310 pending_nonempty_selection
3311 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3312 }
3313
3314 pub fn has_pending_selection(&self) -> bool {
3315 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3316 }
3317
3318 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3319 self.selection_mark_mode = false;
3320
3321 if self.clear_expanded_diff_hunks(cx) {
3322 cx.notify();
3323 return;
3324 }
3325 if self.dismiss_menus_and_popups(true, window, cx) {
3326 return;
3327 }
3328
3329 if self.mode.is_full()
3330 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3331 {
3332 return;
3333 }
3334
3335 cx.propagate();
3336 }
3337
3338 pub fn dismiss_menus_and_popups(
3339 &mut self,
3340 is_user_requested: bool,
3341 window: &mut Window,
3342 cx: &mut Context<Self>,
3343 ) -> bool {
3344 if self.take_rename(false, window, cx).is_some() {
3345 return true;
3346 }
3347
3348 if hide_hover(self, cx) {
3349 return true;
3350 }
3351
3352 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3353 return true;
3354 }
3355
3356 if self.hide_context_menu(window, cx).is_some() {
3357 return true;
3358 }
3359
3360 if self.mouse_context_menu.take().is_some() {
3361 return true;
3362 }
3363
3364 if is_user_requested && self.discard_inline_completion(true, cx) {
3365 return true;
3366 }
3367
3368 if self.snippet_stack.pop().is_some() {
3369 return true;
3370 }
3371
3372 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3373 self.dismiss_diagnostics(cx);
3374 return true;
3375 }
3376
3377 false
3378 }
3379
3380 fn linked_editing_ranges_for(
3381 &self,
3382 selection: Range<text::Anchor>,
3383 cx: &App,
3384 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3385 if self.linked_edit_ranges.is_empty() {
3386 return None;
3387 }
3388 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3389 selection.end.buffer_id.and_then(|end_buffer_id| {
3390 if selection.start.buffer_id != Some(end_buffer_id) {
3391 return None;
3392 }
3393 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3394 let snapshot = buffer.read(cx).snapshot();
3395 self.linked_edit_ranges
3396 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3397 .map(|ranges| (ranges, snapshot, buffer))
3398 })?;
3399 use text::ToOffset as TO;
3400 // find offset from the start of current range to current cursor position
3401 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3402
3403 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3404 let start_difference = start_offset - start_byte_offset;
3405 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3406 let end_difference = end_offset - start_byte_offset;
3407 // Current range has associated linked ranges.
3408 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3409 for range in linked_ranges.iter() {
3410 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3411 let end_offset = start_offset + end_difference;
3412 let start_offset = start_offset + start_difference;
3413 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3414 continue;
3415 }
3416 if self.selections.disjoint_anchor_ranges().any(|s| {
3417 if s.start.buffer_id != selection.start.buffer_id
3418 || s.end.buffer_id != selection.end.buffer_id
3419 {
3420 return false;
3421 }
3422 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3423 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3424 }) {
3425 continue;
3426 }
3427 let start = buffer_snapshot.anchor_after(start_offset);
3428 let end = buffer_snapshot.anchor_after(end_offset);
3429 linked_edits
3430 .entry(buffer.clone())
3431 .or_default()
3432 .push(start..end);
3433 }
3434 Some(linked_edits)
3435 }
3436
3437 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3438 let text: Arc<str> = text.into();
3439
3440 if self.read_only(cx) {
3441 return;
3442 }
3443
3444 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3445
3446 let selections = self.selections.all_adjusted(cx);
3447 let mut bracket_inserted = false;
3448 let mut edits = Vec::new();
3449 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3450 let mut new_selections = Vec::with_capacity(selections.len());
3451 let mut new_autoclose_regions = Vec::new();
3452 let snapshot = self.buffer.read(cx).read(cx);
3453 let mut clear_linked_edit_ranges = false;
3454
3455 for (selection, autoclose_region) in
3456 self.selections_with_autoclose_regions(selections, &snapshot)
3457 {
3458 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3459 // Determine if the inserted text matches the opening or closing
3460 // bracket of any of this language's bracket pairs.
3461 let mut bracket_pair = None;
3462 let mut is_bracket_pair_start = false;
3463 let mut is_bracket_pair_end = false;
3464 if !text.is_empty() {
3465 let mut bracket_pair_matching_end = None;
3466 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3467 // and they are removing the character that triggered IME popup.
3468 for (pair, enabled) in scope.brackets() {
3469 if !pair.close && !pair.surround {
3470 continue;
3471 }
3472
3473 if enabled && pair.start.ends_with(text.as_ref()) {
3474 let prefix_len = pair.start.len() - text.len();
3475 let preceding_text_matches_prefix = prefix_len == 0
3476 || (selection.start.column >= (prefix_len as u32)
3477 && snapshot.contains_str_at(
3478 Point::new(
3479 selection.start.row,
3480 selection.start.column - (prefix_len as u32),
3481 ),
3482 &pair.start[..prefix_len],
3483 ));
3484 if preceding_text_matches_prefix {
3485 bracket_pair = Some(pair.clone());
3486 is_bracket_pair_start = true;
3487 break;
3488 }
3489 }
3490 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3491 {
3492 // take first bracket pair matching end, but don't break in case a later bracket
3493 // pair matches start
3494 bracket_pair_matching_end = Some(pair.clone());
3495 }
3496 }
3497 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3498 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3499 is_bracket_pair_end = true;
3500 }
3501 }
3502
3503 if let Some(bracket_pair) = bracket_pair {
3504 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3505 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3506 let auto_surround =
3507 self.use_auto_surround && snapshot_settings.use_auto_surround;
3508 if selection.is_empty() {
3509 if is_bracket_pair_start {
3510 // If the inserted text is a suffix of an opening bracket and the
3511 // selection is preceded by the rest of the opening bracket, then
3512 // insert the closing bracket.
3513 let following_text_allows_autoclose = snapshot
3514 .chars_at(selection.start)
3515 .next()
3516 .map_or(true, |c| scope.should_autoclose_before(c));
3517
3518 let preceding_text_allows_autoclose = selection.start.column == 0
3519 || snapshot.reversed_chars_at(selection.start).next().map_or(
3520 true,
3521 |c| {
3522 bracket_pair.start != bracket_pair.end
3523 || !snapshot
3524 .char_classifier_at(selection.start)
3525 .is_word(c)
3526 },
3527 );
3528
3529 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3530 && bracket_pair.start.len() == 1
3531 {
3532 let target = bracket_pair.start.chars().next().unwrap();
3533 let current_line_count = snapshot
3534 .reversed_chars_at(selection.start)
3535 .take_while(|&c| c != '\n')
3536 .filter(|&c| c == target)
3537 .count();
3538 current_line_count % 2 == 1
3539 } else {
3540 false
3541 };
3542
3543 if autoclose
3544 && bracket_pair.close
3545 && following_text_allows_autoclose
3546 && preceding_text_allows_autoclose
3547 && !is_closing_quote
3548 {
3549 let anchor = snapshot.anchor_before(selection.end);
3550 new_selections.push((selection.map(|_| anchor), text.len()));
3551 new_autoclose_regions.push((
3552 anchor,
3553 text.len(),
3554 selection.id,
3555 bracket_pair.clone(),
3556 ));
3557 edits.push((
3558 selection.range(),
3559 format!("{}{}", text, bracket_pair.end).into(),
3560 ));
3561 bracket_inserted = true;
3562 continue;
3563 }
3564 }
3565
3566 if let Some(region) = autoclose_region {
3567 // If the selection is followed by an auto-inserted closing bracket,
3568 // then don't insert that closing bracket again; just move the selection
3569 // past the closing bracket.
3570 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3571 && text.as_ref() == region.pair.end.as_str();
3572 if should_skip {
3573 let anchor = snapshot.anchor_after(selection.end);
3574 new_selections
3575 .push((selection.map(|_| anchor), region.pair.end.len()));
3576 continue;
3577 }
3578 }
3579
3580 let always_treat_brackets_as_autoclosed = snapshot
3581 .language_settings_at(selection.start, cx)
3582 .always_treat_brackets_as_autoclosed;
3583 if always_treat_brackets_as_autoclosed
3584 && is_bracket_pair_end
3585 && snapshot.contains_str_at(selection.end, text.as_ref())
3586 {
3587 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3588 // and the inserted text is a closing bracket and the selection is followed
3589 // by the closing bracket then move the selection past the closing bracket.
3590 let anchor = snapshot.anchor_after(selection.end);
3591 new_selections.push((selection.map(|_| anchor), text.len()));
3592 continue;
3593 }
3594 }
3595 // If an opening bracket is 1 character long and is typed while
3596 // text is selected, then surround that text with the bracket pair.
3597 else if auto_surround
3598 && bracket_pair.surround
3599 && is_bracket_pair_start
3600 && bracket_pair.start.chars().count() == 1
3601 {
3602 edits.push((selection.start..selection.start, text.clone()));
3603 edits.push((
3604 selection.end..selection.end,
3605 bracket_pair.end.as_str().into(),
3606 ));
3607 bracket_inserted = true;
3608 new_selections.push((
3609 Selection {
3610 id: selection.id,
3611 start: snapshot.anchor_after(selection.start),
3612 end: snapshot.anchor_before(selection.end),
3613 reversed: selection.reversed,
3614 goal: selection.goal,
3615 },
3616 0,
3617 ));
3618 continue;
3619 }
3620 }
3621 }
3622
3623 if self.auto_replace_emoji_shortcode
3624 && selection.is_empty()
3625 && text.as_ref().ends_with(':')
3626 {
3627 if let Some(possible_emoji_short_code) =
3628 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3629 {
3630 if !possible_emoji_short_code.is_empty() {
3631 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3632 let emoji_shortcode_start = Point::new(
3633 selection.start.row,
3634 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3635 );
3636
3637 // Remove shortcode from buffer
3638 edits.push((
3639 emoji_shortcode_start..selection.start,
3640 "".to_string().into(),
3641 ));
3642 new_selections.push((
3643 Selection {
3644 id: selection.id,
3645 start: snapshot.anchor_after(emoji_shortcode_start),
3646 end: snapshot.anchor_before(selection.start),
3647 reversed: selection.reversed,
3648 goal: selection.goal,
3649 },
3650 0,
3651 ));
3652
3653 // Insert emoji
3654 let selection_start_anchor = snapshot.anchor_after(selection.start);
3655 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3656 edits.push((selection.start..selection.end, emoji.to_string().into()));
3657
3658 continue;
3659 }
3660 }
3661 }
3662 }
3663
3664 // If not handling any auto-close operation, then just replace the selected
3665 // text with the given input and move the selection to the end of the
3666 // newly inserted text.
3667 let anchor = snapshot.anchor_after(selection.end);
3668 if !self.linked_edit_ranges.is_empty() {
3669 let start_anchor = snapshot.anchor_before(selection.start);
3670
3671 let is_word_char = text.chars().next().map_or(true, |char| {
3672 let classifier = snapshot
3673 .char_classifier_at(start_anchor.to_offset(&snapshot))
3674 .ignore_punctuation(true);
3675 classifier.is_word(char)
3676 });
3677
3678 if is_word_char {
3679 if let Some(ranges) = self
3680 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3681 {
3682 for (buffer, edits) in ranges {
3683 linked_edits
3684 .entry(buffer.clone())
3685 .or_default()
3686 .extend(edits.into_iter().map(|range| (range, text.clone())));
3687 }
3688 }
3689 } else {
3690 clear_linked_edit_ranges = true;
3691 }
3692 }
3693
3694 new_selections.push((selection.map(|_| anchor), 0));
3695 edits.push((selection.start..selection.end, text.clone()));
3696 }
3697
3698 drop(snapshot);
3699
3700 self.transact(window, cx, |this, window, cx| {
3701 if clear_linked_edit_ranges {
3702 this.linked_edit_ranges.clear();
3703 }
3704 let initial_buffer_versions =
3705 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3706
3707 this.buffer.update(cx, |buffer, cx| {
3708 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3709 });
3710 for (buffer, edits) in linked_edits {
3711 buffer.update(cx, |buffer, cx| {
3712 let snapshot = buffer.snapshot();
3713 let edits = edits
3714 .into_iter()
3715 .map(|(range, text)| {
3716 use text::ToPoint as TP;
3717 let end_point = TP::to_point(&range.end, &snapshot);
3718 let start_point = TP::to_point(&range.start, &snapshot);
3719 (start_point..end_point, text)
3720 })
3721 .sorted_by_key(|(range, _)| range.start);
3722 buffer.edit(edits, None, cx);
3723 })
3724 }
3725 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3726 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3727 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3728 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3729 .zip(new_selection_deltas)
3730 .map(|(selection, delta)| Selection {
3731 id: selection.id,
3732 start: selection.start + delta,
3733 end: selection.end + delta,
3734 reversed: selection.reversed,
3735 goal: SelectionGoal::None,
3736 })
3737 .collect::<Vec<_>>();
3738
3739 let mut i = 0;
3740 for (position, delta, selection_id, pair) in new_autoclose_regions {
3741 let position = position.to_offset(&map.buffer_snapshot) + delta;
3742 let start = map.buffer_snapshot.anchor_before(position);
3743 let end = map.buffer_snapshot.anchor_after(position);
3744 while let Some(existing_state) = this.autoclose_regions.get(i) {
3745 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3746 Ordering::Less => i += 1,
3747 Ordering::Greater => break,
3748 Ordering::Equal => {
3749 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3750 Ordering::Less => i += 1,
3751 Ordering::Equal => break,
3752 Ordering::Greater => break,
3753 }
3754 }
3755 }
3756 }
3757 this.autoclose_regions.insert(
3758 i,
3759 AutocloseRegion {
3760 selection_id,
3761 range: start..end,
3762 pair,
3763 },
3764 );
3765 }
3766
3767 let had_active_inline_completion = this.has_active_inline_completion();
3768 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3769 s.select(new_selections)
3770 });
3771
3772 if !bracket_inserted {
3773 if let Some(on_type_format_task) =
3774 this.trigger_on_type_formatting(text.to_string(), window, cx)
3775 {
3776 on_type_format_task.detach_and_log_err(cx);
3777 }
3778 }
3779
3780 let editor_settings = EditorSettings::get_global(cx);
3781 if bracket_inserted
3782 && (editor_settings.auto_signature_help
3783 || editor_settings.show_signature_help_after_edits)
3784 {
3785 this.show_signature_help(&ShowSignatureHelp, window, cx);
3786 }
3787
3788 let trigger_in_words =
3789 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3790 if this.hard_wrap.is_some() {
3791 let latest: Range<Point> = this.selections.newest(cx).range();
3792 if latest.is_empty()
3793 && this
3794 .buffer()
3795 .read(cx)
3796 .snapshot(cx)
3797 .line_len(MultiBufferRow(latest.start.row))
3798 == latest.start.column
3799 {
3800 this.rewrap_impl(
3801 RewrapOptions {
3802 override_language_settings: true,
3803 preserve_existing_whitespace: true,
3804 },
3805 cx,
3806 )
3807 }
3808 }
3809 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3810 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3811 this.refresh_inline_completion(true, false, window, cx);
3812 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3813 });
3814 }
3815
3816 fn find_possible_emoji_shortcode_at_position(
3817 snapshot: &MultiBufferSnapshot,
3818 position: Point,
3819 ) -> Option<String> {
3820 let mut chars = Vec::new();
3821 let mut found_colon = false;
3822 for char in snapshot.reversed_chars_at(position).take(100) {
3823 // Found a possible emoji shortcode in the middle of the buffer
3824 if found_colon {
3825 if char.is_whitespace() {
3826 chars.reverse();
3827 return Some(chars.iter().collect());
3828 }
3829 // If the previous character is not a whitespace, we are in the middle of a word
3830 // and we only want to complete the shortcode if the word is made up of other emojis
3831 let mut containing_word = String::new();
3832 for ch in snapshot
3833 .reversed_chars_at(position)
3834 .skip(chars.len() + 1)
3835 .take(100)
3836 {
3837 if ch.is_whitespace() {
3838 break;
3839 }
3840 containing_word.push(ch);
3841 }
3842 let containing_word = containing_word.chars().rev().collect::<String>();
3843 if util::word_consists_of_emojis(containing_word.as_str()) {
3844 chars.reverse();
3845 return Some(chars.iter().collect());
3846 }
3847 }
3848
3849 if char.is_whitespace() || !char.is_ascii() {
3850 return None;
3851 }
3852 if char == ':' {
3853 found_colon = true;
3854 } else {
3855 chars.push(char);
3856 }
3857 }
3858 // Found a possible emoji shortcode at the beginning of the buffer
3859 chars.reverse();
3860 Some(chars.iter().collect())
3861 }
3862
3863 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3864 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3865 self.transact(window, cx, |this, window, cx| {
3866 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
3867 let selections = this.selections.all::<usize>(cx);
3868 let multi_buffer = this.buffer.read(cx);
3869 let buffer = multi_buffer.snapshot(cx);
3870 selections
3871 .iter()
3872 .map(|selection| {
3873 let start_point = selection.start.to_point(&buffer);
3874 let mut indent =
3875 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3876 indent.len = cmp::min(indent.len, start_point.column);
3877 let start = selection.start;
3878 let end = selection.end;
3879 let selection_is_empty = start == end;
3880 let language_scope = buffer.language_scope_at(start);
3881 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
3882 &language_scope
3883 {
3884 let insert_extra_newline =
3885 insert_extra_newline_brackets(&buffer, start..end, language)
3886 || insert_extra_newline_tree_sitter(&buffer, start..end);
3887
3888 // Comment extension on newline is allowed only for cursor selections
3889 let comment_delimiter = maybe!({
3890 if !selection_is_empty {
3891 return None;
3892 }
3893
3894 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3895 return None;
3896 }
3897
3898 let delimiters = language.line_comment_prefixes();
3899 let max_len_of_delimiter =
3900 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3901 let (snapshot, range) =
3902 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3903
3904 let mut index_of_first_non_whitespace = 0;
3905 let comment_candidate = snapshot
3906 .chars_for_range(range)
3907 .skip_while(|c| {
3908 let should_skip = c.is_whitespace();
3909 if should_skip {
3910 index_of_first_non_whitespace += 1;
3911 }
3912 should_skip
3913 })
3914 .take(max_len_of_delimiter)
3915 .collect::<String>();
3916 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3917 comment_candidate.starts_with(comment_prefix.as_ref())
3918 })?;
3919 let cursor_is_placed_after_comment_marker =
3920 index_of_first_non_whitespace + comment_prefix.len()
3921 <= start_point.column as usize;
3922 if cursor_is_placed_after_comment_marker {
3923 Some(comment_prefix.clone())
3924 } else {
3925 None
3926 }
3927 });
3928 (comment_delimiter, insert_extra_newline)
3929 } else {
3930 (None, false)
3931 };
3932
3933 let capacity_for_delimiter = comment_delimiter
3934 .as_deref()
3935 .map(str::len)
3936 .unwrap_or_default();
3937 let mut new_text =
3938 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3939 new_text.push('\n');
3940 new_text.extend(indent.chars());
3941 if let Some(delimiter) = &comment_delimiter {
3942 new_text.push_str(delimiter);
3943 }
3944 if insert_extra_newline {
3945 new_text = new_text.repeat(2);
3946 }
3947
3948 let anchor = buffer.anchor_after(end);
3949 let new_selection = selection.map(|_| anchor);
3950 (
3951 (start..end, new_text),
3952 (insert_extra_newline, new_selection),
3953 )
3954 })
3955 .unzip()
3956 };
3957
3958 this.edit_with_autoindent(edits, cx);
3959 let buffer = this.buffer.read(cx).snapshot(cx);
3960 let new_selections = selection_fixup_info
3961 .into_iter()
3962 .map(|(extra_newline_inserted, new_selection)| {
3963 let mut cursor = new_selection.end.to_point(&buffer);
3964 if extra_newline_inserted {
3965 cursor.row -= 1;
3966 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
3967 }
3968 new_selection.map(|_| cursor)
3969 })
3970 .collect();
3971
3972 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3973 s.select(new_selections)
3974 });
3975 this.refresh_inline_completion(true, false, window, cx);
3976 });
3977 }
3978
3979 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
3980 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3981
3982 let buffer = self.buffer.read(cx);
3983 let snapshot = buffer.snapshot(cx);
3984
3985 let mut edits = Vec::new();
3986 let mut rows = Vec::new();
3987
3988 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
3989 let cursor = selection.head();
3990 let row = cursor.row;
3991
3992 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
3993
3994 let newline = "\n".to_string();
3995 edits.push((start_of_line..start_of_line, newline));
3996
3997 rows.push(row + rows_inserted as u32);
3998 }
3999
4000 self.transact(window, cx, |editor, window, cx| {
4001 editor.edit(edits, cx);
4002
4003 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4004 let mut index = 0;
4005 s.move_cursors_with(|map, _, _| {
4006 let row = rows[index];
4007 index += 1;
4008
4009 let point = Point::new(row, 0);
4010 let boundary = map.next_line_boundary(point).1;
4011 let clipped = map.clip_point(boundary, Bias::Left);
4012
4013 (clipped, SelectionGoal::None)
4014 });
4015 });
4016
4017 let mut indent_edits = Vec::new();
4018 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4019 for row in rows {
4020 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4021 for (row, indent) in indents {
4022 if indent.len == 0 {
4023 continue;
4024 }
4025
4026 let text = match indent.kind {
4027 IndentKind::Space => " ".repeat(indent.len as usize),
4028 IndentKind::Tab => "\t".repeat(indent.len as usize),
4029 };
4030 let point = Point::new(row.0, 0);
4031 indent_edits.push((point..point, text));
4032 }
4033 }
4034 editor.edit(indent_edits, cx);
4035 });
4036 }
4037
4038 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4039 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4040
4041 let buffer = self.buffer.read(cx);
4042 let snapshot = buffer.snapshot(cx);
4043
4044 let mut edits = Vec::new();
4045 let mut rows = Vec::new();
4046 let mut rows_inserted = 0;
4047
4048 for selection in self.selections.all_adjusted(cx) {
4049 let cursor = selection.head();
4050 let row = cursor.row;
4051
4052 let point = Point::new(row + 1, 0);
4053 let start_of_line = snapshot.clip_point(point, Bias::Left);
4054
4055 let newline = "\n".to_string();
4056 edits.push((start_of_line..start_of_line, newline));
4057
4058 rows_inserted += 1;
4059 rows.push(row + rows_inserted);
4060 }
4061
4062 self.transact(window, cx, |editor, window, cx| {
4063 editor.edit(edits, cx);
4064
4065 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4066 let mut index = 0;
4067 s.move_cursors_with(|map, _, _| {
4068 let row = rows[index];
4069 index += 1;
4070
4071 let point = Point::new(row, 0);
4072 let boundary = map.next_line_boundary(point).1;
4073 let clipped = map.clip_point(boundary, Bias::Left);
4074
4075 (clipped, SelectionGoal::None)
4076 });
4077 });
4078
4079 let mut indent_edits = Vec::new();
4080 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4081 for row in rows {
4082 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4083 for (row, indent) in indents {
4084 if indent.len == 0 {
4085 continue;
4086 }
4087
4088 let text = match indent.kind {
4089 IndentKind::Space => " ".repeat(indent.len as usize),
4090 IndentKind::Tab => "\t".repeat(indent.len as usize),
4091 };
4092 let point = Point::new(row.0, 0);
4093 indent_edits.push((point..point, text));
4094 }
4095 }
4096 editor.edit(indent_edits, cx);
4097 });
4098 }
4099
4100 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4101 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4102 original_indent_columns: Vec::new(),
4103 });
4104 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4105 }
4106
4107 fn insert_with_autoindent_mode(
4108 &mut self,
4109 text: &str,
4110 autoindent_mode: Option<AutoindentMode>,
4111 window: &mut Window,
4112 cx: &mut Context<Self>,
4113 ) {
4114 if self.read_only(cx) {
4115 return;
4116 }
4117
4118 let text: Arc<str> = text.into();
4119 self.transact(window, cx, |this, window, cx| {
4120 let old_selections = this.selections.all_adjusted(cx);
4121 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4122 let anchors = {
4123 let snapshot = buffer.read(cx);
4124 old_selections
4125 .iter()
4126 .map(|s| {
4127 let anchor = snapshot.anchor_after(s.head());
4128 s.map(|_| anchor)
4129 })
4130 .collect::<Vec<_>>()
4131 };
4132 buffer.edit(
4133 old_selections
4134 .iter()
4135 .map(|s| (s.start..s.end, text.clone())),
4136 autoindent_mode,
4137 cx,
4138 );
4139 anchors
4140 });
4141
4142 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4143 s.select_anchors(selection_anchors);
4144 });
4145
4146 cx.notify();
4147 });
4148 }
4149
4150 fn trigger_completion_on_input(
4151 &mut self,
4152 text: &str,
4153 trigger_in_words: bool,
4154 window: &mut Window,
4155 cx: &mut Context<Self>,
4156 ) {
4157 let ignore_completion_provider = self
4158 .context_menu
4159 .borrow()
4160 .as_ref()
4161 .map(|menu| match menu {
4162 CodeContextMenu::Completions(completions_menu) => {
4163 completions_menu.ignore_completion_provider
4164 }
4165 CodeContextMenu::CodeActions(_) => false,
4166 })
4167 .unwrap_or(false);
4168
4169 if ignore_completion_provider {
4170 self.show_word_completions(&ShowWordCompletions, window, cx);
4171 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
4172 self.show_completions(
4173 &ShowCompletions {
4174 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4175 },
4176 window,
4177 cx,
4178 );
4179 } else {
4180 self.hide_context_menu(window, cx);
4181 }
4182 }
4183
4184 fn is_completion_trigger(
4185 &self,
4186 text: &str,
4187 trigger_in_words: bool,
4188 cx: &mut Context<Self>,
4189 ) -> bool {
4190 let position = self.selections.newest_anchor().head();
4191 let multibuffer = self.buffer.read(cx);
4192 let Some(buffer) = position
4193 .buffer_id
4194 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4195 else {
4196 return false;
4197 };
4198
4199 if let Some(completion_provider) = &self.completion_provider {
4200 completion_provider.is_completion_trigger(
4201 &buffer,
4202 position.text_anchor,
4203 text,
4204 trigger_in_words,
4205 cx,
4206 )
4207 } else {
4208 false
4209 }
4210 }
4211
4212 /// If any empty selections is touching the start of its innermost containing autoclose
4213 /// region, expand it to select the brackets.
4214 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4215 let selections = self.selections.all::<usize>(cx);
4216 let buffer = self.buffer.read(cx).read(cx);
4217 let new_selections = self
4218 .selections_with_autoclose_regions(selections, &buffer)
4219 .map(|(mut selection, region)| {
4220 if !selection.is_empty() {
4221 return selection;
4222 }
4223
4224 if let Some(region) = region {
4225 let mut range = region.range.to_offset(&buffer);
4226 if selection.start == range.start && range.start >= region.pair.start.len() {
4227 range.start -= region.pair.start.len();
4228 if buffer.contains_str_at(range.start, ®ion.pair.start)
4229 && buffer.contains_str_at(range.end, ®ion.pair.end)
4230 {
4231 range.end += region.pair.end.len();
4232 selection.start = range.start;
4233 selection.end = range.end;
4234
4235 return selection;
4236 }
4237 }
4238 }
4239
4240 let always_treat_brackets_as_autoclosed = buffer
4241 .language_settings_at(selection.start, cx)
4242 .always_treat_brackets_as_autoclosed;
4243
4244 if !always_treat_brackets_as_autoclosed {
4245 return selection;
4246 }
4247
4248 if let Some(scope) = buffer.language_scope_at(selection.start) {
4249 for (pair, enabled) in scope.brackets() {
4250 if !enabled || !pair.close {
4251 continue;
4252 }
4253
4254 if buffer.contains_str_at(selection.start, &pair.end) {
4255 let pair_start_len = pair.start.len();
4256 if buffer.contains_str_at(
4257 selection.start.saturating_sub(pair_start_len),
4258 &pair.start,
4259 ) {
4260 selection.start -= pair_start_len;
4261 selection.end += pair.end.len();
4262
4263 return selection;
4264 }
4265 }
4266 }
4267 }
4268
4269 selection
4270 })
4271 .collect();
4272
4273 drop(buffer);
4274 self.change_selections(None, window, cx, |selections| {
4275 selections.select(new_selections)
4276 });
4277 }
4278
4279 /// Iterate the given selections, and for each one, find the smallest surrounding
4280 /// autoclose region. This uses the ordering of the selections and the autoclose
4281 /// regions to avoid repeated comparisons.
4282 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4283 &'a self,
4284 selections: impl IntoIterator<Item = Selection<D>>,
4285 buffer: &'a MultiBufferSnapshot,
4286 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4287 let mut i = 0;
4288 let mut regions = self.autoclose_regions.as_slice();
4289 selections.into_iter().map(move |selection| {
4290 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4291
4292 let mut enclosing = None;
4293 while let Some(pair_state) = regions.get(i) {
4294 if pair_state.range.end.to_offset(buffer) < range.start {
4295 regions = ®ions[i + 1..];
4296 i = 0;
4297 } else if pair_state.range.start.to_offset(buffer) > range.end {
4298 break;
4299 } else {
4300 if pair_state.selection_id == selection.id {
4301 enclosing = Some(pair_state);
4302 }
4303 i += 1;
4304 }
4305 }
4306
4307 (selection, enclosing)
4308 })
4309 }
4310
4311 /// Remove any autoclose regions that no longer contain their selection.
4312 fn invalidate_autoclose_regions(
4313 &mut self,
4314 mut selections: &[Selection<Anchor>],
4315 buffer: &MultiBufferSnapshot,
4316 ) {
4317 self.autoclose_regions.retain(|state| {
4318 let mut i = 0;
4319 while let Some(selection) = selections.get(i) {
4320 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4321 selections = &selections[1..];
4322 continue;
4323 }
4324 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4325 break;
4326 }
4327 if selection.id == state.selection_id {
4328 return true;
4329 } else {
4330 i += 1;
4331 }
4332 }
4333 false
4334 });
4335 }
4336
4337 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4338 let offset = position.to_offset(buffer);
4339 let (word_range, kind) = buffer.surrounding_word(offset, true);
4340 if offset > word_range.start && kind == Some(CharKind::Word) {
4341 Some(
4342 buffer
4343 .text_for_range(word_range.start..offset)
4344 .collect::<String>(),
4345 )
4346 } else {
4347 None
4348 }
4349 }
4350
4351 pub fn toggle_inline_values(
4352 &mut self,
4353 _: &ToggleInlineValues,
4354 _: &mut Window,
4355 cx: &mut Context<Self>,
4356 ) {
4357 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4358
4359 self.refresh_inline_values(cx);
4360 }
4361
4362 pub fn toggle_inlay_hints(
4363 &mut self,
4364 _: &ToggleInlayHints,
4365 _: &mut Window,
4366 cx: &mut Context<Self>,
4367 ) {
4368 self.refresh_inlay_hints(
4369 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4370 cx,
4371 );
4372 }
4373
4374 pub fn inlay_hints_enabled(&self) -> bool {
4375 self.inlay_hint_cache.enabled
4376 }
4377
4378 pub fn inline_values_enabled(&self) -> bool {
4379 self.inline_value_cache.enabled
4380 }
4381
4382 #[cfg(any(test, feature = "test-support"))]
4383 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4384 self.display_map
4385 .read(cx)
4386 .current_inlays()
4387 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4388 .cloned()
4389 .collect()
4390 }
4391
4392 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4393 if self.semantics_provider.is_none() || !self.mode.is_full() {
4394 return;
4395 }
4396
4397 let reason_description = reason.description();
4398 let ignore_debounce = matches!(
4399 reason,
4400 InlayHintRefreshReason::SettingsChange(_)
4401 | InlayHintRefreshReason::Toggle(_)
4402 | InlayHintRefreshReason::ExcerptsRemoved(_)
4403 | InlayHintRefreshReason::ModifiersChanged(_)
4404 );
4405 let (invalidate_cache, required_languages) = match reason {
4406 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4407 match self.inlay_hint_cache.modifiers_override(enabled) {
4408 Some(enabled) => {
4409 if enabled {
4410 (InvalidationStrategy::RefreshRequested, None)
4411 } else {
4412 self.splice_inlays(
4413 &self
4414 .visible_inlay_hints(cx)
4415 .iter()
4416 .map(|inlay| inlay.id)
4417 .collect::<Vec<InlayId>>(),
4418 Vec::new(),
4419 cx,
4420 );
4421 return;
4422 }
4423 }
4424 None => return,
4425 }
4426 }
4427 InlayHintRefreshReason::Toggle(enabled) => {
4428 if self.inlay_hint_cache.toggle(enabled) {
4429 if enabled {
4430 (InvalidationStrategy::RefreshRequested, None)
4431 } else {
4432 self.splice_inlays(
4433 &self
4434 .visible_inlay_hints(cx)
4435 .iter()
4436 .map(|inlay| inlay.id)
4437 .collect::<Vec<InlayId>>(),
4438 Vec::new(),
4439 cx,
4440 );
4441 return;
4442 }
4443 } else {
4444 return;
4445 }
4446 }
4447 InlayHintRefreshReason::SettingsChange(new_settings) => {
4448 match self.inlay_hint_cache.update_settings(
4449 &self.buffer,
4450 new_settings,
4451 self.visible_inlay_hints(cx),
4452 cx,
4453 ) {
4454 ControlFlow::Break(Some(InlaySplice {
4455 to_remove,
4456 to_insert,
4457 })) => {
4458 self.splice_inlays(&to_remove, to_insert, cx);
4459 return;
4460 }
4461 ControlFlow::Break(None) => return,
4462 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4463 }
4464 }
4465 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4466 if let Some(InlaySplice {
4467 to_remove,
4468 to_insert,
4469 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4470 {
4471 self.splice_inlays(&to_remove, to_insert, cx);
4472 }
4473 self.display_map.update(cx, |display_map, _| {
4474 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4475 });
4476 return;
4477 }
4478 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4479 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4480 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4481 }
4482 InlayHintRefreshReason::RefreshRequested => {
4483 (InvalidationStrategy::RefreshRequested, None)
4484 }
4485 };
4486
4487 if let Some(InlaySplice {
4488 to_remove,
4489 to_insert,
4490 }) = self.inlay_hint_cache.spawn_hint_refresh(
4491 reason_description,
4492 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4493 invalidate_cache,
4494 ignore_debounce,
4495 cx,
4496 ) {
4497 self.splice_inlays(&to_remove, to_insert, cx);
4498 }
4499 }
4500
4501 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4502 self.display_map
4503 .read(cx)
4504 .current_inlays()
4505 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4506 .cloned()
4507 .collect()
4508 }
4509
4510 pub fn excerpts_for_inlay_hints_query(
4511 &self,
4512 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4513 cx: &mut Context<Editor>,
4514 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4515 let Some(project) = self.project.as_ref() else {
4516 return HashMap::default();
4517 };
4518 let project = project.read(cx);
4519 let multi_buffer = self.buffer().read(cx);
4520 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4521 let multi_buffer_visible_start = self
4522 .scroll_manager
4523 .anchor()
4524 .anchor
4525 .to_point(&multi_buffer_snapshot);
4526 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4527 multi_buffer_visible_start
4528 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4529 Bias::Left,
4530 );
4531 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4532 multi_buffer_snapshot
4533 .range_to_buffer_ranges(multi_buffer_visible_range)
4534 .into_iter()
4535 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4536 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4537 let buffer_file = project::File::from_dyn(buffer.file())?;
4538 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4539 let worktree_entry = buffer_worktree
4540 .read(cx)
4541 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4542 if worktree_entry.is_ignored {
4543 return None;
4544 }
4545
4546 let language = buffer.language()?;
4547 if let Some(restrict_to_languages) = restrict_to_languages {
4548 if !restrict_to_languages.contains(language) {
4549 return None;
4550 }
4551 }
4552 Some((
4553 excerpt_id,
4554 (
4555 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4556 buffer.version().clone(),
4557 excerpt_visible_range,
4558 ),
4559 ))
4560 })
4561 .collect()
4562 }
4563
4564 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4565 TextLayoutDetails {
4566 text_system: window.text_system().clone(),
4567 editor_style: self.style.clone().unwrap(),
4568 rem_size: window.rem_size(),
4569 scroll_anchor: self.scroll_manager.anchor(),
4570 visible_rows: self.visible_line_count(),
4571 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4572 }
4573 }
4574
4575 pub fn splice_inlays(
4576 &self,
4577 to_remove: &[InlayId],
4578 to_insert: Vec<Inlay>,
4579 cx: &mut Context<Self>,
4580 ) {
4581 self.display_map.update(cx, |display_map, cx| {
4582 display_map.splice_inlays(to_remove, to_insert, cx)
4583 });
4584 cx.notify();
4585 }
4586
4587 fn trigger_on_type_formatting(
4588 &self,
4589 input: String,
4590 window: &mut Window,
4591 cx: &mut Context<Self>,
4592 ) -> Option<Task<Result<()>>> {
4593 if input.len() != 1 {
4594 return None;
4595 }
4596
4597 let project = self.project.as_ref()?;
4598 let position = self.selections.newest_anchor().head();
4599 let (buffer, buffer_position) = self
4600 .buffer
4601 .read(cx)
4602 .text_anchor_for_position(position, cx)?;
4603
4604 let settings = language_settings::language_settings(
4605 buffer
4606 .read(cx)
4607 .language_at(buffer_position)
4608 .map(|l| l.name()),
4609 buffer.read(cx).file(),
4610 cx,
4611 );
4612 if !settings.use_on_type_format {
4613 return None;
4614 }
4615
4616 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4617 // hence we do LSP request & edit on host side only — add formats to host's history.
4618 let push_to_lsp_host_history = true;
4619 // If this is not the host, append its history with new edits.
4620 let push_to_client_history = project.read(cx).is_via_collab();
4621
4622 let on_type_formatting = project.update(cx, |project, cx| {
4623 project.on_type_format(
4624 buffer.clone(),
4625 buffer_position,
4626 input,
4627 push_to_lsp_host_history,
4628 cx,
4629 )
4630 });
4631 Some(cx.spawn_in(window, async move |editor, cx| {
4632 if let Some(transaction) = on_type_formatting.await? {
4633 if push_to_client_history {
4634 buffer
4635 .update(cx, |buffer, _| {
4636 buffer.push_transaction(transaction, Instant::now());
4637 buffer.finalize_last_transaction();
4638 })
4639 .ok();
4640 }
4641 editor.update(cx, |editor, cx| {
4642 editor.refresh_document_highlights(cx);
4643 })?;
4644 }
4645 Ok(())
4646 }))
4647 }
4648
4649 pub fn show_word_completions(
4650 &mut self,
4651 _: &ShowWordCompletions,
4652 window: &mut Window,
4653 cx: &mut Context<Self>,
4654 ) {
4655 self.open_completions_menu(true, None, window, cx);
4656 }
4657
4658 pub fn show_completions(
4659 &mut self,
4660 options: &ShowCompletions,
4661 window: &mut Window,
4662 cx: &mut Context<Self>,
4663 ) {
4664 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4665 }
4666
4667 fn open_completions_menu(
4668 &mut self,
4669 ignore_completion_provider: bool,
4670 trigger: Option<&str>,
4671 window: &mut Window,
4672 cx: &mut Context<Self>,
4673 ) {
4674 if self.pending_rename.is_some() {
4675 return;
4676 }
4677 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4678 return;
4679 }
4680
4681 let position = self.selections.newest_anchor().head();
4682 if position.diff_base_anchor.is_some() {
4683 return;
4684 }
4685 let (buffer, buffer_position) =
4686 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4687 output
4688 } else {
4689 return;
4690 };
4691 let buffer_snapshot = buffer.read(cx).snapshot();
4692 let show_completion_documentation = buffer_snapshot
4693 .settings_at(buffer_position, cx)
4694 .show_completion_documentation;
4695
4696 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4697
4698 let trigger_kind = match trigger {
4699 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4700 CompletionTriggerKind::TRIGGER_CHARACTER
4701 }
4702 _ => CompletionTriggerKind::INVOKED,
4703 };
4704 let completion_context = CompletionContext {
4705 trigger_character: trigger.and_then(|trigger| {
4706 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4707 Some(String::from(trigger))
4708 } else {
4709 None
4710 }
4711 }),
4712 trigger_kind,
4713 };
4714
4715 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4716 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4717 let word_to_exclude = buffer_snapshot
4718 .text_for_range(old_range.clone())
4719 .collect::<String>();
4720 (
4721 buffer_snapshot.anchor_before(old_range.start)
4722 ..buffer_snapshot.anchor_after(old_range.end),
4723 Some(word_to_exclude),
4724 )
4725 } else {
4726 (buffer_position..buffer_position, None)
4727 };
4728
4729 let completion_settings = language_settings(
4730 buffer_snapshot
4731 .language_at(buffer_position)
4732 .map(|language| language.name()),
4733 buffer_snapshot.file(),
4734 cx,
4735 )
4736 .completions;
4737
4738 // The document can be large, so stay in reasonable bounds when searching for words,
4739 // otherwise completion pop-up might be slow to appear.
4740 const WORD_LOOKUP_ROWS: u32 = 5_000;
4741 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4742 let min_word_search = buffer_snapshot.clip_point(
4743 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4744 Bias::Left,
4745 );
4746 let max_word_search = buffer_snapshot.clip_point(
4747 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4748 Bias::Right,
4749 );
4750 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4751 ..buffer_snapshot.point_to_offset(max_word_search);
4752
4753 let provider = self
4754 .completion_provider
4755 .as_ref()
4756 .filter(|_| !ignore_completion_provider);
4757 let skip_digits = query
4758 .as_ref()
4759 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4760
4761 let (mut words, provided_completions) = match provider {
4762 Some(provider) => {
4763 let completions = provider.completions(
4764 position.excerpt_id,
4765 &buffer,
4766 buffer_position,
4767 completion_context,
4768 window,
4769 cx,
4770 );
4771
4772 let words = match completion_settings.words {
4773 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4774 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4775 .background_spawn(async move {
4776 buffer_snapshot.words_in_range(WordsQuery {
4777 fuzzy_contents: None,
4778 range: word_search_range,
4779 skip_digits,
4780 })
4781 }),
4782 };
4783
4784 (words, completions)
4785 }
4786 None => (
4787 cx.background_spawn(async move {
4788 buffer_snapshot.words_in_range(WordsQuery {
4789 fuzzy_contents: None,
4790 range: word_search_range,
4791 skip_digits,
4792 })
4793 }),
4794 Task::ready(Ok(None)),
4795 ),
4796 };
4797
4798 let sort_completions = provider
4799 .as_ref()
4800 .map_or(false, |provider| provider.sort_completions());
4801
4802 let filter_completions = provider
4803 .as_ref()
4804 .map_or(true, |provider| provider.filter_completions());
4805
4806 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
4807
4808 let id = post_inc(&mut self.next_completion_id);
4809 let task = cx.spawn_in(window, async move |editor, cx| {
4810 async move {
4811 editor.update(cx, |this, _| {
4812 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4813 })?;
4814
4815 let mut completions = Vec::new();
4816 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
4817 completions.extend(provided_completions);
4818 if completion_settings.words == WordsCompletionMode::Fallback {
4819 words = Task::ready(BTreeMap::default());
4820 }
4821 }
4822
4823 let mut words = words.await;
4824 if let Some(word_to_exclude) = &word_to_exclude {
4825 words.remove(word_to_exclude);
4826 }
4827 for lsp_completion in &completions {
4828 words.remove(&lsp_completion.new_text);
4829 }
4830 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
4831 replace_range: old_range.clone(),
4832 new_text: word.clone(),
4833 label: CodeLabel::plain(word, None),
4834 icon_path: None,
4835 documentation: None,
4836 source: CompletionSource::BufferWord {
4837 word_range,
4838 resolved: false,
4839 },
4840 insert_text_mode: Some(InsertTextMode::AS_IS),
4841 confirm: None,
4842 }));
4843
4844 let menu = if completions.is_empty() {
4845 None
4846 } else {
4847 let mut menu = CompletionsMenu::new(
4848 id,
4849 sort_completions,
4850 show_completion_documentation,
4851 ignore_completion_provider,
4852 position,
4853 buffer.clone(),
4854 completions.into(),
4855 snippet_sort_order,
4856 );
4857
4858 menu.filter(
4859 if filter_completions {
4860 query.as_deref()
4861 } else {
4862 None
4863 },
4864 cx.background_executor().clone(),
4865 )
4866 .await;
4867
4868 menu.visible().then_some(menu)
4869 };
4870
4871 editor.update_in(cx, |editor, window, cx| {
4872 match editor.context_menu.borrow().as_ref() {
4873 None => {}
4874 Some(CodeContextMenu::Completions(prev_menu)) => {
4875 if prev_menu.id > id {
4876 return;
4877 }
4878 }
4879 _ => return,
4880 }
4881
4882 if editor.focus_handle.is_focused(window) && menu.is_some() {
4883 let mut menu = menu.unwrap();
4884 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
4885
4886 *editor.context_menu.borrow_mut() =
4887 Some(CodeContextMenu::Completions(menu));
4888
4889 if editor.show_edit_predictions_in_menu() {
4890 editor.update_visible_inline_completion(window, cx);
4891 } else {
4892 editor.discard_inline_completion(false, cx);
4893 }
4894
4895 cx.notify();
4896 } else if editor.completion_tasks.len() <= 1 {
4897 // If there are no more completion tasks and the last menu was
4898 // empty, we should hide it.
4899 let was_hidden = editor.hide_context_menu(window, cx).is_none();
4900 // If it was already hidden and we don't show inline
4901 // completions in the menu, we should also show the
4902 // inline-completion when available.
4903 if was_hidden && editor.show_edit_predictions_in_menu() {
4904 editor.update_visible_inline_completion(window, cx);
4905 }
4906 }
4907 })?;
4908
4909 anyhow::Ok(())
4910 }
4911 .log_err()
4912 .await
4913 });
4914
4915 self.completion_tasks.push((id, task));
4916 }
4917
4918 #[cfg(feature = "test-support")]
4919 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
4920 let menu = self.context_menu.borrow();
4921 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
4922 let completions = menu.completions.borrow();
4923 Some(completions.to_vec())
4924 } else {
4925 None
4926 }
4927 }
4928
4929 pub fn confirm_completion(
4930 &mut self,
4931 action: &ConfirmCompletion,
4932 window: &mut Window,
4933 cx: &mut Context<Self>,
4934 ) -> Option<Task<Result<()>>> {
4935 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4936 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
4937 }
4938
4939 pub fn confirm_completion_insert(
4940 &mut self,
4941 _: &ConfirmCompletionInsert,
4942 window: &mut Window,
4943 cx: &mut Context<Self>,
4944 ) -> Option<Task<Result<()>>> {
4945 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4946 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
4947 }
4948
4949 pub fn confirm_completion_replace(
4950 &mut self,
4951 _: &ConfirmCompletionReplace,
4952 window: &mut Window,
4953 cx: &mut Context<Self>,
4954 ) -> Option<Task<Result<()>>> {
4955 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4956 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
4957 }
4958
4959 pub fn compose_completion(
4960 &mut self,
4961 action: &ComposeCompletion,
4962 window: &mut Window,
4963 cx: &mut Context<Self>,
4964 ) -> Option<Task<Result<()>>> {
4965 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4966 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
4967 }
4968
4969 fn do_completion(
4970 &mut self,
4971 item_ix: Option<usize>,
4972 intent: CompletionIntent,
4973 window: &mut Window,
4974 cx: &mut Context<Editor>,
4975 ) -> Option<Task<Result<()>>> {
4976 use language::ToOffset as _;
4977
4978 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
4979 else {
4980 return None;
4981 };
4982
4983 let candidate_id = {
4984 let entries = completions_menu.entries.borrow();
4985 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
4986 if self.show_edit_predictions_in_menu() {
4987 self.discard_inline_completion(true, cx);
4988 }
4989 mat.candidate_id
4990 };
4991
4992 let buffer_handle = completions_menu.buffer;
4993 let completion = completions_menu
4994 .completions
4995 .borrow()
4996 .get(candidate_id)?
4997 .clone();
4998 cx.stop_propagation();
4999
5000 let snippet;
5001 let new_text;
5002 if completion.is_snippet() {
5003 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
5004 new_text = snippet.as_ref().unwrap().text.clone();
5005 } else {
5006 snippet = None;
5007 new_text = completion.new_text.clone();
5008 };
5009
5010 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
5011 let buffer = buffer_handle.read(cx);
5012 let snapshot = self.buffer.read(cx).snapshot(cx);
5013 let replace_range_multibuffer = {
5014 let excerpt = snapshot
5015 .excerpt_containing(self.selections.newest_anchor().range())
5016 .unwrap();
5017 let multibuffer_anchor = snapshot
5018 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5019 .unwrap()
5020 ..snapshot
5021 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5022 .unwrap();
5023 multibuffer_anchor.start.to_offset(&snapshot)
5024 ..multibuffer_anchor.end.to_offset(&snapshot)
5025 };
5026 let newest_anchor = self.selections.newest_anchor();
5027 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5028 return None;
5029 }
5030
5031 let old_text = buffer
5032 .text_for_range(replace_range.clone())
5033 .collect::<String>();
5034 let lookbehind = newest_anchor
5035 .start
5036 .text_anchor
5037 .to_offset(buffer)
5038 .saturating_sub(replace_range.start);
5039 let lookahead = replace_range
5040 .end
5041 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5042 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5043 let suffix = &old_text[lookbehind.min(old_text.len())..];
5044
5045 let selections = self.selections.all::<usize>(cx);
5046 let mut ranges = Vec::new();
5047 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5048
5049 for selection in &selections {
5050 let range = if selection.id == newest_anchor.id {
5051 replace_range_multibuffer.clone()
5052 } else {
5053 let mut range = selection.range();
5054
5055 // if prefix is present, don't duplicate it
5056 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5057 range.start = range.start.saturating_sub(lookbehind);
5058
5059 // if suffix is also present, mimic the newest cursor and replace it
5060 if selection.id != newest_anchor.id
5061 && snapshot.contains_str_at(range.end, suffix)
5062 {
5063 range.end += lookahead;
5064 }
5065 }
5066 range
5067 };
5068
5069 ranges.push(range.clone());
5070
5071 if !self.linked_edit_ranges.is_empty() {
5072 let start_anchor = snapshot.anchor_before(range.start);
5073 let end_anchor = snapshot.anchor_after(range.end);
5074 if let Some(ranges) = self
5075 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5076 {
5077 for (buffer, edits) in ranges {
5078 linked_edits
5079 .entry(buffer.clone())
5080 .or_default()
5081 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5082 }
5083 }
5084 }
5085 }
5086
5087 cx.emit(EditorEvent::InputHandled {
5088 utf16_range_to_replace: None,
5089 text: new_text.clone().into(),
5090 });
5091
5092 self.transact(window, cx, |this, window, cx| {
5093 if let Some(mut snippet) = snippet {
5094 snippet.text = new_text.to_string();
5095 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5096 } else {
5097 this.buffer.update(cx, |buffer, cx| {
5098 let auto_indent = match completion.insert_text_mode {
5099 Some(InsertTextMode::AS_IS) => None,
5100 _ => this.autoindent_mode.clone(),
5101 };
5102 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5103 buffer.edit(edits, auto_indent, cx);
5104 });
5105 }
5106 for (buffer, edits) in linked_edits {
5107 buffer.update(cx, |buffer, cx| {
5108 let snapshot = buffer.snapshot();
5109 let edits = edits
5110 .into_iter()
5111 .map(|(range, text)| {
5112 use text::ToPoint as TP;
5113 let end_point = TP::to_point(&range.end, &snapshot);
5114 let start_point = TP::to_point(&range.start, &snapshot);
5115 (start_point..end_point, text)
5116 })
5117 .sorted_by_key(|(range, _)| range.start);
5118 buffer.edit(edits, None, cx);
5119 })
5120 }
5121
5122 this.refresh_inline_completion(true, false, window, cx);
5123 });
5124
5125 let show_new_completions_on_confirm = completion
5126 .confirm
5127 .as_ref()
5128 .map_or(false, |confirm| confirm(intent, window, cx));
5129 if show_new_completions_on_confirm {
5130 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5131 }
5132
5133 let provider = self.completion_provider.as_ref()?;
5134 drop(completion);
5135 let apply_edits = provider.apply_additional_edits_for_completion(
5136 buffer_handle,
5137 completions_menu.completions.clone(),
5138 candidate_id,
5139 true,
5140 cx,
5141 );
5142
5143 let editor_settings = EditorSettings::get_global(cx);
5144 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5145 // After the code completion is finished, users often want to know what signatures are needed.
5146 // so we should automatically call signature_help
5147 self.show_signature_help(&ShowSignatureHelp, window, cx);
5148 }
5149
5150 Some(cx.foreground_executor().spawn(async move {
5151 apply_edits.await?;
5152 Ok(())
5153 }))
5154 }
5155
5156 pub fn toggle_code_actions(
5157 &mut self,
5158 action: &ToggleCodeActions,
5159 window: &mut Window,
5160 cx: &mut Context<Self>,
5161 ) {
5162 let quick_launch = action.quick_launch;
5163 let mut context_menu = self.context_menu.borrow_mut();
5164 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5165 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
5166 // Toggle if we're selecting the same one
5167 *context_menu = None;
5168 cx.notify();
5169 return;
5170 } else {
5171 // Otherwise, clear it and start a new one
5172 *context_menu = None;
5173 cx.notify();
5174 }
5175 }
5176 drop(context_menu);
5177 let snapshot = self.snapshot(window, cx);
5178 let deployed_from_indicator = action.deployed_from_indicator;
5179 let mut task = self.code_actions_task.take();
5180 let action = action.clone();
5181 cx.spawn_in(window, async move |editor, cx| {
5182 while let Some(prev_task) = task {
5183 prev_task.await.log_err();
5184 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5185 }
5186
5187 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5188 if editor.focus_handle.is_focused(window) {
5189 let multibuffer_point = action
5190 .deployed_from_indicator
5191 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
5192 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
5193 let (buffer, buffer_row) = snapshot
5194 .buffer_snapshot
5195 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5196 .and_then(|(buffer_snapshot, range)| {
5197 editor
5198 .buffer
5199 .read(cx)
5200 .buffer(buffer_snapshot.remote_id())
5201 .map(|buffer| (buffer, range.start.row))
5202 })?;
5203 let (_, code_actions) = editor
5204 .available_code_actions
5205 .clone()
5206 .and_then(|(location, code_actions)| {
5207 let snapshot = location.buffer.read(cx).snapshot();
5208 let point_range = location.range.to_point(&snapshot);
5209 let point_range = point_range.start.row..=point_range.end.row;
5210 if point_range.contains(&buffer_row) {
5211 Some((location, code_actions))
5212 } else {
5213 None
5214 }
5215 })
5216 .unzip();
5217 let buffer_id = buffer.read(cx).remote_id();
5218 let tasks = editor
5219 .tasks
5220 .get(&(buffer_id, buffer_row))
5221 .map(|t| Arc::new(t.to_owned()));
5222 if tasks.is_none() && code_actions.is_none() {
5223 return None;
5224 }
5225
5226 editor.completion_tasks.clear();
5227 editor.discard_inline_completion(false, cx);
5228 let task_context =
5229 tasks
5230 .as_ref()
5231 .zip(editor.project.clone())
5232 .map(|(tasks, project)| {
5233 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5234 });
5235
5236 Some(cx.spawn_in(window, async move |editor, cx| {
5237 let task_context = match task_context {
5238 Some(task_context) => task_context.await,
5239 None => None,
5240 };
5241 let resolved_tasks =
5242 tasks
5243 .zip(task_context.clone())
5244 .map(|(tasks, task_context)| ResolvedTasks {
5245 templates: tasks.resolve(&task_context).collect(),
5246 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5247 multibuffer_point.row,
5248 tasks.column,
5249 )),
5250 });
5251 let debug_scenarios = editor.update(cx, |editor, cx| {
5252 if cx.has_flag::<DebuggerFeatureFlag>() {
5253 maybe!({
5254 let project = editor.project.as_ref()?;
5255 let dap_store = project.read(cx).dap_store();
5256 let mut scenarios = vec![];
5257 let resolved_tasks = resolved_tasks.as_ref()?;
5258 let buffer = buffer.read(cx);
5259 let language = buffer.language()?;
5260 let file = buffer.file();
5261 let debug_adapter =
5262 language_settings(language.name().into(), file, cx)
5263 .debuggers
5264 .first()
5265 .map(SharedString::from)
5266 .or_else(|| {
5267 language
5268 .config()
5269 .debuggers
5270 .first()
5271 .map(SharedString::from)
5272 })?;
5273
5274 dap_store.update(cx, |this, cx| {
5275 for (_, task) in &resolved_tasks.templates {
5276 if let Some(scenario) = this
5277 .debug_scenario_for_build_task(
5278 task.original_task().clone(),
5279 debug_adapter.clone().into(),
5280 task.display_label().to_owned().into(),
5281 cx,
5282 )
5283 {
5284 scenarios.push(scenario);
5285 }
5286 }
5287 });
5288 Some(scenarios)
5289 })
5290 .unwrap_or_default()
5291 } else {
5292 vec![]
5293 }
5294 })?;
5295 let spawn_straight_away = quick_launch
5296 && resolved_tasks
5297 .as_ref()
5298 .map_or(false, |tasks| tasks.templates.len() == 1)
5299 && code_actions
5300 .as_ref()
5301 .map_or(true, |actions| actions.is_empty())
5302 && debug_scenarios.is_empty();
5303 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5304 *editor.context_menu.borrow_mut() =
5305 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5306 buffer,
5307 actions: CodeActionContents::new(
5308 resolved_tasks,
5309 code_actions,
5310 debug_scenarios,
5311 task_context.unwrap_or_default(),
5312 ),
5313 selected_item: Default::default(),
5314 scroll_handle: UniformListScrollHandle::default(),
5315 deployed_from_indicator,
5316 }));
5317 if spawn_straight_away {
5318 if let Some(task) = editor.confirm_code_action(
5319 &ConfirmCodeAction { item_ix: Some(0) },
5320 window,
5321 cx,
5322 ) {
5323 cx.notify();
5324 return task;
5325 }
5326 }
5327 cx.notify();
5328 Task::ready(Ok(()))
5329 }) {
5330 task.await
5331 } else {
5332 Ok(())
5333 }
5334 }))
5335 } else {
5336 Some(Task::ready(Ok(())))
5337 }
5338 })?;
5339 if let Some(task) = spawned_test_task {
5340 task.await?;
5341 }
5342
5343 Ok::<_, anyhow::Error>(())
5344 })
5345 .detach_and_log_err(cx);
5346 }
5347
5348 pub fn confirm_code_action(
5349 &mut self,
5350 action: &ConfirmCodeAction,
5351 window: &mut Window,
5352 cx: &mut Context<Self>,
5353 ) -> Option<Task<Result<()>>> {
5354 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5355
5356 let actions_menu =
5357 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5358 menu
5359 } else {
5360 return None;
5361 };
5362
5363 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5364 let action = actions_menu.actions.get(action_ix)?;
5365 let title = action.label();
5366 let buffer = actions_menu.buffer;
5367 let workspace = self.workspace()?;
5368
5369 match action {
5370 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5371 workspace.update(cx, |workspace, cx| {
5372 workspace.schedule_resolved_task(
5373 task_source_kind,
5374 resolved_task,
5375 false,
5376 window,
5377 cx,
5378 );
5379
5380 Some(Task::ready(Ok(())))
5381 })
5382 }
5383 CodeActionsItem::CodeAction {
5384 excerpt_id,
5385 action,
5386 provider,
5387 } => {
5388 let apply_code_action =
5389 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5390 let workspace = workspace.downgrade();
5391 Some(cx.spawn_in(window, async move |editor, cx| {
5392 let project_transaction = apply_code_action.await?;
5393 Self::open_project_transaction(
5394 &editor,
5395 workspace,
5396 project_transaction,
5397 title,
5398 cx,
5399 )
5400 .await
5401 }))
5402 }
5403 CodeActionsItem::DebugScenario(scenario) => {
5404 let context = actions_menu.actions.context.clone();
5405
5406 workspace.update(cx, |workspace, cx| {
5407 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5408 });
5409 Some(Task::ready(Ok(())))
5410 }
5411 }
5412 }
5413
5414 pub async fn open_project_transaction(
5415 this: &WeakEntity<Editor>,
5416 workspace: WeakEntity<Workspace>,
5417 transaction: ProjectTransaction,
5418 title: String,
5419 cx: &mut AsyncWindowContext,
5420 ) -> Result<()> {
5421 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5422 cx.update(|_, cx| {
5423 entries.sort_unstable_by_key(|(buffer, _)| {
5424 buffer.read(cx).file().map(|f| f.path().clone())
5425 });
5426 })?;
5427
5428 // If the project transaction's edits are all contained within this editor, then
5429 // avoid opening a new editor to display them.
5430
5431 if let Some((buffer, transaction)) = entries.first() {
5432 if entries.len() == 1 {
5433 let excerpt = this.update(cx, |editor, cx| {
5434 editor
5435 .buffer()
5436 .read(cx)
5437 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5438 })?;
5439 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5440 if excerpted_buffer == *buffer {
5441 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5442 let excerpt_range = excerpt_range.to_offset(buffer);
5443 buffer
5444 .edited_ranges_for_transaction::<usize>(transaction)
5445 .all(|range| {
5446 excerpt_range.start <= range.start
5447 && excerpt_range.end >= range.end
5448 })
5449 })?;
5450
5451 if all_edits_within_excerpt {
5452 return Ok(());
5453 }
5454 }
5455 }
5456 }
5457 } else {
5458 return Ok(());
5459 }
5460
5461 let mut ranges_to_highlight = Vec::new();
5462 let excerpt_buffer = cx.new(|cx| {
5463 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5464 for (buffer_handle, transaction) in &entries {
5465 let edited_ranges = buffer_handle
5466 .read(cx)
5467 .edited_ranges_for_transaction::<Point>(transaction)
5468 .collect::<Vec<_>>();
5469 let (ranges, _) = multibuffer.set_excerpts_for_path(
5470 PathKey::for_buffer(buffer_handle, cx),
5471 buffer_handle.clone(),
5472 edited_ranges,
5473 DEFAULT_MULTIBUFFER_CONTEXT,
5474 cx,
5475 );
5476
5477 ranges_to_highlight.extend(ranges);
5478 }
5479 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5480 multibuffer
5481 })?;
5482
5483 workspace.update_in(cx, |workspace, window, cx| {
5484 let project = workspace.project().clone();
5485 let editor =
5486 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5487 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5488 editor.update(cx, |editor, cx| {
5489 editor.highlight_background::<Self>(
5490 &ranges_to_highlight,
5491 |theme| theme.editor_highlighted_line_background,
5492 cx,
5493 );
5494 });
5495 })?;
5496
5497 Ok(())
5498 }
5499
5500 pub fn clear_code_action_providers(&mut self) {
5501 self.code_action_providers.clear();
5502 self.available_code_actions.take();
5503 }
5504
5505 pub fn add_code_action_provider(
5506 &mut self,
5507 provider: Rc<dyn CodeActionProvider>,
5508 window: &mut Window,
5509 cx: &mut Context<Self>,
5510 ) {
5511 if self
5512 .code_action_providers
5513 .iter()
5514 .any(|existing_provider| existing_provider.id() == provider.id())
5515 {
5516 return;
5517 }
5518
5519 self.code_action_providers.push(provider);
5520 self.refresh_code_actions(window, cx);
5521 }
5522
5523 pub fn remove_code_action_provider(
5524 &mut self,
5525 id: Arc<str>,
5526 window: &mut Window,
5527 cx: &mut Context<Self>,
5528 ) {
5529 self.code_action_providers
5530 .retain(|provider| provider.id() != id);
5531 self.refresh_code_actions(window, cx);
5532 }
5533
5534 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5535 let newest_selection = self.selections.newest_anchor().clone();
5536 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5537 let buffer = self.buffer.read(cx);
5538 if newest_selection.head().diff_base_anchor.is_some() {
5539 return None;
5540 }
5541 let (start_buffer, start) =
5542 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5543 let (end_buffer, end) =
5544 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5545 if start_buffer != end_buffer {
5546 return None;
5547 }
5548
5549 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5550 cx.background_executor()
5551 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5552 .await;
5553
5554 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5555 let providers = this.code_action_providers.clone();
5556 let tasks = this
5557 .code_action_providers
5558 .iter()
5559 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5560 .collect::<Vec<_>>();
5561 (providers, tasks)
5562 })?;
5563
5564 let mut actions = Vec::new();
5565 for (provider, provider_actions) in
5566 providers.into_iter().zip(future::join_all(tasks).await)
5567 {
5568 if let Some(provider_actions) = provider_actions.log_err() {
5569 actions.extend(provider_actions.into_iter().map(|action| {
5570 AvailableCodeAction {
5571 excerpt_id: newest_selection.start.excerpt_id,
5572 action,
5573 provider: provider.clone(),
5574 }
5575 }));
5576 }
5577 }
5578
5579 this.update(cx, |this, cx| {
5580 this.available_code_actions = if actions.is_empty() {
5581 None
5582 } else {
5583 Some((
5584 Location {
5585 buffer: start_buffer,
5586 range: start..end,
5587 },
5588 actions.into(),
5589 ))
5590 };
5591 cx.notify();
5592 })
5593 }));
5594 None
5595 }
5596
5597 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5598 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5599 self.show_git_blame_inline = false;
5600
5601 self.show_git_blame_inline_delay_task =
5602 Some(cx.spawn_in(window, async move |this, cx| {
5603 cx.background_executor().timer(delay).await;
5604
5605 this.update(cx, |this, cx| {
5606 this.show_git_blame_inline = true;
5607 cx.notify();
5608 })
5609 .log_err();
5610 }));
5611 }
5612 }
5613
5614 fn show_blame_popover(
5615 &mut self,
5616 blame_entry: &BlameEntry,
5617 position: gpui::Point<Pixels>,
5618 cx: &mut Context<Self>,
5619 ) {
5620 if let Some(state) = &mut self.inline_blame_popover {
5621 state.hide_task.take();
5622 cx.notify();
5623 } else {
5624 let delay = EditorSettings::get_global(cx).hover_popover_delay;
5625 let show_task = cx.spawn(async move |editor, cx| {
5626 cx.background_executor()
5627 .timer(std::time::Duration::from_millis(delay))
5628 .await;
5629 editor
5630 .update(cx, |editor, cx| {
5631 if let Some(state) = &mut editor.inline_blame_popover {
5632 state.show_task = None;
5633 cx.notify();
5634 }
5635 })
5636 .ok();
5637 });
5638 let Some(blame) = self.blame.as_ref() else {
5639 return;
5640 };
5641 let blame = blame.read(cx);
5642 let details = blame.details_for_entry(&blame_entry);
5643 let markdown = cx.new(|cx| {
5644 Markdown::new(
5645 details
5646 .as_ref()
5647 .map(|message| message.message.clone())
5648 .unwrap_or_default(),
5649 None,
5650 None,
5651 cx,
5652 )
5653 });
5654 self.inline_blame_popover = Some(InlineBlamePopover {
5655 position,
5656 show_task: Some(show_task),
5657 hide_task: None,
5658 popover_bounds: None,
5659 popover_state: InlineBlamePopoverState {
5660 scroll_handle: ScrollHandle::new(),
5661 commit_message: details,
5662 markdown,
5663 },
5664 });
5665 }
5666 }
5667
5668 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
5669 if let Some(state) = &mut self.inline_blame_popover {
5670 if state.show_task.is_some() {
5671 self.inline_blame_popover.take();
5672 cx.notify();
5673 } else {
5674 let hide_task = cx.spawn(async move |editor, cx| {
5675 cx.background_executor()
5676 .timer(std::time::Duration::from_millis(100))
5677 .await;
5678 editor
5679 .update(cx, |editor, cx| {
5680 editor.inline_blame_popover.take();
5681 cx.notify();
5682 })
5683 .ok();
5684 });
5685 state.hide_task = Some(hide_task);
5686 }
5687 }
5688 }
5689
5690 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5691 if self.pending_rename.is_some() {
5692 return None;
5693 }
5694
5695 let provider = self.semantics_provider.clone()?;
5696 let buffer = self.buffer.read(cx);
5697 let newest_selection = self.selections.newest_anchor().clone();
5698 let cursor_position = newest_selection.head();
5699 let (cursor_buffer, cursor_buffer_position) =
5700 buffer.text_anchor_for_position(cursor_position, cx)?;
5701 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5702 if cursor_buffer != tail_buffer {
5703 return None;
5704 }
5705 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5706 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5707 cx.background_executor()
5708 .timer(Duration::from_millis(debounce))
5709 .await;
5710
5711 let highlights = if let Some(highlights) = cx
5712 .update(|cx| {
5713 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5714 })
5715 .ok()
5716 .flatten()
5717 {
5718 highlights.await.log_err()
5719 } else {
5720 None
5721 };
5722
5723 if let Some(highlights) = highlights {
5724 this.update(cx, |this, cx| {
5725 if this.pending_rename.is_some() {
5726 return;
5727 }
5728
5729 let buffer_id = cursor_position.buffer_id;
5730 let buffer = this.buffer.read(cx);
5731 if !buffer
5732 .text_anchor_for_position(cursor_position, cx)
5733 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5734 {
5735 return;
5736 }
5737
5738 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5739 let mut write_ranges = Vec::new();
5740 let mut read_ranges = Vec::new();
5741 for highlight in highlights {
5742 for (excerpt_id, excerpt_range) in
5743 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5744 {
5745 let start = highlight
5746 .range
5747 .start
5748 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5749 let end = highlight
5750 .range
5751 .end
5752 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5753 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5754 continue;
5755 }
5756
5757 let range = Anchor {
5758 buffer_id,
5759 excerpt_id,
5760 text_anchor: start,
5761 diff_base_anchor: None,
5762 }..Anchor {
5763 buffer_id,
5764 excerpt_id,
5765 text_anchor: end,
5766 diff_base_anchor: None,
5767 };
5768 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5769 write_ranges.push(range);
5770 } else {
5771 read_ranges.push(range);
5772 }
5773 }
5774 }
5775
5776 this.highlight_background::<DocumentHighlightRead>(
5777 &read_ranges,
5778 |theme| theme.editor_document_highlight_read_background,
5779 cx,
5780 );
5781 this.highlight_background::<DocumentHighlightWrite>(
5782 &write_ranges,
5783 |theme| theme.editor_document_highlight_write_background,
5784 cx,
5785 );
5786 cx.notify();
5787 })
5788 .log_err();
5789 }
5790 }));
5791 None
5792 }
5793
5794 fn prepare_highlight_query_from_selection(
5795 &mut self,
5796 cx: &mut Context<Editor>,
5797 ) -> Option<(String, Range<Anchor>)> {
5798 if matches!(self.mode, EditorMode::SingleLine { .. }) {
5799 return None;
5800 }
5801 if !EditorSettings::get_global(cx).selection_highlight {
5802 return None;
5803 }
5804 if self.selections.count() != 1 || self.selections.line_mode {
5805 return None;
5806 }
5807 let selection = self.selections.newest::<Point>(cx);
5808 if selection.is_empty() || selection.start.row != selection.end.row {
5809 return None;
5810 }
5811 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5812 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
5813 let query = multi_buffer_snapshot
5814 .text_for_range(selection_anchor_range.clone())
5815 .collect::<String>();
5816 if query.trim().is_empty() {
5817 return None;
5818 }
5819 Some((query, selection_anchor_range))
5820 }
5821
5822 fn update_selection_occurrence_highlights(
5823 &mut self,
5824 query_text: String,
5825 query_range: Range<Anchor>,
5826 multi_buffer_range_to_query: Range<Point>,
5827 use_debounce: bool,
5828 window: &mut Window,
5829 cx: &mut Context<Editor>,
5830 ) -> Task<()> {
5831 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5832 cx.spawn_in(window, async move |editor, cx| {
5833 if use_debounce {
5834 cx.background_executor()
5835 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
5836 .await;
5837 }
5838 let match_task = cx.background_spawn(async move {
5839 let buffer_ranges = multi_buffer_snapshot
5840 .range_to_buffer_ranges(multi_buffer_range_to_query)
5841 .into_iter()
5842 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
5843 let mut match_ranges = Vec::new();
5844 let Ok(regex) = project::search::SearchQuery::text(
5845 query_text.clone(),
5846 false,
5847 false,
5848 false,
5849 Default::default(),
5850 Default::default(),
5851 false,
5852 None,
5853 ) else {
5854 return Vec::default();
5855 };
5856 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
5857 match_ranges.extend(
5858 regex
5859 .search(&buffer_snapshot, Some(search_range.clone()))
5860 .await
5861 .into_iter()
5862 .filter_map(|match_range| {
5863 let match_start = buffer_snapshot
5864 .anchor_after(search_range.start + match_range.start);
5865 let match_end = buffer_snapshot
5866 .anchor_before(search_range.start + match_range.end);
5867 let match_anchor_range = Anchor::range_in_buffer(
5868 excerpt_id,
5869 buffer_snapshot.remote_id(),
5870 match_start..match_end,
5871 );
5872 (match_anchor_range != query_range).then_some(match_anchor_range)
5873 }),
5874 );
5875 }
5876 match_ranges
5877 });
5878 let match_ranges = match_task.await;
5879 editor
5880 .update_in(cx, |editor, _, cx| {
5881 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5882 if !match_ranges.is_empty() {
5883 editor.highlight_background::<SelectedTextHighlight>(
5884 &match_ranges,
5885 |theme| theme.editor_document_highlight_bracket_background,
5886 cx,
5887 )
5888 }
5889 })
5890 .log_err();
5891 })
5892 }
5893
5894 fn refresh_selected_text_highlights(
5895 &mut self,
5896 on_buffer_edit: bool,
5897 window: &mut Window,
5898 cx: &mut Context<Editor>,
5899 ) {
5900 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
5901 else {
5902 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5903 self.quick_selection_highlight_task.take();
5904 self.debounced_selection_highlight_task.take();
5905 return;
5906 };
5907 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5908 if on_buffer_edit
5909 || self
5910 .quick_selection_highlight_task
5911 .as_ref()
5912 .map_or(true, |(prev_anchor_range, _)| {
5913 prev_anchor_range != &query_range
5914 })
5915 {
5916 let multi_buffer_visible_start = self
5917 .scroll_manager
5918 .anchor()
5919 .anchor
5920 .to_point(&multi_buffer_snapshot);
5921 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5922 multi_buffer_visible_start
5923 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5924 Bias::Left,
5925 );
5926 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5927 self.quick_selection_highlight_task = Some((
5928 query_range.clone(),
5929 self.update_selection_occurrence_highlights(
5930 query_text.clone(),
5931 query_range.clone(),
5932 multi_buffer_visible_range,
5933 false,
5934 window,
5935 cx,
5936 ),
5937 ));
5938 }
5939 if on_buffer_edit
5940 || self
5941 .debounced_selection_highlight_task
5942 .as_ref()
5943 .map_or(true, |(prev_anchor_range, _)| {
5944 prev_anchor_range != &query_range
5945 })
5946 {
5947 let multi_buffer_start = multi_buffer_snapshot
5948 .anchor_before(0)
5949 .to_point(&multi_buffer_snapshot);
5950 let multi_buffer_end = multi_buffer_snapshot
5951 .anchor_after(multi_buffer_snapshot.len())
5952 .to_point(&multi_buffer_snapshot);
5953 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
5954 self.debounced_selection_highlight_task = Some((
5955 query_range.clone(),
5956 self.update_selection_occurrence_highlights(
5957 query_text,
5958 query_range,
5959 multi_buffer_full_range,
5960 true,
5961 window,
5962 cx,
5963 ),
5964 ));
5965 }
5966 }
5967
5968 pub fn refresh_inline_completion(
5969 &mut self,
5970 debounce: bool,
5971 user_requested: bool,
5972 window: &mut Window,
5973 cx: &mut Context<Self>,
5974 ) -> Option<()> {
5975 let provider = self.edit_prediction_provider()?;
5976 let cursor = self.selections.newest_anchor().head();
5977 let (buffer, cursor_buffer_position) =
5978 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5979
5980 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
5981 self.discard_inline_completion(false, cx);
5982 return None;
5983 }
5984
5985 if !user_requested
5986 && (!self.should_show_edit_predictions()
5987 || !self.is_focused(window)
5988 || buffer.read(cx).is_empty())
5989 {
5990 self.discard_inline_completion(false, cx);
5991 return None;
5992 }
5993
5994 self.update_visible_inline_completion(window, cx);
5995 provider.refresh(
5996 self.project.clone(),
5997 buffer,
5998 cursor_buffer_position,
5999 debounce,
6000 cx,
6001 );
6002 Some(())
6003 }
6004
6005 fn show_edit_predictions_in_menu(&self) -> bool {
6006 match self.edit_prediction_settings {
6007 EditPredictionSettings::Disabled => false,
6008 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6009 }
6010 }
6011
6012 pub fn edit_predictions_enabled(&self) -> bool {
6013 match self.edit_prediction_settings {
6014 EditPredictionSettings::Disabled => false,
6015 EditPredictionSettings::Enabled { .. } => true,
6016 }
6017 }
6018
6019 fn edit_prediction_requires_modifier(&self) -> bool {
6020 match self.edit_prediction_settings {
6021 EditPredictionSettings::Disabled => false,
6022 EditPredictionSettings::Enabled {
6023 preview_requires_modifier,
6024 ..
6025 } => preview_requires_modifier,
6026 }
6027 }
6028
6029 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6030 if self.edit_prediction_provider.is_none() {
6031 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6032 } else {
6033 let selection = self.selections.newest_anchor();
6034 let cursor = selection.head();
6035
6036 if let Some((buffer, cursor_buffer_position)) =
6037 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6038 {
6039 self.edit_prediction_settings =
6040 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6041 }
6042 }
6043 }
6044
6045 fn edit_prediction_settings_at_position(
6046 &self,
6047 buffer: &Entity<Buffer>,
6048 buffer_position: language::Anchor,
6049 cx: &App,
6050 ) -> EditPredictionSettings {
6051 if !self.mode.is_full()
6052 || !self.show_inline_completions_override.unwrap_or(true)
6053 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6054 {
6055 return EditPredictionSettings::Disabled;
6056 }
6057
6058 let buffer = buffer.read(cx);
6059
6060 let file = buffer.file();
6061
6062 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6063 return EditPredictionSettings::Disabled;
6064 };
6065
6066 let by_provider = matches!(
6067 self.menu_inline_completions_policy,
6068 MenuInlineCompletionsPolicy::ByProvider
6069 );
6070
6071 let show_in_menu = by_provider
6072 && self
6073 .edit_prediction_provider
6074 .as_ref()
6075 .map_or(false, |provider| {
6076 provider.provider.show_completions_in_menu()
6077 });
6078
6079 let preview_requires_modifier =
6080 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6081
6082 EditPredictionSettings::Enabled {
6083 show_in_menu,
6084 preview_requires_modifier,
6085 }
6086 }
6087
6088 fn should_show_edit_predictions(&self) -> bool {
6089 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6090 }
6091
6092 pub fn edit_prediction_preview_is_active(&self) -> bool {
6093 matches!(
6094 self.edit_prediction_preview,
6095 EditPredictionPreview::Active { .. }
6096 )
6097 }
6098
6099 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6100 let cursor = self.selections.newest_anchor().head();
6101 if let Some((buffer, cursor_position)) =
6102 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6103 {
6104 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6105 } else {
6106 false
6107 }
6108 }
6109
6110 fn edit_predictions_enabled_in_buffer(
6111 &self,
6112 buffer: &Entity<Buffer>,
6113 buffer_position: language::Anchor,
6114 cx: &App,
6115 ) -> bool {
6116 maybe!({
6117 if self.read_only(cx) {
6118 return Some(false);
6119 }
6120 let provider = self.edit_prediction_provider()?;
6121 if !provider.is_enabled(&buffer, buffer_position, cx) {
6122 return Some(false);
6123 }
6124 let buffer = buffer.read(cx);
6125 let Some(file) = buffer.file() else {
6126 return Some(true);
6127 };
6128 let settings = all_language_settings(Some(file), cx);
6129 Some(settings.edit_predictions_enabled_for_file(file, cx))
6130 })
6131 .unwrap_or(false)
6132 }
6133
6134 fn cycle_inline_completion(
6135 &mut self,
6136 direction: Direction,
6137 window: &mut Window,
6138 cx: &mut Context<Self>,
6139 ) -> Option<()> {
6140 let provider = self.edit_prediction_provider()?;
6141 let cursor = self.selections.newest_anchor().head();
6142 let (buffer, cursor_buffer_position) =
6143 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6144 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6145 return None;
6146 }
6147
6148 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6149 self.update_visible_inline_completion(window, cx);
6150
6151 Some(())
6152 }
6153
6154 pub fn show_inline_completion(
6155 &mut self,
6156 _: &ShowEditPrediction,
6157 window: &mut Window,
6158 cx: &mut Context<Self>,
6159 ) {
6160 if !self.has_active_inline_completion() {
6161 self.refresh_inline_completion(false, true, window, cx);
6162 return;
6163 }
6164
6165 self.update_visible_inline_completion(window, cx);
6166 }
6167
6168 pub fn display_cursor_names(
6169 &mut self,
6170 _: &DisplayCursorNames,
6171 window: &mut Window,
6172 cx: &mut Context<Self>,
6173 ) {
6174 self.show_cursor_names(window, cx);
6175 }
6176
6177 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6178 self.show_cursor_names = true;
6179 cx.notify();
6180 cx.spawn_in(window, async move |this, cx| {
6181 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6182 this.update(cx, |this, cx| {
6183 this.show_cursor_names = false;
6184 cx.notify()
6185 })
6186 .ok()
6187 })
6188 .detach();
6189 }
6190
6191 pub fn next_edit_prediction(
6192 &mut self,
6193 _: &NextEditPrediction,
6194 window: &mut Window,
6195 cx: &mut Context<Self>,
6196 ) {
6197 if self.has_active_inline_completion() {
6198 self.cycle_inline_completion(Direction::Next, window, cx);
6199 } else {
6200 let is_copilot_disabled = self
6201 .refresh_inline_completion(false, true, window, cx)
6202 .is_none();
6203 if is_copilot_disabled {
6204 cx.propagate();
6205 }
6206 }
6207 }
6208
6209 pub fn previous_edit_prediction(
6210 &mut self,
6211 _: &PreviousEditPrediction,
6212 window: &mut Window,
6213 cx: &mut Context<Self>,
6214 ) {
6215 if self.has_active_inline_completion() {
6216 self.cycle_inline_completion(Direction::Prev, window, cx);
6217 } else {
6218 let is_copilot_disabled = self
6219 .refresh_inline_completion(false, true, window, cx)
6220 .is_none();
6221 if is_copilot_disabled {
6222 cx.propagate();
6223 }
6224 }
6225 }
6226
6227 pub fn accept_edit_prediction(
6228 &mut self,
6229 _: &AcceptEditPrediction,
6230 window: &mut Window,
6231 cx: &mut Context<Self>,
6232 ) {
6233 if self.show_edit_predictions_in_menu() {
6234 self.hide_context_menu(window, cx);
6235 }
6236
6237 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6238 return;
6239 };
6240
6241 self.report_inline_completion_event(
6242 active_inline_completion.completion_id.clone(),
6243 true,
6244 cx,
6245 );
6246
6247 match &active_inline_completion.completion {
6248 InlineCompletion::Move { target, .. } => {
6249 let target = *target;
6250
6251 if let Some(position_map) = &self.last_position_map {
6252 if position_map
6253 .visible_row_range
6254 .contains(&target.to_display_point(&position_map.snapshot).row())
6255 || !self.edit_prediction_requires_modifier()
6256 {
6257 self.unfold_ranges(&[target..target], true, false, cx);
6258 // Note that this is also done in vim's handler of the Tab action.
6259 self.change_selections(
6260 Some(Autoscroll::newest()),
6261 window,
6262 cx,
6263 |selections| {
6264 selections.select_anchor_ranges([target..target]);
6265 },
6266 );
6267 self.clear_row_highlights::<EditPredictionPreview>();
6268
6269 self.edit_prediction_preview
6270 .set_previous_scroll_position(None);
6271 } else {
6272 self.edit_prediction_preview
6273 .set_previous_scroll_position(Some(
6274 position_map.snapshot.scroll_anchor,
6275 ));
6276
6277 self.highlight_rows::<EditPredictionPreview>(
6278 target..target,
6279 cx.theme().colors().editor_highlighted_line_background,
6280 RowHighlightOptions {
6281 autoscroll: true,
6282 ..Default::default()
6283 },
6284 cx,
6285 );
6286 self.request_autoscroll(Autoscroll::fit(), cx);
6287 }
6288 }
6289 }
6290 InlineCompletion::Edit { edits, .. } => {
6291 if let Some(provider) = self.edit_prediction_provider() {
6292 provider.accept(cx);
6293 }
6294
6295 let snapshot = self.buffer.read(cx).snapshot(cx);
6296 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6297
6298 self.buffer.update(cx, |buffer, cx| {
6299 buffer.edit(edits.iter().cloned(), None, cx)
6300 });
6301
6302 self.change_selections(None, window, cx, |s| {
6303 s.select_anchor_ranges([last_edit_end..last_edit_end])
6304 });
6305
6306 self.update_visible_inline_completion(window, cx);
6307 if self.active_inline_completion.is_none() {
6308 self.refresh_inline_completion(true, true, window, cx);
6309 }
6310
6311 cx.notify();
6312 }
6313 }
6314
6315 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6316 }
6317
6318 pub fn accept_partial_inline_completion(
6319 &mut self,
6320 _: &AcceptPartialEditPrediction,
6321 window: &mut Window,
6322 cx: &mut Context<Self>,
6323 ) {
6324 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6325 return;
6326 };
6327 if self.selections.count() != 1 {
6328 return;
6329 }
6330
6331 self.report_inline_completion_event(
6332 active_inline_completion.completion_id.clone(),
6333 true,
6334 cx,
6335 );
6336
6337 match &active_inline_completion.completion {
6338 InlineCompletion::Move { target, .. } => {
6339 let target = *target;
6340 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6341 selections.select_anchor_ranges([target..target]);
6342 });
6343 }
6344 InlineCompletion::Edit { edits, .. } => {
6345 // Find an insertion that starts at the cursor position.
6346 let snapshot = self.buffer.read(cx).snapshot(cx);
6347 let cursor_offset = self.selections.newest::<usize>(cx).head();
6348 let insertion = edits.iter().find_map(|(range, text)| {
6349 let range = range.to_offset(&snapshot);
6350 if range.is_empty() && range.start == cursor_offset {
6351 Some(text)
6352 } else {
6353 None
6354 }
6355 });
6356
6357 if let Some(text) = insertion {
6358 let mut partial_completion = text
6359 .chars()
6360 .by_ref()
6361 .take_while(|c| c.is_alphabetic())
6362 .collect::<String>();
6363 if partial_completion.is_empty() {
6364 partial_completion = text
6365 .chars()
6366 .by_ref()
6367 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6368 .collect::<String>();
6369 }
6370
6371 cx.emit(EditorEvent::InputHandled {
6372 utf16_range_to_replace: None,
6373 text: partial_completion.clone().into(),
6374 });
6375
6376 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6377
6378 self.refresh_inline_completion(true, true, window, cx);
6379 cx.notify();
6380 } else {
6381 self.accept_edit_prediction(&Default::default(), window, cx);
6382 }
6383 }
6384 }
6385 }
6386
6387 fn discard_inline_completion(
6388 &mut self,
6389 should_report_inline_completion_event: bool,
6390 cx: &mut Context<Self>,
6391 ) -> bool {
6392 if should_report_inline_completion_event {
6393 let completion_id = self
6394 .active_inline_completion
6395 .as_ref()
6396 .and_then(|active_completion| active_completion.completion_id.clone());
6397
6398 self.report_inline_completion_event(completion_id, false, cx);
6399 }
6400
6401 if let Some(provider) = self.edit_prediction_provider() {
6402 provider.discard(cx);
6403 }
6404
6405 self.take_active_inline_completion(cx)
6406 }
6407
6408 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6409 let Some(provider) = self.edit_prediction_provider() else {
6410 return;
6411 };
6412
6413 let Some((_, buffer, _)) = self
6414 .buffer
6415 .read(cx)
6416 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6417 else {
6418 return;
6419 };
6420
6421 let extension = buffer
6422 .read(cx)
6423 .file()
6424 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6425
6426 let event_type = match accepted {
6427 true => "Edit Prediction Accepted",
6428 false => "Edit Prediction Discarded",
6429 };
6430 telemetry::event!(
6431 event_type,
6432 provider = provider.name(),
6433 prediction_id = id,
6434 suggestion_accepted = accepted,
6435 file_extension = extension,
6436 );
6437 }
6438
6439 pub fn has_active_inline_completion(&self) -> bool {
6440 self.active_inline_completion.is_some()
6441 }
6442
6443 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6444 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6445 return false;
6446 };
6447
6448 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6449 self.clear_highlights::<InlineCompletionHighlight>(cx);
6450 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6451 true
6452 }
6453
6454 /// Returns true when we're displaying the edit prediction popover below the cursor
6455 /// like we are not previewing and the LSP autocomplete menu is visible
6456 /// or we are in `when_holding_modifier` mode.
6457 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6458 if self.edit_prediction_preview_is_active()
6459 || !self.show_edit_predictions_in_menu()
6460 || !self.edit_predictions_enabled()
6461 {
6462 return false;
6463 }
6464
6465 if self.has_visible_completions_menu() {
6466 return true;
6467 }
6468
6469 has_completion && self.edit_prediction_requires_modifier()
6470 }
6471
6472 fn handle_modifiers_changed(
6473 &mut self,
6474 modifiers: Modifiers,
6475 position_map: &PositionMap,
6476 window: &mut Window,
6477 cx: &mut Context<Self>,
6478 ) {
6479 if self.show_edit_predictions_in_menu() {
6480 self.update_edit_prediction_preview(&modifiers, window, cx);
6481 }
6482
6483 self.update_selection_mode(&modifiers, position_map, window, cx);
6484
6485 let mouse_position = window.mouse_position();
6486 if !position_map.text_hitbox.is_hovered(window) {
6487 return;
6488 }
6489
6490 self.update_hovered_link(
6491 position_map.point_for_position(mouse_position),
6492 &position_map.snapshot,
6493 modifiers,
6494 window,
6495 cx,
6496 )
6497 }
6498
6499 fn update_selection_mode(
6500 &mut self,
6501 modifiers: &Modifiers,
6502 position_map: &PositionMap,
6503 window: &mut Window,
6504 cx: &mut Context<Self>,
6505 ) {
6506 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6507 return;
6508 }
6509
6510 let mouse_position = window.mouse_position();
6511 let point_for_position = position_map.point_for_position(mouse_position);
6512 let position = point_for_position.previous_valid;
6513
6514 self.select(
6515 SelectPhase::BeginColumnar {
6516 position,
6517 reset: false,
6518 goal_column: point_for_position.exact_unclipped.column(),
6519 },
6520 window,
6521 cx,
6522 );
6523 }
6524
6525 fn update_edit_prediction_preview(
6526 &mut self,
6527 modifiers: &Modifiers,
6528 window: &mut Window,
6529 cx: &mut Context<Self>,
6530 ) {
6531 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6532 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6533 return;
6534 };
6535
6536 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6537 if matches!(
6538 self.edit_prediction_preview,
6539 EditPredictionPreview::Inactive { .. }
6540 ) {
6541 self.edit_prediction_preview = EditPredictionPreview::Active {
6542 previous_scroll_position: None,
6543 since: Instant::now(),
6544 };
6545
6546 self.update_visible_inline_completion(window, cx);
6547 cx.notify();
6548 }
6549 } else if let EditPredictionPreview::Active {
6550 previous_scroll_position,
6551 since,
6552 } = self.edit_prediction_preview
6553 {
6554 if let (Some(previous_scroll_position), Some(position_map)) =
6555 (previous_scroll_position, self.last_position_map.as_ref())
6556 {
6557 self.set_scroll_position(
6558 previous_scroll_position
6559 .scroll_position(&position_map.snapshot.display_snapshot),
6560 window,
6561 cx,
6562 );
6563 }
6564
6565 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6566 released_too_fast: since.elapsed() < Duration::from_millis(200),
6567 };
6568 self.clear_row_highlights::<EditPredictionPreview>();
6569 self.update_visible_inline_completion(window, cx);
6570 cx.notify();
6571 }
6572 }
6573
6574 fn update_visible_inline_completion(
6575 &mut self,
6576 _window: &mut Window,
6577 cx: &mut Context<Self>,
6578 ) -> Option<()> {
6579 let selection = self.selections.newest_anchor();
6580 let cursor = selection.head();
6581 let multibuffer = self.buffer.read(cx).snapshot(cx);
6582 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6583 let excerpt_id = cursor.excerpt_id;
6584
6585 let show_in_menu = self.show_edit_predictions_in_menu();
6586 let completions_menu_has_precedence = !show_in_menu
6587 && (self.context_menu.borrow().is_some()
6588 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6589
6590 if completions_menu_has_precedence
6591 || !offset_selection.is_empty()
6592 || self
6593 .active_inline_completion
6594 .as_ref()
6595 .map_or(false, |completion| {
6596 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6597 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6598 !invalidation_range.contains(&offset_selection.head())
6599 })
6600 {
6601 self.discard_inline_completion(false, cx);
6602 return None;
6603 }
6604
6605 self.take_active_inline_completion(cx);
6606 let Some(provider) = self.edit_prediction_provider() else {
6607 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6608 return None;
6609 };
6610
6611 let (buffer, cursor_buffer_position) =
6612 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6613
6614 self.edit_prediction_settings =
6615 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6616
6617 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6618
6619 if self.edit_prediction_indent_conflict {
6620 let cursor_point = cursor.to_point(&multibuffer);
6621
6622 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6623
6624 if let Some((_, indent)) = indents.iter().next() {
6625 if indent.len == cursor_point.column {
6626 self.edit_prediction_indent_conflict = false;
6627 }
6628 }
6629 }
6630
6631 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6632 let edits = inline_completion
6633 .edits
6634 .into_iter()
6635 .flat_map(|(range, new_text)| {
6636 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6637 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6638 Some((start..end, new_text))
6639 })
6640 .collect::<Vec<_>>();
6641 if edits.is_empty() {
6642 return None;
6643 }
6644
6645 let first_edit_start = edits.first().unwrap().0.start;
6646 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6647 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6648
6649 let last_edit_end = edits.last().unwrap().0.end;
6650 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6651 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6652
6653 let cursor_row = cursor.to_point(&multibuffer).row;
6654
6655 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6656
6657 let mut inlay_ids = Vec::new();
6658 let invalidation_row_range;
6659 let move_invalidation_row_range = if cursor_row < edit_start_row {
6660 Some(cursor_row..edit_end_row)
6661 } else if cursor_row > edit_end_row {
6662 Some(edit_start_row..cursor_row)
6663 } else {
6664 None
6665 };
6666 let is_move =
6667 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6668 let completion = if is_move {
6669 invalidation_row_range =
6670 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6671 let target = first_edit_start;
6672 InlineCompletion::Move { target, snapshot }
6673 } else {
6674 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6675 && !self.inline_completions_hidden_for_vim_mode;
6676
6677 if show_completions_in_buffer {
6678 if edits
6679 .iter()
6680 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6681 {
6682 let mut inlays = Vec::new();
6683 for (range, new_text) in &edits {
6684 let inlay = Inlay::inline_completion(
6685 post_inc(&mut self.next_inlay_id),
6686 range.start,
6687 new_text.as_str(),
6688 );
6689 inlay_ids.push(inlay.id);
6690 inlays.push(inlay);
6691 }
6692
6693 self.splice_inlays(&[], inlays, cx);
6694 } else {
6695 let background_color = cx.theme().status().deleted_background;
6696 self.highlight_text::<InlineCompletionHighlight>(
6697 edits.iter().map(|(range, _)| range.clone()).collect(),
6698 HighlightStyle {
6699 background_color: Some(background_color),
6700 ..Default::default()
6701 },
6702 cx,
6703 );
6704 }
6705 }
6706
6707 invalidation_row_range = edit_start_row..edit_end_row;
6708
6709 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6710 if provider.show_tab_accept_marker() {
6711 EditDisplayMode::TabAccept
6712 } else {
6713 EditDisplayMode::Inline
6714 }
6715 } else {
6716 EditDisplayMode::DiffPopover
6717 };
6718
6719 InlineCompletion::Edit {
6720 edits,
6721 edit_preview: inline_completion.edit_preview,
6722 display_mode,
6723 snapshot,
6724 }
6725 };
6726
6727 let invalidation_range = multibuffer
6728 .anchor_before(Point::new(invalidation_row_range.start, 0))
6729 ..multibuffer.anchor_after(Point::new(
6730 invalidation_row_range.end,
6731 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6732 ));
6733
6734 self.stale_inline_completion_in_menu = None;
6735 self.active_inline_completion = Some(InlineCompletionState {
6736 inlay_ids,
6737 completion,
6738 completion_id: inline_completion.id,
6739 invalidation_range,
6740 });
6741
6742 cx.notify();
6743
6744 Some(())
6745 }
6746
6747 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6748 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6749 }
6750
6751 fn clear_tasks(&mut self) {
6752 self.tasks.clear()
6753 }
6754
6755 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6756 if self.tasks.insert(key, value).is_some() {
6757 // This case should hopefully be rare, but just in case...
6758 log::error!(
6759 "multiple different run targets found on a single line, only the last target will be rendered"
6760 )
6761 }
6762 }
6763
6764 /// Get all display points of breakpoints that will be rendered within editor
6765 ///
6766 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6767 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6768 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6769 fn active_breakpoints(
6770 &self,
6771 range: Range<DisplayRow>,
6772 window: &mut Window,
6773 cx: &mut Context<Self>,
6774 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
6775 let mut breakpoint_display_points = HashMap::default();
6776
6777 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
6778 return breakpoint_display_points;
6779 };
6780
6781 let snapshot = self.snapshot(window, cx);
6782
6783 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
6784 let Some(project) = self.project.as_ref() else {
6785 return breakpoint_display_points;
6786 };
6787
6788 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
6789 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
6790
6791 for (buffer_snapshot, range, excerpt_id) in
6792 multi_buffer_snapshot.range_to_buffer_ranges(range)
6793 {
6794 let Some(buffer) = project.read_with(cx, |this, cx| {
6795 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
6796 }) else {
6797 continue;
6798 };
6799 let breakpoints = breakpoint_store.read(cx).breakpoints(
6800 &buffer,
6801 Some(
6802 buffer_snapshot.anchor_before(range.start)
6803 ..buffer_snapshot.anchor_after(range.end),
6804 ),
6805 buffer_snapshot,
6806 cx,
6807 );
6808 for (anchor, breakpoint) in breakpoints {
6809 let multi_buffer_anchor =
6810 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), *anchor);
6811 let position = multi_buffer_anchor
6812 .to_point(&multi_buffer_snapshot)
6813 .to_display_point(&snapshot);
6814
6815 breakpoint_display_points
6816 .insert(position.row(), (multi_buffer_anchor, breakpoint.clone()));
6817 }
6818 }
6819
6820 breakpoint_display_points
6821 }
6822
6823 fn breakpoint_context_menu(
6824 &self,
6825 anchor: Anchor,
6826 window: &mut Window,
6827 cx: &mut Context<Self>,
6828 ) -> Entity<ui::ContextMenu> {
6829 let weak_editor = cx.weak_entity();
6830 let focus_handle = self.focus_handle(cx);
6831
6832 let row = self
6833 .buffer
6834 .read(cx)
6835 .snapshot(cx)
6836 .summary_for_anchor::<Point>(&anchor)
6837 .row;
6838
6839 let breakpoint = self
6840 .breakpoint_at_row(row, window, cx)
6841 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
6842
6843 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
6844 "Edit Log Breakpoint"
6845 } else {
6846 "Set Log Breakpoint"
6847 };
6848
6849 let condition_breakpoint_msg = if breakpoint
6850 .as_ref()
6851 .is_some_and(|bp| bp.1.condition.is_some())
6852 {
6853 "Edit Condition Breakpoint"
6854 } else {
6855 "Set Condition Breakpoint"
6856 };
6857
6858 let hit_condition_breakpoint_msg = if breakpoint
6859 .as_ref()
6860 .is_some_and(|bp| bp.1.hit_condition.is_some())
6861 {
6862 "Edit Hit Condition Breakpoint"
6863 } else {
6864 "Set Hit Condition Breakpoint"
6865 };
6866
6867 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
6868 "Unset Breakpoint"
6869 } else {
6870 "Set Breakpoint"
6871 };
6872
6873 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
6874 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
6875
6876 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
6877 BreakpointState::Enabled => Some("Disable"),
6878 BreakpointState::Disabled => Some("Enable"),
6879 });
6880
6881 let (anchor, breakpoint) =
6882 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
6883
6884 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6885 menu.on_blur_subscription(Subscription::new(|| {}))
6886 .context(focus_handle)
6887 .when(run_to_cursor, |this| {
6888 let weak_editor = weak_editor.clone();
6889 this.entry("Run to cursor", None, move |window, cx| {
6890 weak_editor
6891 .update(cx, |editor, cx| {
6892 editor.change_selections(None, window, cx, |s| {
6893 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
6894 });
6895 })
6896 .ok();
6897
6898 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
6899 })
6900 .separator()
6901 })
6902 .when_some(toggle_state_msg, |this, msg| {
6903 this.entry(msg, None, {
6904 let weak_editor = weak_editor.clone();
6905 let breakpoint = breakpoint.clone();
6906 move |_window, cx| {
6907 weak_editor
6908 .update(cx, |this, cx| {
6909 this.edit_breakpoint_at_anchor(
6910 anchor,
6911 breakpoint.as_ref().clone(),
6912 BreakpointEditAction::InvertState,
6913 cx,
6914 );
6915 })
6916 .log_err();
6917 }
6918 })
6919 })
6920 .entry(set_breakpoint_msg, None, {
6921 let weak_editor = weak_editor.clone();
6922 let breakpoint = breakpoint.clone();
6923 move |_window, cx| {
6924 weak_editor
6925 .update(cx, |this, cx| {
6926 this.edit_breakpoint_at_anchor(
6927 anchor,
6928 breakpoint.as_ref().clone(),
6929 BreakpointEditAction::Toggle,
6930 cx,
6931 );
6932 })
6933 .log_err();
6934 }
6935 })
6936 .entry(log_breakpoint_msg, None, {
6937 let breakpoint = breakpoint.clone();
6938 let weak_editor = weak_editor.clone();
6939 move |window, cx| {
6940 weak_editor
6941 .update(cx, |this, cx| {
6942 this.add_edit_breakpoint_block(
6943 anchor,
6944 breakpoint.as_ref(),
6945 BreakpointPromptEditAction::Log,
6946 window,
6947 cx,
6948 );
6949 })
6950 .log_err();
6951 }
6952 })
6953 .entry(condition_breakpoint_msg, None, {
6954 let breakpoint = breakpoint.clone();
6955 let weak_editor = weak_editor.clone();
6956 move |window, cx| {
6957 weak_editor
6958 .update(cx, |this, cx| {
6959 this.add_edit_breakpoint_block(
6960 anchor,
6961 breakpoint.as_ref(),
6962 BreakpointPromptEditAction::Condition,
6963 window,
6964 cx,
6965 );
6966 })
6967 .log_err();
6968 }
6969 })
6970 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
6971 weak_editor
6972 .update(cx, |this, cx| {
6973 this.add_edit_breakpoint_block(
6974 anchor,
6975 breakpoint.as_ref(),
6976 BreakpointPromptEditAction::HitCondition,
6977 window,
6978 cx,
6979 );
6980 })
6981 .log_err();
6982 })
6983 })
6984 }
6985
6986 fn render_breakpoint(
6987 &self,
6988 position: Anchor,
6989 row: DisplayRow,
6990 breakpoint: &Breakpoint,
6991 cx: &mut Context<Self>,
6992 ) -> IconButton {
6993 // Is it a breakpoint that shows up when hovering over gutter?
6994 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
6995 (false, false),
6996 |PhantomBreakpointIndicator {
6997 is_active,
6998 display_row,
6999 collides_with_existing_breakpoint,
7000 }| {
7001 (
7002 is_active && display_row == row,
7003 collides_with_existing_breakpoint,
7004 )
7005 },
7006 );
7007
7008 let (color, icon) = {
7009 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7010 (false, false) => ui::IconName::DebugBreakpoint,
7011 (true, false) => ui::IconName::DebugLogBreakpoint,
7012 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7013 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7014 };
7015
7016 let color = if is_phantom {
7017 Color::Hint
7018 } else {
7019 Color::Debugger
7020 };
7021
7022 (color, icon)
7023 };
7024
7025 let breakpoint = Arc::from(breakpoint.clone());
7026
7027 let alt_as_text = gpui::Keystroke {
7028 modifiers: Modifiers::secondary_key(),
7029 ..Default::default()
7030 };
7031 let primary_action_text = if breakpoint.is_disabled() {
7032 "enable"
7033 } else if is_phantom && !collides_with_existing {
7034 "set"
7035 } else {
7036 "unset"
7037 };
7038 let mut primary_text = format!("Click to {primary_action_text}");
7039 if collides_with_existing && !breakpoint.is_disabled() {
7040 use std::fmt::Write;
7041 write!(primary_text, ", {alt_as_text}-click to disable").ok();
7042 }
7043 let primary_text = SharedString::from(primary_text);
7044 let focus_handle = self.focus_handle.clone();
7045 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7046 .icon_size(IconSize::XSmall)
7047 .size(ui::ButtonSize::None)
7048 .icon_color(color)
7049 .style(ButtonStyle::Transparent)
7050 .on_click(cx.listener({
7051 let breakpoint = breakpoint.clone();
7052
7053 move |editor, event: &ClickEvent, window, cx| {
7054 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7055 BreakpointEditAction::InvertState
7056 } else {
7057 BreakpointEditAction::Toggle
7058 };
7059
7060 window.focus(&editor.focus_handle(cx));
7061 editor.edit_breakpoint_at_anchor(
7062 position,
7063 breakpoint.as_ref().clone(),
7064 edit_action,
7065 cx,
7066 );
7067 }
7068 }))
7069 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7070 editor.set_breakpoint_context_menu(
7071 row,
7072 Some(position),
7073 event.down.position,
7074 window,
7075 cx,
7076 );
7077 }))
7078 .tooltip(move |window, cx| {
7079 Tooltip::with_meta_in(
7080 primary_text.clone(),
7081 None,
7082 "Right-click for more options",
7083 &focus_handle,
7084 window,
7085 cx,
7086 )
7087 })
7088 }
7089
7090 fn build_tasks_context(
7091 project: &Entity<Project>,
7092 buffer: &Entity<Buffer>,
7093 buffer_row: u32,
7094 tasks: &Arc<RunnableTasks>,
7095 cx: &mut Context<Self>,
7096 ) -> Task<Option<task::TaskContext>> {
7097 let position = Point::new(buffer_row, tasks.column);
7098 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7099 let location = Location {
7100 buffer: buffer.clone(),
7101 range: range_start..range_start,
7102 };
7103 // Fill in the environmental variables from the tree-sitter captures
7104 let mut captured_task_variables = TaskVariables::default();
7105 for (capture_name, value) in tasks.extra_variables.clone() {
7106 captured_task_variables.insert(
7107 task::VariableName::Custom(capture_name.into()),
7108 value.clone(),
7109 );
7110 }
7111 project.update(cx, |project, cx| {
7112 project.task_store().update(cx, |task_store, cx| {
7113 task_store.task_context_for_location(captured_task_variables, location, cx)
7114 })
7115 })
7116 }
7117
7118 pub fn spawn_nearest_task(
7119 &mut self,
7120 action: &SpawnNearestTask,
7121 window: &mut Window,
7122 cx: &mut Context<Self>,
7123 ) {
7124 let Some((workspace, _)) = self.workspace.clone() else {
7125 return;
7126 };
7127 let Some(project) = self.project.clone() else {
7128 return;
7129 };
7130
7131 // Try to find a closest, enclosing node using tree-sitter that has a
7132 // task
7133 let Some((buffer, buffer_row, tasks)) = self
7134 .find_enclosing_node_task(cx)
7135 // Or find the task that's closest in row-distance.
7136 .or_else(|| self.find_closest_task(cx))
7137 else {
7138 return;
7139 };
7140
7141 let reveal_strategy = action.reveal;
7142 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7143 cx.spawn_in(window, async move |_, cx| {
7144 let context = task_context.await?;
7145 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7146
7147 let resolved = &mut resolved_task.resolved;
7148 resolved.reveal = reveal_strategy;
7149
7150 workspace
7151 .update_in(cx, |workspace, window, cx| {
7152 workspace.schedule_resolved_task(
7153 task_source_kind,
7154 resolved_task,
7155 false,
7156 window,
7157 cx,
7158 );
7159 })
7160 .ok()
7161 })
7162 .detach();
7163 }
7164
7165 fn find_closest_task(
7166 &mut self,
7167 cx: &mut Context<Self>,
7168 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7169 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7170
7171 let ((buffer_id, row), tasks) = self
7172 .tasks
7173 .iter()
7174 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7175
7176 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7177 let tasks = Arc::new(tasks.to_owned());
7178 Some((buffer, *row, tasks))
7179 }
7180
7181 fn find_enclosing_node_task(
7182 &mut self,
7183 cx: &mut Context<Self>,
7184 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7185 let snapshot = self.buffer.read(cx).snapshot(cx);
7186 let offset = self.selections.newest::<usize>(cx).head();
7187 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7188 let buffer_id = excerpt.buffer().remote_id();
7189
7190 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7191 let mut cursor = layer.node().walk();
7192
7193 while cursor.goto_first_child_for_byte(offset).is_some() {
7194 if cursor.node().end_byte() == offset {
7195 cursor.goto_next_sibling();
7196 }
7197 }
7198
7199 // Ascend to the smallest ancestor that contains the range and has a task.
7200 loop {
7201 let node = cursor.node();
7202 let node_range = node.byte_range();
7203 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7204
7205 // Check if this node contains our offset
7206 if node_range.start <= offset && node_range.end >= offset {
7207 // If it contains offset, check for task
7208 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7209 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7210 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7211 }
7212 }
7213
7214 if !cursor.goto_parent() {
7215 break;
7216 }
7217 }
7218 None
7219 }
7220
7221 fn render_run_indicator(
7222 &self,
7223 _style: &EditorStyle,
7224 is_active: bool,
7225 row: DisplayRow,
7226 breakpoint: Option<(Anchor, Breakpoint)>,
7227 cx: &mut Context<Self>,
7228 ) -> IconButton {
7229 let color = Color::Muted;
7230 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
7231
7232 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7233 .shape(ui::IconButtonShape::Square)
7234 .icon_size(IconSize::XSmall)
7235 .icon_color(color)
7236 .toggle_state(is_active)
7237 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7238 let quick_launch = e.down.button == MouseButton::Left;
7239 window.focus(&editor.focus_handle(cx));
7240 editor.toggle_code_actions(
7241 &ToggleCodeActions {
7242 deployed_from_indicator: Some(row),
7243 quick_launch,
7244 },
7245 window,
7246 cx,
7247 );
7248 }))
7249 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7250 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7251 }))
7252 }
7253
7254 pub fn context_menu_visible(&self) -> bool {
7255 !self.edit_prediction_preview_is_active()
7256 && self
7257 .context_menu
7258 .borrow()
7259 .as_ref()
7260 .map_or(false, |menu| menu.visible())
7261 }
7262
7263 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7264 self.context_menu
7265 .borrow()
7266 .as_ref()
7267 .map(|menu| menu.origin())
7268 }
7269
7270 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7271 self.context_menu_options = Some(options);
7272 }
7273
7274 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7275 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7276
7277 fn render_edit_prediction_popover(
7278 &mut self,
7279 text_bounds: &Bounds<Pixels>,
7280 content_origin: gpui::Point<Pixels>,
7281 right_margin: Pixels,
7282 editor_snapshot: &EditorSnapshot,
7283 visible_row_range: Range<DisplayRow>,
7284 scroll_top: f32,
7285 scroll_bottom: f32,
7286 line_layouts: &[LineWithInvisibles],
7287 line_height: Pixels,
7288 scroll_pixel_position: gpui::Point<Pixels>,
7289 newest_selection_head: Option<DisplayPoint>,
7290 editor_width: Pixels,
7291 style: &EditorStyle,
7292 window: &mut Window,
7293 cx: &mut App,
7294 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7295 if self.mode().is_minimap() {
7296 return None;
7297 }
7298 let active_inline_completion = self.active_inline_completion.as_ref()?;
7299
7300 if self.edit_prediction_visible_in_cursor_popover(true) {
7301 return None;
7302 }
7303
7304 match &active_inline_completion.completion {
7305 InlineCompletion::Move { target, .. } => {
7306 let target_display_point = target.to_display_point(editor_snapshot);
7307
7308 if self.edit_prediction_requires_modifier() {
7309 if !self.edit_prediction_preview_is_active() {
7310 return None;
7311 }
7312
7313 self.render_edit_prediction_modifier_jump_popover(
7314 text_bounds,
7315 content_origin,
7316 visible_row_range,
7317 line_layouts,
7318 line_height,
7319 scroll_pixel_position,
7320 newest_selection_head,
7321 target_display_point,
7322 window,
7323 cx,
7324 )
7325 } else {
7326 self.render_edit_prediction_eager_jump_popover(
7327 text_bounds,
7328 content_origin,
7329 editor_snapshot,
7330 visible_row_range,
7331 scroll_top,
7332 scroll_bottom,
7333 line_height,
7334 scroll_pixel_position,
7335 target_display_point,
7336 editor_width,
7337 window,
7338 cx,
7339 )
7340 }
7341 }
7342 InlineCompletion::Edit {
7343 display_mode: EditDisplayMode::Inline,
7344 ..
7345 } => None,
7346 InlineCompletion::Edit {
7347 display_mode: EditDisplayMode::TabAccept,
7348 edits,
7349 ..
7350 } => {
7351 let range = &edits.first()?.0;
7352 let target_display_point = range.end.to_display_point(editor_snapshot);
7353
7354 self.render_edit_prediction_end_of_line_popover(
7355 "Accept",
7356 editor_snapshot,
7357 visible_row_range,
7358 target_display_point,
7359 line_height,
7360 scroll_pixel_position,
7361 content_origin,
7362 editor_width,
7363 window,
7364 cx,
7365 )
7366 }
7367 InlineCompletion::Edit {
7368 edits,
7369 edit_preview,
7370 display_mode: EditDisplayMode::DiffPopover,
7371 snapshot,
7372 } => self.render_edit_prediction_diff_popover(
7373 text_bounds,
7374 content_origin,
7375 right_margin,
7376 editor_snapshot,
7377 visible_row_range,
7378 line_layouts,
7379 line_height,
7380 scroll_pixel_position,
7381 newest_selection_head,
7382 editor_width,
7383 style,
7384 edits,
7385 edit_preview,
7386 snapshot,
7387 window,
7388 cx,
7389 ),
7390 }
7391 }
7392
7393 fn render_edit_prediction_modifier_jump_popover(
7394 &mut self,
7395 text_bounds: &Bounds<Pixels>,
7396 content_origin: gpui::Point<Pixels>,
7397 visible_row_range: Range<DisplayRow>,
7398 line_layouts: &[LineWithInvisibles],
7399 line_height: Pixels,
7400 scroll_pixel_position: gpui::Point<Pixels>,
7401 newest_selection_head: Option<DisplayPoint>,
7402 target_display_point: DisplayPoint,
7403 window: &mut Window,
7404 cx: &mut App,
7405 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7406 let scrolled_content_origin =
7407 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7408
7409 const SCROLL_PADDING_Y: Pixels = px(12.);
7410
7411 if target_display_point.row() < visible_row_range.start {
7412 return self.render_edit_prediction_scroll_popover(
7413 |_| SCROLL_PADDING_Y,
7414 IconName::ArrowUp,
7415 visible_row_range,
7416 line_layouts,
7417 newest_selection_head,
7418 scrolled_content_origin,
7419 window,
7420 cx,
7421 );
7422 } else if target_display_point.row() >= visible_row_range.end {
7423 return self.render_edit_prediction_scroll_popover(
7424 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7425 IconName::ArrowDown,
7426 visible_row_range,
7427 line_layouts,
7428 newest_selection_head,
7429 scrolled_content_origin,
7430 window,
7431 cx,
7432 );
7433 }
7434
7435 const POLE_WIDTH: Pixels = px(2.);
7436
7437 let line_layout =
7438 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7439 let target_column = target_display_point.column() as usize;
7440
7441 let target_x = line_layout.x_for_index(target_column);
7442 let target_y =
7443 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7444
7445 let flag_on_right = target_x < text_bounds.size.width / 2.;
7446
7447 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7448 border_color.l += 0.001;
7449
7450 let mut element = v_flex()
7451 .items_end()
7452 .when(flag_on_right, |el| el.items_start())
7453 .child(if flag_on_right {
7454 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7455 .rounded_bl(px(0.))
7456 .rounded_tl(px(0.))
7457 .border_l_2()
7458 .border_color(border_color)
7459 } else {
7460 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7461 .rounded_br(px(0.))
7462 .rounded_tr(px(0.))
7463 .border_r_2()
7464 .border_color(border_color)
7465 })
7466 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7467 .into_any();
7468
7469 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7470
7471 let mut origin = scrolled_content_origin + point(target_x, target_y)
7472 - point(
7473 if flag_on_right {
7474 POLE_WIDTH
7475 } else {
7476 size.width - POLE_WIDTH
7477 },
7478 size.height - line_height,
7479 );
7480
7481 origin.x = origin.x.max(content_origin.x);
7482
7483 element.prepaint_at(origin, window, cx);
7484
7485 Some((element, origin))
7486 }
7487
7488 fn render_edit_prediction_scroll_popover(
7489 &mut self,
7490 to_y: impl Fn(Size<Pixels>) -> Pixels,
7491 scroll_icon: IconName,
7492 visible_row_range: Range<DisplayRow>,
7493 line_layouts: &[LineWithInvisibles],
7494 newest_selection_head: Option<DisplayPoint>,
7495 scrolled_content_origin: gpui::Point<Pixels>,
7496 window: &mut Window,
7497 cx: &mut App,
7498 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7499 let mut element = self
7500 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7501 .into_any();
7502
7503 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7504
7505 let cursor = newest_selection_head?;
7506 let cursor_row_layout =
7507 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7508 let cursor_column = cursor.column() as usize;
7509
7510 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7511
7512 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7513
7514 element.prepaint_at(origin, window, cx);
7515 Some((element, origin))
7516 }
7517
7518 fn render_edit_prediction_eager_jump_popover(
7519 &mut self,
7520 text_bounds: &Bounds<Pixels>,
7521 content_origin: gpui::Point<Pixels>,
7522 editor_snapshot: &EditorSnapshot,
7523 visible_row_range: Range<DisplayRow>,
7524 scroll_top: f32,
7525 scroll_bottom: f32,
7526 line_height: Pixels,
7527 scroll_pixel_position: gpui::Point<Pixels>,
7528 target_display_point: DisplayPoint,
7529 editor_width: Pixels,
7530 window: &mut Window,
7531 cx: &mut App,
7532 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7533 if target_display_point.row().as_f32() < scroll_top {
7534 let mut element = self
7535 .render_edit_prediction_line_popover(
7536 "Jump to Edit",
7537 Some(IconName::ArrowUp),
7538 window,
7539 cx,
7540 )?
7541 .into_any();
7542
7543 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7544 let offset = point(
7545 (text_bounds.size.width - size.width) / 2.,
7546 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7547 );
7548
7549 let origin = text_bounds.origin + offset;
7550 element.prepaint_at(origin, window, cx);
7551 Some((element, origin))
7552 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7553 let mut element = self
7554 .render_edit_prediction_line_popover(
7555 "Jump to Edit",
7556 Some(IconName::ArrowDown),
7557 window,
7558 cx,
7559 )?
7560 .into_any();
7561
7562 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7563 let offset = point(
7564 (text_bounds.size.width - size.width) / 2.,
7565 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7566 );
7567
7568 let origin = text_bounds.origin + offset;
7569 element.prepaint_at(origin, window, cx);
7570 Some((element, origin))
7571 } else {
7572 self.render_edit_prediction_end_of_line_popover(
7573 "Jump to Edit",
7574 editor_snapshot,
7575 visible_row_range,
7576 target_display_point,
7577 line_height,
7578 scroll_pixel_position,
7579 content_origin,
7580 editor_width,
7581 window,
7582 cx,
7583 )
7584 }
7585 }
7586
7587 fn render_edit_prediction_end_of_line_popover(
7588 self: &mut Editor,
7589 label: &'static str,
7590 editor_snapshot: &EditorSnapshot,
7591 visible_row_range: Range<DisplayRow>,
7592 target_display_point: DisplayPoint,
7593 line_height: Pixels,
7594 scroll_pixel_position: gpui::Point<Pixels>,
7595 content_origin: gpui::Point<Pixels>,
7596 editor_width: Pixels,
7597 window: &mut Window,
7598 cx: &mut App,
7599 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7600 let target_line_end = DisplayPoint::new(
7601 target_display_point.row(),
7602 editor_snapshot.line_len(target_display_point.row()),
7603 );
7604
7605 let mut element = self
7606 .render_edit_prediction_line_popover(label, None, window, cx)?
7607 .into_any();
7608
7609 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7610
7611 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7612
7613 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7614 let mut origin = start_point
7615 + line_origin
7616 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7617 origin.x = origin.x.max(content_origin.x);
7618
7619 let max_x = content_origin.x + editor_width - size.width;
7620
7621 if origin.x > max_x {
7622 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7623
7624 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7625 origin.y += offset;
7626 IconName::ArrowUp
7627 } else {
7628 origin.y -= offset;
7629 IconName::ArrowDown
7630 };
7631
7632 element = self
7633 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7634 .into_any();
7635
7636 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7637
7638 origin.x = content_origin.x + editor_width - size.width - px(2.);
7639 }
7640
7641 element.prepaint_at(origin, window, cx);
7642 Some((element, origin))
7643 }
7644
7645 fn render_edit_prediction_diff_popover(
7646 self: &Editor,
7647 text_bounds: &Bounds<Pixels>,
7648 content_origin: gpui::Point<Pixels>,
7649 right_margin: Pixels,
7650 editor_snapshot: &EditorSnapshot,
7651 visible_row_range: Range<DisplayRow>,
7652 line_layouts: &[LineWithInvisibles],
7653 line_height: Pixels,
7654 scroll_pixel_position: gpui::Point<Pixels>,
7655 newest_selection_head: Option<DisplayPoint>,
7656 editor_width: Pixels,
7657 style: &EditorStyle,
7658 edits: &Vec<(Range<Anchor>, String)>,
7659 edit_preview: &Option<language::EditPreview>,
7660 snapshot: &language::BufferSnapshot,
7661 window: &mut Window,
7662 cx: &mut App,
7663 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7664 let edit_start = edits
7665 .first()
7666 .unwrap()
7667 .0
7668 .start
7669 .to_display_point(editor_snapshot);
7670 let edit_end = edits
7671 .last()
7672 .unwrap()
7673 .0
7674 .end
7675 .to_display_point(editor_snapshot);
7676
7677 let is_visible = visible_row_range.contains(&edit_start.row())
7678 || visible_row_range.contains(&edit_end.row());
7679 if !is_visible {
7680 return None;
7681 }
7682
7683 let highlighted_edits =
7684 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7685
7686 let styled_text = highlighted_edits.to_styled_text(&style.text);
7687 let line_count = highlighted_edits.text.lines().count();
7688
7689 const BORDER_WIDTH: Pixels = px(1.);
7690
7691 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7692 let has_keybind = keybind.is_some();
7693
7694 let mut element = h_flex()
7695 .items_start()
7696 .child(
7697 h_flex()
7698 .bg(cx.theme().colors().editor_background)
7699 .border(BORDER_WIDTH)
7700 .shadow_sm()
7701 .border_color(cx.theme().colors().border)
7702 .rounded_l_lg()
7703 .when(line_count > 1, |el| el.rounded_br_lg())
7704 .pr_1()
7705 .child(styled_text),
7706 )
7707 .child(
7708 h_flex()
7709 .h(line_height + BORDER_WIDTH * 2.)
7710 .px_1p5()
7711 .gap_1()
7712 // Workaround: For some reason, there's a gap if we don't do this
7713 .ml(-BORDER_WIDTH)
7714 .shadow(smallvec![gpui::BoxShadow {
7715 color: gpui::black().opacity(0.05),
7716 offset: point(px(1.), px(1.)),
7717 blur_radius: px(2.),
7718 spread_radius: px(0.),
7719 }])
7720 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7721 .border(BORDER_WIDTH)
7722 .border_color(cx.theme().colors().border)
7723 .rounded_r_lg()
7724 .id("edit_prediction_diff_popover_keybind")
7725 .when(!has_keybind, |el| {
7726 let status_colors = cx.theme().status();
7727
7728 el.bg(status_colors.error_background)
7729 .border_color(status_colors.error.opacity(0.6))
7730 .child(Icon::new(IconName::Info).color(Color::Error))
7731 .cursor_default()
7732 .hoverable_tooltip(move |_window, cx| {
7733 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7734 })
7735 })
7736 .children(keybind),
7737 )
7738 .into_any();
7739
7740 let longest_row =
7741 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7742 let longest_line_width = if visible_row_range.contains(&longest_row) {
7743 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7744 } else {
7745 layout_line(
7746 longest_row,
7747 editor_snapshot,
7748 style,
7749 editor_width,
7750 |_| false,
7751 window,
7752 cx,
7753 )
7754 .width
7755 };
7756
7757 let viewport_bounds =
7758 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
7759 right: -right_margin,
7760 ..Default::default()
7761 });
7762
7763 let x_after_longest =
7764 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
7765 - scroll_pixel_position.x;
7766
7767 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7768
7769 // Fully visible if it can be displayed within the window (allow overlapping other
7770 // panes). However, this is only allowed if the popover starts within text_bounds.
7771 let can_position_to_the_right = x_after_longest < text_bounds.right()
7772 && x_after_longest + element_bounds.width < viewport_bounds.right();
7773
7774 let mut origin = if can_position_to_the_right {
7775 point(
7776 x_after_longest,
7777 text_bounds.origin.y + edit_start.row().as_f32() * line_height
7778 - scroll_pixel_position.y,
7779 )
7780 } else {
7781 let cursor_row = newest_selection_head.map(|head| head.row());
7782 let above_edit = edit_start
7783 .row()
7784 .0
7785 .checked_sub(line_count as u32)
7786 .map(DisplayRow);
7787 let below_edit = Some(edit_end.row() + 1);
7788 let above_cursor =
7789 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
7790 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
7791
7792 // Place the edit popover adjacent to the edit if there is a location
7793 // available that is onscreen and does not obscure the cursor. Otherwise,
7794 // place it adjacent to the cursor.
7795 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
7796 .into_iter()
7797 .flatten()
7798 .find(|&start_row| {
7799 let end_row = start_row + line_count as u32;
7800 visible_row_range.contains(&start_row)
7801 && visible_row_range.contains(&end_row)
7802 && cursor_row.map_or(true, |cursor_row| {
7803 !((start_row..end_row).contains(&cursor_row))
7804 })
7805 })?;
7806
7807 content_origin
7808 + point(
7809 -scroll_pixel_position.x,
7810 row_target.as_f32() * line_height - scroll_pixel_position.y,
7811 )
7812 };
7813
7814 origin.x -= BORDER_WIDTH;
7815
7816 window.defer_draw(element, origin, 1);
7817
7818 // Do not return an element, since it will already be drawn due to defer_draw.
7819 None
7820 }
7821
7822 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
7823 px(30.)
7824 }
7825
7826 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
7827 if self.read_only(cx) {
7828 cx.theme().players().read_only()
7829 } else {
7830 self.style.as_ref().unwrap().local_player
7831 }
7832 }
7833
7834 fn render_edit_prediction_accept_keybind(
7835 &self,
7836 window: &mut Window,
7837 cx: &App,
7838 ) -> Option<AnyElement> {
7839 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
7840 let accept_keystroke = accept_binding.keystroke()?;
7841
7842 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7843
7844 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
7845 Color::Accent
7846 } else {
7847 Color::Muted
7848 };
7849
7850 h_flex()
7851 .px_0p5()
7852 .when(is_platform_style_mac, |parent| parent.gap_0p5())
7853 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7854 .text_size(TextSize::XSmall.rems(cx))
7855 .child(h_flex().children(ui::render_modifiers(
7856 &accept_keystroke.modifiers,
7857 PlatformStyle::platform(),
7858 Some(modifiers_color),
7859 Some(IconSize::XSmall.rems().into()),
7860 true,
7861 )))
7862 .when(is_platform_style_mac, |parent| {
7863 parent.child(accept_keystroke.key.clone())
7864 })
7865 .when(!is_platform_style_mac, |parent| {
7866 parent.child(
7867 Key::new(
7868 util::capitalize(&accept_keystroke.key),
7869 Some(Color::Default),
7870 )
7871 .size(Some(IconSize::XSmall.rems().into())),
7872 )
7873 })
7874 .into_any()
7875 .into()
7876 }
7877
7878 fn render_edit_prediction_line_popover(
7879 &self,
7880 label: impl Into<SharedString>,
7881 icon: Option<IconName>,
7882 window: &mut Window,
7883 cx: &App,
7884 ) -> Option<Stateful<Div>> {
7885 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
7886
7887 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7888 let has_keybind = keybind.is_some();
7889
7890 let result = h_flex()
7891 .id("ep-line-popover")
7892 .py_0p5()
7893 .pl_1()
7894 .pr(padding_right)
7895 .gap_1()
7896 .rounded_md()
7897 .border_1()
7898 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7899 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7900 .shadow_sm()
7901 .when(!has_keybind, |el| {
7902 let status_colors = cx.theme().status();
7903
7904 el.bg(status_colors.error_background)
7905 .border_color(status_colors.error.opacity(0.6))
7906 .pl_2()
7907 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7908 .cursor_default()
7909 .hoverable_tooltip(move |_window, cx| {
7910 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7911 })
7912 })
7913 .children(keybind)
7914 .child(
7915 Label::new(label)
7916 .size(LabelSize::Small)
7917 .when(!has_keybind, |el| {
7918 el.color(cx.theme().status().error.into()).strikethrough()
7919 }),
7920 )
7921 .when(!has_keybind, |el| {
7922 el.child(
7923 h_flex().ml_1().child(
7924 Icon::new(IconName::Info)
7925 .size(IconSize::Small)
7926 .color(cx.theme().status().error.into()),
7927 ),
7928 )
7929 })
7930 .when_some(icon, |element, icon| {
7931 element.child(
7932 div()
7933 .mt(px(1.5))
7934 .child(Icon::new(icon).size(IconSize::Small)),
7935 )
7936 });
7937
7938 Some(result)
7939 }
7940
7941 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
7942 let accent_color = cx.theme().colors().text_accent;
7943 let editor_bg_color = cx.theme().colors().editor_background;
7944 editor_bg_color.blend(accent_color.opacity(0.1))
7945 }
7946
7947 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
7948 let accent_color = cx.theme().colors().text_accent;
7949 let editor_bg_color = cx.theme().colors().editor_background;
7950 editor_bg_color.blend(accent_color.opacity(0.6))
7951 }
7952
7953 fn render_edit_prediction_cursor_popover(
7954 &self,
7955 min_width: Pixels,
7956 max_width: Pixels,
7957 cursor_point: Point,
7958 style: &EditorStyle,
7959 accept_keystroke: Option<&gpui::Keystroke>,
7960 _window: &Window,
7961 cx: &mut Context<Editor>,
7962 ) -> Option<AnyElement> {
7963 let provider = self.edit_prediction_provider.as_ref()?;
7964
7965 if provider.provider.needs_terms_acceptance(cx) {
7966 return Some(
7967 h_flex()
7968 .min_w(min_width)
7969 .flex_1()
7970 .px_2()
7971 .py_1()
7972 .gap_3()
7973 .elevation_2(cx)
7974 .hover(|style| style.bg(cx.theme().colors().element_hover))
7975 .id("accept-terms")
7976 .cursor_pointer()
7977 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
7978 .on_click(cx.listener(|this, _event, window, cx| {
7979 cx.stop_propagation();
7980 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
7981 window.dispatch_action(
7982 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
7983 cx,
7984 );
7985 }))
7986 .child(
7987 h_flex()
7988 .flex_1()
7989 .gap_2()
7990 .child(Icon::new(IconName::ZedPredict))
7991 .child(Label::new("Accept Terms of Service"))
7992 .child(div().w_full())
7993 .child(
7994 Icon::new(IconName::ArrowUpRight)
7995 .color(Color::Muted)
7996 .size(IconSize::Small),
7997 )
7998 .into_any_element(),
7999 )
8000 .into_any(),
8001 );
8002 }
8003
8004 let is_refreshing = provider.provider.is_refreshing(cx);
8005
8006 fn pending_completion_container() -> Div {
8007 h_flex()
8008 .h_full()
8009 .flex_1()
8010 .gap_2()
8011 .child(Icon::new(IconName::ZedPredict))
8012 }
8013
8014 let completion = match &self.active_inline_completion {
8015 Some(prediction) => {
8016 if !self.has_visible_completions_menu() {
8017 const RADIUS: Pixels = px(6.);
8018 const BORDER_WIDTH: Pixels = px(1.);
8019
8020 return Some(
8021 h_flex()
8022 .elevation_2(cx)
8023 .border(BORDER_WIDTH)
8024 .border_color(cx.theme().colors().border)
8025 .when(accept_keystroke.is_none(), |el| {
8026 el.border_color(cx.theme().status().error)
8027 })
8028 .rounded(RADIUS)
8029 .rounded_tl(px(0.))
8030 .overflow_hidden()
8031 .child(div().px_1p5().child(match &prediction.completion {
8032 InlineCompletion::Move { target, snapshot } => {
8033 use text::ToPoint as _;
8034 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8035 {
8036 Icon::new(IconName::ZedPredictDown)
8037 } else {
8038 Icon::new(IconName::ZedPredictUp)
8039 }
8040 }
8041 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8042 }))
8043 .child(
8044 h_flex()
8045 .gap_1()
8046 .py_1()
8047 .px_2()
8048 .rounded_r(RADIUS - BORDER_WIDTH)
8049 .border_l_1()
8050 .border_color(cx.theme().colors().border)
8051 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8052 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8053 el.child(
8054 Label::new("Hold")
8055 .size(LabelSize::Small)
8056 .when(accept_keystroke.is_none(), |el| {
8057 el.strikethrough()
8058 })
8059 .line_height_style(LineHeightStyle::UiLabel),
8060 )
8061 })
8062 .id("edit_prediction_cursor_popover_keybind")
8063 .when(accept_keystroke.is_none(), |el| {
8064 let status_colors = cx.theme().status();
8065
8066 el.bg(status_colors.error_background)
8067 .border_color(status_colors.error.opacity(0.6))
8068 .child(Icon::new(IconName::Info).color(Color::Error))
8069 .cursor_default()
8070 .hoverable_tooltip(move |_window, cx| {
8071 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8072 .into()
8073 })
8074 })
8075 .when_some(
8076 accept_keystroke.as_ref(),
8077 |el, accept_keystroke| {
8078 el.child(h_flex().children(ui::render_modifiers(
8079 &accept_keystroke.modifiers,
8080 PlatformStyle::platform(),
8081 Some(Color::Default),
8082 Some(IconSize::XSmall.rems().into()),
8083 false,
8084 )))
8085 },
8086 ),
8087 )
8088 .into_any(),
8089 );
8090 }
8091
8092 self.render_edit_prediction_cursor_popover_preview(
8093 prediction,
8094 cursor_point,
8095 style,
8096 cx,
8097 )?
8098 }
8099
8100 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8101 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8102 stale_completion,
8103 cursor_point,
8104 style,
8105 cx,
8106 )?,
8107
8108 None => {
8109 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8110 }
8111 },
8112
8113 None => pending_completion_container().child(Label::new("No Prediction")),
8114 };
8115
8116 let completion = if is_refreshing {
8117 completion
8118 .with_animation(
8119 "loading-completion",
8120 Animation::new(Duration::from_secs(2))
8121 .repeat()
8122 .with_easing(pulsating_between(0.4, 0.8)),
8123 |label, delta| label.opacity(delta),
8124 )
8125 .into_any_element()
8126 } else {
8127 completion.into_any_element()
8128 };
8129
8130 let has_completion = self.active_inline_completion.is_some();
8131
8132 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8133 Some(
8134 h_flex()
8135 .min_w(min_width)
8136 .max_w(max_width)
8137 .flex_1()
8138 .elevation_2(cx)
8139 .border_color(cx.theme().colors().border)
8140 .child(
8141 div()
8142 .flex_1()
8143 .py_1()
8144 .px_2()
8145 .overflow_hidden()
8146 .child(completion),
8147 )
8148 .when_some(accept_keystroke, |el, accept_keystroke| {
8149 if !accept_keystroke.modifiers.modified() {
8150 return el;
8151 }
8152
8153 el.child(
8154 h_flex()
8155 .h_full()
8156 .border_l_1()
8157 .rounded_r_lg()
8158 .border_color(cx.theme().colors().border)
8159 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8160 .gap_1()
8161 .py_1()
8162 .px_2()
8163 .child(
8164 h_flex()
8165 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8166 .when(is_platform_style_mac, |parent| parent.gap_1())
8167 .child(h_flex().children(ui::render_modifiers(
8168 &accept_keystroke.modifiers,
8169 PlatformStyle::platform(),
8170 Some(if !has_completion {
8171 Color::Muted
8172 } else {
8173 Color::Default
8174 }),
8175 None,
8176 false,
8177 ))),
8178 )
8179 .child(Label::new("Preview").into_any_element())
8180 .opacity(if has_completion { 1.0 } else { 0.4 }),
8181 )
8182 })
8183 .into_any(),
8184 )
8185 }
8186
8187 fn render_edit_prediction_cursor_popover_preview(
8188 &self,
8189 completion: &InlineCompletionState,
8190 cursor_point: Point,
8191 style: &EditorStyle,
8192 cx: &mut Context<Editor>,
8193 ) -> Option<Div> {
8194 use text::ToPoint as _;
8195
8196 fn render_relative_row_jump(
8197 prefix: impl Into<String>,
8198 current_row: u32,
8199 target_row: u32,
8200 ) -> Div {
8201 let (row_diff, arrow) = if target_row < current_row {
8202 (current_row - target_row, IconName::ArrowUp)
8203 } else {
8204 (target_row - current_row, IconName::ArrowDown)
8205 };
8206
8207 h_flex()
8208 .child(
8209 Label::new(format!("{}{}", prefix.into(), row_diff))
8210 .color(Color::Muted)
8211 .size(LabelSize::Small),
8212 )
8213 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8214 }
8215
8216 match &completion.completion {
8217 InlineCompletion::Move {
8218 target, snapshot, ..
8219 } => Some(
8220 h_flex()
8221 .px_2()
8222 .gap_2()
8223 .flex_1()
8224 .child(
8225 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8226 Icon::new(IconName::ZedPredictDown)
8227 } else {
8228 Icon::new(IconName::ZedPredictUp)
8229 },
8230 )
8231 .child(Label::new("Jump to Edit")),
8232 ),
8233
8234 InlineCompletion::Edit {
8235 edits,
8236 edit_preview,
8237 snapshot,
8238 display_mode: _,
8239 } => {
8240 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8241
8242 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8243 &snapshot,
8244 &edits,
8245 edit_preview.as_ref()?,
8246 true,
8247 cx,
8248 )
8249 .first_line_preview();
8250
8251 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8252 .with_default_highlights(&style.text, highlighted_edits.highlights);
8253
8254 let preview = h_flex()
8255 .gap_1()
8256 .min_w_16()
8257 .child(styled_text)
8258 .when(has_more_lines, |parent| parent.child("…"));
8259
8260 let left = if first_edit_row != cursor_point.row {
8261 render_relative_row_jump("", cursor_point.row, first_edit_row)
8262 .into_any_element()
8263 } else {
8264 Icon::new(IconName::ZedPredict).into_any_element()
8265 };
8266
8267 Some(
8268 h_flex()
8269 .h_full()
8270 .flex_1()
8271 .gap_2()
8272 .pr_1()
8273 .overflow_x_hidden()
8274 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8275 .child(left)
8276 .child(preview),
8277 )
8278 }
8279 }
8280 }
8281
8282 fn render_context_menu(
8283 &self,
8284 style: &EditorStyle,
8285 max_height_in_lines: u32,
8286 window: &mut Window,
8287 cx: &mut Context<Editor>,
8288 ) -> Option<AnyElement> {
8289 let menu = self.context_menu.borrow();
8290 let menu = menu.as_ref()?;
8291 if !menu.visible() {
8292 return None;
8293 };
8294 Some(menu.render(style, max_height_in_lines, window, cx))
8295 }
8296
8297 fn render_context_menu_aside(
8298 &mut self,
8299 max_size: Size<Pixels>,
8300 window: &mut Window,
8301 cx: &mut Context<Editor>,
8302 ) -> Option<AnyElement> {
8303 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8304 if menu.visible() {
8305 menu.render_aside(self, max_size, window, cx)
8306 } else {
8307 None
8308 }
8309 })
8310 }
8311
8312 fn hide_context_menu(
8313 &mut self,
8314 window: &mut Window,
8315 cx: &mut Context<Self>,
8316 ) -> Option<CodeContextMenu> {
8317 cx.notify();
8318 self.completion_tasks.clear();
8319 let context_menu = self.context_menu.borrow_mut().take();
8320 self.stale_inline_completion_in_menu.take();
8321 self.update_visible_inline_completion(window, cx);
8322 context_menu
8323 }
8324
8325 fn show_snippet_choices(
8326 &mut self,
8327 choices: &Vec<String>,
8328 selection: Range<Anchor>,
8329 cx: &mut Context<Self>,
8330 ) {
8331 if selection.start.buffer_id.is_none() {
8332 return;
8333 }
8334 let buffer_id = selection.start.buffer_id.unwrap();
8335 let buffer = self.buffer().read(cx).buffer(buffer_id);
8336 let id = post_inc(&mut self.next_completion_id);
8337 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8338
8339 if let Some(buffer) = buffer {
8340 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8341 CompletionsMenu::new_snippet_choices(
8342 id,
8343 true,
8344 choices,
8345 selection,
8346 buffer,
8347 snippet_sort_order,
8348 ),
8349 ));
8350 }
8351 }
8352
8353 pub fn insert_snippet(
8354 &mut self,
8355 insertion_ranges: &[Range<usize>],
8356 snippet: Snippet,
8357 window: &mut Window,
8358 cx: &mut Context<Self>,
8359 ) -> Result<()> {
8360 struct Tabstop<T> {
8361 is_end_tabstop: bool,
8362 ranges: Vec<Range<T>>,
8363 choices: Option<Vec<String>>,
8364 }
8365
8366 let tabstops = self.buffer.update(cx, |buffer, cx| {
8367 let snippet_text: Arc<str> = snippet.text.clone().into();
8368 let edits = insertion_ranges
8369 .iter()
8370 .cloned()
8371 .map(|range| (range, snippet_text.clone()));
8372 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8373
8374 let snapshot = &*buffer.read(cx);
8375 let snippet = &snippet;
8376 snippet
8377 .tabstops
8378 .iter()
8379 .map(|tabstop| {
8380 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8381 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8382 });
8383 let mut tabstop_ranges = tabstop
8384 .ranges
8385 .iter()
8386 .flat_map(|tabstop_range| {
8387 let mut delta = 0_isize;
8388 insertion_ranges.iter().map(move |insertion_range| {
8389 let insertion_start = insertion_range.start as isize + delta;
8390 delta +=
8391 snippet.text.len() as isize - insertion_range.len() as isize;
8392
8393 let start = ((insertion_start + tabstop_range.start) as usize)
8394 .min(snapshot.len());
8395 let end = ((insertion_start + tabstop_range.end) as usize)
8396 .min(snapshot.len());
8397 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8398 })
8399 })
8400 .collect::<Vec<_>>();
8401 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8402
8403 Tabstop {
8404 is_end_tabstop,
8405 ranges: tabstop_ranges,
8406 choices: tabstop.choices.clone(),
8407 }
8408 })
8409 .collect::<Vec<_>>()
8410 });
8411 if let Some(tabstop) = tabstops.first() {
8412 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8413 s.select_ranges(tabstop.ranges.iter().cloned());
8414 });
8415
8416 if let Some(choices) = &tabstop.choices {
8417 if let Some(selection) = tabstop.ranges.first() {
8418 self.show_snippet_choices(choices, selection.clone(), cx)
8419 }
8420 }
8421
8422 // If we're already at the last tabstop and it's at the end of the snippet,
8423 // we're done, we don't need to keep the state around.
8424 if !tabstop.is_end_tabstop {
8425 let choices = tabstops
8426 .iter()
8427 .map(|tabstop| tabstop.choices.clone())
8428 .collect();
8429
8430 let ranges = tabstops
8431 .into_iter()
8432 .map(|tabstop| tabstop.ranges)
8433 .collect::<Vec<_>>();
8434
8435 self.snippet_stack.push(SnippetState {
8436 active_index: 0,
8437 ranges,
8438 choices,
8439 });
8440 }
8441
8442 // Check whether the just-entered snippet ends with an auto-closable bracket.
8443 if self.autoclose_regions.is_empty() {
8444 let snapshot = self.buffer.read(cx).snapshot(cx);
8445 for selection in &mut self.selections.all::<Point>(cx) {
8446 let selection_head = selection.head();
8447 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8448 continue;
8449 };
8450
8451 let mut bracket_pair = None;
8452 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8453 let prev_chars = snapshot
8454 .reversed_chars_at(selection_head)
8455 .collect::<String>();
8456 for (pair, enabled) in scope.brackets() {
8457 if enabled
8458 && pair.close
8459 && prev_chars.starts_with(pair.start.as_str())
8460 && next_chars.starts_with(pair.end.as_str())
8461 {
8462 bracket_pair = Some(pair.clone());
8463 break;
8464 }
8465 }
8466 if let Some(pair) = bracket_pair {
8467 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8468 let autoclose_enabled =
8469 self.use_autoclose && snapshot_settings.use_autoclose;
8470 if autoclose_enabled {
8471 let start = snapshot.anchor_after(selection_head);
8472 let end = snapshot.anchor_after(selection_head);
8473 self.autoclose_regions.push(AutocloseRegion {
8474 selection_id: selection.id,
8475 range: start..end,
8476 pair,
8477 });
8478 }
8479 }
8480 }
8481 }
8482 }
8483 Ok(())
8484 }
8485
8486 pub fn move_to_next_snippet_tabstop(
8487 &mut self,
8488 window: &mut Window,
8489 cx: &mut Context<Self>,
8490 ) -> bool {
8491 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8492 }
8493
8494 pub fn move_to_prev_snippet_tabstop(
8495 &mut self,
8496 window: &mut Window,
8497 cx: &mut Context<Self>,
8498 ) -> bool {
8499 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8500 }
8501
8502 pub fn move_to_snippet_tabstop(
8503 &mut self,
8504 bias: Bias,
8505 window: &mut Window,
8506 cx: &mut Context<Self>,
8507 ) -> bool {
8508 if let Some(mut snippet) = self.snippet_stack.pop() {
8509 match bias {
8510 Bias::Left => {
8511 if snippet.active_index > 0 {
8512 snippet.active_index -= 1;
8513 } else {
8514 self.snippet_stack.push(snippet);
8515 return false;
8516 }
8517 }
8518 Bias::Right => {
8519 if snippet.active_index + 1 < snippet.ranges.len() {
8520 snippet.active_index += 1;
8521 } else {
8522 self.snippet_stack.push(snippet);
8523 return false;
8524 }
8525 }
8526 }
8527 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8528 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8529 s.select_anchor_ranges(current_ranges.iter().cloned())
8530 });
8531
8532 if let Some(choices) = &snippet.choices[snippet.active_index] {
8533 if let Some(selection) = current_ranges.first() {
8534 self.show_snippet_choices(&choices, selection.clone(), cx);
8535 }
8536 }
8537
8538 // If snippet state is not at the last tabstop, push it back on the stack
8539 if snippet.active_index + 1 < snippet.ranges.len() {
8540 self.snippet_stack.push(snippet);
8541 }
8542 return true;
8543 }
8544 }
8545
8546 false
8547 }
8548
8549 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8550 self.transact(window, cx, |this, window, cx| {
8551 this.select_all(&SelectAll, window, cx);
8552 this.insert("", window, cx);
8553 });
8554 }
8555
8556 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8557 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8558 self.transact(window, cx, |this, window, cx| {
8559 this.select_autoclose_pair(window, cx);
8560 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8561 if !this.linked_edit_ranges.is_empty() {
8562 let selections = this.selections.all::<MultiBufferPoint>(cx);
8563 let snapshot = this.buffer.read(cx).snapshot(cx);
8564
8565 for selection in selections.iter() {
8566 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8567 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8568 if selection_start.buffer_id != selection_end.buffer_id {
8569 continue;
8570 }
8571 if let Some(ranges) =
8572 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8573 {
8574 for (buffer, entries) in ranges {
8575 linked_ranges.entry(buffer).or_default().extend(entries);
8576 }
8577 }
8578 }
8579 }
8580
8581 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8582 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8583 for selection in &mut selections {
8584 if selection.is_empty() {
8585 let old_head = selection.head();
8586 let mut new_head =
8587 movement::left(&display_map, old_head.to_display_point(&display_map))
8588 .to_point(&display_map);
8589 if let Some((buffer, line_buffer_range)) = display_map
8590 .buffer_snapshot
8591 .buffer_line_for_row(MultiBufferRow(old_head.row))
8592 {
8593 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8594 let indent_len = match indent_size.kind {
8595 IndentKind::Space => {
8596 buffer.settings_at(line_buffer_range.start, cx).tab_size
8597 }
8598 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8599 };
8600 if old_head.column <= indent_size.len && old_head.column > 0 {
8601 let indent_len = indent_len.get();
8602 new_head = cmp::min(
8603 new_head,
8604 MultiBufferPoint::new(
8605 old_head.row,
8606 ((old_head.column - 1) / indent_len) * indent_len,
8607 ),
8608 );
8609 }
8610 }
8611
8612 selection.set_head(new_head, SelectionGoal::None);
8613 }
8614 }
8615
8616 this.signature_help_state.set_backspace_pressed(true);
8617 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8618 s.select(selections)
8619 });
8620 this.insert("", window, cx);
8621 let empty_str: Arc<str> = Arc::from("");
8622 for (buffer, edits) in linked_ranges {
8623 let snapshot = buffer.read(cx).snapshot();
8624 use text::ToPoint as TP;
8625
8626 let edits = edits
8627 .into_iter()
8628 .map(|range| {
8629 let end_point = TP::to_point(&range.end, &snapshot);
8630 let mut start_point = TP::to_point(&range.start, &snapshot);
8631
8632 if end_point == start_point {
8633 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8634 .saturating_sub(1);
8635 start_point =
8636 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8637 };
8638
8639 (start_point..end_point, empty_str.clone())
8640 })
8641 .sorted_by_key(|(range, _)| range.start)
8642 .collect::<Vec<_>>();
8643 buffer.update(cx, |this, cx| {
8644 this.edit(edits, None, cx);
8645 })
8646 }
8647 this.refresh_inline_completion(true, false, window, cx);
8648 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8649 });
8650 }
8651
8652 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8653 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8654 self.transact(window, cx, |this, window, cx| {
8655 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8656 s.move_with(|map, selection| {
8657 if selection.is_empty() {
8658 let cursor = movement::right(map, selection.head());
8659 selection.end = cursor;
8660 selection.reversed = true;
8661 selection.goal = SelectionGoal::None;
8662 }
8663 })
8664 });
8665 this.insert("", window, cx);
8666 this.refresh_inline_completion(true, false, window, cx);
8667 });
8668 }
8669
8670 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8671 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8672 if self.move_to_prev_snippet_tabstop(window, cx) {
8673 return;
8674 }
8675 self.outdent(&Outdent, window, cx);
8676 }
8677
8678 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8679 if self.move_to_next_snippet_tabstop(window, cx) {
8680 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8681 return;
8682 }
8683 if self.read_only(cx) {
8684 return;
8685 }
8686 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8687 let mut selections = self.selections.all_adjusted(cx);
8688 let buffer = self.buffer.read(cx);
8689 let snapshot = buffer.snapshot(cx);
8690 let rows_iter = selections.iter().map(|s| s.head().row);
8691 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8692
8693 let has_some_cursor_in_whitespace = selections
8694 .iter()
8695 .filter(|selection| selection.is_empty())
8696 .any(|selection| {
8697 let cursor = selection.head();
8698 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8699 cursor.column < current_indent.len
8700 });
8701
8702 let mut edits = Vec::new();
8703 let mut prev_edited_row = 0;
8704 let mut row_delta = 0;
8705 for selection in &mut selections {
8706 if selection.start.row != prev_edited_row {
8707 row_delta = 0;
8708 }
8709 prev_edited_row = selection.end.row;
8710
8711 // If the selection is non-empty, then increase the indentation of the selected lines.
8712 if !selection.is_empty() {
8713 row_delta =
8714 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8715 continue;
8716 }
8717
8718 // If the selection is empty and the cursor is in the leading whitespace before the
8719 // suggested indentation, then auto-indent the line.
8720 let cursor = selection.head();
8721 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8722 if let Some(suggested_indent) =
8723 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8724 {
8725 // If there exist any empty selection in the leading whitespace, then skip
8726 // indent for selections at the boundary.
8727 if has_some_cursor_in_whitespace
8728 && cursor.column == current_indent.len
8729 && current_indent.len == suggested_indent.len
8730 {
8731 continue;
8732 }
8733
8734 if cursor.column < suggested_indent.len
8735 && cursor.column <= current_indent.len
8736 && current_indent.len <= suggested_indent.len
8737 {
8738 selection.start = Point::new(cursor.row, suggested_indent.len);
8739 selection.end = selection.start;
8740 if row_delta == 0 {
8741 edits.extend(Buffer::edit_for_indent_size_adjustment(
8742 cursor.row,
8743 current_indent,
8744 suggested_indent,
8745 ));
8746 row_delta = suggested_indent.len - current_indent.len;
8747 }
8748 continue;
8749 }
8750 }
8751
8752 // Otherwise, insert a hard or soft tab.
8753 let settings = buffer.language_settings_at(cursor, cx);
8754 let tab_size = if settings.hard_tabs {
8755 IndentSize::tab()
8756 } else {
8757 let tab_size = settings.tab_size.get();
8758 let indent_remainder = snapshot
8759 .text_for_range(Point::new(cursor.row, 0)..cursor)
8760 .flat_map(str::chars)
8761 .fold(row_delta % tab_size, |counter: u32, c| {
8762 if c == '\t' {
8763 0
8764 } else {
8765 (counter + 1) % tab_size
8766 }
8767 });
8768
8769 let chars_to_next_tab_stop = tab_size - indent_remainder;
8770 IndentSize::spaces(chars_to_next_tab_stop)
8771 };
8772 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
8773 selection.end = selection.start;
8774 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
8775 row_delta += tab_size.len;
8776 }
8777
8778 self.transact(window, cx, |this, window, cx| {
8779 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8780 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8781 s.select(selections)
8782 });
8783 this.refresh_inline_completion(true, false, window, cx);
8784 });
8785 }
8786
8787 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
8788 if self.read_only(cx) {
8789 return;
8790 }
8791 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8792 let mut selections = self.selections.all::<Point>(cx);
8793 let mut prev_edited_row = 0;
8794 let mut row_delta = 0;
8795 let mut edits = Vec::new();
8796 let buffer = self.buffer.read(cx);
8797 let snapshot = buffer.snapshot(cx);
8798 for selection in &mut selections {
8799 if selection.start.row != prev_edited_row {
8800 row_delta = 0;
8801 }
8802 prev_edited_row = selection.end.row;
8803
8804 row_delta =
8805 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8806 }
8807
8808 self.transact(window, cx, |this, window, cx| {
8809 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8810 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8811 s.select(selections)
8812 });
8813 });
8814 }
8815
8816 fn indent_selection(
8817 buffer: &MultiBuffer,
8818 snapshot: &MultiBufferSnapshot,
8819 selection: &mut Selection<Point>,
8820 edits: &mut Vec<(Range<Point>, String)>,
8821 delta_for_start_row: u32,
8822 cx: &App,
8823 ) -> u32 {
8824 let settings = buffer.language_settings_at(selection.start, cx);
8825 let tab_size = settings.tab_size.get();
8826 let indent_kind = if settings.hard_tabs {
8827 IndentKind::Tab
8828 } else {
8829 IndentKind::Space
8830 };
8831 let mut start_row = selection.start.row;
8832 let mut end_row = selection.end.row + 1;
8833
8834 // If a selection ends at the beginning of a line, don't indent
8835 // that last line.
8836 if selection.end.column == 0 && selection.end.row > selection.start.row {
8837 end_row -= 1;
8838 }
8839
8840 // Avoid re-indenting a row that has already been indented by a
8841 // previous selection, but still update this selection's column
8842 // to reflect that indentation.
8843 if delta_for_start_row > 0 {
8844 start_row += 1;
8845 selection.start.column += delta_for_start_row;
8846 if selection.end.row == selection.start.row {
8847 selection.end.column += delta_for_start_row;
8848 }
8849 }
8850
8851 let mut delta_for_end_row = 0;
8852 let has_multiple_rows = start_row + 1 != end_row;
8853 for row in start_row..end_row {
8854 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
8855 let indent_delta = match (current_indent.kind, indent_kind) {
8856 (IndentKind::Space, IndentKind::Space) => {
8857 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
8858 IndentSize::spaces(columns_to_next_tab_stop)
8859 }
8860 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
8861 (_, IndentKind::Tab) => IndentSize::tab(),
8862 };
8863
8864 let start = if has_multiple_rows || current_indent.len < selection.start.column {
8865 0
8866 } else {
8867 selection.start.column
8868 };
8869 let row_start = Point::new(row, start);
8870 edits.push((
8871 row_start..row_start,
8872 indent_delta.chars().collect::<String>(),
8873 ));
8874
8875 // Update this selection's endpoints to reflect the indentation.
8876 if row == selection.start.row {
8877 selection.start.column += indent_delta.len;
8878 }
8879 if row == selection.end.row {
8880 selection.end.column += indent_delta.len;
8881 delta_for_end_row = indent_delta.len;
8882 }
8883 }
8884
8885 if selection.start.row == selection.end.row {
8886 delta_for_start_row + delta_for_end_row
8887 } else {
8888 delta_for_end_row
8889 }
8890 }
8891
8892 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
8893 if self.read_only(cx) {
8894 return;
8895 }
8896 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8897 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8898 let selections = self.selections.all::<Point>(cx);
8899 let mut deletion_ranges = Vec::new();
8900 let mut last_outdent = None;
8901 {
8902 let buffer = self.buffer.read(cx);
8903 let snapshot = buffer.snapshot(cx);
8904 for selection in &selections {
8905 let settings = buffer.language_settings_at(selection.start, cx);
8906 let tab_size = settings.tab_size.get();
8907 let mut rows = selection.spanned_rows(false, &display_map);
8908
8909 // Avoid re-outdenting a row that has already been outdented by a
8910 // previous selection.
8911 if let Some(last_row) = last_outdent {
8912 if last_row == rows.start {
8913 rows.start = rows.start.next_row();
8914 }
8915 }
8916 let has_multiple_rows = rows.len() > 1;
8917 for row in rows.iter_rows() {
8918 let indent_size = snapshot.indent_size_for_line(row);
8919 if indent_size.len > 0 {
8920 let deletion_len = match indent_size.kind {
8921 IndentKind::Space => {
8922 let columns_to_prev_tab_stop = indent_size.len % tab_size;
8923 if columns_to_prev_tab_stop == 0 {
8924 tab_size
8925 } else {
8926 columns_to_prev_tab_stop
8927 }
8928 }
8929 IndentKind::Tab => 1,
8930 };
8931 let start = if has_multiple_rows
8932 || deletion_len > selection.start.column
8933 || indent_size.len < selection.start.column
8934 {
8935 0
8936 } else {
8937 selection.start.column - deletion_len
8938 };
8939 deletion_ranges.push(
8940 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
8941 );
8942 last_outdent = Some(row);
8943 }
8944 }
8945 }
8946 }
8947
8948 self.transact(window, cx, |this, window, cx| {
8949 this.buffer.update(cx, |buffer, cx| {
8950 let empty_str: Arc<str> = Arc::default();
8951 buffer.edit(
8952 deletion_ranges
8953 .into_iter()
8954 .map(|range| (range, empty_str.clone())),
8955 None,
8956 cx,
8957 );
8958 });
8959 let selections = this.selections.all::<usize>(cx);
8960 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8961 s.select(selections)
8962 });
8963 });
8964 }
8965
8966 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
8967 if self.read_only(cx) {
8968 return;
8969 }
8970 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8971 let selections = self
8972 .selections
8973 .all::<usize>(cx)
8974 .into_iter()
8975 .map(|s| s.range());
8976
8977 self.transact(window, cx, |this, window, cx| {
8978 this.buffer.update(cx, |buffer, cx| {
8979 buffer.autoindent_ranges(selections, cx);
8980 });
8981 let selections = this.selections.all::<usize>(cx);
8982 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8983 s.select(selections)
8984 });
8985 });
8986 }
8987
8988 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
8989 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8990 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8991 let selections = self.selections.all::<Point>(cx);
8992
8993 let mut new_cursors = Vec::new();
8994 let mut edit_ranges = Vec::new();
8995 let mut selections = selections.iter().peekable();
8996 while let Some(selection) = selections.next() {
8997 let mut rows = selection.spanned_rows(false, &display_map);
8998 let goal_display_column = selection.head().to_display_point(&display_map).column();
8999
9000 // Accumulate contiguous regions of rows that we want to delete.
9001 while let Some(next_selection) = selections.peek() {
9002 let next_rows = next_selection.spanned_rows(false, &display_map);
9003 if next_rows.start <= rows.end {
9004 rows.end = next_rows.end;
9005 selections.next().unwrap();
9006 } else {
9007 break;
9008 }
9009 }
9010
9011 let buffer = &display_map.buffer_snapshot;
9012 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9013 let edit_end;
9014 let cursor_buffer_row;
9015 if buffer.max_point().row >= rows.end.0 {
9016 // If there's a line after the range, delete the \n from the end of the row range
9017 // and position the cursor on the next line.
9018 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9019 cursor_buffer_row = rows.end;
9020 } else {
9021 // If there isn't a line after the range, delete the \n from the line before the
9022 // start of the row range and position the cursor there.
9023 edit_start = edit_start.saturating_sub(1);
9024 edit_end = buffer.len();
9025 cursor_buffer_row = rows.start.previous_row();
9026 }
9027
9028 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9029 *cursor.column_mut() =
9030 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9031
9032 new_cursors.push((
9033 selection.id,
9034 buffer.anchor_after(cursor.to_point(&display_map)),
9035 ));
9036 edit_ranges.push(edit_start..edit_end);
9037 }
9038
9039 self.transact(window, cx, |this, window, cx| {
9040 let buffer = this.buffer.update(cx, |buffer, cx| {
9041 let empty_str: Arc<str> = Arc::default();
9042 buffer.edit(
9043 edit_ranges
9044 .into_iter()
9045 .map(|range| (range, empty_str.clone())),
9046 None,
9047 cx,
9048 );
9049 buffer.snapshot(cx)
9050 });
9051 let new_selections = new_cursors
9052 .into_iter()
9053 .map(|(id, cursor)| {
9054 let cursor = cursor.to_point(&buffer);
9055 Selection {
9056 id,
9057 start: cursor,
9058 end: cursor,
9059 reversed: false,
9060 goal: SelectionGoal::None,
9061 }
9062 })
9063 .collect();
9064
9065 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9066 s.select(new_selections);
9067 });
9068 });
9069 }
9070
9071 pub fn join_lines_impl(
9072 &mut self,
9073 insert_whitespace: bool,
9074 window: &mut Window,
9075 cx: &mut Context<Self>,
9076 ) {
9077 if self.read_only(cx) {
9078 return;
9079 }
9080 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9081 for selection in self.selections.all::<Point>(cx) {
9082 let start = MultiBufferRow(selection.start.row);
9083 // Treat single line selections as if they include the next line. Otherwise this action
9084 // would do nothing for single line selections individual cursors.
9085 let end = if selection.start.row == selection.end.row {
9086 MultiBufferRow(selection.start.row + 1)
9087 } else {
9088 MultiBufferRow(selection.end.row)
9089 };
9090
9091 if let Some(last_row_range) = row_ranges.last_mut() {
9092 if start <= last_row_range.end {
9093 last_row_range.end = end;
9094 continue;
9095 }
9096 }
9097 row_ranges.push(start..end);
9098 }
9099
9100 let snapshot = self.buffer.read(cx).snapshot(cx);
9101 let mut cursor_positions = Vec::new();
9102 for row_range in &row_ranges {
9103 let anchor = snapshot.anchor_before(Point::new(
9104 row_range.end.previous_row().0,
9105 snapshot.line_len(row_range.end.previous_row()),
9106 ));
9107 cursor_positions.push(anchor..anchor);
9108 }
9109
9110 self.transact(window, cx, |this, window, cx| {
9111 for row_range in row_ranges.into_iter().rev() {
9112 for row in row_range.iter_rows().rev() {
9113 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9114 let next_line_row = row.next_row();
9115 let indent = snapshot.indent_size_for_line(next_line_row);
9116 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9117
9118 let replace =
9119 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9120 " "
9121 } else {
9122 ""
9123 };
9124
9125 this.buffer.update(cx, |buffer, cx| {
9126 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9127 });
9128 }
9129 }
9130
9131 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9132 s.select_anchor_ranges(cursor_positions)
9133 });
9134 });
9135 }
9136
9137 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9138 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9139 self.join_lines_impl(true, window, cx);
9140 }
9141
9142 pub fn sort_lines_case_sensitive(
9143 &mut self,
9144 _: &SortLinesCaseSensitive,
9145 window: &mut Window,
9146 cx: &mut Context<Self>,
9147 ) {
9148 self.manipulate_lines(window, cx, |lines| lines.sort())
9149 }
9150
9151 pub fn sort_lines_case_insensitive(
9152 &mut self,
9153 _: &SortLinesCaseInsensitive,
9154 window: &mut Window,
9155 cx: &mut Context<Self>,
9156 ) {
9157 self.manipulate_lines(window, cx, |lines| {
9158 lines.sort_by_key(|line| line.to_lowercase())
9159 })
9160 }
9161
9162 pub fn unique_lines_case_insensitive(
9163 &mut self,
9164 _: &UniqueLinesCaseInsensitive,
9165 window: &mut Window,
9166 cx: &mut Context<Self>,
9167 ) {
9168 self.manipulate_lines(window, cx, |lines| {
9169 let mut seen = HashSet::default();
9170 lines.retain(|line| seen.insert(line.to_lowercase()));
9171 })
9172 }
9173
9174 pub fn unique_lines_case_sensitive(
9175 &mut self,
9176 _: &UniqueLinesCaseSensitive,
9177 window: &mut Window,
9178 cx: &mut Context<Self>,
9179 ) {
9180 self.manipulate_lines(window, cx, |lines| {
9181 let mut seen = HashSet::default();
9182 lines.retain(|line| seen.insert(*line));
9183 })
9184 }
9185
9186 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9187 let Some(project) = self.project.clone() else {
9188 return;
9189 };
9190 self.reload(project, window, cx)
9191 .detach_and_notify_err(window, cx);
9192 }
9193
9194 pub fn restore_file(
9195 &mut self,
9196 _: &::git::RestoreFile,
9197 window: &mut Window,
9198 cx: &mut Context<Self>,
9199 ) {
9200 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9201 let mut buffer_ids = HashSet::default();
9202 let snapshot = self.buffer().read(cx).snapshot(cx);
9203 for selection in self.selections.all::<usize>(cx) {
9204 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9205 }
9206
9207 let buffer = self.buffer().read(cx);
9208 let ranges = buffer_ids
9209 .into_iter()
9210 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9211 .collect::<Vec<_>>();
9212
9213 self.restore_hunks_in_ranges(ranges, window, cx);
9214 }
9215
9216 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9217 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9218 let selections = self
9219 .selections
9220 .all(cx)
9221 .into_iter()
9222 .map(|s| s.range())
9223 .collect();
9224 self.restore_hunks_in_ranges(selections, window, cx);
9225 }
9226
9227 pub fn restore_hunks_in_ranges(
9228 &mut self,
9229 ranges: Vec<Range<Point>>,
9230 window: &mut Window,
9231 cx: &mut Context<Editor>,
9232 ) {
9233 let mut revert_changes = HashMap::default();
9234 let chunk_by = self
9235 .snapshot(window, cx)
9236 .hunks_for_ranges(ranges)
9237 .into_iter()
9238 .chunk_by(|hunk| hunk.buffer_id);
9239 for (buffer_id, hunks) in &chunk_by {
9240 let hunks = hunks.collect::<Vec<_>>();
9241 for hunk in &hunks {
9242 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9243 }
9244 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9245 }
9246 drop(chunk_by);
9247 if !revert_changes.is_empty() {
9248 self.transact(window, cx, |editor, window, cx| {
9249 editor.restore(revert_changes, window, cx);
9250 });
9251 }
9252 }
9253
9254 pub fn open_active_item_in_terminal(
9255 &mut self,
9256 _: &OpenInTerminal,
9257 window: &mut Window,
9258 cx: &mut Context<Self>,
9259 ) {
9260 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9261 let project_path = buffer.read(cx).project_path(cx)?;
9262 let project = self.project.as_ref()?.read(cx);
9263 let entry = project.entry_for_path(&project_path, cx)?;
9264 let parent = match &entry.canonical_path {
9265 Some(canonical_path) => canonical_path.to_path_buf(),
9266 None => project.absolute_path(&project_path, cx)?,
9267 }
9268 .parent()?
9269 .to_path_buf();
9270 Some(parent)
9271 }) {
9272 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9273 }
9274 }
9275
9276 fn set_breakpoint_context_menu(
9277 &mut self,
9278 display_row: DisplayRow,
9279 position: Option<Anchor>,
9280 clicked_point: gpui::Point<Pixels>,
9281 window: &mut Window,
9282 cx: &mut Context<Self>,
9283 ) {
9284 if !cx.has_flag::<DebuggerFeatureFlag>() {
9285 return;
9286 }
9287 let source = self
9288 .buffer
9289 .read(cx)
9290 .snapshot(cx)
9291 .anchor_before(Point::new(display_row.0, 0u32));
9292
9293 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9294
9295 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9296 self,
9297 source,
9298 clicked_point,
9299 context_menu,
9300 window,
9301 cx,
9302 );
9303 }
9304
9305 fn add_edit_breakpoint_block(
9306 &mut self,
9307 anchor: Anchor,
9308 breakpoint: &Breakpoint,
9309 edit_action: BreakpointPromptEditAction,
9310 window: &mut Window,
9311 cx: &mut Context<Self>,
9312 ) {
9313 let weak_editor = cx.weak_entity();
9314 let bp_prompt = cx.new(|cx| {
9315 BreakpointPromptEditor::new(
9316 weak_editor,
9317 anchor,
9318 breakpoint.clone(),
9319 edit_action,
9320 window,
9321 cx,
9322 )
9323 });
9324
9325 let height = bp_prompt.update(cx, |this, cx| {
9326 this.prompt
9327 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9328 });
9329 let cloned_prompt = bp_prompt.clone();
9330 let blocks = vec![BlockProperties {
9331 style: BlockStyle::Sticky,
9332 placement: BlockPlacement::Above(anchor),
9333 height: Some(height),
9334 render: Arc::new(move |cx| {
9335 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9336 cloned_prompt.clone().into_any_element()
9337 }),
9338 priority: 0,
9339 render_in_minimap: true,
9340 }];
9341
9342 let focus_handle = bp_prompt.focus_handle(cx);
9343 window.focus(&focus_handle);
9344
9345 let block_ids = self.insert_blocks(blocks, None, cx);
9346 bp_prompt.update(cx, |prompt, _| {
9347 prompt.add_block_ids(block_ids);
9348 });
9349 }
9350
9351 pub(crate) fn breakpoint_at_row(
9352 &self,
9353 row: u32,
9354 window: &mut Window,
9355 cx: &mut Context<Self>,
9356 ) -> Option<(Anchor, Breakpoint)> {
9357 let snapshot = self.snapshot(window, cx);
9358 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9359
9360 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9361 }
9362
9363 pub(crate) fn breakpoint_at_anchor(
9364 &self,
9365 breakpoint_position: Anchor,
9366 snapshot: &EditorSnapshot,
9367 cx: &mut Context<Self>,
9368 ) -> Option<(Anchor, Breakpoint)> {
9369 let project = self.project.clone()?;
9370
9371 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9372 snapshot
9373 .buffer_snapshot
9374 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9375 })?;
9376
9377 let enclosing_excerpt = breakpoint_position.excerpt_id;
9378 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
9379 let buffer_snapshot = buffer.read(cx).snapshot();
9380
9381 let row = buffer_snapshot
9382 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9383 .row;
9384
9385 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9386 let anchor_end = snapshot
9387 .buffer_snapshot
9388 .anchor_after(Point::new(row, line_len));
9389
9390 let bp = self
9391 .breakpoint_store
9392 .as_ref()?
9393 .read_with(cx, |breakpoint_store, cx| {
9394 breakpoint_store
9395 .breakpoints(
9396 &buffer,
9397 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9398 &buffer_snapshot,
9399 cx,
9400 )
9401 .next()
9402 .and_then(|(anchor, bp)| {
9403 let breakpoint_row = buffer_snapshot
9404 .summary_for_anchor::<text::PointUtf16>(anchor)
9405 .row;
9406
9407 if breakpoint_row == row {
9408 snapshot
9409 .buffer_snapshot
9410 .anchor_in_excerpt(enclosing_excerpt, *anchor)
9411 .map(|anchor| (anchor, bp.clone()))
9412 } else {
9413 None
9414 }
9415 })
9416 });
9417 bp
9418 }
9419
9420 pub fn edit_log_breakpoint(
9421 &mut self,
9422 _: &EditLogBreakpoint,
9423 window: &mut Window,
9424 cx: &mut Context<Self>,
9425 ) {
9426 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9427 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9428 message: None,
9429 state: BreakpointState::Enabled,
9430 condition: None,
9431 hit_condition: None,
9432 });
9433
9434 self.add_edit_breakpoint_block(
9435 anchor,
9436 &breakpoint,
9437 BreakpointPromptEditAction::Log,
9438 window,
9439 cx,
9440 );
9441 }
9442 }
9443
9444 fn breakpoints_at_cursors(
9445 &self,
9446 window: &mut Window,
9447 cx: &mut Context<Self>,
9448 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9449 let snapshot = self.snapshot(window, cx);
9450 let cursors = self
9451 .selections
9452 .disjoint_anchors()
9453 .into_iter()
9454 .map(|selection| {
9455 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9456
9457 let breakpoint_position = self
9458 .breakpoint_at_row(cursor_position.row, window, cx)
9459 .map(|bp| bp.0)
9460 .unwrap_or_else(|| {
9461 snapshot
9462 .display_snapshot
9463 .buffer_snapshot
9464 .anchor_after(Point::new(cursor_position.row, 0))
9465 });
9466
9467 let breakpoint = self
9468 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9469 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9470
9471 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9472 })
9473 // 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.
9474 .collect::<HashMap<Anchor, _>>();
9475
9476 cursors.into_iter().collect()
9477 }
9478
9479 pub fn enable_breakpoint(
9480 &mut self,
9481 _: &crate::actions::EnableBreakpoint,
9482 window: &mut Window,
9483 cx: &mut Context<Self>,
9484 ) {
9485 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9486 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9487 continue;
9488 };
9489 self.edit_breakpoint_at_anchor(
9490 anchor,
9491 breakpoint,
9492 BreakpointEditAction::InvertState,
9493 cx,
9494 );
9495 }
9496 }
9497
9498 pub fn disable_breakpoint(
9499 &mut self,
9500 _: &crate::actions::DisableBreakpoint,
9501 window: &mut Window,
9502 cx: &mut Context<Self>,
9503 ) {
9504 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9505 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9506 continue;
9507 };
9508 self.edit_breakpoint_at_anchor(
9509 anchor,
9510 breakpoint,
9511 BreakpointEditAction::InvertState,
9512 cx,
9513 );
9514 }
9515 }
9516
9517 pub fn toggle_breakpoint(
9518 &mut self,
9519 _: &crate::actions::ToggleBreakpoint,
9520 window: &mut Window,
9521 cx: &mut Context<Self>,
9522 ) {
9523 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9524 if let Some(breakpoint) = breakpoint {
9525 self.edit_breakpoint_at_anchor(
9526 anchor,
9527 breakpoint,
9528 BreakpointEditAction::Toggle,
9529 cx,
9530 );
9531 } else {
9532 self.edit_breakpoint_at_anchor(
9533 anchor,
9534 Breakpoint::new_standard(),
9535 BreakpointEditAction::Toggle,
9536 cx,
9537 );
9538 }
9539 }
9540 }
9541
9542 pub fn edit_breakpoint_at_anchor(
9543 &mut self,
9544 breakpoint_position: Anchor,
9545 breakpoint: Breakpoint,
9546 edit_action: BreakpointEditAction,
9547 cx: &mut Context<Self>,
9548 ) {
9549 let Some(breakpoint_store) = &self.breakpoint_store else {
9550 return;
9551 };
9552
9553 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9554 if breakpoint_position == Anchor::min() {
9555 self.buffer()
9556 .read(cx)
9557 .excerpt_buffer_ids()
9558 .into_iter()
9559 .next()
9560 } else {
9561 None
9562 }
9563 }) else {
9564 return;
9565 };
9566
9567 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9568 return;
9569 };
9570
9571 breakpoint_store.update(cx, |breakpoint_store, cx| {
9572 breakpoint_store.toggle_breakpoint(
9573 buffer,
9574 (breakpoint_position.text_anchor, breakpoint),
9575 edit_action,
9576 cx,
9577 );
9578 });
9579
9580 cx.notify();
9581 }
9582
9583 #[cfg(any(test, feature = "test-support"))]
9584 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9585 self.breakpoint_store.clone()
9586 }
9587
9588 pub fn prepare_restore_change(
9589 &self,
9590 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9591 hunk: &MultiBufferDiffHunk,
9592 cx: &mut App,
9593 ) -> Option<()> {
9594 if hunk.is_created_file() {
9595 return None;
9596 }
9597 let buffer = self.buffer.read(cx);
9598 let diff = buffer.diff_for(hunk.buffer_id)?;
9599 let buffer = buffer.buffer(hunk.buffer_id)?;
9600 let buffer = buffer.read(cx);
9601 let original_text = diff
9602 .read(cx)
9603 .base_text()
9604 .as_rope()
9605 .slice(hunk.diff_base_byte_range.clone());
9606 let buffer_snapshot = buffer.snapshot();
9607 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9608 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9609 probe
9610 .0
9611 .start
9612 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9613 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9614 }) {
9615 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9616 Some(())
9617 } else {
9618 None
9619 }
9620 }
9621
9622 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9623 self.manipulate_lines(window, cx, |lines| lines.reverse())
9624 }
9625
9626 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9627 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9628 }
9629
9630 fn manipulate_lines<Fn>(
9631 &mut self,
9632 window: &mut Window,
9633 cx: &mut Context<Self>,
9634 mut callback: Fn,
9635 ) where
9636 Fn: FnMut(&mut Vec<&str>),
9637 {
9638 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9639
9640 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9641 let buffer = self.buffer.read(cx).snapshot(cx);
9642
9643 let mut edits = Vec::new();
9644
9645 let selections = self.selections.all::<Point>(cx);
9646 let mut selections = selections.iter().peekable();
9647 let mut contiguous_row_selections = Vec::new();
9648 let mut new_selections = Vec::new();
9649 let mut added_lines = 0;
9650 let mut removed_lines = 0;
9651
9652 while let Some(selection) = selections.next() {
9653 let (start_row, end_row) = consume_contiguous_rows(
9654 &mut contiguous_row_selections,
9655 selection,
9656 &display_map,
9657 &mut selections,
9658 );
9659
9660 let start_point = Point::new(start_row.0, 0);
9661 let end_point = Point::new(
9662 end_row.previous_row().0,
9663 buffer.line_len(end_row.previous_row()),
9664 );
9665 let text = buffer
9666 .text_for_range(start_point..end_point)
9667 .collect::<String>();
9668
9669 let mut lines = text.split('\n').collect_vec();
9670
9671 let lines_before = lines.len();
9672 callback(&mut lines);
9673 let lines_after = lines.len();
9674
9675 edits.push((start_point..end_point, lines.join("\n")));
9676
9677 // Selections must change based on added and removed line count
9678 let start_row =
9679 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9680 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9681 new_selections.push(Selection {
9682 id: selection.id,
9683 start: start_row,
9684 end: end_row,
9685 goal: SelectionGoal::None,
9686 reversed: selection.reversed,
9687 });
9688
9689 if lines_after > lines_before {
9690 added_lines += lines_after - lines_before;
9691 } else if lines_before > lines_after {
9692 removed_lines += lines_before - lines_after;
9693 }
9694 }
9695
9696 self.transact(window, cx, |this, window, cx| {
9697 let buffer = this.buffer.update(cx, |buffer, cx| {
9698 buffer.edit(edits, None, cx);
9699 buffer.snapshot(cx)
9700 });
9701
9702 // Recalculate offsets on newly edited buffer
9703 let new_selections = new_selections
9704 .iter()
9705 .map(|s| {
9706 let start_point = Point::new(s.start.0, 0);
9707 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9708 Selection {
9709 id: s.id,
9710 start: buffer.point_to_offset(start_point),
9711 end: buffer.point_to_offset(end_point),
9712 goal: s.goal,
9713 reversed: s.reversed,
9714 }
9715 })
9716 .collect();
9717
9718 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9719 s.select(new_selections);
9720 });
9721
9722 this.request_autoscroll(Autoscroll::fit(), cx);
9723 });
9724 }
9725
9726 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
9727 self.manipulate_text(window, cx, |text| {
9728 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
9729 if has_upper_case_characters {
9730 text.to_lowercase()
9731 } else {
9732 text.to_uppercase()
9733 }
9734 })
9735 }
9736
9737 pub fn convert_to_upper_case(
9738 &mut self,
9739 _: &ConvertToUpperCase,
9740 window: &mut Window,
9741 cx: &mut Context<Self>,
9742 ) {
9743 self.manipulate_text(window, cx, |text| text.to_uppercase())
9744 }
9745
9746 pub fn convert_to_lower_case(
9747 &mut self,
9748 _: &ConvertToLowerCase,
9749 window: &mut Window,
9750 cx: &mut Context<Self>,
9751 ) {
9752 self.manipulate_text(window, cx, |text| text.to_lowercase())
9753 }
9754
9755 pub fn convert_to_title_case(
9756 &mut self,
9757 _: &ConvertToTitleCase,
9758 window: &mut Window,
9759 cx: &mut Context<Self>,
9760 ) {
9761 self.manipulate_text(window, cx, |text| {
9762 text.split('\n')
9763 .map(|line| line.to_case(Case::Title))
9764 .join("\n")
9765 })
9766 }
9767
9768 pub fn convert_to_snake_case(
9769 &mut self,
9770 _: &ConvertToSnakeCase,
9771 window: &mut Window,
9772 cx: &mut Context<Self>,
9773 ) {
9774 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
9775 }
9776
9777 pub fn convert_to_kebab_case(
9778 &mut self,
9779 _: &ConvertToKebabCase,
9780 window: &mut Window,
9781 cx: &mut Context<Self>,
9782 ) {
9783 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
9784 }
9785
9786 pub fn convert_to_upper_camel_case(
9787 &mut self,
9788 _: &ConvertToUpperCamelCase,
9789 window: &mut Window,
9790 cx: &mut Context<Self>,
9791 ) {
9792 self.manipulate_text(window, cx, |text| {
9793 text.split('\n')
9794 .map(|line| line.to_case(Case::UpperCamel))
9795 .join("\n")
9796 })
9797 }
9798
9799 pub fn convert_to_lower_camel_case(
9800 &mut self,
9801 _: &ConvertToLowerCamelCase,
9802 window: &mut Window,
9803 cx: &mut Context<Self>,
9804 ) {
9805 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
9806 }
9807
9808 pub fn convert_to_opposite_case(
9809 &mut self,
9810 _: &ConvertToOppositeCase,
9811 window: &mut Window,
9812 cx: &mut Context<Self>,
9813 ) {
9814 self.manipulate_text(window, cx, |text| {
9815 text.chars()
9816 .fold(String::with_capacity(text.len()), |mut t, c| {
9817 if c.is_uppercase() {
9818 t.extend(c.to_lowercase());
9819 } else {
9820 t.extend(c.to_uppercase());
9821 }
9822 t
9823 })
9824 })
9825 }
9826
9827 pub fn convert_to_rot13(
9828 &mut self,
9829 _: &ConvertToRot13,
9830 window: &mut Window,
9831 cx: &mut Context<Self>,
9832 ) {
9833 self.manipulate_text(window, cx, |text| {
9834 text.chars()
9835 .map(|c| match c {
9836 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
9837 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
9838 _ => c,
9839 })
9840 .collect()
9841 })
9842 }
9843
9844 pub fn convert_to_rot47(
9845 &mut self,
9846 _: &ConvertToRot47,
9847 window: &mut Window,
9848 cx: &mut Context<Self>,
9849 ) {
9850 self.manipulate_text(window, cx, |text| {
9851 text.chars()
9852 .map(|c| {
9853 let code_point = c as u32;
9854 if code_point >= 33 && code_point <= 126 {
9855 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
9856 }
9857 c
9858 })
9859 .collect()
9860 })
9861 }
9862
9863 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
9864 where
9865 Fn: FnMut(&str) -> String,
9866 {
9867 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9868 let buffer = self.buffer.read(cx).snapshot(cx);
9869
9870 let mut new_selections = Vec::new();
9871 let mut edits = Vec::new();
9872 let mut selection_adjustment = 0i32;
9873
9874 for selection in self.selections.all::<usize>(cx) {
9875 let selection_is_empty = selection.is_empty();
9876
9877 let (start, end) = if selection_is_empty {
9878 let word_range = movement::surrounding_word(
9879 &display_map,
9880 selection.start.to_display_point(&display_map),
9881 );
9882 let start = word_range.start.to_offset(&display_map, Bias::Left);
9883 let end = word_range.end.to_offset(&display_map, Bias::Left);
9884 (start, end)
9885 } else {
9886 (selection.start, selection.end)
9887 };
9888
9889 let text = buffer.text_for_range(start..end).collect::<String>();
9890 let old_length = text.len() as i32;
9891 let text = callback(&text);
9892
9893 new_selections.push(Selection {
9894 start: (start as i32 - selection_adjustment) as usize,
9895 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
9896 goal: SelectionGoal::None,
9897 ..selection
9898 });
9899
9900 selection_adjustment += old_length - text.len() as i32;
9901
9902 edits.push((start..end, text));
9903 }
9904
9905 self.transact(window, cx, |this, window, cx| {
9906 this.buffer.update(cx, |buffer, cx| {
9907 buffer.edit(edits, None, cx);
9908 });
9909
9910 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9911 s.select(new_selections);
9912 });
9913
9914 this.request_autoscroll(Autoscroll::fit(), cx);
9915 });
9916 }
9917
9918 pub fn duplicate(
9919 &mut self,
9920 upwards: bool,
9921 whole_lines: bool,
9922 window: &mut Window,
9923 cx: &mut Context<Self>,
9924 ) {
9925 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9926
9927 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9928 let buffer = &display_map.buffer_snapshot;
9929 let selections = self.selections.all::<Point>(cx);
9930
9931 let mut edits = Vec::new();
9932 let mut selections_iter = selections.iter().peekable();
9933 while let Some(selection) = selections_iter.next() {
9934 let mut rows = selection.spanned_rows(false, &display_map);
9935 // duplicate line-wise
9936 if whole_lines || selection.start == selection.end {
9937 // Avoid duplicating the same lines twice.
9938 while let Some(next_selection) = selections_iter.peek() {
9939 let next_rows = next_selection.spanned_rows(false, &display_map);
9940 if next_rows.start < rows.end {
9941 rows.end = next_rows.end;
9942 selections_iter.next().unwrap();
9943 } else {
9944 break;
9945 }
9946 }
9947
9948 // Copy the text from the selected row region and splice it either at the start
9949 // or end of the region.
9950 let start = Point::new(rows.start.0, 0);
9951 let end = Point::new(
9952 rows.end.previous_row().0,
9953 buffer.line_len(rows.end.previous_row()),
9954 );
9955 let text = buffer
9956 .text_for_range(start..end)
9957 .chain(Some("\n"))
9958 .collect::<String>();
9959 let insert_location = if upwards {
9960 Point::new(rows.end.0, 0)
9961 } else {
9962 start
9963 };
9964 edits.push((insert_location..insert_location, text));
9965 } else {
9966 // duplicate character-wise
9967 let start = selection.start;
9968 let end = selection.end;
9969 let text = buffer.text_for_range(start..end).collect::<String>();
9970 edits.push((selection.end..selection.end, text));
9971 }
9972 }
9973
9974 self.transact(window, cx, |this, _, cx| {
9975 this.buffer.update(cx, |buffer, cx| {
9976 buffer.edit(edits, None, cx);
9977 });
9978
9979 this.request_autoscroll(Autoscroll::fit(), cx);
9980 });
9981 }
9982
9983 pub fn duplicate_line_up(
9984 &mut self,
9985 _: &DuplicateLineUp,
9986 window: &mut Window,
9987 cx: &mut Context<Self>,
9988 ) {
9989 self.duplicate(true, true, window, cx);
9990 }
9991
9992 pub fn duplicate_line_down(
9993 &mut self,
9994 _: &DuplicateLineDown,
9995 window: &mut Window,
9996 cx: &mut Context<Self>,
9997 ) {
9998 self.duplicate(false, true, window, cx);
9999 }
10000
10001 pub fn duplicate_selection(
10002 &mut self,
10003 _: &DuplicateSelection,
10004 window: &mut Window,
10005 cx: &mut Context<Self>,
10006 ) {
10007 self.duplicate(false, false, window, cx);
10008 }
10009
10010 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10011 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10012
10013 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10014 let buffer = self.buffer.read(cx).snapshot(cx);
10015
10016 let mut edits = Vec::new();
10017 let mut unfold_ranges = Vec::new();
10018 let mut refold_creases = Vec::new();
10019
10020 let selections = self.selections.all::<Point>(cx);
10021 let mut selections = selections.iter().peekable();
10022 let mut contiguous_row_selections = Vec::new();
10023 let mut new_selections = Vec::new();
10024
10025 while let Some(selection) = selections.next() {
10026 // Find all the selections that span a contiguous row range
10027 let (start_row, end_row) = consume_contiguous_rows(
10028 &mut contiguous_row_selections,
10029 selection,
10030 &display_map,
10031 &mut selections,
10032 );
10033
10034 // Move the text spanned by the row range to be before the line preceding the row range
10035 if start_row.0 > 0 {
10036 let range_to_move = Point::new(
10037 start_row.previous_row().0,
10038 buffer.line_len(start_row.previous_row()),
10039 )
10040 ..Point::new(
10041 end_row.previous_row().0,
10042 buffer.line_len(end_row.previous_row()),
10043 );
10044 let insertion_point = display_map
10045 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10046 .0;
10047
10048 // Don't move lines across excerpts
10049 if buffer
10050 .excerpt_containing(insertion_point..range_to_move.end)
10051 .is_some()
10052 {
10053 let text = buffer
10054 .text_for_range(range_to_move.clone())
10055 .flat_map(|s| s.chars())
10056 .skip(1)
10057 .chain(['\n'])
10058 .collect::<String>();
10059
10060 edits.push((
10061 buffer.anchor_after(range_to_move.start)
10062 ..buffer.anchor_before(range_to_move.end),
10063 String::new(),
10064 ));
10065 let insertion_anchor = buffer.anchor_after(insertion_point);
10066 edits.push((insertion_anchor..insertion_anchor, text));
10067
10068 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10069
10070 // Move selections up
10071 new_selections.extend(contiguous_row_selections.drain(..).map(
10072 |mut selection| {
10073 selection.start.row -= row_delta;
10074 selection.end.row -= row_delta;
10075 selection
10076 },
10077 ));
10078
10079 // Move folds up
10080 unfold_ranges.push(range_to_move.clone());
10081 for fold in display_map.folds_in_range(
10082 buffer.anchor_before(range_to_move.start)
10083 ..buffer.anchor_after(range_to_move.end),
10084 ) {
10085 let mut start = fold.range.start.to_point(&buffer);
10086 let mut end = fold.range.end.to_point(&buffer);
10087 start.row -= row_delta;
10088 end.row -= row_delta;
10089 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10090 }
10091 }
10092 }
10093
10094 // If we didn't move line(s), preserve the existing selections
10095 new_selections.append(&mut contiguous_row_selections);
10096 }
10097
10098 self.transact(window, cx, |this, window, cx| {
10099 this.unfold_ranges(&unfold_ranges, true, true, cx);
10100 this.buffer.update(cx, |buffer, cx| {
10101 for (range, text) in edits {
10102 buffer.edit([(range, text)], None, cx);
10103 }
10104 });
10105 this.fold_creases(refold_creases, true, window, cx);
10106 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10107 s.select(new_selections);
10108 })
10109 });
10110 }
10111
10112 pub fn move_line_down(
10113 &mut self,
10114 _: &MoveLineDown,
10115 window: &mut Window,
10116 cx: &mut Context<Self>,
10117 ) {
10118 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10119
10120 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10121 let buffer = self.buffer.read(cx).snapshot(cx);
10122
10123 let mut edits = Vec::new();
10124 let mut unfold_ranges = Vec::new();
10125 let mut refold_creases = Vec::new();
10126
10127 let selections = self.selections.all::<Point>(cx);
10128 let mut selections = selections.iter().peekable();
10129 let mut contiguous_row_selections = Vec::new();
10130 let mut new_selections = Vec::new();
10131
10132 while let Some(selection) = selections.next() {
10133 // Find all the selections that span a contiguous row range
10134 let (start_row, end_row) = consume_contiguous_rows(
10135 &mut contiguous_row_selections,
10136 selection,
10137 &display_map,
10138 &mut selections,
10139 );
10140
10141 // Move the text spanned by the row range to be after the last line of the row range
10142 if end_row.0 <= buffer.max_point().row {
10143 let range_to_move =
10144 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10145 let insertion_point = display_map
10146 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10147 .0;
10148
10149 // Don't move lines across excerpt boundaries
10150 if buffer
10151 .excerpt_containing(range_to_move.start..insertion_point)
10152 .is_some()
10153 {
10154 let mut text = String::from("\n");
10155 text.extend(buffer.text_for_range(range_to_move.clone()));
10156 text.pop(); // Drop trailing newline
10157 edits.push((
10158 buffer.anchor_after(range_to_move.start)
10159 ..buffer.anchor_before(range_to_move.end),
10160 String::new(),
10161 ));
10162 let insertion_anchor = buffer.anchor_after(insertion_point);
10163 edits.push((insertion_anchor..insertion_anchor, text));
10164
10165 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10166
10167 // Move selections down
10168 new_selections.extend(contiguous_row_selections.drain(..).map(
10169 |mut selection| {
10170 selection.start.row += row_delta;
10171 selection.end.row += row_delta;
10172 selection
10173 },
10174 ));
10175
10176 // Move folds down
10177 unfold_ranges.push(range_to_move.clone());
10178 for fold in display_map.folds_in_range(
10179 buffer.anchor_before(range_to_move.start)
10180 ..buffer.anchor_after(range_to_move.end),
10181 ) {
10182 let mut start = fold.range.start.to_point(&buffer);
10183 let mut end = fold.range.end.to_point(&buffer);
10184 start.row += row_delta;
10185 end.row += row_delta;
10186 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10187 }
10188 }
10189 }
10190
10191 // If we didn't move line(s), preserve the existing selections
10192 new_selections.append(&mut contiguous_row_selections);
10193 }
10194
10195 self.transact(window, cx, |this, window, cx| {
10196 this.unfold_ranges(&unfold_ranges, true, true, cx);
10197 this.buffer.update(cx, |buffer, cx| {
10198 for (range, text) in edits {
10199 buffer.edit([(range, text)], None, cx);
10200 }
10201 });
10202 this.fold_creases(refold_creases, true, window, cx);
10203 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10204 s.select(new_selections)
10205 });
10206 });
10207 }
10208
10209 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10210 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10211 let text_layout_details = &self.text_layout_details(window);
10212 self.transact(window, cx, |this, window, cx| {
10213 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10214 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10215 s.move_with(|display_map, selection| {
10216 if !selection.is_empty() {
10217 return;
10218 }
10219
10220 let mut head = selection.head();
10221 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10222 if head.column() == display_map.line_len(head.row()) {
10223 transpose_offset = display_map
10224 .buffer_snapshot
10225 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10226 }
10227
10228 if transpose_offset == 0 {
10229 return;
10230 }
10231
10232 *head.column_mut() += 1;
10233 head = display_map.clip_point(head, Bias::Right);
10234 let goal = SelectionGoal::HorizontalPosition(
10235 display_map
10236 .x_for_display_point(head, text_layout_details)
10237 .into(),
10238 );
10239 selection.collapse_to(head, goal);
10240
10241 let transpose_start = display_map
10242 .buffer_snapshot
10243 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10244 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10245 let transpose_end = display_map
10246 .buffer_snapshot
10247 .clip_offset(transpose_offset + 1, Bias::Right);
10248 if let Some(ch) =
10249 display_map.buffer_snapshot.chars_at(transpose_start).next()
10250 {
10251 edits.push((transpose_start..transpose_offset, String::new()));
10252 edits.push((transpose_end..transpose_end, ch.to_string()));
10253 }
10254 }
10255 });
10256 edits
10257 });
10258 this.buffer
10259 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10260 let selections = this.selections.all::<usize>(cx);
10261 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10262 s.select(selections);
10263 });
10264 });
10265 }
10266
10267 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10268 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10269 self.rewrap_impl(RewrapOptions::default(), cx)
10270 }
10271
10272 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10273 let buffer = self.buffer.read(cx).snapshot(cx);
10274 let selections = self.selections.all::<Point>(cx);
10275 let mut selections = selections.iter().peekable();
10276
10277 let mut edits = Vec::new();
10278 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10279
10280 while let Some(selection) = selections.next() {
10281 let mut start_row = selection.start.row;
10282 let mut end_row = selection.end.row;
10283
10284 // Skip selections that overlap with a range that has already been rewrapped.
10285 let selection_range = start_row..end_row;
10286 if rewrapped_row_ranges
10287 .iter()
10288 .any(|range| range.overlaps(&selection_range))
10289 {
10290 continue;
10291 }
10292
10293 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10294
10295 // Since not all lines in the selection may be at the same indent
10296 // level, choose the indent size that is the most common between all
10297 // of the lines.
10298 //
10299 // If there is a tie, we use the deepest indent.
10300 let (indent_size, indent_end) = {
10301 let mut indent_size_occurrences = HashMap::default();
10302 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10303
10304 for row in start_row..=end_row {
10305 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10306 rows_by_indent_size.entry(indent).or_default().push(row);
10307 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10308 }
10309
10310 let indent_size = indent_size_occurrences
10311 .into_iter()
10312 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10313 .map(|(indent, _)| indent)
10314 .unwrap_or_default();
10315 let row = rows_by_indent_size[&indent_size][0];
10316 let indent_end = Point::new(row, indent_size.len);
10317
10318 (indent_size, indent_end)
10319 };
10320
10321 let mut line_prefix = indent_size.chars().collect::<String>();
10322
10323 let mut inside_comment = false;
10324 if let Some(comment_prefix) =
10325 buffer
10326 .language_scope_at(selection.head())
10327 .and_then(|language| {
10328 language
10329 .line_comment_prefixes()
10330 .iter()
10331 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10332 .cloned()
10333 })
10334 {
10335 line_prefix.push_str(&comment_prefix);
10336 inside_comment = true;
10337 }
10338
10339 let language_settings = buffer.language_settings_at(selection.head(), cx);
10340 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10341 RewrapBehavior::InComments => inside_comment,
10342 RewrapBehavior::InSelections => !selection.is_empty(),
10343 RewrapBehavior::Anywhere => true,
10344 };
10345
10346 let should_rewrap = options.override_language_settings
10347 || allow_rewrap_based_on_language
10348 || self.hard_wrap.is_some();
10349 if !should_rewrap {
10350 continue;
10351 }
10352
10353 if selection.is_empty() {
10354 'expand_upwards: while start_row > 0 {
10355 let prev_row = start_row - 1;
10356 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10357 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10358 {
10359 start_row = prev_row;
10360 } else {
10361 break 'expand_upwards;
10362 }
10363 }
10364
10365 'expand_downwards: while end_row < buffer.max_point().row {
10366 let next_row = end_row + 1;
10367 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10368 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10369 {
10370 end_row = next_row;
10371 } else {
10372 break 'expand_downwards;
10373 }
10374 }
10375 }
10376
10377 let start = Point::new(start_row, 0);
10378 let start_offset = start.to_offset(&buffer);
10379 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10380 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10381 let Some(lines_without_prefixes) = selection_text
10382 .lines()
10383 .map(|line| {
10384 line.strip_prefix(&line_prefix)
10385 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10386 .ok_or_else(|| {
10387 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
10388 })
10389 })
10390 .collect::<Result<Vec<_>, _>>()
10391 .log_err()
10392 else {
10393 continue;
10394 };
10395
10396 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10397 buffer
10398 .language_settings_at(Point::new(start_row, 0), cx)
10399 .preferred_line_length as usize
10400 });
10401 let wrapped_text = wrap_with_prefix(
10402 line_prefix,
10403 lines_without_prefixes.join("\n"),
10404 wrap_column,
10405 tab_size,
10406 options.preserve_existing_whitespace,
10407 );
10408
10409 // TODO: should always use char-based diff while still supporting cursor behavior that
10410 // matches vim.
10411 let mut diff_options = DiffOptions::default();
10412 if options.override_language_settings {
10413 diff_options.max_word_diff_len = 0;
10414 diff_options.max_word_diff_line_count = 0;
10415 } else {
10416 diff_options.max_word_diff_len = usize::MAX;
10417 diff_options.max_word_diff_line_count = usize::MAX;
10418 }
10419
10420 for (old_range, new_text) in
10421 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10422 {
10423 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10424 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10425 edits.push((edit_start..edit_end, new_text));
10426 }
10427
10428 rewrapped_row_ranges.push(start_row..=end_row);
10429 }
10430
10431 self.buffer
10432 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10433 }
10434
10435 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10436 let mut text = String::new();
10437 let buffer = self.buffer.read(cx).snapshot(cx);
10438 let mut selections = self.selections.all::<Point>(cx);
10439 let mut clipboard_selections = Vec::with_capacity(selections.len());
10440 {
10441 let max_point = buffer.max_point();
10442 let mut is_first = true;
10443 for selection in &mut selections {
10444 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10445 if is_entire_line {
10446 selection.start = Point::new(selection.start.row, 0);
10447 if !selection.is_empty() && selection.end.column == 0 {
10448 selection.end = cmp::min(max_point, selection.end);
10449 } else {
10450 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10451 }
10452 selection.goal = SelectionGoal::None;
10453 }
10454 if is_first {
10455 is_first = false;
10456 } else {
10457 text += "\n";
10458 }
10459 let mut len = 0;
10460 for chunk in buffer.text_for_range(selection.start..selection.end) {
10461 text.push_str(chunk);
10462 len += chunk.len();
10463 }
10464 clipboard_selections.push(ClipboardSelection {
10465 len,
10466 is_entire_line,
10467 first_line_indent: buffer
10468 .indent_size_for_line(MultiBufferRow(selection.start.row))
10469 .len,
10470 });
10471 }
10472 }
10473
10474 self.transact(window, cx, |this, window, cx| {
10475 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10476 s.select(selections);
10477 });
10478 this.insert("", window, cx);
10479 });
10480 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10481 }
10482
10483 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10484 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10485 let item = self.cut_common(window, cx);
10486 cx.write_to_clipboard(item);
10487 }
10488
10489 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10490 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10491 self.change_selections(None, window, cx, |s| {
10492 s.move_with(|snapshot, sel| {
10493 if sel.is_empty() {
10494 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10495 }
10496 });
10497 });
10498 let item = self.cut_common(window, cx);
10499 cx.set_global(KillRing(item))
10500 }
10501
10502 pub fn kill_ring_yank(
10503 &mut self,
10504 _: &KillRingYank,
10505 window: &mut Window,
10506 cx: &mut Context<Self>,
10507 ) {
10508 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10509 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10510 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10511 (kill_ring.text().to_string(), kill_ring.metadata_json())
10512 } else {
10513 return;
10514 }
10515 } else {
10516 return;
10517 };
10518 self.do_paste(&text, metadata, false, window, cx);
10519 }
10520
10521 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10522 self.do_copy(true, cx);
10523 }
10524
10525 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10526 self.do_copy(false, cx);
10527 }
10528
10529 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10530 let selections = self.selections.all::<Point>(cx);
10531 let buffer = self.buffer.read(cx).read(cx);
10532 let mut text = String::new();
10533
10534 let mut clipboard_selections = Vec::with_capacity(selections.len());
10535 {
10536 let max_point = buffer.max_point();
10537 let mut is_first = true;
10538 for selection in &selections {
10539 let mut start = selection.start;
10540 let mut end = selection.end;
10541 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10542 if is_entire_line {
10543 start = Point::new(start.row, 0);
10544 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10545 }
10546
10547 let mut trimmed_selections = Vec::new();
10548 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10549 let row = MultiBufferRow(start.row);
10550 let first_indent = buffer.indent_size_for_line(row);
10551 if first_indent.len == 0 || start.column > first_indent.len {
10552 trimmed_selections.push(start..end);
10553 } else {
10554 trimmed_selections.push(
10555 Point::new(row.0, first_indent.len)
10556 ..Point::new(row.0, buffer.line_len(row)),
10557 );
10558 for row in start.row + 1..=end.row {
10559 let mut line_len = buffer.line_len(MultiBufferRow(row));
10560 if row == end.row {
10561 line_len = end.column;
10562 }
10563 if line_len == 0 {
10564 trimmed_selections
10565 .push(Point::new(row, 0)..Point::new(row, line_len));
10566 continue;
10567 }
10568 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10569 if row_indent_size.len >= first_indent.len {
10570 trimmed_selections.push(
10571 Point::new(row, first_indent.len)..Point::new(row, line_len),
10572 );
10573 } else {
10574 trimmed_selections.clear();
10575 trimmed_selections.push(start..end);
10576 break;
10577 }
10578 }
10579 }
10580 } else {
10581 trimmed_selections.push(start..end);
10582 }
10583
10584 for trimmed_range in trimmed_selections {
10585 if is_first {
10586 is_first = false;
10587 } else {
10588 text += "\n";
10589 }
10590 let mut len = 0;
10591 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10592 text.push_str(chunk);
10593 len += chunk.len();
10594 }
10595 clipboard_selections.push(ClipboardSelection {
10596 len,
10597 is_entire_line,
10598 first_line_indent: buffer
10599 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10600 .len,
10601 });
10602 }
10603 }
10604 }
10605
10606 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10607 text,
10608 clipboard_selections,
10609 ));
10610 }
10611
10612 pub fn do_paste(
10613 &mut self,
10614 text: &String,
10615 clipboard_selections: Option<Vec<ClipboardSelection>>,
10616 handle_entire_lines: bool,
10617 window: &mut Window,
10618 cx: &mut Context<Self>,
10619 ) {
10620 if self.read_only(cx) {
10621 return;
10622 }
10623
10624 let clipboard_text = Cow::Borrowed(text);
10625
10626 self.transact(window, cx, |this, window, cx| {
10627 if let Some(mut clipboard_selections) = clipboard_selections {
10628 let old_selections = this.selections.all::<usize>(cx);
10629 let all_selections_were_entire_line =
10630 clipboard_selections.iter().all(|s| s.is_entire_line);
10631 let first_selection_indent_column =
10632 clipboard_selections.first().map(|s| s.first_line_indent);
10633 if clipboard_selections.len() != old_selections.len() {
10634 clipboard_selections.drain(..);
10635 }
10636 let cursor_offset = this.selections.last::<usize>(cx).head();
10637 let mut auto_indent_on_paste = true;
10638
10639 this.buffer.update(cx, |buffer, cx| {
10640 let snapshot = buffer.read(cx);
10641 auto_indent_on_paste = snapshot
10642 .language_settings_at(cursor_offset, cx)
10643 .auto_indent_on_paste;
10644
10645 let mut start_offset = 0;
10646 let mut edits = Vec::new();
10647 let mut original_indent_columns = Vec::new();
10648 for (ix, selection) in old_selections.iter().enumerate() {
10649 let to_insert;
10650 let entire_line;
10651 let original_indent_column;
10652 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10653 let end_offset = start_offset + clipboard_selection.len;
10654 to_insert = &clipboard_text[start_offset..end_offset];
10655 entire_line = clipboard_selection.is_entire_line;
10656 start_offset = end_offset + 1;
10657 original_indent_column = Some(clipboard_selection.first_line_indent);
10658 } else {
10659 to_insert = clipboard_text.as_str();
10660 entire_line = all_selections_were_entire_line;
10661 original_indent_column = first_selection_indent_column
10662 }
10663
10664 // If the corresponding selection was empty when this slice of the
10665 // clipboard text was written, then the entire line containing the
10666 // selection was copied. If this selection is also currently empty,
10667 // then paste the line before the current line of the buffer.
10668 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10669 let column = selection.start.to_point(&snapshot).column as usize;
10670 let line_start = selection.start - column;
10671 line_start..line_start
10672 } else {
10673 selection.range()
10674 };
10675
10676 edits.push((range, to_insert));
10677 original_indent_columns.push(original_indent_column);
10678 }
10679 drop(snapshot);
10680
10681 buffer.edit(
10682 edits,
10683 if auto_indent_on_paste {
10684 Some(AutoindentMode::Block {
10685 original_indent_columns,
10686 })
10687 } else {
10688 None
10689 },
10690 cx,
10691 );
10692 });
10693
10694 let selections = this.selections.all::<usize>(cx);
10695 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10696 s.select(selections)
10697 });
10698 } else {
10699 this.insert(&clipboard_text, window, cx);
10700 }
10701 });
10702 }
10703
10704 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10705 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10706 if let Some(item) = cx.read_from_clipboard() {
10707 let entries = item.entries();
10708
10709 match entries.first() {
10710 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10711 // of all the pasted entries.
10712 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10713 .do_paste(
10714 clipboard_string.text(),
10715 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10716 true,
10717 window,
10718 cx,
10719 ),
10720 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10721 }
10722 }
10723 }
10724
10725 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10726 if self.read_only(cx) {
10727 return;
10728 }
10729
10730 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10731
10732 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10733 if let Some((selections, _)) =
10734 self.selection_history.transaction(transaction_id).cloned()
10735 {
10736 self.change_selections(None, window, cx, |s| {
10737 s.select_anchors(selections.to_vec());
10738 });
10739 } else {
10740 log::error!(
10741 "No entry in selection_history found for undo. \
10742 This may correspond to a bug where undo does not update the selection. \
10743 If this is occurring, please add details to \
10744 https://github.com/zed-industries/zed/issues/22692"
10745 );
10746 }
10747 self.request_autoscroll(Autoscroll::fit(), cx);
10748 self.unmark_text(window, cx);
10749 self.refresh_inline_completion(true, false, window, cx);
10750 cx.emit(EditorEvent::Edited { transaction_id });
10751 cx.emit(EditorEvent::TransactionUndone { transaction_id });
10752 }
10753 }
10754
10755 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
10756 if self.read_only(cx) {
10757 return;
10758 }
10759
10760 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10761
10762 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
10763 if let Some((_, Some(selections))) =
10764 self.selection_history.transaction(transaction_id).cloned()
10765 {
10766 self.change_selections(None, window, cx, |s| {
10767 s.select_anchors(selections.to_vec());
10768 });
10769 } else {
10770 log::error!(
10771 "No entry in selection_history found for redo. \
10772 This may correspond to a bug where undo does not update the selection. \
10773 If this is occurring, please add details to \
10774 https://github.com/zed-industries/zed/issues/22692"
10775 );
10776 }
10777 self.request_autoscroll(Autoscroll::fit(), cx);
10778 self.unmark_text(window, cx);
10779 self.refresh_inline_completion(true, false, window, cx);
10780 cx.emit(EditorEvent::Edited { transaction_id });
10781 }
10782 }
10783
10784 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
10785 self.buffer
10786 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
10787 }
10788
10789 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
10790 self.buffer
10791 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
10792 }
10793
10794 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
10795 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10796 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10797 s.move_with(|map, selection| {
10798 let cursor = if selection.is_empty() {
10799 movement::left(map, selection.start)
10800 } else {
10801 selection.start
10802 };
10803 selection.collapse_to(cursor, SelectionGoal::None);
10804 });
10805 })
10806 }
10807
10808 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
10809 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10810 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10811 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
10812 })
10813 }
10814
10815 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
10816 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10817 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10818 s.move_with(|map, selection| {
10819 let cursor = if selection.is_empty() {
10820 movement::right(map, selection.end)
10821 } else {
10822 selection.end
10823 };
10824 selection.collapse_to(cursor, SelectionGoal::None)
10825 });
10826 })
10827 }
10828
10829 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
10830 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10831 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10832 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
10833 })
10834 }
10835
10836 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
10837 if self.take_rename(true, window, cx).is_some() {
10838 return;
10839 }
10840
10841 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10842 cx.propagate();
10843 return;
10844 }
10845
10846 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10847
10848 let text_layout_details = &self.text_layout_details(window);
10849 let selection_count = self.selections.count();
10850 let first_selection = self.selections.first_anchor();
10851
10852 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10853 s.move_with(|map, selection| {
10854 if !selection.is_empty() {
10855 selection.goal = SelectionGoal::None;
10856 }
10857 let (cursor, goal) = movement::up(
10858 map,
10859 selection.start,
10860 selection.goal,
10861 false,
10862 text_layout_details,
10863 );
10864 selection.collapse_to(cursor, goal);
10865 });
10866 });
10867
10868 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10869 {
10870 cx.propagate();
10871 }
10872 }
10873
10874 pub fn move_up_by_lines(
10875 &mut self,
10876 action: &MoveUpByLines,
10877 window: &mut Window,
10878 cx: &mut Context<Self>,
10879 ) {
10880 if self.take_rename(true, window, cx).is_some() {
10881 return;
10882 }
10883
10884 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10885 cx.propagate();
10886 return;
10887 }
10888
10889 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10890
10891 let text_layout_details = &self.text_layout_details(window);
10892
10893 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10894 s.move_with(|map, selection| {
10895 if !selection.is_empty() {
10896 selection.goal = SelectionGoal::None;
10897 }
10898 let (cursor, goal) = movement::up_by_rows(
10899 map,
10900 selection.start,
10901 action.lines,
10902 selection.goal,
10903 false,
10904 text_layout_details,
10905 );
10906 selection.collapse_to(cursor, goal);
10907 });
10908 })
10909 }
10910
10911 pub fn move_down_by_lines(
10912 &mut self,
10913 action: &MoveDownByLines,
10914 window: &mut Window,
10915 cx: &mut Context<Self>,
10916 ) {
10917 if self.take_rename(true, window, cx).is_some() {
10918 return;
10919 }
10920
10921 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10922 cx.propagate();
10923 return;
10924 }
10925
10926 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10927
10928 let text_layout_details = &self.text_layout_details(window);
10929
10930 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10931 s.move_with(|map, selection| {
10932 if !selection.is_empty() {
10933 selection.goal = SelectionGoal::None;
10934 }
10935 let (cursor, goal) = movement::down_by_rows(
10936 map,
10937 selection.start,
10938 action.lines,
10939 selection.goal,
10940 false,
10941 text_layout_details,
10942 );
10943 selection.collapse_to(cursor, goal);
10944 });
10945 })
10946 }
10947
10948 pub fn select_down_by_lines(
10949 &mut self,
10950 action: &SelectDownByLines,
10951 window: &mut Window,
10952 cx: &mut Context<Self>,
10953 ) {
10954 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10955 let text_layout_details = &self.text_layout_details(window);
10956 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10957 s.move_heads_with(|map, head, goal| {
10958 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
10959 })
10960 })
10961 }
10962
10963 pub fn select_up_by_lines(
10964 &mut self,
10965 action: &SelectUpByLines,
10966 window: &mut Window,
10967 cx: &mut Context<Self>,
10968 ) {
10969 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10970 let text_layout_details = &self.text_layout_details(window);
10971 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10972 s.move_heads_with(|map, head, goal| {
10973 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
10974 })
10975 })
10976 }
10977
10978 pub fn select_page_up(
10979 &mut self,
10980 _: &SelectPageUp,
10981 window: &mut Window,
10982 cx: &mut Context<Self>,
10983 ) {
10984 let Some(row_count) = self.visible_row_count() else {
10985 return;
10986 };
10987
10988 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10989
10990 let text_layout_details = &self.text_layout_details(window);
10991
10992 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10993 s.move_heads_with(|map, head, goal| {
10994 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
10995 })
10996 })
10997 }
10998
10999 pub fn move_page_up(
11000 &mut self,
11001 action: &MovePageUp,
11002 window: &mut Window,
11003 cx: &mut Context<Self>,
11004 ) {
11005 if self.take_rename(true, window, cx).is_some() {
11006 return;
11007 }
11008
11009 if self
11010 .context_menu
11011 .borrow_mut()
11012 .as_mut()
11013 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
11014 .unwrap_or(false)
11015 {
11016 return;
11017 }
11018
11019 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11020 cx.propagate();
11021 return;
11022 }
11023
11024 let Some(row_count) = self.visible_row_count() else {
11025 return;
11026 };
11027
11028 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11029
11030 let autoscroll = if action.center_cursor {
11031 Autoscroll::center()
11032 } else {
11033 Autoscroll::fit()
11034 };
11035
11036 let text_layout_details = &self.text_layout_details(window);
11037
11038 self.change_selections(Some(autoscroll), window, cx, |s| {
11039 s.move_with(|map, selection| {
11040 if !selection.is_empty() {
11041 selection.goal = SelectionGoal::None;
11042 }
11043 let (cursor, goal) = movement::up_by_rows(
11044 map,
11045 selection.end,
11046 row_count,
11047 selection.goal,
11048 false,
11049 text_layout_details,
11050 );
11051 selection.collapse_to(cursor, goal);
11052 });
11053 });
11054 }
11055
11056 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11057 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11058 let text_layout_details = &self.text_layout_details(window);
11059 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11060 s.move_heads_with(|map, head, goal| {
11061 movement::up(map, head, goal, false, text_layout_details)
11062 })
11063 })
11064 }
11065
11066 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11067 self.take_rename(true, window, cx);
11068
11069 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11070 cx.propagate();
11071 return;
11072 }
11073
11074 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11075
11076 let text_layout_details = &self.text_layout_details(window);
11077 let selection_count = self.selections.count();
11078 let first_selection = self.selections.first_anchor();
11079
11080 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11081 s.move_with(|map, selection| {
11082 if !selection.is_empty() {
11083 selection.goal = SelectionGoal::None;
11084 }
11085 let (cursor, goal) = movement::down(
11086 map,
11087 selection.end,
11088 selection.goal,
11089 false,
11090 text_layout_details,
11091 );
11092 selection.collapse_to(cursor, goal);
11093 });
11094 });
11095
11096 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11097 {
11098 cx.propagate();
11099 }
11100 }
11101
11102 pub fn select_page_down(
11103 &mut self,
11104 _: &SelectPageDown,
11105 window: &mut Window,
11106 cx: &mut Context<Self>,
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 text_layout_details = &self.text_layout_details(window);
11115
11116 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11117 s.move_heads_with(|map, head, goal| {
11118 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11119 })
11120 })
11121 }
11122
11123 pub fn move_page_down(
11124 &mut self,
11125 action: &MovePageDown,
11126 window: &mut Window,
11127 cx: &mut Context<Self>,
11128 ) {
11129 if self.take_rename(true, window, cx).is_some() {
11130 return;
11131 }
11132
11133 if self
11134 .context_menu
11135 .borrow_mut()
11136 .as_mut()
11137 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
11138 .unwrap_or(false)
11139 {
11140 return;
11141 }
11142
11143 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11144 cx.propagate();
11145 return;
11146 }
11147
11148 let Some(row_count) = self.visible_row_count() else {
11149 return;
11150 };
11151
11152 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11153
11154 let autoscroll = if action.center_cursor {
11155 Autoscroll::center()
11156 } else {
11157 Autoscroll::fit()
11158 };
11159
11160 let text_layout_details = &self.text_layout_details(window);
11161 self.change_selections(Some(autoscroll), window, cx, |s| {
11162 s.move_with(|map, selection| {
11163 if !selection.is_empty() {
11164 selection.goal = SelectionGoal::None;
11165 }
11166 let (cursor, goal) = movement::down_by_rows(
11167 map,
11168 selection.end,
11169 row_count,
11170 selection.goal,
11171 false,
11172 text_layout_details,
11173 );
11174 selection.collapse_to(cursor, goal);
11175 });
11176 });
11177 }
11178
11179 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11180 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11181 let text_layout_details = &self.text_layout_details(window);
11182 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11183 s.move_heads_with(|map, head, goal| {
11184 movement::down(map, head, goal, false, text_layout_details)
11185 })
11186 });
11187 }
11188
11189 pub fn context_menu_first(
11190 &mut self,
11191 _: &ContextMenuFirst,
11192 _window: &mut Window,
11193 cx: &mut Context<Self>,
11194 ) {
11195 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11196 context_menu.select_first(self.completion_provider.as_deref(), cx);
11197 }
11198 }
11199
11200 pub fn context_menu_prev(
11201 &mut self,
11202 _: &ContextMenuPrevious,
11203 _window: &mut Window,
11204 cx: &mut Context<Self>,
11205 ) {
11206 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11207 context_menu.select_prev(self.completion_provider.as_deref(), cx);
11208 }
11209 }
11210
11211 pub fn context_menu_next(
11212 &mut self,
11213 _: &ContextMenuNext,
11214 _window: &mut Window,
11215 cx: &mut Context<Self>,
11216 ) {
11217 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11218 context_menu.select_next(self.completion_provider.as_deref(), cx);
11219 }
11220 }
11221
11222 pub fn context_menu_last(
11223 &mut self,
11224 _: &ContextMenuLast,
11225 _window: &mut Window,
11226 cx: &mut Context<Self>,
11227 ) {
11228 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11229 context_menu.select_last(self.completion_provider.as_deref(), cx);
11230 }
11231 }
11232
11233 pub fn move_to_previous_word_start(
11234 &mut self,
11235 _: &MoveToPreviousWordStart,
11236 window: &mut Window,
11237 cx: &mut Context<Self>,
11238 ) {
11239 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11240 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11241 s.move_cursors_with(|map, head, _| {
11242 (
11243 movement::previous_word_start(map, head),
11244 SelectionGoal::None,
11245 )
11246 });
11247 })
11248 }
11249
11250 pub fn move_to_previous_subword_start(
11251 &mut self,
11252 _: &MoveToPreviousSubwordStart,
11253 window: &mut Window,
11254 cx: &mut Context<Self>,
11255 ) {
11256 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11257 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11258 s.move_cursors_with(|map, head, _| {
11259 (
11260 movement::previous_subword_start(map, head),
11261 SelectionGoal::None,
11262 )
11263 });
11264 })
11265 }
11266
11267 pub fn select_to_previous_word_start(
11268 &mut self,
11269 _: &SelectToPreviousWordStart,
11270 window: &mut Window,
11271 cx: &mut Context<Self>,
11272 ) {
11273 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11274 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11275 s.move_heads_with(|map, head, _| {
11276 (
11277 movement::previous_word_start(map, head),
11278 SelectionGoal::None,
11279 )
11280 });
11281 })
11282 }
11283
11284 pub fn select_to_previous_subword_start(
11285 &mut self,
11286 _: &SelectToPreviousSubwordStart,
11287 window: &mut Window,
11288 cx: &mut Context<Self>,
11289 ) {
11290 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11291 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11292 s.move_heads_with(|map, head, _| {
11293 (
11294 movement::previous_subword_start(map, head),
11295 SelectionGoal::None,
11296 )
11297 });
11298 })
11299 }
11300
11301 pub fn delete_to_previous_word_start(
11302 &mut self,
11303 action: &DeleteToPreviousWordStart,
11304 window: &mut Window,
11305 cx: &mut Context<Self>,
11306 ) {
11307 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11308 self.transact(window, cx, |this, window, cx| {
11309 this.select_autoclose_pair(window, cx);
11310 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11311 s.move_with(|map, selection| {
11312 if selection.is_empty() {
11313 let cursor = if action.ignore_newlines {
11314 movement::previous_word_start(map, selection.head())
11315 } else {
11316 movement::previous_word_start_or_newline(map, selection.head())
11317 };
11318 selection.set_head(cursor, SelectionGoal::None);
11319 }
11320 });
11321 });
11322 this.insert("", window, cx);
11323 });
11324 }
11325
11326 pub fn delete_to_previous_subword_start(
11327 &mut self,
11328 _: &DeleteToPreviousSubwordStart,
11329 window: &mut Window,
11330 cx: &mut Context<Self>,
11331 ) {
11332 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11333 self.transact(window, cx, |this, window, cx| {
11334 this.select_autoclose_pair(window, cx);
11335 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11336 s.move_with(|map, selection| {
11337 if selection.is_empty() {
11338 let cursor = movement::previous_subword_start(map, selection.head());
11339 selection.set_head(cursor, SelectionGoal::None);
11340 }
11341 });
11342 });
11343 this.insert("", window, cx);
11344 });
11345 }
11346
11347 pub fn move_to_next_word_end(
11348 &mut self,
11349 _: &MoveToNextWordEnd,
11350 window: &mut Window,
11351 cx: &mut Context<Self>,
11352 ) {
11353 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11354 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11355 s.move_cursors_with(|map, head, _| {
11356 (movement::next_word_end(map, head), SelectionGoal::None)
11357 });
11358 })
11359 }
11360
11361 pub fn move_to_next_subword_end(
11362 &mut self,
11363 _: &MoveToNextSubwordEnd,
11364 window: &mut Window,
11365 cx: &mut Context<Self>,
11366 ) {
11367 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11368 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11369 s.move_cursors_with(|map, head, _| {
11370 (movement::next_subword_end(map, head), SelectionGoal::None)
11371 });
11372 })
11373 }
11374
11375 pub fn select_to_next_word_end(
11376 &mut self,
11377 _: &SelectToNextWordEnd,
11378 window: &mut Window,
11379 cx: &mut Context<Self>,
11380 ) {
11381 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11382 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11383 s.move_heads_with(|map, head, _| {
11384 (movement::next_word_end(map, head), SelectionGoal::None)
11385 });
11386 })
11387 }
11388
11389 pub fn select_to_next_subword_end(
11390 &mut self,
11391 _: &SelectToNextSubwordEnd,
11392 window: &mut Window,
11393 cx: &mut Context<Self>,
11394 ) {
11395 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11396 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11397 s.move_heads_with(|map, head, _| {
11398 (movement::next_subword_end(map, head), SelectionGoal::None)
11399 });
11400 })
11401 }
11402
11403 pub fn delete_to_next_word_end(
11404 &mut self,
11405 action: &DeleteToNextWordEnd,
11406 window: &mut Window,
11407 cx: &mut Context<Self>,
11408 ) {
11409 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11410 self.transact(window, cx, |this, window, cx| {
11411 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11412 s.move_with(|map, selection| {
11413 if selection.is_empty() {
11414 let cursor = if action.ignore_newlines {
11415 movement::next_word_end(map, selection.head())
11416 } else {
11417 movement::next_word_end_or_newline(map, selection.head())
11418 };
11419 selection.set_head(cursor, SelectionGoal::None);
11420 }
11421 });
11422 });
11423 this.insert("", window, cx);
11424 });
11425 }
11426
11427 pub fn delete_to_next_subword_end(
11428 &mut self,
11429 _: &DeleteToNextSubwordEnd,
11430 window: &mut Window,
11431 cx: &mut Context<Self>,
11432 ) {
11433 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11434 self.transact(window, cx, |this, window, cx| {
11435 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11436 s.move_with(|map, selection| {
11437 if selection.is_empty() {
11438 let cursor = movement::next_subword_end(map, selection.head());
11439 selection.set_head(cursor, SelectionGoal::None);
11440 }
11441 });
11442 });
11443 this.insert("", window, cx);
11444 });
11445 }
11446
11447 pub fn move_to_beginning_of_line(
11448 &mut self,
11449 action: &MoveToBeginningOfLine,
11450 window: &mut Window,
11451 cx: &mut Context<Self>,
11452 ) {
11453 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11454 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11455 s.move_cursors_with(|map, head, _| {
11456 (
11457 movement::indented_line_beginning(
11458 map,
11459 head,
11460 action.stop_at_soft_wraps,
11461 action.stop_at_indent,
11462 ),
11463 SelectionGoal::None,
11464 )
11465 });
11466 })
11467 }
11468
11469 pub fn select_to_beginning_of_line(
11470 &mut self,
11471 action: &SelectToBeginningOfLine,
11472 window: &mut Window,
11473 cx: &mut Context<Self>,
11474 ) {
11475 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11476 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11477 s.move_heads_with(|map, head, _| {
11478 (
11479 movement::indented_line_beginning(
11480 map,
11481 head,
11482 action.stop_at_soft_wraps,
11483 action.stop_at_indent,
11484 ),
11485 SelectionGoal::None,
11486 )
11487 });
11488 });
11489 }
11490
11491 pub fn delete_to_beginning_of_line(
11492 &mut self,
11493 action: &DeleteToBeginningOfLine,
11494 window: &mut Window,
11495 cx: &mut Context<Self>,
11496 ) {
11497 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11498 self.transact(window, cx, |this, window, cx| {
11499 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11500 s.move_with(|_, selection| {
11501 selection.reversed = true;
11502 });
11503 });
11504
11505 this.select_to_beginning_of_line(
11506 &SelectToBeginningOfLine {
11507 stop_at_soft_wraps: false,
11508 stop_at_indent: action.stop_at_indent,
11509 },
11510 window,
11511 cx,
11512 );
11513 this.backspace(&Backspace, window, cx);
11514 });
11515 }
11516
11517 pub fn move_to_end_of_line(
11518 &mut self,
11519 action: &MoveToEndOfLine,
11520 window: &mut Window,
11521 cx: &mut Context<Self>,
11522 ) {
11523 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11524 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11525 s.move_cursors_with(|map, head, _| {
11526 (
11527 movement::line_end(map, head, action.stop_at_soft_wraps),
11528 SelectionGoal::None,
11529 )
11530 });
11531 })
11532 }
11533
11534 pub fn select_to_end_of_line(
11535 &mut self,
11536 action: &SelectToEndOfLine,
11537 window: &mut Window,
11538 cx: &mut Context<Self>,
11539 ) {
11540 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11541 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11542 s.move_heads_with(|map, head, _| {
11543 (
11544 movement::line_end(map, head, action.stop_at_soft_wraps),
11545 SelectionGoal::None,
11546 )
11547 });
11548 })
11549 }
11550
11551 pub fn delete_to_end_of_line(
11552 &mut self,
11553 _: &DeleteToEndOfLine,
11554 window: &mut Window,
11555 cx: &mut Context<Self>,
11556 ) {
11557 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11558 self.transact(window, cx, |this, window, cx| {
11559 this.select_to_end_of_line(
11560 &SelectToEndOfLine {
11561 stop_at_soft_wraps: false,
11562 },
11563 window,
11564 cx,
11565 );
11566 this.delete(&Delete, window, cx);
11567 });
11568 }
11569
11570 pub fn cut_to_end_of_line(
11571 &mut self,
11572 _: &CutToEndOfLine,
11573 window: &mut Window,
11574 cx: &mut Context<Self>,
11575 ) {
11576 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11577 self.transact(window, cx, |this, window, cx| {
11578 this.select_to_end_of_line(
11579 &SelectToEndOfLine {
11580 stop_at_soft_wraps: false,
11581 },
11582 window,
11583 cx,
11584 );
11585 this.cut(&Cut, window, cx);
11586 });
11587 }
11588
11589 pub fn move_to_start_of_paragraph(
11590 &mut self,
11591 _: &MoveToStartOfParagraph,
11592 window: &mut Window,
11593 cx: &mut Context<Self>,
11594 ) {
11595 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11596 cx.propagate();
11597 return;
11598 }
11599 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11600 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11601 s.move_with(|map, selection| {
11602 selection.collapse_to(
11603 movement::start_of_paragraph(map, selection.head(), 1),
11604 SelectionGoal::None,
11605 )
11606 });
11607 })
11608 }
11609
11610 pub fn move_to_end_of_paragraph(
11611 &mut self,
11612 _: &MoveToEndOfParagraph,
11613 window: &mut Window,
11614 cx: &mut Context<Self>,
11615 ) {
11616 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11617 cx.propagate();
11618 return;
11619 }
11620 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11621 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11622 s.move_with(|map, selection| {
11623 selection.collapse_to(
11624 movement::end_of_paragraph(map, selection.head(), 1),
11625 SelectionGoal::None,
11626 )
11627 });
11628 })
11629 }
11630
11631 pub fn select_to_start_of_paragraph(
11632 &mut self,
11633 _: &SelectToStartOfParagraph,
11634 window: &mut Window,
11635 cx: &mut Context<Self>,
11636 ) {
11637 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11638 cx.propagate();
11639 return;
11640 }
11641 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11642 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11643 s.move_heads_with(|map, head, _| {
11644 (
11645 movement::start_of_paragraph(map, head, 1),
11646 SelectionGoal::None,
11647 )
11648 });
11649 })
11650 }
11651
11652 pub fn select_to_end_of_paragraph(
11653 &mut self,
11654 _: &SelectToEndOfParagraph,
11655 window: &mut Window,
11656 cx: &mut Context<Self>,
11657 ) {
11658 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11659 cx.propagate();
11660 return;
11661 }
11662 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11663 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11664 s.move_heads_with(|map, head, _| {
11665 (
11666 movement::end_of_paragraph(map, head, 1),
11667 SelectionGoal::None,
11668 )
11669 });
11670 })
11671 }
11672
11673 pub fn move_to_start_of_excerpt(
11674 &mut self,
11675 _: &MoveToStartOfExcerpt,
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_excerpt(
11688 map,
11689 selection.head(),
11690 workspace::searchable::Direction::Prev,
11691 ),
11692 SelectionGoal::None,
11693 )
11694 });
11695 })
11696 }
11697
11698 pub fn move_to_start_of_next_excerpt(
11699 &mut self,
11700 _: &MoveToStartOfNextExcerpt,
11701 window: &mut Window,
11702 cx: &mut Context<Self>,
11703 ) {
11704 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11705 cx.propagate();
11706 return;
11707 }
11708
11709 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11710 s.move_with(|map, selection| {
11711 selection.collapse_to(
11712 movement::start_of_excerpt(
11713 map,
11714 selection.head(),
11715 workspace::searchable::Direction::Next,
11716 ),
11717 SelectionGoal::None,
11718 )
11719 });
11720 })
11721 }
11722
11723 pub fn move_to_end_of_excerpt(
11724 &mut self,
11725 _: &MoveToEndOfExcerpt,
11726 window: &mut Window,
11727 cx: &mut Context<Self>,
11728 ) {
11729 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11730 cx.propagate();
11731 return;
11732 }
11733 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11734 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11735 s.move_with(|map, selection| {
11736 selection.collapse_to(
11737 movement::end_of_excerpt(
11738 map,
11739 selection.head(),
11740 workspace::searchable::Direction::Next,
11741 ),
11742 SelectionGoal::None,
11743 )
11744 });
11745 })
11746 }
11747
11748 pub fn move_to_end_of_previous_excerpt(
11749 &mut self,
11750 _: &MoveToEndOfPreviousExcerpt,
11751 window: &mut Window,
11752 cx: &mut Context<Self>,
11753 ) {
11754 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11755 cx.propagate();
11756 return;
11757 }
11758 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11759 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11760 s.move_with(|map, selection| {
11761 selection.collapse_to(
11762 movement::end_of_excerpt(
11763 map,
11764 selection.head(),
11765 workspace::searchable::Direction::Prev,
11766 ),
11767 SelectionGoal::None,
11768 )
11769 });
11770 })
11771 }
11772
11773 pub fn select_to_start_of_excerpt(
11774 &mut self,
11775 _: &SelectToStartOfExcerpt,
11776 window: &mut Window,
11777 cx: &mut Context<Self>,
11778 ) {
11779 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11780 cx.propagate();
11781 return;
11782 }
11783 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11784 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11785 s.move_heads_with(|map, head, _| {
11786 (
11787 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11788 SelectionGoal::None,
11789 )
11790 });
11791 })
11792 }
11793
11794 pub fn select_to_start_of_next_excerpt(
11795 &mut self,
11796 _: &SelectToStartOfNextExcerpt,
11797 window: &mut Window,
11798 cx: &mut Context<Self>,
11799 ) {
11800 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11801 cx.propagate();
11802 return;
11803 }
11804 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11805 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11806 s.move_heads_with(|map, head, _| {
11807 (
11808 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
11809 SelectionGoal::None,
11810 )
11811 });
11812 })
11813 }
11814
11815 pub fn select_to_end_of_excerpt(
11816 &mut self,
11817 _: &SelectToEndOfExcerpt,
11818 window: &mut Window,
11819 cx: &mut Context<Self>,
11820 ) {
11821 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11822 cx.propagate();
11823 return;
11824 }
11825 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11826 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11827 s.move_heads_with(|map, head, _| {
11828 (
11829 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
11830 SelectionGoal::None,
11831 )
11832 });
11833 })
11834 }
11835
11836 pub fn select_to_end_of_previous_excerpt(
11837 &mut self,
11838 _: &SelectToEndOfPreviousExcerpt,
11839 window: &mut Window,
11840 cx: &mut Context<Self>,
11841 ) {
11842 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11843 cx.propagate();
11844 return;
11845 }
11846 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11847 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11848 s.move_heads_with(|map, head, _| {
11849 (
11850 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11851 SelectionGoal::None,
11852 )
11853 });
11854 })
11855 }
11856
11857 pub fn move_to_beginning(
11858 &mut self,
11859 _: &MoveToBeginning,
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.select_ranges(vec![0..0]);
11870 });
11871 }
11872
11873 pub fn select_to_beginning(
11874 &mut self,
11875 _: &SelectToBeginning,
11876 window: &mut Window,
11877 cx: &mut Context<Self>,
11878 ) {
11879 let mut selection = self.selections.last::<Point>(cx);
11880 selection.set_head(Point::zero(), SelectionGoal::None);
11881 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11882 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11883 s.select(vec![selection]);
11884 });
11885 }
11886
11887 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
11888 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11889 cx.propagate();
11890 return;
11891 }
11892 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11893 let cursor = self.buffer.read(cx).read(cx).len();
11894 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11895 s.select_ranges(vec![cursor..cursor])
11896 });
11897 }
11898
11899 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
11900 self.nav_history = nav_history;
11901 }
11902
11903 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
11904 self.nav_history.as_ref()
11905 }
11906
11907 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
11908 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
11909 }
11910
11911 fn push_to_nav_history(
11912 &mut self,
11913 cursor_anchor: Anchor,
11914 new_position: Option<Point>,
11915 is_deactivate: bool,
11916 cx: &mut Context<Self>,
11917 ) {
11918 if let Some(nav_history) = self.nav_history.as_mut() {
11919 let buffer = self.buffer.read(cx).read(cx);
11920 let cursor_position = cursor_anchor.to_point(&buffer);
11921 let scroll_state = self.scroll_manager.anchor();
11922 let scroll_top_row = scroll_state.top_row(&buffer);
11923 drop(buffer);
11924
11925 if let Some(new_position) = new_position {
11926 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
11927 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
11928 return;
11929 }
11930 }
11931
11932 nav_history.push(
11933 Some(NavigationData {
11934 cursor_anchor,
11935 cursor_position,
11936 scroll_anchor: scroll_state,
11937 scroll_top_row,
11938 }),
11939 cx,
11940 );
11941 cx.emit(EditorEvent::PushedToNavHistory {
11942 anchor: cursor_anchor,
11943 is_deactivate,
11944 })
11945 }
11946 }
11947
11948 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
11949 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11950 let buffer = self.buffer.read(cx).snapshot(cx);
11951 let mut selection = self.selections.first::<usize>(cx);
11952 selection.set_head(buffer.len(), SelectionGoal::None);
11953 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11954 s.select(vec![selection]);
11955 });
11956 }
11957
11958 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
11959 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11960 let end = self.buffer.read(cx).read(cx).len();
11961 self.change_selections(None, window, cx, |s| {
11962 s.select_ranges(vec![0..end]);
11963 });
11964 }
11965
11966 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
11967 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11968 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11969 let mut selections = self.selections.all::<Point>(cx);
11970 let max_point = display_map.buffer_snapshot.max_point();
11971 for selection in &mut selections {
11972 let rows = selection.spanned_rows(true, &display_map);
11973 selection.start = Point::new(rows.start.0, 0);
11974 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
11975 selection.reversed = false;
11976 }
11977 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11978 s.select(selections);
11979 });
11980 }
11981
11982 pub fn split_selection_into_lines(
11983 &mut self,
11984 _: &SplitSelectionIntoLines,
11985 window: &mut Window,
11986 cx: &mut Context<Self>,
11987 ) {
11988 let selections = self
11989 .selections
11990 .all::<Point>(cx)
11991 .into_iter()
11992 .map(|selection| selection.start..selection.end)
11993 .collect::<Vec<_>>();
11994 self.unfold_ranges(&selections, true, true, cx);
11995
11996 let mut new_selection_ranges = Vec::new();
11997 {
11998 let buffer = self.buffer.read(cx).read(cx);
11999 for selection in selections {
12000 for row in selection.start.row..selection.end.row {
12001 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12002 new_selection_ranges.push(cursor..cursor);
12003 }
12004
12005 let is_multiline_selection = selection.start.row != selection.end.row;
12006 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12007 // so this action feels more ergonomic when paired with other selection operations
12008 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12009 if !should_skip_last {
12010 new_selection_ranges.push(selection.end..selection.end);
12011 }
12012 }
12013 }
12014 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12015 s.select_ranges(new_selection_ranges);
12016 });
12017 }
12018
12019 pub fn add_selection_above(
12020 &mut self,
12021 _: &AddSelectionAbove,
12022 window: &mut Window,
12023 cx: &mut Context<Self>,
12024 ) {
12025 self.add_selection(true, window, cx);
12026 }
12027
12028 pub fn add_selection_below(
12029 &mut self,
12030 _: &AddSelectionBelow,
12031 window: &mut Window,
12032 cx: &mut Context<Self>,
12033 ) {
12034 self.add_selection(false, window, cx);
12035 }
12036
12037 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12038 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12039
12040 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12041 let mut selections = self.selections.all::<Point>(cx);
12042 let text_layout_details = self.text_layout_details(window);
12043 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12044 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12045 let range = oldest_selection.display_range(&display_map).sorted();
12046
12047 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12048 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12049 let positions = start_x.min(end_x)..start_x.max(end_x);
12050
12051 selections.clear();
12052 let mut stack = Vec::new();
12053 for row in range.start.row().0..=range.end.row().0 {
12054 if let Some(selection) = self.selections.build_columnar_selection(
12055 &display_map,
12056 DisplayRow(row),
12057 &positions,
12058 oldest_selection.reversed,
12059 &text_layout_details,
12060 ) {
12061 stack.push(selection.id);
12062 selections.push(selection);
12063 }
12064 }
12065
12066 if above {
12067 stack.reverse();
12068 }
12069
12070 AddSelectionsState { above, stack }
12071 });
12072
12073 let last_added_selection = *state.stack.last().unwrap();
12074 let mut new_selections = Vec::new();
12075 if above == state.above {
12076 let end_row = if above {
12077 DisplayRow(0)
12078 } else {
12079 display_map.max_point().row()
12080 };
12081
12082 'outer: for selection in selections {
12083 if selection.id == last_added_selection {
12084 let range = selection.display_range(&display_map).sorted();
12085 debug_assert_eq!(range.start.row(), range.end.row());
12086 let mut row = range.start.row();
12087 let positions =
12088 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12089 px(start)..px(end)
12090 } else {
12091 let start_x =
12092 display_map.x_for_display_point(range.start, &text_layout_details);
12093 let end_x =
12094 display_map.x_for_display_point(range.end, &text_layout_details);
12095 start_x.min(end_x)..start_x.max(end_x)
12096 };
12097
12098 while row != end_row {
12099 if above {
12100 row.0 -= 1;
12101 } else {
12102 row.0 += 1;
12103 }
12104
12105 if let Some(new_selection) = self.selections.build_columnar_selection(
12106 &display_map,
12107 row,
12108 &positions,
12109 selection.reversed,
12110 &text_layout_details,
12111 ) {
12112 state.stack.push(new_selection.id);
12113 if above {
12114 new_selections.push(new_selection);
12115 new_selections.push(selection);
12116 } else {
12117 new_selections.push(selection);
12118 new_selections.push(new_selection);
12119 }
12120
12121 continue 'outer;
12122 }
12123 }
12124 }
12125
12126 new_selections.push(selection);
12127 }
12128 } else {
12129 new_selections = selections;
12130 new_selections.retain(|s| s.id != last_added_selection);
12131 state.stack.pop();
12132 }
12133
12134 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12135 s.select(new_selections);
12136 });
12137 if state.stack.len() > 1 {
12138 self.add_selections_state = Some(state);
12139 }
12140 }
12141
12142 fn select_match_ranges(
12143 &mut self,
12144 range: Range<usize>,
12145 reversed: bool,
12146 replace_newest: bool,
12147 auto_scroll: Option<Autoscroll>,
12148 window: &mut Window,
12149 cx: &mut Context<Editor>,
12150 ) {
12151 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12152 self.change_selections(auto_scroll, window, cx, |s| {
12153 if replace_newest {
12154 s.delete(s.newest_anchor().id);
12155 }
12156 if reversed {
12157 s.insert_range(range.end..range.start);
12158 } else {
12159 s.insert_range(range);
12160 }
12161 });
12162 }
12163
12164 pub fn select_next_match_internal(
12165 &mut self,
12166 display_map: &DisplaySnapshot,
12167 replace_newest: bool,
12168 autoscroll: Option<Autoscroll>,
12169 window: &mut Window,
12170 cx: &mut Context<Self>,
12171 ) -> Result<()> {
12172 let buffer = &display_map.buffer_snapshot;
12173 let mut selections = self.selections.all::<usize>(cx);
12174 if let Some(mut select_next_state) = self.select_next_state.take() {
12175 let query = &select_next_state.query;
12176 if !select_next_state.done {
12177 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12178 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12179 let mut next_selected_range = None;
12180
12181 let bytes_after_last_selection =
12182 buffer.bytes_in_range(last_selection.end..buffer.len());
12183 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12184 let query_matches = query
12185 .stream_find_iter(bytes_after_last_selection)
12186 .map(|result| (last_selection.end, result))
12187 .chain(
12188 query
12189 .stream_find_iter(bytes_before_first_selection)
12190 .map(|result| (0, result)),
12191 );
12192
12193 for (start_offset, query_match) in query_matches {
12194 let query_match = query_match.unwrap(); // can only fail due to I/O
12195 let offset_range =
12196 start_offset + query_match.start()..start_offset + query_match.end();
12197 let display_range = offset_range.start.to_display_point(display_map)
12198 ..offset_range.end.to_display_point(display_map);
12199
12200 if !select_next_state.wordwise
12201 || (!movement::is_inside_word(display_map, display_range.start)
12202 && !movement::is_inside_word(display_map, display_range.end))
12203 {
12204 // TODO: This is n^2, because we might check all the selections
12205 if !selections
12206 .iter()
12207 .any(|selection| selection.range().overlaps(&offset_range))
12208 {
12209 next_selected_range = Some(offset_range);
12210 break;
12211 }
12212 }
12213 }
12214
12215 if let Some(next_selected_range) = next_selected_range {
12216 self.select_match_ranges(
12217 next_selected_range,
12218 last_selection.reversed,
12219 replace_newest,
12220 autoscroll,
12221 window,
12222 cx,
12223 );
12224 } else {
12225 select_next_state.done = true;
12226 }
12227 }
12228
12229 self.select_next_state = Some(select_next_state);
12230 } else {
12231 let mut only_carets = true;
12232 let mut same_text_selected = true;
12233 let mut selected_text = None;
12234
12235 let mut selections_iter = selections.iter().peekable();
12236 while let Some(selection) = selections_iter.next() {
12237 if selection.start != selection.end {
12238 only_carets = false;
12239 }
12240
12241 if same_text_selected {
12242 if selected_text.is_none() {
12243 selected_text =
12244 Some(buffer.text_for_range(selection.range()).collect::<String>());
12245 }
12246
12247 if let Some(next_selection) = selections_iter.peek() {
12248 if next_selection.range().len() == selection.range().len() {
12249 let next_selected_text = buffer
12250 .text_for_range(next_selection.range())
12251 .collect::<String>();
12252 if Some(next_selected_text) != selected_text {
12253 same_text_selected = false;
12254 selected_text = None;
12255 }
12256 } else {
12257 same_text_selected = false;
12258 selected_text = None;
12259 }
12260 }
12261 }
12262 }
12263
12264 if only_carets {
12265 for selection in &mut selections {
12266 let word_range = movement::surrounding_word(
12267 display_map,
12268 selection.start.to_display_point(display_map),
12269 );
12270 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12271 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12272 selection.goal = SelectionGoal::None;
12273 selection.reversed = false;
12274 self.select_match_ranges(
12275 selection.start..selection.end,
12276 selection.reversed,
12277 replace_newest,
12278 autoscroll,
12279 window,
12280 cx,
12281 );
12282 }
12283
12284 if selections.len() == 1 {
12285 let selection = selections
12286 .last()
12287 .expect("ensured that there's only one selection");
12288 let query = buffer
12289 .text_for_range(selection.start..selection.end)
12290 .collect::<String>();
12291 let is_empty = query.is_empty();
12292 let select_state = SelectNextState {
12293 query: AhoCorasick::new(&[query])?,
12294 wordwise: true,
12295 done: is_empty,
12296 };
12297 self.select_next_state = Some(select_state);
12298 } else {
12299 self.select_next_state = None;
12300 }
12301 } else if let Some(selected_text) = selected_text {
12302 self.select_next_state = Some(SelectNextState {
12303 query: AhoCorasick::new(&[selected_text])?,
12304 wordwise: false,
12305 done: false,
12306 });
12307 self.select_next_match_internal(
12308 display_map,
12309 replace_newest,
12310 autoscroll,
12311 window,
12312 cx,
12313 )?;
12314 }
12315 }
12316 Ok(())
12317 }
12318
12319 pub fn select_all_matches(
12320 &mut self,
12321 _action: &SelectAllMatches,
12322 window: &mut Window,
12323 cx: &mut Context<Self>,
12324 ) -> Result<()> {
12325 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12326
12327 self.push_to_selection_history();
12328 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12329
12330 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12331 let Some(select_next_state) = self.select_next_state.as_mut() else {
12332 return Ok(());
12333 };
12334 if select_next_state.done {
12335 return Ok(());
12336 }
12337
12338 let mut new_selections = Vec::new();
12339
12340 let reversed = self.selections.oldest::<usize>(cx).reversed;
12341 let buffer = &display_map.buffer_snapshot;
12342 let query_matches = select_next_state
12343 .query
12344 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12345
12346 for query_match in query_matches.into_iter() {
12347 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12348 let offset_range = if reversed {
12349 query_match.end()..query_match.start()
12350 } else {
12351 query_match.start()..query_match.end()
12352 };
12353 let display_range = offset_range.start.to_display_point(&display_map)
12354 ..offset_range.end.to_display_point(&display_map);
12355
12356 if !select_next_state.wordwise
12357 || (!movement::is_inside_word(&display_map, display_range.start)
12358 && !movement::is_inside_word(&display_map, display_range.end))
12359 {
12360 new_selections.push(offset_range.start..offset_range.end);
12361 }
12362 }
12363
12364 select_next_state.done = true;
12365 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12366 self.change_selections(None, window, cx, |selections| {
12367 selections.select_ranges(new_selections)
12368 });
12369
12370 Ok(())
12371 }
12372
12373 pub fn select_next(
12374 &mut self,
12375 action: &SelectNext,
12376 window: &mut Window,
12377 cx: &mut Context<Self>,
12378 ) -> Result<()> {
12379 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12380 self.push_to_selection_history();
12381 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12382 self.select_next_match_internal(
12383 &display_map,
12384 action.replace_newest,
12385 Some(Autoscroll::newest()),
12386 window,
12387 cx,
12388 )?;
12389 Ok(())
12390 }
12391
12392 pub fn select_previous(
12393 &mut self,
12394 action: &SelectPrevious,
12395 window: &mut Window,
12396 cx: &mut Context<Self>,
12397 ) -> Result<()> {
12398 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12399 self.push_to_selection_history();
12400 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12401 let buffer = &display_map.buffer_snapshot;
12402 let mut selections = self.selections.all::<usize>(cx);
12403 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12404 let query = &select_prev_state.query;
12405 if !select_prev_state.done {
12406 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12407 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12408 let mut next_selected_range = None;
12409 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12410 let bytes_before_last_selection =
12411 buffer.reversed_bytes_in_range(0..last_selection.start);
12412 let bytes_after_first_selection =
12413 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12414 let query_matches = query
12415 .stream_find_iter(bytes_before_last_selection)
12416 .map(|result| (last_selection.start, result))
12417 .chain(
12418 query
12419 .stream_find_iter(bytes_after_first_selection)
12420 .map(|result| (buffer.len(), result)),
12421 );
12422 for (end_offset, query_match) in query_matches {
12423 let query_match = query_match.unwrap(); // can only fail due to I/O
12424 let offset_range =
12425 end_offset - query_match.end()..end_offset - query_match.start();
12426 let display_range = offset_range.start.to_display_point(&display_map)
12427 ..offset_range.end.to_display_point(&display_map);
12428
12429 if !select_prev_state.wordwise
12430 || (!movement::is_inside_word(&display_map, display_range.start)
12431 && !movement::is_inside_word(&display_map, display_range.end))
12432 {
12433 next_selected_range = Some(offset_range);
12434 break;
12435 }
12436 }
12437
12438 if let Some(next_selected_range) = next_selected_range {
12439 self.select_match_ranges(
12440 next_selected_range,
12441 last_selection.reversed,
12442 action.replace_newest,
12443 Some(Autoscroll::newest()),
12444 window,
12445 cx,
12446 );
12447 } else {
12448 select_prev_state.done = true;
12449 }
12450 }
12451
12452 self.select_prev_state = Some(select_prev_state);
12453 } else {
12454 let mut only_carets = true;
12455 let mut same_text_selected = true;
12456 let mut selected_text = None;
12457
12458 let mut selections_iter = selections.iter().peekable();
12459 while let Some(selection) = selections_iter.next() {
12460 if selection.start != selection.end {
12461 only_carets = false;
12462 }
12463
12464 if same_text_selected {
12465 if selected_text.is_none() {
12466 selected_text =
12467 Some(buffer.text_for_range(selection.range()).collect::<String>());
12468 }
12469
12470 if let Some(next_selection) = selections_iter.peek() {
12471 if next_selection.range().len() == selection.range().len() {
12472 let next_selected_text = buffer
12473 .text_for_range(next_selection.range())
12474 .collect::<String>();
12475 if Some(next_selected_text) != selected_text {
12476 same_text_selected = false;
12477 selected_text = None;
12478 }
12479 } else {
12480 same_text_selected = false;
12481 selected_text = None;
12482 }
12483 }
12484 }
12485 }
12486
12487 if only_carets {
12488 for selection in &mut selections {
12489 let word_range = movement::surrounding_word(
12490 &display_map,
12491 selection.start.to_display_point(&display_map),
12492 );
12493 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12494 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12495 selection.goal = SelectionGoal::None;
12496 selection.reversed = false;
12497 self.select_match_ranges(
12498 selection.start..selection.end,
12499 selection.reversed,
12500 action.replace_newest,
12501 Some(Autoscroll::newest()),
12502 window,
12503 cx,
12504 );
12505 }
12506 if selections.len() == 1 {
12507 let selection = selections
12508 .last()
12509 .expect("ensured that there's only one selection");
12510 let query = buffer
12511 .text_for_range(selection.start..selection.end)
12512 .collect::<String>();
12513 let is_empty = query.is_empty();
12514 let select_state = SelectNextState {
12515 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12516 wordwise: true,
12517 done: is_empty,
12518 };
12519 self.select_prev_state = Some(select_state);
12520 } else {
12521 self.select_prev_state = None;
12522 }
12523 } else if let Some(selected_text) = selected_text {
12524 self.select_prev_state = Some(SelectNextState {
12525 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12526 wordwise: false,
12527 done: false,
12528 });
12529 self.select_previous(action, window, cx)?;
12530 }
12531 }
12532 Ok(())
12533 }
12534
12535 pub fn find_next_match(
12536 &mut self,
12537 _: &FindNextMatch,
12538 window: &mut Window,
12539 cx: &mut Context<Self>,
12540 ) -> Result<()> {
12541 let selections = self.selections.disjoint_anchors();
12542 match selections.first() {
12543 Some(first) if selections.len() >= 2 => {
12544 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12545 s.select_ranges([first.range()]);
12546 });
12547 }
12548 _ => self.select_next(
12549 &SelectNext {
12550 replace_newest: true,
12551 },
12552 window,
12553 cx,
12554 )?,
12555 }
12556 Ok(())
12557 }
12558
12559 pub fn find_previous_match(
12560 &mut self,
12561 _: &FindPreviousMatch,
12562 window: &mut Window,
12563 cx: &mut Context<Self>,
12564 ) -> Result<()> {
12565 let selections = self.selections.disjoint_anchors();
12566 match selections.last() {
12567 Some(last) if selections.len() >= 2 => {
12568 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12569 s.select_ranges([last.range()]);
12570 });
12571 }
12572 _ => self.select_previous(
12573 &SelectPrevious {
12574 replace_newest: true,
12575 },
12576 window,
12577 cx,
12578 )?,
12579 }
12580 Ok(())
12581 }
12582
12583 pub fn toggle_comments(
12584 &mut self,
12585 action: &ToggleComments,
12586 window: &mut Window,
12587 cx: &mut Context<Self>,
12588 ) {
12589 if self.read_only(cx) {
12590 return;
12591 }
12592 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12593 let text_layout_details = &self.text_layout_details(window);
12594 self.transact(window, cx, |this, window, cx| {
12595 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12596 let mut edits = Vec::new();
12597 let mut selection_edit_ranges = Vec::new();
12598 let mut last_toggled_row = None;
12599 let snapshot = this.buffer.read(cx).read(cx);
12600 let empty_str: Arc<str> = Arc::default();
12601 let mut suffixes_inserted = Vec::new();
12602 let ignore_indent = action.ignore_indent;
12603
12604 fn comment_prefix_range(
12605 snapshot: &MultiBufferSnapshot,
12606 row: MultiBufferRow,
12607 comment_prefix: &str,
12608 comment_prefix_whitespace: &str,
12609 ignore_indent: bool,
12610 ) -> Range<Point> {
12611 let indent_size = if ignore_indent {
12612 0
12613 } else {
12614 snapshot.indent_size_for_line(row).len
12615 };
12616
12617 let start = Point::new(row.0, indent_size);
12618
12619 let mut line_bytes = snapshot
12620 .bytes_in_range(start..snapshot.max_point())
12621 .flatten()
12622 .copied();
12623
12624 // If this line currently begins with the line comment prefix, then record
12625 // the range containing the prefix.
12626 if line_bytes
12627 .by_ref()
12628 .take(comment_prefix.len())
12629 .eq(comment_prefix.bytes())
12630 {
12631 // Include any whitespace that matches the comment prefix.
12632 let matching_whitespace_len = line_bytes
12633 .zip(comment_prefix_whitespace.bytes())
12634 .take_while(|(a, b)| a == b)
12635 .count() as u32;
12636 let end = Point::new(
12637 start.row,
12638 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12639 );
12640 start..end
12641 } else {
12642 start..start
12643 }
12644 }
12645
12646 fn comment_suffix_range(
12647 snapshot: &MultiBufferSnapshot,
12648 row: MultiBufferRow,
12649 comment_suffix: &str,
12650 comment_suffix_has_leading_space: bool,
12651 ) -> Range<Point> {
12652 let end = Point::new(row.0, snapshot.line_len(row));
12653 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12654
12655 let mut line_end_bytes = snapshot
12656 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12657 .flatten()
12658 .copied();
12659
12660 let leading_space_len = if suffix_start_column > 0
12661 && line_end_bytes.next() == Some(b' ')
12662 && comment_suffix_has_leading_space
12663 {
12664 1
12665 } else {
12666 0
12667 };
12668
12669 // If this line currently begins with the line comment prefix, then record
12670 // the range containing the prefix.
12671 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12672 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12673 start..end
12674 } else {
12675 end..end
12676 }
12677 }
12678
12679 // TODO: Handle selections that cross excerpts
12680 for selection in &mut selections {
12681 let start_column = snapshot
12682 .indent_size_for_line(MultiBufferRow(selection.start.row))
12683 .len;
12684 let language = if let Some(language) =
12685 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12686 {
12687 language
12688 } else {
12689 continue;
12690 };
12691
12692 selection_edit_ranges.clear();
12693
12694 // If multiple selections contain a given row, avoid processing that
12695 // row more than once.
12696 let mut start_row = MultiBufferRow(selection.start.row);
12697 if last_toggled_row == Some(start_row) {
12698 start_row = start_row.next_row();
12699 }
12700 let end_row =
12701 if selection.end.row > selection.start.row && selection.end.column == 0 {
12702 MultiBufferRow(selection.end.row - 1)
12703 } else {
12704 MultiBufferRow(selection.end.row)
12705 };
12706 last_toggled_row = Some(end_row);
12707
12708 if start_row > end_row {
12709 continue;
12710 }
12711
12712 // If the language has line comments, toggle those.
12713 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12714
12715 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12716 if ignore_indent {
12717 full_comment_prefixes = full_comment_prefixes
12718 .into_iter()
12719 .map(|s| Arc::from(s.trim_end()))
12720 .collect();
12721 }
12722
12723 if !full_comment_prefixes.is_empty() {
12724 let first_prefix = full_comment_prefixes
12725 .first()
12726 .expect("prefixes is non-empty");
12727 let prefix_trimmed_lengths = full_comment_prefixes
12728 .iter()
12729 .map(|p| p.trim_end_matches(' ').len())
12730 .collect::<SmallVec<[usize; 4]>>();
12731
12732 let mut all_selection_lines_are_comments = true;
12733
12734 for row in start_row.0..=end_row.0 {
12735 let row = MultiBufferRow(row);
12736 if start_row < end_row && snapshot.is_line_blank(row) {
12737 continue;
12738 }
12739
12740 let prefix_range = full_comment_prefixes
12741 .iter()
12742 .zip(prefix_trimmed_lengths.iter().copied())
12743 .map(|(prefix, trimmed_prefix_len)| {
12744 comment_prefix_range(
12745 snapshot.deref(),
12746 row,
12747 &prefix[..trimmed_prefix_len],
12748 &prefix[trimmed_prefix_len..],
12749 ignore_indent,
12750 )
12751 })
12752 .max_by_key(|range| range.end.column - range.start.column)
12753 .expect("prefixes is non-empty");
12754
12755 if prefix_range.is_empty() {
12756 all_selection_lines_are_comments = false;
12757 }
12758
12759 selection_edit_ranges.push(prefix_range);
12760 }
12761
12762 if all_selection_lines_are_comments {
12763 edits.extend(
12764 selection_edit_ranges
12765 .iter()
12766 .cloned()
12767 .map(|range| (range, empty_str.clone())),
12768 );
12769 } else {
12770 let min_column = selection_edit_ranges
12771 .iter()
12772 .map(|range| range.start.column)
12773 .min()
12774 .unwrap_or(0);
12775 edits.extend(selection_edit_ranges.iter().map(|range| {
12776 let position = Point::new(range.start.row, min_column);
12777 (position..position, first_prefix.clone())
12778 }));
12779 }
12780 } else if let Some((full_comment_prefix, comment_suffix)) =
12781 language.block_comment_delimiters()
12782 {
12783 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
12784 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
12785 let prefix_range = comment_prefix_range(
12786 snapshot.deref(),
12787 start_row,
12788 comment_prefix,
12789 comment_prefix_whitespace,
12790 ignore_indent,
12791 );
12792 let suffix_range = comment_suffix_range(
12793 snapshot.deref(),
12794 end_row,
12795 comment_suffix.trim_start_matches(' '),
12796 comment_suffix.starts_with(' '),
12797 );
12798
12799 if prefix_range.is_empty() || suffix_range.is_empty() {
12800 edits.push((
12801 prefix_range.start..prefix_range.start,
12802 full_comment_prefix.clone(),
12803 ));
12804 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
12805 suffixes_inserted.push((end_row, comment_suffix.len()));
12806 } else {
12807 edits.push((prefix_range, empty_str.clone()));
12808 edits.push((suffix_range, empty_str.clone()));
12809 }
12810 } else {
12811 continue;
12812 }
12813 }
12814
12815 drop(snapshot);
12816 this.buffer.update(cx, |buffer, cx| {
12817 buffer.edit(edits, None, cx);
12818 });
12819
12820 // Adjust selections so that they end before any comment suffixes that
12821 // were inserted.
12822 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
12823 let mut selections = this.selections.all::<Point>(cx);
12824 let snapshot = this.buffer.read(cx).read(cx);
12825 for selection in &mut selections {
12826 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
12827 match row.cmp(&MultiBufferRow(selection.end.row)) {
12828 Ordering::Less => {
12829 suffixes_inserted.next();
12830 continue;
12831 }
12832 Ordering::Greater => break,
12833 Ordering::Equal => {
12834 if selection.end.column == snapshot.line_len(row) {
12835 if selection.is_empty() {
12836 selection.start.column -= suffix_len as u32;
12837 }
12838 selection.end.column -= suffix_len as u32;
12839 }
12840 break;
12841 }
12842 }
12843 }
12844 }
12845
12846 drop(snapshot);
12847 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12848 s.select(selections)
12849 });
12850
12851 let selections = this.selections.all::<Point>(cx);
12852 let selections_on_single_row = selections.windows(2).all(|selections| {
12853 selections[0].start.row == selections[1].start.row
12854 && selections[0].end.row == selections[1].end.row
12855 && selections[0].start.row == selections[0].end.row
12856 });
12857 let selections_selecting = selections
12858 .iter()
12859 .any(|selection| selection.start != selection.end);
12860 let advance_downwards = action.advance_downwards
12861 && selections_on_single_row
12862 && !selections_selecting
12863 && !matches!(this.mode, EditorMode::SingleLine { .. });
12864
12865 if advance_downwards {
12866 let snapshot = this.buffer.read(cx).snapshot(cx);
12867
12868 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12869 s.move_cursors_with(|display_snapshot, display_point, _| {
12870 let mut point = display_point.to_point(display_snapshot);
12871 point.row += 1;
12872 point = snapshot.clip_point(point, Bias::Left);
12873 let display_point = point.to_display_point(display_snapshot);
12874 let goal = SelectionGoal::HorizontalPosition(
12875 display_snapshot
12876 .x_for_display_point(display_point, text_layout_details)
12877 .into(),
12878 );
12879 (display_point, goal)
12880 })
12881 });
12882 }
12883 });
12884 }
12885
12886 pub fn select_enclosing_symbol(
12887 &mut self,
12888 _: &SelectEnclosingSymbol,
12889 window: &mut Window,
12890 cx: &mut Context<Self>,
12891 ) {
12892 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12893
12894 let buffer = self.buffer.read(cx).snapshot(cx);
12895 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
12896
12897 fn update_selection(
12898 selection: &Selection<usize>,
12899 buffer_snap: &MultiBufferSnapshot,
12900 ) -> Option<Selection<usize>> {
12901 let cursor = selection.head();
12902 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
12903 for symbol in symbols.iter().rev() {
12904 let start = symbol.range.start.to_offset(buffer_snap);
12905 let end = symbol.range.end.to_offset(buffer_snap);
12906 let new_range = start..end;
12907 if start < selection.start || end > selection.end {
12908 return Some(Selection {
12909 id: selection.id,
12910 start: new_range.start,
12911 end: new_range.end,
12912 goal: SelectionGoal::None,
12913 reversed: selection.reversed,
12914 });
12915 }
12916 }
12917 None
12918 }
12919
12920 let mut selected_larger_symbol = false;
12921 let new_selections = old_selections
12922 .iter()
12923 .map(|selection| match update_selection(selection, &buffer) {
12924 Some(new_selection) => {
12925 if new_selection.range() != selection.range() {
12926 selected_larger_symbol = true;
12927 }
12928 new_selection
12929 }
12930 None => selection.clone(),
12931 })
12932 .collect::<Vec<_>>();
12933
12934 if selected_larger_symbol {
12935 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12936 s.select(new_selections);
12937 });
12938 }
12939 }
12940
12941 pub fn select_larger_syntax_node(
12942 &mut self,
12943 _: &SelectLargerSyntaxNode,
12944 window: &mut Window,
12945 cx: &mut Context<Self>,
12946 ) {
12947 let Some(visible_row_count) = self.visible_row_count() else {
12948 return;
12949 };
12950 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
12951 if old_selections.is_empty() {
12952 return;
12953 }
12954
12955 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12956
12957 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12958 let buffer = self.buffer.read(cx).snapshot(cx);
12959
12960 let mut selected_larger_node = false;
12961 let mut new_selections = old_selections
12962 .iter()
12963 .map(|selection| {
12964 let old_range = selection.start..selection.end;
12965
12966 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
12967 // manually select word at selection
12968 if ["string_content", "inline"].contains(&node.kind()) {
12969 let word_range = {
12970 let display_point = buffer
12971 .offset_to_point(old_range.start)
12972 .to_display_point(&display_map);
12973 let Range { start, end } =
12974 movement::surrounding_word(&display_map, display_point);
12975 start.to_point(&display_map).to_offset(&buffer)
12976 ..end.to_point(&display_map).to_offset(&buffer)
12977 };
12978 // ignore if word is already selected
12979 if !word_range.is_empty() && old_range != word_range {
12980 let last_word_range = {
12981 let display_point = buffer
12982 .offset_to_point(old_range.end)
12983 .to_display_point(&display_map);
12984 let Range { start, end } =
12985 movement::surrounding_word(&display_map, display_point);
12986 start.to_point(&display_map).to_offset(&buffer)
12987 ..end.to_point(&display_map).to_offset(&buffer)
12988 };
12989 // only select word if start and end point belongs to same word
12990 if word_range == last_word_range {
12991 selected_larger_node = true;
12992 return Selection {
12993 id: selection.id,
12994 start: word_range.start,
12995 end: word_range.end,
12996 goal: SelectionGoal::None,
12997 reversed: selection.reversed,
12998 };
12999 }
13000 }
13001 }
13002 }
13003
13004 let mut new_range = old_range.clone();
13005 while let Some((_node, containing_range)) =
13006 buffer.syntax_ancestor(new_range.clone())
13007 {
13008 new_range = match containing_range {
13009 MultiOrSingleBufferOffsetRange::Single(_) => break,
13010 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13011 };
13012 if !display_map.intersects_fold(new_range.start)
13013 && !display_map.intersects_fold(new_range.end)
13014 {
13015 break;
13016 }
13017 }
13018
13019 selected_larger_node |= new_range != old_range;
13020 Selection {
13021 id: selection.id,
13022 start: new_range.start,
13023 end: new_range.end,
13024 goal: SelectionGoal::None,
13025 reversed: selection.reversed,
13026 }
13027 })
13028 .collect::<Vec<_>>();
13029
13030 if !selected_larger_node {
13031 return; // don't put this call in the history
13032 }
13033
13034 // scroll based on transformation done to the last selection created by the user
13035 let (last_old, last_new) = old_selections
13036 .last()
13037 .zip(new_selections.last().cloned())
13038 .expect("old_selections isn't empty");
13039
13040 // revert selection
13041 let is_selection_reversed = {
13042 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13043 new_selections.last_mut().expect("checked above").reversed =
13044 should_newest_selection_be_reversed;
13045 should_newest_selection_be_reversed
13046 };
13047
13048 if selected_larger_node {
13049 self.select_syntax_node_history.disable_clearing = true;
13050 self.change_selections(None, window, cx, |s| {
13051 s.select(new_selections.clone());
13052 });
13053 self.select_syntax_node_history.disable_clearing = false;
13054 }
13055
13056 let start_row = last_new.start.to_display_point(&display_map).row().0;
13057 let end_row = last_new.end.to_display_point(&display_map).row().0;
13058 let selection_height = end_row - start_row + 1;
13059 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13060
13061 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13062 let scroll_behavior = if fits_on_the_screen {
13063 self.request_autoscroll(Autoscroll::fit(), cx);
13064 SelectSyntaxNodeScrollBehavior::FitSelection
13065 } else if is_selection_reversed {
13066 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13067 SelectSyntaxNodeScrollBehavior::CursorTop
13068 } else {
13069 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13070 SelectSyntaxNodeScrollBehavior::CursorBottom
13071 };
13072
13073 self.select_syntax_node_history.push((
13074 old_selections,
13075 scroll_behavior,
13076 is_selection_reversed,
13077 ));
13078 }
13079
13080 pub fn select_smaller_syntax_node(
13081 &mut self,
13082 _: &SelectSmallerSyntaxNode,
13083 window: &mut Window,
13084 cx: &mut Context<Self>,
13085 ) {
13086 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13087
13088 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13089 self.select_syntax_node_history.pop()
13090 {
13091 if let Some(selection) = selections.last_mut() {
13092 selection.reversed = is_selection_reversed;
13093 }
13094
13095 self.select_syntax_node_history.disable_clearing = true;
13096 self.change_selections(None, window, cx, |s| {
13097 s.select(selections.to_vec());
13098 });
13099 self.select_syntax_node_history.disable_clearing = false;
13100
13101 match scroll_behavior {
13102 SelectSyntaxNodeScrollBehavior::CursorTop => {
13103 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13104 }
13105 SelectSyntaxNodeScrollBehavior::FitSelection => {
13106 self.request_autoscroll(Autoscroll::fit(), cx);
13107 }
13108 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13109 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13110 }
13111 }
13112 }
13113 }
13114
13115 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13116 if !EditorSettings::get_global(cx).gutter.runnables {
13117 self.clear_tasks();
13118 return Task::ready(());
13119 }
13120 let project = self.project.as_ref().map(Entity::downgrade);
13121 let task_sources = self.lsp_task_sources(cx);
13122 cx.spawn_in(window, async move |editor, cx| {
13123 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13124 let Some(project) = project.and_then(|p| p.upgrade()) else {
13125 return;
13126 };
13127 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13128 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13129 }) else {
13130 return;
13131 };
13132
13133 let hide_runnables = project
13134 .update(cx, |project, cx| {
13135 // Do not display any test indicators in non-dev server remote projects.
13136 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13137 })
13138 .unwrap_or(true);
13139 if hide_runnables {
13140 return;
13141 }
13142 let new_rows =
13143 cx.background_spawn({
13144 let snapshot = display_snapshot.clone();
13145 async move {
13146 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13147 }
13148 })
13149 .await;
13150 let Ok(lsp_tasks) =
13151 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13152 else {
13153 return;
13154 };
13155 let lsp_tasks = lsp_tasks.await;
13156
13157 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13158 lsp_tasks
13159 .into_iter()
13160 .flat_map(|(kind, tasks)| {
13161 tasks.into_iter().filter_map(move |(location, task)| {
13162 Some((kind.clone(), location?, task))
13163 })
13164 })
13165 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13166 let buffer = location.target.buffer;
13167 let buffer_snapshot = buffer.read(cx).snapshot();
13168 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13169 |(excerpt_id, snapshot, _)| {
13170 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13171 display_snapshot
13172 .buffer_snapshot
13173 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13174 } else {
13175 None
13176 }
13177 },
13178 );
13179 if let Some(offset) = offset {
13180 let task_buffer_range =
13181 location.target.range.to_point(&buffer_snapshot);
13182 let context_buffer_range =
13183 task_buffer_range.to_offset(&buffer_snapshot);
13184 let context_range = BufferOffset(context_buffer_range.start)
13185 ..BufferOffset(context_buffer_range.end);
13186
13187 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13188 .or_insert_with(|| RunnableTasks {
13189 templates: Vec::new(),
13190 offset,
13191 column: task_buffer_range.start.column,
13192 extra_variables: HashMap::default(),
13193 context_range,
13194 })
13195 .templates
13196 .push((kind, task.original_task().clone()));
13197 }
13198
13199 acc
13200 })
13201 }) else {
13202 return;
13203 };
13204
13205 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
13206 editor
13207 .update(cx, |editor, _| {
13208 editor.clear_tasks();
13209 for (key, mut value) in rows {
13210 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13211 value.templates.extend(lsp_tasks.templates);
13212 }
13213
13214 editor.insert_tasks(key, value);
13215 }
13216 for (key, value) in lsp_tasks_by_rows {
13217 editor.insert_tasks(key, value);
13218 }
13219 })
13220 .ok();
13221 })
13222 }
13223 fn fetch_runnable_ranges(
13224 snapshot: &DisplaySnapshot,
13225 range: Range<Anchor>,
13226 ) -> Vec<language::RunnableRange> {
13227 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13228 }
13229
13230 fn runnable_rows(
13231 project: Entity<Project>,
13232 snapshot: DisplaySnapshot,
13233 runnable_ranges: Vec<RunnableRange>,
13234 mut cx: AsyncWindowContext,
13235 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13236 runnable_ranges
13237 .into_iter()
13238 .filter_map(|mut runnable| {
13239 let tasks = cx
13240 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13241 .ok()?;
13242 if tasks.is_empty() {
13243 return None;
13244 }
13245
13246 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13247
13248 let row = snapshot
13249 .buffer_snapshot
13250 .buffer_line_for_row(MultiBufferRow(point.row))?
13251 .1
13252 .start
13253 .row;
13254
13255 let context_range =
13256 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13257 Some((
13258 (runnable.buffer_id, row),
13259 RunnableTasks {
13260 templates: tasks,
13261 offset: snapshot
13262 .buffer_snapshot
13263 .anchor_before(runnable.run_range.start),
13264 context_range,
13265 column: point.column,
13266 extra_variables: runnable.extra_captures,
13267 },
13268 ))
13269 })
13270 .collect()
13271 }
13272
13273 fn templates_with_tags(
13274 project: &Entity<Project>,
13275 runnable: &mut Runnable,
13276 cx: &mut App,
13277 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13278 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13279 let (worktree_id, file) = project
13280 .buffer_for_id(runnable.buffer, cx)
13281 .and_then(|buffer| buffer.read(cx).file())
13282 .map(|file| (file.worktree_id(cx), file.clone()))
13283 .unzip();
13284
13285 (
13286 project.task_store().read(cx).task_inventory().cloned(),
13287 worktree_id,
13288 file,
13289 )
13290 });
13291
13292 let mut templates_with_tags = mem::take(&mut runnable.tags)
13293 .into_iter()
13294 .flat_map(|RunnableTag(tag)| {
13295 inventory
13296 .as_ref()
13297 .into_iter()
13298 .flat_map(|inventory| {
13299 inventory.read(cx).list_tasks(
13300 file.clone(),
13301 Some(runnable.language.clone()),
13302 worktree_id,
13303 cx,
13304 )
13305 })
13306 .filter(move |(_, template)| {
13307 template.tags.iter().any(|source_tag| source_tag == &tag)
13308 })
13309 })
13310 .sorted_by_key(|(kind, _)| kind.to_owned())
13311 .collect::<Vec<_>>();
13312 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13313 // Strongest source wins; if we have worktree tag binding, prefer that to
13314 // global and language bindings;
13315 // if we have a global binding, prefer that to language binding.
13316 let first_mismatch = templates_with_tags
13317 .iter()
13318 .position(|(tag_source, _)| tag_source != leading_tag_source);
13319 if let Some(index) = first_mismatch {
13320 templates_with_tags.truncate(index);
13321 }
13322 }
13323
13324 templates_with_tags
13325 }
13326
13327 pub fn move_to_enclosing_bracket(
13328 &mut self,
13329 _: &MoveToEnclosingBracket,
13330 window: &mut Window,
13331 cx: &mut Context<Self>,
13332 ) {
13333 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13334 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13335 s.move_offsets_with(|snapshot, selection| {
13336 let Some(enclosing_bracket_ranges) =
13337 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13338 else {
13339 return;
13340 };
13341
13342 let mut best_length = usize::MAX;
13343 let mut best_inside = false;
13344 let mut best_in_bracket_range = false;
13345 let mut best_destination = None;
13346 for (open, close) in enclosing_bracket_ranges {
13347 let close = close.to_inclusive();
13348 let length = close.end() - open.start;
13349 let inside = selection.start >= open.end && selection.end <= *close.start();
13350 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13351 || close.contains(&selection.head());
13352
13353 // If best is next to a bracket and current isn't, skip
13354 if !in_bracket_range && best_in_bracket_range {
13355 continue;
13356 }
13357
13358 // Prefer smaller lengths unless best is inside and current isn't
13359 if length > best_length && (best_inside || !inside) {
13360 continue;
13361 }
13362
13363 best_length = length;
13364 best_inside = inside;
13365 best_in_bracket_range = in_bracket_range;
13366 best_destination = Some(
13367 if close.contains(&selection.start) && close.contains(&selection.end) {
13368 if inside { open.end } else { open.start }
13369 } else if inside {
13370 *close.start()
13371 } else {
13372 *close.end()
13373 },
13374 );
13375 }
13376
13377 if let Some(destination) = best_destination {
13378 selection.collapse_to(destination, SelectionGoal::None);
13379 }
13380 })
13381 });
13382 }
13383
13384 pub fn undo_selection(
13385 &mut self,
13386 _: &UndoSelection,
13387 window: &mut Window,
13388 cx: &mut Context<Self>,
13389 ) {
13390 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13391 self.end_selection(window, cx);
13392 self.selection_history.mode = SelectionHistoryMode::Undoing;
13393 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13394 self.change_selections(None, window, cx, |s| {
13395 s.select_anchors(entry.selections.to_vec())
13396 });
13397 self.select_next_state = entry.select_next_state;
13398 self.select_prev_state = entry.select_prev_state;
13399 self.add_selections_state = entry.add_selections_state;
13400 self.request_autoscroll(Autoscroll::newest(), cx);
13401 }
13402 self.selection_history.mode = SelectionHistoryMode::Normal;
13403 }
13404
13405 pub fn redo_selection(
13406 &mut self,
13407 _: &RedoSelection,
13408 window: &mut Window,
13409 cx: &mut Context<Self>,
13410 ) {
13411 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13412 self.end_selection(window, cx);
13413 self.selection_history.mode = SelectionHistoryMode::Redoing;
13414 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13415 self.change_selections(None, window, cx, |s| {
13416 s.select_anchors(entry.selections.to_vec())
13417 });
13418 self.select_next_state = entry.select_next_state;
13419 self.select_prev_state = entry.select_prev_state;
13420 self.add_selections_state = entry.add_selections_state;
13421 self.request_autoscroll(Autoscroll::newest(), cx);
13422 }
13423 self.selection_history.mode = SelectionHistoryMode::Normal;
13424 }
13425
13426 pub fn expand_excerpts(
13427 &mut self,
13428 action: &ExpandExcerpts,
13429 _: &mut Window,
13430 cx: &mut Context<Self>,
13431 ) {
13432 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13433 }
13434
13435 pub fn expand_excerpts_down(
13436 &mut self,
13437 action: &ExpandExcerptsDown,
13438 _: &mut Window,
13439 cx: &mut Context<Self>,
13440 ) {
13441 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13442 }
13443
13444 pub fn expand_excerpts_up(
13445 &mut self,
13446 action: &ExpandExcerptsUp,
13447 _: &mut Window,
13448 cx: &mut Context<Self>,
13449 ) {
13450 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13451 }
13452
13453 pub fn expand_excerpts_for_direction(
13454 &mut self,
13455 lines: u32,
13456 direction: ExpandExcerptDirection,
13457
13458 cx: &mut Context<Self>,
13459 ) {
13460 let selections = self.selections.disjoint_anchors();
13461
13462 let lines = if lines == 0 {
13463 EditorSettings::get_global(cx).expand_excerpt_lines
13464 } else {
13465 lines
13466 };
13467
13468 self.buffer.update(cx, |buffer, cx| {
13469 let snapshot = buffer.snapshot(cx);
13470 let mut excerpt_ids = selections
13471 .iter()
13472 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13473 .collect::<Vec<_>>();
13474 excerpt_ids.sort();
13475 excerpt_ids.dedup();
13476 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13477 })
13478 }
13479
13480 pub fn expand_excerpt(
13481 &mut self,
13482 excerpt: ExcerptId,
13483 direction: ExpandExcerptDirection,
13484 window: &mut Window,
13485 cx: &mut Context<Self>,
13486 ) {
13487 let current_scroll_position = self.scroll_position(cx);
13488 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13489 let mut should_scroll_up = false;
13490
13491 if direction == ExpandExcerptDirection::Down {
13492 let multi_buffer = self.buffer.read(cx);
13493 let snapshot = multi_buffer.snapshot(cx);
13494 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13495 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13496 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13497 let buffer_snapshot = buffer.read(cx).snapshot();
13498 let excerpt_end_row =
13499 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13500 let last_row = buffer_snapshot.max_point().row;
13501 let lines_below = last_row.saturating_sub(excerpt_end_row);
13502 should_scroll_up = lines_below >= lines_to_expand;
13503 }
13504 }
13505 }
13506 }
13507
13508 self.buffer.update(cx, |buffer, cx| {
13509 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13510 });
13511
13512 if should_scroll_up {
13513 let new_scroll_position =
13514 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13515 self.set_scroll_position(new_scroll_position, window, cx);
13516 }
13517 }
13518
13519 pub fn go_to_singleton_buffer_point(
13520 &mut self,
13521 point: Point,
13522 window: &mut Window,
13523 cx: &mut Context<Self>,
13524 ) {
13525 self.go_to_singleton_buffer_range(point..point, window, cx);
13526 }
13527
13528 pub fn go_to_singleton_buffer_range(
13529 &mut self,
13530 range: Range<Point>,
13531 window: &mut Window,
13532 cx: &mut Context<Self>,
13533 ) {
13534 let multibuffer = self.buffer().read(cx);
13535 let Some(buffer) = multibuffer.as_singleton() else {
13536 return;
13537 };
13538 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13539 return;
13540 };
13541 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13542 return;
13543 };
13544 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13545 s.select_anchor_ranges([start..end])
13546 });
13547 }
13548
13549 pub fn go_to_diagnostic(
13550 &mut self,
13551 _: &GoToDiagnostic,
13552 window: &mut Window,
13553 cx: &mut Context<Self>,
13554 ) {
13555 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13556 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13557 }
13558
13559 pub fn go_to_prev_diagnostic(
13560 &mut self,
13561 _: &GoToPreviousDiagnostic,
13562 window: &mut Window,
13563 cx: &mut Context<Self>,
13564 ) {
13565 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13566 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13567 }
13568
13569 pub fn go_to_diagnostic_impl(
13570 &mut self,
13571 direction: Direction,
13572 window: &mut Window,
13573 cx: &mut Context<Self>,
13574 ) {
13575 let buffer = self.buffer.read(cx).snapshot(cx);
13576 let selection = self.selections.newest::<usize>(cx);
13577
13578 let mut active_group_id = None;
13579 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13580 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13581 active_group_id = Some(active_group.group_id);
13582 }
13583 }
13584
13585 fn filtered(
13586 snapshot: EditorSnapshot,
13587 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13588 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13589 diagnostics
13590 .filter(|entry| entry.range.start != entry.range.end)
13591 .filter(|entry| !entry.diagnostic.is_unnecessary)
13592 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13593 }
13594
13595 let snapshot = self.snapshot(window, cx);
13596 let before = filtered(
13597 snapshot.clone(),
13598 buffer
13599 .diagnostics_in_range(0..selection.start)
13600 .filter(|entry| entry.range.start <= selection.start),
13601 );
13602 let after = filtered(
13603 snapshot,
13604 buffer
13605 .diagnostics_in_range(selection.start..buffer.len())
13606 .filter(|entry| entry.range.start >= selection.start),
13607 );
13608
13609 let mut found: Option<DiagnosticEntry<usize>> = None;
13610 if direction == Direction::Prev {
13611 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13612 {
13613 for diagnostic in prev_diagnostics.into_iter().rev() {
13614 if diagnostic.range.start != selection.start
13615 || active_group_id
13616 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13617 {
13618 found = Some(diagnostic);
13619 break 'outer;
13620 }
13621 }
13622 }
13623 } else {
13624 for diagnostic in after.chain(before) {
13625 if diagnostic.range.start != selection.start
13626 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13627 {
13628 found = Some(diagnostic);
13629 break;
13630 }
13631 }
13632 }
13633 let Some(next_diagnostic) = found else {
13634 return;
13635 };
13636
13637 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13638 return;
13639 };
13640 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13641 s.select_ranges(vec![
13642 next_diagnostic.range.start..next_diagnostic.range.start,
13643 ])
13644 });
13645 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13646 self.refresh_inline_completion(false, true, window, cx);
13647 }
13648
13649 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13650 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13651 let snapshot = self.snapshot(window, cx);
13652 let selection = self.selections.newest::<Point>(cx);
13653 self.go_to_hunk_before_or_after_position(
13654 &snapshot,
13655 selection.head(),
13656 Direction::Next,
13657 window,
13658 cx,
13659 );
13660 }
13661
13662 pub fn go_to_hunk_before_or_after_position(
13663 &mut self,
13664 snapshot: &EditorSnapshot,
13665 position: Point,
13666 direction: Direction,
13667 window: &mut Window,
13668 cx: &mut Context<Editor>,
13669 ) {
13670 let row = if direction == Direction::Next {
13671 self.hunk_after_position(snapshot, position)
13672 .map(|hunk| hunk.row_range.start)
13673 } else {
13674 self.hunk_before_position(snapshot, position)
13675 };
13676
13677 if let Some(row) = row {
13678 let destination = Point::new(row.0, 0);
13679 let autoscroll = Autoscroll::center();
13680
13681 self.unfold_ranges(&[destination..destination], false, false, cx);
13682 self.change_selections(Some(autoscroll), window, cx, |s| {
13683 s.select_ranges([destination..destination]);
13684 });
13685 }
13686 }
13687
13688 fn hunk_after_position(
13689 &mut self,
13690 snapshot: &EditorSnapshot,
13691 position: Point,
13692 ) -> Option<MultiBufferDiffHunk> {
13693 snapshot
13694 .buffer_snapshot
13695 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13696 .find(|hunk| hunk.row_range.start.0 > position.row)
13697 .or_else(|| {
13698 snapshot
13699 .buffer_snapshot
13700 .diff_hunks_in_range(Point::zero()..position)
13701 .find(|hunk| hunk.row_range.end.0 < position.row)
13702 })
13703 }
13704
13705 fn go_to_prev_hunk(
13706 &mut self,
13707 _: &GoToPreviousHunk,
13708 window: &mut Window,
13709 cx: &mut Context<Self>,
13710 ) {
13711 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13712 let snapshot = self.snapshot(window, cx);
13713 let selection = self.selections.newest::<Point>(cx);
13714 self.go_to_hunk_before_or_after_position(
13715 &snapshot,
13716 selection.head(),
13717 Direction::Prev,
13718 window,
13719 cx,
13720 );
13721 }
13722
13723 fn hunk_before_position(
13724 &mut self,
13725 snapshot: &EditorSnapshot,
13726 position: Point,
13727 ) -> Option<MultiBufferRow> {
13728 snapshot
13729 .buffer_snapshot
13730 .diff_hunk_before(position)
13731 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
13732 }
13733
13734 fn go_to_next_change(
13735 &mut self,
13736 _: &GoToNextChange,
13737 window: &mut Window,
13738 cx: &mut Context<Self>,
13739 ) {
13740 if let Some(selections) = self
13741 .change_list
13742 .next_change(1, Direction::Next)
13743 .map(|s| s.to_vec())
13744 {
13745 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13746 let map = s.display_map();
13747 s.select_display_ranges(selections.iter().map(|a| {
13748 let point = a.to_display_point(&map);
13749 point..point
13750 }))
13751 })
13752 }
13753 }
13754
13755 fn go_to_previous_change(
13756 &mut self,
13757 _: &GoToPreviousChange,
13758 window: &mut Window,
13759 cx: &mut Context<Self>,
13760 ) {
13761 if let Some(selections) = self
13762 .change_list
13763 .next_change(1, Direction::Prev)
13764 .map(|s| s.to_vec())
13765 {
13766 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13767 let map = s.display_map();
13768 s.select_display_ranges(selections.iter().map(|a| {
13769 let point = a.to_display_point(&map);
13770 point..point
13771 }))
13772 })
13773 }
13774 }
13775
13776 fn go_to_line<T: 'static>(
13777 &mut self,
13778 position: Anchor,
13779 highlight_color: Option<Hsla>,
13780 window: &mut Window,
13781 cx: &mut Context<Self>,
13782 ) {
13783 let snapshot = self.snapshot(window, cx).display_snapshot;
13784 let position = position.to_point(&snapshot.buffer_snapshot);
13785 let start = snapshot
13786 .buffer_snapshot
13787 .clip_point(Point::new(position.row, 0), Bias::Left);
13788 let end = start + Point::new(1, 0);
13789 let start = snapshot.buffer_snapshot.anchor_before(start);
13790 let end = snapshot.buffer_snapshot.anchor_before(end);
13791
13792 self.highlight_rows::<T>(
13793 start..end,
13794 highlight_color
13795 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
13796 Default::default(),
13797 cx,
13798 );
13799 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
13800 }
13801
13802 pub fn go_to_definition(
13803 &mut self,
13804 _: &GoToDefinition,
13805 window: &mut Window,
13806 cx: &mut Context<Self>,
13807 ) -> Task<Result<Navigated>> {
13808 let definition =
13809 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
13810 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
13811 cx.spawn_in(window, async move |editor, cx| {
13812 if definition.await? == Navigated::Yes {
13813 return Ok(Navigated::Yes);
13814 }
13815 match fallback_strategy {
13816 GoToDefinitionFallback::None => Ok(Navigated::No),
13817 GoToDefinitionFallback::FindAllReferences => {
13818 match editor.update_in(cx, |editor, window, cx| {
13819 editor.find_all_references(&FindAllReferences, window, cx)
13820 })? {
13821 Some(references) => references.await,
13822 None => Ok(Navigated::No),
13823 }
13824 }
13825 }
13826 })
13827 }
13828
13829 pub fn go_to_declaration(
13830 &mut self,
13831 _: &GoToDeclaration,
13832 window: &mut Window,
13833 cx: &mut Context<Self>,
13834 ) -> Task<Result<Navigated>> {
13835 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
13836 }
13837
13838 pub fn go_to_declaration_split(
13839 &mut self,
13840 _: &GoToDeclaration,
13841 window: &mut Window,
13842 cx: &mut Context<Self>,
13843 ) -> Task<Result<Navigated>> {
13844 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
13845 }
13846
13847 pub fn go_to_implementation(
13848 &mut self,
13849 _: &GoToImplementation,
13850 window: &mut Window,
13851 cx: &mut Context<Self>,
13852 ) -> Task<Result<Navigated>> {
13853 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
13854 }
13855
13856 pub fn go_to_implementation_split(
13857 &mut self,
13858 _: &GoToImplementationSplit,
13859 window: &mut Window,
13860 cx: &mut Context<Self>,
13861 ) -> Task<Result<Navigated>> {
13862 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
13863 }
13864
13865 pub fn go_to_type_definition(
13866 &mut self,
13867 _: &GoToTypeDefinition,
13868 window: &mut Window,
13869 cx: &mut Context<Self>,
13870 ) -> Task<Result<Navigated>> {
13871 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
13872 }
13873
13874 pub fn go_to_definition_split(
13875 &mut self,
13876 _: &GoToDefinitionSplit,
13877 window: &mut Window,
13878 cx: &mut Context<Self>,
13879 ) -> Task<Result<Navigated>> {
13880 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
13881 }
13882
13883 pub fn go_to_type_definition_split(
13884 &mut self,
13885 _: &GoToTypeDefinitionSplit,
13886 window: &mut Window,
13887 cx: &mut Context<Self>,
13888 ) -> Task<Result<Navigated>> {
13889 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
13890 }
13891
13892 fn go_to_definition_of_kind(
13893 &mut self,
13894 kind: GotoDefinitionKind,
13895 split: bool,
13896 window: &mut Window,
13897 cx: &mut Context<Self>,
13898 ) -> Task<Result<Navigated>> {
13899 let Some(provider) = self.semantics_provider.clone() else {
13900 return Task::ready(Ok(Navigated::No));
13901 };
13902 let head = self.selections.newest::<usize>(cx).head();
13903 let buffer = self.buffer.read(cx);
13904 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
13905 text_anchor
13906 } else {
13907 return Task::ready(Ok(Navigated::No));
13908 };
13909
13910 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
13911 return Task::ready(Ok(Navigated::No));
13912 };
13913
13914 cx.spawn_in(window, async move |editor, cx| {
13915 let definitions = definitions.await?;
13916 let navigated = editor
13917 .update_in(cx, |editor, window, cx| {
13918 editor.navigate_to_hover_links(
13919 Some(kind),
13920 definitions
13921 .into_iter()
13922 .filter(|location| {
13923 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
13924 })
13925 .map(HoverLink::Text)
13926 .collect::<Vec<_>>(),
13927 split,
13928 window,
13929 cx,
13930 )
13931 })?
13932 .await?;
13933 anyhow::Ok(navigated)
13934 })
13935 }
13936
13937 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
13938 let selection = self.selections.newest_anchor();
13939 let head = selection.head();
13940 let tail = selection.tail();
13941
13942 let Some((buffer, start_position)) =
13943 self.buffer.read(cx).text_anchor_for_position(head, cx)
13944 else {
13945 return;
13946 };
13947
13948 let end_position = if head != tail {
13949 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
13950 return;
13951 };
13952 Some(pos)
13953 } else {
13954 None
13955 };
13956
13957 let url_finder = cx.spawn_in(window, async move |editor, cx| {
13958 let url = if let Some(end_pos) = end_position {
13959 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
13960 } else {
13961 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
13962 };
13963
13964 if let Some(url) = url {
13965 editor.update(cx, |_, cx| {
13966 cx.open_url(&url);
13967 })
13968 } else {
13969 Ok(())
13970 }
13971 });
13972
13973 url_finder.detach();
13974 }
13975
13976 pub fn open_selected_filename(
13977 &mut self,
13978 _: &OpenSelectedFilename,
13979 window: &mut Window,
13980 cx: &mut Context<Self>,
13981 ) {
13982 let Some(workspace) = self.workspace() else {
13983 return;
13984 };
13985
13986 let position = self.selections.newest_anchor().head();
13987
13988 let Some((buffer, buffer_position)) =
13989 self.buffer.read(cx).text_anchor_for_position(position, cx)
13990 else {
13991 return;
13992 };
13993
13994 let project = self.project.clone();
13995
13996 cx.spawn_in(window, async move |_, cx| {
13997 let result = find_file(&buffer, project, buffer_position, cx).await;
13998
13999 if let Some((_, path)) = result {
14000 workspace
14001 .update_in(cx, |workspace, window, cx| {
14002 workspace.open_resolved_path(path, window, cx)
14003 })?
14004 .await?;
14005 }
14006 anyhow::Ok(())
14007 })
14008 .detach();
14009 }
14010
14011 pub(crate) fn navigate_to_hover_links(
14012 &mut self,
14013 kind: Option<GotoDefinitionKind>,
14014 mut definitions: Vec<HoverLink>,
14015 split: bool,
14016 window: &mut Window,
14017 cx: &mut Context<Editor>,
14018 ) -> Task<Result<Navigated>> {
14019 // If there is one definition, just open it directly
14020 if definitions.len() == 1 {
14021 let definition = definitions.pop().unwrap();
14022
14023 enum TargetTaskResult {
14024 Location(Option<Location>),
14025 AlreadyNavigated,
14026 }
14027
14028 let target_task = match definition {
14029 HoverLink::Text(link) => {
14030 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14031 }
14032 HoverLink::InlayHint(lsp_location, server_id) => {
14033 let computation =
14034 self.compute_target_location(lsp_location, server_id, window, cx);
14035 cx.background_spawn(async move {
14036 let location = computation.await?;
14037 Ok(TargetTaskResult::Location(location))
14038 })
14039 }
14040 HoverLink::Url(url) => {
14041 cx.open_url(&url);
14042 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14043 }
14044 HoverLink::File(path) => {
14045 if let Some(workspace) = self.workspace() {
14046 cx.spawn_in(window, async move |_, cx| {
14047 workspace
14048 .update_in(cx, |workspace, window, cx| {
14049 workspace.open_resolved_path(path, window, cx)
14050 })?
14051 .await
14052 .map(|_| TargetTaskResult::AlreadyNavigated)
14053 })
14054 } else {
14055 Task::ready(Ok(TargetTaskResult::Location(None)))
14056 }
14057 }
14058 };
14059 cx.spawn_in(window, async move |editor, cx| {
14060 let target = match target_task.await.context("target resolution task")? {
14061 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14062 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14063 TargetTaskResult::Location(Some(target)) => target,
14064 };
14065
14066 editor.update_in(cx, |editor, window, cx| {
14067 let Some(workspace) = editor.workspace() else {
14068 return Navigated::No;
14069 };
14070 let pane = workspace.read(cx).active_pane().clone();
14071
14072 let range = target.range.to_point(target.buffer.read(cx));
14073 let range = editor.range_for_match(&range);
14074 let range = collapse_multiline_range(range);
14075
14076 if !split
14077 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14078 {
14079 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14080 } else {
14081 window.defer(cx, move |window, cx| {
14082 let target_editor: Entity<Self> =
14083 workspace.update(cx, |workspace, cx| {
14084 let pane = if split {
14085 workspace.adjacent_pane(window, cx)
14086 } else {
14087 workspace.active_pane().clone()
14088 };
14089
14090 workspace.open_project_item(
14091 pane,
14092 target.buffer.clone(),
14093 true,
14094 true,
14095 window,
14096 cx,
14097 )
14098 });
14099 target_editor.update(cx, |target_editor, cx| {
14100 // When selecting a definition in a different buffer, disable the nav history
14101 // to avoid creating a history entry at the previous cursor location.
14102 pane.update(cx, |pane, _| pane.disable_history());
14103 target_editor.go_to_singleton_buffer_range(range, window, cx);
14104 pane.update(cx, |pane, _| pane.enable_history());
14105 });
14106 });
14107 }
14108 Navigated::Yes
14109 })
14110 })
14111 } else if !definitions.is_empty() {
14112 cx.spawn_in(window, async move |editor, cx| {
14113 let (title, location_tasks, workspace) = editor
14114 .update_in(cx, |editor, window, cx| {
14115 let tab_kind = match kind {
14116 Some(GotoDefinitionKind::Implementation) => "Implementations",
14117 _ => "Definitions",
14118 };
14119 let title = definitions
14120 .iter()
14121 .find_map(|definition| match definition {
14122 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14123 let buffer = origin.buffer.read(cx);
14124 format!(
14125 "{} for {}",
14126 tab_kind,
14127 buffer
14128 .text_for_range(origin.range.clone())
14129 .collect::<String>()
14130 )
14131 }),
14132 HoverLink::InlayHint(_, _) => None,
14133 HoverLink::Url(_) => None,
14134 HoverLink::File(_) => None,
14135 })
14136 .unwrap_or(tab_kind.to_string());
14137 let location_tasks = definitions
14138 .into_iter()
14139 .map(|definition| match definition {
14140 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14141 HoverLink::InlayHint(lsp_location, server_id) => editor
14142 .compute_target_location(lsp_location, server_id, window, cx),
14143 HoverLink::Url(_) => Task::ready(Ok(None)),
14144 HoverLink::File(_) => Task::ready(Ok(None)),
14145 })
14146 .collect::<Vec<_>>();
14147 (title, location_tasks, editor.workspace().clone())
14148 })
14149 .context("location tasks preparation")?;
14150
14151 let locations = future::join_all(location_tasks)
14152 .await
14153 .into_iter()
14154 .filter_map(|location| location.transpose())
14155 .collect::<Result<_>>()
14156 .context("location tasks")?;
14157
14158 let Some(workspace) = workspace else {
14159 return Ok(Navigated::No);
14160 };
14161 let opened = workspace
14162 .update_in(cx, |workspace, window, cx| {
14163 Self::open_locations_in_multibuffer(
14164 workspace,
14165 locations,
14166 title,
14167 split,
14168 MultibufferSelectionMode::First,
14169 window,
14170 cx,
14171 )
14172 })
14173 .ok();
14174
14175 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14176 })
14177 } else {
14178 Task::ready(Ok(Navigated::No))
14179 }
14180 }
14181
14182 fn compute_target_location(
14183 &self,
14184 lsp_location: lsp::Location,
14185 server_id: LanguageServerId,
14186 window: &mut Window,
14187 cx: &mut Context<Self>,
14188 ) -> Task<anyhow::Result<Option<Location>>> {
14189 let Some(project) = self.project.clone() else {
14190 return Task::ready(Ok(None));
14191 };
14192
14193 cx.spawn_in(window, async move |editor, cx| {
14194 let location_task = editor.update(cx, |_, cx| {
14195 project.update(cx, |project, cx| {
14196 let language_server_name = project
14197 .language_server_statuses(cx)
14198 .find(|(id, _)| server_id == *id)
14199 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14200 language_server_name.map(|language_server_name| {
14201 project.open_local_buffer_via_lsp(
14202 lsp_location.uri.clone(),
14203 server_id,
14204 language_server_name,
14205 cx,
14206 )
14207 })
14208 })
14209 })?;
14210 let location = match location_task {
14211 Some(task) => Some({
14212 let target_buffer_handle = task.await.context("open local buffer")?;
14213 let range = target_buffer_handle.update(cx, |target_buffer, _| {
14214 let target_start = target_buffer
14215 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14216 let target_end = target_buffer
14217 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14218 target_buffer.anchor_after(target_start)
14219 ..target_buffer.anchor_before(target_end)
14220 })?;
14221 Location {
14222 buffer: target_buffer_handle,
14223 range,
14224 }
14225 }),
14226 None => None,
14227 };
14228 Ok(location)
14229 })
14230 }
14231
14232 pub fn find_all_references(
14233 &mut self,
14234 _: &FindAllReferences,
14235 window: &mut Window,
14236 cx: &mut Context<Self>,
14237 ) -> Option<Task<Result<Navigated>>> {
14238 let selection = self.selections.newest::<usize>(cx);
14239 let multi_buffer = self.buffer.read(cx);
14240 let head = selection.head();
14241
14242 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14243 let head_anchor = multi_buffer_snapshot.anchor_at(
14244 head,
14245 if head < selection.tail() {
14246 Bias::Right
14247 } else {
14248 Bias::Left
14249 },
14250 );
14251
14252 match self
14253 .find_all_references_task_sources
14254 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14255 {
14256 Ok(_) => {
14257 log::info!(
14258 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14259 );
14260 return None;
14261 }
14262 Err(i) => {
14263 self.find_all_references_task_sources.insert(i, head_anchor);
14264 }
14265 }
14266
14267 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14268 let workspace = self.workspace()?;
14269 let project = workspace.read(cx).project().clone();
14270 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14271 Some(cx.spawn_in(window, async move |editor, cx| {
14272 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14273 if let Ok(i) = editor
14274 .find_all_references_task_sources
14275 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14276 {
14277 editor.find_all_references_task_sources.remove(i);
14278 }
14279 });
14280
14281 let locations = references.await?;
14282 if locations.is_empty() {
14283 return anyhow::Ok(Navigated::No);
14284 }
14285
14286 workspace.update_in(cx, |workspace, window, cx| {
14287 let title = locations
14288 .first()
14289 .as_ref()
14290 .map(|location| {
14291 let buffer = location.buffer.read(cx);
14292 format!(
14293 "References to `{}`",
14294 buffer
14295 .text_for_range(location.range.clone())
14296 .collect::<String>()
14297 )
14298 })
14299 .unwrap();
14300 Self::open_locations_in_multibuffer(
14301 workspace,
14302 locations,
14303 title,
14304 false,
14305 MultibufferSelectionMode::First,
14306 window,
14307 cx,
14308 );
14309 Navigated::Yes
14310 })
14311 }))
14312 }
14313
14314 /// Opens a multibuffer with the given project locations in it
14315 pub fn open_locations_in_multibuffer(
14316 workspace: &mut Workspace,
14317 mut locations: Vec<Location>,
14318 title: String,
14319 split: bool,
14320 multibuffer_selection_mode: MultibufferSelectionMode,
14321 window: &mut Window,
14322 cx: &mut Context<Workspace>,
14323 ) {
14324 // If there are multiple definitions, open them in a multibuffer
14325 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14326 let mut locations = locations.into_iter().peekable();
14327 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14328 let capability = workspace.project().read(cx).capability();
14329
14330 let excerpt_buffer = cx.new(|cx| {
14331 let mut multibuffer = MultiBuffer::new(capability);
14332 while let Some(location) = locations.next() {
14333 let buffer = location.buffer.read(cx);
14334 let mut ranges_for_buffer = Vec::new();
14335 let range = location.range.to_point(buffer);
14336 ranges_for_buffer.push(range.clone());
14337
14338 while let Some(next_location) = locations.peek() {
14339 if next_location.buffer == location.buffer {
14340 ranges_for_buffer.push(next_location.range.to_point(buffer));
14341 locations.next();
14342 } else {
14343 break;
14344 }
14345 }
14346
14347 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14348 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14349 PathKey::for_buffer(&location.buffer, cx),
14350 location.buffer.clone(),
14351 ranges_for_buffer,
14352 DEFAULT_MULTIBUFFER_CONTEXT,
14353 cx,
14354 );
14355 ranges.extend(new_ranges)
14356 }
14357
14358 multibuffer.with_title(title)
14359 });
14360
14361 let editor = cx.new(|cx| {
14362 Editor::for_multibuffer(
14363 excerpt_buffer,
14364 Some(workspace.project().clone()),
14365 window,
14366 cx,
14367 )
14368 });
14369 editor.update(cx, |editor, cx| {
14370 match multibuffer_selection_mode {
14371 MultibufferSelectionMode::First => {
14372 if let Some(first_range) = ranges.first() {
14373 editor.change_selections(None, window, cx, |selections| {
14374 selections.clear_disjoint();
14375 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14376 });
14377 }
14378 editor.highlight_background::<Self>(
14379 &ranges,
14380 |theme| theme.editor_highlighted_line_background,
14381 cx,
14382 );
14383 }
14384 MultibufferSelectionMode::All => {
14385 editor.change_selections(None, window, cx, |selections| {
14386 selections.clear_disjoint();
14387 selections.select_anchor_ranges(ranges);
14388 });
14389 }
14390 }
14391 editor.register_buffers_with_language_servers(cx);
14392 });
14393
14394 let item = Box::new(editor);
14395 let item_id = item.item_id();
14396
14397 if split {
14398 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14399 } else {
14400 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14401 let (preview_item_id, preview_item_idx) =
14402 workspace.active_pane().update(cx, |pane, _| {
14403 (pane.preview_item_id(), pane.preview_item_idx())
14404 });
14405
14406 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14407
14408 if let Some(preview_item_id) = preview_item_id {
14409 workspace.active_pane().update(cx, |pane, cx| {
14410 pane.remove_item(preview_item_id, false, false, window, cx);
14411 });
14412 }
14413 } else {
14414 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14415 }
14416 }
14417 workspace.active_pane().update(cx, |pane, cx| {
14418 pane.set_preview_item_id(Some(item_id), cx);
14419 });
14420 }
14421
14422 pub fn rename(
14423 &mut self,
14424 _: &Rename,
14425 window: &mut Window,
14426 cx: &mut Context<Self>,
14427 ) -> Option<Task<Result<()>>> {
14428 use language::ToOffset as _;
14429
14430 let provider = self.semantics_provider.clone()?;
14431 let selection = self.selections.newest_anchor().clone();
14432 let (cursor_buffer, cursor_buffer_position) = self
14433 .buffer
14434 .read(cx)
14435 .text_anchor_for_position(selection.head(), cx)?;
14436 let (tail_buffer, cursor_buffer_position_end) = self
14437 .buffer
14438 .read(cx)
14439 .text_anchor_for_position(selection.tail(), cx)?;
14440 if tail_buffer != cursor_buffer {
14441 return None;
14442 }
14443
14444 let snapshot = cursor_buffer.read(cx).snapshot();
14445 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14446 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14447 let prepare_rename = provider
14448 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14449 .unwrap_or_else(|| Task::ready(Ok(None)));
14450 drop(snapshot);
14451
14452 Some(cx.spawn_in(window, async move |this, cx| {
14453 let rename_range = if let Some(range) = prepare_rename.await? {
14454 Some(range)
14455 } else {
14456 this.update(cx, |this, cx| {
14457 let buffer = this.buffer.read(cx).snapshot(cx);
14458 let mut buffer_highlights = this
14459 .document_highlights_for_position(selection.head(), &buffer)
14460 .filter(|highlight| {
14461 highlight.start.excerpt_id == selection.head().excerpt_id
14462 && highlight.end.excerpt_id == selection.head().excerpt_id
14463 });
14464 buffer_highlights
14465 .next()
14466 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14467 })?
14468 };
14469 if let Some(rename_range) = rename_range {
14470 this.update_in(cx, |this, window, cx| {
14471 let snapshot = cursor_buffer.read(cx).snapshot();
14472 let rename_buffer_range = rename_range.to_offset(&snapshot);
14473 let cursor_offset_in_rename_range =
14474 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14475 let cursor_offset_in_rename_range_end =
14476 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14477
14478 this.take_rename(false, window, cx);
14479 let buffer = this.buffer.read(cx).read(cx);
14480 let cursor_offset = selection.head().to_offset(&buffer);
14481 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14482 let rename_end = rename_start + rename_buffer_range.len();
14483 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14484 let mut old_highlight_id = None;
14485 let old_name: Arc<str> = buffer
14486 .chunks(rename_start..rename_end, true)
14487 .map(|chunk| {
14488 if old_highlight_id.is_none() {
14489 old_highlight_id = chunk.syntax_highlight_id;
14490 }
14491 chunk.text
14492 })
14493 .collect::<String>()
14494 .into();
14495
14496 drop(buffer);
14497
14498 // Position the selection in the rename editor so that it matches the current selection.
14499 this.show_local_selections = false;
14500 let rename_editor = cx.new(|cx| {
14501 let mut editor = Editor::single_line(window, cx);
14502 editor.buffer.update(cx, |buffer, cx| {
14503 buffer.edit([(0..0, old_name.clone())], None, cx)
14504 });
14505 let rename_selection_range = match cursor_offset_in_rename_range
14506 .cmp(&cursor_offset_in_rename_range_end)
14507 {
14508 Ordering::Equal => {
14509 editor.select_all(&SelectAll, window, cx);
14510 return editor;
14511 }
14512 Ordering::Less => {
14513 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14514 }
14515 Ordering::Greater => {
14516 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14517 }
14518 };
14519 if rename_selection_range.end > old_name.len() {
14520 editor.select_all(&SelectAll, window, cx);
14521 } else {
14522 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14523 s.select_ranges([rename_selection_range]);
14524 });
14525 }
14526 editor
14527 });
14528 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14529 if e == &EditorEvent::Focused {
14530 cx.emit(EditorEvent::FocusedIn)
14531 }
14532 })
14533 .detach();
14534
14535 let write_highlights =
14536 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14537 let read_highlights =
14538 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14539 let ranges = write_highlights
14540 .iter()
14541 .flat_map(|(_, ranges)| ranges.iter())
14542 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14543 .cloned()
14544 .collect();
14545
14546 this.highlight_text::<Rename>(
14547 ranges,
14548 HighlightStyle {
14549 fade_out: Some(0.6),
14550 ..Default::default()
14551 },
14552 cx,
14553 );
14554 let rename_focus_handle = rename_editor.focus_handle(cx);
14555 window.focus(&rename_focus_handle);
14556 let block_id = this.insert_blocks(
14557 [BlockProperties {
14558 style: BlockStyle::Flex,
14559 placement: BlockPlacement::Below(range.start),
14560 height: Some(1),
14561 render: Arc::new({
14562 let rename_editor = rename_editor.clone();
14563 move |cx: &mut BlockContext| {
14564 let mut text_style = cx.editor_style.text.clone();
14565 if let Some(highlight_style) = old_highlight_id
14566 .and_then(|h| h.style(&cx.editor_style.syntax))
14567 {
14568 text_style = text_style.highlight(highlight_style);
14569 }
14570 div()
14571 .block_mouse_down()
14572 .pl(cx.anchor_x)
14573 .child(EditorElement::new(
14574 &rename_editor,
14575 EditorStyle {
14576 background: cx.theme().system().transparent,
14577 local_player: cx.editor_style.local_player,
14578 text: text_style,
14579 scrollbar_width: cx.editor_style.scrollbar_width,
14580 syntax: cx.editor_style.syntax.clone(),
14581 status: cx.editor_style.status.clone(),
14582 inlay_hints_style: HighlightStyle {
14583 font_weight: Some(FontWeight::BOLD),
14584 ..make_inlay_hints_style(cx.app)
14585 },
14586 inline_completion_styles: make_suggestion_styles(
14587 cx.app,
14588 ),
14589 ..EditorStyle::default()
14590 },
14591 ))
14592 .into_any_element()
14593 }
14594 }),
14595 priority: 0,
14596 render_in_minimap: true,
14597 }],
14598 Some(Autoscroll::fit()),
14599 cx,
14600 )[0];
14601 this.pending_rename = Some(RenameState {
14602 range,
14603 old_name,
14604 editor: rename_editor,
14605 block_id,
14606 });
14607 })?;
14608 }
14609
14610 Ok(())
14611 }))
14612 }
14613
14614 pub fn confirm_rename(
14615 &mut self,
14616 _: &ConfirmRename,
14617 window: &mut Window,
14618 cx: &mut Context<Self>,
14619 ) -> Option<Task<Result<()>>> {
14620 let rename = self.take_rename(false, window, cx)?;
14621 let workspace = self.workspace()?.downgrade();
14622 let (buffer, start) = self
14623 .buffer
14624 .read(cx)
14625 .text_anchor_for_position(rename.range.start, cx)?;
14626 let (end_buffer, _) = self
14627 .buffer
14628 .read(cx)
14629 .text_anchor_for_position(rename.range.end, cx)?;
14630 if buffer != end_buffer {
14631 return None;
14632 }
14633
14634 let old_name = rename.old_name;
14635 let new_name = rename.editor.read(cx).text(cx);
14636
14637 let rename = self.semantics_provider.as_ref()?.perform_rename(
14638 &buffer,
14639 start,
14640 new_name.clone(),
14641 cx,
14642 )?;
14643
14644 Some(cx.spawn_in(window, async move |editor, cx| {
14645 let project_transaction = rename.await?;
14646 Self::open_project_transaction(
14647 &editor,
14648 workspace,
14649 project_transaction,
14650 format!("Rename: {} → {}", old_name, new_name),
14651 cx,
14652 )
14653 .await?;
14654
14655 editor.update(cx, |editor, cx| {
14656 editor.refresh_document_highlights(cx);
14657 })?;
14658 Ok(())
14659 }))
14660 }
14661
14662 fn take_rename(
14663 &mut self,
14664 moving_cursor: bool,
14665 window: &mut Window,
14666 cx: &mut Context<Self>,
14667 ) -> Option<RenameState> {
14668 let rename = self.pending_rename.take()?;
14669 if rename.editor.focus_handle(cx).is_focused(window) {
14670 window.focus(&self.focus_handle);
14671 }
14672
14673 self.remove_blocks(
14674 [rename.block_id].into_iter().collect(),
14675 Some(Autoscroll::fit()),
14676 cx,
14677 );
14678 self.clear_highlights::<Rename>(cx);
14679 self.show_local_selections = true;
14680
14681 if moving_cursor {
14682 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14683 editor.selections.newest::<usize>(cx).head()
14684 });
14685
14686 // Update the selection to match the position of the selection inside
14687 // the rename editor.
14688 let snapshot = self.buffer.read(cx).read(cx);
14689 let rename_range = rename.range.to_offset(&snapshot);
14690 let cursor_in_editor = snapshot
14691 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14692 .min(rename_range.end);
14693 drop(snapshot);
14694
14695 self.change_selections(None, window, cx, |s| {
14696 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14697 });
14698 } else {
14699 self.refresh_document_highlights(cx);
14700 }
14701
14702 Some(rename)
14703 }
14704
14705 pub fn pending_rename(&self) -> Option<&RenameState> {
14706 self.pending_rename.as_ref()
14707 }
14708
14709 fn format(
14710 &mut self,
14711 _: &Format,
14712 window: &mut Window,
14713 cx: &mut Context<Self>,
14714 ) -> Option<Task<Result<()>>> {
14715 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14716
14717 let project = match &self.project {
14718 Some(project) => project.clone(),
14719 None => return None,
14720 };
14721
14722 Some(self.perform_format(
14723 project,
14724 FormatTrigger::Manual,
14725 FormatTarget::Buffers,
14726 window,
14727 cx,
14728 ))
14729 }
14730
14731 fn format_selections(
14732 &mut self,
14733 _: &FormatSelections,
14734 window: &mut Window,
14735 cx: &mut Context<Self>,
14736 ) -> Option<Task<Result<()>>> {
14737 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14738
14739 let project = match &self.project {
14740 Some(project) => project.clone(),
14741 None => return None,
14742 };
14743
14744 let ranges = self
14745 .selections
14746 .all_adjusted(cx)
14747 .into_iter()
14748 .map(|selection| selection.range())
14749 .collect_vec();
14750
14751 Some(self.perform_format(
14752 project,
14753 FormatTrigger::Manual,
14754 FormatTarget::Ranges(ranges),
14755 window,
14756 cx,
14757 ))
14758 }
14759
14760 fn perform_format(
14761 &mut self,
14762 project: Entity<Project>,
14763 trigger: FormatTrigger,
14764 target: FormatTarget,
14765 window: &mut Window,
14766 cx: &mut Context<Self>,
14767 ) -> Task<Result<()>> {
14768 let buffer = self.buffer.clone();
14769 let (buffers, target) = match target {
14770 FormatTarget::Buffers => {
14771 let mut buffers = buffer.read(cx).all_buffers();
14772 if trigger == FormatTrigger::Save {
14773 buffers.retain(|buffer| buffer.read(cx).is_dirty());
14774 }
14775 (buffers, LspFormatTarget::Buffers)
14776 }
14777 FormatTarget::Ranges(selection_ranges) => {
14778 let multi_buffer = buffer.read(cx);
14779 let snapshot = multi_buffer.read(cx);
14780 let mut buffers = HashSet::default();
14781 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
14782 BTreeMap::new();
14783 for selection_range in selection_ranges {
14784 for (buffer, buffer_range, _) in
14785 snapshot.range_to_buffer_ranges(selection_range)
14786 {
14787 let buffer_id = buffer.remote_id();
14788 let start = buffer.anchor_before(buffer_range.start);
14789 let end = buffer.anchor_after(buffer_range.end);
14790 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
14791 buffer_id_to_ranges
14792 .entry(buffer_id)
14793 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
14794 .or_insert_with(|| vec![start..end]);
14795 }
14796 }
14797 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
14798 }
14799 };
14800
14801 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
14802 let selections_prev = transaction_id_prev
14803 .and_then(|transaction_id_prev| {
14804 // default to selections as they were after the last edit, if we have them,
14805 // instead of how they are now.
14806 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
14807 // will take you back to where you made the last edit, instead of staying where you scrolled
14808 self.selection_history
14809 .transaction(transaction_id_prev)
14810 .map(|t| t.0.clone())
14811 })
14812 .unwrap_or_else(|| {
14813 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
14814 self.selections.disjoint_anchors()
14815 });
14816
14817 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
14818 let format = project.update(cx, |project, cx| {
14819 project.format(buffers, target, true, trigger, cx)
14820 });
14821
14822 cx.spawn_in(window, async move |editor, cx| {
14823 let transaction = futures::select_biased! {
14824 transaction = format.log_err().fuse() => transaction,
14825 () = timeout => {
14826 log::warn!("timed out waiting for formatting");
14827 None
14828 }
14829 };
14830
14831 buffer
14832 .update(cx, |buffer, cx| {
14833 if let Some(transaction) = transaction {
14834 if !buffer.is_singleton() {
14835 buffer.push_transaction(&transaction.0, cx);
14836 }
14837 }
14838 cx.notify();
14839 })
14840 .ok();
14841
14842 if let Some(transaction_id_now) =
14843 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
14844 {
14845 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
14846 if has_new_transaction {
14847 _ = editor.update(cx, |editor, _| {
14848 editor
14849 .selection_history
14850 .insert_transaction(transaction_id_now, selections_prev);
14851 });
14852 }
14853 }
14854
14855 Ok(())
14856 })
14857 }
14858
14859 fn organize_imports(
14860 &mut self,
14861 _: &OrganizeImports,
14862 window: &mut Window,
14863 cx: &mut Context<Self>,
14864 ) -> Option<Task<Result<()>>> {
14865 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14866 let project = match &self.project {
14867 Some(project) => project.clone(),
14868 None => return None,
14869 };
14870 Some(self.perform_code_action_kind(
14871 project,
14872 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
14873 window,
14874 cx,
14875 ))
14876 }
14877
14878 fn perform_code_action_kind(
14879 &mut self,
14880 project: Entity<Project>,
14881 kind: CodeActionKind,
14882 window: &mut Window,
14883 cx: &mut Context<Self>,
14884 ) -> Task<Result<()>> {
14885 let buffer = self.buffer.clone();
14886 let buffers = buffer.read(cx).all_buffers();
14887 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
14888 let apply_action = project.update(cx, |project, cx| {
14889 project.apply_code_action_kind(buffers, kind, true, cx)
14890 });
14891 cx.spawn_in(window, async move |_, cx| {
14892 let transaction = futures::select_biased! {
14893 () = timeout => {
14894 log::warn!("timed out waiting for executing code action");
14895 None
14896 }
14897 transaction = apply_action.log_err().fuse() => transaction,
14898 };
14899 buffer
14900 .update(cx, |buffer, cx| {
14901 // check if we need this
14902 if let Some(transaction) = transaction {
14903 if !buffer.is_singleton() {
14904 buffer.push_transaction(&transaction.0, cx);
14905 }
14906 }
14907 cx.notify();
14908 })
14909 .ok();
14910 Ok(())
14911 })
14912 }
14913
14914 fn restart_language_server(
14915 &mut self,
14916 _: &RestartLanguageServer,
14917 _: &mut Window,
14918 cx: &mut Context<Self>,
14919 ) {
14920 if let Some(project) = self.project.clone() {
14921 self.buffer.update(cx, |multi_buffer, cx| {
14922 project.update(cx, |project, cx| {
14923 project.restart_language_servers_for_buffers(
14924 multi_buffer.all_buffers().into_iter().collect(),
14925 cx,
14926 );
14927 });
14928 })
14929 }
14930 }
14931
14932 fn stop_language_server(
14933 &mut self,
14934 _: &StopLanguageServer,
14935 _: &mut Window,
14936 cx: &mut Context<Self>,
14937 ) {
14938 if let Some(project) = self.project.clone() {
14939 self.buffer.update(cx, |multi_buffer, cx| {
14940 project.update(cx, |project, cx| {
14941 project.stop_language_servers_for_buffers(
14942 multi_buffer.all_buffers().into_iter().collect(),
14943 cx,
14944 );
14945 cx.emit(project::Event::RefreshInlayHints);
14946 });
14947 });
14948 }
14949 }
14950
14951 fn cancel_language_server_work(
14952 workspace: &mut Workspace,
14953 _: &actions::CancelLanguageServerWork,
14954 _: &mut Window,
14955 cx: &mut Context<Workspace>,
14956 ) {
14957 let project = workspace.project();
14958 let buffers = workspace
14959 .active_item(cx)
14960 .and_then(|item| item.act_as::<Editor>(cx))
14961 .map_or(HashSet::default(), |editor| {
14962 editor.read(cx).buffer.read(cx).all_buffers()
14963 });
14964 project.update(cx, |project, cx| {
14965 project.cancel_language_server_work_for_buffers(buffers, cx);
14966 });
14967 }
14968
14969 fn show_character_palette(
14970 &mut self,
14971 _: &ShowCharacterPalette,
14972 window: &mut Window,
14973 _: &mut Context<Self>,
14974 ) {
14975 window.show_character_palette();
14976 }
14977
14978 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
14979 if self.mode.is_minimap() {
14980 return;
14981 }
14982
14983 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
14984 let buffer = self.buffer.read(cx).snapshot(cx);
14985 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
14986 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
14987 let is_valid = buffer
14988 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
14989 .any(|entry| {
14990 entry.diagnostic.is_primary
14991 && !entry.range.is_empty()
14992 && entry.range.start == primary_range_start
14993 && entry.diagnostic.message == active_diagnostics.active_message
14994 });
14995
14996 if !is_valid {
14997 self.dismiss_diagnostics(cx);
14998 }
14999 }
15000 }
15001
15002 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15003 match &self.active_diagnostics {
15004 ActiveDiagnostic::Group(group) => Some(group),
15005 _ => None,
15006 }
15007 }
15008
15009 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15010 self.dismiss_diagnostics(cx);
15011 self.active_diagnostics = ActiveDiagnostic::All;
15012 }
15013
15014 fn activate_diagnostics(
15015 &mut self,
15016 buffer_id: BufferId,
15017 diagnostic: DiagnosticEntry<usize>,
15018 window: &mut Window,
15019 cx: &mut Context<Self>,
15020 ) {
15021 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15022 return;
15023 }
15024 self.dismiss_diagnostics(cx);
15025 let snapshot = self.snapshot(window, cx);
15026 let buffer = self.buffer.read(cx).snapshot(cx);
15027 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15028 return;
15029 };
15030
15031 let diagnostic_group = buffer
15032 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15033 .collect::<Vec<_>>();
15034
15035 let blocks =
15036 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15037
15038 let blocks = self.display_map.update(cx, |display_map, cx| {
15039 display_map.insert_blocks(blocks, cx).into_iter().collect()
15040 });
15041 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15042 active_range: buffer.anchor_before(diagnostic.range.start)
15043 ..buffer.anchor_after(diagnostic.range.end),
15044 active_message: diagnostic.diagnostic.message.clone(),
15045 group_id: diagnostic.diagnostic.group_id,
15046 blocks,
15047 });
15048 cx.notify();
15049 }
15050
15051 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15052 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15053 return;
15054 };
15055
15056 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15057 if let ActiveDiagnostic::Group(group) = prev {
15058 self.display_map.update(cx, |display_map, cx| {
15059 display_map.remove_blocks(group.blocks, cx);
15060 });
15061 cx.notify();
15062 }
15063 }
15064
15065 /// Disable inline diagnostics rendering for this editor.
15066 pub fn disable_inline_diagnostics(&mut self) {
15067 self.inline_diagnostics_enabled = false;
15068 self.inline_diagnostics_update = Task::ready(());
15069 self.inline_diagnostics.clear();
15070 }
15071
15072 pub fn inline_diagnostics_enabled(&self) -> bool {
15073 self.inline_diagnostics_enabled
15074 }
15075
15076 pub fn show_inline_diagnostics(&self) -> bool {
15077 self.show_inline_diagnostics
15078 }
15079
15080 pub fn toggle_inline_diagnostics(
15081 &mut self,
15082 _: &ToggleInlineDiagnostics,
15083 window: &mut Window,
15084 cx: &mut Context<Editor>,
15085 ) {
15086 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15087 self.refresh_inline_diagnostics(false, window, cx);
15088 }
15089
15090 fn refresh_inline_diagnostics(
15091 &mut self,
15092 debounce: bool,
15093 window: &mut Window,
15094 cx: &mut Context<Self>,
15095 ) {
15096 if self.mode.is_minimap()
15097 || !self.inline_diagnostics_enabled
15098 || !self.show_inline_diagnostics
15099 {
15100 self.inline_diagnostics_update = Task::ready(());
15101 self.inline_diagnostics.clear();
15102 return;
15103 }
15104
15105 let debounce_ms = ProjectSettings::get_global(cx)
15106 .diagnostics
15107 .inline
15108 .update_debounce_ms;
15109 let debounce = if debounce && debounce_ms > 0 {
15110 Some(Duration::from_millis(debounce_ms))
15111 } else {
15112 None
15113 };
15114 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15115 let editor = editor.upgrade().unwrap();
15116
15117 if let Some(debounce) = debounce {
15118 cx.background_executor().timer(debounce).await;
15119 }
15120 let Some(snapshot) = editor
15121 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15122 .ok()
15123 else {
15124 return;
15125 };
15126
15127 let new_inline_diagnostics = cx
15128 .background_spawn(async move {
15129 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15130 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15131 let message = diagnostic_entry
15132 .diagnostic
15133 .message
15134 .split_once('\n')
15135 .map(|(line, _)| line)
15136 .map(SharedString::new)
15137 .unwrap_or_else(|| {
15138 SharedString::from(diagnostic_entry.diagnostic.message)
15139 });
15140 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15141 let (Ok(i) | Err(i)) = inline_diagnostics
15142 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15143 inline_diagnostics.insert(
15144 i,
15145 (
15146 start_anchor,
15147 InlineDiagnostic {
15148 message,
15149 group_id: diagnostic_entry.diagnostic.group_id,
15150 start: diagnostic_entry.range.start.to_point(&snapshot),
15151 is_primary: diagnostic_entry.diagnostic.is_primary,
15152 severity: diagnostic_entry.diagnostic.severity,
15153 },
15154 ),
15155 );
15156 }
15157 inline_diagnostics
15158 })
15159 .await;
15160
15161 editor
15162 .update(cx, |editor, cx| {
15163 editor.inline_diagnostics = new_inline_diagnostics;
15164 cx.notify();
15165 })
15166 .ok();
15167 });
15168 }
15169
15170 pub fn set_selections_from_remote(
15171 &mut self,
15172 selections: Vec<Selection<Anchor>>,
15173 pending_selection: Option<Selection<Anchor>>,
15174 window: &mut Window,
15175 cx: &mut Context<Self>,
15176 ) {
15177 let old_cursor_position = self.selections.newest_anchor().head();
15178 self.selections.change_with(cx, |s| {
15179 s.select_anchors(selections);
15180 if let Some(pending_selection) = pending_selection {
15181 s.set_pending(pending_selection, SelectMode::Character);
15182 } else {
15183 s.clear_pending();
15184 }
15185 });
15186 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15187 }
15188
15189 fn push_to_selection_history(&mut self) {
15190 self.selection_history.push(SelectionHistoryEntry {
15191 selections: self.selections.disjoint_anchors(),
15192 select_next_state: self.select_next_state.clone(),
15193 select_prev_state: self.select_prev_state.clone(),
15194 add_selections_state: self.add_selections_state.clone(),
15195 });
15196 }
15197
15198 pub fn transact(
15199 &mut self,
15200 window: &mut Window,
15201 cx: &mut Context<Self>,
15202 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15203 ) -> Option<TransactionId> {
15204 self.start_transaction_at(Instant::now(), window, cx);
15205 update(self, window, cx);
15206 self.end_transaction_at(Instant::now(), cx)
15207 }
15208
15209 pub fn start_transaction_at(
15210 &mut self,
15211 now: Instant,
15212 window: &mut Window,
15213 cx: &mut Context<Self>,
15214 ) {
15215 self.end_selection(window, cx);
15216 if let Some(tx_id) = self
15217 .buffer
15218 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15219 {
15220 self.selection_history
15221 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15222 cx.emit(EditorEvent::TransactionBegun {
15223 transaction_id: tx_id,
15224 })
15225 }
15226 }
15227
15228 pub fn end_transaction_at(
15229 &mut self,
15230 now: Instant,
15231 cx: &mut Context<Self>,
15232 ) -> Option<TransactionId> {
15233 if let Some(transaction_id) = self
15234 .buffer
15235 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15236 {
15237 if let Some((_, end_selections)) =
15238 self.selection_history.transaction_mut(transaction_id)
15239 {
15240 *end_selections = Some(self.selections.disjoint_anchors());
15241 } else {
15242 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15243 }
15244
15245 cx.emit(EditorEvent::Edited { transaction_id });
15246 Some(transaction_id)
15247 } else {
15248 None
15249 }
15250 }
15251
15252 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15253 if self.selection_mark_mode {
15254 self.change_selections(None, window, cx, |s| {
15255 s.move_with(|_, sel| {
15256 sel.collapse_to(sel.head(), SelectionGoal::None);
15257 });
15258 })
15259 }
15260 self.selection_mark_mode = true;
15261 cx.notify();
15262 }
15263
15264 pub fn swap_selection_ends(
15265 &mut self,
15266 _: &actions::SwapSelectionEnds,
15267 window: &mut Window,
15268 cx: &mut Context<Self>,
15269 ) {
15270 self.change_selections(None, window, cx, |s| {
15271 s.move_with(|_, sel| {
15272 if sel.start != sel.end {
15273 sel.reversed = !sel.reversed
15274 }
15275 });
15276 });
15277 self.request_autoscroll(Autoscroll::newest(), cx);
15278 cx.notify();
15279 }
15280
15281 pub fn toggle_fold(
15282 &mut self,
15283 _: &actions::ToggleFold,
15284 window: &mut Window,
15285 cx: &mut Context<Self>,
15286 ) {
15287 if self.is_singleton(cx) {
15288 let selection = self.selections.newest::<Point>(cx);
15289
15290 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15291 let range = if selection.is_empty() {
15292 let point = selection.head().to_display_point(&display_map);
15293 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15294 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15295 .to_point(&display_map);
15296 start..end
15297 } else {
15298 selection.range()
15299 };
15300 if display_map.folds_in_range(range).next().is_some() {
15301 self.unfold_lines(&Default::default(), window, cx)
15302 } else {
15303 self.fold(&Default::default(), window, cx)
15304 }
15305 } else {
15306 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15307 let buffer_ids: HashSet<_> = self
15308 .selections
15309 .disjoint_anchor_ranges()
15310 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15311 .collect();
15312
15313 let should_unfold = buffer_ids
15314 .iter()
15315 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15316
15317 for buffer_id in buffer_ids {
15318 if should_unfold {
15319 self.unfold_buffer(buffer_id, cx);
15320 } else {
15321 self.fold_buffer(buffer_id, cx);
15322 }
15323 }
15324 }
15325 }
15326
15327 pub fn toggle_fold_recursive(
15328 &mut self,
15329 _: &actions::ToggleFoldRecursive,
15330 window: &mut Window,
15331 cx: &mut Context<Self>,
15332 ) {
15333 let selection = self.selections.newest::<Point>(cx);
15334
15335 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15336 let range = if selection.is_empty() {
15337 let point = selection.head().to_display_point(&display_map);
15338 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15339 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15340 .to_point(&display_map);
15341 start..end
15342 } else {
15343 selection.range()
15344 };
15345 if display_map.folds_in_range(range).next().is_some() {
15346 self.unfold_recursive(&Default::default(), window, cx)
15347 } else {
15348 self.fold_recursive(&Default::default(), window, cx)
15349 }
15350 }
15351
15352 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15353 if self.is_singleton(cx) {
15354 let mut to_fold = Vec::new();
15355 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15356 let selections = self.selections.all_adjusted(cx);
15357
15358 for selection in selections {
15359 let range = selection.range().sorted();
15360 let buffer_start_row = range.start.row;
15361
15362 if range.start.row != range.end.row {
15363 let mut found = false;
15364 let mut row = range.start.row;
15365 while row <= range.end.row {
15366 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15367 {
15368 found = true;
15369 row = crease.range().end.row + 1;
15370 to_fold.push(crease);
15371 } else {
15372 row += 1
15373 }
15374 }
15375 if found {
15376 continue;
15377 }
15378 }
15379
15380 for row in (0..=range.start.row).rev() {
15381 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15382 if crease.range().end.row >= buffer_start_row {
15383 to_fold.push(crease);
15384 if row <= range.start.row {
15385 break;
15386 }
15387 }
15388 }
15389 }
15390 }
15391
15392 self.fold_creases(to_fold, true, window, cx);
15393 } else {
15394 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15395 let buffer_ids = self
15396 .selections
15397 .disjoint_anchor_ranges()
15398 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15399 .collect::<HashSet<_>>();
15400 for buffer_id in buffer_ids {
15401 self.fold_buffer(buffer_id, cx);
15402 }
15403 }
15404 }
15405
15406 fn fold_at_level(
15407 &mut self,
15408 fold_at: &FoldAtLevel,
15409 window: &mut Window,
15410 cx: &mut Context<Self>,
15411 ) {
15412 if !self.buffer.read(cx).is_singleton() {
15413 return;
15414 }
15415
15416 let fold_at_level = fold_at.0;
15417 let snapshot = self.buffer.read(cx).snapshot(cx);
15418 let mut to_fold = Vec::new();
15419 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15420
15421 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15422 while start_row < end_row {
15423 match self
15424 .snapshot(window, cx)
15425 .crease_for_buffer_row(MultiBufferRow(start_row))
15426 {
15427 Some(crease) => {
15428 let nested_start_row = crease.range().start.row + 1;
15429 let nested_end_row = crease.range().end.row;
15430
15431 if current_level < fold_at_level {
15432 stack.push((nested_start_row, nested_end_row, current_level + 1));
15433 } else if current_level == fold_at_level {
15434 to_fold.push(crease);
15435 }
15436
15437 start_row = nested_end_row + 1;
15438 }
15439 None => start_row += 1,
15440 }
15441 }
15442 }
15443
15444 self.fold_creases(to_fold, true, window, cx);
15445 }
15446
15447 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15448 if self.buffer.read(cx).is_singleton() {
15449 let mut fold_ranges = Vec::new();
15450 let snapshot = self.buffer.read(cx).snapshot(cx);
15451
15452 for row in 0..snapshot.max_row().0 {
15453 if let Some(foldable_range) = self
15454 .snapshot(window, cx)
15455 .crease_for_buffer_row(MultiBufferRow(row))
15456 {
15457 fold_ranges.push(foldable_range);
15458 }
15459 }
15460
15461 self.fold_creases(fold_ranges, true, window, cx);
15462 } else {
15463 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15464 editor
15465 .update_in(cx, |editor, _, cx| {
15466 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15467 editor.fold_buffer(buffer_id, cx);
15468 }
15469 })
15470 .ok();
15471 });
15472 }
15473 }
15474
15475 pub fn fold_function_bodies(
15476 &mut self,
15477 _: &actions::FoldFunctionBodies,
15478 window: &mut Window,
15479 cx: &mut Context<Self>,
15480 ) {
15481 let snapshot = self.buffer.read(cx).snapshot(cx);
15482
15483 let ranges = snapshot
15484 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15485 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15486 .collect::<Vec<_>>();
15487
15488 let creases = ranges
15489 .into_iter()
15490 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15491 .collect();
15492
15493 self.fold_creases(creases, true, window, cx);
15494 }
15495
15496 pub fn fold_recursive(
15497 &mut self,
15498 _: &actions::FoldRecursive,
15499 window: &mut Window,
15500 cx: &mut Context<Self>,
15501 ) {
15502 let mut to_fold = Vec::new();
15503 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15504 let selections = self.selections.all_adjusted(cx);
15505
15506 for selection in selections {
15507 let range = selection.range().sorted();
15508 let buffer_start_row = range.start.row;
15509
15510 if range.start.row != range.end.row {
15511 let mut found = false;
15512 for row in range.start.row..=range.end.row {
15513 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15514 found = true;
15515 to_fold.push(crease);
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 } else {
15528 break;
15529 }
15530 }
15531 }
15532 }
15533
15534 self.fold_creases(to_fold, true, window, cx);
15535 }
15536
15537 pub fn fold_at(
15538 &mut self,
15539 buffer_row: MultiBufferRow,
15540 window: &mut Window,
15541 cx: &mut Context<Self>,
15542 ) {
15543 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15544
15545 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15546 let autoscroll = self
15547 .selections
15548 .all::<Point>(cx)
15549 .iter()
15550 .any(|selection| crease.range().overlaps(&selection.range()));
15551
15552 self.fold_creases(vec![crease], autoscroll, window, cx);
15553 }
15554 }
15555
15556 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15557 if self.is_singleton(cx) {
15558 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15559 let buffer = &display_map.buffer_snapshot;
15560 let selections = self.selections.all::<Point>(cx);
15561 let ranges = selections
15562 .iter()
15563 .map(|s| {
15564 let range = s.display_range(&display_map).sorted();
15565 let mut start = range.start.to_point(&display_map);
15566 let mut end = range.end.to_point(&display_map);
15567 start.column = 0;
15568 end.column = buffer.line_len(MultiBufferRow(end.row));
15569 start..end
15570 })
15571 .collect::<Vec<_>>();
15572
15573 self.unfold_ranges(&ranges, true, true, cx);
15574 } else {
15575 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15576 let buffer_ids = self
15577 .selections
15578 .disjoint_anchor_ranges()
15579 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15580 .collect::<HashSet<_>>();
15581 for buffer_id in buffer_ids {
15582 self.unfold_buffer(buffer_id, cx);
15583 }
15584 }
15585 }
15586
15587 pub fn unfold_recursive(
15588 &mut self,
15589 _: &UnfoldRecursive,
15590 _window: &mut Window,
15591 cx: &mut Context<Self>,
15592 ) {
15593 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15594 let selections = self.selections.all::<Point>(cx);
15595 let ranges = selections
15596 .iter()
15597 .map(|s| {
15598 let mut range = s.display_range(&display_map).sorted();
15599 *range.start.column_mut() = 0;
15600 *range.end.column_mut() = display_map.line_len(range.end.row());
15601 let start = range.start.to_point(&display_map);
15602 let end = range.end.to_point(&display_map);
15603 start..end
15604 })
15605 .collect::<Vec<_>>();
15606
15607 self.unfold_ranges(&ranges, true, true, cx);
15608 }
15609
15610 pub fn unfold_at(
15611 &mut self,
15612 buffer_row: MultiBufferRow,
15613 _window: &mut Window,
15614 cx: &mut Context<Self>,
15615 ) {
15616 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15617
15618 let intersection_range = Point::new(buffer_row.0, 0)
15619 ..Point::new(
15620 buffer_row.0,
15621 display_map.buffer_snapshot.line_len(buffer_row),
15622 );
15623
15624 let autoscroll = self
15625 .selections
15626 .all::<Point>(cx)
15627 .iter()
15628 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15629
15630 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15631 }
15632
15633 pub fn unfold_all(
15634 &mut self,
15635 _: &actions::UnfoldAll,
15636 _window: &mut Window,
15637 cx: &mut Context<Self>,
15638 ) {
15639 if self.buffer.read(cx).is_singleton() {
15640 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15641 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15642 } else {
15643 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15644 editor
15645 .update(cx, |editor, cx| {
15646 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15647 editor.unfold_buffer(buffer_id, cx);
15648 }
15649 })
15650 .ok();
15651 });
15652 }
15653 }
15654
15655 pub fn fold_selected_ranges(
15656 &mut self,
15657 _: &FoldSelectedRanges,
15658 window: &mut Window,
15659 cx: &mut Context<Self>,
15660 ) {
15661 let selections = self.selections.all_adjusted(cx);
15662 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15663 let ranges = selections
15664 .into_iter()
15665 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
15666 .collect::<Vec<_>>();
15667 self.fold_creases(ranges, true, window, cx);
15668 }
15669
15670 pub fn fold_ranges<T: ToOffset + Clone>(
15671 &mut self,
15672 ranges: Vec<Range<T>>,
15673 auto_scroll: bool,
15674 window: &mut Window,
15675 cx: &mut Context<Self>,
15676 ) {
15677 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15678 let ranges = ranges
15679 .into_iter()
15680 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
15681 .collect::<Vec<_>>();
15682 self.fold_creases(ranges, auto_scroll, window, cx);
15683 }
15684
15685 pub fn fold_creases<T: ToOffset + Clone>(
15686 &mut self,
15687 creases: Vec<Crease<T>>,
15688 auto_scroll: bool,
15689 _window: &mut Window,
15690 cx: &mut Context<Self>,
15691 ) {
15692 if creases.is_empty() {
15693 return;
15694 }
15695
15696 let mut buffers_affected = HashSet::default();
15697 let multi_buffer = self.buffer().read(cx);
15698 for crease in &creases {
15699 if let Some((_, buffer, _)) =
15700 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
15701 {
15702 buffers_affected.insert(buffer.read(cx).remote_id());
15703 };
15704 }
15705
15706 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
15707
15708 if auto_scroll {
15709 self.request_autoscroll(Autoscroll::fit(), cx);
15710 }
15711
15712 cx.notify();
15713
15714 self.scrollbar_marker_state.dirty = true;
15715 self.folds_did_change(cx);
15716 }
15717
15718 /// Removes any folds whose ranges intersect any of the given ranges.
15719 pub fn unfold_ranges<T: ToOffset + Clone>(
15720 &mut self,
15721 ranges: &[Range<T>],
15722 inclusive: bool,
15723 auto_scroll: bool,
15724 cx: &mut Context<Self>,
15725 ) {
15726 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15727 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
15728 });
15729 self.folds_did_change(cx);
15730 }
15731
15732 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15733 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
15734 return;
15735 }
15736 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15737 self.display_map.update(cx, |display_map, cx| {
15738 display_map.fold_buffers([buffer_id], cx)
15739 });
15740 cx.emit(EditorEvent::BufferFoldToggled {
15741 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
15742 folded: true,
15743 });
15744 cx.notify();
15745 }
15746
15747 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15748 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
15749 return;
15750 }
15751 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15752 self.display_map.update(cx, |display_map, cx| {
15753 display_map.unfold_buffers([buffer_id], cx);
15754 });
15755 cx.emit(EditorEvent::BufferFoldToggled {
15756 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
15757 folded: false,
15758 });
15759 cx.notify();
15760 }
15761
15762 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
15763 self.display_map.read(cx).is_buffer_folded(buffer)
15764 }
15765
15766 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
15767 self.display_map.read(cx).folded_buffers()
15768 }
15769
15770 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15771 self.display_map.update(cx, |display_map, cx| {
15772 display_map.disable_header_for_buffer(buffer_id, cx);
15773 });
15774 cx.notify();
15775 }
15776
15777 /// Removes any folds with the given ranges.
15778 pub fn remove_folds_with_type<T: ToOffset + Clone>(
15779 &mut self,
15780 ranges: &[Range<T>],
15781 type_id: TypeId,
15782 auto_scroll: bool,
15783 cx: &mut Context<Self>,
15784 ) {
15785 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15786 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
15787 });
15788 self.folds_did_change(cx);
15789 }
15790
15791 fn remove_folds_with<T: ToOffset + Clone>(
15792 &mut self,
15793 ranges: &[Range<T>],
15794 auto_scroll: bool,
15795 cx: &mut Context<Self>,
15796 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
15797 ) {
15798 if ranges.is_empty() {
15799 return;
15800 }
15801
15802 let mut buffers_affected = HashSet::default();
15803 let multi_buffer = self.buffer().read(cx);
15804 for range in ranges {
15805 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
15806 buffers_affected.insert(buffer.read(cx).remote_id());
15807 };
15808 }
15809
15810 self.display_map.update(cx, update);
15811
15812 if auto_scroll {
15813 self.request_autoscroll(Autoscroll::fit(), cx);
15814 }
15815
15816 cx.notify();
15817 self.scrollbar_marker_state.dirty = true;
15818 self.active_indent_guides_state.dirty = true;
15819 }
15820
15821 pub fn update_fold_widths(
15822 &mut self,
15823 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
15824 cx: &mut Context<Self>,
15825 ) -> bool {
15826 self.display_map
15827 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
15828 }
15829
15830 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
15831 self.display_map.read(cx).fold_placeholder.clone()
15832 }
15833
15834 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
15835 self.buffer.update(cx, |buffer, cx| {
15836 buffer.set_all_diff_hunks_expanded(cx);
15837 });
15838 }
15839
15840 pub fn expand_all_diff_hunks(
15841 &mut self,
15842 _: &ExpandAllDiffHunks,
15843 _window: &mut Window,
15844 cx: &mut Context<Self>,
15845 ) {
15846 self.buffer.update(cx, |buffer, cx| {
15847 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
15848 });
15849 }
15850
15851 pub fn toggle_selected_diff_hunks(
15852 &mut self,
15853 _: &ToggleSelectedDiffHunks,
15854 _window: &mut Window,
15855 cx: &mut Context<Self>,
15856 ) {
15857 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15858 self.toggle_diff_hunks_in_ranges(ranges, cx);
15859 }
15860
15861 pub fn diff_hunks_in_ranges<'a>(
15862 &'a self,
15863 ranges: &'a [Range<Anchor>],
15864 buffer: &'a MultiBufferSnapshot,
15865 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
15866 ranges.iter().flat_map(move |range| {
15867 let end_excerpt_id = range.end.excerpt_id;
15868 let range = range.to_point(buffer);
15869 let mut peek_end = range.end;
15870 if range.end.row < buffer.max_row().0 {
15871 peek_end = Point::new(range.end.row + 1, 0);
15872 }
15873 buffer
15874 .diff_hunks_in_range(range.start..peek_end)
15875 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
15876 })
15877 }
15878
15879 pub fn has_stageable_diff_hunks_in_ranges(
15880 &self,
15881 ranges: &[Range<Anchor>],
15882 snapshot: &MultiBufferSnapshot,
15883 ) -> bool {
15884 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
15885 hunks.any(|hunk| hunk.status().has_secondary_hunk())
15886 }
15887
15888 pub fn toggle_staged_selected_diff_hunks(
15889 &mut self,
15890 _: &::git::ToggleStaged,
15891 _: &mut Window,
15892 cx: &mut Context<Self>,
15893 ) {
15894 let snapshot = self.buffer.read(cx).snapshot(cx);
15895 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15896 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
15897 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15898 }
15899
15900 pub fn set_render_diff_hunk_controls(
15901 &mut self,
15902 render_diff_hunk_controls: RenderDiffHunkControlsFn,
15903 cx: &mut Context<Self>,
15904 ) {
15905 self.render_diff_hunk_controls = render_diff_hunk_controls;
15906 cx.notify();
15907 }
15908
15909 pub fn stage_and_next(
15910 &mut self,
15911 _: &::git::StageAndNext,
15912 window: &mut Window,
15913 cx: &mut Context<Self>,
15914 ) {
15915 self.do_stage_or_unstage_and_next(true, window, cx);
15916 }
15917
15918 pub fn unstage_and_next(
15919 &mut self,
15920 _: &::git::UnstageAndNext,
15921 window: &mut Window,
15922 cx: &mut Context<Self>,
15923 ) {
15924 self.do_stage_or_unstage_and_next(false, window, cx);
15925 }
15926
15927 pub fn stage_or_unstage_diff_hunks(
15928 &mut self,
15929 stage: bool,
15930 ranges: Vec<Range<Anchor>>,
15931 cx: &mut Context<Self>,
15932 ) {
15933 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
15934 cx.spawn(async move |this, cx| {
15935 task.await?;
15936 this.update(cx, |this, cx| {
15937 let snapshot = this.buffer.read(cx).snapshot(cx);
15938 let chunk_by = this
15939 .diff_hunks_in_ranges(&ranges, &snapshot)
15940 .chunk_by(|hunk| hunk.buffer_id);
15941 for (buffer_id, hunks) in &chunk_by {
15942 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
15943 }
15944 })
15945 })
15946 .detach_and_log_err(cx);
15947 }
15948
15949 fn save_buffers_for_ranges_if_needed(
15950 &mut self,
15951 ranges: &[Range<Anchor>],
15952 cx: &mut Context<Editor>,
15953 ) -> Task<Result<()>> {
15954 let multibuffer = self.buffer.read(cx);
15955 let snapshot = multibuffer.read(cx);
15956 let buffer_ids: HashSet<_> = ranges
15957 .iter()
15958 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
15959 .collect();
15960 drop(snapshot);
15961
15962 let mut buffers = HashSet::default();
15963 for buffer_id in buffer_ids {
15964 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
15965 let buffer = buffer_entity.read(cx);
15966 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
15967 {
15968 buffers.insert(buffer_entity);
15969 }
15970 }
15971 }
15972
15973 if let Some(project) = &self.project {
15974 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
15975 } else {
15976 Task::ready(Ok(()))
15977 }
15978 }
15979
15980 fn do_stage_or_unstage_and_next(
15981 &mut self,
15982 stage: bool,
15983 window: &mut Window,
15984 cx: &mut Context<Self>,
15985 ) {
15986 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
15987
15988 if ranges.iter().any(|range| range.start != range.end) {
15989 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15990 return;
15991 }
15992
15993 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15994 let snapshot = self.snapshot(window, cx);
15995 let position = self.selections.newest::<Point>(cx).head();
15996 let mut row = snapshot
15997 .buffer_snapshot
15998 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15999 .find(|hunk| hunk.row_range.start.0 > position.row)
16000 .map(|hunk| hunk.row_range.start);
16001
16002 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16003 // Outside of the project diff editor, wrap around to the beginning.
16004 if !all_diff_hunks_expanded {
16005 row = row.or_else(|| {
16006 snapshot
16007 .buffer_snapshot
16008 .diff_hunks_in_range(Point::zero()..position)
16009 .find(|hunk| hunk.row_range.end.0 < position.row)
16010 .map(|hunk| hunk.row_range.start)
16011 });
16012 }
16013
16014 if let Some(row) = row {
16015 let destination = Point::new(row.0, 0);
16016 let autoscroll = Autoscroll::center();
16017
16018 self.unfold_ranges(&[destination..destination], false, false, cx);
16019 self.change_selections(Some(autoscroll), window, cx, |s| {
16020 s.select_ranges([destination..destination]);
16021 });
16022 }
16023 }
16024
16025 fn do_stage_or_unstage(
16026 &self,
16027 stage: bool,
16028 buffer_id: BufferId,
16029 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16030 cx: &mut App,
16031 ) -> Option<()> {
16032 let project = self.project.as_ref()?;
16033 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16034 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16035 let buffer_snapshot = buffer.read(cx).snapshot();
16036 let file_exists = buffer_snapshot
16037 .file()
16038 .is_some_and(|file| file.disk_state().exists());
16039 diff.update(cx, |diff, cx| {
16040 diff.stage_or_unstage_hunks(
16041 stage,
16042 &hunks
16043 .map(|hunk| buffer_diff::DiffHunk {
16044 buffer_range: hunk.buffer_range,
16045 diff_base_byte_range: hunk.diff_base_byte_range,
16046 secondary_status: hunk.secondary_status,
16047 range: Point::zero()..Point::zero(), // unused
16048 })
16049 .collect::<Vec<_>>(),
16050 &buffer_snapshot,
16051 file_exists,
16052 cx,
16053 )
16054 });
16055 None
16056 }
16057
16058 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16059 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16060 self.buffer
16061 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16062 }
16063
16064 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16065 self.buffer.update(cx, |buffer, cx| {
16066 let ranges = vec![Anchor::min()..Anchor::max()];
16067 if !buffer.all_diff_hunks_expanded()
16068 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16069 {
16070 buffer.collapse_diff_hunks(ranges, cx);
16071 true
16072 } else {
16073 false
16074 }
16075 })
16076 }
16077
16078 fn toggle_diff_hunks_in_ranges(
16079 &mut self,
16080 ranges: Vec<Range<Anchor>>,
16081 cx: &mut Context<Editor>,
16082 ) {
16083 self.buffer.update(cx, |buffer, cx| {
16084 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16085 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16086 })
16087 }
16088
16089 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16090 self.buffer.update(cx, |buffer, cx| {
16091 let snapshot = buffer.snapshot(cx);
16092 let excerpt_id = range.end.excerpt_id;
16093 let point_range = range.to_point(&snapshot);
16094 let expand = !buffer.single_hunk_is_expanded(range, cx);
16095 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16096 })
16097 }
16098
16099 pub(crate) fn apply_all_diff_hunks(
16100 &mut self,
16101 _: &ApplyAllDiffHunks,
16102 window: &mut Window,
16103 cx: &mut Context<Self>,
16104 ) {
16105 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16106
16107 let buffers = self.buffer.read(cx).all_buffers();
16108 for branch_buffer in buffers {
16109 branch_buffer.update(cx, |branch_buffer, cx| {
16110 branch_buffer.merge_into_base(Vec::new(), cx);
16111 });
16112 }
16113
16114 if let Some(project) = self.project.clone() {
16115 self.save(true, project, window, cx).detach_and_log_err(cx);
16116 }
16117 }
16118
16119 pub(crate) fn apply_selected_diff_hunks(
16120 &mut self,
16121 _: &ApplyDiffHunk,
16122 window: &mut Window,
16123 cx: &mut Context<Self>,
16124 ) {
16125 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16126 let snapshot = self.snapshot(window, cx);
16127 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16128 let mut ranges_by_buffer = HashMap::default();
16129 self.transact(window, cx, |editor, _window, cx| {
16130 for hunk in hunks {
16131 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16132 ranges_by_buffer
16133 .entry(buffer.clone())
16134 .or_insert_with(Vec::new)
16135 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16136 }
16137 }
16138
16139 for (buffer, ranges) in ranges_by_buffer {
16140 buffer.update(cx, |buffer, cx| {
16141 buffer.merge_into_base(ranges, cx);
16142 });
16143 }
16144 });
16145
16146 if let Some(project) = self.project.clone() {
16147 self.save(true, project, window, cx).detach_and_log_err(cx);
16148 }
16149 }
16150
16151 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16152 if hovered != self.gutter_hovered {
16153 self.gutter_hovered = hovered;
16154 cx.notify();
16155 }
16156 }
16157
16158 pub fn insert_blocks(
16159 &mut self,
16160 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16161 autoscroll: Option<Autoscroll>,
16162 cx: &mut Context<Self>,
16163 ) -> Vec<CustomBlockId> {
16164 let blocks = self
16165 .display_map
16166 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16167 if let Some(autoscroll) = autoscroll {
16168 self.request_autoscroll(autoscroll, cx);
16169 }
16170 cx.notify();
16171 blocks
16172 }
16173
16174 pub fn resize_blocks(
16175 &mut self,
16176 heights: HashMap<CustomBlockId, u32>,
16177 autoscroll: Option<Autoscroll>,
16178 cx: &mut Context<Self>,
16179 ) {
16180 self.display_map
16181 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16182 if let Some(autoscroll) = autoscroll {
16183 self.request_autoscroll(autoscroll, cx);
16184 }
16185 cx.notify();
16186 }
16187
16188 pub fn replace_blocks(
16189 &mut self,
16190 renderers: HashMap<CustomBlockId, RenderBlock>,
16191 autoscroll: Option<Autoscroll>,
16192 cx: &mut Context<Self>,
16193 ) {
16194 self.display_map
16195 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16196 if let Some(autoscroll) = autoscroll {
16197 self.request_autoscroll(autoscroll, cx);
16198 }
16199 cx.notify();
16200 }
16201
16202 pub fn remove_blocks(
16203 &mut self,
16204 block_ids: HashSet<CustomBlockId>,
16205 autoscroll: Option<Autoscroll>,
16206 cx: &mut Context<Self>,
16207 ) {
16208 self.display_map.update(cx, |display_map, cx| {
16209 display_map.remove_blocks(block_ids, cx)
16210 });
16211 if let Some(autoscroll) = autoscroll {
16212 self.request_autoscroll(autoscroll, cx);
16213 }
16214 cx.notify();
16215 }
16216
16217 pub fn row_for_block(
16218 &self,
16219 block_id: CustomBlockId,
16220 cx: &mut Context<Self>,
16221 ) -> Option<DisplayRow> {
16222 self.display_map
16223 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16224 }
16225
16226 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16227 self.focused_block = Some(focused_block);
16228 }
16229
16230 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16231 self.focused_block.take()
16232 }
16233
16234 pub fn insert_creases(
16235 &mut self,
16236 creases: impl IntoIterator<Item = Crease<Anchor>>,
16237 cx: &mut Context<Self>,
16238 ) -> Vec<CreaseId> {
16239 self.display_map
16240 .update(cx, |map, cx| map.insert_creases(creases, cx))
16241 }
16242
16243 pub fn remove_creases(
16244 &mut self,
16245 ids: impl IntoIterator<Item = CreaseId>,
16246 cx: &mut Context<Self>,
16247 ) -> Vec<(CreaseId, Range<Anchor>)> {
16248 self.display_map
16249 .update(cx, |map, cx| map.remove_creases(ids, cx))
16250 }
16251
16252 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16253 self.display_map
16254 .update(cx, |map, cx| map.snapshot(cx))
16255 .longest_row()
16256 }
16257
16258 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16259 self.display_map
16260 .update(cx, |map, cx| map.snapshot(cx))
16261 .max_point()
16262 }
16263
16264 pub fn text(&self, cx: &App) -> String {
16265 self.buffer.read(cx).read(cx).text()
16266 }
16267
16268 pub fn is_empty(&self, cx: &App) -> bool {
16269 self.buffer.read(cx).read(cx).is_empty()
16270 }
16271
16272 pub fn text_option(&self, cx: &App) -> Option<String> {
16273 let text = self.text(cx);
16274 let text = text.trim();
16275
16276 if text.is_empty() {
16277 return None;
16278 }
16279
16280 Some(text.to_string())
16281 }
16282
16283 pub fn set_text(
16284 &mut self,
16285 text: impl Into<Arc<str>>,
16286 window: &mut Window,
16287 cx: &mut Context<Self>,
16288 ) {
16289 self.transact(window, cx, |this, _, cx| {
16290 this.buffer
16291 .read(cx)
16292 .as_singleton()
16293 .expect("you can only call set_text on editors for singleton buffers")
16294 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16295 });
16296 }
16297
16298 pub fn display_text(&self, cx: &mut App) -> String {
16299 self.display_map
16300 .update(cx, |map, cx| map.snapshot(cx))
16301 .text()
16302 }
16303
16304 fn create_minimap(
16305 &self,
16306 minimap_settings: MinimapSettings,
16307 window: &mut Window,
16308 cx: &mut Context<Self>,
16309 ) -> Option<Entity<Self>> {
16310 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
16311 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
16312 }
16313
16314 fn initialize_new_minimap(
16315 &self,
16316 minimap_settings: MinimapSettings,
16317 window: &mut Window,
16318 cx: &mut Context<Self>,
16319 ) -> Entity<Self> {
16320 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
16321
16322 let mut minimap = Editor::new_internal(
16323 EditorMode::Minimap {
16324 parent: cx.weak_entity(),
16325 },
16326 self.buffer.clone(),
16327 self.project.clone(),
16328 Some(self.display_map.clone()),
16329 window,
16330 cx,
16331 );
16332 minimap.scroll_manager.clone_state(&self.scroll_manager);
16333 minimap.set_text_style_refinement(TextStyleRefinement {
16334 font_size: Some(MINIMAP_FONT_SIZE),
16335 font_weight: Some(MINIMAP_FONT_WEIGHT),
16336 ..Default::default()
16337 });
16338 minimap.update_minimap_configuration(minimap_settings, cx);
16339 cx.new(|_| minimap)
16340 }
16341
16342 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
16343 let current_line_highlight = minimap_settings
16344 .current_line_highlight
16345 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
16346 self.set_current_line_highlight(Some(current_line_highlight));
16347 }
16348
16349 pub fn minimap(&self) -> Option<&Entity<Self>> {
16350 self.minimap.as_ref().filter(|_| self.show_minimap)
16351 }
16352
16353 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16354 let mut wrap_guides = smallvec::smallvec![];
16355
16356 if self.show_wrap_guides == Some(false) {
16357 return wrap_guides;
16358 }
16359
16360 let settings = self.buffer.read(cx).language_settings(cx);
16361 if settings.show_wrap_guides {
16362 match self.soft_wrap_mode(cx) {
16363 SoftWrap::Column(soft_wrap) => {
16364 wrap_guides.push((soft_wrap as usize, true));
16365 }
16366 SoftWrap::Bounded(soft_wrap) => {
16367 wrap_guides.push((soft_wrap as usize, true));
16368 }
16369 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16370 }
16371 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16372 }
16373
16374 wrap_guides
16375 }
16376
16377 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16378 let settings = self.buffer.read(cx).language_settings(cx);
16379 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16380 match mode {
16381 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16382 SoftWrap::None
16383 }
16384 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16385 language_settings::SoftWrap::PreferredLineLength => {
16386 SoftWrap::Column(settings.preferred_line_length)
16387 }
16388 language_settings::SoftWrap::Bounded => {
16389 SoftWrap::Bounded(settings.preferred_line_length)
16390 }
16391 }
16392 }
16393
16394 pub fn set_soft_wrap_mode(
16395 &mut self,
16396 mode: language_settings::SoftWrap,
16397
16398 cx: &mut Context<Self>,
16399 ) {
16400 self.soft_wrap_mode_override = Some(mode);
16401 cx.notify();
16402 }
16403
16404 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16405 self.hard_wrap = hard_wrap;
16406 cx.notify();
16407 }
16408
16409 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16410 self.text_style_refinement = Some(style);
16411 }
16412
16413 /// called by the Element so we know what style we were most recently rendered with.
16414 pub(crate) fn set_style(
16415 &mut self,
16416 style: EditorStyle,
16417 window: &mut Window,
16418 cx: &mut Context<Self>,
16419 ) {
16420 // We intentionally do not inform the display map about the minimap style
16421 // so that wrapping is not recalculated and stays consistent for the editor
16422 // and its linked minimap.
16423 if !self.mode.is_minimap() {
16424 let rem_size = window.rem_size();
16425 self.display_map.update(cx, |map, cx| {
16426 map.set_font(
16427 style.text.font(),
16428 style.text.font_size.to_pixels(rem_size),
16429 cx,
16430 )
16431 });
16432 }
16433 self.style = Some(style);
16434 }
16435
16436 pub fn style(&self) -> Option<&EditorStyle> {
16437 self.style.as_ref()
16438 }
16439
16440 // Called by the element. This method is not designed to be called outside of the editor
16441 // element's layout code because it does not notify when rewrapping is computed synchronously.
16442 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16443 self.display_map
16444 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16445 }
16446
16447 pub fn set_soft_wrap(&mut self) {
16448 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16449 }
16450
16451 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16452 if self.soft_wrap_mode_override.is_some() {
16453 self.soft_wrap_mode_override.take();
16454 } else {
16455 let soft_wrap = match self.soft_wrap_mode(cx) {
16456 SoftWrap::GitDiff => return,
16457 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16458 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16459 language_settings::SoftWrap::None
16460 }
16461 };
16462 self.soft_wrap_mode_override = Some(soft_wrap);
16463 }
16464 cx.notify();
16465 }
16466
16467 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16468 let Some(workspace) = self.workspace() else {
16469 return;
16470 };
16471 let fs = workspace.read(cx).app_state().fs.clone();
16472 let current_show = TabBarSettings::get_global(cx).show;
16473 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16474 setting.show = Some(!current_show);
16475 });
16476 }
16477
16478 pub fn toggle_indent_guides(
16479 &mut self,
16480 _: &ToggleIndentGuides,
16481 _: &mut Window,
16482 cx: &mut Context<Self>,
16483 ) {
16484 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16485 self.buffer
16486 .read(cx)
16487 .language_settings(cx)
16488 .indent_guides
16489 .enabled
16490 });
16491 self.show_indent_guides = Some(!currently_enabled);
16492 cx.notify();
16493 }
16494
16495 fn should_show_indent_guides(&self) -> Option<bool> {
16496 self.show_indent_guides
16497 }
16498
16499 pub fn toggle_line_numbers(
16500 &mut self,
16501 _: &ToggleLineNumbers,
16502 _: &mut Window,
16503 cx: &mut Context<Self>,
16504 ) {
16505 let mut editor_settings = EditorSettings::get_global(cx).clone();
16506 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16507 EditorSettings::override_global(editor_settings, cx);
16508 }
16509
16510 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16511 if let Some(show_line_numbers) = self.show_line_numbers {
16512 return show_line_numbers;
16513 }
16514 EditorSettings::get_global(cx).gutter.line_numbers
16515 }
16516
16517 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16518 self.use_relative_line_numbers
16519 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16520 }
16521
16522 pub fn toggle_relative_line_numbers(
16523 &mut self,
16524 _: &ToggleRelativeLineNumbers,
16525 _: &mut Window,
16526 cx: &mut Context<Self>,
16527 ) {
16528 let is_relative = self.should_use_relative_line_numbers(cx);
16529 self.set_relative_line_number(Some(!is_relative), cx)
16530 }
16531
16532 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16533 self.use_relative_line_numbers = is_relative;
16534 cx.notify();
16535 }
16536
16537 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16538 self.show_gutter = show_gutter;
16539 cx.notify();
16540 }
16541
16542 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
16543 self.show_scrollbars = show_scrollbars;
16544 cx.notify();
16545 }
16546
16547 pub fn set_show_minimap(&mut self, show_minimap: bool, cx: &mut Context<Self>) {
16548 self.show_minimap = show_minimap;
16549 cx.notify();
16550 }
16551
16552 pub fn disable_scrollbars_and_minimap(&mut self, cx: &mut Context<Self>) {
16553 self.set_show_scrollbars(false, cx);
16554 self.set_show_minimap(false, cx);
16555 }
16556
16557 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
16558 self.show_line_numbers = Some(show_line_numbers);
16559 cx.notify();
16560 }
16561
16562 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
16563 self.disable_expand_excerpt_buttons = true;
16564 cx.notify();
16565 }
16566
16567 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
16568 self.show_git_diff_gutter = Some(show_git_diff_gutter);
16569 cx.notify();
16570 }
16571
16572 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
16573 self.show_code_actions = Some(show_code_actions);
16574 cx.notify();
16575 }
16576
16577 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
16578 self.show_runnables = Some(show_runnables);
16579 cx.notify();
16580 }
16581
16582 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
16583 self.show_breakpoints = Some(show_breakpoints);
16584 cx.notify();
16585 }
16586
16587 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
16588 if self.display_map.read(cx).masked != masked {
16589 self.display_map.update(cx, |map, _| map.masked = masked);
16590 }
16591 cx.notify()
16592 }
16593
16594 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
16595 self.show_wrap_guides = Some(show_wrap_guides);
16596 cx.notify();
16597 }
16598
16599 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
16600 self.show_indent_guides = Some(show_indent_guides);
16601 cx.notify();
16602 }
16603
16604 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
16605 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
16606 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
16607 if let Some(dir) = file.abs_path(cx).parent() {
16608 return Some(dir.to_owned());
16609 }
16610 }
16611
16612 if let Some(project_path) = buffer.read(cx).project_path(cx) {
16613 return Some(project_path.path.to_path_buf());
16614 }
16615 }
16616
16617 None
16618 }
16619
16620 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
16621 self.active_excerpt(cx)?
16622 .1
16623 .read(cx)
16624 .file()
16625 .and_then(|f| f.as_local())
16626 }
16627
16628 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16629 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16630 let buffer = buffer.read(cx);
16631 if let Some(project_path) = buffer.project_path(cx) {
16632 let project = self.project.as_ref()?.read(cx);
16633 project.absolute_path(&project_path, cx)
16634 } else {
16635 buffer
16636 .file()
16637 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
16638 }
16639 })
16640 }
16641
16642 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16643 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16644 let project_path = buffer.read(cx).project_path(cx)?;
16645 let project = self.project.as_ref()?.read(cx);
16646 let entry = project.entry_for_path(&project_path, cx)?;
16647 let path = entry.path.to_path_buf();
16648 Some(path)
16649 })
16650 }
16651
16652 pub fn reveal_in_finder(
16653 &mut self,
16654 _: &RevealInFileManager,
16655 _window: &mut Window,
16656 cx: &mut Context<Self>,
16657 ) {
16658 if let Some(target) = self.target_file(cx) {
16659 cx.reveal_path(&target.abs_path(cx));
16660 }
16661 }
16662
16663 pub fn copy_path(
16664 &mut self,
16665 _: &zed_actions::workspace::CopyPath,
16666 _window: &mut Window,
16667 cx: &mut Context<Self>,
16668 ) {
16669 if let Some(path) = self.target_file_abs_path(cx) {
16670 if let Some(path) = path.to_str() {
16671 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16672 }
16673 }
16674 }
16675
16676 pub fn copy_relative_path(
16677 &mut self,
16678 _: &zed_actions::workspace::CopyRelativePath,
16679 _window: &mut Window,
16680 cx: &mut Context<Self>,
16681 ) {
16682 if let Some(path) = self.target_file_path(cx) {
16683 if let Some(path) = path.to_str() {
16684 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16685 }
16686 }
16687 }
16688
16689 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
16690 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
16691 buffer.read(cx).project_path(cx)
16692 } else {
16693 None
16694 }
16695 }
16696
16697 // Returns true if the editor handled a go-to-line request
16698 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
16699 maybe!({
16700 let breakpoint_store = self.breakpoint_store.as_ref()?;
16701
16702 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
16703 else {
16704 self.clear_row_highlights::<ActiveDebugLine>();
16705 return None;
16706 };
16707
16708 let position = active_stack_frame.position;
16709 let buffer_id = position.buffer_id?;
16710 let snapshot = self
16711 .project
16712 .as_ref()?
16713 .read(cx)
16714 .buffer_for_id(buffer_id, cx)?
16715 .read(cx)
16716 .snapshot();
16717
16718 let mut handled = false;
16719 for (id, ExcerptRange { context, .. }) in
16720 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
16721 {
16722 if context.start.cmp(&position, &snapshot).is_ge()
16723 || context.end.cmp(&position, &snapshot).is_lt()
16724 {
16725 continue;
16726 }
16727 let snapshot = self.buffer.read(cx).snapshot(cx);
16728 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
16729
16730 handled = true;
16731 self.clear_row_highlights::<ActiveDebugLine>();
16732 self.go_to_line::<ActiveDebugLine>(
16733 multibuffer_anchor,
16734 Some(cx.theme().colors().editor_debugger_active_line_background),
16735 window,
16736 cx,
16737 );
16738
16739 cx.notify();
16740 }
16741
16742 handled.then_some(())
16743 })
16744 .is_some()
16745 }
16746
16747 pub fn copy_file_name_without_extension(
16748 &mut self,
16749 _: &CopyFileNameWithoutExtension,
16750 _: &mut Window,
16751 cx: &mut Context<Self>,
16752 ) {
16753 if let Some(file) = self.target_file(cx) {
16754 if let Some(file_stem) = file.path().file_stem() {
16755 if let Some(name) = file_stem.to_str() {
16756 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16757 }
16758 }
16759 }
16760 }
16761
16762 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
16763 if let Some(file) = self.target_file(cx) {
16764 if let Some(file_name) = file.path().file_name() {
16765 if let Some(name) = file_name.to_str() {
16766 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16767 }
16768 }
16769 }
16770 }
16771
16772 pub fn toggle_git_blame(
16773 &mut self,
16774 _: &::git::Blame,
16775 window: &mut Window,
16776 cx: &mut Context<Self>,
16777 ) {
16778 self.show_git_blame_gutter = !self.show_git_blame_gutter;
16779
16780 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
16781 self.start_git_blame(true, window, cx);
16782 }
16783
16784 cx.notify();
16785 }
16786
16787 pub fn toggle_git_blame_inline(
16788 &mut self,
16789 _: &ToggleGitBlameInline,
16790 window: &mut Window,
16791 cx: &mut Context<Self>,
16792 ) {
16793 self.toggle_git_blame_inline_internal(true, window, cx);
16794 cx.notify();
16795 }
16796
16797 pub fn open_git_blame_commit(
16798 &mut self,
16799 _: &OpenGitBlameCommit,
16800 window: &mut Window,
16801 cx: &mut Context<Self>,
16802 ) {
16803 self.open_git_blame_commit_internal(window, cx);
16804 }
16805
16806 fn open_git_blame_commit_internal(
16807 &mut self,
16808 window: &mut Window,
16809 cx: &mut Context<Self>,
16810 ) -> Option<()> {
16811 let blame = self.blame.as_ref()?;
16812 let snapshot = self.snapshot(window, cx);
16813 let cursor = self.selections.newest::<Point>(cx).head();
16814 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
16815 let blame_entry = blame
16816 .update(cx, |blame, cx| {
16817 blame
16818 .blame_for_rows(
16819 &[RowInfo {
16820 buffer_id: Some(buffer.remote_id()),
16821 buffer_row: Some(point.row),
16822 ..Default::default()
16823 }],
16824 cx,
16825 )
16826 .next()
16827 })
16828 .flatten()?;
16829 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
16830 let repo = blame.read(cx).repository(cx)?;
16831 let workspace = self.workspace()?.downgrade();
16832 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
16833 None
16834 }
16835
16836 pub fn git_blame_inline_enabled(&self) -> bool {
16837 self.git_blame_inline_enabled
16838 }
16839
16840 pub fn toggle_selection_menu(
16841 &mut self,
16842 _: &ToggleSelectionMenu,
16843 _: &mut Window,
16844 cx: &mut Context<Self>,
16845 ) {
16846 self.show_selection_menu = self
16847 .show_selection_menu
16848 .map(|show_selections_menu| !show_selections_menu)
16849 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
16850
16851 cx.notify();
16852 }
16853
16854 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
16855 self.show_selection_menu
16856 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
16857 }
16858
16859 fn start_git_blame(
16860 &mut self,
16861 user_triggered: bool,
16862 window: &mut Window,
16863 cx: &mut Context<Self>,
16864 ) {
16865 if let Some(project) = self.project.as_ref() {
16866 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
16867 return;
16868 };
16869
16870 if buffer.read(cx).file().is_none() {
16871 return;
16872 }
16873
16874 let focused = self.focus_handle(cx).contains_focused(window, cx);
16875
16876 let project = project.clone();
16877 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
16878 self.blame_subscription =
16879 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
16880 self.blame = Some(blame);
16881 }
16882 }
16883
16884 fn toggle_git_blame_inline_internal(
16885 &mut self,
16886 user_triggered: bool,
16887 window: &mut Window,
16888 cx: &mut Context<Self>,
16889 ) {
16890 if self.git_blame_inline_enabled {
16891 self.git_blame_inline_enabled = false;
16892 self.show_git_blame_inline = false;
16893 self.show_git_blame_inline_delay_task.take();
16894 } else {
16895 self.git_blame_inline_enabled = true;
16896 self.start_git_blame_inline(user_triggered, window, cx);
16897 }
16898
16899 cx.notify();
16900 }
16901
16902 fn start_git_blame_inline(
16903 &mut self,
16904 user_triggered: bool,
16905 window: &mut Window,
16906 cx: &mut Context<Self>,
16907 ) {
16908 self.start_git_blame(user_triggered, window, cx);
16909
16910 if ProjectSettings::get_global(cx)
16911 .git
16912 .inline_blame_delay()
16913 .is_some()
16914 {
16915 self.start_inline_blame_timer(window, cx);
16916 } else {
16917 self.show_git_blame_inline = true
16918 }
16919 }
16920
16921 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
16922 self.blame.as_ref()
16923 }
16924
16925 pub fn show_git_blame_gutter(&self) -> bool {
16926 self.show_git_blame_gutter
16927 }
16928
16929 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
16930 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
16931 }
16932
16933 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
16934 self.show_git_blame_inline
16935 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
16936 && !self.newest_selection_head_on_empty_line(cx)
16937 && self.has_blame_entries(cx)
16938 }
16939
16940 fn has_blame_entries(&self, cx: &App) -> bool {
16941 self.blame()
16942 .map_or(false, |blame| blame.read(cx).has_generated_entries())
16943 }
16944
16945 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
16946 let cursor_anchor = self.selections.newest_anchor().head();
16947
16948 let snapshot = self.buffer.read(cx).snapshot(cx);
16949 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
16950
16951 snapshot.line_len(buffer_row) == 0
16952 }
16953
16954 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
16955 let buffer_and_selection = maybe!({
16956 let selection = self.selections.newest::<Point>(cx);
16957 let selection_range = selection.range();
16958
16959 let multi_buffer = self.buffer().read(cx);
16960 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16961 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
16962
16963 let (buffer, range, _) = if selection.reversed {
16964 buffer_ranges.first()
16965 } else {
16966 buffer_ranges.last()
16967 }?;
16968
16969 let selection = text::ToPoint::to_point(&range.start, &buffer).row
16970 ..text::ToPoint::to_point(&range.end, &buffer).row;
16971 Some((
16972 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
16973 selection,
16974 ))
16975 });
16976
16977 let Some((buffer, selection)) = buffer_and_selection else {
16978 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
16979 };
16980
16981 let Some(project) = self.project.as_ref() else {
16982 return Task::ready(Err(anyhow!("editor does not have project")));
16983 };
16984
16985 project.update(cx, |project, cx| {
16986 project.get_permalink_to_line(&buffer, selection, cx)
16987 })
16988 }
16989
16990 pub fn copy_permalink_to_line(
16991 &mut self,
16992 _: &CopyPermalinkToLine,
16993 window: &mut Window,
16994 cx: &mut Context<Self>,
16995 ) {
16996 let permalink_task = self.get_permalink_to_line(cx);
16997 let workspace = self.workspace();
16998
16999 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17000 Ok(permalink) => {
17001 cx.update(|_, cx| {
17002 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17003 })
17004 .ok();
17005 }
17006 Err(err) => {
17007 let message = format!("Failed to copy permalink: {err}");
17008
17009 Err::<(), anyhow::Error>(err).log_err();
17010
17011 if let Some(workspace) = workspace {
17012 workspace
17013 .update_in(cx, |workspace, _, cx| {
17014 struct CopyPermalinkToLine;
17015
17016 workspace.show_toast(
17017 Toast::new(
17018 NotificationId::unique::<CopyPermalinkToLine>(),
17019 message,
17020 ),
17021 cx,
17022 )
17023 })
17024 .ok();
17025 }
17026 }
17027 })
17028 .detach();
17029 }
17030
17031 pub fn copy_file_location(
17032 &mut self,
17033 _: &CopyFileLocation,
17034 _: &mut Window,
17035 cx: &mut Context<Self>,
17036 ) {
17037 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17038 if let Some(file) = self.target_file(cx) {
17039 if let Some(path) = file.path().to_str() {
17040 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17041 }
17042 }
17043 }
17044
17045 pub fn open_permalink_to_line(
17046 &mut self,
17047 _: &OpenPermalinkToLine,
17048 window: &mut Window,
17049 cx: &mut Context<Self>,
17050 ) {
17051 let permalink_task = self.get_permalink_to_line(cx);
17052 let workspace = self.workspace();
17053
17054 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17055 Ok(permalink) => {
17056 cx.update(|_, cx| {
17057 cx.open_url(permalink.as_ref());
17058 })
17059 .ok();
17060 }
17061 Err(err) => {
17062 let message = format!("Failed to open permalink: {err}");
17063
17064 Err::<(), anyhow::Error>(err).log_err();
17065
17066 if let Some(workspace) = workspace {
17067 workspace
17068 .update(cx, |workspace, cx| {
17069 struct OpenPermalinkToLine;
17070
17071 workspace.show_toast(
17072 Toast::new(
17073 NotificationId::unique::<OpenPermalinkToLine>(),
17074 message,
17075 ),
17076 cx,
17077 )
17078 })
17079 .ok();
17080 }
17081 }
17082 })
17083 .detach();
17084 }
17085
17086 pub fn insert_uuid_v4(
17087 &mut self,
17088 _: &InsertUuidV4,
17089 window: &mut Window,
17090 cx: &mut Context<Self>,
17091 ) {
17092 self.insert_uuid(UuidVersion::V4, window, cx);
17093 }
17094
17095 pub fn insert_uuid_v7(
17096 &mut self,
17097 _: &InsertUuidV7,
17098 window: &mut Window,
17099 cx: &mut Context<Self>,
17100 ) {
17101 self.insert_uuid(UuidVersion::V7, window, cx);
17102 }
17103
17104 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17105 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17106 self.transact(window, cx, |this, window, cx| {
17107 let edits = this
17108 .selections
17109 .all::<Point>(cx)
17110 .into_iter()
17111 .map(|selection| {
17112 let uuid = match version {
17113 UuidVersion::V4 => uuid::Uuid::new_v4(),
17114 UuidVersion::V7 => uuid::Uuid::now_v7(),
17115 };
17116
17117 (selection.range(), uuid.to_string())
17118 });
17119 this.edit(edits, cx);
17120 this.refresh_inline_completion(true, false, window, cx);
17121 });
17122 }
17123
17124 pub fn open_selections_in_multibuffer(
17125 &mut self,
17126 _: &OpenSelectionsInMultibuffer,
17127 window: &mut Window,
17128 cx: &mut Context<Self>,
17129 ) {
17130 let multibuffer = self.buffer.read(cx);
17131
17132 let Some(buffer) = multibuffer.as_singleton() else {
17133 return;
17134 };
17135
17136 let Some(workspace) = self.workspace() else {
17137 return;
17138 };
17139
17140 let locations = self
17141 .selections
17142 .disjoint_anchors()
17143 .iter()
17144 .map(|range| Location {
17145 buffer: buffer.clone(),
17146 range: range.start.text_anchor..range.end.text_anchor,
17147 })
17148 .collect::<Vec<_>>();
17149
17150 let title = multibuffer.title(cx).to_string();
17151
17152 cx.spawn_in(window, async move |_, cx| {
17153 workspace.update_in(cx, |workspace, window, cx| {
17154 Self::open_locations_in_multibuffer(
17155 workspace,
17156 locations,
17157 format!("Selections for '{title}'"),
17158 false,
17159 MultibufferSelectionMode::All,
17160 window,
17161 cx,
17162 );
17163 })
17164 })
17165 .detach();
17166 }
17167
17168 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17169 /// last highlight added will be used.
17170 ///
17171 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17172 pub fn highlight_rows<T: 'static>(
17173 &mut self,
17174 range: Range<Anchor>,
17175 color: Hsla,
17176 options: RowHighlightOptions,
17177 cx: &mut Context<Self>,
17178 ) {
17179 let snapshot = self.buffer().read(cx).snapshot(cx);
17180 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17181 let ix = row_highlights.binary_search_by(|highlight| {
17182 Ordering::Equal
17183 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17184 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17185 });
17186
17187 if let Err(mut ix) = ix {
17188 let index = post_inc(&mut self.highlight_order);
17189
17190 // If this range intersects with the preceding highlight, then merge it with
17191 // the preceding highlight. Otherwise insert a new highlight.
17192 let mut merged = false;
17193 if ix > 0 {
17194 let prev_highlight = &mut row_highlights[ix - 1];
17195 if prev_highlight
17196 .range
17197 .end
17198 .cmp(&range.start, &snapshot)
17199 .is_ge()
17200 {
17201 ix -= 1;
17202 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17203 prev_highlight.range.end = range.end;
17204 }
17205 merged = true;
17206 prev_highlight.index = index;
17207 prev_highlight.color = color;
17208 prev_highlight.options = options;
17209 }
17210 }
17211
17212 if !merged {
17213 row_highlights.insert(
17214 ix,
17215 RowHighlight {
17216 range: range.clone(),
17217 index,
17218 color,
17219 options,
17220 type_id: TypeId::of::<T>(),
17221 },
17222 );
17223 }
17224
17225 // If any of the following highlights intersect with this one, merge them.
17226 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17227 let highlight = &row_highlights[ix];
17228 if next_highlight
17229 .range
17230 .start
17231 .cmp(&highlight.range.end, &snapshot)
17232 .is_le()
17233 {
17234 if next_highlight
17235 .range
17236 .end
17237 .cmp(&highlight.range.end, &snapshot)
17238 .is_gt()
17239 {
17240 row_highlights[ix].range.end = next_highlight.range.end;
17241 }
17242 row_highlights.remove(ix + 1);
17243 } else {
17244 break;
17245 }
17246 }
17247 }
17248 }
17249
17250 /// Remove any highlighted row ranges of the given type that intersect the
17251 /// given ranges.
17252 pub fn remove_highlighted_rows<T: 'static>(
17253 &mut self,
17254 ranges_to_remove: Vec<Range<Anchor>>,
17255 cx: &mut Context<Self>,
17256 ) {
17257 let snapshot = self.buffer().read(cx).snapshot(cx);
17258 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17259 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17260 row_highlights.retain(|highlight| {
17261 while let Some(range_to_remove) = ranges_to_remove.peek() {
17262 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17263 Ordering::Less | Ordering::Equal => {
17264 ranges_to_remove.next();
17265 }
17266 Ordering::Greater => {
17267 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17268 Ordering::Less | Ordering::Equal => {
17269 return false;
17270 }
17271 Ordering::Greater => break,
17272 }
17273 }
17274 }
17275 }
17276
17277 true
17278 })
17279 }
17280
17281 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17282 pub fn clear_row_highlights<T: 'static>(&mut self) {
17283 self.highlighted_rows.remove(&TypeId::of::<T>());
17284 }
17285
17286 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17287 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17288 self.highlighted_rows
17289 .get(&TypeId::of::<T>())
17290 .map_or(&[] as &[_], |vec| vec.as_slice())
17291 .iter()
17292 .map(|highlight| (highlight.range.clone(), highlight.color))
17293 }
17294
17295 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17296 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17297 /// Allows to ignore certain kinds of highlights.
17298 pub fn highlighted_display_rows(
17299 &self,
17300 window: &mut Window,
17301 cx: &mut App,
17302 ) -> BTreeMap<DisplayRow, LineHighlight> {
17303 let snapshot = self.snapshot(window, cx);
17304 let mut used_highlight_orders = HashMap::default();
17305 self.highlighted_rows
17306 .iter()
17307 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17308 .fold(
17309 BTreeMap::<DisplayRow, LineHighlight>::new(),
17310 |mut unique_rows, highlight| {
17311 let start = highlight.range.start.to_display_point(&snapshot);
17312 let end = highlight.range.end.to_display_point(&snapshot);
17313 let start_row = start.row().0;
17314 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17315 && end.column() == 0
17316 {
17317 end.row().0.saturating_sub(1)
17318 } else {
17319 end.row().0
17320 };
17321 for row in start_row..=end_row {
17322 let used_index =
17323 used_highlight_orders.entry(row).or_insert(highlight.index);
17324 if highlight.index >= *used_index {
17325 *used_index = highlight.index;
17326 unique_rows.insert(
17327 DisplayRow(row),
17328 LineHighlight {
17329 include_gutter: highlight.options.include_gutter,
17330 border: None,
17331 background: highlight.color.into(),
17332 type_id: Some(highlight.type_id),
17333 },
17334 );
17335 }
17336 }
17337 unique_rows
17338 },
17339 )
17340 }
17341
17342 pub fn highlighted_display_row_for_autoscroll(
17343 &self,
17344 snapshot: &DisplaySnapshot,
17345 ) -> Option<DisplayRow> {
17346 self.highlighted_rows
17347 .values()
17348 .flat_map(|highlighted_rows| highlighted_rows.iter())
17349 .filter_map(|highlight| {
17350 if highlight.options.autoscroll {
17351 Some(highlight.range.start.to_display_point(snapshot).row())
17352 } else {
17353 None
17354 }
17355 })
17356 .min()
17357 }
17358
17359 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17360 self.highlight_background::<SearchWithinRange>(
17361 ranges,
17362 |colors| colors.editor_document_highlight_read_background,
17363 cx,
17364 )
17365 }
17366
17367 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17368 self.breadcrumb_header = Some(new_header);
17369 }
17370
17371 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17372 self.clear_background_highlights::<SearchWithinRange>(cx);
17373 }
17374
17375 pub fn highlight_background<T: 'static>(
17376 &mut self,
17377 ranges: &[Range<Anchor>],
17378 color_fetcher: fn(&ThemeColors) -> Hsla,
17379 cx: &mut Context<Self>,
17380 ) {
17381 self.background_highlights
17382 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17383 self.scrollbar_marker_state.dirty = true;
17384 cx.notify();
17385 }
17386
17387 pub fn clear_background_highlights<T: 'static>(
17388 &mut self,
17389 cx: &mut Context<Self>,
17390 ) -> Option<BackgroundHighlight> {
17391 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17392 if !text_highlights.1.is_empty() {
17393 self.scrollbar_marker_state.dirty = true;
17394 cx.notify();
17395 }
17396 Some(text_highlights)
17397 }
17398
17399 pub fn highlight_gutter<T: 'static>(
17400 &mut self,
17401 ranges: &[Range<Anchor>],
17402 color_fetcher: fn(&App) -> Hsla,
17403 cx: &mut Context<Self>,
17404 ) {
17405 self.gutter_highlights
17406 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17407 cx.notify();
17408 }
17409
17410 pub fn clear_gutter_highlights<T: 'static>(
17411 &mut self,
17412 cx: &mut Context<Self>,
17413 ) -> Option<GutterHighlight> {
17414 cx.notify();
17415 self.gutter_highlights.remove(&TypeId::of::<T>())
17416 }
17417
17418 #[cfg(feature = "test-support")]
17419 pub fn all_text_background_highlights(
17420 &self,
17421 window: &mut Window,
17422 cx: &mut Context<Self>,
17423 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17424 let snapshot = self.snapshot(window, cx);
17425 let buffer = &snapshot.buffer_snapshot;
17426 let start = buffer.anchor_before(0);
17427 let end = buffer.anchor_after(buffer.len());
17428 let theme = cx.theme().colors();
17429 self.background_highlights_in_range(start..end, &snapshot, theme)
17430 }
17431
17432 #[cfg(feature = "test-support")]
17433 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17434 let snapshot = self.buffer().read(cx).snapshot(cx);
17435
17436 let highlights = self
17437 .background_highlights
17438 .get(&TypeId::of::<items::BufferSearchHighlights>());
17439
17440 if let Some((_color, ranges)) = highlights {
17441 ranges
17442 .iter()
17443 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17444 .collect_vec()
17445 } else {
17446 vec![]
17447 }
17448 }
17449
17450 fn document_highlights_for_position<'a>(
17451 &'a self,
17452 position: Anchor,
17453 buffer: &'a MultiBufferSnapshot,
17454 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17455 let read_highlights = self
17456 .background_highlights
17457 .get(&TypeId::of::<DocumentHighlightRead>())
17458 .map(|h| &h.1);
17459 let write_highlights = self
17460 .background_highlights
17461 .get(&TypeId::of::<DocumentHighlightWrite>())
17462 .map(|h| &h.1);
17463 let left_position = position.bias_left(buffer);
17464 let right_position = position.bias_right(buffer);
17465 read_highlights
17466 .into_iter()
17467 .chain(write_highlights)
17468 .flat_map(move |ranges| {
17469 let start_ix = match ranges.binary_search_by(|probe| {
17470 let cmp = probe.end.cmp(&left_position, buffer);
17471 if cmp.is_ge() {
17472 Ordering::Greater
17473 } else {
17474 Ordering::Less
17475 }
17476 }) {
17477 Ok(i) | Err(i) => i,
17478 };
17479
17480 ranges[start_ix..]
17481 .iter()
17482 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17483 })
17484 }
17485
17486 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17487 self.background_highlights
17488 .get(&TypeId::of::<T>())
17489 .map_or(false, |(_, highlights)| !highlights.is_empty())
17490 }
17491
17492 pub fn background_highlights_in_range(
17493 &self,
17494 search_range: Range<Anchor>,
17495 display_snapshot: &DisplaySnapshot,
17496 theme: &ThemeColors,
17497 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17498 let mut results = Vec::new();
17499 for (color_fetcher, ranges) in self.background_highlights.values() {
17500 let color = color_fetcher(theme);
17501 let start_ix = match ranges.binary_search_by(|probe| {
17502 let cmp = probe
17503 .end
17504 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17505 if cmp.is_gt() {
17506 Ordering::Greater
17507 } else {
17508 Ordering::Less
17509 }
17510 }) {
17511 Ok(i) | Err(i) => i,
17512 };
17513 for range in &ranges[start_ix..] {
17514 if range
17515 .start
17516 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17517 .is_ge()
17518 {
17519 break;
17520 }
17521
17522 let start = range.start.to_display_point(display_snapshot);
17523 let end = range.end.to_display_point(display_snapshot);
17524 results.push((start..end, color))
17525 }
17526 }
17527 results
17528 }
17529
17530 pub fn background_highlight_row_ranges<T: 'static>(
17531 &self,
17532 search_range: Range<Anchor>,
17533 display_snapshot: &DisplaySnapshot,
17534 count: usize,
17535 ) -> Vec<RangeInclusive<DisplayPoint>> {
17536 let mut results = Vec::new();
17537 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
17538 return vec![];
17539 };
17540
17541 let start_ix = match ranges.binary_search_by(|probe| {
17542 let cmp = probe
17543 .end
17544 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17545 if cmp.is_gt() {
17546 Ordering::Greater
17547 } else {
17548 Ordering::Less
17549 }
17550 }) {
17551 Ok(i) | Err(i) => i,
17552 };
17553 let mut push_region = |start: Option<Point>, end: Option<Point>| {
17554 if let (Some(start_display), Some(end_display)) = (start, end) {
17555 results.push(
17556 start_display.to_display_point(display_snapshot)
17557 ..=end_display.to_display_point(display_snapshot),
17558 );
17559 }
17560 };
17561 let mut start_row: Option<Point> = None;
17562 let mut end_row: Option<Point> = None;
17563 if ranges.len() > count {
17564 return Vec::new();
17565 }
17566 for range in &ranges[start_ix..] {
17567 if range
17568 .start
17569 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17570 .is_ge()
17571 {
17572 break;
17573 }
17574 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
17575 if let Some(current_row) = &end_row {
17576 if end.row == current_row.row {
17577 continue;
17578 }
17579 }
17580 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
17581 if start_row.is_none() {
17582 assert_eq!(end_row, None);
17583 start_row = Some(start);
17584 end_row = Some(end);
17585 continue;
17586 }
17587 if let Some(current_end) = end_row.as_mut() {
17588 if start.row > current_end.row + 1 {
17589 push_region(start_row, end_row);
17590 start_row = Some(start);
17591 end_row = Some(end);
17592 } else {
17593 // Merge two hunks.
17594 *current_end = end;
17595 }
17596 } else {
17597 unreachable!();
17598 }
17599 }
17600 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
17601 push_region(start_row, end_row);
17602 results
17603 }
17604
17605 pub fn gutter_highlights_in_range(
17606 &self,
17607 search_range: Range<Anchor>,
17608 display_snapshot: &DisplaySnapshot,
17609 cx: &App,
17610 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17611 let mut results = Vec::new();
17612 for (color_fetcher, ranges) in self.gutter_highlights.values() {
17613 let color = color_fetcher(cx);
17614 let start_ix = match ranges.binary_search_by(|probe| {
17615 let cmp = probe
17616 .end
17617 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17618 if cmp.is_gt() {
17619 Ordering::Greater
17620 } else {
17621 Ordering::Less
17622 }
17623 }) {
17624 Ok(i) | Err(i) => i,
17625 };
17626 for range in &ranges[start_ix..] {
17627 if range
17628 .start
17629 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17630 .is_ge()
17631 {
17632 break;
17633 }
17634
17635 let start = range.start.to_display_point(display_snapshot);
17636 let end = range.end.to_display_point(display_snapshot);
17637 results.push((start..end, color))
17638 }
17639 }
17640 results
17641 }
17642
17643 /// Get the text ranges corresponding to the redaction query
17644 pub fn redacted_ranges(
17645 &self,
17646 search_range: Range<Anchor>,
17647 display_snapshot: &DisplaySnapshot,
17648 cx: &App,
17649 ) -> Vec<Range<DisplayPoint>> {
17650 display_snapshot
17651 .buffer_snapshot
17652 .redacted_ranges(search_range, |file| {
17653 if let Some(file) = file {
17654 file.is_private()
17655 && EditorSettings::get(
17656 Some(SettingsLocation {
17657 worktree_id: file.worktree_id(cx),
17658 path: file.path().as_ref(),
17659 }),
17660 cx,
17661 )
17662 .redact_private_values
17663 } else {
17664 false
17665 }
17666 })
17667 .map(|range| {
17668 range.start.to_display_point(display_snapshot)
17669 ..range.end.to_display_point(display_snapshot)
17670 })
17671 .collect()
17672 }
17673
17674 pub fn highlight_text<T: 'static>(
17675 &mut self,
17676 ranges: Vec<Range<Anchor>>,
17677 style: HighlightStyle,
17678 cx: &mut Context<Self>,
17679 ) {
17680 self.display_map.update(cx, |map, _| {
17681 map.highlight_text(TypeId::of::<T>(), ranges, style)
17682 });
17683 cx.notify();
17684 }
17685
17686 pub(crate) fn highlight_inlays<T: 'static>(
17687 &mut self,
17688 highlights: Vec<InlayHighlight>,
17689 style: HighlightStyle,
17690 cx: &mut Context<Self>,
17691 ) {
17692 self.display_map.update(cx, |map, _| {
17693 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
17694 });
17695 cx.notify();
17696 }
17697
17698 pub fn text_highlights<'a, T: 'static>(
17699 &'a self,
17700 cx: &'a App,
17701 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
17702 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
17703 }
17704
17705 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
17706 let cleared = self
17707 .display_map
17708 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
17709 if cleared {
17710 cx.notify();
17711 }
17712 }
17713
17714 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
17715 (self.read_only(cx) || self.blink_manager.read(cx).visible())
17716 && self.focus_handle.is_focused(window)
17717 }
17718
17719 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
17720 self.show_cursor_when_unfocused = is_enabled;
17721 cx.notify();
17722 }
17723
17724 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
17725 cx.notify();
17726 }
17727
17728 fn on_debug_session_event(
17729 &mut self,
17730 _session: Entity<Session>,
17731 event: &SessionEvent,
17732 cx: &mut Context<Self>,
17733 ) {
17734 match event {
17735 SessionEvent::InvalidateInlineValue => {
17736 self.refresh_inline_values(cx);
17737 }
17738 _ => {}
17739 }
17740 }
17741
17742 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
17743 let Some(project) = self.project.clone() else {
17744 return;
17745 };
17746 let Some(buffer) = self.buffer.read(cx).as_singleton() else {
17747 return;
17748 };
17749 if !self.inline_value_cache.enabled {
17750 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
17751 self.splice_inlays(&inlays, Vec::new(), cx);
17752 return;
17753 }
17754
17755 let current_execution_position = self
17756 .highlighted_rows
17757 .get(&TypeId::of::<ActiveDebugLine>())
17758 .and_then(|lines| lines.last().map(|line| line.range.start));
17759
17760 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
17761 let snapshot = editor
17762 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17763 .ok()?;
17764
17765 let inline_values = editor
17766 .update(cx, |_, cx| {
17767 let Some(current_execution_position) = current_execution_position else {
17768 return Some(Task::ready(Ok(Vec::new())));
17769 };
17770
17771 // todo(debugger) when introducing multi buffer inline values check execution position's buffer id to make sure the text
17772 // anchor is in the same buffer
17773 let range =
17774 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
17775 project.inline_values(buffer, range, cx)
17776 })
17777 .ok()
17778 .flatten()?
17779 .await
17780 .context("refreshing debugger inlays")
17781 .log_err()?;
17782
17783 let (excerpt_id, buffer_id) = snapshot
17784 .excerpts()
17785 .next()
17786 .map(|excerpt| (excerpt.0, excerpt.1.remote_id()))?;
17787 editor
17788 .update(cx, |editor, cx| {
17789 let new_inlays = inline_values
17790 .into_iter()
17791 .map(|debugger_value| {
17792 Inlay::debugger_hint(
17793 post_inc(&mut editor.next_inlay_id),
17794 Anchor::in_buffer(excerpt_id, buffer_id, debugger_value.position),
17795 debugger_value.text(),
17796 )
17797 })
17798 .collect::<Vec<_>>();
17799 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
17800 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
17801
17802 editor.splice_inlays(&inlay_ids, new_inlays, cx);
17803 })
17804 .ok()?;
17805 Some(())
17806 });
17807 }
17808
17809 fn on_buffer_event(
17810 &mut self,
17811 multibuffer: &Entity<MultiBuffer>,
17812 event: &multi_buffer::Event,
17813 window: &mut Window,
17814 cx: &mut Context<Self>,
17815 ) {
17816 match event {
17817 multi_buffer::Event::Edited {
17818 singleton_buffer_edited,
17819 edited_buffer: buffer_edited,
17820 } => {
17821 self.scrollbar_marker_state.dirty = true;
17822 self.active_indent_guides_state.dirty = true;
17823 self.refresh_active_diagnostics(cx);
17824 self.refresh_code_actions(window, cx);
17825 self.refresh_selected_text_highlights(true, window, cx);
17826 refresh_matching_bracket_highlights(self, window, cx);
17827 if self.has_active_inline_completion() {
17828 self.update_visible_inline_completion(window, cx);
17829 }
17830 if let Some(buffer) = buffer_edited {
17831 let buffer_id = buffer.read(cx).remote_id();
17832 if !self.registered_buffers.contains_key(&buffer_id) {
17833 if let Some(project) = self.project.as_ref() {
17834 project.update(cx, |project, cx| {
17835 self.registered_buffers.insert(
17836 buffer_id,
17837 project.register_buffer_with_language_servers(&buffer, cx),
17838 );
17839 })
17840 }
17841 }
17842 }
17843 cx.emit(EditorEvent::BufferEdited);
17844 cx.emit(SearchEvent::MatchesInvalidated);
17845 if *singleton_buffer_edited {
17846 if let Some(project) = &self.project {
17847 #[allow(clippy::mutable_key_type)]
17848 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
17849 multibuffer
17850 .all_buffers()
17851 .into_iter()
17852 .filter_map(|buffer| {
17853 buffer.update(cx, |buffer, cx| {
17854 let language = buffer.language()?;
17855 let should_discard = project.update(cx, |project, cx| {
17856 project.is_local()
17857 && !project.has_language_servers_for(buffer, cx)
17858 });
17859 should_discard.not().then_some(language.clone())
17860 })
17861 })
17862 .collect::<HashSet<_>>()
17863 });
17864 if !languages_affected.is_empty() {
17865 self.refresh_inlay_hints(
17866 InlayHintRefreshReason::BufferEdited(languages_affected),
17867 cx,
17868 );
17869 }
17870 }
17871 }
17872
17873 let Some(project) = &self.project else { return };
17874 let (telemetry, is_via_ssh) = {
17875 let project = project.read(cx);
17876 let telemetry = project.client().telemetry().clone();
17877 let is_via_ssh = project.is_via_ssh();
17878 (telemetry, is_via_ssh)
17879 };
17880 refresh_linked_ranges(self, window, cx);
17881 telemetry.log_edit_event("editor", is_via_ssh);
17882 }
17883 multi_buffer::Event::ExcerptsAdded {
17884 buffer,
17885 predecessor,
17886 excerpts,
17887 } => {
17888 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17889 let buffer_id = buffer.read(cx).remote_id();
17890 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
17891 if let Some(project) = &self.project {
17892 update_uncommitted_diff_for_buffer(
17893 cx.entity(),
17894 project,
17895 [buffer.clone()],
17896 self.buffer.clone(),
17897 cx,
17898 )
17899 .detach();
17900 }
17901 }
17902 cx.emit(EditorEvent::ExcerptsAdded {
17903 buffer: buffer.clone(),
17904 predecessor: *predecessor,
17905 excerpts: excerpts.clone(),
17906 });
17907 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17908 }
17909 multi_buffer::Event::ExcerptsRemoved {
17910 ids,
17911 removed_buffer_ids,
17912 } => {
17913 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
17914 let buffer = self.buffer.read(cx);
17915 self.registered_buffers
17916 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
17917 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17918 cx.emit(EditorEvent::ExcerptsRemoved {
17919 ids: ids.clone(),
17920 removed_buffer_ids: removed_buffer_ids.clone(),
17921 })
17922 }
17923 multi_buffer::Event::ExcerptsEdited {
17924 excerpt_ids,
17925 buffer_ids,
17926 } => {
17927 self.display_map.update(cx, |map, cx| {
17928 map.unfold_buffers(buffer_ids.iter().copied(), cx)
17929 });
17930 cx.emit(EditorEvent::ExcerptsEdited {
17931 ids: excerpt_ids.clone(),
17932 })
17933 }
17934 multi_buffer::Event::ExcerptsExpanded { ids } => {
17935 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17936 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
17937 }
17938 multi_buffer::Event::Reparsed(buffer_id) => {
17939 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17940 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17941
17942 cx.emit(EditorEvent::Reparsed(*buffer_id));
17943 }
17944 multi_buffer::Event::DiffHunksToggled => {
17945 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17946 }
17947 multi_buffer::Event::LanguageChanged(buffer_id) => {
17948 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
17949 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17950 cx.emit(EditorEvent::Reparsed(*buffer_id));
17951 cx.notify();
17952 }
17953 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
17954 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
17955 multi_buffer::Event::FileHandleChanged
17956 | multi_buffer::Event::Reloaded
17957 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
17958 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
17959 multi_buffer::Event::DiagnosticsUpdated => {
17960 self.refresh_active_diagnostics(cx);
17961 self.refresh_inline_diagnostics(true, window, cx);
17962 self.scrollbar_marker_state.dirty = true;
17963 cx.notify();
17964 }
17965 _ => {}
17966 };
17967 }
17968
17969 pub fn start_temporary_diff_override(&mut self) {
17970 self.load_diff_task.take();
17971 self.temporary_diff_override = true;
17972 }
17973
17974 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
17975 self.temporary_diff_override = false;
17976 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
17977 self.buffer.update(cx, |buffer, cx| {
17978 buffer.set_all_diff_hunks_collapsed(cx);
17979 });
17980
17981 if let Some(project) = self.project.clone() {
17982 self.load_diff_task = Some(
17983 update_uncommitted_diff_for_buffer(
17984 cx.entity(),
17985 &project,
17986 self.buffer.read(cx).all_buffers(),
17987 self.buffer.clone(),
17988 cx,
17989 )
17990 .shared(),
17991 );
17992 }
17993 }
17994
17995 fn on_display_map_changed(
17996 &mut self,
17997 _: Entity<DisplayMap>,
17998 _: &mut Window,
17999 cx: &mut Context<Self>,
18000 ) {
18001 cx.notify();
18002 }
18003
18004 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18005 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18006 self.update_edit_prediction_settings(cx);
18007 self.refresh_inline_completion(true, false, window, cx);
18008 self.refresh_inlay_hints(
18009 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18010 self.selections.newest_anchor().head(),
18011 &self.buffer.read(cx).snapshot(cx),
18012 cx,
18013 )),
18014 cx,
18015 );
18016
18017 let old_cursor_shape = self.cursor_shape;
18018
18019 {
18020 let editor_settings = EditorSettings::get_global(cx);
18021 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18022 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18023 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18024 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18025 }
18026
18027 if old_cursor_shape != self.cursor_shape {
18028 cx.emit(EditorEvent::CursorShapeChanged);
18029 }
18030
18031 let project_settings = ProjectSettings::get_global(cx);
18032 self.serialize_dirty_buffers =
18033 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18034
18035 if self.mode.is_full() {
18036 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18037 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18038 if self.show_inline_diagnostics != show_inline_diagnostics {
18039 self.show_inline_diagnostics = show_inline_diagnostics;
18040 self.refresh_inline_diagnostics(false, window, cx);
18041 }
18042
18043 if self.git_blame_inline_enabled != inline_blame_enabled {
18044 self.toggle_git_blame_inline_internal(false, window, cx);
18045 }
18046
18047 let minimap_settings = EditorSettings::get_global(cx).minimap;
18048 if self.minimap.as_ref().is_some() != minimap_settings.minimap_enabled() {
18049 self.minimap = self.create_minimap(minimap_settings, window, cx);
18050 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18051 minimap_entity.update(cx, |minimap_editor, cx| {
18052 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18053 })
18054 }
18055 }
18056
18057 cx.notify();
18058 }
18059
18060 pub fn set_searchable(&mut self, searchable: bool) {
18061 self.searchable = searchable;
18062 }
18063
18064 pub fn searchable(&self) -> bool {
18065 self.searchable
18066 }
18067
18068 fn open_proposed_changes_editor(
18069 &mut self,
18070 _: &OpenProposedChangesEditor,
18071 window: &mut Window,
18072 cx: &mut Context<Self>,
18073 ) {
18074 let Some(workspace) = self.workspace() else {
18075 cx.propagate();
18076 return;
18077 };
18078
18079 let selections = self.selections.all::<usize>(cx);
18080 let multi_buffer = self.buffer.read(cx);
18081 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18082 let mut new_selections_by_buffer = HashMap::default();
18083 for selection in selections {
18084 for (buffer, range, _) in
18085 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18086 {
18087 let mut range = range.to_point(buffer);
18088 range.start.column = 0;
18089 range.end.column = buffer.line_len(range.end.row);
18090 new_selections_by_buffer
18091 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18092 .or_insert(Vec::new())
18093 .push(range)
18094 }
18095 }
18096
18097 let proposed_changes_buffers = new_selections_by_buffer
18098 .into_iter()
18099 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18100 .collect::<Vec<_>>();
18101 let proposed_changes_editor = cx.new(|cx| {
18102 ProposedChangesEditor::new(
18103 "Proposed changes",
18104 proposed_changes_buffers,
18105 self.project.clone(),
18106 window,
18107 cx,
18108 )
18109 });
18110
18111 window.defer(cx, move |window, cx| {
18112 workspace.update(cx, |workspace, cx| {
18113 workspace.active_pane().update(cx, |pane, cx| {
18114 pane.add_item(
18115 Box::new(proposed_changes_editor),
18116 true,
18117 true,
18118 None,
18119 window,
18120 cx,
18121 );
18122 });
18123 });
18124 });
18125 }
18126
18127 pub fn open_excerpts_in_split(
18128 &mut self,
18129 _: &OpenExcerptsSplit,
18130 window: &mut Window,
18131 cx: &mut Context<Self>,
18132 ) {
18133 self.open_excerpts_common(None, true, window, cx)
18134 }
18135
18136 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18137 self.open_excerpts_common(None, false, window, cx)
18138 }
18139
18140 fn open_excerpts_common(
18141 &mut self,
18142 jump_data: Option<JumpData>,
18143 split: bool,
18144 window: &mut Window,
18145 cx: &mut Context<Self>,
18146 ) {
18147 let Some(workspace) = self.workspace() else {
18148 cx.propagate();
18149 return;
18150 };
18151
18152 if self.buffer.read(cx).is_singleton() {
18153 cx.propagate();
18154 return;
18155 }
18156
18157 let mut new_selections_by_buffer = HashMap::default();
18158 match &jump_data {
18159 Some(JumpData::MultiBufferPoint {
18160 excerpt_id,
18161 position,
18162 anchor,
18163 line_offset_from_top,
18164 }) => {
18165 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18166 if let Some(buffer) = multi_buffer_snapshot
18167 .buffer_id_for_excerpt(*excerpt_id)
18168 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18169 {
18170 let buffer_snapshot = buffer.read(cx).snapshot();
18171 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18172 language::ToPoint::to_point(anchor, &buffer_snapshot)
18173 } else {
18174 buffer_snapshot.clip_point(*position, Bias::Left)
18175 };
18176 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18177 new_selections_by_buffer.insert(
18178 buffer,
18179 (
18180 vec![jump_to_offset..jump_to_offset],
18181 Some(*line_offset_from_top),
18182 ),
18183 );
18184 }
18185 }
18186 Some(JumpData::MultiBufferRow {
18187 row,
18188 line_offset_from_top,
18189 }) => {
18190 let point = MultiBufferPoint::new(row.0, 0);
18191 if let Some((buffer, buffer_point, _)) =
18192 self.buffer.read(cx).point_to_buffer_point(point, cx)
18193 {
18194 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18195 new_selections_by_buffer
18196 .entry(buffer)
18197 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18198 .0
18199 .push(buffer_offset..buffer_offset)
18200 }
18201 }
18202 None => {
18203 let selections = self.selections.all::<usize>(cx);
18204 let multi_buffer = self.buffer.read(cx);
18205 for selection in selections {
18206 for (snapshot, range, _, anchor) in multi_buffer
18207 .snapshot(cx)
18208 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18209 {
18210 if let Some(anchor) = anchor {
18211 // selection is in a deleted hunk
18212 let Some(buffer_id) = anchor.buffer_id else {
18213 continue;
18214 };
18215 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18216 continue;
18217 };
18218 let offset = text::ToOffset::to_offset(
18219 &anchor.text_anchor,
18220 &buffer_handle.read(cx).snapshot(),
18221 );
18222 let range = offset..offset;
18223 new_selections_by_buffer
18224 .entry(buffer_handle)
18225 .or_insert((Vec::new(), None))
18226 .0
18227 .push(range)
18228 } else {
18229 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18230 else {
18231 continue;
18232 };
18233 new_selections_by_buffer
18234 .entry(buffer_handle)
18235 .or_insert((Vec::new(), None))
18236 .0
18237 .push(range)
18238 }
18239 }
18240 }
18241 }
18242 }
18243
18244 new_selections_by_buffer
18245 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18246
18247 if new_selections_by_buffer.is_empty() {
18248 return;
18249 }
18250
18251 // We defer the pane interaction because we ourselves are a workspace item
18252 // and activating a new item causes the pane to call a method on us reentrantly,
18253 // which panics if we're on the stack.
18254 window.defer(cx, move |window, cx| {
18255 workspace.update(cx, |workspace, cx| {
18256 let pane = if split {
18257 workspace.adjacent_pane(window, cx)
18258 } else {
18259 workspace.active_pane().clone()
18260 };
18261
18262 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18263 let editor = buffer
18264 .read(cx)
18265 .file()
18266 .is_none()
18267 .then(|| {
18268 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18269 // so `workspace.open_project_item` will never find them, always opening a new editor.
18270 // Instead, we try to activate the existing editor in the pane first.
18271 let (editor, pane_item_index) =
18272 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18273 let editor = item.downcast::<Editor>()?;
18274 let singleton_buffer =
18275 editor.read(cx).buffer().read(cx).as_singleton()?;
18276 if singleton_buffer == buffer {
18277 Some((editor, i))
18278 } else {
18279 None
18280 }
18281 })?;
18282 pane.update(cx, |pane, cx| {
18283 pane.activate_item(pane_item_index, true, true, window, cx)
18284 });
18285 Some(editor)
18286 })
18287 .flatten()
18288 .unwrap_or_else(|| {
18289 workspace.open_project_item::<Self>(
18290 pane.clone(),
18291 buffer,
18292 true,
18293 true,
18294 window,
18295 cx,
18296 )
18297 });
18298
18299 editor.update(cx, |editor, cx| {
18300 let autoscroll = match scroll_offset {
18301 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18302 None => Autoscroll::newest(),
18303 };
18304 let nav_history = editor.nav_history.take();
18305 editor.change_selections(Some(autoscroll), window, cx, |s| {
18306 s.select_ranges(ranges);
18307 });
18308 editor.nav_history = nav_history;
18309 });
18310 }
18311 })
18312 });
18313 }
18314
18315 // For now, don't allow opening excerpts in buffers that aren't backed by
18316 // regular project files.
18317 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18318 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18319 }
18320
18321 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18322 let snapshot = self.buffer.read(cx).read(cx);
18323 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18324 Some(
18325 ranges
18326 .iter()
18327 .map(move |range| {
18328 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18329 })
18330 .collect(),
18331 )
18332 }
18333
18334 fn selection_replacement_ranges(
18335 &self,
18336 range: Range<OffsetUtf16>,
18337 cx: &mut App,
18338 ) -> Vec<Range<OffsetUtf16>> {
18339 let selections = self.selections.all::<OffsetUtf16>(cx);
18340 let newest_selection = selections
18341 .iter()
18342 .max_by_key(|selection| selection.id)
18343 .unwrap();
18344 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
18345 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
18346 let snapshot = self.buffer.read(cx).read(cx);
18347 selections
18348 .into_iter()
18349 .map(|mut selection| {
18350 selection.start.0 =
18351 (selection.start.0 as isize).saturating_add(start_delta) as usize;
18352 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
18353 snapshot.clip_offset_utf16(selection.start, Bias::Left)
18354 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
18355 })
18356 .collect()
18357 }
18358
18359 fn report_editor_event(
18360 &self,
18361 event_type: &'static str,
18362 file_extension: Option<String>,
18363 cx: &App,
18364 ) {
18365 if cfg!(any(test, feature = "test-support")) {
18366 return;
18367 }
18368
18369 let Some(project) = &self.project else { return };
18370
18371 // If None, we are in a file without an extension
18372 let file = self
18373 .buffer
18374 .read(cx)
18375 .as_singleton()
18376 .and_then(|b| b.read(cx).file());
18377 let file_extension = file_extension.or(file
18378 .as_ref()
18379 .and_then(|file| Path::new(file.file_name(cx)).extension())
18380 .and_then(|e| e.to_str())
18381 .map(|a| a.to_string()));
18382
18383 let vim_mode = vim_enabled(cx);
18384
18385 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
18386 let copilot_enabled = edit_predictions_provider
18387 == language::language_settings::EditPredictionProvider::Copilot;
18388 let copilot_enabled_for_language = self
18389 .buffer
18390 .read(cx)
18391 .language_settings(cx)
18392 .show_edit_predictions;
18393
18394 let project = project.read(cx);
18395 telemetry::event!(
18396 event_type,
18397 file_extension,
18398 vim_mode,
18399 copilot_enabled,
18400 copilot_enabled_for_language,
18401 edit_predictions_provider,
18402 is_via_ssh = project.is_via_ssh(),
18403 );
18404 }
18405
18406 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
18407 /// with each line being an array of {text, highlight} objects.
18408 fn copy_highlight_json(
18409 &mut self,
18410 _: &CopyHighlightJson,
18411 window: &mut Window,
18412 cx: &mut Context<Self>,
18413 ) {
18414 #[derive(Serialize)]
18415 struct Chunk<'a> {
18416 text: String,
18417 highlight: Option<&'a str>,
18418 }
18419
18420 let snapshot = self.buffer.read(cx).snapshot(cx);
18421 let range = self
18422 .selected_text_range(false, window, cx)
18423 .and_then(|selection| {
18424 if selection.range.is_empty() {
18425 None
18426 } else {
18427 Some(selection.range)
18428 }
18429 })
18430 .unwrap_or_else(|| 0..snapshot.len());
18431
18432 let chunks = snapshot.chunks(range, true);
18433 let mut lines = Vec::new();
18434 let mut line: VecDeque<Chunk> = VecDeque::new();
18435
18436 let Some(style) = self.style.as_ref() else {
18437 return;
18438 };
18439
18440 for chunk in chunks {
18441 let highlight = chunk
18442 .syntax_highlight_id
18443 .and_then(|id| id.name(&style.syntax));
18444 let mut chunk_lines = chunk.text.split('\n').peekable();
18445 while let Some(text) = chunk_lines.next() {
18446 let mut merged_with_last_token = false;
18447 if let Some(last_token) = line.back_mut() {
18448 if last_token.highlight == highlight {
18449 last_token.text.push_str(text);
18450 merged_with_last_token = true;
18451 }
18452 }
18453
18454 if !merged_with_last_token {
18455 line.push_back(Chunk {
18456 text: text.into(),
18457 highlight,
18458 });
18459 }
18460
18461 if chunk_lines.peek().is_some() {
18462 if line.len() > 1 && line.front().unwrap().text.is_empty() {
18463 line.pop_front();
18464 }
18465 if line.len() > 1 && line.back().unwrap().text.is_empty() {
18466 line.pop_back();
18467 }
18468
18469 lines.push(mem::take(&mut line));
18470 }
18471 }
18472 }
18473
18474 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
18475 return;
18476 };
18477 cx.write_to_clipboard(ClipboardItem::new_string(lines));
18478 }
18479
18480 pub fn open_context_menu(
18481 &mut self,
18482 _: &OpenContextMenu,
18483 window: &mut Window,
18484 cx: &mut Context<Self>,
18485 ) {
18486 self.request_autoscroll(Autoscroll::newest(), cx);
18487 let position = self.selections.newest_display(cx).start;
18488 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
18489 }
18490
18491 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
18492 &self.inlay_hint_cache
18493 }
18494
18495 pub fn replay_insert_event(
18496 &mut self,
18497 text: &str,
18498 relative_utf16_range: Option<Range<isize>>,
18499 window: &mut Window,
18500 cx: &mut Context<Self>,
18501 ) {
18502 if !self.input_enabled {
18503 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18504 return;
18505 }
18506 if let Some(relative_utf16_range) = relative_utf16_range {
18507 let selections = self.selections.all::<OffsetUtf16>(cx);
18508 self.change_selections(None, window, cx, |s| {
18509 let new_ranges = selections.into_iter().map(|range| {
18510 let start = OffsetUtf16(
18511 range
18512 .head()
18513 .0
18514 .saturating_add_signed(relative_utf16_range.start),
18515 );
18516 let end = OffsetUtf16(
18517 range
18518 .head()
18519 .0
18520 .saturating_add_signed(relative_utf16_range.end),
18521 );
18522 start..end
18523 });
18524 s.select_ranges(new_ranges);
18525 });
18526 }
18527
18528 self.handle_input(text, window, cx);
18529 }
18530
18531 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
18532 let Some(provider) = self.semantics_provider.as_ref() else {
18533 return false;
18534 };
18535
18536 let mut supports = false;
18537 self.buffer().update(cx, |this, cx| {
18538 this.for_each_buffer(|buffer| {
18539 supports |= provider.supports_inlay_hints(buffer, cx);
18540 });
18541 });
18542
18543 supports
18544 }
18545
18546 pub fn is_focused(&self, window: &Window) -> bool {
18547 self.focus_handle.is_focused(window)
18548 }
18549
18550 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18551 cx.emit(EditorEvent::Focused);
18552
18553 if let Some(descendant) = self
18554 .last_focused_descendant
18555 .take()
18556 .and_then(|descendant| descendant.upgrade())
18557 {
18558 window.focus(&descendant);
18559 } else {
18560 if let Some(blame) = self.blame.as_ref() {
18561 blame.update(cx, GitBlame::focus)
18562 }
18563
18564 self.blink_manager.update(cx, BlinkManager::enable);
18565 self.show_cursor_names(window, cx);
18566 self.buffer.update(cx, |buffer, cx| {
18567 buffer.finalize_last_transaction(cx);
18568 if self.leader_id.is_none() {
18569 buffer.set_active_selections(
18570 &self.selections.disjoint_anchors(),
18571 self.selections.line_mode,
18572 self.cursor_shape,
18573 cx,
18574 );
18575 }
18576 });
18577 }
18578 }
18579
18580 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18581 cx.emit(EditorEvent::FocusedIn)
18582 }
18583
18584 fn handle_focus_out(
18585 &mut self,
18586 event: FocusOutEvent,
18587 _window: &mut Window,
18588 cx: &mut Context<Self>,
18589 ) {
18590 if event.blurred != self.focus_handle {
18591 self.last_focused_descendant = Some(event.blurred);
18592 }
18593 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
18594 }
18595
18596 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18597 self.blink_manager.update(cx, BlinkManager::disable);
18598 self.buffer
18599 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
18600
18601 if let Some(blame) = self.blame.as_ref() {
18602 blame.update(cx, GitBlame::blur)
18603 }
18604 if !self.hover_state.focused(window, cx) {
18605 hide_hover(self, cx);
18606 }
18607 if !self
18608 .context_menu
18609 .borrow()
18610 .as_ref()
18611 .is_some_and(|context_menu| context_menu.focused(window, cx))
18612 {
18613 self.hide_context_menu(window, cx);
18614 }
18615 self.discard_inline_completion(false, cx);
18616 cx.emit(EditorEvent::Blurred);
18617 cx.notify();
18618 }
18619
18620 pub fn register_action<A: Action>(
18621 &mut self,
18622 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
18623 ) -> Subscription {
18624 let id = self.next_editor_action_id.post_inc();
18625 let listener = Arc::new(listener);
18626 self.editor_actions.borrow_mut().insert(
18627 id,
18628 Box::new(move |window, _| {
18629 let listener = listener.clone();
18630 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
18631 let action = action.downcast_ref().unwrap();
18632 if phase == DispatchPhase::Bubble {
18633 listener(action, window, cx)
18634 }
18635 })
18636 }),
18637 );
18638
18639 let editor_actions = self.editor_actions.clone();
18640 Subscription::new(move || {
18641 editor_actions.borrow_mut().remove(&id);
18642 })
18643 }
18644
18645 pub fn file_header_size(&self) -> u32 {
18646 FILE_HEADER_HEIGHT
18647 }
18648
18649 pub fn restore(
18650 &mut self,
18651 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
18652 window: &mut Window,
18653 cx: &mut Context<Self>,
18654 ) {
18655 let workspace = self.workspace();
18656 let project = self.project.as_ref();
18657 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
18658 let mut tasks = Vec::new();
18659 for (buffer_id, changes) in revert_changes {
18660 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
18661 buffer.update(cx, |buffer, cx| {
18662 buffer.edit(
18663 changes
18664 .into_iter()
18665 .map(|(range, text)| (range, text.to_string())),
18666 None,
18667 cx,
18668 );
18669 });
18670
18671 if let Some(project) =
18672 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
18673 {
18674 project.update(cx, |project, cx| {
18675 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
18676 })
18677 }
18678 }
18679 }
18680 tasks
18681 });
18682 cx.spawn_in(window, async move |_, cx| {
18683 for (buffer, task) in save_tasks {
18684 let result = task.await;
18685 if result.is_err() {
18686 let Some(path) = buffer
18687 .read_with(cx, |buffer, cx| buffer.project_path(cx))
18688 .ok()
18689 else {
18690 continue;
18691 };
18692 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
18693 let Some(task) = cx
18694 .update_window_entity(&workspace, |workspace, window, cx| {
18695 workspace
18696 .open_path_preview(path, None, false, false, false, window, cx)
18697 })
18698 .ok()
18699 else {
18700 continue;
18701 };
18702 task.await.log_err();
18703 }
18704 }
18705 }
18706 })
18707 .detach();
18708 self.change_selections(None, window, cx, |selections| selections.refresh());
18709 }
18710
18711 pub fn to_pixel_point(
18712 &self,
18713 source: multi_buffer::Anchor,
18714 editor_snapshot: &EditorSnapshot,
18715 window: &mut Window,
18716 ) -> Option<gpui::Point<Pixels>> {
18717 let source_point = source.to_display_point(editor_snapshot);
18718 self.display_to_pixel_point(source_point, editor_snapshot, window)
18719 }
18720
18721 pub fn display_to_pixel_point(
18722 &self,
18723 source: DisplayPoint,
18724 editor_snapshot: &EditorSnapshot,
18725 window: &mut Window,
18726 ) -> Option<gpui::Point<Pixels>> {
18727 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
18728 let text_layout_details = self.text_layout_details(window);
18729 let scroll_top = text_layout_details
18730 .scroll_anchor
18731 .scroll_position(editor_snapshot)
18732 .y;
18733
18734 if source.row().as_f32() < scroll_top.floor() {
18735 return None;
18736 }
18737 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
18738 let source_y = line_height * (source.row().as_f32() - scroll_top);
18739 Some(gpui::Point::new(source_x, source_y))
18740 }
18741
18742 pub fn has_visible_completions_menu(&self) -> bool {
18743 !self.edit_prediction_preview_is_active()
18744 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
18745 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
18746 })
18747 }
18748
18749 pub fn register_addon<T: Addon>(&mut self, instance: T) {
18750 if self.mode.is_minimap() {
18751 return;
18752 }
18753 self.addons
18754 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
18755 }
18756
18757 pub fn unregister_addon<T: Addon>(&mut self) {
18758 self.addons.remove(&std::any::TypeId::of::<T>());
18759 }
18760
18761 pub fn addon<T: Addon>(&self) -> Option<&T> {
18762 let type_id = std::any::TypeId::of::<T>();
18763 self.addons
18764 .get(&type_id)
18765 .and_then(|item| item.to_any().downcast_ref::<T>())
18766 }
18767
18768 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
18769 let type_id = std::any::TypeId::of::<T>();
18770 self.addons
18771 .get_mut(&type_id)
18772 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
18773 }
18774
18775 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
18776 let text_layout_details = self.text_layout_details(window);
18777 let style = &text_layout_details.editor_style;
18778 let font_id = window.text_system().resolve_font(&style.text.font());
18779 let font_size = style.text.font_size.to_pixels(window.rem_size());
18780 let line_height = style.text.line_height_in_pixels(window.rem_size());
18781 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
18782
18783 gpui::Size::new(em_width, line_height)
18784 }
18785
18786 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
18787 self.load_diff_task.clone()
18788 }
18789
18790 fn read_metadata_from_db(
18791 &mut self,
18792 item_id: u64,
18793 workspace_id: WorkspaceId,
18794 window: &mut Window,
18795 cx: &mut Context<Editor>,
18796 ) {
18797 if self.is_singleton(cx)
18798 && !self.mode.is_minimap()
18799 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
18800 {
18801 let buffer_snapshot = OnceCell::new();
18802
18803 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
18804 if !folds.is_empty() {
18805 let snapshot =
18806 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18807 self.fold_ranges(
18808 folds
18809 .into_iter()
18810 .map(|(start, end)| {
18811 snapshot.clip_offset(start, Bias::Left)
18812 ..snapshot.clip_offset(end, Bias::Right)
18813 })
18814 .collect(),
18815 false,
18816 window,
18817 cx,
18818 );
18819 }
18820 }
18821
18822 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
18823 if !selections.is_empty() {
18824 let snapshot =
18825 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18826 self.change_selections(None, window, cx, |s| {
18827 s.select_ranges(selections.into_iter().map(|(start, end)| {
18828 snapshot.clip_offset(start, Bias::Left)
18829 ..snapshot.clip_offset(end, Bias::Right)
18830 }));
18831 });
18832 }
18833 };
18834 }
18835
18836 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
18837 }
18838}
18839
18840fn vim_enabled(cx: &App) -> bool {
18841 cx.global::<SettingsStore>()
18842 .raw_user_settings()
18843 .get("vim_mode")
18844 == Some(&serde_json::Value::Bool(true))
18845}
18846
18847// Consider user intent and default settings
18848fn choose_completion_range(
18849 completion: &Completion,
18850 intent: CompletionIntent,
18851 buffer: &Entity<Buffer>,
18852 cx: &mut Context<Editor>,
18853) -> Range<usize> {
18854 fn should_replace(
18855 completion: &Completion,
18856 insert_range: &Range<text::Anchor>,
18857 intent: CompletionIntent,
18858 completion_mode_setting: LspInsertMode,
18859 buffer: &Buffer,
18860 ) -> bool {
18861 // specific actions take precedence over settings
18862 match intent {
18863 CompletionIntent::CompleteWithInsert => return false,
18864 CompletionIntent::CompleteWithReplace => return true,
18865 CompletionIntent::Complete | CompletionIntent::Compose => {}
18866 }
18867
18868 match completion_mode_setting {
18869 LspInsertMode::Insert => false,
18870 LspInsertMode::Replace => true,
18871 LspInsertMode::ReplaceSubsequence => {
18872 let mut text_to_replace = buffer.chars_for_range(
18873 buffer.anchor_before(completion.replace_range.start)
18874 ..buffer.anchor_after(completion.replace_range.end),
18875 );
18876 let mut completion_text = completion.new_text.chars();
18877
18878 // is `text_to_replace` a subsequence of `completion_text`
18879 text_to_replace
18880 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
18881 }
18882 LspInsertMode::ReplaceSuffix => {
18883 let range_after_cursor = insert_range.end..completion.replace_range.end;
18884
18885 let text_after_cursor = buffer
18886 .text_for_range(
18887 buffer.anchor_before(range_after_cursor.start)
18888 ..buffer.anchor_after(range_after_cursor.end),
18889 )
18890 .collect::<String>();
18891 completion.new_text.ends_with(&text_after_cursor)
18892 }
18893 }
18894 }
18895
18896 let buffer = buffer.read(cx);
18897
18898 if let CompletionSource::Lsp {
18899 insert_range: Some(insert_range),
18900 ..
18901 } = &completion.source
18902 {
18903 let completion_mode_setting =
18904 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
18905 .completions
18906 .lsp_insert_mode;
18907
18908 if !should_replace(
18909 completion,
18910 &insert_range,
18911 intent,
18912 completion_mode_setting,
18913 buffer,
18914 ) {
18915 return insert_range.to_offset(buffer);
18916 }
18917 }
18918
18919 completion.replace_range.to_offset(buffer)
18920}
18921
18922fn insert_extra_newline_brackets(
18923 buffer: &MultiBufferSnapshot,
18924 range: Range<usize>,
18925 language: &language::LanguageScope,
18926) -> bool {
18927 let leading_whitespace_len = buffer
18928 .reversed_chars_at(range.start)
18929 .take_while(|c| c.is_whitespace() && *c != '\n')
18930 .map(|c| c.len_utf8())
18931 .sum::<usize>();
18932 let trailing_whitespace_len = buffer
18933 .chars_at(range.end)
18934 .take_while(|c| c.is_whitespace() && *c != '\n')
18935 .map(|c| c.len_utf8())
18936 .sum::<usize>();
18937 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
18938
18939 language.brackets().any(|(pair, enabled)| {
18940 let pair_start = pair.start.trim_end();
18941 let pair_end = pair.end.trim_start();
18942
18943 enabled
18944 && pair.newline
18945 && buffer.contains_str_at(range.end, pair_end)
18946 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
18947 })
18948}
18949
18950fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
18951 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
18952 [(buffer, range, _)] => (*buffer, range.clone()),
18953 _ => return false,
18954 };
18955 let pair = {
18956 let mut result: Option<BracketMatch> = None;
18957
18958 for pair in buffer
18959 .all_bracket_ranges(range.clone())
18960 .filter(move |pair| {
18961 pair.open_range.start <= range.start && pair.close_range.end >= range.end
18962 })
18963 {
18964 let len = pair.close_range.end - pair.open_range.start;
18965
18966 if let Some(existing) = &result {
18967 let existing_len = existing.close_range.end - existing.open_range.start;
18968 if len > existing_len {
18969 continue;
18970 }
18971 }
18972
18973 result = Some(pair);
18974 }
18975
18976 result
18977 };
18978 let Some(pair) = pair else {
18979 return false;
18980 };
18981 pair.newline_only
18982 && buffer
18983 .chars_for_range(pair.open_range.end..range.start)
18984 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
18985 .all(|c| c.is_whitespace() && c != '\n')
18986}
18987
18988fn update_uncommitted_diff_for_buffer(
18989 editor: Entity<Editor>,
18990 project: &Entity<Project>,
18991 buffers: impl IntoIterator<Item = Entity<Buffer>>,
18992 buffer: Entity<MultiBuffer>,
18993 cx: &mut App,
18994) -> Task<()> {
18995 let mut tasks = Vec::new();
18996 project.update(cx, |project, cx| {
18997 for buffer in buffers {
18998 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
18999 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
19000 }
19001 }
19002 });
19003 cx.spawn(async move |cx| {
19004 let diffs = future::join_all(tasks).await;
19005 if editor
19006 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19007 .unwrap_or(false)
19008 {
19009 return;
19010 }
19011
19012 buffer
19013 .update(cx, |buffer, cx| {
19014 for diff in diffs.into_iter().flatten() {
19015 buffer.add_diff(diff, cx);
19016 }
19017 })
19018 .ok();
19019 })
19020}
19021
19022fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
19023 let tab_size = tab_size.get() as usize;
19024 let mut width = offset;
19025
19026 for ch in text.chars() {
19027 width += if ch == '\t' {
19028 tab_size - (width % tab_size)
19029 } else {
19030 1
19031 };
19032 }
19033
19034 width - offset
19035}
19036
19037#[cfg(test)]
19038mod tests {
19039 use super::*;
19040
19041 #[test]
19042 fn test_string_size_with_expanded_tabs() {
19043 let nz = |val| NonZeroU32::new(val).unwrap();
19044 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
19045 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
19046 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
19047 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
19048 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
19049 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
19050 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
19051 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
19052 }
19053}
19054
19055/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
19056struct WordBreakingTokenizer<'a> {
19057 input: &'a str,
19058}
19059
19060impl<'a> WordBreakingTokenizer<'a> {
19061 fn new(input: &'a str) -> Self {
19062 Self { input }
19063 }
19064}
19065
19066fn is_char_ideographic(ch: char) -> bool {
19067 use unicode_script::Script::*;
19068 use unicode_script::UnicodeScript;
19069 matches!(ch.script(), Han | Tangut | Yi)
19070}
19071
19072fn is_grapheme_ideographic(text: &str) -> bool {
19073 text.chars().any(is_char_ideographic)
19074}
19075
19076fn is_grapheme_whitespace(text: &str) -> bool {
19077 text.chars().any(|x| x.is_whitespace())
19078}
19079
19080fn should_stay_with_preceding_ideograph(text: &str) -> bool {
19081 text.chars().next().map_or(false, |ch| {
19082 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
19083 })
19084}
19085
19086#[derive(PartialEq, Eq, Debug, Clone, Copy)]
19087enum WordBreakToken<'a> {
19088 Word { token: &'a str, grapheme_len: usize },
19089 InlineWhitespace { token: &'a str, grapheme_len: usize },
19090 Newline,
19091}
19092
19093impl<'a> Iterator for WordBreakingTokenizer<'a> {
19094 /// Yields a span, the count of graphemes in the token, and whether it was
19095 /// whitespace. Note that it also breaks at word boundaries.
19096 type Item = WordBreakToken<'a>;
19097
19098 fn next(&mut self) -> Option<Self::Item> {
19099 use unicode_segmentation::UnicodeSegmentation;
19100 if self.input.is_empty() {
19101 return None;
19102 }
19103
19104 let mut iter = self.input.graphemes(true).peekable();
19105 let mut offset = 0;
19106 let mut grapheme_len = 0;
19107 if let Some(first_grapheme) = iter.next() {
19108 let is_newline = first_grapheme == "\n";
19109 let is_whitespace = is_grapheme_whitespace(first_grapheme);
19110 offset += first_grapheme.len();
19111 grapheme_len += 1;
19112 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
19113 if let Some(grapheme) = iter.peek().copied() {
19114 if should_stay_with_preceding_ideograph(grapheme) {
19115 offset += grapheme.len();
19116 grapheme_len += 1;
19117 }
19118 }
19119 } else {
19120 let mut words = self.input[offset..].split_word_bound_indices().peekable();
19121 let mut next_word_bound = words.peek().copied();
19122 if next_word_bound.map_or(false, |(i, _)| i == 0) {
19123 next_word_bound = words.next();
19124 }
19125 while let Some(grapheme) = iter.peek().copied() {
19126 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19127 break;
19128 };
19129 if is_grapheme_whitespace(grapheme) != is_whitespace
19130 || (grapheme == "\n") != is_newline
19131 {
19132 break;
19133 };
19134 offset += grapheme.len();
19135 grapheme_len += 1;
19136 iter.next();
19137 }
19138 }
19139 let token = &self.input[..offset];
19140 self.input = &self.input[offset..];
19141 if token == "\n" {
19142 Some(WordBreakToken::Newline)
19143 } else if is_whitespace {
19144 Some(WordBreakToken::InlineWhitespace {
19145 token,
19146 grapheme_len,
19147 })
19148 } else {
19149 Some(WordBreakToken::Word {
19150 token,
19151 grapheme_len,
19152 })
19153 }
19154 } else {
19155 None
19156 }
19157 }
19158}
19159
19160#[test]
19161fn test_word_breaking_tokenizer() {
19162 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
19163 ("", &[]),
19164 (" ", &[whitespace(" ", 2)]),
19165 ("Ʒ", &[word("Ʒ", 1)]),
19166 ("Ǽ", &[word("Ǽ", 1)]),
19167 ("⋑", &[word("⋑", 1)]),
19168 ("⋑⋑", &[word("⋑⋑", 2)]),
19169 (
19170 "原理,进而",
19171 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
19172 ),
19173 (
19174 "hello world",
19175 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
19176 ),
19177 (
19178 "hello, world",
19179 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
19180 ),
19181 (
19182 " hello world",
19183 &[
19184 whitespace(" ", 2),
19185 word("hello", 5),
19186 whitespace(" ", 1),
19187 word("world", 5),
19188 ],
19189 ),
19190 (
19191 "这是什么 \n 钢笔",
19192 &[
19193 word("这", 1),
19194 word("是", 1),
19195 word("什", 1),
19196 word("么", 1),
19197 whitespace(" ", 1),
19198 newline(),
19199 whitespace(" ", 1),
19200 word("钢", 1),
19201 word("笔", 1),
19202 ],
19203 ),
19204 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
19205 ];
19206
19207 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19208 WordBreakToken::Word {
19209 token,
19210 grapheme_len,
19211 }
19212 }
19213
19214 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19215 WordBreakToken::InlineWhitespace {
19216 token,
19217 grapheme_len,
19218 }
19219 }
19220
19221 fn newline() -> WordBreakToken<'static> {
19222 WordBreakToken::Newline
19223 }
19224
19225 for (input, result) in tests {
19226 assert_eq!(
19227 WordBreakingTokenizer::new(input)
19228 .collect::<Vec<_>>()
19229 .as_slice(),
19230 *result,
19231 );
19232 }
19233}
19234
19235fn wrap_with_prefix(
19236 line_prefix: String,
19237 unwrapped_text: String,
19238 wrap_column: usize,
19239 tab_size: NonZeroU32,
19240 preserve_existing_whitespace: bool,
19241) -> String {
19242 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
19243 let mut wrapped_text = String::new();
19244 let mut current_line = line_prefix.clone();
19245
19246 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
19247 let mut current_line_len = line_prefix_len;
19248 let mut in_whitespace = false;
19249 for token in tokenizer {
19250 let have_preceding_whitespace = in_whitespace;
19251 match token {
19252 WordBreakToken::Word {
19253 token,
19254 grapheme_len,
19255 } => {
19256 in_whitespace = false;
19257 if current_line_len + grapheme_len > wrap_column
19258 && current_line_len != line_prefix_len
19259 {
19260 wrapped_text.push_str(current_line.trim_end());
19261 wrapped_text.push('\n');
19262 current_line.truncate(line_prefix.len());
19263 current_line_len = line_prefix_len;
19264 }
19265 current_line.push_str(token);
19266 current_line_len += grapheme_len;
19267 }
19268 WordBreakToken::InlineWhitespace {
19269 mut token,
19270 mut grapheme_len,
19271 } => {
19272 in_whitespace = true;
19273 if have_preceding_whitespace && !preserve_existing_whitespace {
19274 continue;
19275 }
19276 if !preserve_existing_whitespace {
19277 token = " ";
19278 grapheme_len = 1;
19279 }
19280 if current_line_len + grapheme_len > wrap_column {
19281 wrapped_text.push_str(current_line.trim_end());
19282 wrapped_text.push('\n');
19283 current_line.truncate(line_prefix.len());
19284 current_line_len = line_prefix_len;
19285 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
19286 current_line.push_str(token);
19287 current_line_len += grapheme_len;
19288 }
19289 }
19290 WordBreakToken::Newline => {
19291 in_whitespace = true;
19292 if preserve_existing_whitespace {
19293 wrapped_text.push_str(current_line.trim_end());
19294 wrapped_text.push('\n');
19295 current_line.truncate(line_prefix.len());
19296 current_line_len = line_prefix_len;
19297 } else if have_preceding_whitespace {
19298 continue;
19299 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
19300 {
19301 wrapped_text.push_str(current_line.trim_end());
19302 wrapped_text.push('\n');
19303 current_line.truncate(line_prefix.len());
19304 current_line_len = line_prefix_len;
19305 } else if current_line_len != line_prefix_len {
19306 current_line.push(' ');
19307 current_line_len += 1;
19308 }
19309 }
19310 }
19311 }
19312
19313 if !current_line.is_empty() {
19314 wrapped_text.push_str(¤t_line);
19315 }
19316 wrapped_text
19317}
19318
19319#[test]
19320fn test_wrap_with_prefix() {
19321 assert_eq!(
19322 wrap_with_prefix(
19323 "# ".to_string(),
19324 "abcdefg".to_string(),
19325 4,
19326 NonZeroU32::new(4).unwrap(),
19327 false,
19328 ),
19329 "# abcdefg"
19330 );
19331 assert_eq!(
19332 wrap_with_prefix(
19333 "".to_string(),
19334 "\thello world".to_string(),
19335 8,
19336 NonZeroU32::new(4).unwrap(),
19337 false,
19338 ),
19339 "hello\nworld"
19340 );
19341 assert_eq!(
19342 wrap_with_prefix(
19343 "// ".to_string(),
19344 "xx \nyy zz aa bb cc".to_string(),
19345 12,
19346 NonZeroU32::new(4).unwrap(),
19347 false,
19348 ),
19349 "// xx yy zz\n// aa bb cc"
19350 );
19351 assert_eq!(
19352 wrap_with_prefix(
19353 String::new(),
19354 "这是什么 \n 钢笔".to_string(),
19355 3,
19356 NonZeroU32::new(4).unwrap(),
19357 false,
19358 ),
19359 "这是什\n么 钢\n笔"
19360 );
19361}
19362
19363pub trait CollaborationHub {
19364 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
19365 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
19366 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
19367}
19368
19369impl CollaborationHub for Entity<Project> {
19370 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
19371 self.read(cx).collaborators()
19372 }
19373
19374 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
19375 self.read(cx).user_store().read(cx).participant_indices()
19376 }
19377
19378 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
19379 let this = self.read(cx);
19380 let user_ids = this.collaborators().values().map(|c| c.user_id);
19381 this.user_store().read_with(cx, |user_store, cx| {
19382 user_store.participant_names(user_ids, cx)
19383 })
19384 }
19385}
19386
19387pub trait SemanticsProvider {
19388 fn hover(
19389 &self,
19390 buffer: &Entity<Buffer>,
19391 position: text::Anchor,
19392 cx: &mut App,
19393 ) -> Option<Task<Vec<project::Hover>>>;
19394
19395 fn inline_values(
19396 &self,
19397 buffer_handle: Entity<Buffer>,
19398 range: Range<text::Anchor>,
19399 cx: &mut App,
19400 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19401
19402 fn inlay_hints(
19403 &self,
19404 buffer_handle: Entity<Buffer>,
19405 range: Range<text::Anchor>,
19406 cx: &mut App,
19407 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19408
19409 fn resolve_inlay_hint(
19410 &self,
19411 hint: InlayHint,
19412 buffer_handle: Entity<Buffer>,
19413 server_id: LanguageServerId,
19414 cx: &mut App,
19415 ) -> Option<Task<anyhow::Result<InlayHint>>>;
19416
19417 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
19418
19419 fn document_highlights(
19420 &self,
19421 buffer: &Entity<Buffer>,
19422 position: text::Anchor,
19423 cx: &mut App,
19424 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
19425
19426 fn definitions(
19427 &self,
19428 buffer: &Entity<Buffer>,
19429 position: text::Anchor,
19430 kind: GotoDefinitionKind,
19431 cx: &mut App,
19432 ) -> Option<Task<Result<Vec<LocationLink>>>>;
19433
19434 fn range_for_rename(
19435 &self,
19436 buffer: &Entity<Buffer>,
19437 position: text::Anchor,
19438 cx: &mut App,
19439 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
19440
19441 fn perform_rename(
19442 &self,
19443 buffer: &Entity<Buffer>,
19444 position: text::Anchor,
19445 new_name: String,
19446 cx: &mut App,
19447 ) -> Option<Task<Result<ProjectTransaction>>>;
19448}
19449
19450pub trait CompletionProvider {
19451 fn completions(
19452 &self,
19453 excerpt_id: ExcerptId,
19454 buffer: &Entity<Buffer>,
19455 buffer_position: text::Anchor,
19456 trigger: CompletionContext,
19457 window: &mut Window,
19458 cx: &mut Context<Editor>,
19459 ) -> Task<Result<Option<Vec<Completion>>>>;
19460
19461 fn resolve_completions(
19462 &self,
19463 buffer: Entity<Buffer>,
19464 completion_indices: Vec<usize>,
19465 completions: Rc<RefCell<Box<[Completion]>>>,
19466 cx: &mut Context<Editor>,
19467 ) -> Task<Result<bool>>;
19468
19469 fn apply_additional_edits_for_completion(
19470 &self,
19471 _buffer: Entity<Buffer>,
19472 _completions: Rc<RefCell<Box<[Completion]>>>,
19473 _completion_index: usize,
19474 _push_to_history: bool,
19475 _cx: &mut Context<Editor>,
19476 ) -> Task<Result<Option<language::Transaction>>> {
19477 Task::ready(Ok(None))
19478 }
19479
19480 fn is_completion_trigger(
19481 &self,
19482 buffer: &Entity<Buffer>,
19483 position: language::Anchor,
19484 text: &str,
19485 trigger_in_words: bool,
19486 cx: &mut Context<Editor>,
19487 ) -> bool;
19488
19489 fn sort_completions(&self) -> bool {
19490 true
19491 }
19492
19493 fn filter_completions(&self) -> bool {
19494 true
19495 }
19496}
19497
19498pub trait CodeActionProvider {
19499 fn id(&self) -> Arc<str>;
19500
19501 fn code_actions(
19502 &self,
19503 buffer: &Entity<Buffer>,
19504 range: Range<text::Anchor>,
19505 window: &mut Window,
19506 cx: &mut App,
19507 ) -> Task<Result<Vec<CodeAction>>>;
19508
19509 fn apply_code_action(
19510 &self,
19511 buffer_handle: Entity<Buffer>,
19512 action: CodeAction,
19513 excerpt_id: ExcerptId,
19514 push_to_history: bool,
19515 window: &mut Window,
19516 cx: &mut App,
19517 ) -> Task<Result<ProjectTransaction>>;
19518}
19519
19520impl CodeActionProvider for Entity<Project> {
19521 fn id(&self) -> Arc<str> {
19522 "project".into()
19523 }
19524
19525 fn code_actions(
19526 &self,
19527 buffer: &Entity<Buffer>,
19528 range: Range<text::Anchor>,
19529 _window: &mut Window,
19530 cx: &mut App,
19531 ) -> Task<Result<Vec<CodeAction>>> {
19532 self.update(cx, |project, cx| {
19533 let code_lens = project.code_lens(buffer, range.clone(), cx);
19534 let code_actions = project.code_actions(buffer, range, None, cx);
19535 cx.background_spawn(async move {
19536 let (code_lens, code_actions) = join(code_lens, code_actions).await;
19537 Ok(code_lens
19538 .context("code lens fetch")?
19539 .into_iter()
19540 .chain(code_actions.context("code action fetch")?)
19541 .collect())
19542 })
19543 })
19544 }
19545
19546 fn apply_code_action(
19547 &self,
19548 buffer_handle: Entity<Buffer>,
19549 action: CodeAction,
19550 _excerpt_id: ExcerptId,
19551 push_to_history: bool,
19552 _window: &mut Window,
19553 cx: &mut App,
19554 ) -> Task<Result<ProjectTransaction>> {
19555 self.update(cx, |project, cx| {
19556 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19557 })
19558 }
19559}
19560
19561fn snippet_completions(
19562 project: &Project,
19563 buffer: &Entity<Buffer>,
19564 buffer_position: text::Anchor,
19565 cx: &mut App,
19566) -> Task<Result<Vec<Completion>>> {
19567 let languages = buffer.read(cx).languages_at(buffer_position);
19568 let snippet_store = project.snippets().read(cx);
19569
19570 let scopes: Vec<_> = languages
19571 .iter()
19572 .filter_map(|language| {
19573 let language_name = language.lsp_id();
19574 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19575
19576 if snippets.is_empty() {
19577 None
19578 } else {
19579 Some((language.default_scope(), snippets))
19580 }
19581 })
19582 .collect();
19583
19584 if scopes.is_empty() {
19585 return Task::ready(Ok(vec![]));
19586 }
19587
19588 let snapshot = buffer.read(cx).text_snapshot();
19589 let chars: String = snapshot
19590 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19591 .collect();
19592 let executor = cx.background_executor().clone();
19593
19594 cx.background_spawn(async move {
19595 let mut all_results: Vec<Completion> = Vec::new();
19596 for (scope, snippets) in scopes.into_iter() {
19597 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19598 let mut last_word = chars
19599 .chars()
19600 .take_while(|c| classifier.is_word(*c))
19601 .collect::<String>();
19602 last_word = last_word.chars().rev().collect();
19603
19604 if last_word.is_empty() {
19605 return Ok(vec![]);
19606 }
19607
19608 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
19609 let to_lsp = |point: &text::Anchor| {
19610 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
19611 point_to_lsp(end)
19612 };
19613 let lsp_end = to_lsp(&buffer_position);
19614
19615 let candidates = snippets
19616 .iter()
19617 .enumerate()
19618 .flat_map(|(ix, snippet)| {
19619 snippet
19620 .prefix
19621 .iter()
19622 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
19623 })
19624 .collect::<Vec<StringMatchCandidate>>();
19625
19626 let mut matches = fuzzy::match_strings(
19627 &candidates,
19628 &last_word,
19629 last_word.chars().any(|c| c.is_uppercase()),
19630 100,
19631 &Default::default(),
19632 executor.clone(),
19633 )
19634 .await;
19635
19636 // Remove all candidates where the query's start does not match the start of any word in the candidate
19637 if let Some(query_start) = last_word.chars().next() {
19638 matches.retain(|string_match| {
19639 split_words(&string_match.string).any(|word| {
19640 // Check that the first codepoint of the word as lowercase matches the first
19641 // codepoint of the query as lowercase
19642 word.chars()
19643 .flat_map(|codepoint| codepoint.to_lowercase())
19644 .zip(query_start.to_lowercase())
19645 .all(|(word_cp, query_cp)| word_cp == query_cp)
19646 })
19647 });
19648 }
19649
19650 let matched_strings = matches
19651 .into_iter()
19652 .map(|m| m.string)
19653 .collect::<HashSet<_>>();
19654
19655 let mut result: Vec<Completion> = snippets
19656 .iter()
19657 .filter_map(|snippet| {
19658 let matching_prefix = snippet
19659 .prefix
19660 .iter()
19661 .find(|prefix| matched_strings.contains(*prefix))?;
19662 let start = as_offset - last_word.len();
19663 let start = snapshot.anchor_before(start);
19664 let range = start..buffer_position;
19665 let lsp_start = to_lsp(&start);
19666 let lsp_range = lsp::Range {
19667 start: lsp_start,
19668 end: lsp_end,
19669 };
19670 Some(Completion {
19671 replace_range: range,
19672 new_text: snippet.body.clone(),
19673 source: CompletionSource::Lsp {
19674 insert_range: None,
19675 server_id: LanguageServerId(usize::MAX),
19676 resolved: true,
19677 lsp_completion: Box::new(lsp::CompletionItem {
19678 label: snippet.prefix.first().unwrap().clone(),
19679 kind: Some(CompletionItemKind::SNIPPET),
19680 label_details: snippet.description.as_ref().map(|description| {
19681 lsp::CompletionItemLabelDetails {
19682 detail: Some(description.clone()),
19683 description: None,
19684 }
19685 }),
19686 insert_text_format: Some(InsertTextFormat::SNIPPET),
19687 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19688 lsp::InsertReplaceEdit {
19689 new_text: snippet.body.clone(),
19690 insert: lsp_range,
19691 replace: lsp_range,
19692 },
19693 )),
19694 filter_text: Some(snippet.body.clone()),
19695 sort_text: Some(char::MAX.to_string()),
19696 ..lsp::CompletionItem::default()
19697 }),
19698 lsp_defaults: None,
19699 },
19700 label: CodeLabel {
19701 text: matching_prefix.clone(),
19702 runs: Vec::new(),
19703 filter_range: 0..matching_prefix.len(),
19704 },
19705 icon_path: None,
19706 documentation: snippet.description.clone().map(|description| {
19707 CompletionDocumentation::SingleLine(description.into())
19708 }),
19709 insert_text_mode: None,
19710 confirm: None,
19711 })
19712 })
19713 .collect();
19714
19715 all_results.append(&mut result);
19716 }
19717
19718 Ok(all_results)
19719 })
19720}
19721
19722impl CompletionProvider for Entity<Project> {
19723 fn completions(
19724 &self,
19725 _excerpt_id: ExcerptId,
19726 buffer: &Entity<Buffer>,
19727 buffer_position: text::Anchor,
19728 options: CompletionContext,
19729 _window: &mut Window,
19730 cx: &mut Context<Editor>,
19731 ) -> Task<Result<Option<Vec<Completion>>>> {
19732 self.update(cx, |project, cx| {
19733 let snippets = snippet_completions(project, buffer, buffer_position, cx);
19734 let project_completions = project.completions(buffer, buffer_position, options, cx);
19735 cx.background_spawn(async move {
19736 let snippets_completions = snippets.await?;
19737 match project_completions.await? {
19738 Some(mut completions) => {
19739 completions.extend(snippets_completions);
19740 Ok(Some(completions))
19741 }
19742 None => {
19743 if snippets_completions.is_empty() {
19744 Ok(None)
19745 } else {
19746 Ok(Some(snippets_completions))
19747 }
19748 }
19749 }
19750 })
19751 })
19752 }
19753
19754 fn resolve_completions(
19755 &self,
19756 buffer: Entity<Buffer>,
19757 completion_indices: Vec<usize>,
19758 completions: Rc<RefCell<Box<[Completion]>>>,
19759 cx: &mut Context<Editor>,
19760 ) -> Task<Result<bool>> {
19761 self.update(cx, |project, cx| {
19762 project.lsp_store().update(cx, |lsp_store, cx| {
19763 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
19764 })
19765 })
19766 }
19767
19768 fn apply_additional_edits_for_completion(
19769 &self,
19770 buffer: Entity<Buffer>,
19771 completions: Rc<RefCell<Box<[Completion]>>>,
19772 completion_index: usize,
19773 push_to_history: bool,
19774 cx: &mut Context<Editor>,
19775 ) -> Task<Result<Option<language::Transaction>>> {
19776 self.update(cx, |project, cx| {
19777 project.lsp_store().update(cx, |lsp_store, cx| {
19778 lsp_store.apply_additional_edits_for_completion(
19779 buffer,
19780 completions,
19781 completion_index,
19782 push_to_history,
19783 cx,
19784 )
19785 })
19786 })
19787 }
19788
19789 fn is_completion_trigger(
19790 &self,
19791 buffer: &Entity<Buffer>,
19792 position: language::Anchor,
19793 text: &str,
19794 trigger_in_words: bool,
19795 cx: &mut Context<Editor>,
19796 ) -> bool {
19797 let mut chars = text.chars();
19798 let char = if let Some(char) = chars.next() {
19799 char
19800 } else {
19801 return false;
19802 };
19803 if chars.next().is_some() {
19804 return false;
19805 }
19806
19807 let buffer = buffer.read(cx);
19808 let snapshot = buffer.snapshot();
19809 if !snapshot.settings_at(position, cx).show_completions_on_input {
19810 return false;
19811 }
19812 let classifier = snapshot.char_classifier_at(position).for_completion(true);
19813 if trigger_in_words && classifier.is_word(char) {
19814 return true;
19815 }
19816
19817 buffer.completion_triggers().contains(text)
19818 }
19819}
19820
19821impl SemanticsProvider for Entity<Project> {
19822 fn hover(
19823 &self,
19824 buffer: &Entity<Buffer>,
19825 position: text::Anchor,
19826 cx: &mut App,
19827 ) -> Option<Task<Vec<project::Hover>>> {
19828 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
19829 }
19830
19831 fn document_highlights(
19832 &self,
19833 buffer: &Entity<Buffer>,
19834 position: text::Anchor,
19835 cx: &mut App,
19836 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
19837 Some(self.update(cx, |project, cx| {
19838 project.document_highlights(buffer, position, cx)
19839 }))
19840 }
19841
19842 fn definitions(
19843 &self,
19844 buffer: &Entity<Buffer>,
19845 position: text::Anchor,
19846 kind: GotoDefinitionKind,
19847 cx: &mut App,
19848 ) -> Option<Task<Result<Vec<LocationLink>>>> {
19849 Some(self.update(cx, |project, cx| match kind {
19850 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
19851 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
19852 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
19853 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
19854 }))
19855 }
19856
19857 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
19858 // TODO: make this work for remote projects
19859 self.update(cx, |project, cx| {
19860 if project
19861 .active_debug_session(cx)
19862 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
19863 {
19864 return true;
19865 }
19866
19867 buffer.update(cx, |buffer, cx| {
19868 project.any_language_server_supports_inlay_hints(buffer, cx)
19869 })
19870 })
19871 }
19872
19873 fn inline_values(
19874 &self,
19875 buffer_handle: Entity<Buffer>,
19876 range: Range<text::Anchor>,
19877 cx: &mut App,
19878 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
19879 self.update(cx, |project, cx| {
19880 let (session, active_stack_frame) = project.active_debug_session(cx)?;
19881
19882 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
19883 })
19884 }
19885
19886 fn inlay_hints(
19887 &self,
19888 buffer_handle: Entity<Buffer>,
19889 range: Range<text::Anchor>,
19890 cx: &mut App,
19891 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
19892 Some(self.update(cx, |project, cx| {
19893 project.inlay_hints(buffer_handle, range, cx)
19894 }))
19895 }
19896
19897 fn resolve_inlay_hint(
19898 &self,
19899 hint: InlayHint,
19900 buffer_handle: Entity<Buffer>,
19901 server_id: LanguageServerId,
19902 cx: &mut App,
19903 ) -> Option<Task<anyhow::Result<InlayHint>>> {
19904 Some(self.update(cx, |project, cx| {
19905 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
19906 }))
19907 }
19908
19909 fn range_for_rename(
19910 &self,
19911 buffer: &Entity<Buffer>,
19912 position: text::Anchor,
19913 cx: &mut App,
19914 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
19915 Some(self.update(cx, |project, cx| {
19916 let buffer = buffer.clone();
19917 let task = project.prepare_rename(buffer.clone(), position, cx);
19918 cx.spawn(async move |_, cx| {
19919 Ok(match task.await? {
19920 PrepareRenameResponse::Success(range) => Some(range),
19921 PrepareRenameResponse::InvalidPosition => None,
19922 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
19923 // Fallback on using TreeSitter info to determine identifier range
19924 buffer.update(cx, |buffer, _| {
19925 let snapshot = buffer.snapshot();
19926 let (range, kind) = snapshot.surrounding_word(position);
19927 if kind != Some(CharKind::Word) {
19928 return None;
19929 }
19930 Some(
19931 snapshot.anchor_before(range.start)
19932 ..snapshot.anchor_after(range.end),
19933 )
19934 })?
19935 }
19936 })
19937 })
19938 }))
19939 }
19940
19941 fn perform_rename(
19942 &self,
19943 buffer: &Entity<Buffer>,
19944 position: text::Anchor,
19945 new_name: String,
19946 cx: &mut App,
19947 ) -> Option<Task<Result<ProjectTransaction>>> {
19948 Some(self.update(cx, |project, cx| {
19949 project.perform_rename(buffer.clone(), position, new_name, cx)
19950 }))
19951 }
19952}
19953
19954fn inlay_hint_settings(
19955 location: Anchor,
19956 snapshot: &MultiBufferSnapshot,
19957 cx: &mut Context<Editor>,
19958) -> InlayHintSettings {
19959 let file = snapshot.file_at(location);
19960 let language = snapshot.language_at(location).map(|l| l.name());
19961 language_settings(language, file, cx).inlay_hints
19962}
19963
19964fn consume_contiguous_rows(
19965 contiguous_row_selections: &mut Vec<Selection<Point>>,
19966 selection: &Selection<Point>,
19967 display_map: &DisplaySnapshot,
19968 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
19969) -> (MultiBufferRow, MultiBufferRow) {
19970 contiguous_row_selections.push(selection.clone());
19971 let start_row = MultiBufferRow(selection.start.row);
19972 let mut end_row = ending_row(selection, display_map);
19973
19974 while let Some(next_selection) = selections.peek() {
19975 if next_selection.start.row <= end_row.0 {
19976 end_row = ending_row(next_selection, display_map);
19977 contiguous_row_selections.push(selections.next().unwrap().clone());
19978 } else {
19979 break;
19980 }
19981 }
19982 (start_row, end_row)
19983}
19984
19985fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
19986 if next_selection.end.column > 0 || next_selection.is_empty() {
19987 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
19988 } else {
19989 MultiBufferRow(next_selection.end.row)
19990 }
19991}
19992
19993impl EditorSnapshot {
19994 pub fn remote_selections_in_range<'a>(
19995 &'a self,
19996 range: &'a Range<Anchor>,
19997 collaboration_hub: &dyn CollaborationHub,
19998 cx: &'a App,
19999 ) -> impl 'a + Iterator<Item = RemoteSelection> {
20000 let participant_names = collaboration_hub.user_names(cx);
20001 let participant_indices = collaboration_hub.user_participant_indices(cx);
20002 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20003 let collaborators_by_replica_id = collaborators_by_peer_id
20004 .iter()
20005 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
20006 .collect::<HashMap<_, _>>();
20007 self.buffer_snapshot
20008 .selections_in_range(range, false)
20009 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20010 if replica_id == AGENT_REPLICA_ID {
20011 Some(RemoteSelection {
20012 replica_id,
20013 selection,
20014 cursor_shape,
20015 line_mode,
20016 collaborator_id: CollaboratorId::Agent,
20017 user_name: Some("Agent".into()),
20018 color: cx.theme().players().agent(),
20019 })
20020 } else {
20021 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20022 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20023 let user_name = participant_names.get(&collaborator.user_id).cloned();
20024 Some(RemoteSelection {
20025 replica_id,
20026 selection,
20027 cursor_shape,
20028 line_mode,
20029 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20030 user_name,
20031 color: if let Some(index) = participant_index {
20032 cx.theme().players().color_for_participant(index.0)
20033 } else {
20034 cx.theme().players().absent()
20035 },
20036 })
20037 }
20038 })
20039 }
20040
20041 pub fn hunks_for_ranges(
20042 &self,
20043 ranges: impl IntoIterator<Item = Range<Point>>,
20044 ) -> Vec<MultiBufferDiffHunk> {
20045 let mut hunks = Vec::new();
20046 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20047 HashMap::default();
20048 for query_range in ranges {
20049 let query_rows =
20050 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20051 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20052 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20053 ) {
20054 // Include deleted hunks that are adjacent to the query range, because
20055 // otherwise they would be missed.
20056 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20057 if hunk.status().is_deleted() {
20058 intersects_range |= hunk.row_range.start == query_rows.end;
20059 intersects_range |= hunk.row_range.end == query_rows.start;
20060 }
20061 if intersects_range {
20062 if !processed_buffer_rows
20063 .entry(hunk.buffer_id)
20064 .or_default()
20065 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20066 {
20067 continue;
20068 }
20069 hunks.push(hunk);
20070 }
20071 }
20072 }
20073
20074 hunks
20075 }
20076
20077 fn display_diff_hunks_for_rows<'a>(
20078 &'a self,
20079 display_rows: Range<DisplayRow>,
20080 folded_buffers: &'a HashSet<BufferId>,
20081 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20082 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20083 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20084
20085 self.buffer_snapshot
20086 .diff_hunks_in_range(buffer_start..buffer_end)
20087 .filter_map(|hunk| {
20088 if folded_buffers.contains(&hunk.buffer_id) {
20089 return None;
20090 }
20091
20092 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20093 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20094
20095 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20096 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20097
20098 let display_hunk = if hunk_display_start.column() != 0 {
20099 DisplayDiffHunk::Folded {
20100 display_row: hunk_display_start.row(),
20101 }
20102 } else {
20103 let mut end_row = hunk_display_end.row();
20104 if hunk_display_end.column() > 0 {
20105 end_row.0 += 1;
20106 }
20107 let is_created_file = hunk.is_created_file();
20108 DisplayDiffHunk::Unfolded {
20109 status: hunk.status(),
20110 diff_base_byte_range: hunk.diff_base_byte_range,
20111 display_row_range: hunk_display_start.row()..end_row,
20112 multi_buffer_range: Anchor::range_in_buffer(
20113 hunk.excerpt_id,
20114 hunk.buffer_id,
20115 hunk.buffer_range,
20116 ),
20117 is_created_file,
20118 }
20119 };
20120
20121 Some(display_hunk)
20122 })
20123 }
20124
20125 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20126 self.display_snapshot.buffer_snapshot.language_at(position)
20127 }
20128
20129 pub fn is_focused(&self) -> bool {
20130 self.is_focused
20131 }
20132
20133 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20134 self.placeholder_text.as_ref()
20135 }
20136
20137 pub fn scroll_position(&self) -> gpui::Point<f32> {
20138 self.scroll_anchor.scroll_position(&self.display_snapshot)
20139 }
20140
20141 fn gutter_dimensions(
20142 &self,
20143 font_id: FontId,
20144 font_size: Pixels,
20145 max_line_number_width: Pixels,
20146 cx: &App,
20147 ) -> Option<GutterDimensions> {
20148 if !self.show_gutter {
20149 return None;
20150 }
20151
20152 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20153 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20154
20155 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20156 matches!(
20157 ProjectSettings::get_global(cx).git.git_gutter,
20158 Some(GitGutterSetting::TrackedFiles)
20159 )
20160 });
20161 let gutter_settings = EditorSettings::get_global(cx).gutter;
20162 let show_line_numbers = self
20163 .show_line_numbers
20164 .unwrap_or(gutter_settings.line_numbers);
20165 let line_gutter_width = if show_line_numbers {
20166 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20167 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20168 max_line_number_width.max(min_width_for_number_on_gutter)
20169 } else {
20170 0.0.into()
20171 };
20172
20173 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20174 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20175
20176 let git_blame_entries_width =
20177 self.git_blame_gutter_max_author_length
20178 .map(|max_author_length| {
20179 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20180 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20181
20182 /// The number of characters to dedicate to gaps and margins.
20183 const SPACING_WIDTH: usize = 4;
20184
20185 let max_char_count = max_author_length.min(renderer.max_author_length())
20186 + ::git::SHORT_SHA_LENGTH
20187 + MAX_RELATIVE_TIMESTAMP.len()
20188 + SPACING_WIDTH;
20189
20190 em_advance * max_char_count
20191 });
20192
20193 let is_singleton = self.buffer_snapshot.is_singleton();
20194
20195 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
20196 left_padding += if !is_singleton {
20197 em_width * 4.0
20198 } else if show_runnables || show_breakpoints {
20199 em_width * 3.0
20200 } else if show_git_gutter && show_line_numbers {
20201 em_width * 2.0
20202 } else if show_git_gutter || show_line_numbers {
20203 em_width
20204 } else {
20205 px(0.)
20206 };
20207
20208 let shows_folds = is_singleton && gutter_settings.folds;
20209
20210 let right_padding = if shows_folds && show_line_numbers {
20211 em_width * 4.0
20212 } else if shows_folds || (!is_singleton && show_line_numbers) {
20213 em_width * 3.0
20214 } else if show_line_numbers {
20215 em_width
20216 } else {
20217 px(0.)
20218 };
20219
20220 Some(GutterDimensions {
20221 left_padding,
20222 right_padding,
20223 width: line_gutter_width + left_padding + right_padding,
20224 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
20225 git_blame_entries_width,
20226 })
20227 }
20228
20229 pub fn render_crease_toggle(
20230 &self,
20231 buffer_row: MultiBufferRow,
20232 row_contains_cursor: bool,
20233 editor: Entity<Editor>,
20234 window: &mut Window,
20235 cx: &mut App,
20236 ) -> Option<AnyElement> {
20237 let folded = self.is_line_folded(buffer_row);
20238 let mut is_foldable = false;
20239
20240 if let Some(crease) = self
20241 .crease_snapshot
20242 .query_row(buffer_row, &self.buffer_snapshot)
20243 {
20244 is_foldable = true;
20245 match crease {
20246 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20247 if let Some(render_toggle) = render_toggle {
20248 let toggle_callback =
20249 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20250 if folded {
20251 editor.update(cx, |editor, cx| {
20252 editor.fold_at(buffer_row, window, cx)
20253 });
20254 } else {
20255 editor.update(cx, |editor, cx| {
20256 editor.unfold_at(buffer_row, window, cx)
20257 });
20258 }
20259 });
20260 return Some((render_toggle)(
20261 buffer_row,
20262 folded,
20263 toggle_callback,
20264 window,
20265 cx,
20266 ));
20267 }
20268 }
20269 }
20270 }
20271
20272 is_foldable |= self.starts_indent(buffer_row);
20273
20274 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20275 Some(
20276 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20277 .toggle_state(folded)
20278 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20279 if folded {
20280 this.unfold_at(buffer_row, window, cx);
20281 } else {
20282 this.fold_at(buffer_row, window, cx);
20283 }
20284 }))
20285 .into_any_element(),
20286 )
20287 } else {
20288 None
20289 }
20290 }
20291
20292 pub fn render_crease_trailer(
20293 &self,
20294 buffer_row: MultiBufferRow,
20295 window: &mut Window,
20296 cx: &mut App,
20297 ) -> Option<AnyElement> {
20298 let folded = self.is_line_folded(buffer_row);
20299 if let Crease::Inline { render_trailer, .. } = self
20300 .crease_snapshot
20301 .query_row(buffer_row, &self.buffer_snapshot)?
20302 {
20303 let render_trailer = render_trailer.as_ref()?;
20304 Some(render_trailer(buffer_row, folded, window, cx))
20305 } else {
20306 None
20307 }
20308 }
20309}
20310
20311impl Deref for EditorSnapshot {
20312 type Target = DisplaySnapshot;
20313
20314 fn deref(&self) -> &Self::Target {
20315 &self.display_snapshot
20316 }
20317}
20318
20319#[derive(Clone, Debug, PartialEq, Eq)]
20320pub enum EditorEvent {
20321 InputIgnored {
20322 text: Arc<str>,
20323 },
20324 InputHandled {
20325 utf16_range_to_replace: Option<Range<isize>>,
20326 text: Arc<str>,
20327 },
20328 ExcerptsAdded {
20329 buffer: Entity<Buffer>,
20330 predecessor: ExcerptId,
20331 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
20332 },
20333 ExcerptsRemoved {
20334 ids: Vec<ExcerptId>,
20335 removed_buffer_ids: Vec<BufferId>,
20336 },
20337 BufferFoldToggled {
20338 ids: Vec<ExcerptId>,
20339 folded: bool,
20340 },
20341 ExcerptsEdited {
20342 ids: Vec<ExcerptId>,
20343 },
20344 ExcerptsExpanded {
20345 ids: Vec<ExcerptId>,
20346 },
20347 BufferEdited,
20348 Edited {
20349 transaction_id: clock::Lamport,
20350 },
20351 Reparsed(BufferId),
20352 Focused,
20353 FocusedIn,
20354 Blurred,
20355 DirtyChanged,
20356 Saved,
20357 TitleChanged,
20358 DiffBaseChanged,
20359 SelectionsChanged {
20360 local: bool,
20361 },
20362 ScrollPositionChanged {
20363 local: bool,
20364 autoscroll: bool,
20365 },
20366 Closed,
20367 TransactionUndone {
20368 transaction_id: clock::Lamport,
20369 },
20370 TransactionBegun {
20371 transaction_id: clock::Lamport,
20372 },
20373 Reloaded,
20374 CursorShapeChanged,
20375 PushedToNavHistory {
20376 anchor: Anchor,
20377 is_deactivate: bool,
20378 },
20379}
20380
20381impl EventEmitter<EditorEvent> for Editor {}
20382
20383impl Focusable for Editor {
20384 fn focus_handle(&self, _cx: &App) -> FocusHandle {
20385 self.focus_handle.clone()
20386 }
20387}
20388
20389impl Render for Editor {
20390 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20391 let settings = ThemeSettings::get_global(cx);
20392
20393 let mut text_style = match self.mode {
20394 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
20395 color: cx.theme().colors().editor_foreground,
20396 font_family: settings.ui_font.family.clone(),
20397 font_features: settings.ui_font.features.clone(),
20398 font_fallbacks: settings.ui_font.fallbacks.clone(),
20399 font_size: rems(0.875).into(),
20400 font_weight: settings.ui_font.weight,
20401 line_height: relative(settings.buffer_line_height.value()),
20402 ..Default::default()
20403 },
20404 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
20405 color: cx.theme().colors().editor_foreground,
20406 font_family: settings.buffer_font.family.clone(),
20407 font_features: settings.buffer_font.features.clone(),
20408 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20409 font_size: settings.buffer_font_size(cx).into(),
20410 font_weight: settings.buffer_font.weight,
20411 line_height: relative(settings.buffer_line_height.value()),
20412 ..Default::default()
20413 },
20414 };
20415 if let Some(text_style_refinement) = &self.text_style_refinement {
20416 text_style.refine(text_style_refinement)
20417 }
20418
20419 let background = match self.mode {
20420 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
20421 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
20422 EditorMode::Full { .. } => cx.theme().colors().editor_background,
20423 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
20424 };
20425
20426 let show_underlines = !self.mode.is_minimap();
20427
20428 EditorElement::new(
20429 &cx.entity(),
20430 EditorStyle {
20431 background,
20432 local_player: cx.theme().players().local(),
20433 text: text_style,
20434 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
20435 syntax: cx.theme().syntax().clone(),
20436 status: cx.theme().status().clone(),
20437 inlay_hints_style: make_inlay_hints_style(cx),
20438 inline_completion_styles: make_suggestion_styles(cx),
20439 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
20440 show_underlines,
20441 },
20442 )
20443 }
20444}
20445
20446impl EntityInputHandler for Editor {
20447 fn text_for_range(
20448 &mut self,
20449 range_utf16: Range<usize>,
20450 adjusted_range: &mut Option<Range<usize>>,
20451 _: &mut Window,
20452 cx: &mut Context<Self>,
20453 ) -> Option<String> {
20454 let snapshot = self.buffer.read(cx).read(cx);
20455 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
20456 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
20457 if (start.0..end.0) != range_utf16 {
20458 adjusted_range.replace(start.0..end.0);
20459 }
20460 Some(snapshot.text_for_range(start..end).collect())
20461 }
20462
20463 fn selected_text_range(
20464 &mut self,
20465 ignore_disabled_input: bool,
20466 _: &mut Window,
20467 cx: &mut Context<Self>,
20468 ) -> Option<UTF16Selection> {
20469 // Prevent the IME menu from appearing when holding down an alphabetic key
20470 // while input is disabled.
20471 if !ignore_disabled_input && !self.input_enabled {
20472 return None;
20473 }
20474
20475 let selection = self.selections.newest::<OffsetUtf16>(cx);
20476 let range = selection.range();
20477
20478 Some(UTF16Selection {
20479 range: range.start.0..range.end.0,
20480 reversed: selection.reversed,
20481 })
20482 }
20483
20484 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
20485 let snapshot = self.buffer.read(cx).read(cx);
20486 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
20487 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
20488 }
20489
20490 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20491 self.clear_highlights::<InputComposition>(cx);
20492 self.ime_transaction.take();
20493 }
20494
20495 fn replace_text_in_range(
20496 &mut self,
20497 range_utf16: Option<Range<usize>>,
20498 text: &str,
20499 window: &mut Window,
20500 cx: &mut Context<Self>,
20501 ) {
20502 if !self.input_enabled {
20503 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20504 return;
20505 }
20506
20507 self.transact(window, cx, |this, window, cx| {
20508 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
20509 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20510 Some(this.selection_replacement_ranges(range_utf16, cx))
20511 } else {
20512 this.marked_text_ranges(cx)
20513 };
20514
20515 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
20516 let newest_selection_id = this.selections.newest_anchor().id;
20517 this.selections
20518 .all::<OffsetUtf16>(cx)
20519 .iter()
20520 .zip(ranges_to_replace.iter())
20521 .find_map(|(selection, range)| {
20522 if selection.id == newest_selection_id {
20523 Some(
20524 (range.start.0 as isize - selection.head().0 as isize)
20525 ..(range.end.0 as isize - selection.head().0 as isize),
20526 )
20527 } else {
20528 None
20529 }
20530 })
20531 });
20532
20533 cx.emit(EditorEvent::InputHandled {
20534 utf16_range_to_replace: range_to_replace,
20535 text: text.into(),
20536 });
20537
20538 if let Some(new_selected_ranges) = new_selected_ranges {
20539 this.change_selections(None, window, cx, |selections| {
20540 selections.select_ranges(new_selected_ranges)
20541 });
20542 this.backspace(&Default::default(), window, cx);
20543 }
20544
20545 this.handle_input(text, window, cx);
20546 });
20547
20548 if let Some(transaction) = self.ime_transaction {
20549 self.buffer.update(cx, |buffer, cx| {
20550 buffer.group_until_transaction(transaction, cx);
20551 });
20552 }
20553
20554 self.unmark_text(window, cx);
20555 }
20556
20557 fn replace_and_mark_text_in_range(
20558 &mut self,
20559 range_utf16: Option<Range<usize>>,
20560 text: &str,
20561 new_selected_range_utf16: Option<Range<usize>>,
20562 window: &mut Window,
20563 cx: &mut Context<Self>,
20564 ) {
20565 if !self.input_enabled {
20566 return;
20567 }
20568
20569 let transaction = self.transact(window, cx, |this, window, cx| {
20570 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
20571 let snapshot = this.buffer.read(cx).read(cx);
20572 if let Some(relative_range_utf16) = range_utf16.as_ref() {
20573 for marked_range in &mut marked_ranges {
20574 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
20575 marked_range.start.0 += relative_range_utf16.start;
20576 marked_range.start =
20577 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
20578 marked_range.end =
20579 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
20580 }
20581 }
20582 Some(marked_ranges)
20583 } else if let Some(range_utf16) = range_utf16 {
20584 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20585 Some(this.selection_replacement_ranges(range_utf16, cx))
20586 } else {
20587 None
20588 };
20589
20590 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
20591 let newest_selection_id = this.selections.newest_anchor().id;
20592 this.selections
20593 .all::<OffsetUtf16>(cx)
20594 .iter()
20595 .zip(ranges_to_replace.iter())
20596 .find_map(|(selection, range)| {
20597 if selection.id == newest_selection_id {
20598 Some(
20599 (range.start.0 as isize - selection.head().0 as isize)
20600 ..(range.end.0 as isize - selection.head().0 as isize),
20601 )
20602 } else {
20603 None
20604 }
20605 })
20606 });
20607
20608 cx.emit(EditorEvent::InputHandled {
20609 utf16_range_to_replace: range_to_replace,
20610 text: text.into(),
20611 });
20612
20613 if let Some(ranges) = ranges_to_replace {
20614 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
20615 }
20616
20617 let marked_ranges = {
20618 let snapshot = this.buffer.read(cx).read(cx);
20619 this.selections
20620 .disjoint_anchors()
20621 .iter()
20622 .map(|selection| {
20623 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
20624 })
20625 .collect::<Vec<_>>()
20626 };
20627
20628 if text.is_empty() {
20629 this.unmark_text(window, cx);
20630 } else {
20631 this.highlight_text::<InputComposition>(
20632 marked_ranges.clone(),
20633 HighlightStyle {
20634 underline: Some(UnderlineStyle {
20635 thickness: px(1.),
20636 color: None,
20637 wavy: false,
20638 }),
20639 ..Default::default()
20640 },
20641 cx,
20642 );
20643 }
20644
20645 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
20646 let use_autoclose = this.use_autoclose;
20647 let use_auto_surround = this.use_auto_surround;
20648 this.set_use_autoclose(false);
20649 this.set_use_auto_surround(false);
20650 this.handle_input(text, window, cx);
20651 this.set_use_autoclose(use_autoclose);
20652 this.set_use_auto_surround(use_auto_surround);
20653
20654 if let Some(new_selected_range) = new_selected_range_utf16 {
20655 let snapshot = this.buffer.read(cx).read(cx);
20656 let new_selected_ranges = marked_ranges
20657 .into_iter()
20658 .map(|marked_range| {
20659 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
20660 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
20661 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
20662 snapshot.clip_offset_utf16(new_start, Bias::Left)
20663 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
20664 })
20665 .collect::<Vec<_>>();
20666
20667 drop(snapshot);
20668 this.change_selections(None, window, cx, |selections| {
20669 selections.select_ranges(new_selected_ranges)
20670 });
20671 }
20672 });
20673
20674 self.ime_transaction = self.ime_transaction.or(transaction);
20675 if let Some(transaction) = self.ime_transaction {
20676 self.buffer.update(cx, |buffer, cx| {
20677 buffer.group_until_transaction(transaction, cx);
20678 });
20679 }
20680
20681 if self.text_highlights::<InputComposition>(cx).is_none() {
20682 self.ime_transaction.take();
20683 }
20684 }
20685
20686 fn bounds_for_range(
20687 &mut self,
20688 range_utf16: Range<usize>,
20689 element_bounds: gpui::Bounds<Pixels>,
20690 window: &mut Window,
20691 cx: &mut Context<Self>,
20692 ) -> Option<gpui::Bounds<Pixels>> {
20693 let text_layout_details = self.text_layout_details(window);
20694 let gpui::Size {
20695 width: em_width,
20696 height: line_height,
20697 } = self.character_size(window);
20698
20699 let snapshot = self.snapshot(window, cx);
20700 let scroll_position = snapshot.scroll_position();
20701 let scroll_left = scroll_position.x * em_width;
20702
20703 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
20704 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
20705 + self.gutter_dimensions.width
20706 + self.gutter_dimensions.margin;
20707 let y = line_height * (start.row().as_f32() - scroll_position.y);
20708
20709 Some(Bounds {
20710 origin: element_bounds.origin + point(x, y),
20711 size: size(em_width, line_height),
20712 })
20713 }
20714
20715 fn character_index_for_point(
20716 &mut self,
20717 point: gpui::Point<Pixels>,
20718 _window: &mut Window,
20719 _cx: &mut Context<Self>,
20720 ) -> Option<usize> {
20721 let position_map = self.last_position_map.as_ref()?;
20722 if !position_map.text_hitbox.contains(&point) {
20723 return None;
20724 }
20725 let display_point = position_map.point_for_position(point).previous_valid;
20726 let anchor = position_map
20727 .snapshot
20728 .display_point_to_anchor(display_point, Bias::Left);
20729 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
20730 Some(utf16_offset.0)
20731 }
20732}
20733
20734trait SelectionExt {
20735 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
20736 fn spanned_rows(
20737 &self,
20738 include_end_if_at_line_start: bool,
20739 map: &DisplaySnapshot,
20740 ) -> Range<MultiBufferRow>;
20741}
20742
20743impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
20744 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
20745 let start = self
20746 .start
20747 .to_point(&map.buffer_snapshot)
20748 .to_display_point(map);
20749 let end = self
20750 .end
20751 .to_point(&map.buffer_snapshot)
20752 .to_display_point(map);
20753 if self.reversed {
20754 end..start
20755 } else {
20756 start..end
20757 }
20758 }
20759
20760 fn spanned_rows(
20761 &self,
20762 include_end_if_at_line_start: bool,
20763 map: &DisplaySnapshot,
20764 ) -> Range<MultiBufferRow> {
20765 let start = self.start.to_point(&map.buffer_snapshot);
20766 let mut end = self.end.to_point(&map.buffer_snapshot);
20767 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
20768 end.row -= 1;
20769 }
20770
20771 let buffer_start = map.prev_line_boundary(start).0;
20772 let buffer_end = map.next_line_boundary(end).0;
20773 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
20774 }
20775}
20776
20777impl<T: InvalidationRegion> InvalidationStack<T> {
20778 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
20779 where
20780 S: Clone + ToOffset,
20781 {
20782 while let Some(region) = self.last() {
20783 let all_selections_inside_invalidation_ranges =
20784 if selections.len() == region.ranges().len() {
20785 selections
20786 .iter()
20787 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
20788 .all(|(selection, invalidation_range)| {
20789 let head = selection.head().to_offset(buffer);
20790 invalidation_range.start <= head && invalidation_range.end >= head
20791 })
20792 } else {
20793 false
20794 };
20795
20796 if all_selections_inside_invalidation_ranges {
20797 break;
20798 } else {
20799 self.pop();
20800 }
20801 }
20802 }
20803}
20804
20805impl<T> Default for InvalidationStack<T> {
20806 fn default() -> Self {
20807 Self(Default::default())
20808 }
20809}
20810
20811impl<T> Deref for InvalidationStack<T> {
20812 type Target = Vec<T>;
20813
20814 fn deref(&self) -> &Self::Target {
20815 &self.0
20816 }
20817}
20818
20819impl<T> DerefMut for InvalidationStack<T> {
20820 fn deref_mut(&mut self) -> &mut Self::Target {
20821 &mut self.0
20822 }
20823}
20824
20825impl InvalidationRegion for SnippetState {
20826 fn ranges(&self) -> &[Range<Anchor>] {
20827 &self.ranges[self.active_index]
20828 }
20829}
20830
20831fn inline_completion_edit_text(
20832 current_snapshot: &BufferSnapshot,
20833 edits: &[(Range<Anchor>, String)],
20834 edit_preview: &EditPreview,
20835 include_deletions: bool,
20836 cx: &App,
20837) -> HighlightedText {
20838 let edits = edits
20839 .iter()
20840 .map(|(anchor, text)| {
20841 (
20842 anchor.start.text_anchor..anchor.end.text_anchor,
20843 text.clone(),
20844 )
20845 })
20846 .collect::<Vec<_>>();
20847
20848 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
20849}
20850
20851pub fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
20852 match severity {
20853 DiagnosticSeverity::ERROR => colors.error,
20854 DiagnosticSeverity::WARNING => colors.warning,
20855 DiagnosticSeverity::INFORMATION => colors.info,
20856 DiagnosticSeverity::HINT => colors.info,
20857 _ => colors.ignored,
20858 }
20859}
20860
20861pub fn styled_runs_for_code_label<'a>(
20862 label: &'a CodeLabel,
20863 syntax_theme: &'a theme::SyntaxTheme,
20864) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
20865 let fade_out = HighlightStyle {
20866 fade_out: Some(0.35),
20867 ..Default::default()
20868 };
20869
20870 let mut prev_end = label.filter_range.end;
20871 label
20872 .runs
20873 .iter()
20874 .enumerate()
20875 .flat_map(move |(ix, (range, highlight_id))| {
20876 let style = if let Some(style) = highlight_id.style(syntax_theme) {
20877 style
20878 } else {
20879 return Default::default();
20880 };
20881 let mut muted_style = style;
20882 muted_style.highlight(fade_out);
20883
20884 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
20885 if range.start >= label.filter_range.end {
20886 if range.start > prev_end {
20887 runs.push((prev_end..range.start, fade_out));
20888 }
20889 runs.push((range.clone(), muted_style));
20890 } else if range.end <= label.filter_range.end {
20891 runs.push((range.clone(), style));
20892 } else {
20893 runs.push((range.start..label.filter_range.end, style));
20894 runs.push((label.filter_range.end..range.end, muted_style));
20895 }
20896 prev_end = cmp::max(prev_end, range.end);
20897
20898 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
20899 runs.push((prev_end..label.text.len(), fade_out));
20900 }
20901
20902 runs
20903 })
20904}
20905
20906pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
20907 let mut prev_index = 0;
20908 let mut prev_codepoint: Option<char> = None;
20909 text.char_indices()
20910 .chain([(text.len(), '\0')])
20911 .filter_map(move |(index, codepoint)| {
20912 let prev_codepoint = prev_codepoint.replace(codepoint)?;
20913 let is_boundary = index == text.len()
20914 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
20915 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
20916 if is_boundary {
20917 let chunk = &text[prev_index..index];
20918 prev_index = index;
20919 Some(chunk)
20920 } else {
20921 None
20922 }
20923 })
20924}
20925
20926pub trait RangeToAnchorExt: Sized {
20927 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
20928
20929 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
20930 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
20931 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
20932 }
20933}
20934
20935impl<T: ToOffset> RangeToAnchorExt for Range<T> {
20936 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
20937 let start_offset = self.start.to_offset(snapshot);
20938 let end_offset = self.end.to_offset(snapshot);
20939 if start_offset == end_offset {
20940 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
20941 } else {
20942 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
20943 }
20944 }
20945}
20946
20947pub trait RowExt {
20948 fn as_f32(&self) -> f32;
20949
20950 fn next_row(&self) -> Self;
20951
20952 fn previous_row(&self) -> Self;
20953
20954 fn minus(&self, other: Self) -> u32;
20955}
20956
20957impl RowExt for DisplayRow {
20958 fn as_f32(&self) -> f32 {
20959 self.0 as f32
20960 }
20961
20962 fn next_row(&self) -> Self {
20963 Self(self.0 + 1)
20964 }
20965
20966 fn previous_row(&self) -> Self {
20967 Self(self.0.saturating_sub(1))
20968 }
20969
20970 fn minus(&self, other: Self) -> u32 {
20971 self.0 - other.0
20972 }
20973}
20974
20975impl RowExt for MultiBufferRow {
20976 fn as_f32(&self) -> f32 {
20977 self.0 as f32
20978 }
20979
20980 fn next_row(&self) -> Self {
20981 Self(self.0 + 1)
20982 }
20983
20984 fn previous_row(&self) -> Self {
20985 Self(self.0.saturating_sub(1))
20986 }
20987
20988 fn minus(&self, other: Self) -> u32 {
20989 self.0 - other.0
20990 }
20991}
20992
20993trait RowRangeExt {
20994 type Row;
20995
20996 fn len(&self) -> usize;
20997
20998 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
20999}
21000
21001impl RowRangeExt for Range<MultiBufferRow> {
21002 type Row = MultiBufferRow;
21003
21004 fn len(&self) -> usize {
21005 (self.end.0 - self.start.0) as usize
21006 }
21007
21008 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21009 (self.start.0..self.end.0).map(MultiBufferRow)
21010 }
21011}
21012
21013impl RowRangeExt for Range<DisplayRow> {
21014 type Row = DisplayRow;
21015
21016 fn len(&self) -> usize {
21017 (self.end.0 - self.start.0) as usize
21018 }
21019
21020 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21021 (self.start.0..self.end.0).map(DisplayRow)
21022 }
21023}
21024
21025/// If select range has more than one line, we
21026/// just point the cursor to range.start.
21027fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21028 if range.start.row == range.end.row {
21029 range
21030 } else {
21031 range.start..range.start
21032 }
21033}
21034pub struct KillRing(ClipboardItem);
21035impl Global for KillRing {}
21036
21037const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21038
21039enum BreakpointPromptEditAction {
21040 Log,
21041 Condition,
21042 HitCondition,
21043}
21044
21045struct BreakpointPromptEditor {
21046 pub(crate) prompt: Entity<Editor>,
21047 editor: WeakEntity<Editor>,
21048 breakpoint_anchor: Anchor,
21049 breakpoint: Breakpoint,
21050 edit_action: BreakpointPromptEditAction,
21051 block_ids: HashSet<CustomBlockId>,
21052 editor_margins: Arc<Mutex<EditorMargins>>,
21053 _subscriptions: Vec<Subscription>,
21054}
21055
21056impl BreakpointPromptEditor {
21057 const MAX_LINES: u8 = 4;
21058
21059 fn new(
21060 editor: WeakEntity<Editor>,
21061 breakpoint_anchor: Anchor,
21062 breakpoint: Breakpoint,
21063 edit_action: BreakpointPromptEditAction,
21064 window: &mut Window,
21065 cx: &mut Context<Self>,
21066 ) -> Self {
21067 let base_text = match edit_action {
21068 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21069 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21070 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21071 }
21072 .map(|msg| msg.to_string())
21073 .unwrap_or_default();
21074
21075 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21076 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21077
21078 let prompt = cx.new(|cx| {
21079 let mut prompt = Editor::new(
21080 EditorMode::AutoHeight {
21081 max_lines: Self::MAX_LINES as usize,
21082 },
21083 buffer,
21084 None,
21085 window,
21086 cx,
21087 );
21088 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21089 prompt.set_show_cursor_when_unfocused(false, cx);
21090 prompt.set_placeholder_text(
21091 match edit_action {
21092 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21093 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21094 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21095 },
21096 cx,
21097 );
21098
21099 prompt
21100 });
21101
21102 Self {
21103 prompt,
21104 editor,
21105 breakpoint_anchor,
21106 breakpoint,
21107 edit_action,
21108 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21109 block_ids: Default::default(),
21110 _subscriptions: vec![],
21111 }
21112 }
21113
21114 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21115 self.block_ids.extend(block_ids)
21116 }
21117
21118 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21119 if let Some(editor) = self.editor.upgrade() {
21120 let message = self
21121 .prompt
21122 .read(cx)
21123 .buffer
21124 .read(cx)
21125 .as_singleton()
21126 .expect("A multi buffer in breakpoint prompt isn't possible")
21127 .read(cx)
21128 .as_rope()
21129 .to_string();
21130
21131 editor.update(cx, |editor, cx| {
21132 editor.edit_breakpoint_at_anchor(
21133 self.breakpoint_anchor,
21134 self.breakpoint.clone(),
21135 match self.edit_action {
21136 BreakpointPromptEditAction::Log => {
21137 BreakpointEditAction::EditLogMessage(message.into())
21138 }
21139 BreakpointPromptEditAction::Condition => {
21140 BreakpointEditAction::EditCondition(message.into())
21141 }
21142 BreakpointPromptEditAction::HitCondition => {
21143 BreakpointEditAction::EditHitCondition(message.into())
21144 }
21145 },
21146 cx,
21147 );
21148
21149 editor.remove_blocks(self.block_ids.clone(), None, cx);
21150 cx.focus_self(window);
21151 });
21152 }
21153 }
21154
21155 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21156 self.editor
21157 .update(cx, |editor, cx| {
21158 editor.remove_blocks(self.block_ids.clone(), None, cx);
21159 window.focus(&editor.focus_handle);
21160 })
21161 .log_err();
21162 }
21163
21164 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21165 let settings = ThemeSettings::get_global(cx);
21166 let text_style = TextStyle {
21167 color: if self.prompt.read(cx).read_only(cx) {
21168 cx.theme().colors().text_disabled
21169 } else {
21170 cx.theme().colors().text
21171 },
21172 font_family: settings.buffer_font.family.clone(),
21173 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21174 font_size: settings.buffer_font_size(cx).into(),
21175 font_weight: settings.buffer_font.weight,
21176 line_height: relative(settings.buffer_line_height.value()),
21177 ..Default::default()
21178 };
21179 EditorElement::new(
21180 &self.prompt,
21181 EditorStyle {
21182 background: cx.theme().colors().editor_background,
21183 local_player: cx.theme().players().local(),
21184 text: text_style,
21185 ..Default::default()
21186 },
21187 )
21188 }
21189}
21190
21191impl Render for BreakpointPromptEditor {
21192 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21193 let editor_margins = *self.editor_margins.lock();
21194 let gutter_dimensions = editor_margins.gutter;
21195 h_flex()
21196 .key_context("Editor")
21197 .bg(cx.theme().colors().editor_background)
21198 .border_y_1()
21199 .border_color(cx.theme().status().info_border)
21200 .size_full()
21201 .py(window.line_height() / 2.5)
21202 .on_action(cx.listener(Self::confirm))
21203 .on_action(cx.listener(Self::cancel))
21204 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21205 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21206 }
21207}
21208
21209impl Focusable for BreakpointPromptEditor {
21210 fn focus_handle(&self, cx: &App) -> FocusHandle {
21211 self.prompt.focus_handle(cx)
21212 }
21213}
21214
21215fn all_edits_insertions_or_deletions(
21216 edits: &Vec<(Range<Anchor>, String)>,
21217 snapshot: &MultiBufferSnapshot,
21218) -> bool {
21219 let mut all_insertions = true;
21220 let mut all_deletions = true;
21221
21222 for (range, new_text) in edits.iter() {
21223 let range_is_empty = range.to_offset(&snapshot).is_empty();
21224 let text_is_empty = new_text.is_empty();
21225
21226 if range_is_empty != text_is_empty {
21227 if range_is_empty {
21228 all_deletions = false;
21229 } else {
21230 all_insertions = false;
21231 }
21232 } else {
21233 return false;
21234 }
21235
21236 if !all_insertions && !all_deletions {
21237 return false;
21238 }
21239 }
21240 all_insertions || all_deletions
21241}
21242
21243struct MissingEditPredictionKeybindingTooltip;
21244
21245impl Render for MissingEditPredictionKeybindingTooltip {
21246 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21247 ui::tooltip_container(window, cx, |container, _, cx| {
21248 container
21249 .flex_shrink_0()
21250 .max_w_80()
21251 .min_h(rems_from_px(124.))
21252 .justify_between()
21253 .child(
21254 v_flex()
21255 .flex_1()
21256 .text_ui_sm(cx)
21257 .child(Label::new("Conflict with Accept Keybinding"))
21258 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21259 )
21260 .child(
21261 h_flex()
21262 .pb_1()
21263 .gap_1()
21264 .items_end()
21265 .w_full()
21266 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21267 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21268 }))
21269 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21270 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21271 })),
21272 )
21273 })
21274 }
21275}
21276
21277#[derive(Debug, Clone, Copy, PartialEq)]
21278pub struct LineHighlight {
21279 pub background: Background,
21280 pub border: Option<gpui::Hsla>,
21281 pub include_gutter: bool,
21282 pub type_id: Option<TypeId>,
21283}
21284
21285fn render_diff_hunk_controls(
21286 row: u32,
21287 status: &DiffHunkStatus,
21288 hunk_range: Range<Anchor>,
21289 is_created_file: bool,
21290 line_height: Pixels,
21291 editor: &Entity<Editor>,
21292 _window: &mut Window,
21293 cx: &mut App,
21294) -> AnyElement {
21295 h_flex()
21296 .h(line_height)
21297 .mr_1()
21298 .gap_1()
21299 .px_0p5()
21300 .pb_1()
21301 .border_x_1()
21302 .border_b_1()
21303 .border_color(cx.theme().colors().border_variant)
21304 .rounded_b_lg()
21305 .bg(cx.theme().colors().editor_background)
21306 .gap_1()
21307 .occlude()
21308 .shadow_md()
21309 .child(if status.has_secondary_hunk() {
21310 Button::new(("stage", row as u64), "Stage")
21311 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21312 .tooltip({
21313 let focus_handle = editor.focus_handle(cx);
21314 move |window, cx| {
21315 Tooltip::for_action_in(
21316 "Stage Hunk",
21317 &::git::ToggleStaged,
21318 &focus_handle,
21319 window,
21320 cx,
21321 )
21322 }
21323 })
21324 .on_click({
21325 let editor = editor.clone();
21326 move |_event, _window, cx| {
21327 editor.update(cx, |editor, cx| {
21328 editor.stage_or_unstage_diff_hunks(
21329 true,
21330 vec![hunk_range.start..hunk_range.start],
21331 cx,
21332 );
21333 });
21334 }
21335 })
21336 } else {
21337 Button::new(("unstage", row as u64), "Unstage")
21338 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21339 .tooltip({
21340 let focus_handle = editor.focus_handle(cx);
21341 move |window, cx| {
21342 Tooltip::for_action_in(
21343 "Unstage Hunk",
21344 &::git::ToggleStaged,
21345 &focus_handle,
21346 window,
21347 cx,
21348 )
21349 }
21350 })
21351 .on_click({
21352 let editor = editor.clone();
21353 move |_event, _window, cx| {
21354 editor.update(cx, |editor, cx| {
21355 editor.stage_or_unstage_diff_hunks(
21356 false,
21357 vec![hunk_range.start..hunk_range.start],
21358 cx,
21359 );
21360 });
21361 }
21362 })
21363 })
21364 .child(
21365 Button::new(("restore", row as u64), "Restore")
21366 .tooltip({
21367 let focus_handle = editor.focus_handle(cx);
21368 move |window, cx| {
21369 Tooltip::for_action_in(
21370 "Restore Hunk",
21371 &::git::Restore,
21372 &focus_handle,
21373 window,
21374 cx,
21375 )
21376 }
21377 })
21378 .on_click({
21379 let editor = editor.clone();
21380 move |_event, window, cx| {
21381 editor.update(cx, |editor, cx| {
21382 let snapshot = editor.snapshot(window, cx);
21383 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
21384 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
21385 });
21386 }
21387 })
21388 .disabled(is_created_file),
21389 )
21390 .when(
21391 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
21392 |el| {
21393 el.child(
21394 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
21395 .shape(IconButtonShape::Square)
21396 .icon_size(IconSize::Small)
21397 // .disabled(!has_multiple_hunks)
21398 .tooltip({
21399 let focus_handle = editor.focus_handle(cx);
21400 move |window, cx| {
21401 Tooltip::for_action_in(
21402 "Next Hunk",
21403 &GoToHunk,
21404 &focus_handle,
21405 window,
21406 cx,
21407 )
21408 }
21409 })
21410 .on_click({
21411 let editor = editor.clone();
21412 move |_event, window, cx| {
21413 editor.update(cx, |editor, cx| {
21414 let snapshot = editor.snapshot(window, cx);
21415 let position =
21416 hunk_range.end.to_point(&snapshot.buffer_snapshot);
21417 editor.go_to_hunk_before_or_after_position(
21418 &snapshot,
21419 position,
21420 Direction::Next,
21421 window,
21422 cx,
21423 );
21424 editor.expand_selected_diff_hunks(cx);
21425 });
21426 }
21427 }),
21428 )
21429 .child(
21430 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
21431 .shape(IconButtonShape::Square)
21432 .icon_size(IconSize::Small)
21433 // .disabled(!has_multiple_hunks)
21434 .tooltip({
21435 let focus_handle = editor.focus_handle(cx);
21436 move |window, cx| {
21437 Tooltip::for_action_in(
21438 "Previous Hunk",
21439 &GoToPreviousHunk,
21440 &focus_handle,
21441 window,
21442 cx,
21443 )
21444 }
21445 })
21446 .on_click({
21447 let editor = editor.clone();
21448 move |_event, window, cx| {
21449 editor.update(cx, |editor, cx| {
21450 let snapshot = editor.snapshot(window, cx);
21451 let point =
21452 hunk_range.start.to_point(&snapshot.buffer_snapshot);
21453 editor.go_to_hunk_before_or_after_position(
21454 &snapshot,
21455 point,
21456 Direction::Prev,
21457 window,
21458 cx,
21459 );
21460 editor.expand_selected_diff_hunks(cx);
21461 });
21462 }
21463 }),
21464 )
21465 },
21466 )
21467 .into_any_element()
21468}