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(),
5280 cx,
5281 )
5282 {
5283 scenarios.push(scenario);
5284 }
5285 }
5286 });
5287 Some(scenarios)
5288 })
5289 .unwrap_or_default()
5290 } else {
5291 vec![]
5292 }
5293 })?;
5294 let spawn_straight_away = quick_launch
5295 && resolved_tasks
5296 .as_ref()
5297 .map_or(false, |tasks| tasks.templates.len() == 1)
5298 && code_actions
5299 .as_ref()
5300 .map_or(true, |actions| actions.is_empty())
5301 && debug_scenarios.is_empty();
5302 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5303 *editor.context_menu.borrow_mut() =
5304 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5305 buffer,
5306 actions: CodeActionContents::new(
5307 resolved_tasks,
5308 code_actions,
5309 debug_scenarios,
5310 task_context.unwrap_or_default(),
5311 ),
5312 selected_item: Default::default(),
5313 scroll_handle: UniformListScrollHandle::default(),
5314 deployed_from_indicator,
5315 }));
5316 if spawn_straight_away {
5317 if let Some(task) = editor.confirm_code_action(
5318 &ConfirmCodeAction { item_ix: Some(0) },
5319 window,
5320 cx,
5321 ) {
5322 cx.notify();
5323 return task;
5324 }
5325 }
5326 cx.notify();
5327 Task::ready(Ok(()))
5328 }) {
5329 task.await
5330 } else {
5331 Ok(())
5332 }
5333 }))
5334 } else {
5335 Some(Task::ready(Ok(())))
5336 }
5337 })?;
5338 if let Some(task) = spawned_test_task {
5339 task.await?;
5340 }
5341
5342 Ok::<_, anyhow::Error>(())
5343 })
5344 .detach_and_log_err(cx);
5345 }
5346
5347 pub fn confirm_code_action(
5348 &mut self,
5349 action: &ConfirmCodeAction,
5350 window: &mut Window,
5351 cx: &mut Context<Self>,
5352 ) -> Option<Task<Result<()>>> {
5353 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5354
5355 let actions_menu =
5356 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5357 menu
5358 } else {
5359 return None;
5360 };
5361
5362 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5363 let action = actions_menu.actions.get(action_ix)?;
5364 let title = action.label();
5365 let buffer = actions_menu.buffer;
5366 let workspace = self.workspace()?;
5367
5368 match action {
5369 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5370 workspace.update(cx, |workspace, cx| {
5371 workspace.schedule_resolved_task(
5372 task_source_kind,
5373 resolved_task,
5374 false,
5375 window,
5376 cx,
5377 );
5378
5379 Some(Task::ready(Ok(())))
5380 })
5381 }
5382 CodeActionsItem::CodeAction {
5383 excerpt_id,
5384 action,
5385 provider,
5386 } => {
5387 let apply_code_action =
5388 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5389 let workspace = workspace.downgrade();
5390 Some(cx.spawn_in(window, async move |editor, cx| {
5391 let project_transaction = apply_code_action.await?;
5392 Self::open_project_transaction(
5393 &editor,
5394 workspace,
5395 project_transaction,
5396 title,
5397 cx,
5398 )
5399 .await
5400 }))
5401 }
5402 CodeActionsItem::DebugScenario(scenario) => {
5403 let context = actions_menu.actions.context.clone();
5404
5405 workspace.update(cx, |workspace, cx| {
5406 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5407 });
5408 Some(Task::ready(Ok(())))
5409 }
5410 }
5411 }
5412
5413 pub async fn open_project_transaction(
5414 this: &WeakEntity<Editor>,
5415 workspace: WeakEntity<Workspace>,
5416 transaction: ProjectTransaction,
5417 title: String,
5418 cx: &mut AsyncWindowContext,
5419 ) -> Result<()> {
5420 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5421 cx.update(|_, cx| {
5422 entries.sort_unstable_by_key(|(buffer, _)| {
5423 buffer.read(cx).file().map(|f| f.path().clone())
5424 });
5425 })?;
5426
5427 // If the project transaction's edits are all contained within this editor, then
5428 // avoid opening a new editor to display them.
5429
5430 if let Some((buffer, transaction)) = entries.first() {
5431 if entries.len() == 1 {
5432 let excerpt = this.update(cx, |editor, cx| {
5433 editor
5434 .buffer()
5435 .read(cx)
5436 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5437 })?;
5438 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5439 if excerpted_buffer == *buffer {
5440 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5441 let excerpt_range = excerpt_range.to_offset(buffer);
5442 buffer
5443 .edited_ranges_for_transaction::<usize>(transaction)
5444 .all(|range| {
5445 excerpt_range.start <= range.start
5446 && excerpt_range.end >= range.end
5447 })
5448 })?;
5449
5450 if all_edits_within_excerpt {
5451 return Ok(());
5452 }
5453 }
5454 }
5455 }
5456 } else {
5457 return Ok(());
5458 }
5459
5460 let mut ranges_to_highlight = Vec::new();
5461 let excerpt_buffer = cx.new(|cx| {
5462 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5463 for (buffer_handle, transaction) in &entries {
5464 let edited_ranges = buffer_handle
5465 .read(cx)
5466 .edited_ranges_for_transaction::<Point>(transaction)
5467 .collect::<Vec<_>>();
5468 let (ranges, _) = multibuffer.set_excerpts_for_path(
5469 PathKey::for_buffer(buffer_handle, cx),
5470 buffer_handle.clone(),
5471 edited_ranges,
5472 DEFAULT_MULTIBUFFER_CONTEXT,
5473 cx,
5474 );
5475
5476 ranges_to_highlight.extend(ranges);
5477 }
5478 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5479 multibuffer
5480 })?;
5481
5482 workspace.update_in(cx, |workspace, window, cx| {
5483 let project = workspace.project().clone();
5484 let editor =
5485 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5486 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5487 editor.update(cx, |editor, cx| {
5488 editor.highlight_background::<Self>(
5489 &ranges_to_highlight,
5490 |theme| theme.editor_highlighted_line_background,
5491 cx,
5492 );
5493 });
5494 })?;
5495
5496 Ok(())
5497 }
5498
5499 pub fn clear_code_action_providers(&mut self) {
5500 self.code_action_providers.clear();
5501 self.available_code_actions.take();
5502 }
5503
5504 pub fn add_code_action_provider(
5505 &mut self,
5506 provider: Rc<dyn CodeActionProvider>,
5507 window: &mut Window,
5508 cx: &mut Context<Self>,
5509 ) {
5510 if self
5511 .code_action_providers
5512 .iter()
5513 .any(|existing_provider| existing_provider.id() == provider.id())
5514 {
5515 return;
5516 }
5517
5518 self.code_action_providers.push(provider);
5519 self.refresh_code_actions(window, cx);
5520 }
5521
5522 pub fn remove_code_action_provider(
5523 &mut self,
5524 id: Arc<str>,
5525 window: &mut Window,
5526 cx: &mut Context<Self>,
5527 ) {
5528 self.code_action_providers
5529 .retain(|provider| provider.id() != id);
5530 self.refresh_code_actions(window, cx);
5531 }
5532
5533 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5534 let newest_selection = self.selections.newest_anchor().clone();
5535 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5536 let buffer = self.buffer.read(cx);
5537 if newest_selection.head().diff_base_anchor.is_some() {
5538 return None;
5539 }
5540 let (start_buffer, start) =
5541 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5542 let (end_buffer, end) =
5543 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5544 if start_buffer != end_buffer {
5545 return None;
5546 }
5547
5548 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5549 cx.background_executor()
5550 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5551 .await;
5552
5553 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5554 let providers = this.code_action_providers.clone();
5555 let tasks = this
5556 .code_action_providers
5557 .iter()
5558 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5559 .collect::<Vec<_>>();
5560 (providers, tasks)
5561 })?;
5562
5563 let mut actions = Vec::new();
5564 for (provider, provider_actions) in
5565 providers.into_iter().zip(future::join_all(tasks).await)
5566 {
5567 if let Some(provider_actions) = provider_actions.log_err() {
5568 actions.extend(provider_actions.into_iter().map(|action| {
5569 AvailableCodeAction {
5570 excerpt_id: newest_selection.start.excerpt_id,
5571 action,
5572 provider: provider.clone(),
5573 }
5574 }));
5575 }
5576 }
5577
5578 this.update(cx, |this, cx| {
5579 this.available_code_actions = if actions.is_empty() {
5580 None
5581 } else {
5582 Some((
5583 Location {
5584 buffer: start_buffer,
5585 range: start..end,
5586 },
5587 actions.into(),
5588 ))
5589 };
5590 cx.notify();
5591 })
5592 }));
5593 None
5594 }
5595
5596 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5597 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5598 self.show_git_blame_inline = false;
5599
5600 self.show_git_blame_inline_delay_task =
5601 Some(cx.spawn_in(window, async move |this, cx| {
5602 cx.background_executor().timer(delay).await;
5603
5604 this.update(cx, |this, cx| {
5605 this.show_git_blame_inline = true;
5606 cx.notify();
5607 })
5608 .log_err();
5609 }));
5610 }
5611 }
5612
5613 fn show_blame_popover(
5614 &mut self,
5615 blame_entry: &BlameEntry,
5616 position: gpui::Point<Pixels>,
5617 cx: &mut Context<Self>,
5618 ) {
5619 if let Some(state) = &mut self.inline_blame_popover {
5620 state.hide_task.take();
5621 cx.notify();
5622 } else {
5623 let delay = EditorSettings::get_global(cx).hover_popover_delay;
5624 let show_task = cx.spawn(async move |editor, cx| {
5625 cx.background_executor()
5626 .timer(std::time::Duration::from_millis(delay))
5627 .await;
5628 editor
5629 .update(cx, |editor, cx| {
5630 if let Some(state) = &mut editor.inline_blame_popover {
5631 state.show_task = None;
5632 cx.notify();
5633 }
5634 })
5635 .ok();
5636 });
5637 let Some(blame) = self.blame.as_ref() else {
5638 return;
5639 };
5640 let blame = blame.read(cx);
5641 let details = blame.details_for_entry(&blame_entry);
5642 let markdown = cx.new(|cx| {
5643 Markdown::new(
5644 details
5645 .as_ref()
5646 .map(|message| message.message.clone())
5647 .unwrap_or_default(),
5648 None,
5649 None,
5650 cx,
5651 )
5652 });
5653 self.inline_blame_popover = Some(InlineBlamePopover {
5654 position,
5655 show_task: Some(show_task),
5656 hide_task: None,
5657 popover_bounds: None,
5658 popover_state: InlineBlamePopoverState {
5659 scroll_handle: ScrollHandle::new(),
5660 commit_message: details,
5661 markdown,
5662 },
5663 });
5664 }
5665 }
5666
5667 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
5668 if let Some(state) = &mut self.inline_blame_popover {
5669 if state.show_task.is_some() {
5670 self.inline_blame_popover.take();
5671 cx.notify();
5672 } else {
5673 let hide_task = cx.spawn(async move |editor, cx| {
5674 cx.background_executor()
5675 .timer(std::time::Duration::from_millis(100))
5676 .await;
5677 editor
5678 .update(cx, |editor, cx| {
5679 editor.inline_blame_popover.take();
5680 cx.notify();
5681 })
5682 .ok();
5683 });
5684 state.hide_task = Some(hide_task);
5685 }
5686 }
5687 }
5688
5689 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5690 if self.pending_rename.is_some() {
5691 return None;
5692 }
5693
5694 let provider = self.semantics_provider.clone()?;
5695 let buffer = self.buffer.read(cx);
5696 let newest_selection = self.selections.newest_anchor().clone();
5697 let cursor_position = newest_selection.head();
5698 let (cursor_buffer, cursor_buffer_position) =
5699 buffer.text_anchor_for_position(cursor_position, cx)?;
5700 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5701 if cursor_buffer != tail_buffer {
5702 return None;
5703 }
5704 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5705 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5706 cx.background_executor()
5707 .timer(Duration::from_millis(debounce))
5708 .await;
5709
5710 let highlights = if let Some(highlights) = cx
5711 .update(|cx| {
5712 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5713 })
5714 .ok()
5715 .flatten()
5716 {
5717 highlights.await.log_err()
5718 } else {
5719 None
5720 };
5721
5722 if let Some(highlights) = highlights {
5723 this.update(cx, |this, cx| {
5724 if this.pending_rename.is_some() {
5725 return;
5726 }
5727
5728 let buffer_id = cursor_position.buffer_id;
5729 let buffer = this.buffer.read(cx);
5730 if !buffer
5731 .text_anchor_for_position(cursor_position, cx)
5732 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5733 {
5734 return;
5735 }
5736
5737 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5738 let mut write_ranges = Vec::new();
5739 let mut read_ranges = Vec::new();
5740 for highlight in highlights {
5741 for (excerpt_id, excerpt_range) in
5742 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5743 {
5744 let start = highlight
5745 .range
5746 .start
5747 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5748 let end = highlight
5749 .range
5750 .end
5751 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5752 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5753 continue;
5754 }
5755
5756 let range = Anchor {
5757 buffer_id,
5758 excerpt_id,
5759 text_anchor: start,
5760 diff_base_anchor: None,
5761 }..Anchor {
5762 buffer_id,
5763 excerpt_id,
5764 text_anchor: end,
5765 diff_base_anchor: None,
5766 };
5767 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5768 write_ranges.push(range);
5769 } else {
5770 read_ranges.push(range);
5771 }
5772 }
5773 }
5774
5775 this.highlight_background::<DocumentHighlightRead>(
5776 &read_ranges,
5777 |theme| theme.editor_document_highlight_read_background,
5778 cx,
5779 );
5780 this.highlight_background::<DocumentHighlightWrite>(
5781 &write_ranges,
5782 |theme| theme.editor_document_highlight_write_background,
5783 cx,
5784 );
5785 cx.notify();
5786 })
5787 .log_err();
5788 }
5789 }));
5790 None
5791 }
5792
5793 fn prepare_highlight_query_from_selection(
5794 &mut self,
5795 cx: &mut Context<Editor>,
5796 ) -> Option<(String, Range<Anchor>)> {
5797 if matches!(self.mode, EditorMode::SingleLine { .. }) {
5798 return None;
5799 }
5800 if !EditorSettings::get_global(cx).selection_highlight {
5801 return None;
5802 }
5803 if self.selections.count() != 1 || self.selections.line_mode {
5804 return None;
5805 }
5806 let selection = self.selections.newest::<Point>(cx);
5807 if selection.is_empty() || selection.start.row != selection.end.row {
5808 return None;
5809 }
5810 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5811 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
5812 let query = multi_buffer_snapshot
5813 .text_for_range(selection_anchor_range.clone())
5814 .collect::<String>();
5815 if query.trim().is_empty() {
5816 return None;
5817 }
5818 Some((query, selection_anchor_range))
5819 }
5820
5821 fn update_selection_occurrence_highlights(
5822 &mut self,
5823 query_text: String,
5824 query_range: Range<Anchor>,
5825 multi_buffer_range_to_query: Range<Point>,
5826 use_debounce: bool,
5827 window: &mut Window,
5828 cx: &mut Context<Editor>,
5829 ) -> Task<()> {
5830 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5831 cx.spawn_in(window, async move |editor, cx| {
5832 if use_debounce {
5833 cx.background_executor()
5834 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
5835 .await;
5836 }
5837 let match_task = cx.background_spawn(async move {
5838 let buffer_ranges = multi_buffer_snapshot
5839 .range_to_buffer_ranges(multi_buffer_range_to_query)
5840 .into_iter()
5841 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
5842 let mut match_ranges = Vec::new();
5843 let Ok(regex) = project::search::SearchQuery::text(
5844 query_text.clone(),
5845 false,
5846 false,
5847 false,
5848 Default::default(),
5849 Default::default(),
5850 false,
5851 None,
5852 ) else {
5853 return Vec::default();
5854 };
5855 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
5856 match_ranges.extend(
5857 regex
5858 .search(&buffer_snapshot, Some(search_range.clone()))
5859 .await
5860 .into_iter()
5861 .filter_map(|match_range| {
5862 let match_start = buffer_snapshot
5863 .anchor_after(search_range.start + match_range.start);
5864 let match_end = buffer_snapshot
5865 .anchor_before(search_range.start + match_range.end);
5866 let match_anchor_range = Anchor::range_in_buffer(
5867 excerpt_id,
5868 buffer_snapshot.remote_id(),
5869 match_start..match_end,
5870 );
5871 (match_anchor_range != query_range).then_some(match_anchor_range)
5872 }),
5873 );
5874 }
5875 match_ranges
5876 });
5877 let match_ranges = match_task.await;
5878 editor
5879 .update_in(cx, |editor, _, cx| {
5880 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5881 if !match_ranges.is_empty() {
5882 editor.highlight_background::<SelectedTextHighlight>(
5883 &match_ranges,
5884 |theme| theme.editor_document_highlight_bracket_background,
5885 cx,
5886 )
5887 }
5888 })
5889 .log_err();
5890 })
5891 }
5892
5893 fn refresh_selected_text_highlights(
5894 &mut self,
5895 on_buffer_edit: bool,
5896 window: &mut Window,
5897 cx: &mut Context<Editor>,
5898 ) {
5899 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
5900 else {
5901 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5902 self.quick_selection_highlight_task.take();
5903 self.debounced_selection_highlight_task.take();
5904 return;
5905 };
5906 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5907 if on_buffer_edit
5908 || self
5909 .quick_selection_highlight_task
5910 .as_ref()
5911 .map_or(true, |(prev_anchor_range, _)| {
5912 prev_anchor_range != &query_range
5913 })
5914 {
5915 let multi_buffer_visible_start = self
5916 .scroll_manager
5917 .anchor()
5918 .anchor
5919 .to_point(&multi_buffer_snapshot);
5920 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5921 multi_buffer_visible_start
5922 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5923 Bias::Left,
5924 );
5925 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5926 self.quick_selection_highlight_task = Some((
5927 query_range.clone(),
5928 self.update_selection_occurrence_highlights(
5929 query_text.clone(),
5930 query_range.clone(),
5931 multi_buffer_visible_range,
5932 false,
5933 window,
5934 cx,
5935 ),
5936 ));
5937 }
5938 if on_buffer_edit
5939 || self
5940 .debounced_selection_highlight_task
5941 .as_ref()
5942 .map_or(true, |(prev_anchor_range, _)| {
5943 prev_anchor_range != &query_range
5944 })
5945 {
5946 let multi_buffer_start = multi_buffer_snapshot
5947 .anchor_before(0)
5948 .to_point(&multi_buffer_snapshot);
5949 let multi_buffer_end = multi_buffer_snapshot
5950 .anchor_after(multi_buffer_snapshot.len())
5951 .to_point(&multi_buffer_snapshot);
5952 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
5953 self.debounced_selection_highlight_task = Some((
5954 query_range.clone(),
5955 self.update_selection_occurrence_highlights(
5956 query_text,
5957 query_range,
5958 multi_buffer_full_range,
5959 true,
5960 window,
5961 cx,
5962 ),
5963 ));
5964 }
5965 }
5966
5967 pub fn refresh_inline_completion(
5968 &mut self,
5969 debounce: bool,
5970 user_requested: bool,
5971 window: &mut Window,
5972 cx: &mut Context<Self>,
5973 ) -> Option<()> {
5974 let provider = self.edit_prediction_provider()?;
5975 let cursor = self.selections.newest_anchor().head();
5976 let (buffer, cursor_buffer_position) =
5977 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5978
5979 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
5980 self.discard_inline_completion(false, cx);
5981 return None;
5982 }
5983
5984 if !user_requested
5985 && (!self.should_show_edit_predictions()
5986 || !self.is_focused(window)
5987 || buffer.read(cx).is_empty())
5988 {
5989 self.discard_inline_completion(false, cx);
5990 return None;
5991 }
5992
5993 self.update_visible_inline_completion(window, cx);
5994 provider.refresh(
5995 self.project.clone(),
5996 buffer,
5997 cursor_buffer_position,
5998 debounce,
5999 cx,
6000 );
6001 Some(())
6002 }
6003
6004 fn show_edit_predictions_in_menu(&self) -> bool {
6005 match self.edit_prediction_settings {
6006 EditPredictionSettings::Disabled => false,
6007 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6008 }
6009 }
6010
6011 pub fn edit_predictions_enabled(&self) -> bool {
6012 match self.edit_prediction_settings {
6013 EditPredictionSettings::Disabled => false,
6014 EditPredictionSettings::Enabled { .. } => true,
6015 }
6016 }
6017
6018 fn edit_prediction_requires_modifier(&self) -> bool {
6019 match self.edit_prediction_settings {
6020 EditPredictionSettings::Disabled => false,
6021 EditPredictionSettings::Enabled {
6022 preview_requires_modifier,
6023 ..
6024 } => preview_requires_modifier,
6025 }
6026 }
6027
6028 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6029 if self.edit_prediction_provider.is_none() {
6030 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6031 } else {
6032 let selection = self.selections.newest_anchor();
6033 let cursor = selection.head();
6034
6035 if let Some((buffer, cursor_buffer_position)) =
6036 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6037 {
6038 self.edit_prediction_settings =
6039 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6040 }
6041 }
6042 }
6043
6044 fn edit_prediction_settings_at_position(
6045 &self,
6046 buffer: &Entity<Buffer>,
6047 buffer_position: language::Anchor,
6048 cx: &App,
6049 ) -> EditPredictionSettings {
6050 if !self.mode.is_full()
6051 || !self.show_inline_completions_override.unwrap_or(true)
6052 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6053 {
6054 return EditPredictionSettings::Disabled;
6055 }
6056
6057 let buffer = buffer.read(cx);
6058
6059 let file = buffer.file();
6060
6061 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6062 return EditPredictionSettings::Disabled;
6063 };
6064
6065 let by_provider = matches!(
6066 self.menu_inline_completions_policy,
6067 MenuInlineCompletionsPolicy::ByProvider
6068 );
6069
6070 let show_in_menu = by_provider
6071 && self
6072 .edit_prediction_provider
6073 .as_ref()
6074 .map_or(false, |provider| {
6075 provider.provider.show_completions_in_menu()
6076 });
6077
6078 let preview_requires_modifier =
6079 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6080
6081 EditPredictionSettings::Enabled {
6082 show_in_menu,
6083 preview_requires_modifier,
6084 }
6085 }
6086
6087 fn should_show_edit_predictions(&self) -> bool {
6088 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6089 }
6090
6091 pub fn edit_prediction_preview_is_active(&self) -> bool {
6092 matches!(
6093 self.edit_prediction_preview,
6094 EditPredictionPreview::Active { .. }
6095 )
6096 }
6097
6098 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6099 let cursor = self.selections.newest_anchor().head();
6100 if let Some((buffer, cursor_position)) =
6101 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6102 {
6103 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6104 } else {
6105 false
6106 }
6107 }
6108
6109 fn edit_predictions_enabled_in_buffer(
6110 &self,
6111 buffer: &Entity<Buffer>,
6112 buffer_position: language::Anchor,
6113 cx: &App,
6114 ) -> bool {
6115 maybe!({
6116 if self.read_only(cx) {
6117 return Some(false);
6118 }
6119 let provider = self.edit_prediction_provider()?;
6120 if !provider.is_enabled(&buffer, buffer_position, cx) {
6121 return Some(false);
6122 }
6123 let buffer = buffer.read(cx);
6124 let Some(file) = buffer.file() else {
6125 return Some(true);
6126 };
6127 let settings = all_language_settings(Some(file), cx);
6128 Some(settings.edit_predictions_enabled_for_file(file, cx))
6129 })
6130 .unwrap_or(false)
6131 }
6132
6133 fn cycle_inline_completion(
6134 &mut self,
6135 direction: Direction,
6136 window: &mut Window,
6137 cx: &mut Context<Self>,
6138 ) -> Option<()> {
6139 let provider = self.edit_prediction_provider()?;
6140 let cursor = self.selections.newest_anchor().head();
6141 let (buffer, cursor_buffer_position) =
6142 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6143 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6144 return None;
6145 }
6146
6147 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6148 self.update_visible_inline_completion(window, cx);
6149
6150 Some(())
6151 }
6152
6153 pub fn show_inline_completion(
6154 &mut self,
6155 _: &ShowEditPrediction,
6156 window: &mut Window,
6157 cx: &mut Context<Self>,
6158 ) {
6159 if !self.has_active_inline_completion() {
6160 self.refresh_inline_completion(false, true, window, cx);
6161 return;
6162 }
6163
6164 self.update_visible_inline_completion(window, cx);
6165 }
6166
6167 pub fn display_cursor_names(
6168 &mut self,
6169 _: &DisplayCursorNames,
6170 window: &mut Window,
6171 cx: &mut Context<Self>,
6172 ) {
6173 self.show_cursor_names(window, cx);
6174 }
6175
6176 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6177 self.show_cursor_names = true;
6178 cx.notify();
6179 cx.spawn_in(window, async move |this, cx| {
6180 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6181 this.update(cx, |this, cx| {
6182 this.show_cursor_names = false;
6183 cx.notify()
6184 })
6185 .ok()
6186 })
6187 .detach();
6188 }
6189
6190 pub fn next_edit_prediction(
6191 &mut self,
6192 _: &NextEditPrediction,
6193 window: &mut Window,
6194 cx: &mut Context<Self>,
6195 ) {
6196 if self.has_active_inline_completion() {
6197 self.cycle_inline_completion(Direction::Next, window, cx);
6198 } else {
6199 let is_copilot_disabled = self
6200 .refresh_inline_completion(false, true, window, cx)
6201 .is_none();
6202 if is_copilot_disabled {
6203 cx.propagate();
6204 }
6205 }
6206 }
6207
6208 pub fn previous_edit_prediction(
6209 &mut self,
6210 _: &PreviousEditPrediction,
6211 window: &mut Window,
6212 cx: &mut Context<Self>,
6213 ) {
6214 if self.has_active_inline_completion() {
6215 self.cycle_inline_completion(Direction::Prev, window, cx);
6216 } else {
6217 let is_copilot_disabled = self
6218 .refresh_inline_completion(false, true, window, cx)
6219 .is_none();
6220 if is_copilot_disabled {
6221 cx.propagate();
6222 }
6223 }
6224 }
6225
6226 pub fn accept_edit_prediction(
6227 &mut self,
6228 _: &AcceptEditPrediction,
6229 window: &mut Window,
6230 cx: &mut Context<Self>,
6231 ) {
6232 if self.show_edit_predictions_in_menu() {
6233 self.hide_context_menu(window, cx);
6234 }
6235
6236 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6237 return;
6238 };
6239
6240 self.report_inline_completion_event(
6241 active_inline_completion.completion_id.clone(),
6242 true,
6243 cx,
6244 );
6245
6246 match &active_inline_completion.completion {
6247 InlineCompletion::Move { target, .. } => {
6248 let target = *target;
6249
6250 if let Some(position_map) = &self.last_position_map {
6251 if position_map
6252 .visible_row_range
6253 .contains(&target.to_display_point(&position_map.snapshot).row())
6254 || !self.edit_prediction_requires_modifier()
6255 {
6256 self.unfold_ranges(&[target..target], true, false, cx);
6257 // Note that this is also done in vim's handler of the Tab action.
6258 self.change_selections(
6259 Some(Autoscroll::newest()),
6260 window,
6261 cx,
6262 |selections| {
6263 selections.select_anchor_ranges([target..target]);
6264 },
6265 );
6266 self.clear_row_highlights::<EditPredictionPreview>();
6267
6268 self.edit_prediction_preview
6269 .set_previous_scroll_position(None);
6270 } else {
6271 self.edit_prediction_preview
6272 .set_previous_scroll_position(Some(
6273 position_map.snapshot.scroll_anchor,
6274 ));
6275
6276 self.highlight_rows::<EditPredictionPreview>(
6277 target..target,
6278 cx.theme().colors().editor_highlighted_line_background,
6279 RowHighlightOptions {
6280 autoscroll: true,
6281 ..Default::default()
6282 },
6283 cx,
6284 );
6285 self.request_autoscroll(Autoscroll::fit(), cx);
6286 }
6287 }
6288 }
6289 InlineCompletion::Edit { edits, .. } => {
6290 if let Some(provider) = self.edit_prediction_provider() {
6291 provider.accept(cx);
6292 }
6293
6294 let snapshot = self.buffer.read(cx).snapshot(cx);
6295 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6296
6297 self.buffer.update(cx, |buffer, cx| {
6298 buffer.edit(edits.iter().cloned(), None, cx)
6299 });
6300
6301 self.change_selections(None, window, cx, |s| {
6302 s.select_anchor_ranges([last_edit_end..last_edit_end])
6303 });
6304
6305 self.update_visible_inline_completion(window, cx);
6306 if self.active_inline_completion.is_none() {
6307 self.refresh_inline_completion(true, true, window, cx);
6308 }
6309
6310 cx.notify();
6311 }
6312 }
6313
6314 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6315 }
6316
6317 pub fn accept_partial_inline_completion(
6318 &mut self,
6319 _: &AcceptPartialEditPrediction,
6320 window: &mut Window,
6321 cx: &mut Context<Self>,
6322 ) {
6323 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6324 return;
6325 };
6326 if self.selections.count() != 1 {
6327 return;
6328 }
6329
6330 self.report_inline_completion_event(
6331 active_inline_completion.completion_id.clone(),
6332 true,
6333 cx,
6334 );
6335
6336 match &active_inline_completion.completion {
6337 InlineCompletion::Move { target, .. } => {
6338 let target = *target;
6339 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6340 selections.select_anchor_ranges([target..target]);
6341 });
6342 }
6343 InlineCompletion::Edit { edits, .. } => {
6344 // Find an insertion that starts at the cursor position.
6345 let snapshot = self.buffer.read(cx).snapshot(cx);
6346 let cursor_offset = self.selections.newest::<usize>(cx).head();
6347 let insertion = edits.iter().find_map(|(range, text)| {
6348 let range = range.to_offset(&snapshot);
6349 if range.is_empty() && range.start == cursor_offset {
6350 Some(text)
6351 } else {
6352 None
6353 }
6354 });
6355
6356 if let Some(text) = insertion {
6357 let mut partial_completion = text
6358 .chars()
6359 .by_ref()
6360 .take_while(|c| c.is_alphabetic())
6361 .collect::<String>();
6362 if partial_completion.is_empty() {
6363 partial_completion = text
6364 .chars()
6365 .by_ref()
6366 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6367 .collect::<String>();
6368 }
6369
6370 cx.emit(EditorEvent::InputHandled {
6371 utf16_range_to_replace: None,
6372 text: partial_completion.clone().into(),
6373 });
6374
6375 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6376
6377 self.refresh_inline_completion(true, true, window, cx);
6378 cx.notify();
6379 } else {
6380 self.accept_edit_prediction(&Default::default(), window, cx);
6381 }
6382 }
6383 }
6384 }
6385
6386 fn discard_inline_completion(
6387 &mut self,
6388 should_report_inline_completion_event: bool,
6389 cx: &mut Context<Self>,
6390 ) -> bool {
6391 if should_report_inline_completion_event {
6392 let completion_id = self
6393 .active_inline_completion
6394 .as_ref()
6395 .and_then(|active_completion| active_completion.completion_id.clone());
6396
6397 self.report_inline_completion_event(completion_id, false, cx);
6398 }
6399
6400 if let Some(provider) = self.edit_prediction_provider() {
6401 provider.discard(cx);
6402 }
6403
6404 self.take_active_inline_completion(cx)
6405 }
6406
6407 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6408 let Some(provider) = self.edit_prediction_provider() else {
6409 return;
6410 };
6411
6412 let Some((_, buffer, _)) = self
6413 .buffer
6414 .read(cx)
6415 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6416 else {
6417 return;
6418 };
6419
6420 let extension = buffer
6421 .read(cx)
6422 .file()
6423 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6424
6425 let event_type = match accepted {
6426 true => "Edit Prediction Accepted",
6427 false => "Edit Prediction Discarded",
6428 };
6429 telemetry::event!(
6430 event_type,
6431 provider = provider.name(),
6432 prediction_id = id,
6433 suggestion_accepted = accepted,
6434 file_extension = extension,
6435 );
6436 }
6437
6438 pub fn has_active_inline_completion(&self) -> bool {
6439 self.active_inline_completion.is_some()
6440 }
6441
6442 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6443 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6444 return false;
6445 };
6446
6447 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6448 self.clear_highlights::<InlineCompletionHighlight>(cx);
6449 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6450 true
6451 }
6452
6453 /// Returns true when we're displaying the edit prediction popover below the cursor
6454 /// like we are not previewing and the LSP autocomplete menu is visible
6455 /// or we are in `when_holding_modifier` mode.
6456 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6457 if self.edit_prediction_preview_is_active()
6458 || !self.show_edit_predictions_in_menu()
6459 || !self.edit_predictions_enabled()
6460 {
6461 return false;
6462 }
6463
6464 if self.has_visible_completions_menu() {
6465 return true;
6466 }
6467
6468 has_completion && self.edit_prediction_requires_modifier()
6469 }
6470
6471 fn handle_modifiers_changed(
6472 &mut self,
6473 modifiers: Modifiers,
6474 position_map: &PositionMap,
6475 window: &mut Window,
6476 cx: &mut Context<Self>,
6477 ) {
6478 if self.show_edit_predictions_in_menu() {
6479 self.update_edit_prediction_preview(&modifiers, window, cx);
6480 }
6481
6482 self.update_selection_mode(&modifiers, position_map, window, cx);
6483
6484 let mouse_position = window.mouse_position();
6485 if !position_map.text_hitbox.is_hovered(window) {
6486 return;
6487 }
6488
6489 self.update_hovered_link(
6490 position_map.point_for_position(mouse_position),
6491 &position_map.snapshot,
6492 modifiers,
6493 window,
6494 cx,
6495 )
6496 }
6497
6498 fn update_selection_mode(
6499 &mut self,
6500 modifiers: &Modifiers,
6501 position_map: &PositionMap,
6502 window: &mut Window,
6503 cx: &mut Context<Self>,
6504 ) {
6505 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6506 return;
6507 }
6508
6509 let mouse_position = window.mouse_position();
6510 let point_for_position = position_map.point_for_position(mouse_position);
6511 let position = point_for_position.previous_valid;
6512
6513 self.select(
6514 SelectPhase::BeginColumnar {
6515 position,
6516 reset: false,
6517 goal_column: point_for_position.exact_unclipped.column(),
6518 },
6519 window,
6520 cx,
6521 );
6522 }
6523
6524 fn update_edit_prediction_preview(
6525 &mut self,
6526 modifiers: &Modifiers,
6527 window: &mut Window,
6528 cx: &mut Context<Self>,
6529 ) {
6530 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6531 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6532 return;
6533 };
6534
6535 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6536 if matches!(
6537 self.edit_prediction_preview,
6538 EditPredictionPreview::Inactive { .. }
6539 ) {
6540 self.edit_prediction_preview = EditPredictionPreview::Active {
6541 previous_scroll_position: None,
6542 since: Instant::now(),
6543 };
6544
6545 self.update_visible_inline_completion(window, cx);
6546 cx.notify();
6547 }
6548 } else if let EditPredictionPreview::Active {
6549 previous_scroll_position,
6550 since,
6551 } = self.edit_prediction_preview
6552 {
6553 if let (Some(previous_scroll_position), Some(position_map)) =
6554 (previous_scroll_position, self.last_position_map.as_ref())
6555 {
6556 self.set_scroll_position(
6557 previous_scroll_position
6558 .scroll_position(&position_map.snapshot.display_snapshot),
6559 window,
6560 cx,
6561 );
6562 }
6563
6564 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6565 released_too_fast: since.elapsed() < Duration::from_millis(200),
6566 };
6567 self.clear_row_highlights::<EditPredictionPreview>();
6568 self.update_visible_inline_completion(window, cx);
6569 cx.notify();
6570 }
6571 }
6572
6573 fn update_visible_inline_completion(
6574 &mut self,
6575 _window: &mut Window,
6576 cx: &mut Context<Self>,
6577 ) -> Option<()> {
6578 let selection = self.selections.newest_anchor();
6579 let cursor = selection.head();
6580 let multibuffer = self.buffer.read(cx).snapshot(cx);
6581 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6582 let excerpt_id = cursor.excerpt_id;
6583
6584 let show_in_menu = self.show_edit_predictions_in_menu();
6585 let completions_menu_has_precedence = !show_in_menu
6586 && (self.context_menu.borrow().is_some()
6587 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6588
6589 if completions_menu_has_precedence
6590 || !offset_selection.is_empty()
6591 || self
6592 .active_inline_completion
6593 .as_ref()
6594 .map_or(false, |completion| {
6595 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6596 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6597 !invalidation_range.contains(&offset_selection.head())
6598 })
6599 {
6600 self.discard_inline_completion(false, cx);
6601 return None;
6602 }
6603
6604 self.take_active_inline_completion(cx);
6605 let Some(provider) = self.edit_prediction_provider() else {
6606 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6607 return None;
6608 };
6609
6610 let (buffer, cursor_buffer_position) =
6611 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6612
6613 self.edit_prediction_settings =
6614 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6615
6616 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6617
6618 if self.edit_prediction_indent_conflict {
6619 let cursor_point = cursor.to_point(&multibuffer);
6620
6621 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6622
6623 if let Some((_, indent)) = indents.iter().next() {
6624 if indent.len == cursor_point.column {
6625 self.edit_prediction_indent_conflict = false;
6626 }
6627 }
6628 }
6629
6630 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6631 let edits = inline_completion
6632 .edits
6633 .into_iter()
6634 .flat_map(|(range, new_text)| {
6635 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6636 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6637 Some((start..end, new_text))
6638 })
6639 .collect::<Vec<_>>();
6640 if edits.is_empty() {
6641 return None;
6642 }
6643
6644 let first_edit_start = edits.first().unwrap().0.start;
6645 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6646 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6647
6648 let last_edit_end = edits.last().unwrap().0.end;
6649 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6650 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6651
6652 let cursor_row = cursor.to_point(&multibuffer).row;
6653
6654 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6655
6656 let mut inlay_ids = Vec::new();
6657 let invalidation_row_range;
6658 let move_invalidation_row_range = if cursor_row < edit_start_row {
6659 Some(cursor_row..edit_end_row)
6660 } else if cursor_row > edit_end_row {
6661 Some(edit_start_row..cursor_row)
6662 } else {
6663 None
6664 };
6665 let is_move =
6666 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6667 let completion = if is_move {
6668 invalidation_row_range =
6669 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6670 let target = first_edit_start;
6671 InlineCompletion::Move { target, snapshot }
6672 } else {
6673 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6674 && !self.inline_completions_hidden_for_vim_mode;
6675
6676 if show_completions_in_buffer {
6677 if edits
6678 .iter()
6679 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6680 {
6681 let mut inlays = Vec::new();
6682 for (range, new_text) in &edits {
6683 let inlay = Inlay::inline_completion(
6684 post_inc(&mut self.next_inlay_id),
6685 range.start,
6686 new_text.as_str(),
6687 );
6688 inlay_ids.push(inlay.id);
6689 inlays.push(inlay);
6690 }
6691
6692 self.splice_inlays(&[], inlays, cx);
6693 } else {
6694 let background_color = cx.theme().status().deleted_background;
6695 self.highlight_text::<InlineCompletionHighlight>(
6696 edits.iter().map(|(range, _)| range.clone()).collect(),
6697 HighlightStyle {
6698 background_color: Some(background_color),
6699 ..Default::default()
6700 },
6701 cx,
6702 );
6703 }
6704 }
6705
6706 invalidation_row_range = edit_start_row..edit_end_row;
6707
6708 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6709 if provider.show_tab_accept_marker() {
6710 EditDisplayMode::TabAccept
6711 } else {
6712 EditDisplayMode::Inline
6713 }
6714 } else {
6715 EditDisplayMode::DiffPopover
6716 };
6717
6718 InlineCompletion::Edit {
6719 edits,
6720 edit_preview: inline_completion.edit_preview,
6721 display_mode,
6722 snapshot,
6723 }
6724 };
6725
6726 let invalidation_range = multibuffer
6727 .anchor_before(Point::new(invalidation_row_range.start, 0))
6728 ..multibuffer.anchor_after(Point::new(
6729 invalidation_row_range.end,
6730 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6731 ));
6732
6733 self.stale_inline_completion_in_menu = None;
6734 self.active_inline_completion = Some(InlineCompletionState {
6735 inlay_ids,
6736 completion,
6737 completion_id: inline_completion.id,
6738 invalidation_range,
6739 });
6740
6741 cx.notify();
6742
6743 Some(())
6744 }
6745
6746 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6747 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6748 }
6749
6750 fn clear_tasks(&mut self) {
6751 self.tasks.clear()
6752 }
6753
6754 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6755 if self.tasks.insert(key, value).is_some() {
6756 // This case should hopefully be rare, but just in case...
6757 log::error!(
6758 "multiple different run targets found on a single line, only the last target will be rendered"
6759 )
6760 }
6761 }
6762
6763 /// Get all display points of breakpoints that will be rendered within editor
6764 ///
6765 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6766 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6767 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6768 fn active_breakpoints(
6769 &self,
6770 range: Range<DisplayRow>,
6771 window: &mut Window,
6772 cx: &mut Context<Self>,
6773 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
6774 let mut breakpoint_display_points = HashMap::default();
6775
6776 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
6777 return breakpoint_display_points;
6778 };
6779
6780 let snapshot = self.snapshot(window, cx);
6781
6782 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
6783 let Some(project) = self.project.as_ref() else {
6784 return breakpoint_display_points;
6785 };
6786
6787 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
6788 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
6789
6790 for (buffer_snapshot, range, excerpt_id) in
6791 multi_buffer_snapshot.range_to_buffer_ranges(range)
6792 {
6793 let Some(buffer) = project.read_with(cx, |this, cx| {
6794 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
6795 }) else {
6796 continue;
6797 };
6798 let breakpoints = breakpoint_store.read(cx).breakpoints(
6799 &buffer,
6800 Some(
6801 buffer_snapshot.anchor_before(range.start)
6802 ..buffer_snapshot.anchor_after(range.end),
6803 ),
6804 buffer_snapshot,
6805 cx,
6806 );
6807 for (anchor, breakpoint) in breakpoints {
6808 let multi_buffer_anchor =
6809 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), *anchor);
6810 let position = multi_buffer_anchor
6811 .to_point(&multi_buffer_snapshot)
6812 .to_display_point(&snapshot);
6813
6814 breakpoint_display_points
6815 .insert(position.row(), (multi_buffer_anchor, breakpoint.clone()));
6816 }
6817 }
6818
6819 breakpoint_display_points
6820 }
6821
6822 fn breakpoint_context_menu(
6823 &self,
6824 anchor: Anchor,
6825 window: &mut Window,
6826 cx: &mut Context<Self>,
6827 ) -> Entity<ui::ContextMenu> {
6828 let weak_editor = cx.weak_entity();
6829 let focus_handle = self.focus_handle(cx);
6830
6831 let row = self
6832 .buffer
6833 .read(cx)
6834 .snapshot(cx)
6835 .summary_for_anchor::<Point>(&anchor)
6836 .row;
6837
6838 let breakpoint = self
6839 .breakpoint_at_row(row, window, cx)
6840 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
6841
6842 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
6843 "Edit Log Breakpoint"
6844 } else {
6845 "Set Log Breakpoint"
6846 };
6847
6848 let condition_breakpoint_msg = if breakpoint
6849 .as_ref()
6850 .is_some_and(|bp| bp.1.condition.is_some())
6851 {
6852 "Edit Condition Breakpoint"
6853 } else {
6854 "Set Condition Breakpoint"
6855 };
6856
6857 let hit_condition_breakpoint_msg = if breakpoint
6858 .as_ref()
6859 .is_some_and(|bp| bp.1.hit_condition.is_some())
6860 {
6861 "Edit Hit Condition Breakpoint"
6862 } else {
6863 "Set Hit Condition Breakpoint"
6864 };
6865
6866 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
6867 "Unset Breakpoint"
6868 } else {
6869 "Set Breakpoint"
6870 };
6871
6872 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
6873 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
6874
6875 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
6876 BreakpointState::Enabled => Some("Disable"),
6877 BreakpointState::Disabled => Some("Enable"),
6878 });
6879
6880 let (anchor, breakpoint) =
6881 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
6882
6883 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6884 menu.on_blur_subscription(Subscription::new(|| {}))
6885 .context(focus_handle)
6886 .when(run_to_cursor, |this| {
6887 let weak_editor = weak_editor.clone();
6888 this.entry("Run to cursor", None, move |window, cx| {
6889 weak_editor
6890 .update(cx, |editor, cx| {
6891 editor.change_selections(None, window, cx, |s| {
6892 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
6893 });
6894 })
6895 .ok();
6896
6897 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
6898 })
6899 .separator()
6900 })
6901 .when_some(toggle_state_msg, |this, msg| {
6902 this.entry(msg, None, {
6903 let weak_editor = weak_editor.clone();
6904 let breakpoint = breakpoint.clone();
6905 move |_window, cx| {
6906 weak_editor
6907 .update(cx, |this, cx| {
6908 this.edit_breakpoint_at_anchor(
6909 anchor,
6910 breakpoint.as_ref().clone(),
6911 BreakpointEditAction::InvertState,
6912 cx,
6913 );
6914 })
6915 .log_err();
6916 }
6917 })
6918 })
6919 .entry(set_breakpoint_msg, None, {
6920 let weak_editor = weak_editor.clone();
6921 let breakpoint = breakpoint.clone();
6922 move |_window, cx| {
6923 weak_editor
6924 .update(cx, |this, cx| {
6925 this.edit_breakpoint_at_anchor(
6926 anchor,
6927 breakpoint.as_ref().clone(),
6928 BreakpointEditAction::Toggle,
6929 cx,
6930 );
6931 })
6932 .log_err();
6933 }
6934 })
6935 .entry(log_breakpoint_msg, None, {
6936 let breakpoint = breakpoint.clone();
6937 let weak_editor = weak_editor.clone();
6938 move |window, cx| {
6939 weak_editor
6940 .update(cx, |this, cx| {
6941 this.add_edit_breakpoint_block(
6942 anchor,
6943 breakpoint.as_ref(),
6944 BreakpointPromptEditAction::Log,
6945 window,
6946 cx,
6947 );
6948 })
6949 .log_err();
6950 }
6951 })
6952 .entry(condition_breakpoint_msg, None, {
6953 let breakpoint = breakpoint.clone();
6954 let weak_editor = weak_editor.clone();
6955 move |window, cx| {
6956 weak_editor
6957 .update(cx, |this, cx| {
6958 this.add_edit_breakpoint_block(
6959 anchor,
6960 breakpoint.as_ref(),
6961 BreakpointPromptEditAction::Condition,
6962 window,
6963 cx,
6964 );
6965 })
6966 .log_err();
6967 }
6968 })
6969 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
6970 weak_editor
6971 .update(cx, |this, cx| {
6972 this.add_edit_breakpoint_block(
6973 anchor,
6974 breakpoint.as_ref(),
6975 BreakpointPromptEditAction::HitCondition,
6976 window,
6977 cx,
6978 );
6979 })
6980 .log_err();
6981 })
6982 })
6983 }
6984
6985 fn render_breakpoint(
6986 &self,
6987 position: Anchor,
6988 row: DisplayRow,
6989 breakpoint: &Breakpoint,
6990 cx: &mut Context<Self>,
6991 ) -> IconButton {
6992 // Is it a breakpoint that shows up when hovering over gutter?
6993 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
6994 (false, false),
6995 |PhantomBreakpointIndicator {
6996 is_active,
6997 display_row,
6998 collides_with_existing_breakpoint,
6999 }| {
7000 (
7001 is_active && display_row == row,
7002 collides_with_existing_breakpoint,
7003 )
7004 },
7005 );
7006
7007 let (color, icon) = {
7008 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7009 (false, false) => ui::IconName::DebugBreakpoint,
7010 (true, false) => ui::IconName::DebugLogBreakpoint,
7011 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7012 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7013 };
7014
7015 let color = if is_phantom {
7016 Color::Hint
7017 } else {
7018 Color::Debugger
7019 };
7020
7021 (color, icon)
7022 };
7023
7024 let breakpoint = Arc::from(breakpoint.clone());
7025
7026 let alt_as_text = gpui::Keystroke {
7027 modifiers: Modifiers::secondary_key(),
7028 ..Default::default()
7029 };
7030 let primary_action_text = if breakpoint.is_disabled() {
7031 "enable"
7032 } else if is_phantom && !collides_with_existing {
7033 "set"
7034 } else {
7035 "unset"
7036 };
7037 let mut primary_text = format!("Click to {primary_action_text}");
7038 if collides_with_existing && !breakpoint.is_disabled() {
7039 use std::fmt::Write;
7040 write!(primary_text, ", {alt_as_text}-click to disable").ok();
7041 }
7042 let primary_text = SharedString::from(primary_text);
7043 let focus_handle = self.focus_handle.clone();
7044 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7045 .icon_size(IconSize::XSmall)
7046 .size(ui::ButtonSize::None)
7047 .icon_color(color)
7048 .style(ButtonStyle::Transparent)
7049 .on_click(cx.listener({
7050 let breakpoint = breakpoint.clone();
7051
7052 move |editor, event: &ClickEvent, window, cx| {
7053 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7054 BreakpointEditAction::InvertState
7055 } else {
7056 BreakpointEditAction::Toggle
7057 };
7058
7059 window.focus(&editor.focus_handle(cx));
7060 editor.edit_breakpoint_at_anchor(
7061 position,
7062 breakpoint.as_ref().clone(),
7063 edit_action,
7064 cx,
7065 );
7066 }
7067 }))
7068 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7069 editor.set_breakpoint_context_menu(
7070 row,
7071 Some(position),
7072 event.down.position,
7073 window,
7074 cx,
7075 );
7076 }))
7077 .tooltip(move |window, cx| {
7078 Tooltip::with_meta_in(
7079 primary_text.clone(),
7080 None,
7081 "Right-click for more options",
7082 &focus_handle,
7083 window,
7084 cx,
7085 )
7086 })
7087 }
7088
7089 fn build_tasks_context(
7090 project: &Entity<Project>,
7091 buffer: &Entity<Buffer>,
7092 buffer_row: u32,
7093 tasks: &Arc<RunnableTasks>,
7094 cx: &mut Context<Self>,
7095 ) -> Task<Option<task::TaskContext>> {
7096 let position = Point::new(buffer_row, tasks.column);
7097 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7098 let location = Location {
7099 buffer: buffer.clone(),
7100 range: range_start..range_start,
7101 };
7102 // Fill in the environmental variables from the tree-sitter captures
7103 let mut captured_task_variables = TaskVariables::default();
7104 for (capture_name, value) in tasks.extra_variables.clone() {
7105 captured_task_variables.insert(
7106 task::VariableName::Custom(capture_name.into()),
7107 value.clone(),
7108 );
7109 }
7110 project.update(cx, |project, cx| {
7111 project.task_store().update(cx, |task_store, cx| {
7112 task_store.task_context_for_location(captured_task_variables, location, cx)
7113 })
7114 })
7115 }
7116
7117 pub fn spawn_nearest_task(
7118 &mut self,
7119 action: &SpawnNearestTask,
7120 window: &mut Window,
7121 cx: &mut Context<Self>,
7122 ) {
7123 let Some((workspace, _)) = self.workspace.clone() else {
7124 return;
7125 };
7126 let Some(project) = self.project.clone() else {
7127 return;
7128 };
7129
7130 // Try to find a closest, enclosing node using tree-sitter that has a
7131 // task
7132 let Some((buffer, buffer_row, tasks)) = self
7133 .find_enclosing_node_task(cx)
7134 // Or find the task that's closest in row-distance.
7135 .or_else(|| self.find_closest_task(cx))
7136 else {
7137 return;
7138 };
7139
7140 let reveal_strategy = action.reveal;
7141 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7142 cx.spawn_in(window, async move |_, cx| {
7143 let context = task_context.await?;
7144 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7145
7146 let resolved = &mut resolved_task.resolved;
7147 resolved.reveal = reveal_strategy;
7148
7149 workspace
7150 .update_in(cx, |workspace, window, cx| {
7151 workspace.schedule_resolved_task(
7152 task_source_kind,
7153 resolved_task,
7154 false,
7155 window,
7156 cx,
7157 );
7158 })
7159 .ok()
7160 })
7161 .detach();
7162 }
7163
7164 fn find_closest_task(
7165 &mut self,
7166 cx: &mut Context<Self>,
7167 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7168 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7169
7170 let ((buffer_id, row), tasks) = self
7171 .tasks
7172 .iter()
7173 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7174
7175 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7176 let tasks = Arc::new(tasks.to_owned());
7177 Some((buffer, *row, tasks))
7178 }
7179
7180 fn find_enclosing_node_task(
7181 &mut self,
7182 cx: &mut Context<Self>,
7183 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7184 let snapshot = self.buffer.read(cx).snapshot(cx);
7185 let offset = self.selections.newest::<usize>(cx).head();
7186 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7187 let buffer_id = excerpt.buffer().remote_id();
7188
7189 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7190 let mut cursor = layer.node().walk();
7191
7192 while cursor.goto_first_child_for_byte(offset).is_some() {
7193 if cursor.node().end_byte() == offset {
7194 cursor.goto_next_sibling();
7195 }
7196 }
7197
7198 // Ascend to the smallest ancestor that contains the range and has a task.
7199 loop {
7200 let node = cursor.node();
7201 let node_range = node.byte_range();
7202 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7203
7204 // Check if this node contains our offset
7205 if node_range.start <= offset && node_range.end >= offset {
7206 // If it contains offset, check for task
7207 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7208 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7209 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7210 }
7211 }
7212
7213 if !cursor.goto_parent() {
7214 break;
7215 }
7216 }
7217 None
7218 }
7219
7220 fn render_run_indicator(
7221 &self,
7222 _style: &EditorStyle,
7223 is_active: bool,
7224 row: DisplayRow,
7225 breakpoint: Option<(Anchor, Breakpoint)>,
7226 cx: &mut Context<Self>,
7227 ) -> IconButton {
7228 let color = Color::Muted;
7229 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
7230
7231 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7232 .shape(ui::IconButtonShape::Square)
7233 .icon_size(IconSize::XSmall)
7234 .icon_color(color)
7235 .toggle_state(is_active)
7236 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7237 let quick_launch = e.down.button == MouseButton::Left;
7238 window.focus(&editor.focus_handle(cx));
7239 editor.toggle_code_actions(
7240 &ToggleCodeActions {
7241 deployed_from_indicator: Some(row),
7242 quick_launch,
7243 },
7244 window,
7245 cx,
7246 );
7247 }))
7248 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7249 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7250 }))
7251 }
7252
7253 pub fn context_menu_visible(&self) -> bool {
7254 !self.edit_prediction_preview_is_active()
7255 && self
7256 .context_menu
7257 .borrow()
7258 .as_ref()
7259 .map_or(false, |menu| menu.visible())
7260 }
7261
7262 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7263 self.context_menu
7264 .borrow()
7265 .as_ref()
7266 .map(|menu| menu.origin())
7267 }
7268
7269 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7270 self.context_menu_options = Some(options);
7271 }
7272
7273 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7274 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7275
7276 fn render_edit_prediction_popover(
7277 &mut self,
7278 text_bounds: &Bounds<Pixels>,
7279 content_origin: gpui::Point<Pixels>,
7280 right_margin: Pixels,
7281 editor_snapshot: &EditorSnapshot,
7282 visible_row_range: Range<DisplayRow>,
7283 scroll_top: f32,
7284 scroll_bottom: f32,
7285 line_layouts: &[LineWithInvisibles],
7286 line_height: Pixels,
7287 scroll_pixel_position: gpui::Point<Pixels>,
7288 newest_selection_head: Option<DisplayPoint>,
7289 editor_width: Pixels,
7290 style: &EditorStyle,
7291 window: &mut Window,
7292 cx: &mut App,
7293 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7294 if self.mode().is_minimap() {
7295 return None;
7296 }
7297 let active_inline_completion = self.active_inline_completion.as_ref()?;
7298
7299 if self.edit_prediction_visible_in_cursor_popover(true) {
7300 return None;
7301 }
7302
7303 match &active_inline_completion.completion {
7304 InlineCompletion::Move { target, .. } => {
7305 let target_display_point = target.to_display_point(editor_snapshot);
7306
7307 if self.edit_prediction_requires_modifier() {
7308 if !self.edit_prediction_preview_is_active() {
7309 return None;
7310 }
7311
7312 self.render_edit_prediction_modifier_jump_popover(
7313 text_bounds,
7314 content_origin,
7315 visible_row_range,
7316 line_layouts,
7317 line_height,
7318 scroll_pixel_position,
7319 newest_selection_head,
7320 target_display_point,
7321 window,
7322 cx,
7323 )
7324 } else {
7325 self.render_edit_prediction_eager_jump_popover(
7326 text_bounds,
7327 content_origin,
7328 editor_snapshot,
7329 visible_row_range,
7330 scroll_top,
7331 scroll_bottom,
7332 line_height,
7333 scroll_pixel_position,
7334 target_display_point,
7335 editor_width,
7336 window,
7337 cx,
7338 )
7339 }
7340 }
7341 InlineCompletion::Edit {
7342 display_mode: EditDisplayMode::Inline,
7343 ..
7344 } => None,
7345 InlineCompletion::Edit {
7346 display_mode: EditDisplayMode::TabAccept,
7347 edits,
7348 ..
7349 } => {
7350 let range = &edits.first()?.0;
7351 let target_display_point = range.end.to_display_point(editor_snapshot);
7352
7353 self.render_edit_prediction_end_of_line_popover(
7354 "Accept",
7355 editor_snapshot,
7356 visible_row_range,
7357 target_display_point,
7358 line_height,
7359 scroll_pixel_position,
7360 content_origin,
7361 editor_width,
7362 window,
7363 cx,
7364 )
7365 }
7366 InlineCompletion::Edit {
7367 edits,
7368 edit_preview,
7369 display_mode: EditDisplayMode::DiffPopover,
7370 snapshot,
7371 } => self.render_edit_prediction_diff_popover(
7372 text_bounds,
7373 content_origin,
7374 right_margin,
7375 editor_snapshot,
7376 visible_row_range,
7377 line_layouts,
7378 line_height,
7379 scroll_pixel_position,
7380 newest_selection_head,
7381 editor_width,
7382 style,
7383 edits,
7384 edit_preview,
7385 snapshot,
7386 window,
7387 cx,
7388 ),
7389 }
7390 }
7391
7392 fn render_edit_prediction_modifier_jump_popover(
7393 &mut self,
7394 text_bounds: &Bounds<Pixels>,
7395 content_origin: gpui::Point<Pixels>,
7396 visible_row_range: Range<DisplayRow>,
7397 line_layouts: &[LineWithInvisibles],
7398 line_height: Pixels,
7399 scroll_pixel_position: gpui::Point<Pixels>,
7400 newest_selection_head: Option<DisplayPoint>,
7401 target_display_point: DisplayPoint,
7402 window: &mut Window,
7403 cx: &mut App,
7404 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7405 let scrolled_content_origin =
7406 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7407
7408 const SCROLL_PADDING_Y: Pixels = px(12.);
7409
7410 if target_display_point.row() < visible_row_range.start {
7411 return self.render_edit_prediction_scroll_popover(
7412 |_| SCROLL_PADDING_Y,
7413 IconName::ArrowUp,
7414 visible_row_range,
7415 line_layouts,
7416 newest_selection_head,
7417 scrolled_content_origin,
7418 window,
7419 cx,
7420 );
7421 } else if target_display_point.row() >= visible_row_range.end {
7422 return self.render_edit_prediction_scroll_popover(
7423 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7424 IconName::ArrowDown,
7425 visible_row_range,
7426 line_layouts,
7427 newest_selection_head,
7428 scrolled_content_origin,
7429 window,
7430 cx,
7431 );
7432 }
7433
7434 const POLE_WIDTH: Pixels = px(2.);
7435
7436 let line_layout =
7437 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7438 let target_column = target_display_point.column() as usize;
7439
7440 let target_x = line_layout.x_for_index(target_column);
7441 let target_y =
7442 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7443
7444 let flag_on_right = target_x < text_bounds.size.width / 2.;
7445
7446 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7447 border_color.l += 0.001;
7448
7449 let mut element = v_flex()
7450 .items_end()
7451 .when(flag_on_right, |el| el.items_start())
7452 .child(if flag_on_right {
7453 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7454 .rounded_bl(px(0.))
7455 .rounded_tl(px(0.))
7456 .border_l_2()
7457 .border_color(border_color)
7458 } else {
7459 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7460 .rounded_br(px(0.))
7461 .rounded_tr(px(0.))
7462 .border_r_2()
7463 .border_color(border_color)
7464 })
7465 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7466 .into_any();
7467
7468 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7469
7470 let mut origin = scrolled_content_origin + point(target_x, target_y)
7471 - point(
7472 if flag_on_right {
7473 POLE_WIDTH
7474 } else {
7475 size.width - POLE_WIDTH
7476 },
7477 size.height - line_height,
7478 );
7479
7480 origin.x = origin.x.max(content_origin.x);
7481
7482 element.prepaint_at(origin, window, cx);
7483
7484 Some((element, origin))
7485 }
7486
7487 fn render_edit_prediction_scroll_popover(
7488 &mut self,
7489 to_y: impl Fn(Size<Pixels>) -> Pixels,
7490 scroll_icon: IconName,
7491 visible_row_range: Range<DisplayRow>,
7492 line_layouts: &[LineWithInvisibles],
7493 newest_selection_head: Option<DisplayPoint>,
7494 scrolled_content_origin: gpui::Point<Pixels>,
7495 window: &mut Window,
7496 cx: &mut App,
7497 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7498 let mut element = self
7499 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7500 .into_any();
7501
7502 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7503
7504 let cursor = newest_selection_head?;
7505 let cursor_row_layout =
7506 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7507 let cursor_column = cursor.column() as usize;
7508
7509 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7510
7511 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7512
7513 element.prepaint_at(origin, window, cx);
7514 Some((element, origin))
7515 }
7516
7517 fn render_edit_prediction_eager_jump_popover(
7518 &mut self,
7519 text_bounds: &Bounds<Pixels>,
7520 content_origin: gpui::Point<Pixels>,
7521 editor_snapshot: &EditorSnapshot,
7522 visible_row_range: Range<DisplayRow>,
7523 scroll_top: f32,
7524 scroll_bottom: f32,
7525 line_height: Pixels,
7526 scroll_pixel_position: gpui::Point<Pixels>,
7527 target_display_point: DisplayPoint,
7528 editor_width: Pixels,
7529 window: &mut Window,
7530 cx: &mut App,
7531 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7532 if target_display_point.row().as_f32() < scroll_top {
7533 let mut element = self
7534 .render_edit_prediction_line_popover(
7535 "Jump to Edit",
7536 Some(IconName::ArrowUp),
7537 window,
7538 cx,
7539 )?
7540 .into_any();
7541
7542 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7543 let offset = point(
7544 (text_bounds.size.width - size.width) / 2.,
7545 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7546 );
7547
7548 let origin = text_bounds.origin + offset;
7549 element.prepaint_at(origin, window, cx);
7550 Some((element, origin))
7551 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7552 let mut element = self
7553 .render_edit_prediction_line_popover(
7554 "Jump to Edit",
7555 Some(IconName::ArrowDown),
7556 window,
7557 cx,
7558 )?
7559 .into_any();
7560
7561 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7562 let offset = point(
7563 (text_bounds.size.width - size.width) / 2.,
7564 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7565 );
7566
7567 let origin = text_bounds.origin + offset;
7568 element.prepaint_at(origin, window, cx);
7569 Some((element, origin))
7570 } else {
7571 self.render_edit_prediction_end_of_line_popover(
7572 "Jump to Edit",
7573 editor_snapshot,
7574 visible_row_range,
7575 target_display_point,
7576 line_height,
7577 scroll_pixel_position,
7578 content_origin,
7579 editor_width,
7580 window,
7581 cx,
7582 )
7583 }
7584 }
7585
7586 fn render_edit_prediction_end_of_line_popover(
7587 self: &mut Editor,
7588 label: &'static str,
7589 editor_snapshot: &EditorSnapshot,
7590 visible_row_range: Range<DisplayRow>,
7591 target_display_point: DisplayPoint,
7592 line_height: Pixels,
7593 scroll_pixel_position: gpui::Point<Pixels>,
7594 content_origin: gpui::Point<Pixels>,
7595 editor_width: Pixels,
7596 window: &mut Window,
7597 cx: &mut App,
7598 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7599 let target_line_end = DisplayPoint::new(
7600 target_display_point.row(),
7601 editor_snapshot.line_len(target_display_point.row()),
7602 );
7603
7604 let mut element = self
7605 .render_edit_prediction_line_popover(label, None, window, cx)?
7606 .into_any();
7607
7608 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7609
7610 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7611
7612 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7613 let mut origin = start_point
7614 + line_origin
7615 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7616 origin.x = origin.x.max(content_origin.x);
7617
7618 let max_x = content_origin.x + editor_width - size.width;
7619
7620 if origin.x > max_x {
7621 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7622
7623 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7624 origin.y += offset;
7625 IconName::ArrowUp
7626 } else {
7627 origin.y -= offset;
7628 IconName::ArrowDown
7629 };
7630
7631 element = self
7632 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7633 .into_any();
7634
7635 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7636
7637 origin.x = content_origin.x + editor_width - size.width - px(2.);
7638 }
7639
7640 element.prepaint_at(origin, window, cx);
7641 Some((element, origin))
7642 }
7643
7644 fn render_edit_prediction_diff_popover(
7645 self: &Editor,
7646 text_bounds: &Bounds<Pixels>,
7647 content_origin: gpui::Point<Pixels>,
7648 right_margin: Pixels,
7649 editor_snapshot: &EditorSnapshot,
7650 visible_row_range: Range<DisplayRow>,
7651 line_layouts: &[LineWithInvisibles],
7652 line_height: Pixels,
7653 scroll_pixel_position: gpui::Point<Pixels>,
7654 newest_selection_head: Option<DisplayPoint>,
7655 editor_width: Pixels,
7656 style: &EditorStyle,
7657 edits: &Vec<(Range<Anchor>, String)>,
7658 edit_preview: &Option<language::EditPreview>,
7659 snapshot: &language::BufferSnapshot,
7660 window: &mut Window,
7661 cx: &mut App,
7662 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7663 let edit_start = edits
7664 .first()
7665 .unwrap()
7666 .0
7667 .start
7668 .to_display_point(editor_snapshot);
7669 let edit_end = edits
7670 .last()
7671 .unwrap()
7672 .0
7673 .end
7674 .to_display_point(editor_snapshot);
7675
7676 let is_visible = visible_row_range.contains(&edit_start.row())
7677 || visible_row_range.contains(&edit_end.row());
7678 if !is_visible {
7679 return None;
7680 }
7681
7682 let highlighted_edits =
7683 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7684
7685 let styled_text = highlighted_edits.to_styled_text(&style.text);
7686 let line_count = highlighted_edits.text.lines().count();
7687
7688 const BORDER_WIDTH: Pixels = px(1.);
7689
7690 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7691 let has_keybind = keybind.is_some();
7692
7693 let mut element = h_flex()
7694 .items_start()
7695 .child(
7696 h_flex()
7697 .bg(cx.theme().colors().editor_background)
7698 .border(BORDER_WIDTH)
7699 .shadow_sm()
7700 .border_color(cx.theme().colors().border)
7701 .rounded_l_lg()
7702 .when(line_count > 1, |el| el.rounded_br_lg())
7703 .pr_1()
7704 .child(styled_text),
7705 )
7706 .child(
7707 h_flex()
7708 .h(line_height + BORDER_WIDTH * 2.)
7709 .px_1p5()
7710 .gap_1()
7711 // Workaround: For some reason, there's a gap if we don't do this
7712 .ml(-BORDER_WIDTH)
7713 .shadow(smallvec![gpui::BoxShadow {
7714 color: gpui::black().opacity(0.05),
7715 offset: point(px(1.), px(1.)),
7716 blur_radius: px(2.),
7717 spread_radius: px(0.),
7718 }])
7719 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7720 .border(BORDER_WIDTH)
7721 .border_color(cx.theme().colors().border)
7722 .rounded_r_lg()
7723 .id("edit_prediction_diff_popover_keybind")
7724 .when(!has_keybind, |el| {
7725 let status_colors = cx.theme().status();
7726
7727 el.bg(status_colors.error_background)
7728 .border_color(status_colors.error.opacity(0.6))
7729 .child(Icon::new(IconName::Info).color(Color::Error))
7730 .cursor_default()
7731 .hoverable_tooltip(move |_window, cx| {
7732 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7733 })
7734 })
7735 .children(keybind),
7736 )
7737 .into_any();
7738
7739 let longest_row =
7740 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7741 let longest_line_width = if visible_row_range.contains(&longest_row) {
7742 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7743 } else {
7744 layout_line(
7745 longest_row,
7746 editor_snapshot,
7747 style,
7748 editor_width,
7749 |_| false,
7750 window,
7751 cx,
7752 )
7753 .width
7754 };
7755
7756 let viewport_bounds =
7757 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
7758 right: -right_margin,
7759 ..Default::default()
7760 });
7761
7762 let x_after_longest =
7763 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
7764 - scroll_pixel_position.x;
7765
7766 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7767
7768 // Fully visible if it can be displayed within the window (allow overlapping other
7769 // panes). However, this is only allowed if the popover starts within text_bounds.
7770 let can_position_to_the_right = x_after_longest < text_bounds.right()
7771 && x_after_longest + element_bounds.width < viewport_bounds.right();
7772
7773 let mut origin = if can_position_to_the_right {
7774 point(
7775 x_after_longest,
7776 text_bounds.origin.y + edit_start.row().as_f32() * line_height
7777 - scroll_pixel_position.y,
7778 )
7779 } else {
7780 let cursor_row = newest_selection_head.map(|head| head.row());
7781 let above_edit = edit_start
7782 .row()
7783 .0
7784 .checked_sub(line_count as u32)
7785 .map(DisplayRow);
7786 let below_edit = Some(edit_end.row() + 1);
7787 let above_cursor =
7788 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
7789 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
7790
7791 // Place the edit popover adjacent to the edit if there is a location
7792 // available that is onscreen and does not obscure the cursor. Otherwise,
7793 // place it adjacent to the cursor.
7794 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
7795 .into_iter()
7796 .flatten()
7797 .find(|&start_row| {
7798 let end_row = start_row + line_count as u32;
7799 visible_row_range.contains(&start_row)
7800 && visible_row_range.contains(&end_row)
7801 && cursor_row.map_or(true, |cursor_row| {
7802 !((start_row..end_row).contains(&cursor_row))
7803 })
7804 })?;
7805
7806 content_origin
7807 + point(
7808 -scroll_pixel_position.x,
7809 row_target.as_f32() * line_height - scroll_pixel_position.y,
7810 )
7811 };
7812
7813 origin.x -= BORDER_WIDTH;
7814
7815 window.defer_draw(element, origin, 1);
7816
7817 // Do not return an element, since it will already be drawn due to defer_draw.
7818 None
7819 }
7820
7821 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
7822 px(30.)
7823 }
7824
7825 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
7826 if self.read_only(cx) {
7827 cx.theme().players().read_only()
7828 } else {
7829 self.style.as_ref().unwrap().local_player
7830 }
7831 }
7832
7833 fn render_edit_prediction_accept_keybind(
7834 &self,
7835 window: &mut Window,
7836 cx: &App,
7837 ) -> Option<AnyElement> {
7838 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
7839 let accept_keystroke = accept_binding.keystroke()?;
7840
7841 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7842
7843 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
7844 Color::Accent
7845 } else {
7846 Color::Muted
7847 };
7848
7849 h_flex()
7850 .px_0p5()
7851 .when(is_platform_style_mac, |parent| parent.gap_0p5())
7852 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7853 .text_size(TextSize::XSmall.rems(cx))
7854 .child(h_flex().children(ui::render_modifiers(
7855 &accept_keystroke.modifiers,
7856 PlatformStyle::platform(),
7857 Some(modifiers_color),
7858 Some(IconSize::XSmall.rems().into()),
7859 true,
7860 )))
7861 .when(is_platform_style_mac, |parent| {
7862 parent.child(accept_keystroke.key.clone())
7863 })
7864 .when(!is_platform_style_mac, |parent| {
7865 parent.child(
7866 Key::new(
7867 util::capitalize(&accept_keystroke.key),
7868 Some(Color::Default),
7869 )
7870 .size(Some(IconSize::XSmall.rems().into())),
7871 )
7872 })
7873 .into_any()
7874 .into()
7875 }
7876
7877 fn render_edit_prediction_line_popover(
7878 &self,
7879 label: impl Into<SharedString>,
7880 icon: Option<IconName>,
7881 window: &mut Window,
7882 cx: &App,
7883 ) -> Option<Stateful<Div>> {
7884 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
7885
7886 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7887 let has_keybind = keybind.is_some();
7888
7889 let result = h_flex()
7890 .id("ep-line-popover")
7891 .py_0p5()
7892 .pl_1()
7893 .pr(padding_right)
7894 .gap_1()
7895 .rounded_md()
7896 .border_1()
7897 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7898 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7899 .shadow_sm()
7900 .when(!has_keybind, |el| {
7901 let status_colors = cx.theme().status();
7902
7903 el.bg(status_colors.error_background)
7904 .border_color(status_colors.error.opacity(0.6))
7905 .pl_2()
7906 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7907 .cursor_default()
7908 .hoverable_tooltip(move |_window, cx| {
7909 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7910 })
7911 })
7912 .children(keybind)
7913 .child(
7914 Label::new(label)
7915 .size(LabelSize::Small)
7916 .when(!has_keybind, |el| {
7917 el.color(cx.theme().status().error.into()).strikethrough()
7918 }),
7919 )
7920 .when(!has_keybind, |el| {
7921 el.child(
7922 h_flex().ml_1().child(
7923 Icon::new(IconName::Info)
7924 .size(IconSize::Small)
7925 .color(cx.theme().status().error.into()),
7926 ),
7927 )
7928 })
7929 .when_some(icon, |element, icon| {
7930 element.child(
7931 div()
7932 .mt(px(1.5))
7933 .child(Icon::new(icon).size(IconSize::Small)),
7934 )
7935 });
7936
7937 Some(result)
7938 }
7939
7940 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
7941 let accent_color = cx.theme().colors().text_accent;
7942 let editor_bg_color = cx.theme().colors().editor_background;
7943 editor_bg_color.blend(accent_color.opacity(0.1))
7944 }
7945
7946 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
7947 let accent_color = cx.theme().colors().text_accent;
7948 let editor_bg_color = cx.theme().colors().editor_background;
7949 editor_bg_color.blend(accent_color.opacity(0.6))
7950 }
7951
7952 fn render_edit_prediction_cursor_popover(
7953 &self,
7954 min_width: Pixels,
7955 max_width: Pixels,
7956 cursor_point: Point,
7957 style: &EditorStyle,
7958 accept_keystroke: Option<&gpui::Keystroke>,
7959 _window: &Window,
7960 cx: &mut Context<Editor>,
7961 ) -> Option<AnyElement> {
7962 let provider = self.edit_prediction_provider.as_ref()?;
7963
7964 if provider.provider.needs_terms_acceptance(cx) {
7965 return Some(
7966 h_flex()
7967 .min_w(min_width)
7968 .flex_1()
7969 .px_2()
7970 .py_1()
7971 .gap_3()
7972 .elevation_2(cx)
7973 .hover(|style| style.bg(cx.theme().colors().element_hover))
7974 .id("accept-terms")
7975 .cursor_pointer()
7976 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
7977 .on_click(cx.listener(|this, _event, window, cx| {
7978 cx.stop_propagation();
7979 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
7980 window.dispatch_action(
7981 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
7982 cx,
7983 );
7984 }))
7985 .child(
7986 h_flex()
7987 .flex_1()
7988 .gap_2()
7989 .child(Icon::new(IconName::ZedPredict))
7990 .child(Label::new("Accept Terms of Service"))
7991 .child(div().w_full())
7992 .child(
7993 Icon::new(IconName::ArrowUpRight)
7994 .color(Color::Muted)
7995 .size(IconSize::Small),
7996 )
7997 .into_any_element(),
7998 )
7999 .into_any(),
8000 );
8001 }
8002
8003 let is_refreshing = provider.provider.is_refreshing(cx);
8004
8005 fn pending_completion_container() -> Div {
8006 h_flex()
8007 .h_full()
8008 .flex_1()
8009 .gap_2()
8010 .child(Icon::new(IconName::ZedPredict))
8011 }
8012
8013 let completion = match &self.active_inline_completion {
8014 Some(prediction) => {
8015 if !self.has_visible_completions_menu() {
8016 const RADIUS: Pixels = px(6.);
8017 const BORDER_WIDTH: Pixels = px(1.);
8018
8019 return Some(
8020 h_flex()
8021 .elevation_2(cx)
8022 .border(BORDER_WIDTH)
8023 .border_color(cx.theme().colors().border)
8024 .when(accept_keystroke.is_none(), |el| {
8025 el.border_color(cx.theme().status().error)
8026 })
8027 .rounded(RADIUS)
8028 .rounded_tl(px(0.))
8029 .overflow_hidden()
8030 .child(div().px_1p5().child(match &prediction.completion {
8031 InlineCompletion::Move { target, snapshot } => {
8032 use text::ToPoint as _;
8033 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8034 {
8035 Icon::new(IconName::ZedPredictDown)
8036 } else {
8037 Icon::new(IconName::ZedPredictUp)
8038 }
8039 }
8040 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8041 }))
8042 .child(
8043 h_flex()
8044 .gap_1()
8045 .py_1()
8046 .px_2()
8047 .rounded_r(RADIUS - BORDER_WIDTH)
8048 .border_l_1()
8049 .border_color(cx.theme().colors().border)
8050 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8051 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8052 el.child(
8053 Label::new("Hold")
8054 .size(LabelSize::Small)
8055 .when(accept_keystroke.is_none(), |el| {
8056 el.strikethrough()
8057 })
8058 .line_height_style(LineHeightStyle::UiLabel),
8059 )
8060 })
8061 .id("edit_prediction_cursor_popover_keybind")
8062 .when(accept_keystroke.is_none(), |el| {
8063 let status_colors = cx.theme().status();
8064
8065 el.bg(status_colors.error_background)
8066 .border_color(status_colors.error.opacity(0.6))
8067 .child(Icon::new(IconName::Info).color(Color::Error))
8068 .cursor_default()
8069 .hoverable_tooltip(move |_window, cx| {
8070 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8071 .into()
8072 })
8073 })
8074 .when_some(
8075 accept_keystroke.as_ref(),
8076 |el, accept_keystroke| {
8077 el.child(h_flex().children(ui::render_modifiers(
8078 &accept_keystroke.modifiers,
8079 PlatformStyle::platform(),
8080 Some(Color::Default),
8081 Some(IconSize::XSmall.rems().into()),
8082 false,
8083 )))
8084 },
8085 ),
8086 )
8087 .into_any(),
8088 );
8089 }
8090
8091 self.render_edit_prediction_cursor_popover_preview(
8092 prediction,
8093 cursor_point,
8094 style,
8095 cx,
8096 )?
8097 }
8098
8099 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8100 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8101 stale_completion,
8102 cursor_point,
8103 style,
8104 cx,
8105 )?,
8106
8107 None => {
8108 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8109 }
8110 },
8111
8112 None => pending_completion_container().child(Label::new("No Prediction")),
8113 };
8114
8115 let completion = if is_refreshing {
8116 completion
8117 .with_animation(
8118 "loading-completion",
8119 Animation::new(Duration::from_secs(2))
8120 .repeat()
8121 .with_easing(pulsating_between(0.4, 0.8)),
8122 |label, delta| label.opacity(delta),
8123 )
8124 .into_any_element()
8125 } else {
8126 completion.into_any_element()
8127 };
8128
8129 let has_completion = self.active_inline_completion.is_some();
8130
8131 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8132 Some(
8133 h_flex()
8134 .min_w(min_width)
8135 .max_w(max_width)
8136 .flex_1()
8137 .elevation_2(cx)
8138 .border_color(cx.theme().colors().border)
8139 .child(
8140 div()
8141 .flex_1()
8142 .py_1()
8143 .px_2()
8144 .overflow_hidden()
8145 .child(completion),
8146 )
8147 .when_some(accept_keystroke, |el, accept_keystroke| {
8148 if !accept_keystroke.modifiers.modified() {
8149 return el;
8150 }
8151
8152 el.child(
8153 h_flex()
8154 .h_full()
8155 .border_l_1()
8156 .rounded_r_lg()
8157 .border_color(cx.theme().colors().border)
8158 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8159 .gap_1()
8160 .py_1()
8161 .px_2()
8162 .child(
8163 h_flex()
8164 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8165 .when(is_platform_style_mac, |parent| parent.gap_1())
8166 .child(h_flex().children(ui::render_modifiers(
8167 &accept_keystroke.modifiers,
8168 PlatformStyle::platform(),
8169 Some(if !has_completion {
8170 Color::Muted
8171 } else {
8172 Color::Default
8173 }),
8174 None,
8175 false,
8176 ))),
8177 )
8178 .child(Label::new("Preview").into_any_element())
8179 .opacity(if has_completion { 1.0 } else { 0.4 }),
8180 )
8181 })
8182 .into_any(),
8183 )
8184 }
8185
8186 fn render_edit_prediction_cursor_popover_preview(
8187 &self,
8188 completion: &InlineCompletionState,
8189 cursor_point: Point,
8190 style: &EditorStyle,
8191 cx: &mut Context<Editor>,
8192 ) -> Option<Div> {
8193 use text::ToPoint as _;
8194
8195 fn render_relative_row_jump(
8196 prefix: impl Into<String>,
8197 current_row: u32,
8198 target_row: u32,
8199 ) -> Div {
8200 let (row_diff, arrow) = if target_row < current_row {
8201 (current_row - target_row, IconName::ArrowUp)
8202 } else {
8203 (target_row - current_row, IconName::ArrowDown)
8204 };
8205
8206 h_flex()
8207 .child(
8208 Label::new(format!("{}{}", prefix.into(), row_diff))
8209 .color(Color::Muted)
8210 .size(LabelSize::Small),
8211 )
8212 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8213 }
8214
8215 match &completion.completion {
8216 InlineCompletion::Move {
8217 target, snapshot, ..
8218 } => Some(
8219 h_flex()
8220 .px_2()
8221 .gap_2()
8222 .flex_1()
8223 .child(
8224 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8225 Icon::new(IconName::ZedPredictDown)
8226 } else {
8227 Icon::new(IconName::ZedPredictUp)
8228 },
8229 )
8230 .child(Label::new("Jump to Edit")),
8231 ),
8232
8233 InlineCompletion::Edit {
8234 edits,
8235 edit_preview,
8236 snapshot,
8237 display_mode: _,
8238 } => {
8239 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8240
8241 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8242 &snapshot,
8243 &edits,
8244 edit_preview.as_ref()?,
8245 true,
8246 cx,
8247 )
8248 .first_line_preview();
8249
8250 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8251 .with_default_highlights(&style.text, highlighted_edits.highlights);
8252
8253 let preview = h_flex()
8254 .gap_1()
8255 .min_w_16()
8256 .child(styled_text)
8257 .when(has_more_lines, |parent| parent.child("…"));
8258
8259 let left = if first_edit_row != cursor_point.row {
8260 render_relative_row_jump("", cursor_point.row, first_edit_row)
8261 .into_any_element()
8262 } else {
8263 Icon::new(IconName::ZedPredict).into_any_element()
8264 };
8265
8266 Some(
8267 h_flex()
8268 .h_full()
8269 .flex_1()
8270 .gap_2()
8271 .pr_1()
8272 .overflow_x_hidden()
8273 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8274 .child(left)
8275 .child(preview),
8276 )
8277 }
8278 }
8279 }
8280
8281 fn render_context_menu(
8282 &self,
8283 style: &EditorStyle,
8284 max_height_in_lines: u32,
8285 window: &mut Window,
8286 cx: &mut Context<Editor>,
8287 ) -> Option<AnyElement> {
8288 let menu = self.context_menu.borrow();
8289 let menu = menu.as_ref()?;
8290 if !menu.visible() {
8291 return None;
8292 };
8293 Some(menu.render(style, max_height_in_lines, window, cx))
8294 }
8295
8296 fn render_context_menu_aside(
8297 &mut self,
8298 max_size: Size<Pixels>,
8299 window: &mut Window,
8300 cx: &mut Context<Editor>,
8301 ) -> Option<AnyElement> {
8302 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8303 if menu.visible() {
8304 menu.render_aside(self, max_size, window, cx)
8305 } else {
8306 None
8307 }
8308 })
8309 }
8310
8311 fn hide_context_menu(
8312 &mut self,
8313 window: &mut Window,
8314 cx: &mut Context<Self>,
8315 ) -> Option<CodeContextMenu> {
8316 cx.notify();
8317 self.completion_tasks.clear();
8318 let context_menu = self.context_menu.borrow_mut().take();
8319 self.stale_inline_completion_in_menu.take();
8320 self.update_visible_inline_completion(window, cx);
8321 context_menu
8322 }
8323
8324 fn show_snippet_choices(
8325 &mut self,
8326 choices: &Vec<String>,
8327 selection: Range<Anchor>,
8328 cx: &mut Context<Self>,
8329 ) {
8330 if selection.start.buffer_id.is_none() {
8331 return;
8332 }
8333 let buffer_id = selection.start.buffer_id.unwrap();
8334 let buffer = self.buffer().read(cx).buffer(buffer_id);
8335 let id = post_inc(&mut self.next_completion_id);
8336 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8337
8338 if let Some(buffer) = buffer {
8339 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8340 CompletionsMenu::new_snippet_choices(
8341 id,
8342 true,
8343 choices,
8344 selection,
8345 buffer,
8346 snippet_sort_order,
8347 ),
8348 ));
8349 }
8350 }
8351
8352 pub fn insert_snippet(
8353 &mut self,
8354 insertion_ranges: &[Range<usize>],
8355 snippet: Snippet,
8356 window: &mut Window,
8357 cx: &mut Context<Self>,
8358 ) -> Result<()> {
8359 struct Tabstop<T> {
8360 is_end_tabstop: bool,
8361 ranges: Vec<Range<T>>,
8362 choices: Option<Vec<String>>,
8363 }
8364
8365 let tabstops = self.buffer.update(cx, |buffer, cx| {
8366 let snippet_text: Arc<str> = snippet.text.clone().into();
8367 let edits = insertion_ranges
8368 .iter()
8369 .cloned()
8370 .map(|range| (range, snippet_text.clone()));
8371 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8372
8373 let snapshot = &*buffer.read(cx);
8374 let snippet = &snippet;
8375 snippet
8376 .tabstops
8377 .iter()
8378 .map(|tabstop| {
8379 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8380 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8381 });
8382 let mut tabstop_ranges = tabstop
8383 .ranges
8384 .iter()
8385 .flat_map(|tabstop_range| {
8386 let mut delta = 0_isize;
8387 insertion_ranges.iter().map(move |insertion_range| {
8388 let insertion_start = insertion_range.start as isize + delta;
8389 delta +=
8390 snippet.text.len() as isize - insertion_range.len() as isize;
8391
8392 let start = ((insertion_start + tabstop_range.start) as usize)
8393 .min(snapshot.len());
8394 let end = ((insertion_start + tabstop_range.end) as usize)
8395 .min(snapshot.len());
8396 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8397 })
8398 })
8399 .collect::<Vec<_>>();
8400 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8401
8402 Tabstop {
8403 is_end_tabstop,
8404 ranges: tabstop_ranges,
8405 choices: tabstop.choices.clone(),
8406 }
8407 })
8408 .collect::<Vec<_>>()
8409 });
8410 if let Some(tabstop) = tabstops.first() {
8411 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8412 s.select_ranges(tabstop.ranges.iter().cloned());
8413 });
8414
8415 if let Some(choices) = &tabstop.choices {
8416 if let Some(selection) = tabstop.ranges.first() {
8417 self.show_snippet_choices(choices, selection.clone(), cx)
8418 }
8419 }
8420
8421 // If we're already at the last tabstop and it's at the end of the snippet,
8422 // we're done, we don't need to keep the state around.
8423 if !tabstop.is_end_tabstop {
8424 let choices = tabstops
8425 .iter()
8426 .map(|tabstop| tabstop.choices.clone())
8427 .collect();
8428
8429 let ranges = tabstops
8430 .into_iter()
8431 .map(|tabstop| tabstop.ranges)
8432 .collect::<Vec<_>>();
8433
8434 self.snippet_stack.push(SnippetState {
8435 active_index: 0,
8436 ranges,
8437 choices,
8438 });
8439 }
8440
8441 // Check whether the just-entered snippet ends with an auto-closable bracket.
8442 if self.autoclose_regions.is_empty() {
8443 let snapshot = self.buffer.read(cx).snapshot(cx);
8444 for selection in &mut self.selections.all::<Point>(cx) {
8445 let selection_head = selection.head();
8446 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8447 continue;
8448 };
8449
8450 let mut bracket_pair = None;
8451 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8452 let prev_chars = snapshot
8453 .reversed_chars_at(selection_head)
8454 .collect::<String>();
8455 for (pair, enabled) in scope.brackets() {
8456 if enabled
8457 && pair.close
8458 && prev_chars.starts_with(pair.start.as_str())
8459 && next_chars.starts_with(pair.end.as_str())
8460 {
8461 bracket_pair = Some(pair.clone());
8462 break;
8463 }
8464 }
8465 if let Some(pair) = bracket_pair {
8466 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8467 let autoclose_enabled =
8468 self.use_autoclose && snapshot_settings.use_autoclose;
8469 if autoclose_enabled {
8470 let start = snapshot.anchor_after(selection_head);
8471 let end = snapshot.anchor_after(selection_head);
8472 self.autoclose_regions.push(AutocloseRegion {
8473 selection_id: selection.id,
8474 range: start..end,
8475 pair,
8476 });
8477 }
8478 }
8479 }
8480 }
8481 }
8482 Ok(())
8483 }
8484
8485 pub fn move_to_next_snippet_tabstop(
8486 &mut self,
8487 window: &mut Window,
8488 cx: &mut Context<Self>,
8489 ) -> bool {
8490 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8491 }
8492
8493 pub fn move_to_prev_snippet_tabstop(
8494 &mut self,
8495 window: &mut Window,
8496 cx: &mut Context<Self>,
8497 ) -> bool {
8498 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8499 }
8500
8501 pub fn move_to_snippet_tabstop(
8502 &mut self,
8503 bias: Bias,
8504 window: &mut Window,
8505 cx: &mut Context<Self>,
8506 ) -> bool {
8507 if let Some(mut snippet) = self.snippet_stack.pop() {
8508 match bias {
8509 Bias::Left => {
8510 if snippet.active_index > 0 {
8511 snippet.active_index -= 1;
8512 } else {
8513 self.snippet_stack.push(snippet);
8514 return false;
8515 }
8516 }
8517 Bias::Right => {
8518 if snippet.active_index + 1 < snippet.ranges.len() {
8519 snippet.active_index += 1;
8520 } else {
8521 self.snippet_stack.push(snippet);
8522 return false;
8523 }
8524 }
8525 }
8526 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8527 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8528 s.select_anchor_ranges(current_ranges.iter().cloned())
8529 });
8530
8531 if let Some(choices) = &snippet.choices[snippet.active_index] {
8532 if let Some(selection) = current_ranges.first() {
8533 self.show_snippet_choices(&choices, selection.clone(), cx);
8534 }
8535 }
8536
8537 // If snippet state is not at the last tabstop, push it back on the stack
8538 if snippet.active_index + 1 < snippet.ranges.len() {
8539 self.snippet_stack.push(snippet);
8540 }
8541 return true;
8542 }
8543 }
8544
8545 false
8546 }
8547
8548 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8549 self.transact(window, cx, |this, window, cx| {
8550 this.select_all(&SelectAll, window, cx);
8551 this.insert("", window, cx);
8552 });
8553 }
8554
8555 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8556 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8557 self.transact(window, cx, |this, window, cx| {
8558 this.select_autoclose_pair(window, cx);
8559 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8560 if !this.linked_edit_ranges.is_empty() {
8561 let selections = this.selections.all::<MultiBufferPoint>(cx);
8562 let snapshot = this.buffer.read(cx).snapshot(cx);
8563
8564 for selection in selections.iter() {
8565 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8566 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8567 if selection_start.buffer_id != selection_end.buffer_id {
8568 continue;
8569 }
8570 if let Some(ranges) =
8571 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8572 {
8573 for (buffer, entries) in ranges {
8574 linked_ranges.entry(buffer).or_default().extend(entries);
8575 }
8576 }
8577 }
8578 }
8579
8580 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8581 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8582 for selection in &mut selections {
8583 if selection.is_empty() {
8584 let old_head = selection.head();
8585 let mut new_head =
8586 movement::left(&display_map, old_head.to_display_point(&display_map))
8587 .to_point(&display_map);
8588 if let Some((buffer, line_buffer_range)) = display_map
8589 .buffer_snapshot
8590 .buffer_line_for_row(MultiBufferRow(old_head.row))
8591 {
8592 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8593 let indent_len = match indent_size.kind {
8594 IndentKind::Space => {
8595 buffer.settings_at(line_buffer_range.start, cx).tab_size
8596 }
8597 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8598 };
8599 if old_head.column <= indent_size.len && old_head.column > 0 {
8600 let indent_len = indent_len.get();
8601 new_head = cmp::min(
8602 new_head,
8603 MultiBufferPoint::new(
8604 old_head.row,
8605 ((old_head.column - 1) / indent_len) * indent_len,
8606 ),
8607 );
8608 }
8609 }
8610
8611 selection.set_head(new_head, SelectionGoal::None);
8612 }
8613 }
8614
8615 this.signature_help_state.set_backspace_pressed(true);
8616 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8617 s.select(selections)
8618 });
8619 this.insert("", window, cx);
8620 let empty_str: Arc<str> = Arc::from("");
8621 for (buffer, edits) in linked_ranges {
8622 let snapshot = buffer.read(cx).snapshot();
8623 use text::ToPoint as TP;
8624
8625 let edits = edits
8626 .into_iter()
8627 .map(|range| {
8628 let end_point = TP::to_point(&range.end, &snapshot);
8629 let mut start_point = TP::to_point(&range.start, &snapshot);
8630
8631 if end_point == start_point {
8632 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8633 .saturating_sub(1);
8634 start_point =
8635 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8636 };
8637
8638 (start_point..end_point, empty_str.clone())
8639 })
8640 .sorted_by_key(|(range, _)| range.start)
8641 .collect::<Vec<_>>();
8642 buffer.update(cx, |this, cx| {
8643 this.edit(edits, None, cx);
8644 })
8645 }
8646 this.refresh_inline_completion(true, false, window, cx);
8647 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8648 });
8649 }
8650
8651 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8652 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8653 self.transact(window, cx, |this, window, cx| {
8654 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8655 s.move_with(|map, selection| {
8656 if selection.is_empty() {
8657 let cursor = movement::right(map, selection.head());
8658 selection.end = cursor;
8659 selection.reversed = true;
8660 selection.goal = SelectionGoal::None;
8661 }
8662 })
8663 });
8664 this.insert("", window, cx);
8665 this.refresh_inline_completion(true, false, window, cx);
8666 });
8667 }
8668
8669 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8670 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8671 if self.move_to_prev_snippet_tabstop(window, cx) {
8672 return;
8673 }
8674 self.outdent(&Outdent, window, cx);
8675 }
8676
8677 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8678 if self.move_to_next_snippet_tabstop(window, cx) {
8679 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8680 return;
8681 }
8682 if self.read_only(cx) {
8683 return;
8684 }
8685 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8686 let mut selections = self.selections.all_adjusted(cx);
8687 let buffer = self.buffer.read(cx);
8688 let snapshot = buffer.snapshot(cx);
8689 let rows_iter = selections.iter().map(|s| s.head().row);
8690 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8691
8692 let has_some_cursor_in_whitespace = selections
8693 .iter()
8694 .filter(|selection| selection.is_empty())
8695 .any(|selection| {
8696 let cursor = selection.head();
8697 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8698 cursor.column < current_indent.len
8699 });
8700
8701 let mut edits = Vec::new();
8702 let mut prev_edited_row = 0;
8703 let mut row_delta = 0;
8704 for selection in &mut selections {
8705 if selection.start.row != prev_edited_row {
8706 row_delta = 0;
8707 }
8708 prev_edited_row = selection.end.row;
8709
8710 // If the selection is non-empty, then increase the indentation of the selected lines.
8711 if !selection.is_empty() {
8712 row_delta =
8713 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8714 continue;
8715 }
8716
8717 // If the selection is empty and the cursor is in the leading whitespace before the
8718 // suggested indentation, then auto-indent the line.
8719 let cursor = selection.head();
8720 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8721 if let Some(suggested_indent) =
8722 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8723 {
8724 // If there exist any empty selection in the leading whitespace, then skip
8725 // indent for selections at the boundary.
8726 if has_some_cursor_in_whitespace
8727 && cursor.column == current_indent.len
8728 && current_indent.len == suggested_indent.len
8729 {
8730 continue;
8731 }
8732
8733 if cursor.column < suggested_indent.len
8734 && cursor.column <= current_indent.len
8735 && current_indent.len <= suggested_indent.len
8736 {
8737 selection.start = Point::new(cursor.row, suggested_indent.len);
8738 selection.end = selection.start;
8739 if row_delta == 0 {
8740 edits.extend(Buffer::edit_for_indent_size_adjustment(
8741 cursor.row,
8742 current_indent,
8743 suggested_indent,
8744 ));
8745 row_delta = suggested_indent.len - current_indent.len;
8746 }
8747 continue;
8748 }
8749 }
8750
8751 // Otherwise, insert a hard or soft tab.
8752 let settings = buffer.language_settings_at(cursor, cx);
8753 let tab_size = if settings.hard_tabs {
8754 IndentSize::tab()
8755 } else {
8756 let tab_size = settings.tab_size.get();
8757 let indent_remainder = snapshot
8758 .text_for_range(Point::new(cursor.row, 0)..cursor)
8759 .flat_map(str::chars)
8760 .fold(row_delta % tab_size, |counter: u32, c| {
8761 if c == '\t' {
8762 0
8763 } else {
8764 (counter + 1) % tab_size
8765 }
8766 });
8767
8768 let chars_to_next_tab_stop = tab_size - indent_remainder;
8769 IndentSize::spaces(chars_to_next_tab_stop)
8770 };
8771 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
8772 selection.end = selection.start;
8773 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
8774 row_delta += tab_size.len;
8775 }
8776
8777 self.transact(window, cx, |this, window, cx| {
8778 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8779 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8780 s.select(selections)
8781 });
8782 this.refresh_inline_completion(true, false, window, cx);
8783 });
8784 }
8785
8786 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
8787 if self.read_only(cx) {
8788 return;
8789 }
8790 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8791 let mut selections = self.selections.all::<Point>(cx);
8792 let mut prev_edited_row = 0;
8793 let mut row_delta = 0;
8794 let mut edits = Vec::new();
8795 let buffer = self.buffer.read(cx);
8796 let snapshot = buffer.snapshot(cx);
8797 for selection in &mut selections {
8798 if selection.start.row != prev_edited_row {
8799 row_delta = 0;
8800 }
8801 prev_edited_row = selection.end.row;
8802
8803 row_delta =
8804 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8805 }
8806
8807 self.transact(window, cx, |this, window, cx| {
8808 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8809 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8810 s.select(selections)
8811 });
8812 });
8813 }
8814
8815 fn indent_selection(
8816 buffer: &MultiBuffer,
8817 snapshot: &MultiBufferSnapshot,
8818 selection: &mut Selection<Point>,
8819 edits: &mut Vec<(Range<Point>, String)>,
8820 delta_for_start_row: u32,
8821 cx: &App,
8822 ) -> u32 {
8823 let settings = buffer.language_settings_at(selection.start, cx);
8824 let tab_size = settings.tab_size.get();
8825 let indent_kind = if settings.hard_tabs {
8826 IndentKind::Tab
8827 } else {
8828 IndentKind::Space
8829 };
8830 let mut start_row = selection.start.row;
8831 let mut end_row = selection.end.row + 1;
8832
8833 // If a selection ends at the beginning of a line, don't indent
8834 // that last line.
8835 if selection.end.column == 0 && selection.end.row > selection.start.row {
8836 end_row -= 1;
8837 }
8838
8839 // Avoid re-indenting a row that has already been indented by a
8840 // previous selection, but still update this selection's column
8841 // to reflect that indentation.
8842 if delta_for_start_row > 0 {
8843 start_row += 1;
8844 selection.start.column += delta_for_start_row;
8845 if selection.end.row == selection.start.row {
8846 selection.end.column += delta_for_start_row;
8847 }
8848 }
8849
8850 let mut delta_for_end_row = 0;
8851 let has_multiple_rows = start_row + 1 != end_row;
8852 for row in start_row..end_row {
8853 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
8854 let indent_delta = match (current_indent.kind, indent_kind) {
8855 (IndentKind::Space, IndentKind::Space) => {
8856 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
8857 IndentSize::spaces(columns_to_next_tab_stop)
8858 }
8859 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
8860 (_, IndentKind::Tab) => IndentSize::tab(),
8861 };
8862
8863 let start = if has_multiple_rows || current_indent.len < selection.start.column {
8864 0
8865 } else {
8866 selection.start.column
8867 };
8868 let row_start = Point::new(row, start);
8869 edits.push((
8870 row_start..row_start,
8871 indent_delta.chars().collect::<String>(),
8872 ));
8873
8874 // Update this selection's endpoints to reflect the indentation.
8875 if row == selection.start.row {
8876 selection.start.column += indent_delta.len;
8877 }
8878 if row == selection.end.row {
8879 selection.end.column += indent_delta.len;
8880 delta_for_end_row = indent_delta.len;
8881 }
8882 }
8883
8884 if selection.start.row == selection.end.row {
8885 delta_for_start_row + delta_for_end_row
8886 } else {
8887 delta_for_end_row
8888 }
8889 }
8890
8891 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
8892 if self.read_only(cx) {
8893 return;
8894 }
8895 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8896 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8897 let selections = self.selections.all::<Point>(cx);
8898 let mut deletion_ranges = Vec::new();
8899 let mut last_outdent = None;
8900 {
8901 let buffer = self.buffer.read(cx);
8902 let snapshot = buffer.snapshot(cx);
8903 for selection in &selections {
8904 let settings = buffer.language_settings_at(selection.start, cx);
8905 let tab_size = settings.tab_size.get();
8906 let mut rows = selection.spanned_rows(false, &display_map);
8907
8908 // Avoid re-outdenting a row that has already been outdented by a
8909 // previous selection.
8910 if let Some(last_row) = last_outdent {
8911 if last_row == rows.start {
8912 rows.start = rows.start.next_row();
8913 }
8914 }
8915 let has_multiple_rows = rows.len() > 1;
8916 for row in rows.iter_rows() {
8917 let indent_size = snapshot.indent_size_for_line(row);
8918 if indent_size.len > 0 {
8919 let deletion_len = match indent_size.kind {
8920 IndentKind::Space => {
8921 let columns_to_prev_tab_stop = indent_size.len % tab_size;
8922 if columns_to_prev_tab_stop == 0 {
8923 tab_size
8924 } else {
8925 columns_to_prev_tab_stop
8926 }
8927 }
8928 IndentKind::Tab => 1,
8929 };
8930 let start = if has_multiple_rows
8931 || deletion_len > selection.start.column
8932 || indent_size.len < selection.start.column
8933 {
8934 0
8935 } else {
8936 selection.start.column - deletion_len
8937 };
8938 deletion_ranges.push(
8939 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
8940 );
8941 last_outdent = Some(row);
8942 }
8943 }
8944 }
8945 }
8946
8947 self.transact(window, cx, |this, window, cx| {
8948 this.buffer.update(cx, |buffer, cx| {
8949 let empty_str: Arc<str> = Arc::default();
8950 buffer.edit(
8951 deletion_ranges
8952 .into_iter()
8953 .map(|range| (range, empty_str.clone())),
8954 None,
8955 cx,
8956 );
8957 });
8958 let selections = this.selections.all::<usize>(cx);
8959 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8960 s.select(selections)
8961 });
8962 });
8963 }
8964
8965 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
8966 if self.read_only(cx) {
8967 return;
8968 }
8969 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8970 let selections = self
8971 .selections
8972 .all::<usize>(cx)
8973 .into_iter()
8974 .map(|s| s.range());
8975
8976 self.transact(window, cx, |this, window, cx| {
8977 this.buffer.update(cx, |buffer, cx| {
8978 buffer.autoindent_ranges(selections, cx);
8979 });
8980 let selections = this.selections.all::<usize>(cx);
8981 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8982 s.select(selections)
8983 });
8984 });
8985 }
8986
8987 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
8988 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8989 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8990 let selections = self.selections.all::<Point>(cx);
8991
8992 let mut new_cursors = Vec::new();
8993 let mut edit_ranges = Vec::new();
8994 let mut selections = selections.iter().peekable();
8995 while let Some(selection) = selections.next() {
8996 let mut rows = selection.spanned_rows(false, &display_map);
8997 let goal_display_column = selection.head().to_display_point(&display_map).column();
8998
8999 // Accumulate contiguous regions of rows that we want to delete.
9000 while let Some(next_selection) = selections.peek() {
9001 let next_rows = next_selection.spanned_rows(false, &display_map);
9002 if next_rows.start <= rows.end {
9003 rows.end = next_rows.end;
9004 selections.next().unwrap();
9005 } else {
9006 break;
9007 }
9008 }
9009
9010 let buffer = &display_map.buffer_snapshot;
9011 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9012 let edit_end;
9013 let cursor_buffer_row;
9014 if buffer.max_point().row >= rows.end.0 {
9015 // If there's a line after the range, delete the \n from the end of the row range
9016 // and position the cursor on the next line.
9017 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9018 cursor_buffer_row = rows.end;
9019 } else {
9020 // If there isn't a line after the range, delete the \n from the line before the
9021 // start of the row range and position the cursor there.
9022 edit_start = edit_start.saturating_sub(1);
9023 edit_end = buffer.len();
9024 cursor_buffer_row = rows.start.previous_row();
9025 }
9026
9027 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9028 *cursor.column_mut() =
9029 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9030
9031 new_cursors.push((
9032 selection.id,
9033 buffer.anchor_after(cursor.to_point(&display_map)),
9034 ));
9035 edit_ranges.push(edit_start..edit_end);
9036 }
9037
9038 self.transact(window, cx, |this, window, cx| {
9039 let buffer = this.buffer.update(cx, |buffer, cx| {
9040 let empty_str: Arc<str> = Arc::default();
9041 buffer.edit(
9042 edit_ranges
9043 .into_iter()
9044 .map(|range| (range, empty_str.clone())),
9045 None,
9046 cx,
9047 );
9048 buffer.snapshot(cx)
9049 });
9050 let new_selections = new_cursors
9051 .into_iter()
9052 .map(|(id, cursor)| {
9053 let cursor = cursor.to_point(&buffer);
9054 Selection {
9055 id,
9056 start: cursor,
9057 end: cursor,
9058 reversed: false,
9059 goal: SelectionGoal::None,
9060 }
9061 })
9062 .collect();
9063
9064 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9065 s.select(new_selections);
9066 });
9067 });
9068 }
9069
9070 pub fn join_lines_impl(
9071 &mut self,
9072 insert_whitespace: bool,
9073 window: &mut Window,
9074 cx: &mut Context<Self>,
9075 ) {
9076 if self.read_only(cx) {
9077 return;
9078 }
9079 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9080 for selection in self.selections.all::<Point>(cx) {
9081 let start = MultiBufferRow(selection.start.row);
9082 // Treat single line selections as if they include the next line. Otherwise this action
9083 // would do nothing for single line selections individual cursors.
9084 let end = if selection.start.row == selection.end.row {
9085 MultiBufferRow(selection.start.row + 1)
9086 } else {
9087 MultiBufferRow(selection.end.row)
9088 };
9089
9090 if let Some(last_row_range) = row_ranges.last_mut() {
9091 if start <= last_row_range.end {
9092 last_row_range.end = end;
9093 continue;
9094 }
9095 }
9096 row_ranges.push(start..end);
9097 }
9098
9099 let snapshot = self.buffer.read(cx).snapshot(cx);
9100 let mut cursor_positions = Vec::new();
9101 for row_range in &row_ranges {
9102 let anchor = snapshot.anchor_before(Point::new(
9103 row_range.end.previous_row().0,
9104 snapshot.line_len(row_range.end.previous_row()),
9105 ));
9106 cursor_positions.push(anchor..anchor);
9107 }
9108
9109 self.transact(window, cx, |this, window, cx| {
9110 for row_range in row_ranges.into_iter().rev() {
9111 for row in row_range.iter_rows().rev() {
9112 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9113 let next_line_row = row.next_row();
9114 let indent = snapshot.indent_size_for_line(next_line_row);
9115 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9116
9117 let replace =
9118 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9119 " "
9120 } else {
9121 ""
9122 };
9123
9124 this.buffer.update(cx, |buffer, cx| {
9125 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9126 });
9127 }
9128 }
9129
9130 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9131 s.select_anchor_ranges(cursor_positions)
9132 });
9133 });
9134 }
9135
9136 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9137 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9138 self.join_lines_impl(true, window, cx);
9139 }
9140
9141 pub fn sort_lines_case_sensitive(
9142 &mut self,
9143 _: &SortLinesCaseSensitive,
9144 window: &mut Window,
9145 cx: &mut Context<Self>,
9146 ) {
9147 self.manipulate_lines(window, cx, |lines| lines.sort())
9148 }
9149
9150 pub fn sort_lines_case_insensitive(
9151 &mut self,
9152 _: &SortLinesCaseInsensitive,
9153 window: &mut Window,
9154 cx: &mut Context<Self>,
9155 ) {
9156 self.manipulate_lines(window, cx, |lines| {
9157 lines.sort_by_key(|line| line.to_lowercase())
9158 })
9159 }
9160
9161 pub fn unique_lines_case_insensitive(
9162 &mut self,
9163 _: &UniqueLinesCaseInsensitive,
9164 window: &mut Window,
9165 cx: &mut Context<Self>,
9166 ) {
9167 self.manipulate_lines(window, cx, |lines| {
9168 let mut seen = HashSet::default();
9169 lines.retain(|line| seen.insert(line.to_lowercase()));
9170 })
9171 }
9172
9173 pub fn unique_lines_case_sensitive(
9174 &mut self,
9175 _: &UniqueLinesCaseSensitive,
9176 window: &mut Window,
9177 cx: &mut Context<Self>,
9178 ) {
9179 self.manipulate_lines(window, cx, |lines| {
9180 let mut seen = HashSet::default();
9181 lines.retain(|line| seen.insert(*line));
9182 })
9183 }
9184
9185 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9186 let Some(project) = self.project.clone() else {
9187 return;
9188 };
9189 self.reload(project, window, cx)
9190 .detach_and_notify_err(window, cx);
9191 }
9192
9193 pub fn restore_file(
9194 &mut self,
9195 _: &::git::RestoreFile,
9196 window: &mut Window,
9197 cx: &mut Context<Self>,
9198 ) {
9199 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9200 let mut buffer_ids = HashSet::default();
9201 let snapshot = self.buffer().read(cx).snapshot(cx);
9202 for selection in self.selections.all::<usize>(cx) {
9203 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9204 }
9205
9206 let buffer = self.buffer().read(cx);
9207 let ranges = buffer_ids
9208 .into_iter()
9209 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9210 .collect::<Vec<_>>();
9211
9212 self.restore_hunks_in_ranges(ranges, window, cx);
9213 }
9214
9215 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9216 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9217 let selections = self
9218 .selections
9219 .all(cx)
9220 .into_iter()
9221 .map(|s| s.range())
9222 .collect();
9223 self.restore_hunks_in_ranges(selections, window, cx);
9224 }
9225
9226 pub fn restore_hunks_in_ranges(
9227 &mut self,
9228 ranges: Vec<Range<Point>>,
9229 window: &mut Window,
9230 cx: &mut Context<Editor>,
9231 ) {
9232 let mut revert_changes = HashMap::default();
9233 let chunk_by = self
9234 .snapshot(window, cx)
9235 .hunks_for_ranges(ranges)
9236 .into_iter()
9237 .chunk_by(|hunk| hunk.buffer_id);
9238 for (buffer_id, hunks) in &chunk_by {
9239 let hunks = hunks.collect::<Vec<_>>();
9240 for hunk in &hunks {
9241 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9242 }
9243 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9244 }
9245 drop(chunk_by);
9246 if !revert_changes.is_empty() {
9247 self.transact(window, cx, |editor, window, cx| {
9248 editor.restore(revert_changes, window, cx);
9249 });
9250 }
9251 }
9252
9253 pub fn open_active_item_in_terminal(
9254 &mut self,
9255 _: &OpenInTerminal,
9256 window: &mut Window,
9257 cx: &mut Context<Self>,
9258 ) {
9259 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9260 let project_path = buffer.read(cx).project_path(cx)?;
9261 let project = self.project.as_ref()?.read(cx);
9262 let entry = project.entry_for_path(&project_path, cx)?;
9263 let parent = match &entry.canonical_path {
9264 Some(canonical_path) => canonical_path.to_path_buf(),
9265 None => project.absolute_path(&project_path, cx)?,
9266 }
9267 .parent()?
9268 .to_path_buf();
9269 Some(parent)
9270 }) {
9271 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9272 }
9273 }
9274
9275 fn set_breakpoint_context_menu(
9276 &mut self,
9277 display_row: DisplayRow,
9278 position: Option<Anchor>,
9279 clicked_point: gpui::Point<Pixels>,
9280 window: &mut Window,
9281 cx: &mut Context<Self>,
9282 ) {
9283 if !cx.has_flag::<DebuggerFeatureFlag>() {
9284 return;
9285 }
9286 let source = self
9287 .buffer
9288 .read(cx)
9289 .snapshot(cx)
9290 .anchor_before(Point::new(display_row.0, 0u32));
9291
9292 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9293
9294 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9295 self,
9296 source,
9297 clicked_point,
9298 context_menu,
9299 window,
9300 cx,
9301 );
9302 }
9303
9304 fn add_edit_breakpoint_block(
9305 &mut self,
9306 anchor: Anchor,
9307 breakpoint: &Breakpoint,
9308 edit_action: BreakpointPromptEditAction,
9309 window: &mut Window,
9310 cx: &mut Context<Self>,
9311 ) {
9312 let weak_editor = cx.weak_entity();
9313 let bp_prompt = cx.new(|cx| {
9314 BreakpointPromptEditor::new(
9315 weak_editor,
9316 anchor,
9317 breakpoint.clone(),
9318 edit_action,
9319 window,
9320 cx,
9321 )
9322 });
9323
9324 let height = bp_prompt.update(cx, |this, cx| {
9325 this.prompt
9326 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9327 });
9328 let cloned_prompt = bp_prompt.clone();
9329 let blocks = vec![BlockProperties {
9330 style: BlockStyle::Sticky,
9331 placement: BlockPlacement::Above(anchor),
9332 height: Some(height),
9333 render: Arc::new(move |cx| {
9334 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9335 cloned_prompt.clone().into_any_element()
9336 }),
9337 priority: 0,
9338 render_in_minimap: true,
9339 }];
9340
9341 let focus_handle = bp_prompt.focus_handle(cx);
9342 window.focus(&focus_handle);
9343
9344 let block_ids = self.insert_blocks(blocks, None, cx);
9345 bp_prompt.update(cx, |prompt, _| {
9346 prompt.add_block_ids(block_ids);
9347 });
9348 }
9349
9350 pub(crate) fn breakpoint_at_row(
9351 &self,
9352 row: u32,
9353 window: &mut Window,
9354 cx: &mut Context<Self>,
9355 ) -> Option<(Anchor, Breakpoint)> {
9356 let snapshot = self.snapshot(window, cx);
9357 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9358
9359 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9360 }
9361
9362 pub(crate) fn breakpoint_at_anchor(
9363 &self,
9364 breakpoint_position: Anchor,
9365 snapshot: &EditorSnapshot,
9366 cx: &mut Context<Self>,
9367 ) -> Option<(Anchor, Breakpoint)> {
9368 let project = self.project.clone()?;
9369
9370 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9371 snapshot
9372 .buffer_snapshot
9373 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9374 })?;
9375
9376 let enclosing_excerpt = breakpoint_position.excerpt_id;
9377 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
9378 let buffer_snapshot = buffer.read(cx).snapshot();
9379
9380 let row = buffer_snapshot
9381 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9382 .row;
9383
9384 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9385 let anchor_end = snapshot
9386 .buffer_snapshot
9387 .anchor_after(Point::new(row, line_len));
9388
9389 let bp = self
9390 .breakpoint_store
9391 .as_ref()?
9392 .read_with(cx, |breakpoint_store, cx| {
9393 breakpoint_store
9394 .breakpoints(
9395 &buffer,
9396 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9397 &buffer_snapshot,
9398 cx,
9399 )
9400 .next()
9401 .and_then(|(anchor, bp)| {
9402 let breakpoint_row = buffer_snapshot
9403 .summary_for_anchor::<text::PointUtf16>(anchor)
9404 .row;
9405
9406 if breakpoint_row == row {
9407 snapshot
9408 .buffer_snapshot
9409 .anchor_in_excerpt(enclosing_excerpt, *anchor)
9410 .map(|anchor| (anchor, bp.clone()))
9411 } else {
9412 None
9413 }
9414 })
9415 });
9416 bp
9417 }
9418
9419 pub fn edit_log_breakpoint(
9420 &mut self,
9421 _: &EditLogBreakpoint,
9422 window: &mut Window,
9423 cx: &mut Context<Self>,
9424 ) {
9425 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9426 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9427 message: None,
9428 state: BreakpointState::Enabled,
9429 condition: None,
9430 hit_condition: None,
9431 });
9432
9433 self.add_edit_breakpoint_block(
9434 anchor,
9435 &breakpoint,
9436 BreakpointPromptEditAction::Log,
9437 window,
9438 cx,
9439 );
9440 }
9441 }
9442
9443 fn breakpoints_at_cursors(
9444 &self,
9445 window: &mut Window,
9446 cx: &mut Context<Self>,
9447 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9448 let snapshot = self.snapshot(window, cx);
9449 let cursors = self
9450 .selections
9451 .disjoint_anchors()
9452 .into_iter()
9453 .map(|selection| {
9454 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9455
9456 let breakpoint_position = self
9457 .breakpoint_at_row(cursor_position.row, window, cx)
9458 .map(|bp| bp.0)
9459 .unwrap_or_else(|| {
9460 snapshot
9461 .display_snapshot
9462 .buffer_snapshot
9463 .anchor_after(Point::new(cursor_position.row, 0))
9464 });
9465
9466 let breakpoint = self
9467 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9468 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9469
9470 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9471 })
9472 // 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.
9473 .collect::<HashMap<Anchor, _>>();
9474
9475 cursors.into_iter().collect()
9476 }
9477
9478 pub fn enable_breakpoint(
9479 &mut self,
9480 _: &crate::actions::EnableBreakpoint,
9481 window: &mut Window,
9482 cx: &mut Context<Self>,
9483 ) {
9484 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9485 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9486 continue;
9487 };
9488 self.edit_breakpoint_at_anchor(
9489 anchor,
9490 breakpoint,
9491 BreakpointEditAction::InvertState,
9492 cx,
9493 );
9494 }
9495 }
9496
9497 pub fn disable_breakpoint(
9498 &mut self,
9499 _: &crate::actions::DisableBreakpoint,
9500 window: &mut Window,
9501 cx: &mut Context<Self>,
9502 ) {
9503 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9504 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9505 continue;
9506 };
9507 self.edit_breakpoint_at_anchor(
9508 anchor,
9509 breakpoint,
9510 BreakpointEditAction::InvertState,
9511 cx,
9512 );
9513 }
9514 }
9515
9516 pub fn toggle_breakpoint(
9517 &mut self,
9518 _: &crate::actions::ToggleBreakpoint,
9519 window: &mut Window,
9520 cx: &mut Context<Self>,
9521 ) {
9522 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9523 if let Some(breakpoint) = breakpoint {
9524 self.edit_breakpoint_at_anchor(
9525 anchor,
9526 breakpoint,
9527 BreakpointEditAction::Toggle,
9528 cx,
9529 );
9530 } else {
9531 self.edit_breakpoint_at_anchor(
9532 anchor,
9533 Breakpoint::new_standard(),
9534 BreakpointEditAction::Toggle,
9535 cx,
9536 );
9537 }
9538 }
9539 }
9540
9541 pub fn edit_breakpoint_at_anchor(
9542 &mut self,
9543 breakpoint_position: Anchor,
9544 breakpoint: Breakpoint,
9545 edit_action: BreakpointEditAction,
9546 cx: &mut Context<Self>,
9547 ) {
9548 let Some(breakpoint_store) = &self.breakpoint_store else {
9549 return;
9550 };
9551
9552 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9553 if breakpoint_position == Anchor::min() {
9554 self.buffer()
9555 .read(cx)
9556 .excerpt_buffer_ids()
9557 .into_iter()
9558 .next()
9559 } else {
9560 None
9561 }
9562 }) else {
9563 return;
9564 };
9565
9566 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9567 return;
9568 };
9569
9570 breakpoint_store.update(cx, |breakpoint_store, cx| {
9571 breakpoint_store.toggle_breakpoint(
9572 buffer,
9573 (breakpoint_position.text_anchor, breakpoint),
9574 edit_action,
9575 cx,
9576 );
9577 });
9578
9579 cx.notify();
9580 }
9581
9582 #[cfg(any(test, feature = "test-support"))]
9583 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9584 self.breakpoint_store.clone()
9585 }
9586
9587 pub fn prepare_restore_change(
9588 &self,
9589 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9590 hunk: &MultiBufferDiffHunk,
9591 cx: &mut App,
9592 ) -> Option<()> {
9593 if hunk.is_created_file() {
9594 return None;
9595 }
9596 let buffer = self.buffer.read(cx);
9597 let diff = buffer.diff_for(hunk.buffer_id)?;
9598 let buffer = buffer.buffer(hunk.buffer_id)?;
9599 let buffer = buffer.read(cx);
9600 let original_text = diff
9601 .read(cx)
9602 .base_text()
9603 .as_rope()
9604 .slice(hunk.diff_base_byte_range.clone());
9605 let buffer_snapshot = buffer.snapshot();
9606 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9607 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9608 probe
9609 .0
9610 .start
9611 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9612 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9613 }) {
9614 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9615 Some(())
9616 } else {
9617 None
9618 }
9619 }
9620
9621 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9622 self.manipulate_lines(window, cx, |lines| lines.reverse())
9623 }
9624
9625 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9626 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9627 }
9628
9629 fn manipulate_lines<Fn>(
9630 &mut self,
9631 window: &mut Window,
9632 cx: &mut Context<Self>,
9633 mut callback: Fn,
9634 ) where
9635 Fn: FnMut(&mut Vec<&str>),
9636 {
9637 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9638
9639 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9640 let buffer = self.buffer.read(cx).snapshot(cx);
9641
9642 let mut edits = Vec::new();
9643
9644 let selections = self.selections.all::<Point>(cx);
9645 let mut selections = selections.iter().peekable();
9646 let mut contiguous_row_selections = Vec::new();
9647 let mut new_selections = Vec::new();
9648 let mut added_lines = 0;
9649 let mut removed_lines = 0;
9650
9651 while let Some(selection) = selections.next() {
9652 let (start_row, end_row) = consume_contiguous_rows(
9653 &mut contiguous_row_selections,
9654 selection,
9655 &display_map,
9656 &mut selections,
9657 );
9658
9659 let start_point = Point::new(start_row.0, 0);
9660 let end_point = Point::new(
9661 end_row.previous_row().0,
9662 buffer.line_len(end_row.previous_row()),
9663 );
9664 let text = buffer
9665 .text_for_range(start_point..end_point)
9666 .collect::<String>();
9667
9668 let mut lines = text.split('\n').collect_vec();
9669
9670 let lines_before = lines.len();
9671 callback(&mut lines);
9672 let lines_after = lines.len();
9673
9674 edits.push((start_point..end_point, lines.join("\n")));
9675
9676 // Selections must change based on added and removed line count
9677 let start_row =
9678 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9679 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9680 new_selections.push(Selection {
9681 id: selection.id,
9682 start: start_row,
9683 end: end_row,
9684 goal: SelectionGoal::None,
9685 reversed: selection.reversed,
9686 });
9687
9688 if lines_after > lines_before {
9689 added_lines += lines_after - lines_before;
9690 } else if lines_before > lines_after {
9691 removed_lines += lines_before - lines_after;
9692 }
9693 }
9694
9695 self.transact(window, cx, |this, window, cx| {
9696 let buffer = this.buffer.update(cx, |buffer, cx| {
9697 buffer.edit(edits, None, cx);
9698 buffer.snapshot(cx)
9699 });
9700
9701 // Recalculate offsets on newly edited buffer
9702 let new_selections = new_selections
9703 .iter()
9704 .map(|s| {
9705 let start_point = Point::new(s.start.0, 0);
9706 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9707 Selection {
9708 id: s.id,
9709 start: buffer.point_to_offset(start_point),
9710 end: buffer.point_to_offset(end_point),
9711 goal: s.goal,
9712 reversed: s.reversed,
9713 }
9714 })
9715 .collect();
9716
9717 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9718 s.select(new_selections);
9719 });
9720
9721 this.request_autoscroll(Autoscroll::fit(), cx);
9722 });
9723 }
9724
9725 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
9726 self.manipulate_text(window, cx, |text| {
9727 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
9728 if has_upper_case_characters {
9729 text.to_lowercase()
9730 } else {
9731 text.to_uppercase()
9732 }
9733 })
9734 }
9735
9736 pub fn convert_to_upper_case(
9737 &mut self,
9738 _: &ConvertToUpperCase,
9739 window: &mut Window,
9740 cx: &mut Context<Self>,
9741 ) {
9742 self.manipulate_text(window, cx, |text| text.to_uppercase())
9743 }
9744
9745 pub fn convert_to_lower_case(
9746 &mut self,
9747 _: &ConvertToLowerCase,
9748 window: &mut Window,
9749 cx: &mut Context<Self>,
9750 ) {
9751 self.manipulate_text(window, cx, |text| text.to_lowercase())
9752 }
9753
9754 pub fn convert_to_title_case(
9755 &mut self,
9756 _: &ConvertToTitleCase,
9757 window: &mut Window,
9758 cx: &mut Context<Self>,
9759 ) {
9760 self.manipulate_text(window, cx, |text| {
9761 text.split('\n')
9762 .map(|line| line.to_case(Case::Title))
9763 .join("\n")
9764 })
9765 }
9766
9767 pub fn convert_to_snake_case(
9768 &mut self,
9769 _: &ConvertToSnakeCase,
9770 window: &mut Window,
9771 cx: &mut Context<Self>,
9772 ) {
9773 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
9774 }
9775
9776 pub fn convert_to_kebab_case(
9777 &mut self,
9778 _: &ConvertToKebabCase,
9779 window: &mut Window,
9780 cx: &mut Context<Self>,
9781 ) {
9782 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
9783 }
9784
9785 pub fn convert_to_upper_camel_case(
9786 &mut self,
9787 _: &ConvertToUpperCamelCase,
9788 window: &mut Window,
9789 cx: &mut Context<Self>,
9790 ) {
9791 self.manipulate_text(window, cx, |text| {
9792 text.split('\n')
9793 .map(|line| line.to_case(Case::UpperCamel))
9794 .join("\n")
9795 })
9796 }
9797
9798 pub fn convert_to_lower_camel_case(
9799 &mut self,
9800 _: &ConvertToLowerCamelCase,
9801 window: &mut Window,
9802 cx: &mut Context<Self>,
9803 ) {
9804 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
9805 }
9806
9807 pub fn convert_to_opposite_case(
9808 &mut self,
9809 _: &ConvertToOppositeCase,
9810 window: &mut Window,
9811 cx: &mut Context<Self>,
9812 ) {
9813 self.manipulate_text(window, cx, |text| {
9814 text.chars()
9815 .fold(String::with_capacity(text.len()), |mut t, c| {
9816 if c.is_uppercase() {
9817 t.extend(c.to_lowercase());
9818 } else {
9819 t.extend(c.to_uppercase());
9820 }
9821 t
9822 })
9823 })
9824 }
9825
9826 pub fn convert_to_rot13(
9827 &mut self,
9828 _: &ConvertToRot13,
9829 window: &mut Window,
9830 cx: &mut Context<Self>,
9831 ) {
9832 self.manipulate_text(window, cx, |text| {
9833 text.chars()
9834 .map(|c| match c {
9835 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
9836 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
9837 _ => c,
9838 })
9839 .collect()
9840 })
9841 }
9842
9843 pub fn convert_to_rot47(
9844 &mut self,
9845 _: &ConvertToRot47,
9846 window: &mut Window,
9847 cx: &mut Context<Self>,
9848 ) {
9849 self.manipulate_text(window, cx, |text| {
9850 text.chars()
9851 .map(|c| {
9852 let code_point = c as u32;
9853 if code_point >= 33 && code_point <= 126 {
9854 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
9855 }
9856 c
9857 })
9858 .collect()
9859 })
9860 }
9861
9862 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
9863 where
9864 Fn: FnMut(&str) -> String,
9865 {
9866 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9867 let buffer = self.buffer.read(cx).snapshot(cx);
9868
9869 let mut new_selections = Vec::new();
9870 let mut edits = Vec::new();
9871 let mut selection_adjustment = 0i32;
9872
9873 for selection in self.selections.all::<usize>(cx) {
9874 let selection_is_empty = selection.is_empty();
9875
9876 let (start, end) = if selection_is_empty {
9877 let word_range = movement::surrounding_word(
9878 &display_map,
9879 selection.start.to_display_point(&display_map),
9880 );
9881 let start = word_range.start.to_offset(&display_map, Bias::Left);
9882 let end = word_range.end.to_offset(&display_map, Bias::Left);
9883 (start, end)
9884 } else {
9885 (selection.start, selection.end)
9886 };
9887
9888 let text = buffer.text_for_range(start..end).collect::<String>();
9889 let old_length = text.len() as i32;
9890 let text = callback(&text);
9891
9892 new_selections.push(Selection {
9893 start: (start as i32 - selection_adjustment) as usize,
9894 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
9895 goal: SelectionGoal::None,
9896 ..selection
9897 });
9898
9899 selection_adjustment += old_length - text.len() as i32;
9900
9901 edits.push((start..end, text));
9902 }
9903
9904 self.transact(window, cx, |this, window, cx| {
9905 this.buffer.update(cx, |buffer, cx| {
9906 buffer.edit(edits, None, cx);
9907 });
9908
9909 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9910 s.select(new_selections);
9911 });
9912
9913 this.request_autoscroll(Autoscroll::fit(), cx);
9914 });
9915 }
9916
9917 pub fn duplicate(
9918 &mut self,
9919 upwards: bool,
9920 whole_lines: bool,
9921 window: &mut Window,
9922 cx: &mut Context<Self>,
9923 ) {
9924 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9925
9926 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9927 let buffer = &display_map.buffer_snapshot;
9928 let selections = self.selections.all::<Point>(cx);
9929
9930 let mut edits = Vec::new();
9931 let mut selections_iter = selections.iter().peekable();
9932 while let Some(selection) = selections_iter.next() {
9933 let mut rows = selection.spanned_rows(false, &display_map);
9934 // duplicate line-wise
9935 if whole_lines || selection.start == selection.end {
9936 // Avoid duplicating the same lines twice.
9937 while let Some(next_selection) = selections_iter.peek() {
9938 let next_rows = next_selection.spanned_rows(false, &display_map);
9939 if next_rows.start < rows.end {
9940 rows.end = next_rows.end;
9941 selections_iter.next().unwrap();
9942 } else {
9943 break;
9944 }
9945 }
9946
9947 // Copy the text from the selected row region and splice it either at the start
9948 // or end of the region.
9949 let start = Point::new(rows.start.0, 0);
9950 let end = Point::new(
9951 rows.end.previous_row().0,
9952 buffer.line_len(rows.end.previous_row()),
9953 );
9954 let text = buffer
9955 .text_for_range(start..end)
9956 .chain(Some("\n"))
9957 .collect::<String>();
9958 let insert_location = if upwards {
9959 Point::new(rows.end.0, 0)
9960 } else {
9961 start
9962 };
9963 edits.push((insert_location..insert_location, text));
9964 } else {
9965 // duplicate character-wise
9966 let start = selection.start;
9967 let end = selection.end;
9968 let text = buffer.text_for_range(start..end).collect::<String>();
9969 edits.push((selection.end..selection.end, text));
9970 }
9971 }
9972
9973 self.transact(window, cx, |this, _, cx| {
9974 this.buffer.update(cx, |buffer, cx| {
9975 buffer.edit(edits, None, cx);
9976 });
9977
9978 this.request_autoscroll(Autoscroll::fit(), cx);
9979 });
9980 }
9981
9982 pub fn duplicate_line_up(
9983 &mut self,
9984 _: &DuplicateLineUp,
9985 window: &mut Window,
9986 cx: &mut Context<Self>,
9987 ) {
9988 self.duplicate(true, true, window, cx);
9989 }
9990
9991 pub fn duplicate_line_down(
9992 &mut self,
9993 _: &DuplicateLineDown,
9994 window: &mut Window,
9995 cx: &mut Context<Self>,
9996 ) {
9997 self.duplicate(false, true, window, cx);
9998 }
9999
10000 pub fn duplicate_selection(
10001 &mut self,
10002 _: &DuplicateSelection,
10003 window: &mut Window,
10004 cx: &mut Context<Self>,
10005 ) {
10006 self.duplicate(false, false, window, cx);
10007 }
10008
10009 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10010 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10011
10012 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10013 let buffer = self.buffer.read(cx).snapshot(cx);
10014
10015 let mut edits = Vec::new();
10016 let mut unfold_ranges = Vec::new();
10017 let mut refold_creases = Vec::new();
10018
10019 let selections = self.selections.all::<Point>(cx);
10020 let mut selections = selections.iter().peekable();
10021 let mut contiguous_row_selections = Vec::new();
10022 let mut new_selections = Vec::new();
10023
10024 while let Some(selection) = selections.next() {
10025 // Find all the selections that span a contiguous row range
10026 let (start_row, end_row) = consume_contiguous_rows(
10027 &mut contiguous_row_selections,
10028 selection,
10029 &display_map,
10030 &mut selections,
10031 );
10032
10033 // Move the text spanned by the row range to be before the line preceding the row range
10034 if start_row.0 > 0 {
10035 let range_to_move = Point::new(
10036 start_row.previous_row().0,
10037 buffer.line_len(start_row.previous_row()),
10038 )
10039 ..Point::new(
10040 end_row.previous_row().0,
10041 buffer.line_len(end_row.previous_row()),
10042 );
10043 let insertion_point = display_map
10044 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10045 .0;
10046
10047 // Don't move lines across excerpts
10048 if buffer
10049 .excerpt_containing(insertion_point..range_to_move.end)
10050 .is_some()
10051 {
10052 let text = buffer
10053 .text_for_range(range_to_move.clone())
10054 .flat_map(|s| s.chars())
10055 .skip(1)
10056 .chain(['\n'])
10057 .collect::<String>();
10058
10059 edits.push((
10060 buffer.anchor_after(range_to_move.start)
10061 ..buffer.anchor_before(range_to_move.end),
10062 String::new(),
10063 ));
10064 let insertion_anchor = buffer.anchor_after(insertion_point);
10065 edits.push((insertion_anchor..insertion_anchor, text));
10066
10067 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10068
10069 // Move selections up
10070 new_selections.extend(contiguous_row_selections.drain(..).map(
10071 |mut selection| {
10072 selection.start.row -= row_delta;
10073 selection.end.row -= row_delta;
10074 selection
10075 },
10076 ));
10077
10078 // Move folds up
10079 unfold_ranges.push(range_to_move.clone());
10080 for fold in display_map.folds_in_range(
10081 buffer.anchor_before(range_to_move.start)
10082 ..buffer.anchor_after(range_to_move.end),
10083 ) {
10084 let mut start = fold.range.start.to_point(&buffer);
10085 let mut end = fold.range.end.to_point(&buffer);
10086 start.row -= row_delta;
10087 end.row -= row_delta;
10088 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10089 }
10090 }
10091 }
10092
10093 // If we didn't move line(s), preserve the existing selections
10094 new_selections.append(&mut contiguous_row_selections);
10095 }
10096
10097 self.transact(window, cx, |this, window, cx| {
10098 this.unfold_ranges(&unfold_ranges, true, true, cx);
10099 this.buffer.update(cx, |buffer, cx| {
10100 for (range, text) in edits {
10101 buffer.edit([(range, text)], None, cx);
10102 }
10103 });
10104 this.fold_creases(refold_creases, true, window, cx);
10105 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10106 s.select(new_selections);
10107 })
10108 });
10109 }
10110
10111 pub fn move_line_down(
10112 &mut self,
10113 _: &MoveLineDown,
10114 window: &mut Window,
10115 cx: &mut Context<Self>,
10116 ) {
10117 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10118
10119 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10120 let buffer = self.buffer.read(cx).snapshot(cx);
10121
10122 let mut edits = Vec::new();
10123 let mut unfold_ranges = Vec::new();
10124 let mut refold_creases = Vec::new();
10125
10126 let selections = self.selections.all::<Point>(cx);
10127 let mut selections = selections.iter().peekable();
10128 let mut contiguous_row_selections = Vec::new();
10129 let mut new_selections = Vec::new();
10130
10131 while let Some(selection) = selections.next() {
10132 // Find all the selections that span a contiguous row range
10133 let (start_row, end_row) = consume_contiguous_rows(
10134 &mut contiguous_row_selections,
10135 selection,
10136 &display_map,
10137 &mut selections,
10138 );
10139
10140 // Move the text spanned by the row range to be after the last line of the row range
10141 if end_row.0 <= buffer.max_point().row {
10142 let range_to_move =
10143 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10144 let insertion_point = display_map
10145 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10146 .0;
10147
10148 // Don't move lines across excerpt boundaries
10149 if buffer
10150 .excerpt_containing(range_to_move.start..insertion_point)
10151 .is_some()
10152 {
10153 let mut text = String::from("\n");
10154 text.extend(buffer.text_for_range(range_to_move.clone()));
10155 text.pop(); // Drop trailing newline
10156 edits.push((
10157 buffer.anchor_after(range_to_move.start)
10158 ..buffer.anchor_before(range_to_move.end),
10159 String::new(),
10160 ));
10161 let insertion_anchor = buffer.anchor_after(insertion_point);
10162 edits.push((insertion_anchor..insertion_anchor, text));
10163
10164 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10165
10166 // Move selections down
10167 new_selections.extend(contiguous_row_selections.drain(..).map(
10168 |mut selection| {
10169 selection.start.row += row_delta;
10170 selection.end.row += row_delta;
10171 selection
10172 },
10173 ));
10174
10175 // Move folds down
10176 unfold_ranges.push(range_to_move.clone());
10177 for fold in display_map.folds_in_range(
10178 buffer.anchor_before(range_to_move.start)
10179 ..buffer.anchor_after(range_to_move.end),
10180 ) {
10181 let mut start = fold.range.start.to_point(&buffer);
10182 let mut end = fold.range.end.to_point(&buffer);
10183 start.row += row_delta;
10184 end.row += row_delta;
10185 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10186 }
10187 }
10188 }
10189
10190 // If we didn't move line(s), preserve the existing selections
10191 new_selections.append(&mut contiguous_row_selections);
10192 }
10193
10194 self.transact(window, cx, |this, window, cx| {
10195 this.unfold_ranges(&unfold_ranges, true, true, cx);
10196 this.buffer.update(cx, |buffer, cx| {
10197 for (range, text) in edits {
10198 buffer.edit([(range, text)], None, cx);
10199 }
10200 });
10201 this.fold_creases(refold_creases, true, window, cx);
10202 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10203 s.select(new_selections)
10204 });
10205 });
10206 }
10207
10208 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10209 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10210 let text_layout_details = &self.text_layout_details(window);
10211 self.transact(window, cx, |this, window, cx| {
10212 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10213 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10214 s.move_with(|display_map, selection| {
10215 if !selection.is_empty() {
10216 return;
10217 }
10218
10219 let mut head = selection.head();
10220 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10221 if head.column() == display_map.line_len(head.row()) {
10222 transpose_offset = display_map
10223 .buffer_snapshot
10224 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10225 }
10226
10227 if transpose_offset == 0 {
10228 return;
10229 }
10230
10231 *head.column_mut() += 1;
10232 head = display_map.clip_point(head, Bias::Right);
10233 let goal = SelectionGoal::HorizontalPosition(
10234 display_map
10235 .x_for_display_point(head, text_layout_details)
10236 .into(),
10237 );
10238 selection.collapse_to(head, goal);
10239
10240 let transpose_start = display_map
10241 .buffer_snapshot
10242 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10243 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10244 let transpose_end = display_map
10245 .buffer_snapshot
10246 .clip_offset(transpose_offset + 1, Bias::Right);
10247 if let Some(ch) =
10248 display_map.buffer_snapshot.chars_at(transpose_start).next()
10249 {
10250 edits.push((transpose_start..transpose_offset, String::new()));
10251 edits.push((transpose_end..transpose_end, ch.to_string()));
10252 }
10253 }
10254 });
10255 edits
10256 });
10257 this.buffer
10258 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10259 let selections = this.selections.all::<usize>(cx);
10260 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10261 s.select(selections);
10262 });
10263 });
10264 }
10265
10266 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10267 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10268 self.rewrap_impl(RewrapOptions::default(), cx)
10269 }
10270
10271 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10272 let buffer = self.buffer.read(cx).snapshot(cx);
10273 let selections = self.selections.all::<Point>(cx);
10274 let mut selections = selections.iter().peekable();
10275
10276 let mut edits = Vec::new();
10277 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10278
10279 while let Some(selection) = selections.next() {
10280 let mut start_row = selection.start.row;
10281 let mut end_row = selection.end.row;
10282
10283 // Skip selections that overlap with a range that has already been rewrapped.
10284 let selection_range = start_row..end_row;
10285 if rewrapped_row_ranges
10286 .iter()
10287 .any(|range| range.overlaps(&selection_range))
10288 {
10289 continue;
10290 }
10291
10292 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10293
10294 // Since not all lines in the selection may be at the same indent
10295 // level, choose the indent size that is the most common between all
10296 // of the lines.
10297 //
10298 // If there is a tie, we use the deepest indent.
10299 let (indent_size, indent_end) = {
10300 let mut indent_size_occurrences = HashMap::default();
10301 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10302
10303 for row in start_row..=end_row {
10304 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10305 rows_by_indent_size.entry(indent).or_default().push(row);
10306 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10307 }
10308
10309 let indent_size = indent_size_occurrences
10310 .into_iter()
10311 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10312 .map(|(indent, _)| indent)
10313 .unwrap_or_default();
10314 let row = rows_by_indent_size[&indent_size][0];
10315 let indent_end = Point::new(row, indent_size.len);
10316
10317 (indent_size, indent_end)
10318 };
10319
10320 let mut line_prefix = indent_size.chars().collect::<String>();
10321
10322 let mut inside_comment = false;
10323 if let Some(comment_prefix) =
10324 buffer
10325 .language_scope_at(selection.head())
10326 .and_then(|language| {
10327 language
10328 .line_comment_prefixes()
10329 .iter()
10330 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10331 .cloned()
10332 })
10333 {
10334 line_prefix.push_str(&comment_prefix);
10335 inside_comment = true;
10336 }
10337
10338 let language_settings = buffer.language_settings_at(selection.head(), cx);
10339 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10340 RewrapBehavior::InComments => inside_comment,
10341 RewrapBehavior::InSelections => !selection.is_empty(),
10342 RewrapBehavior::Anywhere => true,
10343 };
10344
10345 let should_rewrap = options.override_language_settings
10346 || allow_rewrap_based_on_language
10347 || self.hard_wrap.is_some();
10348 if !should_rewrap {
10349 continue;
10350 }
10351
10352 if selection.is_empty() {
10353 'expand_upwards: while start_row > 0 {
10354 let prev_row = start_row - 1;
10355 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10356 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10357 {
10358 start_row = prev_row;
10359 } else {
10360 break 'expand_upwards;
10361 }
10362 }
10363
10364 'expand_downwards: while end_row < buffer.max_point().row {
10365 let next_row = end_row + 1;
10366 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10367 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10368 {
10369 end_row = next_row;
10370 } else {
10371 break 'expand_downwards;
10372 }
10373 }
10374 }
10375
10376 let start = Point::new(start_row, 0);
10377 let start_offset = start.to_offset(&buffer);
10378 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10379 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10380 let Some(lines_without_prefixes) = selection_text
10381 .lines()
10382 .map(|line| {
10383 line.strip_prefix(&line_prefix)
10384 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10385 .ok_or_else(|| {
10386 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
10387 })
10388 })
10389 .collect::<Result<Vec<_>, _>>()
10390 .log_err()
10391 else {
10392 continue;
10393 };
10394
10395 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10396 buffer
10397 .language_settings_at(Point::new(start_row, 0), cx)
10398 .preferred_line_length as usize
10399 });
10400 let wrapped_text = wrap_with_prefix(
10401 line_prefix,
10402 lines_without_prefixes.join("\n"),
10403 wrap_column,
10404 tab_size,
10405 options.preserve_existing_whitespace,
10406 );
10407
10408 // TODO: should always use char-based diff while still supporting cursor behavior that
10409 // matches vim.
10410 let mut diff_options = DiffOptions::default();
10411 if options.override_language_settings {
10412 diff_options.max_word_diff_len = 0;
10413 diff_options.max_word_diff_line_count = 0;
10414 } else {
10415 diff_options.max_word_diff_len = usize::MAX;
10416 diff_options.max_word_diff_line_count = usize::MAX;
10417 }
10418
10419 for (old_range, new_text) in
10420 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10421 {
10422 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10423 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10424 edits.push((edit_start..edit_end, new_text));
10425 }
10426
10427 rewrapped_row_ranges.push(start_row..=end_row);
10428 }
10429
10430 self.buffer
10431 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10432 }
10433
10434 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10435 let mut text = String::new();
10436 let buffer = self.buffer.read(cx).snapshot(cx);
10437 let mut selections = self.selections.all::<Point>(cx);
10438 let mut clipboard_selections = Vec::with_capacity(selections.len());
10439 {
10440 let max_point = buffer.max_point();
10441 let mut is_first = true;
10442 for selection in &mut selections {
10443 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10444 if is_entire_line {
10445 selection.start = Point::new(selection.start.row, 0);
10446 if !selection.is_empty() && selection.end.column == 0 {
10447 selection.end = cmp::min(max_point, selection.end);
10448 } else {
10449 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10450 }
10451 selection.goal = SelectionGoal::None;
10452 }
10453 if is_first {
10454 is_first = false;
10455 } else {
10456 text += "\n";
10457 }
10458 let mut len = 0;
10459 for chunk in buffer.text_for_range(selection.start..selection.end) {
10460 text.push_str(chunk);
10461 len += chunk.len();
10462 }
10463 clipboard_selections.push(ClipboardSelection {
10464 len,
10465 is_entire_line,
10466 first_line_indent: buffer
10467 .indent_size_for_line(MultiBufferRow(selection.start.row))
10468 .len,
10469 });
10470 }
10471 }
10472
10473 self.transact(window, cx, |this, window, cx| {
10474 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10475 s.select(selections);
10476 });
10477 this.insert("", window, cx);
10478 });
10479 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10480 }
10481
10482 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10483 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10484 let item = self.cut_common(window, cx);
10485 cx.write_to_clipboard(item);
10486 }
10487
10488 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10489 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10490 self.change_selections(None, window, cx, |s| {
10491 s.move_with(|snapshot, sel| {
10492 if sel.is_empty() {
10493 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10494 }
10495 });
10496 });
10497 let item = self.cut_common(window, cx);
10498 cx.set_global(KillRing(item))
10499 }
10500
10501 pub fn kill_ring_yank(
10502 &mut self,
10503 _: &KillRingYank,
10504 window: &mut Window,
10505 cx: &mut Context<Self>,
10506 ) {
10507 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10508 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10509 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10510 (kill_ring.text().to_string(), kill_ring.metadata_json())
10511 } else {
10512 return;
10513 }
10514 } else {
10515 return;
10516 };
10517 self.do_paste(&text, metadata, false, window, cx);
10518 }
10519
10520 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10521 self.do_copy(true, cx);
10522 }
10523
10524 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10525 self.do_copy(false, cx);
10526 }
10527
10528 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10529 let selections = self.selections.all::<Point>(cx);
10530 let buffer = self.buffer.read(cx).read(cx);
10531 let mut text = String::new();
10532
10533 let mut clipboard_selections = Vec::with_capacity(selections.len());
10534 {
10535 let max_point = buffer.max_point();
10536 let mut is_first = true;
10537 for selection in &selections {
10538 let mut start = selection.start;
10539 let mut end = selection.end;
10540 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10541 if is_entire_line {
10542 start = Point::new(start.row, 0);
10543 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10544 }
10545
10546 let mut trimmed_selections = Vec::new();
10547 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10548 let row = MultiBufferRow(start.row);
10549 let first_indent = buffer.indent_size_for_line(row);
10550 if first_indent.len == 0 || start.column > first_indent.len {
10551 trimmed_selections.push(start..end);
10552 } else {
10553 trimmed_selections.push(
10554 Point::new(row.0, first_indent.len)
10555 ..Point::new(row.0, buffer.line_len(row)),
10556 );
10557 for row in start.row + 1..=end.row {
10558 let mut line_len = buffer.line_len(MultiBufferRow(row));
10559 if row == end.row {
10560 line_len = end.column;
10561 }
10562 if line_len == 0 {
10563 trimmed_selections
10564 .push(Point::new(row, 0)..Point::new(row, line_len));
10565 continue;
10566 }
10567 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10568 if row_indent_size.len >= first_indent.len {
10569 trimmed_selections.push(
10570 Point::new(row, first_indent.len)..Point::new(row, line_len),
10571 );
10572 } else {
10573 trimmed_selections.clear();
10574 trimmed_selections.push(start..end);
10575 break;
10576 }
10577 }
10578 }
10579 } else {
10580 trimmed_selections.push(start..end);
10581 }
10582
10583 for trimmed_range in trimmed_selections {
10584 if is_first {
10585 is_first = false;
10586 } else {
10587 text += "\n";
10588 }
10589 let mut len = 0;
10590 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10591 text.push_str(chunk);
10592 len += chunk.len();
10593 }
10594 clipboard_selections.push(ClipboardSelection {
10595 len,
10596 is_entire_line,
10597 first_line_indent: buffer
10598 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10599 .len,
10600 });
10601 }
10602 }
10603 }
10604
10605 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10606 text,
10607 clipboard_selections,
10608 ));
10609 }
10610
10611 pub fn do_paste(
10612 &mut self,
10613 text: &String,
10614 clipboard_selections: Option<Vec<ClipboardSelection>>,
10615 handle_entire_lines: bool,
10616 window: &mut Window,
10617 cx: &mut Context<Self>,
10618 ) {
10619 if self.read_only(cx) {
10620 return;
10621 }
10622
10623 let clipboard_text = Cow::Borrowed(text);
10624
10625 self.transact(window, cx, |this, window, cx| {
10626 if let Some(mut clipboard_selections) = clipboard_selections {
10627 let old_selections = this.selections.all::<usize>(cx);
10628 let all_selections_were_entire_line =
10629 clipboard_selections.iter().all(|s| s.is_entire_line);
10630 let first_selection_indent_column =
10631 clipboard_selections.first().map(|s| s.first_line_indent);
10632 if clipboard_selections.len() != old_selections.len() {
10633 clipboard_selections.drain(..);
10634 }
10635 let cursor_offset = this.selections.last::<usize>(cx).head();
10636 let mut auto_indent_on_paste = true;
10637
10638 this.buffer.update(cx, |buffer, cx| {
10639 let snapshot = buffer.read(cx);
10640 auto_indent_on_paste = snapshot
10641 .language_settings_at(cursor_offset, cx)
10642 .auto_indent_on_paste;
10643
10644 let mut start_offset = 0;
10645 let mut edits = Vec::new();
10646 let mut original_indent_columns = Vec::new();
10647 for (ix, selection) in old_selections.iter().enumerate() {
10648 let to_insert;
10649 let entire_line;
10650 let original_indent_column;
10651 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10652 let end_offset = start_offset + clipboard_selection.len;
10653 to_insert = &clipboard_text[start_offset..end_offset];
10654 entire_line = clipboard_selection.is_entire_line;
10655 start_offset = end_offset + 1;
10656 original_indent_column = Some(clipboard_selection.first_line_indent);
10657 } else {
10658 to_insert = clipboard_text.as_str();
10659 entire_line = all_selections_were_entire_line;
10660 original_indent_column = first_selection_indent_column
10661 }
10662
10663 // If the corresponding selection was empty when this slice of the
10664 // clipboard text was written, then the entire line containing the
10665 // selection was copied. If this selection is also currently empty,
10666 // then paste the line before the current line of the buffer.
10667 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10668 let column = selection.start.to_point(&snapshot).column as usize;
10669 let line_start = selection.start - column;
10670 line_start..line_start
10671 } else {
10672 selection.range()
10673 };
10674
10675 edits.push((range, to_insert));
10676 original_indent_columns.push(original_indent_column);
10677 }
10678 drop(snapshot);
10679
10680 buffer.edit(
10681 edits,
10682 if auto_indent_on_paste {
10683 Some(AutoindentMode::Block {
10684 original_indent_columns,
10685 })
10686 } else {
10687 None
10688 },
10689 cx,
10690 );
10691 });
10692
10693 let selections = this.selections.all::<usize>(cx);
10694 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10695 s.select(selections)
10696 });
10697 } else {
10698 this.insert(&clipboard_text, window, cx);
10699 }
10700 });
10701 }
10702
10703 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10704 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10705 if let Some(item) = cx.read_from_clipboard() {
10706 let entries = item.entries();
10707
10708 match entries.first() {
10709 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10710 // of all the pasted entries.
10711 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10712 .do_paste(
10713 clipboard_string.text(),
10714 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10715 true,
10716 window,
10717 cx,
10718 ),
10719 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10720 }
10721 }
10722 }
10723
10724 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10725 if self.read_only(cx) {
10726 return;
10727 }
10728
10729 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10730
10731 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10732 if let Some((selections, _)) =
10733 self.selection_history.transaction(transaction_id).cloned()
10734 {
10735 self.change_selections(None, window, cx, |s| {
10736 s.select_anchors(selections.to_vec());
10737 });
10738 } else {
10739 log::error!(
10740 "No entry in selection_history found for undo. \
10741 This may correspond to a bug where undo does not update the selection. \
10742 If this is occurring, please add details to \
10743 https://github.com/zed-industries/zed/issues/22692"
10744 );
10745 }
10746 self.request_autoscroll(Autoscroll::fit(), cx);
10747 self.unmark_text(window, cx);
10748 self.refresh_inline_completion(true, false, window, cx);
10749 cx.emit(EditorEvent::Edited { transaction_id });
10750 cx.emit(EditorEvent::TransactionUndone { transaction_id });
10751 }
10752 }
10753
10754 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
10755 if self.read_only(cx) {
10756 return;
10757 }
10758
10759 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10760
10761 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
10762 if let Some((_, Some(selections))) =
10763 self.selection_history.transaction(transaction_id).cloned()
10764 {
10765 self.change_selections(None, window, cx, |s| {
10766 s.select_anchors(selections.to_vec());
10767 });
10768 } else {
10769 log::error!(
10770 "No entry in selection_history found for redo. \
10771 This may correspond to a bug where undo does not update the selection. \
10772 If this is occurring, please add details to \
10773 https://github.com/zed-industries/zed/issues/22692"
10774 );
10775 }
10776 self.request_autoscroll(Autoscroll::fit(), cx);
10777 self.unmark_text(window, cx);
10778 self.refresh_inline_completion(true, false, window, cx);
10779 cx.emit(EditorEvent::Edited { transaction_id });
10780 }
10781 }
10782
10783 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
10784 self.buffer
10785 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
10786 }
10787
10788 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
10789 self.buffer
10790 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
10791 }
10792
10793 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
10794 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10795 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10796 s.move_with(|map, selection| {
10797 let cursor = if selection.is_empty() {
10798 movement::left(map, selection.start)
10799 } else {
10800 selection.start
10801 };
10802 selection.collapse_to(cursor, SelectionGoal::None);
10803 });
10804 })
10805 }
10806
10807 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
10808 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10809 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10810 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
10811 })
10812 }
10813
10814 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
10815 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10816 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10817 s.move_with(|map, selection| {
10818 let cursor = if selection.is_empty() {
10819 movement::right(map, selection.end)
10820 } else {
10821 selection.end
10822 };
10823 selection.collapse_to(cursor, SelectionGoal::None)
10824 });
10825 })
10826 }
10827
10828 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
10829 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10830 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10831 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
10832 })
10833 }
10834
10835 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
10836 if self.take_rename(true, window, cx).is_some() {
10837 return;
10838 }
10839
10840 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10841 cx.propagate();
10842 return;
10843 }
10844
10845 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10846
10847 let text_layout_details = &self.text_layout_details(window);
10848 let selection_count = self.selections.count();
10849 let first_selection = self.selections.first_anchor();
10850
10851 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10852 s.move_with(|map, selection| {
10853 if !selection.is_empty() {
10854 selection.goal = SelectionGoal::None;
10855 }
10856 let (cursor, goal) = movement::up(
10857 map,
10858 selection.start,
10859 selection.goal,
10860 false,
10861 text_layout_details,
10862 );
10863 selection.collapse_to(cursor, goal);
10864 });
10865 });
10866
10867 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10868 {
10869 cx.propagate();
10870 }
10871 }
10872
10873 pub fn move_up_by_lines(
10874 &mut self,
10875 action: &MoveUpByLines,
10876 window: &mut Window,
10877 cx: &mut Context<Self>,
10878 ) {
10879 if self.take_rename(true, window, cx).is_some() {
10880 return;
10881 }
10882
10883 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10884 cx.propagate();
10885 return;
10886 }
10887
10888 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10889
10890 let text_layout_details = &self.text_layout_details(window);
10891
10892 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10893 s.move_with(|map, selection| {
10894 if !selection.is_empty() {
10895 selection.goal = SelectionGoal::None;
10896 }
10897 let (cursor, goal) = movement::up_by_rows(
10898 map,
10899 selection.start,
10900 action.lines,
10901 selection.goal,
10902 false,
10903 text_layout_details,
10904 );
10905 selection.collapse_to(cursor, goal);
10906 });
10907 })
10908 }
10909
10910 pub fn move_down_by_lines(
10911 &mut self,
10912 action: &MoveDownByLines,
10913 window: &mut Window,
10914 cx: &mut Context<Self>,
10915 ) {
10916 if self.take_rename(true, window, cx).is_some() {
10917 return;
10918 }
10919
10920 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10921 cx.propagate();
10922 return;
10923 }
10924
10925 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10926
10927 let text_layout_details = &self.text_layout_details(window);
10928
10929 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10930 s.move_with(|map, selection| {
10931 if !selection.is_empty() {
10932 selection.goal = SelectionGoal::None;
10933 }
10934 let (cursor, goal) = movement::down_by_rows(
10935 map,
10936 selection.start,
10937 action.lines,
10938 selection.goal,
10939 false,
10940 text_layout_details,
10941 );
10942 selection.collapse_to(cursor, goal);
10943 });
10944 })
10945 }
10946
10947 pub fn select_down_by_lines(
10948 &mut self,
10949 action: &SelectDownByLines,
10950 window: &mut Window,
10951 cx: &mut Context<Self>,
10952 ) {
10953 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10954 let text_layout_details = &self.text_layout_details(window);
10955 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10956 s.move_heads_with(|map, head, goal| {
10957 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
10958 })
10959 })
10960 }
10961
10962 pub fn select_up_by_lines(
10963 &mut self,
10964 action: &SelectUpByLines,
10965 window: &mut Window,
10966 cx: &mut Context<Self>,
10967 ) {
10968 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10969 let text_layout_details = &self.text_layout_details(window);
10970 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10971 s.move_heads_with(|map, head, goal| {
10972 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
10973 })
10974 })
10975 }
10976
10977 pub fn select_page_up(
10978 &mut self,
10979 _: &SelectPageUp,
10980 window: &mut Window,
10981 cx: &mut Context<Self>,
10982 ) {
10983 let Some(row_count) = self.visible_row_count() else {
10984 return;
10985 };
10986
10987 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10988
10989 let text_layout_details = &self.text_layout_details(window);
10990
10991 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10992 s.move_heads_with(|map, head, goal| {
10993 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
10994 })
10995 })
10996 }
10997
10998 pub fn move_page_up(
10999 &mut self,
11000 action: &MovePageUp,
11001 window: &mut Window,
11002 cx: &mut Context<Self>,
11003 ) {
11004 if self.take_rename(true, window, cx).is_some() {
11005 return;
11006 }
11007
11008 if self
11009 .context_menu
11010 .borrow_mut()
11011 .as_mut()
11012 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
11013 .unwrap_or(false)
11014 {
11015 return;
11016 }
11017
11018 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11019 cx.propagate();
11020 return;
11021 }
11022
11023 let Some(row_count) = self.visible_row_count() else {
11024 return;
11025 };
11026
11027 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11028
11029 let autoscroll = if action.center_cursor {
11030 Autoscroll::center()
11031 } else {
11032 Autoscroll::fit()
11033 };
11034
11035 let text_layout_details = &self.text_layout_details(window);
11036
11037 self.change_selections(Some(autoscroll), window, cx, |s| {
11038 s.move_with(|map, selection| {
11039 if !selection.is_empty() {
11040 selection.goal = SelectionGoal::None;
11041 }
11042 let (cursor, goal) = movement::up_by_rows(
11043 map,
11044 selection.end,
11045 row_count,
11046 selection.goal,
11047 false,
11048 text_layout_details,
11049 );
11050 selection.collapse_to(cursor, goal);
11051 });
11052 });
11053 }
11054
11055 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11056 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11057 let text_layout_details = &self.text_layout_details(window);
11058 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11059 s.move_heads_with(|map, head, goal| {
11060 movement::up(map, head, goal, false, text_layout_details)
11061 })
11062 })
11063 }
11064
11065 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11066 self.take_rename(true, window, cx);
11067
11068 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11069 cx.propagate();
11070 return;
11071 }
11072
11073 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11074
11075 let text_layout_details = &self.text_layout_details(window);
11076 let selection_count = self.selections.count();
11077 let first_selection = self.selections.first_anchor();
11078
11079 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11080 s.move_with(|map, selection| {
11081 if !selection.is_empty() {
11082 selection.goal = SelectionGoal::None;
11083 }
11084 let (cursor, goal) = movement::down(
11085 map,
11086 selection.end,
11087 selection.goal,
11088 false,
11089 text_layout_details,
11090 );
11091 selection.collapse_to(cursor, goal);
11092 });
11093 });
11094
11095 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11096 {
11097 cx.propagate();
11098 }
11099 }
11100
11101 pub fn select_page_down(
11102 &mut self,
11103 _: &SelectPageDown,
11104 window: &mut Window,
11105 cx: &mut Context<Self>,
11106 ) {
11107 let Some(row_count) = self.visible_row_count() else {
11108 return;
11109 };
11110
11111 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11112
11113 let text_layout_details = &self.text_layout_details(window);
11114
11115 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11116 s.move_heads_with(|map, head, goal| {
11117 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11118 })
11119 })
11120 }
11121
11122 pub fn move_page_down(
11123 &mut self,
11124 action: &MovePageDown,
11125 window: &mut Window,
11126 cx: &mut Context<Self>,
11127 ) {
11128 if self.take_rename(true, window, cx).is_some() {
11129 return;
11130 }
11131
11132 if self
11133 .context_menu
11134 .borrow_mut()
11135 .as_mut()
11136 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
11137 .unwrap_or(false)
11138 {
11139 return;
11140 }
11141
11142 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11143 cx.propagate();
11144 return;
11145 }
11146
11147 let Some(row_count) = self.visible_row_count() else {
11148 return;
11149 };
11150
11151 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11152
11153 let autoscroll = if action.center_cursor {
11154 Autoscroll::center()
11155 } else {
11156 Autoscroll::fit()
11157 };
11158
11159 let text_layout_details = &self.text_layout_details(window);
11160 self.change_selections(Some(autoscroll), window, cx, |s| {
11161 s.move_with(|map, selection| {
11162 if !selection.is_empty() {
11163 selection.goal = SelectionGoal::None;
11164 }
11165 let (cursor, goal) = movement::down_by_rows(
11166 map,
11167 selection.end,
11168 row_count,
11169 selection.goal,
11170 false,
11171 text_layout_details,
11172 );
11173 selection.collapse_to(cursor, goal);
11174 });
11175 });
11176 }
11177
11178 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11179 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11180 let text_layout_details = &self.text_layout_details(window);
11181 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11182 s.move_heads_with(|map, head, goal| {
11183 movement::down(map, head, goal, false, text_layout_details)
11184 })
11185 });
11186 }
11187
11188 pub fn context_menu_first(
11189 &mut self,
11190 _: &ContextMenuFirst,
11191 _window: &mut Window,
11192 cx: &mut Context<Self>,
11193 ) {
11194 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11195 context_menu.select_first(self.completion_provider.as_deref(), cx);
11196 }
11197 }
11198
11199 pub fn context_menu_prev(
11200 &mut self,
11201 _: &ContextMenuPrevious,
11202 _window: &mut Window,
11203 cx: &mut Context<Self>,
11204 ) {
11205 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11206 context_menu.select_prev(self.completion_provider.as_deref(), cx);
11207 }
11208 }
11209
11210 pub fn context_menu_next(
11211 &mut self,
11212 _: &ContextMenuNext,
11213 _window: &mut Window,
11214 cx: &mut Context<Self>,
11215 ) {
11216 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11217 context_menu.select_next(self.completion_provider.as_deref(), cx);
11218 }
11219 }
11220
11221 pub fn context_menu_last(
11222 &mut self,
11223 _: &ContextMenuLast,
11224 _window: &mut Window,
11225 cx: &mut Context<Self>,
11226 ) {
11227 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11228 context_menu.select_last(self.completion_provider.as_deref(), cx);
11229 }
11230 }
11231
11232 pub fn move_to_previous_word_start(
11233 &mut self,
11234 _: &MoveToPreviousWordStart,
11235 window: &mut Window,
11236 cx: &mut Context<Self>,
11237 ) {
11238 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11239 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11240 s.move_cursors_with(|map, head, _| {
11241 (
11242 movement::previous_word_start(map, head),
11243 SelectionGoal::None,
11244 )
11245 });
11246 })
11247 }
11248
11249 pub fn move_to_previous_subword_start(
11250 &mut self,
11251 _: &MoveToPreviousSubwordStart,
11252 window: &mut Window,
11253 cx: &mut Context<Self>,
11254 ) {
11255 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11256 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11257 s.move_cursors_with(|map, head, _| {
11258 (
11259 movement::previous_subword_start(map, head),
11260 SelectionGoal::None,
11261 )
11262 });
11263 })
11264 }
11265
11266 pub fn select_to_previous_word_start(
11267 &mut self,
11268 _: &SelectToPreviousWordStart,
11269 window: &mut Window,
11270 cx: &mut Context<Self>,
11271 ) {
11272 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11273 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11274 s.move_heads_with(|map, head, _| {
11275 (
11276 movement::previous_word_start(map, head),
11277 SelectionGoal::None,
11278 )
11279 });
11280 })
11281 }
11282
11283 pub fn select_to_previous_subword_start(
11284 &mut self,
11285 _: &SelectToPreviousSubwordStart,
11286 window: &mut Window,
11287 cx: &mut Context<Self>,
11288 ) {
11289 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11290 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11291 s.move_heads_with(|map, head, _| {
11292 (
11293 movement::previous_subword_start(map, head),
11294 SelectionGoal::None,
11295 )
11296 });
11297 })
11298 }
11299
11300 pub fn delete_to_previous_word_start(
11301 &mut self,
11302 action: &DeleteToPreviousWordStart,
11303 window: &mut Window,
11304 cx: &mut Context<Self>,
11305 ) {
11306 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11307 self.transact(window, cx, |this, window, cx| {
11308 this.select_autoclose_pair(window, cx);
11309 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11310 s.move_with(|map, selection| {
11311 if selection.is_empty() {
11312 let cursor = if action.ignore_newlines {
11313 movement::previous_word_start(map, selection.head())
11314 } else {
11315 movement::previous_word_start_or_newline(map, selection.head())
11316 };
11317 selection.set_head(cursor, SelectionGoal::None);
11318 }
11319 });
11320 });
11321 this.insert("", window, cx);
11322 });
11323 }
11324
11325 pub fn delete_to_previous_subword_start(
11326 &mut self,
11327 _: &DeleteToPreviousSubwordStart,
11328 window: &mut Window,
11329 cx: &mut Context<Self>,
11330 ) {
11331 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11332 self.transact(window, cx, |this, window, cx| {
11333 this.select_autoclose_pair(window, cx);
11334 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11335 s.move_with(|map, selection| {
11336 if selection.is_empty() {
11337 let cursor = movement::previous_subword_start(map, selection.head());
11338 selection.set_head(cursor, SelectionGoal::None);
11339 }
11340 });
11341 });
11342 this.insert("", window, cx);
11343 });
11344 }
11345
11346 pub fn move_to_next_word_end(
11347 &mut self,
11348 _: &MoveToNextWordEnd,
11349 window: &mut Window,
11350 cx: &mut Context<Self>,
11351 ) {
11352 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11353 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11354 s.move_cursors_with(|map, head, _| {
11355 (movement::next_word_end(map, head), SelectionGoal::None)
11356 });
11357 })
11358 }
11359
11360 pub fn move_to_next_subword_end(
11361 &mut self,
11362 _: &MoveToNextSubwordEnd,
11363 window: &mut Window,
11364 cx: &mut Context<Self>,
11365 ) {
11366 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11367 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11368 s.move_cursors_with(|map, head, _| {
11369 (movement::next_subword_end(map, head), SelectionGoal::None)
11370 });
11371 })
11372 }
11373
11374 pub fn select_to_next_word_end(
11375 &mut self,
11376 _: &SelectToNextWordEnd,
11377 window: &mut Window,
11378 cx: &mut Context<Self>,
11379 ) {
11380 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11381 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11382 s.move_heads_with(|map, head, _| {
11383 (movement::next_word_end(map, head), SelectionGoal::None)
11384 });
11385 })
11386 }
11387
11388 pub fn select_to_next_subword_end(
11389 &mut self,
11390 _: &SelectToNextSubwordEnd,
11391 window: &mut Window,
11392 cx: &mut Context<Self>,
11393 ) {
11394 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11395 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11396 s.move_heads_with(|map, head, _| {
11397 (movement::next_subword_end(map, head), SelectionGoal::None)
11398 });
11399 })
11400 }
11401
11402 pub fn delete_to_next_word_end(
11403 &mut self,
11404 action: &DeleteToNextWordEnd,
11405 window: &mut Window,
11406 cx: &mut Context<Self>,
11407 ) {
11408 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11409 self.transact(window, cx, |this, window, cx| {
11410 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11411 s.move_with(|map, selection| {
11412 if selection.is_empty() {
11413 let cursor = if action.ignore_newlines {
11414 movement::next_word_end(map, selection.head())
11415 } else {
11416 movement::next_word_end_or_newline(map, selection.head())
11417 };
11418 selection.set_head(cursor, SelectionGoal::None);
11419 }
11420 });
11421 });
11422 this.insert("", window, cx);
11423 });
11424 }
11425
11426 pub fn delete_to_next_subword_end(
11427 &mut self,
11428 _: &DeleteToNextSubwordEnd,
11429 window: &mut Window,
11430 cx: &mut Context<Self>,
11431 ) {
11432 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11433 self.transact(window, cx, |this, window, cx| {
11434 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11435 s.move_with(|map, selection| {
11436 if selection.is_empty() {
11437 let cursor = movement::next_subword_end(map, selection.head());
11438 selection.set_head(cursor, SelectionGoal::None);
11439 }
11440 });
11441 });
11442 this.insert("", window, cx);
11443 });
11444 }
11445
11446 pub fn move_to_beginning_of_line(
11447 &mut self,
11448 action: &MoveToBeginningOfLine,
11449 window: &mut Window,
11450 cx: &mut Context<Self>,
11451 ) {
11452 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11453 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11454 s.move_cursors_with(|map, head, _| {
11455 (
11456 movement::indented_line_beginning(
11457 map,
11458 head,
11459 action.stop_at_soft_wraps,
11460 action.stop_at_indent,
11461 ),
11462 SelectionGoal::None,
11463 )
11464 });
11465 })
11466 }
11467
11468 pub fn select_to_beginning_of_line(
11469 &mut self,
11470 action: &SelectToBeginningOfLine,
11471 window: &mut Window,
11472 cx: &mut Context<Self>,
11473 ) {
11474 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11475 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11476 s.move_heads_with(|map, head, _| {
11477 (
11478 movement::indented_line_beginning(
11479 map,
11480 head,
11481 action.stop_at_soft_wraps,
11482 action.stop_at_indent,
11483 ),
11484 SelectionGoal::None,
11485 )
11486 });
11487 });
11488 }
11489
11490 pub fn delete_to_beginning_of_line(
11491 &mut self,
11492 action: &DeleteToBeginningOfLine,
11493 window: &mut Window,
11494 cx: &mut Context<Self>,
11495 ) {
11496 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11497 self.transact(window, cx, |this, window, cx| {
11498 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11499 s.move_with(|_, selection| {
11500 selection.reversed = true;
11501 });
11502 });
11503
11504 this.select_to_beginning_of_line(
11505 &SelectToBeginningOfLine {
11506 stop_at_soft_wraps: false,
11507 stop_at_indent: action.stop_at_indent,
11508 },
11509 window,
11510 cx,
11511 );
11512 this.backspace(&Backspace, window, cx);
11513 });
11514 }
11515
11516 pub fn move_to_end_of_line(
11517 &mut self,
11518 action: &MoveToEndOfLine,
11519 window: &mut Window,
11520 cx: &mut Context<Self>,
11521 ) {
11522 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11523 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11524 s.move_cursors_with(|map, head, _| {
11525 (
11526 movement::line_end(map, head, action.stop_at_soft_wraps),
11527 SelectionGoal::None,
11528 )
11529 });
11530 })
11531 }
11532
11533 pub fn select_to_end_of_line(
11534 &mut self,
11535 action: &SelectToEndOfLine,
11536 window: &mut Window,
11537 cx: &mut Context<Self>,
11538 ) {
11539 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11540 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11541 s.move_heads_with(|map, head, _| {
11542 (
11543 movement::line_end(map, head, action.stop_at_soft_wraps),
11544 SelectionGoal::None,
11545 )
11546 });
11547 })
11548 }
11549
11550 pub fn delete_to_end_of_line(
11551 &mut self,
11552 _: &DeleteToEndOfLine,
11553 window: &mut Window,
11554 cx: &mut Context<Self>,
11555 ) {
11556 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11557 self.transact(window, cx, |this, window, cx| {
11558 this.select_to_end_of_line(
11559 &SelectToEndOfLine {
11560 stop_at_soft_wraps: false,
11561 },
11562 window,
11563 cx,
11564 );
11565 this.delete(&Delete, window, cx);
11566 });
11567 }
11568
11569 pub fn cut_to_end_of_line(
11570 &mut self,
11571 _: &CutToEndOfLine,
11572 window: &mut Window,
11573 cx: &mut Context<Self>,
11574 ) {
11575 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11576 self.transact(window, cx, |this, window, cx| {
11577 this.select_to_end_of_line(
11578 &SelectToEndOfLine {
11579 stop_at_soft_wraps: false,
11580 },
11581 window,
11582 cx,
11583 );
11584 this.cut(&Cut, window, cx);
11585 });
11586 }
11587
11588 pub fn move_to_start_of_paragraph(
11589 &mut self,
11590 _: &MoveToStartOfParagraph,
11591 window: &mut Window,
11592 cx: &mut Context<Self>,
11593 ) {
11594 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11595 cx.propagate();
11596 return;
11597 }
11598 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11599 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11600 s.move_with(|map, selection| {
11601 selection.collapse_to(
11602 movement::start_of_paragraph(map, selection.head(), 1),
11603 SelectionGoal::None,
11604 )
11605 });
11606 })
11607 }
11608
11609 pub fn move_to_end_of_paragraph(
11610 &mut self,
11611 _: &MoveToEndOfParagraph,
11612 window: &mut Window,
11613 cx: &mut Context<Self>,
11614 ) {
11615 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11616 cx.propagate();
11617 return;
11618 }
11619 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11620 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11621 s.move_with(|map, selection| {
11622 selection.collapse_to(
11623 movement::end_of_paragraph(map, selection.head(), 1),
11624 SelectionGoal::None,
11625 )
11626 });
11627 })
11628 }
11629
11630 pub fn select_to_start_of_paragraph(
11631 &mut self,
11632 _: &SelectToStartOfParagraph,
11633 window: &mut Window,
11634 cx: &mut Context<Self>,
11635 ) {
11636 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11637 cx.propagate();
11638 return;
11639 }
11640 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11641 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11642 s.move_heads_with(|map, head, _| {
11643 (
11644 movement::start_of_paragraph(map, head, 1),
11645 SelectionGoal::None,
11646 )
11647 });
11648 })
11649 }
11650
11651 pub fn select_to_end_of_paragraph(
11652 &mut self,
11653 _: &SelectToEndOfParagraph,
11654 window: &mut Window,
11655 cx: &mut Context<Self>,
11656 ) {
11657 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11658 cx.propagate();
11659 return;
11660 }
11661 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11662 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11663 s.move_heads_with(|map, head, _| {
11664 (
11665 movement::end_of_paragraph(map, head, 1),
11666 SelectionGoal::None,
11667 )
11668 });
11669 })
11670 }
11671
11672 pub fn move_to_start_of_excerpt(
11673 &mut self,
11674 _: &MoveToStartOfExcerpt,
11675 window: &mut Window,
11676 cx: &mut Context<Self>,
11677 ) {
11678 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11679 cx.propagate();
11680 return;
11681 }
11682 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11683 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11684 s.move_with(|map, selection| {
11685 selection.collapse_to(
11686 movement::start_of_excerpt(
11687 map,
11688 selection.head(),
11689 workspace::searchable::Direction::Prev,
11690 ),
11691 SelectionGoal::None,
11692 )
11693 });
11694 })
11695 }
11696
11697 pub fn move_to_start_of_next_excerpt(
11698 &mut self,
11699 _: &MoveToStartOfNextExcerpt,
11700 window: &mut Window,
11701 cx: &mut Context<Self>,
11702 ) {
11703 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11704 cx.propagate();
11705 return;
11706 }
11707
11708 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11709 s.move_with(|map, selection| {
11710 selection.collapse_to(
11711 movement::start_of_excerpt(
11712 map,
11713 selection.head(),
11714 workspace::searchable::Direction::Next,
11715 ),
11716 SelectionGoal::None,
11717 )
11718 });
11719 })
11720 }
11721
11722 pub fn move_to_end_of_excerpt(
11723 &mut self,
11724 _: &MoveToEndOfExcerpt,
11725 window: &mut Window,
11726 cx: &mut Context<Self>,
11727 ) {
11728 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11729 cx.propagate();
11730 return;
11731 }
11732 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11733 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11734 s.move_with(|map, selection| {
11735 selection.collapse_to(
11736 movement::end_of_excerpt(
11737 map,
11738 selection.head(),
11739 workspace::searchable::Direction::Next,
11740 ),
11741 SelectionGoal::None,
11742 )
11743 });
11744 })
11745 }
11746
11747 pub fn move_to_end_of_previous_excerpt(
11748 &mut self,
11749 _: &MoveToEndOfPreviousExcerpt,
11750 window: &mut Window,
11751 cx: &mut Context<Self>,
11752 ) {
11753 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11754 cx.propagate();
11755 return;
11756 }
11757 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11758 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11759 s.move_with(|map, selection| {
11760 selection.collapse_to(
11761 movement::end_of_excerpt(
11762 map,
11763 selection.head(),
11764 workspace::searchable::Direction::Prev,
11765 ),
11766 SelectionGoal::None,
11767 )
11768 });
11769 })
11770 }
11771
11772 pub fn select_to_start_of_excerpt(
11773 &mut self,
11774 _: &SelectToStartOfExcerpt,
11775 window: &mut Window,
11776 cx: &mut Context<Self>,
11777 ) {
11778 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11779 cx.propagate();
11780 return;
11781 }
11782 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11783 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11784 s.move_heads_with(|map, head, _| {
11785 (
11786 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11787 SelectionGoal::None,
11788 )
11789 });
11790 })
11791 }
11792
11793 pub fn select_to_start_of_next_excerpt(
11794 &mut self,
11795 _: &SelectToStartOfNextExcerpt,
11796 window: &mut Window,
11797 cx: &mut Context<Self>,
11798 ) {
11799 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11800 cx.propagate();
11801 return;
11802 }
11803 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11804 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11805 s.move_heads_with(|map, head, _| {
11806 (
11807 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
11808 SelectionGoal::None,
11809 )
11810 });
11811 })
11812 }
11813
11814 pub fn select_to_end_of_excerpt(
11815 &mut self,
11816 _: &SelectToEndOfExcerpt,
11817 window: &mut Window,
11818 cx: &mut Context<Self>,
11819 ) {
11820 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11821 cx.propagate();
11822 return;
11823 }
11824 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11825 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11826 s.move_heads_with(|map, head, _| {
11827 (
11828 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
11829 SelectionGoal::None,
11830 )
11831 });
11832 })
11833 }
11834
11835 pub fn select_to_end_of_previous_excerpt(
11836 &mut self,
11837 _: &SelectToEndOfPreviousExcerpt,
11838 window: &mut Window,
11839 cx: &mut Context<Self>,
11840 ) {
11841 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11842 cx.propagate();
11843 return;
11844 }
11845 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11846 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11847 s.move_heads_with(|map, head, _| {
11848 (
11849 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11850 SelectionGoal::None,
11851 )
11852 });
11853 })
11854 }
11855
11856 pub fn move_to_beginning(
11857 &mut self,
11858 _: &MoveToBeginning,
11859 window: &mut Window,
11860 cx: &mut Context<Self>,
11861 ) {
11862 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11863 cx.propagate();
11864 return;
11865 }
11866 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11867 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11868 s.select_ranges(vec![0..0]);
11869 });
11870 }
11871
11872 pub fn select_to_beginning(
11873 &mut self,
11874 _: &SelectToBeginning,
11875 window: &mut Window,
11876 cx: &mut Context<Self>,
11877 ) {
11878 let mut selection = self.selections.last::<Point>(cx);
11879 selection.set_head(Point::zero(), SelectionGoal::None);
11880 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11881 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11882 s.select(vec![selection]);
11883 });
11884 }
11885
11886 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
11887 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11888 cx.propagate();
11889 return;
11890 }
11891 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11892 let cursor = self.buffer.read(cx).read(cx).len();
11893 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11894 s.select_ranges(vec![cursor..cursor])
11895 });
11896 }
11897
11898 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
11899 self.nav_history = nav_history;
11900 }
11901
11902 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
11903 self.nav_history.as_ref()
11904 }
11905
11906 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
11907 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
11908 }
11909
11910 fn push_to_nav_history(
11911 &mut self,
11912 cursor_anchor: Anchor,
11913 new_position: Option<Point>,
11914 is_deactivate: bool,
11915 cx: &mut Context<Self>,
11916 ) {
11917 if let Some(nav_history) = self.nav_history.as_mut() {
11918 let buffer = self.buffer.read(cx).read(cx);
11919 let cursor_position = cursor_anchor.to_point(&buffer);
11920 let scroll_state = self.scroll_manager.anchor();
11921 let scroll_top_row = scroll_state.top_row(&buffer);
11922 drop(buffer);
11923
11924 if let Some(new_position) = new_position {
11925 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
11926 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
11927 return;
11928 }
11929 }
11930
11931 nav_history.push(
11932 Some(NavigationData {
11933 cursor_anchor,
11934 cursor_position,
11935 scroll_anchor: scroll_state,
11936 scroll_top_row,
11937 }),
11938 cx,
11939 );
11940 cx.emit(EditorEvent::PushedToNavHistory {
11941 anchor: cursor_anchor,
11942 is_deactivate,
11943 })
11944 }
11945 }
11946
11947 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
11948 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11949 let buffer = self.buffer.read(cx).snapshot(cx);
11950 let mut selection = self.selections.first::<usize>(cx);
11951 selection.set_head(buffer.len(), SelectionGoal::None);
11952 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11953 s.select(vec![selection]);
11954 });
11955 }
11956
11957 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
11958 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11959 let end = self.buffer.read(cx).read(cx).len();
11960 self.change_selections(None, window, cx, |s| {
11961 s.select_ranges(vec![0..end]);
11962 });
11963 }
11964
11965 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
11966 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11967 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11968 let mut selections = self.selections.all::<Point>(cx);
11969 let max_point = display_map.buffer_snapshot.max_point();
11970 for selection in &mut selections {
11971 let rows = selection.spanned_rows(true, &display_map);
11972 selection.start = Point::new(rows.start.0, 0);
11973 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
11974 selection.reversed = false;
11975 }
11976 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11977 s.select(selections);
11978 });
11979 }
11980
11981 pub fn split_selection_into_lines(
11982 &mut self,
11983 _: &SplitSelectionIntoLines,
11984 window: &mut Window,
11985 cx: &mut Context<Self>,
11986 ) {
11987 let selections = self
11988 .selections
11989 .all::<Point>(cx)
11990 .into_iter()
11991 .map(|selection| selection.start..selection.end)
11992 .collect::<Vec<_>>();
11993 self.unfold_ranges(&selections, true, true, cx);
11994
11995 let mut new_selection_ranges = Vec::new();
11996 {
11997 let buffer = self.buffer.read(cx).read(cx);
11998 for selection in selections {
11999 for row in selection.start.row..selection.end.row {
12000 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12001 new_selection_ranges.push(cursor..cursor);
12002 }
12003
12004 let is_multiline_selection = selection.start.row != selection.end.row;
12005 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12006 // so this action feels more ergonomic when paired with other selection operations
12007 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12008 if !should_skip_last {
12009 new_selection_ranges.push(selection.end..selection.end);
12010 }
12011 }
12012 }
12013 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12014 s.select_ranges(new_selection_ranges);
12015 });
12016 }
12017
12018 pub fn add_selection_above(
12019 &mut self,
12020 _: &AddSelectionAbove,
12021 window: &mut Window,
12022 cx: &mut Context<Self>,
12023 ) {
12024 self.add_selection(true, window, cx);
12025 }
12026
12027 pub fn add_selection_below(
12028 &mut self,
12029 _: &AddSelectionBelow,
12030 window: &mut Window,
12031 cx: &mut Context<Self>,
12032 ) {
12033 self.add_selection(false, window, cx);
12034 }
12035
12036 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12037 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12038
12039 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12040 let mut selections = self.selections.all::<Point>(cx);
12041 let text_layout_details = self.text_layout_details(window);
12042 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12043 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12044 let range = oldest_selection.display_range(&display_map).sorted();
12045
12046 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12047 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12048 let positions = start_x.min(end_x)..start_x.max(end_x);
12049
12050 selections.clear();
12051 let mut stack = Vec::new();
12052 for row in range.start.row().0..=range.end.row().0 {
12053 if let Some(selection) = self.selections.build_columnar_selection(
12054 &display_map,
12055 DisplayRow(row),
12056 &positions,
12057 oldest_selection.reversed,
12058 &text_layout_details,
12059 ) {
12060 stack.push(selection.id);
12061 selections.push(selection);
12062 }
12063 }
12064
12065 if above {
12066 stack.reverse();
12067 }
12068
12069 AddSelectionsState { above, stack }
12070 });
12071
12072 let last_added_selection = *state.stack.last().unwrap();
12073 let mut new_selections = Vec::new();
12074 if above == state.above {
12075 let end_row = if above {
12076 DisplayRow(0)
12077 } else {
12078 display_map.max_point().row()
12079 };
12080
12081 'outer: for selection in selections {
12082 if selection.id == last_added_selection {
12083 let range = selection.display_range(&display_map).sorted();
12084 debug_assert_eq!(range.start.row(), range.end.row());
12085 let mut row = range.start.row();
12086 let positions =
12087 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12088 px(start)..px(end)
12089 } else {
12090 let start_x =
12091 display_map.x_for_display_point(range.start, &text_layout_details);
12092 let end_x =
12093 display_map.x_for_display_point(range.end, &text_layout_details);
12094 start_x.min(end_x)..start_x.max(end_x)
12095 };
12096
12097 while row != end_row {
12098 if above {
12099 row.0 -= 1;
12100 } else {
12101 row.0 += 1;
12102 }
12103
12104 if let Some(new_selection) = self.selections.build_columnar_selection(
12105 &display_map,
12106 row,
12107 &positions,
12108 selection.reversed,
12109 &text_layout_details,
12110 ) {
12111 state.stack.push(new_selection.id);
12112 if above {
12113 new_selections.push(new_selection);
12114 new_selections.push(selection);
12115 } else {
12116 new_selections.push(selection);
12117 new_selections.push(new_selection);
12118 }
12119
12120 continue 'outer;
12121 }
12122 }
12123 }
12124
12125 new_selections.push(selection);
12126 }
12127 } else {
12128 new_selections = selections;
12129 new_selections.retain(|s| s.id != last_added_selection);
12130 state.stack.pop();
12131 }
12132
12133 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12134 s.select(new_selections);
12135 });
12136 if state.stack.len() > 1 {
12137 self.add_selections_state = Some(state);
12138 }
12139 }
12140
12141 fn select_match_ranges(
12142 &mut self,
12143 range: Range<usize>,
12144 reversed: bool,
12145 replace_newest: bool,
12146 auto_scroll: Option<Autoscroll>,
12147 window: &mut Window,
12148 cx: &mut Context<Editor>,
12149 ) {
12150 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12151 self.change_selections(auto_scroll, window, cx, |s| {
12152 if replace_newest {
12153 s.delete(s.newest_anchor().id);
12154 }
12155 if reversed {
12156 s.insert_range(range.end..range.start);
12157 } else {
12158 s.insert_range(range);
12159 }
12160 });
12161 }
12162
12163 pub fn select_next_match_internal(
12164 &mut self,
12165 display_map: &DisplaySnapshot,
12166 replace_newest: bool,
12167 autoscroll: Option<Autoscroll>,
12168 window: &mut Window,
12169 cx: &mut Context<Self>,
12170 ) -> Result<()> {
12171 let buffer = &display_map.buffer_snapshot;
12172 let mut selections = self.selections.all::<usize>(cx);
12173 if let Some(mut select_next_state) = self.select_next_state.take() {
12174 let query = &select_next_state.query;
12175 if !select_next_state.done {
12176 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12177 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12178 let mut next_selected_range = None;
12179
12180 let bytes_after_last_selection =
12181 buffer.bytes_in_range(last_selection.end..buffer.len());
12182 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12183 let query_matches = query
12184 .stream_find_iter(bytes_after_last_selection)
12185 .map(|result| (last_selection.end, result))
12186 .chain(
12187 query
12188 .stream_find_iter(bytes_before_first_selection)
12189 .map(|result| (0, result)),
12190 );
12191
12192 for (start_offset, query_match) in query_matches {
12193 let query_match = query_match.unwrap(); // can only fail due to I/O
12194 let offset_range =
12195 start_offset + query_match.start()..start_offset + query_match.end();
12196 let display_range = offset_range.start.to_display_point(display_map)
12197 ..offset_range.end.to_display_point(display_map);
12198
12199 if !select_next_state.wordwise
12200 || (!movement::is_inside_word(display_map, display_range.start)
12201 && !movement::is_inside_word(display_map, display_range.end))
12202 {
12203 // TODO: This is n^2, because we might check all the selections
12204 if !selections
12205 .iter()
12206 .any(|selection| selection.range().overlaps(&offset_range))
12207 {
12208 next_selected_range = Some(offset_range);
12209 break;
12210 }
12211 }
12212 }
12213
12214 if let Some(next_selected_range) = next_selected_range {
12215 self.select_match_ranges(
12216 next_selected_range,
12217 last_selection.reversed,
12218 replace_newest,
12219 autoscroll,
12220 window,
12221 cx,
12222 );
12223 } else {
12224 select_next_state.done = true;
12225 }
12226 }
12227
12228 self.select_next_state = Some(select_next_state);
12229 } else {
12230 let mut only_carets = true;
12231 let mut same_text_selected = true;
12232 let mut selected_text = None;
12233
12234 let mut selections_iter = selections.iter().peekable();
12235 while let Some(selection) = selections_iter.next() {
12236 if selection.start != selection.end {
12237 only_carets = false;
12238 }
12239
12240 if same_text_selected {
12241 if selected_text.is_none() {
12242 selected_text =
12243 Some(buffer.text_for_range(selection.range()).collect::<String>());
12244 }
12245
12246 if let Some(next_selection) = selections_iter.peek() {
12247 if next_selection.range().len() == selection.range().len() {
12248 let next_selected_text = buffer
12249 .text_for_range(next_selection.range())
12250 .collect::<String>();
12251 if Some(next_selected_text) != selected_text {
12252 same_text_selected = false;
12253 selected_text = None;
12254 }
12255 } else {
12256 same_text_selected = false;
12257 selected_text = None;
12258 }
12259 }
12260 }
12261 }
12262
12263 if only_carets {
12264 for selection in &mut selections {
12265 let word_range = movement::surrounding_word(
12266 display_map,
12267 selection.start.to_display_point(display_map),
12268 );
12269 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12270 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12271 selection.goal = SelectionGoal::None;
12272 selection.reversed = false;
12273 self.select_match_ranges(
12274 selection.start..selection.end,
12275 selection.reversed,
12276 replace_newest,
12277 autoscroll,
12278 window,
12279 cx,
12280 );
12281 }
12282
12283 if selections.len() == 1 {
12284 let selection = selections
12285 .last()
12286 .expect("ensured that there's only one selection");
12287 let query = buffer
12288 .text_for_range(selection.start..selection.end)
12289 .collect::<String>();
12290 let is_empty = query.is_empty();
12291 let select_state = SelectNextState {
12292 query: AhoCorasick::new(&[query])?,
12293 wordwise: true,
12294 done: is_empty,
12295 };
12296 self.select_next_state = Some(select_state);
12297 } else {
12298 self.select_next_state = None;
12299 }
12300 } else if let Some(selected_text) = selected_text {
12301 self.select_next_state = Some(SelectNextState {
12302 query: AhoCorasick::new(&[selected_text])?,
12303 wordwise: false,
12304 done: false,
12305 });
12306 self.select_next_match_internal(
12307 display_map,
12308 replace_newest,
12309 autoscroll,
12310 window,
12311 cx,
12312 )?;
12313 }
12314 }
12315 Ok(())
12316 }
12317
12318 pub fn select_all_matches(
12319 &mut self,
12320 _action: &SelectAllMatches,
12321 window: &mut Window,
12322 cx: &mut Context<Self>,
12323 ) -> Result<()> {
12324 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12325
12326 self.push_to_selection_history();
12327 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12328
12329 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12330 let Some(select_next_state) = self.select_next_state.as_mut() else {
12331 return Ok(());
12332 };
12333 if select_next_state.done {
12334 return Ok(());
12335 }
12336
12337 let mut new_selections = Vec::new();
12338
12339 let reversed = self.selections.oldest::<usize>(cx).reversed;
12340 let buffer = &display_map.buffer_snapshot;
12341 let query_matches = select_next_state
12342 .query
12343 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12344
12345 for query_match in query_matches.into_iter() {
12346 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12347 let offset_range = if reversed {
12348 query_match.end()..query_match.start()
12349 } else {
12350 query_match.start()..query_match.end()
12351 };
12352 let display_range = offset_range.start.to_display_point(&display_map)
12353 ..offset_range.end.to_display_point(&display_map);
12354
12355 if !select_next_state.wordwise
12356 || (!movement::is_inside_word(&display_map, display_range.start)
12357 && !movement::is_inside_word(&display_map, display_range.end))
12358 {
12359 new_selections.push(offset_range.start..offset_range.end);
12360 }
12361 }
12362
12363 select_next_state.done = true;
12364 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12365 self.change_selections(None, window, cx, |selections| {
12366 selections.select_ranges(new_selections)
12367 });
12368
12369 Ok(())
12370 }
12371
12372 pub fn select_next(
12373 &mut self,
12374 action: &SelectNext,
12375 window: &mut Window,
12376 cx: &mut Context<Self>,
12377 ) -> Result<()> {
12378 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12379 self.push_to_selection_history();
12380 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12381 self.select_next_match_internal(
12382 &display_map,
12383 action.replace_newest,
12384 Some(Autoscroll::newest()),
12385 window,
12386 cx,
12387 )?;
12388 Ok(())
12389 }
12390
12391 pub fn select_previous(
12392 &mut self,
12393 action: &SelectPrevious,
12394 window: &mut Window,
12395 cx: &mut Context<Self>,
12396 ) -> Result<()> {
12397 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12398 self.push_to_selection_history();
12399 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12400 let buffer = &display_map.buffer_snapshot;
12401 let mut selections = self.selections.all::<usize>(cx);
12402 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12403 let query = &select_prev_state.query;
12404 if !select_prev_state.done {
12405 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12406 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12407 let mut next_selected_range = None;
12408 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12409 let bytes_before_last_selection =
12410 buffer.reversed_bytes_in_range(0..last_selection.start);
12411 let bytes_after_first_selection =
12412 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12413 let query_matches = query
12414 .stream_find_iter(bytes_before_last_selection)
12415 .map(|result| (last_selection.start, result))
12416 .chain(
12417 query
12418 .stream_find_iter(bytes_after_first_selection)
12419 .map(|result| (buffer.len(), result)),
12420 );
12421 for (end_offset, query_match) in query_matches {
12422 let query_match = query_match.unwrap(); // can only fail due to I/O
12423 let offset_range =
12424 end_offset - query_match.end()..end_offset - query_match.start();
12425 let display_range = offset_range.start.to_display_point(&display_map)
12426 ..offset_range.end.to_display_point(&display_map);
12427
12428 if !select_prev_state.wordwise
12429 || (!movement::is_inside_word(&display_map, display_range.start)
12430 && !movement::is_inside_word(&display_map, display_range.end))
12431 {
12432 next_selected_range = Some(offset_range);
12433 break;
12434 }
12435 }
12436
12437 if let Some(next_selected_range) = next_selected_range {
12438 self.select_match_ranges(
12439 next_selected_range,
12440 last_selection.reversed,
12441 action.replace_newest,
12442 Some(Autoscroll::newest()),
12443 window,
12444 cx,
12445 );
12446 } else {
12447 select_prev_state.done = true;
12448 }
12449 }
12450
12451 self.select_prev_state = Some(select_prev_state);
12452 } else {
12453 let mut only_carets = true;
12454 let mut same_text_selected = true;
12455 let mut selected_text = None;
12456
12457 let mut selections_iter = selections.iter().peekable();
12458 while let Some(selection) = selections_iter.next() {
12459 if selection.start != selection.end {
12460 only_carets = false;
12461 }
12462
12463 if same_text_selected {
12464 if selected_text.is_none() {
12465 selected_text =
12466 Some(buffer.text_for_range(selection.range()).collect::<String>());
12467 }
12468
12469 if let Some(next_selection) = selections_iter.peek() {
12470 if next_selection.range().len() == selection.range().len() {
12471 let next_selected_text = buffer
12472 .text_for_range(next_selection.range())
12473 .collect::<String>();
12474 if Some(next_selected_text) != selected_text {
12475 same_text_selected = false;
12476 selected_text = None;
12477 }
12478 } else {
12479 same_text_selected = false;
12480 selected_text = None;
12481 }
12482 }
12483 }
12484 }
12485
12486 if only_carets {
12487 for selection in &mut selections {
12488 let word_range = movement::surrounding_word(
12489 &display_map,
12490 selection.start.to_display_point(&display_map),
12491 );
12492 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12493 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12494 selection.goal = SelectionGoal::None;
12495 selection.reversed = false;
12496 self.select_match_ranges(
12497 selection.start..selection.end,
12498 selection.reversed,
12499 action.replace_newest,
12500 Some(Autoscroll::newest()),
12501 window,
12502 cx,
12503 );
12504 }
12505 if selections.len() == 1 {
12506 let selection = selections
12507 .last()
12508 .expect("ensured that there's only one selection");
12509 let query = buffer
12510 .text_for_range(selection.start..selection.end)
12511 .collect::<String>();
12512 let is_empty = query.is_empty();
12513 let select_state = SelectNextState {
12514 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12515 wordwise: true,
12516 done: is_empty,
12517 };
12518 self.select_prev_state = Some(select_state);
12519 } else {
12520 self.select_prev_state = None;
12521 }
12522 } else if let Some(selected_text) = selected_text {
12523 self.select_prev_state = Some(SelectNextState {
12524 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12525 wordwise: false,
12526 done: false,
12527 });
12528 self.select_previous(action, window, cx)?;
12529 }
12530 }
12531 Ok(())
12532 }
12533
12534 pub fn find_next_match(
12535 &mut self,
12536 _: &FindNextMatch,
12537 window: &mut Window,
12538 cx: &mut Context<Self>,
12539 ) -> Result<()> {
12540 let selections = self.selections.disjoint_anchors();
12541 match selections.first() {
12542 Some(first) if selections.len() >= 2 => {
12543 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12544 s.select_ranges([first.range()]);
12545 });
12546 }
12547 _ => self.select_next(
12548 &SelectNext {
12549 replace_newest: true,
12550 },
12551 window,
12552 cx,
12553 )?,
12554 }
12555 Ok(())
12556 }
12557
12558 pub fn find_previous_match(
12559 &mut self,
12560 _: &FindPreviousMatch,
12561 window: &mut Window,
12562 cx: &mut Context<Self>,
12563 ) -> Result<()> {
12564 let selections = self.selections.disjoint_anchors();
12565 match selections.last() {
12566 Some(last) if selections.len() >= 2 => {
12567 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12568 s.select_ranges([last.range()]);
12569 });
12570 }
12571 _ => self.select_previous(
12572 &SelectPrevious {
12573 replace_newest: true,
12574 },
12575 window,
12576 cx,
12577 )?,
12578 }
12579 Ok(())
12580 }
12581
12582 pub fn toggle_comments(
12583 &mut self,
12584 action: &ToggleComments,
12585 window: &mut Window,
12586 cx: &mut Context<Self>,
12587 ) {
12588 if self.read_only(cx) {
12589 return;
12590 }
12591 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12592 let text_layout_details = &self.text_layout_details(window);
12593 self.transact(window, cx, |this, window, cx| {
12594 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12595 let mut edits = Vec::new();
12596 let mut selection_edit_ranges = Vec::new();
12597 let mut last_toggled_row = None;
12598 let snapshot = this.buffer.read(cx).read(cx);
12599 let empty_str: Arc<str> = Arc::default();
12600 let mut suffixes_inserted = Vec::new();
12601 let ignore_indent = action.ignore_indent;
12602
12603 fn comment_prefix_range(
12604 snapshot: &MultiBufferSnapshot,
12605 row: MultiBufferRow,
12606 comment_prefix: &str,
12607 comment_prefix_whitespace: &str,
12608 ignore_indent: bool,
12609 ) -> Range<Point> {
12610 let indent_size = if ignore_indent {
12611 0
12612 } else {
12613 snapshot.indent_size_for_line(row).len
12614 };
12615
12616 let start = Point::new(row.0, indent_size);
12617
12618 let mut line_bytes = snapshot
12619 .bytes_in_range(start..snapshot.max_point())
12620 .flatten()
12621 .copied();
12622
12623 // If this line currently begins with the line comment prefix, then record
12624 // the range containing the prefix.
12625 if line_bytes
12626 .by_ref()
12627 .take(comment_prefix.len())
12628 .eq(comment_prefix.bytes())
12629 {
12630 // Include any whitespace that matches the comment prefix.
12631 let matching_whitespace_len = line_bytes
12632 .zip(comment_prefix_whitespace.bytes())
12633 .take_while(|(a, b)| a == b)
12634 .count() as u32;
12635 let end = Point::new(
12636 start.row,
12637 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12638 );
12639 start..end
12640 } else {
12641 start..start
12642 }
12643 }
12644
12645 fn comment_suffix_range(
12646 snapshot: &MultiBufferSnapshot,
12647 row: MultiBufferRow,
12648 comment_suffix: &str,
12649 comment_suffix_has_leading_space: bool,
12650 ) -> Range<Point> {
12651 let end = Point::new(row.0, snapshot.line_len(row));
12652 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12653
12654 let mut line_end_bytes = snapshot
12655 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12656 .flatten()
12657 .copied();
12658
12659 let leading_space_len = if suffix_start_column > 0
12660 && line_end_bytes.next() == Some(b' ')
12661 && comment_suffix_has_leading_space
12662 {
12663 1
12664 } else {
12665 0
12666 };
12667
12668 // If this line currently begins with the line comment prefix, then record
12669 // the range containing the prefix.
12670 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12671 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12672 start..end
12673 } else {
12674 end..end
12675 }
12676 }
12677
12678 // TODO: Handle selections that cross excerpts
12679 for selection in &mut selections {
12680 let start_column = snapshot
12681 .indent_size_for_line(MultiBufferRow(selection.start.row))
12682 .len;
12683 let language = if let Some(language) =
12684 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12685 {
12686 language
12687 } else {
12688 continue;
12689 };
12690
12691 selection_edit_ranges.clear();
12692
12693 // If multiple selections contain a given row, avoid processing that
12694 // row more than once.
12695 let mut start_row = MultiBufferRow(selection.start.row);
12696 if last_toggled_row == Some(start_row) {
12697 start_row = start_row.next_row();
12698 }
12699 let end_row =
12700 if selection.end.row > selection.start.row && selection.end.column == 0 {
12701 MultiBufferRow(selection.end.row - 1)
12702 } else {
12703 MultiBufferRow(selection.end.row)
12704 };
12705 last_toggled_row = Some(end_row);
12706
12707 if start_row > end_row {
12708 continue;
12709 }
12710
12711 // If the language has line comments, toggle those.
12712 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12713
12714 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12715 if ignore_indent {
12716 full_comment_prefixes = full_comment_prefixes
12717 .into_iter()
12718 .map(|s| Arc::from(s.trim_end()))
12719 .collect();
12720 }
12721
12722 if !full_comment_prefixes.is_empty() {
12723 let first_prefix = full_comment_prefixes
12724 .first()
12725 .expect("prefixes is non-empty");
12726 let prefix_trimmed_lengths = full_comment_prefixes
12727 .iter()
12728 .map(|p| p.trim_end_matches(' ').len())
12729 .collect::<SmallVec<[usize; 4]>>();
12730
12731 let mut all_selection_lines_are_comments = true;
12732
12733 for row in start_row.0..=end_row.0 {
12734 let row = MultiBufferRow(row);
12735 if start_row < end_row && snapshot.is_line_blank(row) {
12736 continue;
12737 }
12738
12739 let prefix_range = full_comment_prefixes
12740 .iter()
12741 .zip(prefix_trimmed_lengths.iter().copied())
12742 .map(|(prefix, trimmed_prefix_len)| {
12743 comment_prefix_range(
12744 snapshot.deref(),
12745 row,
12746 &prefix[..trimmed_prefix_len],
12747 &prefix[trimmed_prefix_len..],
12748 ignore_indent,
12749 )
12750 })
12751 .max_by_key(|range| range.end.column - range.start.column)
12752 .expect("prefixes is non-empty");
12753
12754 if prefix_range.is_empty() {
12755 all_selection_lines_are_comments = false;
12756 }
12757
12758 selection_edit_ranges.push(prefix_range);
12759 }
12760
12761 if all_selection_lines_are_comments {
12762 edits.extend(
12763 selection_edit_ranges
12764 .iter()
12765 .cloned()
12766 .map(|range| (range, empty_str.clone())),
12767 );
12768 } else {
12769 let min_column = selection_edit_ranges
12770 .iter()
12771 .map(|range| range.start.column)
12772 .min()
12773 .unwrap_or(0);
12774 edits.extend(selection_edit_ranges.iter().map(|range| {
12775 let position = Point::new(range.start.row, min_column);
12776 (position..position, first_prefix.clone())
12777 }));
12778 }
12779 } else if let Some((full_comment_prefix, comment_suffix)) =
12780 language.block_comment_delimiters()
12781 {
12782 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
12783 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
12784 let prefix_range = comment_prefix_range(
12785 snapshot.deref(),
12786 start_row,
12787 comment_prefix,
12788 comment_prefix_whitespace,
12789 ignore_indent,
12790 );
12791 let suffix_range = comment_suffix_range(
12792 snapshot.deref(),
12793 end_row,
12794 comment_suffix.trim_start_matches(' '),
12795 comment_suffix.starts_with(' '),
12796 );
12797
12798 if prefix_range.is_empty() || suffix_range.is_empty() {
12799 edits.push((
12800 prefix_range.start..prefix_range.start,
12801 full_comment_prefix.clone(),
12802 ));
12803 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
12804 suffixes_inserted.push((end_row, comment_suffix.len()));
12805 } else {
12806 edits.push((prefix_range, empty_str.clone()));
12807 edits.push((suffix_range, empty_str.clone()));
12808 }
12809 } else {
12810 continue;
12811 }
12812 }
12813
12814 drop(snapshot);
12815 this.buffer.update(cx, |buffer, cx| {
12816 buffer.edit(edits, None, cx);
12817 });
12818
12819 // Adjust selections so that they end before any comment suffixes that
12820 // were inserted.
12821 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
12822 let mut selections = this.selections.all::<Point>(cx);
12823 let snapshot = this.buffer.read(cx).read(cx);
12824 for selection in &mut selections {
12825 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
12826 match row.cmp(&MultiBufferRow(selection.end.row)) {
12827 Ordering::Less => {
12828 suffixes_inserted.next();
12829 continue;
12830 }
12831 Ordering::Greater => break,
12832 Ordering::Equal => {
12833 if selection.end.column == snapshot.line_len(row) {
12834 if selection.is_empty() {
12835 selection.start.column -= suffix_len as u32;
12836 }
12837 selection.end.column -= suffix_len as u32;
12838 }
12839 break;
12840 }
12841 }
12842 }
12843 }
12844
12845 drop(snapshot);
12846 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12847 s.select(selections)
12848 });
12849
12850 let selections = this.selections.all::<Point>(cx);
12851 let selections_on_single_row = selections.windows(2).all(|selections| {
12852 selections[0].start.row == selections[1].start.row
12853 && selections[0].end.row == selections[1].end.row
12854 && selections[0].start.row == selections[0].end.row
12855 });
12856 let selections_selecting = selections
12857 .iter()
12858 .any(|selection| selection.start != selection.end);
12859 let advance_downwards = action.advance_downwards
12860 && selections_on_single_row
12861 && !selections_selecting
12862 && !matches!(this.mode, EditorMode::SingleLine { .. });
12863
12864 if advance_downwards {
12865 let snapshot = this.buffer.read(cx).snapshot(cx);
12866
12867 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12868 s.move_cursors_with(|display_snapshot, display_point, _| {
12869 let mut point = display_point.to_point(display_snapshot);
12870 point.row += 1;
12871 point = snapshot.clip_point(point, Bias::Left);
12872 let display_point = point.to_display_point(display_snapshot);
12873 let goal = SelectionGoal::HorizontalPosition(
12874 display_snapshot
12875 .x_for_display_point(display_point, text_layout_details)
12876 .into(),
12877 );
12878 (display_point, goal)
12879 })
12880 });
12881 }
12882 });
12883 }
12884
12885 pub fn select_enclosing_symbol(
12886 &mut self,
12887 _: &SelectEnclosingSymbol,
12888 window: &mut Window,
12889 cx: &mut Context<Self>,
12890 ) {
12891 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12892
12893 let buffer = self.buffer.read(cx).snapshot(cx);
12894 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
12895
12896 fn update_selection(
12897 selection: &Selection<usize>,
12898 buffer_snap: &MultiBufferSnapshot,
12899 ) -> Option<Selection<usize>> {
12900 let cursor = selection.head();
12901 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
12902 for symbol in symbols.iter().rev() {
12903 let start = symbol.range.start.to_offset(buffer_snap);
12904 let end = symbol.range.end.to_offset(buffer_snap);
12905 let new_range = start..end;
12906 if start < selection.start || end > selection.end {
12907 return Some(Selection {
12908 id: selection.id,
12909 start: new_range.start,
12910 end: new_range.end,
12911 goal: SelectionGoal::None,
12912 reversed: selection.reversed,
12913 });
12914 }
12915 }
12916 None
12917 }
12918
12919 let mut selected_larger_symbol = false;
12920 let new_selections = old_selections
12921 .iter()
12922 .map(|selection| match update_selection(selection, &buffer) {
12923 Some(new_selection) => {
12924 if new_selection.range() != selection.range() {
12925 selected_larger_symbol = true;
12926 }
12927 new_selection
12928 }
12929 None => selection.clone(),
12930 })
12931 .collect::<Vec<_>>();
12932
12933 if selected_larger_symbol {
12934 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12935 s.select(new_selections);
12936 });
12937 }
12938 }
12939
12940 pub fn select_larger_syntax_node(
12941 &mut self,
12942 _: &SelectLargerSyntaxNode,
12943 window: &mut Window,
12944 cx: &mut Context<Self>,
12945 ) {
12946 let Some(visible_row_count) = self.visible_row_count() else {
12947 return;
12948 };
12949 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
12950 if old_selections.is_empty() {
12951 return;
12952 }
12953
12954 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12955
12956 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12957 let buffer = self.buffer.read(cx).snapshot(cx);
12958
12959 let mut selected_larger_node = false;
12960 let mut new_selections = old_selections
12961 .iter()
12962 .map(|selection| {
12963 let old_range = selection.start..selection.end;
12964
12965 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
12966 // manually select word at selection
12967 if ["string_content", "inline"].contains(&node.kind()) {
12968 let word_range = {
12969 let display_point = buffer
12970 .offset_to_point(old_range.start)
12971 .to_display_point(&display_map);
12972 let Range { start, end } =
12973 movement::surrounding_word(&display_map, display_point);
12974 start.to_point(&display_map).to_offset(&buffer)
12975 ..end.to_point(&display_map).to_offset(&buffer)
12976 };
12977 // ignore if word is already selected
12978 if !word_range.is_empty() && old_range != word_range {
12979 let last_word_range = {
12980 let display_point = buffer
12981 .offset_to_point(old_range.end)
12982 .to_display_point(&display_map);
12983 let Range { start, end } =
12984 movement::surrounding_word(&display_map, display_point);
12985 start.to_point(&display_map).to_offset(&buffer)
12986 ..end.to_point(&display_map).to_offset(&buffer)
12987 };
12988 // only select word if start and end point belongs to same word
12989 if word_range == last_word_range {
12990 selected_larger_node = true;
12991 return Selection {
12992 id: selection.id,
12993 start: word_range.start,
12994 end: word_range.end,
12995 goal: SelectionGoal::None,
12996 reversed: selection.reversed,
12997 };
12998 }
12999 }
13000 }
13001 }
13002
13003 let mut new_range = old_range.clone();
13004 while let Some((_node, containing_range)) =
13005 buffer.syntax_ancestor(new_range.clone())
13006 {
13007 new_range = match containing_range {
13008 MultiOrSingleBufferOffsetRange::Single(_) => break,
13009 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13010 };
13011 if !display_map.intersects_fold(new_range.start)
13012 && !display_map.intersects_fold(new_range.end)
13013 {
13014 break;
13015 }
13016 }
13017
13018 selected_larger_node |= new_range != old_range;
13019 Selection {
13020 id: selection.id,
13021 start: new_range.start,
13022 end: new_range.end,
13023 goal: SelectionGoal::None,
13024 reversed: selection.reversed,
13025 }
13026 })
13027 .collect::<Vec<_>>();
13028
13029 if !selected_larger_node {
13030 return; // don't put this call in the history
13031 }
13032
13033 // scroll based on transformation done to the last selection created by the user
13034 let (last_old, last_new) = old_selections
13035 .last()
13036 .zip(new_selections.last().cloned())
13037 .expect("old_selections isn't empty");
13038
13039 // revert selection
13040 let is_selection_reversed = {
13041 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13042 new_selections.last_mut().expect("checked above").reversed =
13043 should_newest_selection_be_reversed;
13044 should_newest_selection_be_reversed
13045 };
13046
13047 if selected_larger_node {
13048 self.select_syntax_node_history.disable_clearing = true;
13049 self.change_selections(None, window, cx, |s| {
13050 s.select(new_selections.clone());
13051 });
13052 self.select_syntax_node_history.disable_clearing = false;
13053 }
13054
13055 let start_row = last_new.start.to_display_point(&display_map).row().0;
13056 let end_row = last_new.end.to_display_point(&display_map).row().0;
13057 let selection_height = end_row - start_row + 1;
13058 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13059
13060 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13061 let scroll_behavior = if fits_on_the_screen {
13062 self.request_autoscroll(Autoscroll::fit(), cx);
13063 SelectSyntaxNodeScrollBehavior::FitSelection
13064 } else if is_selection_reversed {
13065 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13066 SelectSyntaxNodeScrollBehavior::CursorTop
13067 } else {
13068 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13069 SelectSyntaxNodeScrollBehavior::CursorBottom
13070 };
13071
13072 self.select_syntax_node_history.push((
13073 old_selections,
13074 scroll_behavior,
13075 is_selection_reversed,
13076 ));
13077 }
13078
13079 pub fn select_smaller_syntax_node(
13080 &mut self,
13081 _: &SelectSmallerSyntaxNode,
13082 window: &mut Window,
13083 cx: &mut Context<Self>,
13084 ) {
13085 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13086
13087 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13088 self.select_syntax_node_history.pop()
13089 {
13090 if let Some(selection) = selections.last_mut() {
13091 selection.reversed = is_selection_reversed;
13092 }
13093
13094 self.select_syntax_node_history.disable_clearing = true;
13095 self.change_selections(None, window, cx, |s| {
13096 s.select(selections.to_vec());
13097 });
13098 self.select_syntax_node_history.disable_clearing = false;
13099
13100 match scroll_behavior {
13101 SelectSyntaxNodeScrollBehavior::CursorTop => {
13102 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13103 }
13104 SelectSyntaxNodeScrollBehavior::FitSelection => {
13105 self.request_autoscroll(Autoscroll::fit(), cx);
13106 }
13107 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13108 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13109 }
13110 }
13111 }
13112 }
13113
13114 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13115 if !EditorSettings::get_global(cx).gutter.runnables {
13116 self.clear_tasks();
13117 return Task::ready(());
13118 }
13119 let project = self.project.as_ref().map(Entity::downgrade);
13120 let task_sources = self.lsp_task_sources(cx);
13121 cx.spawn_in(window, async move |editor, cx| {
13122 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13123 let Some(project) = project.and_then(|p| p.upgrade()) else {
13124 return;
13125 };
13126 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13127 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13128 }) else {
13129 return;
13130 };
13131
13132 let hide_runnables = project
13133 .update(cx, |project, cx| {
13134 // Do not display any test indicators in non-dev server remote projects.
13135 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13136 })
13137 .unwrap_or(true);
13138 if hide_runnables {
13139 return;
13140 }
13141 let new_rows =
13142 cx.background_spawn({
13143 let snapshot = display_snapshot.clone();
13144 async move {
13145 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13146 }
13147 })
13148 .await;
13149 let Ok(lsp_tasks) =
13150 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13151 else {
13152 return;
13153 };
13154 let lsp_tasks = lsp_tasks.await;
13155
13156 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13157 lsp_tasks
13158 .into_iter()
13159 .flat_map(|(kind, tasks)| {
13160 tasks.into_iter().filter_map(move |(location, task)| {
13161 Some((kind.clone(), location?, task))
13162 })
13163 })
13164 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13165 let buffer = location.target.buffer;
13166 let buffer_snapshot = buffer.read(cx).snapshot();
13167 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13168 |(excerpt_id, snapshot, _)| {
13169 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13170 display_snapshot
13171 .buffer_snapshot
13172 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13173 } else {
13174 None
13175 }
13176 },
13177 );
13178 if let Some(offset) = offset {
13179 let task_buffer_range =
13180 location.target.range.to_point(&buffer_snapshot);
13181 let context_buffer_range =
13182 task_buffer_range.to_offset(&buffer_snapshot);
13183 let context_range = BufferOffset(context_buffer_range.start)
13184 ..BufferOffset(context_buffer_range.end);
13185
13186 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13187 .or_insert_with(|| RunnableTasks {
13188 templates: Vec::new(),
13189 offset,
13190 column: task_buffer_range.start.column,
13191 extra_variables: HashMap::default(),
13192 context_range,
13193 })
13194 .templates
13195 .push((kind, task.original_task().clone()));
13196 }
13197
13198 acc
13199 })
13200 }) else {
13201 return;
13202 };
13203
13204 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
13205 editor
13206 .update(cx, |editor, _| {
13207 editor.clear_tasks();
13208 for (key, mut value) in rows {
13209 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13210 value.templates.extend(lsp_tasks.templates);
13211 }
13212
13213 editor.insert_tasks(key, value);
13214 }
13215 for (key, value) in lsp_tasks_by_rows {
13216 editor.insert_tasks(key, value);
13217 }
13218 })
13219 .ok();
13220 })
13221 }
13222 fn fetch_runnable_ranges(
13223 snapshot: &DisplaySnapshot,
13224 range: Range<Anchor>,
13225 ) -> Vec<language::RunnableRange> {
13226 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13227 }
13228
13229 fn runnable_rows(
13230 project: Entity<Project>,
13231 snapshot: DisplaySnapshot,
13232 runnable_ranges: Vec<RunnableRange>,
13233 mut cx: AsyncWindowContext,
13234 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13235 runnable_ranges
13236 .into_iter()
13237 .filter_map(|mut runnable| {
13238 let tasks = cx
13239 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13240 .ok()?;
13241 if tasks.is_empty() {
13242 return None;
13243 }
13244
13245 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13246
13247 let row = snapshot
13248 .buffer_snapshot
13249 .buffer_line_for_row(MultiBufferRow(point.row))?
13250 .1
13251 .start
13252 .row;
13253
13254 let context_range =
13255 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13256 Some((
13257 (runnable.buffer_id, row),
13258 RunnableTasks {
13259 templates: tasks,
13260 offset: snapshot
13261 .buffer_snapshot
13262 .anchor_before(runnable.run_range.start),
13263 context_range,
13264 column: point.column,
13265 extra_variables: runnable.extra_captures,
13266 },
13267 ))
13268 })
13269 .collect()
13270 }
13271
13272 fn templates_with_tags(
13273 project: &Entity<Project>,
13274 runnable: &mut Runnable,
13275 cx: &mut App,
13276 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13277 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13278 let (worktree_id, file) = project
13279 .buffer_for_id(runnable.buffer, cx)
13280 .and_then(|buffer| buffer.read(cx).file())
13281 .map(|file| (file.worktree_id(cx), file.clone()))
13282 .unzip();
13283
13284 (
13285 project.task_store().read(cx).task_inventory().cloned(),
13286 worktree_id,
13287 file,
13288 )
13289 });
13290
13291 let mut templates_with_tags = mem::take(&mut runnable.tags)
13292 .into_iter()
13293 .flat_map(|RunnableTag(tag)| {
13294 inventory
13295 .as_ref()
13296 .into_iter()
13297 .flat_map(|inventory| {
13298 inventory.read(cx).list_tasks(
13299 file.clone(),
13300 Some(runnable.language.clone()),
13301 worktree_id,
13302 cx,
13303 )
13304 })
13305 .filter(move |(_, template)| {
13306 template.tags.iter().any(|source_tag| source_tag == &tag)
13307 })
13308 })
13309 .sorted_by_key(|(kind, _)| kind.to_owned())
13310 .collect::<Vec<_>>();
13311 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13312 // Strongest source wins; if we have worktree tag binding, prefer that to
13313 // global and language bindings;
13314 // if we have a global binding, prefer that to language binding.
13315 let first_mismatch = templates_with_tags
13316 .iter()
13317 .position(|(tag_source, _)| tag_source != leading_tag_source);
13318 if let Some(index) = first_mismatch {
13319 templates_with_tags.truncate(index);
13320 }
13321 }
13322
13323 templates_with_tags
13324 }
13325
13326 pub fn move_to_enclosing_bracket(
13327 &mut self,
13328 _: &MoveToEnclosingBracket,
13329 window: &mut Window,
13330 cx: &mut Context<Self>,
13331 ) {
13332 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13333 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13334 s.move_offsets_with(|snapshot, selection| {
13335 let Some(enclosing_bracket_ranges) =
13336 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13337 else {
13338 return;
13339 };
13340
13341 let mut best_length = usize::MAX;
13342 let mut best_inside = false;
13343 let mut best_in_bracket_range = false;
13344 let mut best_destination = None;
13345 for (open, close) in enclosing_bracket_ranges {
13346 let close = close.to_inclusive();
13347 let length = close.end() - open.start;
13348 let inside = selection.start >= open.end && selection.end <= *close.start();
13349 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13350 || close.contains(&selection.head());
13351
13352 // If best is next to a bracket and current isn't, skip
13353 if !in_bracket_range && best_in_bracket_range {
13354 continue;
13355 }
13356
13357 // Prefer smaller lengths unless best is inside and current isn't
13358 if length > best_length && (best_inside || !inside) {
13359 continue;
13360 }
13361
13362 best_length = length;
13363 best_inside = inside;
13364 best_in_bracket_range = in_bracket_range;
13365 best_destination = Some(
13366 if close.contains(&selection.start) && close.contains(&selection.end) {
13367 if inside { open.end } else { open.start }
13368 } else if inside {
13369 *close.start()
13370 } else {
13371 *close.end()
13372 },
13373 );
13374 }
13375
13376 if let Some(destination) = best_destination {
13377 selection.collapse_to(destination, SelectionGoal::None);
13378 }
13379 })
13380 });
13381 }
13382
13383 pub fn undo_selection(
13384 &mut self,
13385 _: &UndoSelection,
13386 window: &mut Window,
13387 cx: &mut Context<Self>,
13388 ) {
13389 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13390 self.end_selection(window, cx);
13391 self.selection_history.mode = SelectionHistoryMode::Undoing;
13392 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13393 self.change_selections(None, window, cx, |s| {
13394 s.select_anchors(entry.selections.to_vec())
13395 });
13396 self.select_next_state = entry.select_next_state;
13397 self.select_prev_state = entry.select_prev_state;
13398 self.add_selections_state = entry.add_selections_state;
13399 self.request_autoscroll(Autoscroll::newest(), cx);
13400 }
13401 self.selection_history.mode = SelectionHistoryMode::Normal;
13402 }
13403
13404 pub fn redo_selection(
13405 &mut self,
13406 _: &RedoSelection,
13407 window: &mut Window,
13408 cx: &mut Context<Self>,
13409 ) {
13410 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13411 self.end_selection(window, cx);
13412 self.selection_history.mode = SelectionHistoryMode::Redoing;
13413 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13414 self.change_selections(None, window, cx, |s| {
13415 s.select_anchors(entry.selections.to_vec())
13416 });
13417 self.select_next_state = entry.select_next_state;
13418 self.select_prev_state = entry.select_prev_state;
13419 self.add_selections_state = entry.add_selections_state;
13420 self.request_autoscroll(Autoscroll::newest(), cx);
13421 }
13422 self.selection_history.mode = SelectionHistoryMode::Normal;
13423 }
13424
13425 pub fn expand_excerpts(
13426 &mut self,
13427 action: &ExpandExcerpts,
13428 _: &mut Window,
13429 cx: &mut Context<Self>,
13430 ) {
13431 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13432 }
13433
13434 pub fn expand_excerpts_down(
13435 &mut self,
13436 action: &ExpandExcerptsDown,
13437 _: &mut Window,
13438 cx: &mut Context<Self>,
13439 ) {
13440 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13441 }
13442
13443 pub fn expand_excerpts_up(
13444 &mut self,
13445 action: &ExpandExcerptsUp,
13446 _: &mut Window,
13447 cx: &mut Context<Self>,
13448 ) {
13449 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13450 }
13451
13452 pub fn expand_excerpts_for_direction(
13453 &mut self,
13454 lines: u32,
13455 direction: ExpandExcerptDirection,
13456
13457 cx: &mut Context<Self>,
13458 ) {
13459 let selections = self.selections.disjoint_anchors();
13460
13461 let lines = if lines == 0 {
13462 EditorSettings::get_global(cx).expand_excerpt_lines
13463 } else {
13464 lines
13465 };
13466
13467 self.buffer.update(cx, |buffer, cx| {
13468 let snapshot = buffer.snapshot(cx);
13469 let mut excerpt_ids = selections
13470 .iter()
13471 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13472 .collect::<Vec<_>>();
13473 excerpt_ids.sort();
13474 excerpt_ids.dedup();
13475 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13476 })
13477 }
13478
13479 pub fn expand_excerpt(
13480 &mut self,
13481 excerpt: ExcerptId,
13482 direction: ExpandExcerptDirection,
13483 window: &mut Window,
13484 cx: &mut Context<Self>,
13485 ) {
13486 let current_scroll_position = self.scroll_position(cx);
13487 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13488 let mut should_scroll_up = false;
13489
13490 if direction == ExpandExcerptDirection::Down {
13491 let multi_buffer = self.buffer.read(cx);
13492 let snapshot = multi_buffer.snapshot(cx);
13493 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13494 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13495 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13496 let buffer_snapshot = buffer.read(cx).snapshot();
13497 let excerpt_end_row =
13498 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13499 let last_row = buffer_snapshot.max_point().row;
13500 let lines_below = last_row.saturating_sub(excerpt_end_row);
13501 should_scroll_up = lines_below >= lines_to_expand;
13502 }
13503 }
13504 }
13505 }
13506
13507 self.buffer.update(cx, |buffer, cx| {
13508 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13509 });
13510
13511 if should_scroll_up {
13512 let new_scroll_position =
13513 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13514 self.set_scroll_position(new_scroll_position, window, cx);
13515 }
13516 }
13517
13518 pub fn go_to_singleton_buffer_point(
13519 &mut self,
13520 point: Point,
13521 window: &mut Window,
13522 cx: &mut Context<Self>,
13523 ) {
13524 self.go_to_singleton_buffer_range(point..point, window, cx);
13525 }
13526
13527 pub fn go_to_singleton_buffer_range(
13528 &mut self,
13529 range: Range<Point>,
13530 window: &mut Window,
13531 cx: &mut Context<Self>,
13532 ) {
13533 let multibuffer = self.buffer().read(cx);
13534 let Some(buffer) = multibuffer.as_singleton() else {
13535 return;
13536 };
13537 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13538 return;
13539 };
13540 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13541 return;
13542 };
13543 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13544 s.select_anchor_ranges([start..end])
13545 });
13546 }
13547
13548 pub fn go_to_diagnostic(
13549 &mut self,
13550 _: &GoToDiagnostic,
13551 window: &mut Window,
13552 cx: &mut Context<Self>,
13553 ) {
13554 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13555 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13556 }
13557
13558 pub fn go_to_prev_diagnostic(
13559 &mut self,
13560 _: &GoToPreviousDiagnostic,
13561 window: &mut Window,
13562 cx: &mut Context<Self>,
13563 ) {
13564 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13565 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13566 }
13567
13568 pub fn go_to_diagnostic_impl(
13569 &mut self,
13570 direction: Direction,
13571 window: &mut Window,
13572 cx: &mut Context<Self>,
13573 ) {
13574 let buffer = self.buffer.read(cx).snapshot(cx);
13575 let selection = self.selections.newest::<usize>(cx);
13576
13577 let mut active_group_id = None;
13578 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13579 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13580 active_group_id = Some(active_group.group_id);
13581 }
13582 }
13583
13584 fn filtered(
13585 snapshot: EditorSnapshot,
13586 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13587 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13588 diagnostics
13589 .filter(|entry| entry.range.start != entry.range.end)
13590 .filter(|entry| !entry.diagnostic.is_unnecessary)
13591 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13592 }
13593
13594 let snapshot = self.snapshot(window, cx);
13595 let before = filtered(
13596 snapshot.clone(),
13597 buffer
13598 .diagnostics_in_range(0..selection.start)
13599 .filter(|entry| entry.range.start <= selection.start),
13600 );
13601 let after = filtered(
13602 snapshot,
13603 buffer
13604 .diagnostics_in_range(selection.start..buffer.len())
13605 .filter(|entry| entry.range.start >= selection.start),
13606 );
13607
13608 let mut found: Option<DiagnosticEntry<usize>> = None;
13609 if direction == Direction::Prev {
13610 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13611 {
13612 for diagnostic in prev_diagnostics.into_iter().rev() {
13613 if diagnostic.range.start != selection.start
13614 || active_group_id
13615 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13616 {
13617 found = Some(diagnostic);
13618 break 'outer;
13619 }
13620 }
13621 }
13622 } else {
13623 for diagnostic in after.chain(before) {
13624 if diagnostic.range.start != selection.start
13625 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13626 {
13627 found = Some(diagnostic);
13628 break;
13629 }
13630 }
13631 }
13632 let Some(next_diagnostic) = found else {
13633 return;
13634 };
13635
13636 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13637 return;
13638 };
13639 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13640 s.select_ranges(vec![
13641 next_diagnostic.range.start..next_diagnostic.range.start,
13642 ])
13643 });
13644 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13645 self.refresh_inline_completion(false, true, window, cx);
13646 }
13647
13648 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13649 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13650 let snapshot = self.snapshot(window, cx);
13651 let selection = self.selections.newest::<Point>(cx);
13652 self.go_to_hunk_before_or_after_position(
13653 &snapshot,
13654 selection.head(),
13655 Direction::Next,
13656 window,
13657 cx,
13658 );
13659 }
13660
13661 pub fn go_to_hunk_before_or_after_position(
13662 &mut self,
13663 snapshot: &EditorSnapshot,
13664 position: Point,
13665 direction: Direction,
13666 window: &mut Window,
13667 cx: &mut Context<Editor>,
13668 ) {
13669 let row = if direction == Direction::Next {
13670 self.hunk_after_position(snapshot, position)
13671 .map(|hunk| hunk.row_range.start)
13672 } else {
13673 self.hunk_before_position(snapshot, position)
13674 };
13675
13676 if let Some(row) = row {
13677 let destination = Point::new(row.0, 0);
13678 let autoscroll = Autoscroll::center();
13679
13680 self.unfold_ranges(&[destination..destination], false, false, cx);
13681 self.change_selections(Some(autoscroll), window, cx, |s| {
13682 s.select_ranges([destination..destination]);
13683 });
13684 }
13685 }
13686
13687 fn hunk_after_position(
13688 &mut self,
13689 snapshot: &EditorSnapshot,
13690 position: Point,
13691 ) -> Option<MultiBufferDiffHunk> {
13692 snapshot
13693 .buffer_snapshot
13694 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13695 .find(|hunk| hunk.row_range.start.0 > position.row)
13696 .or_else(|| {
13697 snapshot
13698 .buffer_snapshot
13699 .diff_hunks_in_range(Point::zero()..position)
13700 .find(|hunk| hunk.row_range.end.0 < position.row)
13701 })
13702 }
13703
13704 fn go_to_prev_hunk(
13705 &mut self,
13706 _: &GoToPreviousHunk,
13707 window: &mut Window,
13708 cx: &mut Context<Self>,
13709 ) {
13710 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13711 let snapshot = self.snapshot(window, cx);
13712 let selection = self.selections.newest::<Point>(cx);
13713 self.go_to_hunk_before_or_after_position(
13714 &snapshot,
13715 selection.head(),
13716 Direction::Prev,
13717 window,
13718 cx,
13719 );
13720 }
13721
13722 fn hunk_before_position(
13723 &mut self,
13724 snapshot: &EditorSnapshot,
13725 position: Point,
13726 ) -> Option<MultiBufferRow> {
13727 snapshot
13728 .buffer_snapshot
13729 .diff_hunk_before(position)
13730 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
13731 }
13732
13733 fn go_to_next_change(
13734 &mut self,
13735 _: &GoToNextChange,
13736 window: &mut Window,
13737 cx: &mut Context<Self>,
13738 ) {
13739 if let Some(selections) = self
13740 .change_list
13741 .next_change(1, Direction::Next)
13742 .map(|s| s.to_vec())
13743 {
13744 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13745 let map = s.display_map();
13746 s.select_display_ranges(selections.iter().map(|a| {
13747 let point = a.to_display_point(&map);
13748 point..point
13749 }))
13750 })
13751 }
13752 }
13753
13754 fn go_to_previous_change(
13755 &mut self,
13756 _: &GoToPreviousChange,
13757 window: &mut Window,
13758 cx: &mut Context<Self>,
13759 ) {
13760 if let Some(selections) = self
13761 .change_list
13762 .next_change(1, Direction::Prev)
13763 .map(|s| s.to_vec())
13764 {
13765 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13766 let map = s.display_map();
13767 s.select_display_ranges(selections.iter().map(|a| {
13768 let point = a.to_display_point(&map);
13769 point..point
13770 }))
13771 })
13772 }
13773 }
13774
13775 fn go_to_line<T: 'static>(
13776 &mut self,
13777 position: Anchor,
13778 highlight_color: Option<Hsla>,
13779 window: &mut Window,
13780 cx: &mut Context<Self>,
13781 ) {
13782 let snapshot = self.snapshot(window, cx).display_snapshot;
13783 let position = position.to_point(&snapshot.buffer_snapshot);
13784 let start = snapshot
13785 .buffer_snapshot
13786 .clip_point(Point::new(position.row, 0), Bias::Left);
13787 let end = start + Point::new(1, 0);
13788 let start = snapshot.buffer_snapshot.anchor_before(start);
13789 let end = snapshot.buffer_snapshot.anchor_before(end);
13790
13791 self.highlight_rows::<T>(
13792 start..end,
13793 highlight_color
13794 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
13795 Default::default(),
13796 cx,
13797 );
13798 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
13799 }
13800
13801 pub fn go_to_definition(
13802 &mut self,
13803 _: &GoToDefinition,
13804 window: &mut Window,
13805 cx: &mut Context<Self>,
13806 ) -> Task<Result<Navigated>> {
13807 let definition =
13808 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
13809 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
13810 cx.spawn_in(window, async move |editor, cx| {
13811 if definition.await? == Navigated::Yes {
13812 return Ok(Navigated::Yes);
13813 }
13814 match fallback_strategy {
13815 GoToDefinitionFallback::None => Ok(Navigated::No),
13816 GoToDefinitionFallback::FindAllReferences => {
13817 match editor.update_in(cx, |editor, window, cx| {
13818 editor.find_all_references(&FindAllReferences, window, cx)
13819 })? {
13820 Some(references) => references.await,
13821 None => Ok(Navigated::No),
13822 }
13823 }
13824 }
13825 })
13826 }
13827
13828 pub fn go_to_declaration(
13829 &mut self,
13830 _: &GoToDeclaration,
13831 window: &mut Window,
13832 cx: &mut Context<Self>,
13833 ) -> Task<Result<Navigated>> {
13834 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
13835 }
13836
13837 pub fn go_to_declaration_split(
13838 &mut self,
13839 _: &GoToDeclaration,
13840 window: &mut Window,
13841 cx: &mut Context<Self>,
13842 ) -> Task<Result<Navigated>> {
13843 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
13844 }
13845
13846 pub fn go_to_implementation(
13847 &mut self,
13848 _: &GoToImplementation,
13849 window: &mut Window,
13850 cx: &mut Context<Self>,
13851 ) -> Task<Result<Navigated>> {
13852 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
13853 }
13854
13855 pub fn go_to_implementation_split(
13856 &mut self,
13857 _: &GoToImplementationSplit,
13858 window: &mut Window,
13859 cx: &mut Context<Self>,
13860 ) -> Task<Result<Navigated>> {
13861 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
13862 }
13863
13864 pub fn go_to_type_definition(
13865 &mut self,
13866 _: &GoToTypeDefinition,
13867 window: &mut Window,
13868 cx: &mut Context<Self>,
13869 ) -> Task<Result<Navigated>> {
13870 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
13871 }
13872
13873 pub fn go_to_definition_split(
13874 &mut self,
13875 _: &GoToDefinitionSplit,
13876 window: &mut Window,
13877 cx: &mut Context<Self>,
13878 ) -> Task<Result<Navigated>> {
13879 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
13880 }
13881
13882 pub fn go_to_type_definition_split(
13883 &mut self,
13884 _: &GoToTypeDefinitionSplit,
13885 window: &mut Window,
13886 cx: &mut Context<Self>,
13887 ) -> Task<Result<Navigated>> {
13888 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
13889 }
13890
13891 fn go_to_definition_of_kind(
13892 &mut self,
13893 kind: GotoDefinitionKind,
13894 split: bool,
13895 window: &mut Window,
13896 cx: &mut Context<Self>,
13897 ) -> Task<Result<Navigated>> {
13898 let Some(provider) = self.semantics_provider.clone() else {
13899 return Task::ready(Ok(Navigated::No));
13900 };
13901 let head = self.selections.newest::<usize>(cx).head();
13902 let buffer = self.buffer.read(cx);
13903 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
13904 text_anchor
13905 } else {
13906 return Task::ready(Ok(Navigated::No));
13907 };
13908
13909 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
13910 return Task::ready(Ok(Navigated::No));
13911 };
13912
13913 cx.spawn_in(window, async move |editor, cx| {
13914 let definitions = definitions.await?;
13915 let navigated = editor
13916 .update_in(cx, |editor, window, cx| {
13917 editor.navigate_to_hover_links(
13918 Some(kind),
13919 definitions
13920 .into_iter()
13921 .filter(|location| {
13922 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
13923 })
13924 .map(HoverLink::Text)
13925 .collect::<Vec<_>>(),
13926 split,
13927 window,
13928 cx,
13929 )
13930 })?
13931 .await?;
13932 anyhow::Ok(navigated)
13933 })
13934 }
13935
13936 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
13937 let selection = self.selections.newest_anchor();
13938 let head = selection.head();
13939 let tail = selection.tail();
13940
13941 let Some((buffer, start_position)) =
13942 self.buffer.read(cx).text_anchor_for_position(head, cx)
13943 else {
13944 return;
13945 };
13946
13947 let end_position = if head != tail {
13948 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
13949 return;
13950 };
13951 Some(pos)
13952 } else {
13953 None
13954 };
13955
13956 let url_finder = cx.spawn_in(window, async move |editor, cx| {
13957 let url = if let Some(end_pos) = end_position {
13958 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
13959 } else {
13960 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
13961 };
13962
13963 if let Some(url) = url {
13964 editor.update(cx, |_, cx| {
13965 cx.open_url(&url);
13966 })
13967 } else {
13968 Ok(())
13969 }
13970 });
13971
13972 url_finder.detach();
13973 }
13974
13975 pub fn open_selected_filename(
13976 &mut self,
13977 _: &OpenSelectedFilename,
13978 window: &mut Window,
13979 cx: &mut Context<Self>,
13980 ) {
13981 let Some(workspace) = self.workspace() else {
13982 return;
13983 };
13984
13985 let position = self.selections.newest_anchor().head();
13986
13987 let Some((buffer, buffer_position)) =
13988 self.buffer.read(cx).text_anchor_for_position(position, cx)
13989 else {
13990 return;
13991 };
13992
13993 let project = self.project.clone();
13994
13995 cx.spawn_in(window, async move |_, cx| {
13996 let result = find_file(&buffer, project, buffer_position, cx).await;
13997
13998 if let Some((_, path)) = result {
13999 workspace
14000 .update_in(cx, |workspace, window, cx| {
14001 workspace.open_resolved_path(path, window, cx)
14002 })?
14003 .await?;
14004 }
14005 anyhow::Ok(())
14006 })
14007 .detach();
14008 }
14009
14010 pub(crate) fn navigate_to_hover_links(
14011 &mut self,
14012 kind: Option<GotoDefinitionKind>,
14013 mut definitions: Vec<HoverLink>,
14014 split: bool,
14015 window: &mut Window,
14016 cx: &mut Context<Editor>,
14017 ) -> Task<Result<Navigated>> {
14018 // If there is one definition, just open it directly
14019 if definitions.len() == 1 {
14020 let definition = definitions.pop().unwrap();
14021
14022 enum TargetTaskResult {
14023 Location(Option<Location>),
14024 AlreadyNavigated,
14025 }
14026
14027 let target_task = match definition {
14028 HoverLink::Text(link) => {
14029 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14030 }
14031 HoverLink::InlayHint(lsp_location, server_id) => {
14032 let computation =
14033 self.compute_target_location(lsp_location, server_id, window, cx);
14034 cx.background_spawn(async move {
14035 let location = computation.await?;
14036 Ok(TargetTaskResult::Location(location))
14037 })
14038 }
14039 HoverLink::Url(url) => {
14040 cx.open_url(&url);
14041 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14042 }
14043 HoverLink::File(path) => {
14044 if let Some(workspace) = self.workspace() {
14045 cx.spawn_in(window, async move |_, cx| {
14046 workspace
14047 .update_in(cx, |workspace, window, cx| {
14048 workspace.open_resolved_path(path, window, cx)
14049 })?
14050 .await
14051 .map(|_| TargetTaskResult::AlreadyNavigated)
14052 })
14053 } else {
14054 Task::ready(Ok(TargetTaskResult::Location(None)))
14055 }
14056 }
14057 };
14058 cx.spawn_in(window, async move |editor, cx| {
14059 let target = match target_task.await.context("target resolution task")? {
14060 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14061 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14062 TargetTaskResult::Location(Some(target)) => target,
14063 };
14064
14065 editor.update_in(cx, |editor, window, cx| {
14066 let Some(workspace) = editor.workspace() else {
14067 return Navigated::No;
14068 };
14069 let pane = workspace.read(cx).active_pane().clone();
14070
14071 let range = target.range.to_point(target.buffer.read(cx));
14072 let range = editor.range_for_match(&range);
14073 let range = collapse_multiline_range(range);
14074
14075 if !split
14076 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14077 {
14078 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14079 } else {
14080 window.defer(cx, move |window, cx| {
14081 let target_editor: Entity<Self> =
14082 workspace.update(cx, |workspace, cx| {
14083 let pane = if split {
14084 workspace.adjacent_pane(window, cx)
14085 } else {
14086 workspace.active_pane().clone()
14087 };
14088
14089 workspace.open_project_item(
14090 pane,
14091 target.buffer.clone(),
14092 true,
14093 true,
14094 window,
14095 cx,
14096 )
14097 });
14098 target_editor.update(cx, |target_editor, cx| {
14099 // When selecting a definition in a different buffer, disable the nav history
14100 // to avoid creating a history entry at the previous cursor location.
14101 pane.update(cx, |pane, _| pane.disable_history());
14102 target_editor.go_to_singleton_buffer_range(range, window, cx);
14103 pane.update(cx, |pane, _| pane.enable_history());
14104 });
14105 });
14106 }
14107 Navigated::Yes
14108 })
14109 })
14110 } else if !definitions.is_empty() {
14111 cx.spawn_in(window, async move |editor, cx| {
14112 let (title, location_tasks, workspace) = editor
14113 .update_in(cx, |editor, window, cx| {
14114 let tab_kind = match kind {
14115 Some(GotoDefinitionKind::Implementation) => "Implementations",
14116 _ => "Definitions",
14117 };
14118 let title = definitions
14119 .iter()
14120 .find_map(|definition| match definition {
14121 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14122 let buffer = origin.buffer.read(cx);
14123 format!(
14124 "{} for {}",
14125 tab_kind,
14126 buffer
14127 .text_for_range(origin.range.clone())
14128 .collect::<String>()
14129 )
14130 }),
14131 HoverLink::InlayHint(_, _) => None,
14132 HoverLink::Url(_) => None,
14133 HoverLink::File(_) => None,
14134 })
14135 .unwrap_or(tab_kind.to_string());
14136 let location_tasks = definitions
14137 .into_iter()
14138 .map(|definition| match definition {
14139 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14140 HoverLink::InlayHint(lsp_location, server_id) => editor
14141 .compute_target_location(lsp_location, server_id, window, cx),
14142 HoverLink::Url(_) => Task::ready(Ok(None)),
14143 HoverLink::File(_) => Task::ready(Ok(None)),
14144 })
14145 .collect::<Vec<_>>();
14146 (title, location_tasks, editor.workspace().clone())
14147 })
14148 .context("location tasks preparation")?;
14149
14150 let locations = future::join_all(location_tasks)
14151 .await
14152 .into_iter()
14153 .filter_map(|location| location.transpose())
14154 .collect::<Result<_>>()
14155 .context("location tasks")?;
14156
14157 let Some(workspace) = workspace else {
14158 return Ok(Navigated::No);
14159 };
14160 let opened = workspace
14161 .update_in(cx, |workspace, window, cx| {
14162 Self::open_locations_in_multibuffer(
14163 workspace,
14164 locations,
14165 title,
14166 split,
14167 MultibufferSelectionMode::First,
14168 window,
14169 cx,
14170 )
14171 })
14172 .ok();
14173
14174 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14175 })
14176 } else {
14177 Task::ready(Ok(Navigated::No))
14178 }
14179 }
14180
14181 fn compute_target_location(
14182 &self,
14183 lsp_location: lsp::Location,
14184 server_id: LanguageServerId,
14185 window: &mut Window,
14186 cx: &mut Context<Self>,
14187 ) -> Task<anyhow::Result<Option<Location>>> {
14188 let Some(project) = self.project.clone() else {
14189 return Task::ready(Ok(None));
14190 };
14191
14192 cx.spawn_in(window, async move |editor, cx| {
14193 let location_task = editor.update(cx, |_, cx| {
14194 project.update(cx, |project, cx| {
14195 let language_server_name = project
14196 .language_server_statuses(cx)
14197 .find(|(id, _)| server_id == *id)
14198 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14199 language_server_name.map(|language_server_name| {
14200 project.open_local_buffer_via_lsp(
14201 lsp_location.uri.clone(),
14202 server_id,
14203 language_server_name,
14204 cx,
14205 )
14206 })
14207 })
14208 })?;
14209 let location = match location_task {
14210 Some(task) => Some({
14211 let target_buffer_handle = task.await.context("open local buffer")?;
14212 let range = target_buffer_handle.update(cx, |target_buffer, _| {
14213 let target_start = target_buffer
14214 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14215 let target_end = target_buffer
14216 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14217 target_buffer.anchor_after(target_start)
14218 ..target_buffer.anchor_before(target_end)
14219 })?;
14220 Location {
14221 buffer: target_buffer_handle,
14222 range,
14223 }
14224 }),
14225 None => None,
14226 };
14227 Ok(location)
14228 })
14229 }
14230
14231 pub fn find_all_references(
14232 &mut self,
14233 _: &FindAllReferences,
14234 window: &mut Window,
14235 cx: &mut Context<Self>,
14236 ) -> Option<Task<Result<Navigated>>> {
14237 let selection = self.selections.newest::<usize>(cx);
14238 let multi_buffer = self.buffer.read(cx);
14239 let head = selection.head();
14240
14241 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14242 let head_anchor = multi_buffer_snapshot.anchor_at(
14243 head,
14244 if head < selection.tail() {
14245 Bias::Right
14246 } else {
14247 Bias::Left
14248 },
14249 );
14250
14251 match self
14252 .find_all_references_task_sources
14253 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14254 {
14255 Ok(_) => {
14256 log::info!(
14257 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14258 );
14259 return None;
14260 }
14261 Err(i) => {
14262 self.find_all_references_task_sources.insert(i, head_anchor);
14263 }
14264 }
14265
14266 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14267 let workspace = self.workspace()?;
14268 let project = workspace.read(cx).project().clone();
14269 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14270 Some(cx.spawn_in(window, async move |editor, cx| {
14271 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14272 if let Ok(i) = editor
14273 .find_all_references_task_sources
14274 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14275 {
14276 editor.find_all_references_task_sources.remove(i);
14277 }
14278 });
14279
14280 let locations = references.await?;
14281 if locations.is_empty() {
14282 return anyhow::Ok(Navigated::No);
14283 }
14284
14285 workspace.update_in(cx, |workspace, window, cx| {
14286 let title = locations
14287 .first()
14288 .as_ref()
14289 .map(|location| {
14290 let buffer = location.buffer.read(cx);
14291 format!(
14292 "References to `{}`",
14293 buffer
14294 .text_for_range(location.range.clone())
14295 .collect::<String>()
14296 )
14297 })
14298 .unwrap();
14299 Self::open_locations_in_multibuffer(
14300 workspace,
14301 locations,
14302 title,
14303 false,
14304 MultibufferSelectionMode::First,
14305 window,
14306 cx,
14307 );
14308 Navigated::Yes
14309 })
14310 }))
14311 }
14312
14313 /// Opens a multibuffer with the given project locations in it
14314 pub fn open_locations_in_multibuffer(
14315 workspace: &mut Workspace,
14316 mut locations: Vec<Location>,
14317 title: String,
14318 split: bool,
14319 multibuffer_selection_mode: MultibufferSelectionMode,
14320 window: &mut Window,
14321 cx: &mut Context<Workspace>,
14322 ) {
14323 // If there are multiple definitions, open them in a multibuffer
14324 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14325 let mut locations = locations.into_iter().peekable();
14326 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14327 let capability = workspace.project().read(cx).capability();
14328
14329 let excerpt_buffer = cx.new(|cx| {
14330 let mut multibuffer = MultiBuffer::new(capability);
14331 while let Some(location) = locations.next() {
14332 let buffer = location.buffer.read(cx);
14333 let mut ranges_for_buffer = Vec::new();
14334 let range = location.range.to_point(buffer);
14335 ranges_for_buffer.push(range.clone());
14336
14337 while let Some(next_location) = locations.peek() {
14338 if next_location.buffer == location.buffer {
14339 ranges_for_buffer.push(next_location.range.to_point(buffer));
14340 locations.next();
14341 } else {
14342 break;
14343 }
14344 }
14345
14346 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14347 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14348 PathKey::for_buffer(&location.buffer, cx),
14349 location.buffer.clone(),
14350 ranges_for_buffer,
14351 DEFAULT_MULTIBUFFER_CONTEXT,
14352 cx,
14353 );
14354 ranges.extend(new_ranges)
14355 }
14356
14357 multibuffer.with_title(title)
14358 });
14359
14360 let editor = cx.new(|cx| {
14361 Editor::for_multibuffer(
14362 excerpt_buffer,
14363 Some(workspace.project().clone()),
14364 window,
14365 cx,
14366 )
14367 });
14368 editor.update(cx, |editor, cx| {
14369 match multibuffer_selection_mode {
14370 MultibufferSelectionMode::First => {
14371 if let Some(first_range) = ranges.first() {
14372 editor.change_selections(None, window, cx, |selections| {
14373 selections.clear_disjoint();
14374 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14375 });
14376 }
14377 editor.highlight_background::<Self>(
14378 &ranges,
14379 |theme| theme.editor_highlighted_line_background,
14380 cx,
14381 );
14382 }
14383 MultibufferSelectionMode::All => {
14384 editor.change_selections(None, window, cx, |selections| {
14385 selections.clear_disjoint();
14386 selections.select_anchor_ranges(ranges);
14387 });
14388 }
14389 }
14390 editor.register_buffers_with_language_servers(cx);
14391 });
14392
14393 let item = Box::new(editor);
14394 let item_id = item.item_id();
14395
14396 if split {
14397 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14398 } else {
14399 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14400 let (preview_item_id, preview_item_idx) =
14401 workspace.active_pane().update(cx, |pane, _| {
14402 (pane.preview_item_id(), pane.preview_item_idx())
14403 });
14404
14405 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14406
14407 if let Some(preview_item_id) = preview_item_id {
14408 workspace.active_pane().update(cx, |pane, cx| {
14409 pane.remove_item(preview_item_id, false, false, window, cx);
14410 });
14411 }
14412 } else {
14413 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14414 }
14415 }
14416 workspace.active_pane().update(cx, |pane, cx| {
14417 pane.set_preview_item_id(Some(item_id), cx);
14418 });
14419 }
14420
14421 pub fn rename(
14422 &mut self,
14423 _: &Rename,
14424 window: &mut Window,
14425 cx: &mut Context<Self>,
14426 ) -> Option<Task<Result<()>>> {
14427 use language::ToOffset as _;
14428
14429 let provider = self.semantics_provider.clone()?;
14430 let selection = self.selections.newest_anchor().clone();
14431 let (cursor_buffer, cursor_buffer_position) = self
14432 .buffer
14433 .read(cx)
14434 .text_anchor_for_position(selection.head(), cx)?;
14435 let (tail_buffer, cursor_buffer_position_end) = self
14436 .buffer
14437 .read(cx)
14438 .text_anchor_for_position(selection.tail(), cx)?;
14439 if tail_buffer != cursor_buffer {
14440 return None;
14441 }
14442
14443 let snapshot = cursor_buffer.read(cx).snapshot();
14444 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14445 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14446 let prepare_rename = provider
14447 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14448 .unwrap_or_else(|| Task::ready(Ok(None)));
14449 drop(snapshot);
14450
14451 Some(cx.spawn_in(window, async move |this, cx| {
14452 let rename_range = if let Some(range) = prepare_rename.await? {
14453 Some(range)
14454 } else {
14455 this.update(cx, |this, cx| {
14456 let buffer = this.buffer.read(cx).snapshot(cx);
14457 let mut buffer_highlights = this
14458 .document_highlights_for_position(selection.head(), &buffer)
14459 .filter(|highlight| {
14460 highlight.start.excerpt_id == selection.head().excerpt_id
14461 && highlight.end.excerpt_id == selection.head().excerpt_id
14462 });
14463 buffer_highlights
14464 .next()
14465 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14466 })?
14467 };
14468 if let Some(rename_range) = rename_range {
14469 this.update_in(cx, |this, window, cx| {
14470 let snapshot = cursor_buffer.read(cx).snapshot();
14471 let rename_buffer_range = rename_range.to_offset(&snapshot);
14472 let cursor_offset_in_rename_range =
14473 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14474 let cursor_offset_in_rename_range_end =
14475 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14476
14477 this.take_rename(false, window, cx);
14478 let buffer = this.buffer.read(cx).read(cx);
14479 let cursor_offset = selection.head().to_offset(&buffer);
14480 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14481 let rename_end = rename_start + rename_buffer_range.len();
14482 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14483 let mut old_highlight_id = None;
14484 let old_name: Arc<str> = buffer
14485 .chunks(rename_start..rename_end, true)
14486 .map(|chunk| {
14487 if old_highlight_id.is_none() {
14488 old_highlight_id = chunk.syntax_highlight_id;
14489 }
14490 chunk.text
14491 })
14492 .collect::<String>()
14493 .into();
14494
14495 drop(buffer);
14496
14497 // Position the selection in the rename editor so that it matches the current selection.
14498 this.show_local_selections = false;
14499 let rename_editor = cx.new(|cx| {
14500 let mut editor = Editor::single_line(window, cx);
14501 editor.buffer.update(cx, |buffer, cx| {
14502 buffer.edit([(0..0, old_name.clone())], None, cx)
14503 });
14504 let rename_selection_range = match cursor_offset_in_rename_range
14505 .cmp(&cursor_offset_in_rename_range_end)
14506 {
14507 Ordering::Equal => {
14508 editor.select_all(&SelectAll, window, cx);
14509 return editor;
14510 }
14511 Ordering::Less => {
14512 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14513 }
14514 Ordering::Greater => {
14515 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14516 }
14517 };
14518 if rename_selection_range.end > old_name.len() {
14519 editor.select_all(&SelectAll, window, cx);
14520 } else {
14521 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14522 s.select_ranges([rename_selection_range]);
14523 });
14524 }
14525 editor
14526 });
14527 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14528 if e == &EditorEvent::Focused {
14529 cx.emit(EditorEvent::FocusedIn)
14530 }
14531 })
14532 .detach();
14533
14534 let write_highlights =
14535 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14536 let read_highlights =
14537 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14538 let ranges = write_highlights
14539 .iter()
14540 .flat_map(|(_, ranges)| ranges.iter())
14541 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14542 .cloned()
14543 .collect();
14544
14545 this.highlight_text::<Rename>(
14546 ranges,
14547 HighlightStyle {
14548 fade_out: Some(0.6),
14549 ..Default::default()
14550 },
14551 cx,
14552 );
14553 let rename_focus_handle = rename_editor.focus_handle(cx);
14554 window.focus(&rename_focus_handle);
14555 let block_id = this.insert_blocks(
14556 [BlockProperties {
14557 style: BlockStyle::Flex,
14558 placement: BlockPlacement::Below(range.start),
14559 height: Some(1),
14560 render: Arc::new({
14561 let rename_editor = rename_editor.clone();
14562 move |cx: &mut BlockContext| {
14563 let mut text_style = cx.editor_style.text.clone();
14564 if let Some(highlight_style) = old_highlight_id
14565 .and_then(|h| h.style(&cx.editor_style.syntax))
14566 {
14567 text_style = text_style.highlight(highlight_style);
14568 }
14569 div()
14570 .block_mouse_down()
14571 .pl(cx.anchor_x)
14572 .child(EditorElement::new(
14573 &rename_editor,
14574 EditorStyle {
14575 background: cx.theme().system().transparent,
14576 local_player: cx.editor_style.local_player,
14577 text: text_style,
14578 scrollbar_width: cx.editor_style.scrollbar_width,
14579 syntax: cx.editor_style.syntax.clone(),
14580 status: cx.editor_style.status.clone(),
14581 inlay_hints_style: HighlightStyle {
14582 font_weight: Some(FontWeight::BOLD),
14583 ..make_inlay_hints_style(cx.app)
14584 },
14585 inline_completion_styles: make_suggestion_styles(
14586 cx.app,
14587 ),
14588 ..EditorStyle::default()
14589 },
14590 ))
14591 .into_any_element()
14592 }
14593 }),
14594 priority: 0,
14595 render_in_minimap: true,
14596 }],
14597 Some(Autoscroll::fit()),
14598 cx,
14599 )[0];
14600 this.pending_rename = Some(RenameState {
14601 range,
14602 old_name,
14603 editor: rename_editor,
14604 block_id,
14605 });
14606 })?;
14607 }
14608
14609 Ok(())
14610 }))
14611 }
14612
14613 pub fn confirm_rename(
14614 &mut self,
14615 _: &ConfirmRename,
14616 window: &mut Window,
14617 cx: &mut Context<Self>,
14618 ) -> Option<Task<Result<()>>> {
14619 let rename = self.take_rename(false, window, cx)?;
14620 let workspace = self.workspace()?.downgrade();
14621 let (buffer, start) = self
14622 .buffer
14623 .read(cx)
14624 .text_anchor_for_position(rename.range.start, cx)?;
14625 let (end_buffer, _) = self
14626 .buffer
14627 .read(cx)
14628 .text_anchor_for_position(rename.range.end, cx)?;
14629 if buffer != end_buffer {
14630 return None;
14631 }
14632
14633 let old_name = rename.old_name;
14634 let new_name = rename.editor.read(cx).text(cx);
14635
14636 let rename = self.semantics_provider.as_ref()?.perform_rename(
14637 &buffer,
14638 start,
14639 new_name.clone(),
14640 cx,
14641 )?;
14642
14643 Some(cx.spawn_in(window, async move |editor, cx| {
14644 let project_transaction = rename.await?;
14645 Self::open_project_transaction(
14646 &editor,
14647 workspace,
14648 project_transaction,
14649 format!("Rename: {} → {}", old_name, new_name),
14650 cx,
14651 )
14652 .await?;
14653
14654 editor.update(cx, |editor, cx| {
14655 editor.refresh_document_highlights(cx);
14656 })?;
14657 Ok(())
14658 }))
14659 }
14660
14661 fn take_rename(
14662 &mut self,
14663 moving_cursor: bool,
14664 window: &mut Window,
14665 cx: &mut Context<Self>,
14666 ) -> Option<RenameState> {
14667 let rename = self.pending_rename.take()?;
14668 if rename.editor.focus_handle(cx).is_focused(window) {
14669 window.focus(&self.focus_handle);
14670 }
14671
14672 self.remove_blocks(
14673 [rename.block_id].into_iter().collect(),
14674 Some(Autoscroll::fit()),
14675 cx,
14676 );
14677 self.clear_highlights::<Rename>(cx);
14678 self.show_local_selections = true;
14679
14680 if moving_cursor {
14681 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14682 editor.selections.newest::<usize>(cx).head()
14683 });
14684
14685 // Update the selection to match the position of the selection inside
14686 // the rename editor.
14687 let snapshot = self.buffer.read(cx).read(cx);
14688 let rename_range = rename.range.to_offset(&snapshot);
14689 let cursor_in_editor = snapshot
14690 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14691 .min(rename_range.end);
14692 drop(snapshot);
14693
14694 self.change_selections(None, window, cx, |s| {
14695 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14696 });
14697 } else {
14698 self.refresh_document_highlights(cx);
14699 }
14700
14701 Some(rename)
14702 }
14703
14704 pub fn pending_rename(&self) -> Option<&RenameState> {
14705 self.pending_rename.as_ref()
14706 }
14707
14708 fn format(
14709 &mut self,
14710 _: &Format,
14711 window: &mut Window,
14712 cx: &mut Context<Self>,
14713 ) -> Option<Task<Result<()>>> {
14714 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14715
14716 let project = match &self.project {
14717 Some(project) => project.clone(),
14718 None => return None,
14719 };
14720
14721 Some(self.perform_format(
14722 project,
14723 FormatTrigger::Manual,
14724 FormatTarget::Buffers,
14725 window,
14726 cx,
14727 ))
14728 }
14729
14730 fn format_selections(
14731 &mut self,
14732 _: &FormatSelections,
14733 window: &mut Window,
14734 cx: &mut Context<Self>,
14735 ) -> Option<Task<Result<()>>> {
14736 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14737
14738 let project = match &self.project {
14739 Some(project) => project.clone(),
14740 None => return None,
14741 };
14742
14743 let ranges = self
14744 .selections
14745 .all_adjusted(cx)
14746 .into_iter()
14747 .map(|selection| selection.range())
14748 .collect_vec();
14749
14750 Some(self.perform_format(
14751 project,
14752 FormatTrigger::Manual,
14753 FormatTarget::Ranges(ranges),
14754 window,
14755 cx,
14756 ))
14757 }
14758
14759 fn perform_format(
14760 &mut self,
14761 project: Entity<Project>,
14762 trigger: FormatTrigger,
14763 target: FormatTarget,
14764 window: &mut Window,
14765 cx: &mut Context<Self>,
14766 ) -> Task<Result<()>> {
14767 let buffer = self.buffer.clone();
14768 let (buffers, target) = match target {
14769 FormatTarget::Buffers => {
14770 let mut buffers = buffer.read(cx).all_buffers();
14771 if trigger == FormatTrigger::Save {
14772 buffers.retain(|buffer| buffer.read(cx).is_dirty());
14773 }
14774 (buffers, LspFormatTarget::Buffers)
14775 }
14776 FormatTarget::Ranges(selection_ranges) => {
14777 let multi_buffer = buffer.read(cx);
14778 let snapshot = multi_buffer.read(cx);
14779 let mut buffers = HashSet::default();
14780 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
14781 BTreeMap::new();
14782 for selection_range in selection_ranges {
14783 for (buffer, buffer_range, _) in
14784 snapshot.range_to_buffer_ranges(selection_range)
14785 {
14786 let buffer_id = buffer.remote_id();
14787 let start = buffer.anchor_before(buffer_range.start);
14788 let end = buffer.anchor_after(buffer_range.end);
14789 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
14790 buffer_id_to_ranges
14791 .entry(buffer_id)
14792 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
14793 .or_insert_with(|| vec![start..end]);
14794 }
14795 }
14796 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
14797 }
14798 };
14799
14800 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
14801 let selections_prev = transaction_id_prev
14802 .and_then(|transaction_id_prev| {
14803 // default to selections as they were after the last edit, if we have them,
14804 // instead of how they are now.
14805 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
14806 // will take you back to where you made the last edit, instead of staying where you scrolled
14807 self.selection_history
14808 .transaction(transaction_id_prev)
14809 .map(|t| t.0.clone())
14810 })
14811 .unwrap_or_else(|| {
14812 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
14813 self.selections.disjoint_anchors()
14814 });
14815
14816 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
14817 let format = project.update(cx, |project, cx| {
14818 project.format(buffers, target, true, trigger, cx)
14819 });
14820
14821 cx.spawn_in(window, async move |editor, cx| {
14822 let transaction = futures::select_biased! {
14823 transaction = format.log_err().fuse() => transaction,
14824 () = timeout => {
14825 log::warn!("timed out waiting for formatting");
14826 None
14827 }
14828 };
14829
14830 buffer
14831 .update(cx, |buffer, cx| {
14832 if let Some(transaction) = transaction {
14833 if !buffer.is_singleton() {
14834 buffer.push_transaction(&transaction.0, cx);
14835 }
14836 }
14837 cx.notify();
14838 })
14839 .ok();
14840
14841 if let Some(transaction_id_now) =
14842 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
14843 {
14844 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
14845 if has_new_transaction {
14846 _ = editor.update(cx, |editor, _| {
14847 editor
14848 .selection_history
14849 .insert_transaction(transaction_id_now, selections_prev);
14850 });
14851 }
14852 }
14853
14854 Ok(())
14855 })
14856 }
14857
14858 fn organize_imports(
14859 &mut self,
14860 _: &OrganizeImports,
14861 window: &mut Window,
14862 cx: &mut Context<Self>,
14863 ) -> Option<Task<Result<()>>> {
14864 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14865 let project = match &self.project {
14866 Some(project) => project.clone(),
14867 None => return None,
14868 };
14869 Some(self.perform_code_action_kind(
14870 project,
14871 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
14872 window,
14873 cx,
14874 ))
14875 }
14876
14877 fn perform_code_action_kind(
14878 &mut self,
14879 project: Entity<Project>,
14880 kind: CodeActionKind,
14881 window: &mut Window,
14882 cx: &mut Context<Self>,
14883 ) -> Task<Result<()>> {
14884 let buffer = self.buffer.clone();
14885 let buffers = buffer.read(cx).all_buffers();
14886 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
14887 let apply_action = project.update(cx, |project, cx| {
14888 project.apply_code_action_kind(buffers, kind, true, cx)
14889 });
14890 cx.spawn_in(window, async move |_, cx| {
14891 let transaction = futures::select_biased! {
14892 () = timeout => {
14893 log::warn!("timed out waiting for executing code action");
14894 None
14895 }
14896 transaction = apply_action.log_err().fuse() => transaction,
14897 };
14898 buffer
14899 .update(cx, |buffer, cx| {
14900 // check if we need this
14901 if let Some(transaction) = transaction {
14902 if !buffer.is_singleton() {
14903 buffer.push_transaction(&transaction.0, cx);
14904 }
14905 }
14906 cx.notify();
14907 })
14908 .ok();
14909 Ok(())
14910 })
14911 }
14912
14913 fn restart_language_server(
14914 &mut self,
14915 _: &RestartLanguageServer,
14916 _: &mut Window,
14917 cx: &mut Context<Self>,
14918 ) {
14919 if let Some(project) = self.project.clone() {
14920 self.buffer.update(cx, |multi_buffer, cx| {
14921 project.update(cx, |project, cx| {
14922 project.restart_language_servers_for_buffers(
14923 multi_buffer.all_buffers().into_iter().collect(),
14924 cx,
14925 );
14926 });
14927 })
14928 }
14929 }
14930
14931 fn stop_language_server(
14932 &mut self,
14933 _: &StopLanguageServer,
14934 _: &mut Window,
14935 cx: &mut Context<Self>,
14936 ) {
14937 if let Some(project) = self.project.clone() {
14938 self.buffer.update(cx, |multi_buffer, cx| {
14939 project.update(cx, |project, cx| {
14940 project.stop_language_servers_for_buffers(
14941 multi_buffer.all_buffers().into_iter().collect(),
14942 cx,
14943 );
14944 cx.emit(project::Event::RefreshInlayHints);
14945 });
14946 });
14947 }
14948 }
14949
14950 fn cancel_language_server_work(
14951 workspace: &mut Workspace,
14952 _: &actions::CancelLanguageServerWork,
14953 _: &mut Window,
14954 cx: &mut Context<Workspace>,
14955 ) {
14956 let project = workspace.project();
14957 let buffers = workspace
14958 .active_item(cx)
14959 .and_then(|item| item.act_as::<Editor>(cx))
14960 .map_or(HashSet::default(), |editor| {
14961 editor.read(cx).buffer.read(cx).all_buffers()
14962 });
14963 project.update(cx, |project, cx| {
14964 project.cancel_language_server_work_for_buffers(buffers, cx);
14965 });
14966 }
14967
14968 fn show_character_palette(
14969 &mut self,
14970 _: &ShowCharacterPalette,
14971 window: &mut Window,
14972 _: &mut Context<Self>,
14973 ) {
14974 window.show_character_palette();
14975 }
14976
14977 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
14978 if self.mode.is_minimap() {
14979 return;
14980 }
14981
14982 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
14983 let buffer = self.buffer.read(cx).snapshot(cx);
14984 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
14985 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
14986 let is_valid = buffer
14987 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
14988 .any(|entry| {
14989 entry.diagnostic.is_primary
14990 && !entry.range.is_empty()
14991 && entry.range.start == primary_range_start
14992 && entry.diagnostic.message == active_diagnostics.active_message
14993 });
14994
14995 if !is_valid {
14996 self.dismiss_diagnostics(cx);
14997 }
14998 }
14999 }
15000
15001 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15002 match &self.active_diagnostics {
15003 ActiveDiagnostic::Group(group) => Some(group),
15004 _ => None,
15005 }
15006 }
15007
15008 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15009 self.dismiss_diagnostics(cx);
15010 self.active_diagnostics = ActiveDiagnostic::All;
15011 }
15012
15013 fn activate_diagnostics(
15014 &mut self,
15015 buffer_id: BufferId,
15016 diagnostic: DiagnosticEntry<usize>,
15017 window: &mut Window,
15018 cx: &mut Context<Self>,
15019 ) {
15020 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15021 return;
15022 }
15023 self.dismiss_diagnostics(cx);
15024 let snapshot = self.snapshot(window, cx);
15025 let buffer = self.buffer.read(cx).snapshot(cx);
15026 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15027 return;
15028 };
15029
15030 let diagnostic_group = buffer
15031 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15032 .collect::<Vec<_>>();
15033
15034 let blocks =
15035 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15036
15037 let blocks = self.display_map.update(cx, |display_map, cx| {
15038 display_map.insert_blocks(blocks, cx).into_iter().collect()
15039 });
15040 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15041 active_range: buffer.anchor_before(diagnostic.range.start)
15042 ..buffer.anchor_after(diagnostic.range.end),
15043 active_message: diagnostic.diagnostic.message.clone(),
15044 group_id: diagnostic.diagnostic.group_id,
15045 blocks,
15046 });
15047 cx.notify();
15048 }
15049
15050 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15051 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15052 return;
15053 };
15054
15055 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15056 if let ActiveDiagnostic::Group(group) = prev {
15057 self.display_map.update(cx, |display_map, cx| {
15058 display_map.remove_blocks(group.blocks, cx);
15059 });
15060 cx.notify();
15061 }
15062 }
15063
15064 /// Disable inline diagnostics rendering for this editor.
15065 pub fn disable_inline_diagnostics(&mut self) {
15066 self.inline_diagnostics_enabled = false;
15067 self.inline_diagnostics_update = Task::ready(());
15068 self.inline_diagnostics.clear();
15069 }
15070
15071 pub fn inline_diagnostics_enabled(&self) -> bool {
15072 self.inline_diagnostics_enabled
15073 }
15074
15075 pub fn show_inline_diagnostics(&self) -> bool {
15076 self.show_inline_diagnostics
15077 }
15078
15079 pub fn toggle_inline_diagnostics(
15080 &mut self,
15081 _: &ToggleInlineDiagnostics,
15082 window: &mut Window,
15083 cx: &mut Context<Editor>,
15084 ) {
15085 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15086 self.refresh_inline_diagnostics(false, window, cx);
15087 }
15088
15089 fn refresh_inline_diagnostics(
15090 &mut self,
15091 debounce: bool,
15092 window: &mut Window,
15093 cx: &mut Context<Self>,
15094 ) {
15095 if self.mode.is_minimap()
15096 || !self.inline_diagnostics_enabled
15097 || !self.show_inline_diagnostics
15098 {
15099 self.inline_diagnostics_update = Task::ready(());
15100 self.inline_diagnostics.clear();
15101 return;
15102 }
15103
15104 let debounce_ms = ProjectSettings::get_global(cx)
15105 .diagnostics
15106 .inline
15107 .update_debounce_ms;
15108 let debounce = if debounce && debounce_ms > 0 {
15109 Some(Duration::from_millis(debounce_ms))
15110 } else {
15111 None
15112 };
15113 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15114 let editor = editor.upgrade().unwrap();
15115
15116 if let Some(debounce) = debounce {
15117 cx.background_executor().timer(debounce).await;
15118 }
15119 let Some(snapshot) = editor
15120 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15121 .ok()
15122 else {
15123 return;
15124 };
15125
15126 let new_inline_diagnostics = cx
15127 .background_spawn(async move {
15128 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15129 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15130 let message = diagnostic_entry
15131 .diagnostic
15132 .message
15133 .split_once('\n')
15134 .map(|(line, _)| line)
15135 .map(SharedString::new)
15136 .unwrap_or_else(|| {
15137 SharedString::from(diagnostic_entry.diagnostic.message)
15138 });
15139 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15140 let (Ok(i) | Err(i)) = inline_diagnostics
15141 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15142 inline_diagnostics.insert(
15143 i,
15144 (
15145 start_anchor,
15146 InlineDiagnostic {
15147 message,
15148 group_id: diagnostic_entry.diagnostic.group_id,
15149 start: diagnostic_entry.range.start.to_point(&snapshot),
15150 is_primary: diagnostic_entry.diagnostic.is_primary,
15151 severity: diagnostic_entry.diagnostic.severity,
15152 },
15153 ),
15154 );
15155 }
15156 inline_diagnostics
15157 })
15158 .await;
15159
15160 editor
15161 .update(cx, |editor, cx| {
15162 editor.inline_diagnostics = new_inline_diagnostics;
15163 cx.notify();
15164 })
15165 .ok();
15166 });
15167 }
15168
15169 pub fn set_selections_from_remote(
15170 &mut self,
15171 selections: Vec<Selection<Anchor>>,
15172 pending_selection: Option<Selection<Anchor>>,
15173 window: &mut Window,
15174 cx: &mut Context<Self>,
15175 ) {
15176 let old_cursor_position = self.selections.newest_anchor().head();
15177 self.selections.change_with(cx, |s| {
15178 s.select_anchors(selections);
15179 if let Some(pending_selection) = pending_selection {
15180 s.set_pending(pending_selection, SelectMode::Character);
15181 } else {
15182 s.clear_pending();
15183 }
15184 });
15185 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15186 }
15187
15188 fn push_to_selection_history(&mut self) {
15189 self.selection_history.push(SelectionHistoryEntry {
15190 selections: self.selections.disjoint_anchors(),
15191 select_next_state: self.select_next_state.clone(),
15192 select_prev_state: self.select_prev_state.clone(),
15193 add_selections_state: self.add_selections_state.clone(),
15194 });
15195 }
15196
15197 pub fn transact(
15198 &mut self,
15199 window: &mut Window,
15200 cx: &mut Context<Self>,
15201 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15202 ) -> Option<TransactionId> {
15203 self.start_transaction_at(Instant::now(), window, cx);
15204 update(self, window, cx);
15205 self.end_transaction_at(Instant::now(), cx)
15206 }
15207
15208 pub fn start_transaction_at(
15209 &mut self,
15210 now: Instant,
15211 window: &mut Window,
15212 cx: &mut Context<Self>,
15213 ) {
15214 self.end_selection(window, cx);
15215 if let Some(tx_id) = self
15216 .buffer
15217 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15218 {
15219 self.selection_history
15220 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15221 cx.emit(EditorEvent::TransactionBegun {
15222 transaction_id: tx_id,
15223 })
15224 }
15225 }
15226
15227 pub fn end_transaction_at(
15228 &mut self,
15229 now: Instant,
15230 cx: &mut Context<Self>,
15231 ) -> Option<TransactionId> {
15232 if let Some(transaction_id) = self
15233 .buffer
15234 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15235 {
15236 if let Some((_, end_selections)) =
15237 self.selection_history.transaction_mut(transaction_id)
15238 {
15239 *end_selections = Some(self.selections.disjoint_anchors());
15240 } else {
15241 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15242 }
15243
15244 cx.emit(EditorEvent::Edited { transaction_id });
15245 Some(transaction_id)
15246 } else {
15247 None
15248 }
15249 }
15250
15251 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15252 if self.selection_mark_mode {
15253 self.change_selections(None, window, cx, |s| {
15254 s.move_with(|_, sel| {
15255 sel.collapse_to(sel.head(), SelectionGoal::None);
15256 });
15257 })
15258 }
15259 self.selection_mark_mode = true;
15260 cx.notify();
15261 }
15262
15263 pub fn swap_selection_ends(
15264 &mut self,
15265 _: &actions::SwapSelectionEnds,
15266 window: &mut Window,
15267 cx: &mut Context<Self>,
15268 ) {
15269 self.change_selections(None, window, cx, |s| {
15270 s.move_with(|_, sel| {
15271 if sel.start != sel.end {
15272 sel.reversed = !sel.reversed
15273 }
15274 });
15275 });
15276 self.request_autoscroll(Autoscroll::newest(), cx);
15277 cx.notify();
15278 }
15279
15280 pub fn toggle_fold(
15281 &mut self,
15282 _: &actions::ToggleFold,
15283 window: &mut Window,
15284 cx: &mut Context<Self>,
15285 ) {
15286 if self.is_singleton(cx) {
15287 let selection = self.selections.newest::<Point>(cx);
15288
15289 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15290 let range = if selection.is_empty() {
15291 let point = selection.head().to_display_point(&display_map);
15292 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15293 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15294 .to_point(&display_map);
15295 start..end
15296 } else {
15297 selection.range()
15298 };
15299 if display_map.folds_in_range(range).next().is_some() {
15300 self.unfold_lines(&Default::default(), window, cx)
15301 } else {
15302 self.fold(&Default::default(), window, cx)
15303 }
15304 } else {
15305 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15306 let buffer_ids: HashSet<_> = self
15307 .selections
15308 .disjoint_anchor_ranges()
15309 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15310 .collect();
15311
15312 let should_unfold = buffer_ids
15313 .iter()
15314 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15315
15316 for buffer_id in buffer_ids {
15317 if should_unfold {
15318 self.unfold_buffer(buffer_id, cx);
15319 } else {
15320 self.fold_buffer(buffer_id, cx);
15321 }
15322 }
15323 }
15324 }
15325
15326 pub fn toggle_fold_recursive(
15327 &mut self,
15328 _: &actions::ToggleFoldRecursive,
15329 window: &mut Window,
15330 cx: &mut Context<Self>,
15331 ) {
15332 let selection = self.selections.newest::<Point>(cx);
15333
15334 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15335 let range = if selection.is_empty() {
15336 let point = selection.head().to_display_point(&display_map);
15337 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15338 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15339 .to_point(&display_map);
15340 start..end
15341 } else {
15342 selection.range()
15343 };
15344 if display_map.folds_in_range(range).next().is_some() {
15345 self.unfold_recursive(&Default::default(), window, cx)
15346 } else {
15347 self.fold_recursive(&Default::default(), window, cx)
15348 }
15349 }
15350
15351 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15352 if self.is_singleton(cx) {
15353 let mut to_fold = Vec::new();
15354 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15355 let selections = self.selections.all_adjusted(cx);
15356
15357 for selection in selections {
15358 let range = selection.range().sorted();
15359 let buffer_start_row = range.start.row;
15360
15361 if range.start.row != range.end.row {
15362 let mut found = false;
15363 let mut row = range.start.row;
15364 while row <= range.end.row {
15365 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15366 {
15367 found = true;
15368 row = crease.range().end.row + 1;
15369 to_fold.push(crease);
15370 } else {
15371 row += 1
15372 }
15373 }
15374 if found {
15375 continue;
15376 }
15377 }
15378
15379 for row in (0..=range.start.row).rev() {
15380 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15381 if crease.range().end.row >= buffer_start_row {
15382 to_fold.push(crease);
15383 if row <= range.start.row {
15384 break;
15385 }
15386 }
15387 }
15388 }
15389 }
15390
15391 self.fold_creases(to_fold, true, window, cx);
15392 } else {
15393 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15394 let buffer_ids = self
15395 .selections
15396 .disjoint_anchor_ranges()
15397 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15398 .collect::<HashSet<_>>();
15399 for buffer_id in buffer_ids {
15400 self.fold_buffer(buffer_id, cx);
15401 }
15402 }
15403 }
15404
15405 fn fold_at_level(
15406 &mut self,
15407 fold_at: &FoldAtLevel,
15408 window: &mut Window,
15409 cx: &mut Context<Self>,
15410 ) {
15411 if !self.buffer.read(cx).is_singleton() {
15412 return;
15413 }
15414
15415 let fold_at_level = fold_at.0;
15416 let snapshot = self.buffer.read(cx).snapshot(cx);
15417 let mut to_fold = Vec::new();
15418 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15419
15420 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15421 while start_row < end_row {
15422 match self
15423 .snapshot(window, cx)
15424 .crease_for_buffer_row(MultiBufferRow(start_row))
15425 {
15426 Some(crease) => {
15427 let nested_start_row = crease.range().start.row + 1;
15428 let nested_end_row = crease.range().end.row;
15429
15430 if current_level < fold_at_level {
15431 stack.push((nested_start_row, nested_end_row, current_level + 1));
15432 } else if current_level == fold_at_level {
15433 to_fold.push(crease);
15434 }
15435
15436 start_row = nested_end_row + 1;
15437 }
15438 None => start_row += 1,
15439 }
15440 }
15441 }
15442
15443 self.fold_creases(to_fold, true, window, cx);
15444 }
15445
15446 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15447 if self.buffer.read(cx).is_singleton() {
15448 let mut fold_ranges = Vec::new();
15449 let snapshot = self.buffer.read(cx).snapshot(cx);
15450
15451 for row in 0..snapshot.max_row().0 {
15452 if let Some(foldable_range) = self
15453 .snapshot(window, cx)
15454 .crease_for_buffer_row(MultiBufferRow(row))
15455 {
15456 fold_ranges.push(foldable_range);
15457 }
15458 }
15459
15460 self.fold_creases(fold_ranges, true, window, cx);
15461 } else {
15462 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15463 editor
15464 .update_in(cx, |editor, _, cx| {
15465 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15466 editor.fold_buffer(buffer_id, cx);
15467 }
15468 })
15469 .ok();
15470 });
15471 }
15472 }
15473
15474 pub fn fold_function_bodies(
15475 &mut self,
15476 _: &actions::FoldFunctionBodies,
15477 window: &mut Window,
15478 cx: &mut Context<Self>,
15479 ) {
15480 let snapshot = self.buffer.read(cx).snapshot(cx);
15481
15482 let ranges = snapshot
15483 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15484 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15485 .collect::<Vec<_>>();
15486
15487 let creases = ranges
15488 .into_iter()
15489 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15490 .collect();
15491
15492 self.fold_creases(creases, true, window, cx);
15493 }
15494
15495 pub fn fold_recursive(
15496 &mut self,
15497 _: &actions::FoldRecursive,
15498 window: &mut Window,
15499 cx: &mut Context<Self>,
15500 ) {
15501 let mut to_fold = Vec::new();
15502 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15503 let selections = self.selections.all_adjusted(cx);
15504
15505 for selection in selections {
15506 let range = selection.range().sorted();
15507 let buffer_start_row = range.start.row;
15508
15509 if range.start.row != range.end.row {
15510 let mut found = false;
15511 for row in range.start.row..=range.end.row {
15512 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15513 found = true;
15514 to_fold.push(crease);
15515 }
15516 }
15517 if found {
15518 continue;
15519 }
15520 }
15521
15522 for row in (0..=range.start.row).rev() {
15523 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15524 if crease.range().end.row >= buffer_start_row {
15525 to_fold.push(crease);
15526 } else {
15527 break;
15528 }
15529 }
15530 }
15531 }
15532
15533 self.fold_creases(to_fold, true, window, cx);
15534 }
15535
15536 pub fn fold_at(
15537 &mut self,
15538 buffer_row: MultiBufferRow,
15539 window: &mut Window,
15540 cx: &mut Context<Self>,
15541 ) {
15542 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15543
15544 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15545 let autoscroll = self
15546 .selections
15547 .all::<Point>(cx)
15548 .iter()
15549 .any(|selection| crease.range().overlaps(&selection.range()));
15550
15551 self.fold_creases(vec![crease], autoscroll, window, cx);
15552 }
15553 }
15554
15555 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15556 if self.is_singleton(cx) {
15557 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15558 let buffer = &display_map.buffer_snapshot;
15559 let selections = self.selections.all::<Point>(cx);
15560 let ranges = selections
15561 .iter()
15562 .map(|s| {
15563 let range = s.display_range(&display_map).sorted();
15564 let mut start = range.start.to_point(&display_map);
15565 let mut end = range.end.to_point(&display_map);
15566 start.column = 0;
15567 end.column = buffer.line_len(MultiBufferRow(end.row));
15568 start..end
15569 })
15570 .collect::<Vec<_>>();
15571
15572 self.unfold_ranges(&ranges, true, true, cx);
15573 } else {
15574 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15575 let buffer_ids = self
15576 .selections
15577 .disjoint_anchor_ranges()
15578 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15579 .collect::<HashSet<_>>();
15580 for buffer_id in buffer_ids {
15581 self.unfold_buffer(buffer_id, cx);
15582 }
15583 }
15584 }
15585
15586 pub fn unfold_recursive(
15587 &mut self,
15588 _: &UnfoldRecursive,
15589 _window: &mut Window,
15590 cx: &mut Context<Self>,
15591 ) {
15592 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15593 let selections = self.selections.all::<Point>(cx);
15594 let ranges = selections
15595 .iter()
15596 .map(|s| {
15597 let mut range = s.display_range(&display_map).sorted();
15598 *range.start.column_mut() = 0;
15599 *range.end.column_mut() = display_map.line_len(range.end.row());
15600 let start = range.start.to_point(&display_map);
15601 let end = range.end.to_point(&display_map);
15602 start..end
15603 })
15604 .collect::<Vec<_>>();
15605
15606 self.unfold_ranges(&ranges, true, true, cx);
15607 }
15608
15609 pub fn unfold_at(
15610 &mut self,
15611 buffer_row: MultiBufferRow,
15612 _window: &mut Window,
15613 cx: &mut Context<Self>,
15614 ) {
15615 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15616
15617 let intersection_range = Point::new(buffer_row.0, 0)
15618 ..Point::new(
15619 buffer_row.0,
15620 display_map.buffer_snapshot.line_len(buffer_row),
15621 );
15622
15623 let autoscroll = self
15624 .selections
15625 .all::<Point>(cx)
15626 .iter()
15627 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15628
15629 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15630 }
15631
15632 pub fn unfold_all(
15633 &mut self,
15634 _: &actions::UnfoldAll,
15635 _window: &mut Window,
15636 cx: &mut Context<Self>,
15637 ) {
15638 if self.buffer.read(cx).is_singleton() {
15639 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15640 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15641 } else {
15642 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15643 editor
15644 .update(cx, |editor, cx| {
15645 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15646 editor.unfold_buffer(buffer_id, cx);
15647 }
15648 })
15649 .ok();
15650 });
15651 }
15652 }
15653
15654 pub fn fold_selected_ranges(
15655 &mut self,
15656 _: &FoldSelectedRanges,
15657 window: &mut Window,
15658 cx: &mut Context<Self>,
15659 ) {
15660 let selections = self.selections.all_adjusted(cx);
15661 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15662 let ranges = selections
15663 .into_iter()
15664 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
15665 .collect::<Vec<_>>();
15666 self.fold_creases(ranges, true, window, cx);
15667 }
15668
15669 pub fn fold_ranges<T: ToOffset + Clone>(
15670 &mut self,
15671 ranges: Vec<Range<T>>,
15672 auto_scroll: bool,
15673 window: &mut Window,
15674 cx: &mut Context<Self>,
15675 ) {
15676 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15677 let ranges = ranges
15678 .into_iter()
15679 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
15680 .collect::<Vec<_>>();
15681 self.fold_creases(ranges, auto_scroll, window, cx);
15682 }
15683
15684 pub fn fold_creases<T: ToOffset + Clone>(
15685 &mut self,
15686 creases: Vec<Crease<T>>,
15687 auto_scroll: bool,
15688 _window: &mut Window,
15689 cx: &mut Context<Self>,
15690 ) {
15691 if creases.is_empty() {
15692 return;
15693 }
15694
15695 let mut buffers_affected = HashSet::default();
15696 let multi_buffer = self.buffer().read(cx);
15697 for crease in &creases {
15698 if let Some((_, buffer, _)) =
15699 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
15700 {
15701 buffers_affected.insert(buffer.read(cx).remote_id());
15702 };
15703 }
15704
15705 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
15706
15707 if auto_scroll {
15708 self.request_autoscroll(Autoscroll::fit(), cx);
15709 }
15710
15711 cx.notify();
15712
15713 self.scrollbar_marker_state.dirty = true;
15714 self.folds_did_change(cx);
15715 }
15716
15717 /// Removes any folds whose ranges intersect any of the given ranges.
15718 pub fn unfold_ranges<T: ToOffset + Clone>(
15719 &mut self,
15720 ranges: &[Range<T>],
15721 inclusive: bool,
15722 auto_scroll: bool,
15723 cx: &mut Context<Self>,
15724 ) {
15725 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15726 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
15727 });
15728 self.folds_did_change(cx);
15729 }
15730
15731 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15732 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
15733 return;
15734 }
15735 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15736 self.display_map.update(cx, |display_map, cx| {
15737 display_map.fold_buffers([buffer_id], cx)
15738 });
15739 cx.emit(EditorEvent::BufferFoldToggled {
15740 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
15741 folded: true,
15742 });
15743 cx.notify();
15744 }
15745
15746 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15747 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
15748 return;
15749 }
15750 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15751 self.display_map.update(cx, |display_map, cx| {
15752 display_map.unfold_buffers([buffer_id], cx);
15753 });
15754 cx.emit(EditorEvent::BufferFoldToggled {
15755 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
15756 folded: false,
15757 });
15758 cx.notify();
15759 }
15760
15761 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
15762 self.display_map.read(cx).is_buffer_folded(buffer)
15763 }
15764
15765 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
15766 self.display_map.read(cx).folded_buffers()
15767 }
15768
15769 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15770 self.display_map.update(cx, |display_map, cx| {
15771 display_map.disable_header_for_buffer(buffer_id, cx);
15772 });
15773 cx.notify();
15774 }
15775
15776 /// Removes any folds with the given ranges.
15777 pub fn remove_folds_with_type<T: ToOffset + Clone>(
15778 &mut self,
15779 ranges: &[Range<T>],
15780 type_id: TypeId,
15781 auto_scroll: bool,
15782 cx: &mut Context<Self>,
15783 ) {
15784 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15785 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
15786 });
15787 self.folds_did_change(cx);
15788 }
15789
15790 fn remove_folds_with<T: ToOffset + Clone>(
15791 &mut self,
15792 ranges: &[Range<T>],
15793 auto_scroll: bool,
15794 cx: &mut Context<Self>,
15795 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
15796 ) {
15797 if ranges.is_empty() {
15798 return;
15799 }
15800
15801 let mut buffers_affected = HashSet::default();
15802 let multi_buffer = self.buffer().read(cx);
15803 for range in ranges {
15804 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
15805 buffers_affected.insert(buffer.read(cx).remote_id());
15806 };
15807 }
15808
15809 self.display_map.update(cx, update);
15810
15811 if auto_scroll {
15812 self.request_autoscroll(Autoscroll::fit(), cx);
15813 }
15814
15815 cx.notify();
15816 self.scrollbar_marker_state.dirty = true;
15817 self.active_indent_guides_state.dirty = true;
15818 }
15819
15820 pub fn update_fold_widths(
15821 &mut self,
15822 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
15823 cx: &mut Context<Self>,
15824 ) -> bool {
15825 self.display_map
15826 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
15827 }
15828
15829 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
15830 self.display_map.read(cx).fold_placeholder.clone()
15831 }
15832
15833 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
15834 self.buffer.update(cx, |buffer, cx| {
15835 buffer.set_all_diff_hunks_expanded(cx);
15836 });
15837 }
15838
15839 pub fn expand_all_diff_hunks(
15840 &mut self,
15841 _: &ExpandAllDiffHunks,
15842 _window: &mut Window,
15843 cx: &mut Context<Self>,
15844 ) {
15845 self.buffer.update(cx, |buffer, cx| {
15846 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
15847 });
15848 }
15849
15850 pub fn toggle_selected_diff_hunks(
15851 &mut self,
15852 _: &ToggleSelectedDiffHunks,
15853 _window: &mut Window,
15854 cx: &mut Context<Self>,
15855 ) {
15856 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15857 self.toggle_diff_hunks_in_ranges(ranges, cx);
15858 }
15859
15860 pub fn diff_hunks_in_ranges<'a>(
15861 &'a self,
15862 ranges: &'a [Range<Anchor>],
15863 buffer: &'a MultiBufferSnapshot,
15864 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
15865 ranges.iter().flat_map(move |range| {
15866 let end_excerpt_id = range.end.excerpt_id;
15867 let range = range.to_point(buffer);
15868 let mut peek_end = range.end;
15869 if range.end.row < buffer.max_row().0 {
15870 peek_end = Point::new(range.end.row + 1, 0);
15871 }
15872 buffer
15873 .diff_hunks_in_range(range.start..peek_end)
15874 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
15875 })
15876 }
15877
15878 pub fn has_stageable_diff_hunks_in_ranges(
15879 &self,
15880 ranges: &[Range<Anchor>],
15881 snapshot: &MultiBufferSnapshot,
15882 ) -> bool {
15883 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
15884 hunks.any(|hunk| hunk.status().has_secondary_hunk())
15885 }
15886
15887 pub fn toggle_staged_selected_diff_hunks(
15888 &mut self,
15889 _: &::git::ToggleStaged,
15890 _: &mut Window,
15891 cx: &mut Context<Self>,
15892 ) {
15893 let snapshot = self.buffer.read(cx).snapshot(cx);
15894 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15895 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
15896 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15897 }
15898
15899 pub fn set_render_diff_hunk_controls(
15900 &mut self,
15901 render_diff_hunk_controls: RenderDiffHunkControlsFn,
15902 cx: &mut Context<Self>,
15903 ) {
15904 self.render_diff_hunk_controls = render_diff_hunk_controls;
15905 cx.notify();
15906 }
15907
15908 pub fn stage_and_next(
15909 &mut self,
15910 _: &::git::StageAndNext,
15911 window: &mut Window,
15912 cx: &mut Context<Self>,
15913 ) {
15914 self.do_stage_or_unstage_and_next(true, window, cx);
15915 }
15916
15917 pub fn unstage_and_next(
15918 &mut self,
15919 _: &::git::UnstageAndNext,
15920 window: &mut Window,
15921 cx: &mut Context<Self>,
15922 ) {
15923 self.do_stage_or_unstage_and_next(false, window, cx);
15924 }
15925
15926 pub fn stage_or_unstage_diff_hunks(
15927 &mut self,
15928 stage: bool,
15929 ranges: Vec<Range<Anchor>>,
15930 cx: &mut Context<Self>,
15931 ) {
15932 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
15933 cx.spawn(async move |this, cx| {
15934 task.await?;
15935 this.update(cx, |this, cx| {
15936 let snapshot = this.buffer.read(cx).snapshot(cx);
15937 let chunk_by = this
15938 .diff_hunks_in_ranges(&ranges, &snapshot)
15939 .chunk_by(|hunk| hunk.buffer_id);
15940 for (buffer_id, hunks) in &chunk_by {
15941 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
15942 }
15943 })
15944 })
15945 .detach_and_log_err(cx);
15946 }
15947
15948 fn save_buffers_for_ranges_if_needed(
15949 &mut self,
15950 ranges: &[Range<Anchor>],
15951 cx: &mut Context<Editor>,
15952 ) -> Task<Result<()>> {
15953 let multibuffer = self.buffer.read(cx);
15954 let snapshot = multibuffer.read(cx);
15955 let buffer_ids: HashSet<_> = ranges
15956 .iter()
15957 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
15958 .collect();
15959 drop(snapshot);
15960
15961 let mut buffers = HashSet::default();
15962 for buffer_id in buffer_ids {
15963 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
15964 let buffer = buffer_entity.read(cx);
15965 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
15966 {
15967 buffers.insert(buffer_entity);
15968 }
15969 }
15970 }
15971
15972 if let Some(project) = &self.project {
15973 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
15974 } else {
15975 Task::ready(Ok(()))
15976 }
15977 }
15978
15979 fn do_stage_or_unstage_and_next(
15980 &mut self,
15981 stage: bool,
15982 window: &mut Window,
15983 cx: &mut Context<Self>,
15984 ) {
15985 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
15986
15987 if ranges.iter().any(|range| range.start != range.end) {
15988 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15989 return;
15990 }
15991
15992 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15993 let snapshot = self.snapshot(window, cx);
15994 let position = self.selections.newest::<Point>(cx).head();
15995 let mut row = snapshot
15996 .buffer_snapshot
15997 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15998 .find(|hunk| hunk.row_range.start.0 > position.row)
15999 .map(|hunk| hunk.row_range.start);
16000
16001 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16002 // Outside of the project diff editor, wrap around to the beginning.
16003 if !all_diff_hunks_expanded {
16004 row = row.or_else(|| {
16005 snapshot
16006 .buffer_snapshot
16007 .diff_hunks_in_range(Point::zero()..position)
16008 .find(|hunk| hunk.row_range.end.0 < position.row)
16009 .map(|hunk| hunk.row_range.start)
16010 });
16011 }
16012
16013 if let Some(row) = row {
16014 let destination = Point::new(row.0, 0);
16015 let autoscroll = Autoscroll::center();
16016
16017 self.unfold_ranges(&[destination..destination], false, false, cx);
16018 self.change_selections(Some(autoscroll), window, cx, |s| {
16019 s.select_ranges([destination..destination]);
16020 });
16021 }
16022 }
16023
16024 fn do_stage_or_unstage(
16025 &self,
16026 stage: bool,
16027 buffer_id: BufferId,
16028 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16029 cx: &mut App,
16030 ) -> Option<()> {
16031 let project = self.project.as_ref()?;
16032 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16033 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16034 let buffer_snapshot = buffer.read(cx).snapshot();
16035 let file_exists = buffer_snapshot
16036 .file()
16037 .is_some_and(|file| file.disk_state().exists());
16038 diff.update(cx, |diff, cx| {
16039 diff.stage_or_unstage_hunks(
16040 stage,
16041 &hunks
16042 .map(|hunk| buffer_diff::DiffHunk {
16043 buffer_range: hunk.buffer_range,
16044 diff_base_byte_range: hunk.diff_base_byte_range,
16045 secondary_status: hunk.secondary_status,
16046 range: Point::zero()..Point::zero(), // unused
16047 })
16048 .collect::<Vec<_>>(),
16049 &buffer_snapshot,
16050 file_exists,
16051 cx,
16052 )
16053 });
16054 None
16055 }
16056
16057 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16058 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16059 self.buffer
16060 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16061 }
16062
16063 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16064 self.buffer.update(cx, |buffer, cx| {
16065 let ranges = vec![Anchor::min()..Anchor::max()];
16066 if !buffer.all_diff_hunks_expanded()
16067 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16068 {
16069 buffer.collapse_diff_hunks(ranges, cx);
16070 true
16071 } else {
16072 false
16073 }
16074 })
16075 }
16076
16077 fn toggle_diff_hunks_in_ranges(
16078 &mut self,
16079 ranges: Vec<Range<Anchor>>,
16080 cx: &mut Context<Editor>,
16081 ) {
16082 self.buffer.update(cx, |buffer, cx| {
16083 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16084 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16085 })
16086 }
16087
16088 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16089 self.buffer.update(cx, |buffer, cx| {
16090 let snapshot = buffer.snapshot(cx);
16091 let excerpt_id = range.end.excerpt_id;
16092 let point_range = range.to_point(&snapshot);
16093 let expand = !buffer.single_hunk_is_expanded(range, cx);
16094 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16095 })
16096 }
16097
16098 pub(crate) fn apply_all_diff_hunks(
16099 &mut self,
16100 _: &ApplyAllDiffHunks,
16101 window: &mut Window,
16102 cx: &mut Context<Self>,
16103 ) {
16104 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16105
16106 let buffers = self.buffer.read(cx).all_buffers();
16107 for branch_buffer in buffers {
16108 branch_buffer.update(cx, |branch_buffer, cx| {
16109 branch_buffer.merge_into_base(Vec::new(), cx);
16110 });
16111 }
16112
16113 if let Some(project) = self.project.clone() {
16114 self.save(true, project, window, cx).detach_and_log_err(cx);
16115 }
16116 }
16117
16118 pub(crate) fn apply_selected_diff_hunks(
16119 &mut self,
16120 _: &ApplyDiffHunk,
16121 window: &mut Window,
16122 cx: &mut Context<Self>,
16123 ) {
16124 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16125 let snapshot = self.snapshot(window, cx);
16126 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16127 let mut ranges_by_buffer = HashMap::default();
16128 self.transact(window, cx, |editor, _window, cx| {
16129 for hunk in hunks {
16130 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16131 ranges_by_buffer
16132 .entry(buffer.clone())
16133 .or_insert_with(Vec::new)
16134 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16135 }
16136 }
16137
16138 for (buffer, ranges) in ranges_by_buffer {
16139 buffer.update(cx, |buffer, cx| {
16140 buffer.merge_into_base(ranges, cx);
16141 });
16142 }
16143 });
16144
16145 if let Some(project) = self.project.clone() {
16146 self.save(true, project, window, cx).detach_and_log_err(cx);
16147 }
16148 }
16149
16150 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16151 if hovered != self.gutter_hovered {
16152 self.gutter_hovered = hovered;
16153 cx.notify();
16154 }
16155 }
16156
16157 pub fn insert_blocks(
16158 &mut self,
16159 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16160 autoscroll: Option<Autoscroll>,
16161 cx: &mut Context<Self>,
16162 ) -> Vec<CustomBlockId> {
16163 let blocks = self
16164 .display_map
16165 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16166 if let Some(autoscroll) = autoscroll {
16167 self.request_autoscroll(autoscroll, cx);
16168 }
16169 cx.notify();
16170 blocks
16171 }
16172
16173 pub fn resize_blocks(
16174 &mut self,
16175 heights: HashMap<CustomBlockId, u32>,
16176 autoscroll: Option<Autoscroll>,
16177 cx: &mut Context<Self>,
16178 ) {
16179 self.display_map
16180 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16181 if let Some(autoscroll) = autoscroll {
16182 self.request_autoscroll(autoscroll, cx);
16183 }
16184 cx.notify();
16185 }
16186
16187 pub fn replace_blocks(
16188 &mut self,
16189 renderers: HashMap<CustomBlockId, RenderBlock>,
16190 autoscroll: Option<Autoscroll>,
16191 cx: &mut Context<Self>,
16192 ) {
16193 self.display_map
16194 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16195 if let Some(autoscroll) = autoscroll {
16196 self.request_autoscroll(autoscroll, cx);
16197 }
16198 cx.notify();
16199 }
16200
16201 pub fn remove_blocks(
16202 &mut self,
16203 block_ids: HashSet<CustomBlockId>,
16204 autoscroll: Option<Autoscroll>,
16205 cx: &mut Context<Self>,
16206 ) {
16207 self.display_map.update(cx, |display_map, cx| {
16208 display_map.remove_blocks(block_ids, cx)
16209 });
16210 if let Some(autoscroll) = autoscroll {
16211 self.request_autoscroll(autoscroll, cx);
16212 }
16213 cx.notify();
16214 }
16215
16216 pub fn row_for_block(
16217 &self,
16218 block_id: CustomBlockId,
16219 cx: &mut Context<Self>,
16220 ) -> Option<DisplayRow> {
16221 self.display_map
16222 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16223 }
16224
16225 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16226 self.focused_block = Some(focused_block);
16227 }
16228
16229 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16230 self.focused_block.take()
16231 }
16232
16233 pub fn insert_creases(
16234 &mut self,
16235 creases: impl IntoIterator<Item = Crease<Anchor>>,
16236 cx: &mut Context<Self>,
16237 ) -> Vec<CreaseId> {
16238 self.display_map
16239 .update(cx, |map, cx| map.insert_creases(creases, cx))
16240 }
16241
16242 pub fn remove_creases(
16243 &mut self,
16244 ids: impl IntoIterator<Item = CreaseId>,
16245 cx: &mut Context<Self>,
16246 ) -> Vec<(CreaseId, Range<Anchor>)> {
16247 self.display_map
16248 .update(cx, |map, cx| map.remove_creases(ids, cx))
16249 }
16250
16251 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16252 self.display_map
16253 .update(cx, |map, cx| map.snapshot(cx))
16254 .longest_row()
16255 }
16256
16257 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16258 self.display_map
16259 .update(cx, |map, cx| map.snapshot(cx))
16260 .max_point()
16261 }
16262
16263 pub fn text(&self, cx: &App) -> String {
16264 self.buffer.read(cx).read(cx).text()
16265 }
16266
16267 pub fn is_empty(&self, cx: &App) -> bool {
16268 self.buffer.read(cx).read(cx).is_empty()
16269 }
16270
16271 pub fn text_option(&self, cx: &App) -> Option<String> {
16272 let text = self.text(cx);
16273 let text = text.trim();
16274
16275 if text.is_empty() {
16276 return None;
16277 }
16278
16279 Some(text.to_string())
16280 }
16281
16282 pub fn set_text(
16283 &mut self,
16284 text: impl Into<Arc<str>>,
16285 window: &mut Window,
16286 cx: &mut Context<Self>,
16287 ) {
16288 self.transact(window, cx, |this, _, cx| {
16289 this.buffer
16290 .read(cx)
16291 .as_singleton()
16292 .expect("you can only call set_text on editors for singleton buffers")
16293 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16294 });
16295 }
16296
16297 pub fn display_text(&self, cx: &mut App) -> String {
16298 self.display_map
16299 .update(cx, |map, cx| map.snapshot(cx))
16300 .text()
16301 }
16302
16303 fn create_minimap(
16304 &self,
16305 minimap_settings: MinimapSettings,
16306 window: &mut Window,
16307 cx: &mut Context<Self>,
16308 ) -> Option<Entity<Self>> {
16309 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
16310 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
16311 }
16312
16313 fn initialize_new_minimap(
16314 &self,
16315 minimap_settings: MinimapSettings,
16316 window: &mut Window,
16317 cx: &mut Context<Self>,
16318 ) -> Entity<Self> {
16319 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
16320
16321 let mut minimap = Editor::new_internal(
16322 EditorMode::Minimap {
16323 parent: cx.weak_entity(),
16324 },
16325 self.buffer.clone(),
16326 self.project.clone(),
16327 Some(self.display_map.clone()),
16328 window,
16329 cx,
16330 );
16331 minimap.scroll_manager.clone_state(&self.scroll_manager);
16332 minimap.set_text_style_refinement(TextStyleRefinement {
16333 font_size: Some(MINIMAP_FONT_SIZE),
16334 font_weight: Some(MINIMAP_FONT_WEIGHT),
16335 ..Default::default()
16336 });
16337 minimap.update_minimap_configuration(minimap_settings, cx);
16338 cx.new(|_| minimap)
16339 }
16340
16341 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
16342 let current_line_highlight = minimap_settings
16343 .current_line_highlight
16344 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
16345 self.set_current_line_highlight(Some(current_line_highlight));
16346 }
16347
16348 pub fn minimap(&self) -> Option<&Entity<Self>> {
16349 self.minimap.as_ref().filter(|_| self.show_minimap)
16350 }
16351
16352 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16353 let mut wrap_guides = smallvec::smallvec![];
16354
16355 if self.show_wrap_guides == Some(false) {
16356 return wrap_guides;
16357 }
16358
16359 let settings = self.buffer.read(cx).language_settings(cx);
16360 if settings.show_wrap_guides {
16361 match self.soft_wrap_mode(cx) {
16362 SoftWrap::Column(soft_wrap) => {
16363 wrap_guides.push((soft_wrap as usize, true));
16364 }
16365 SoftWrap::Bounded(soft_wrap) => {
16366 wrap_guides.push((soft_wrap as usize, true));
16367 }
16368 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16369 }
16370 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16371 }
16372
16373 wrap_guides
16374 }
16375
16376 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16377 let settings = self.buffer.read(cx).language_settings(cx);
16378 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16379 match mode {
16380 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16381 SoftWrap::None
16382 }
16383 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16384 language_settings::SoftWrap::PreferredLineLength => {
16385 SoftWrap::Column(settings.preferred_line_length)
16386 }
16387 language_settings::SoftWrap::Bounded => {
16388 SoftWrap::Bounded(settings.preferred_line_length)
16389 }
16390 }
16391 }
16392
16393 pub fn set_soft_wrap_mode(
16394 &mut self,
16395 mode: language_settings::SoftWrap,
16396
16397 cx: &mut Context<Self>,
16398 ) {
16399 self.soft_wrap_mode_override = Some(mode);
16400 cx.notify();
16401 }
16402
16403 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16404 self.hard_wrap = hard_wrap;
16405 cx.notify();
16406 }
16407
16408 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16409 self.text_style_refinement = Some(style);
16410 }
16411
16412 /// called by the Element so we know what style we were most recently rendered with.
16413 pub(crate) fn set_style(
16414 &mut self,
16415 style: EditorStyle,
16416 window: &mut Window,
16417 cx: &mut Context<Self>,
16418 ) {
16419 // We intentionally do not inform the display map about the minimap style
16420 // so that wrapping is not recalculated and stays consistent for the editor
16421 // and its linked minimap.
16422 if !self.mode.is_minimap() {
16423 let rem_size = window.rem_size();
16424 self.display_map.update(cx, |map, cx| {
16425 map.set_font(
16426 style.text.font(),
16427 style.text.font_size.to_pixels(rem_size),
16428 cx,
16429 )
16430 });
16431 }
16432 self.style = Some(style);
16433 }
16434
16435 pub fn style(&self) -> Option<&EditorStyle> {
16436 self.style.as_ref()
16437 }
16438
16439 // Called by the element. This method is not designed to be called outside of the editor
16440 // element's layout code because it does not notify when rewrapping is computed synchronously.
16441 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16442 self.display_map
16443 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16444 }
16445
16446 pub fn set_soft_wrap(&mut self) {
16447 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16448 }
16449
16450 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16451 if self.soft_wrap_mode_override.is_some() {
16452 self.soft_wrap_mode_override.take();
16453 } else {
16454 let soft_wrap = match self.soft_wrap_mode(cx) {
16455 SoftWrap::GitDiff => return,
16456 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16457 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16458 language_settings::SoftWrap::None
16459 }
16460 };
16461 self.soft_wrap_mode_override = Some(soft_wrap);
16462 }
16463 cx.notify();
16464 }
16465
16466 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16467 let Some(workspace) = self.workspace() else {
16468 return;
16469 };
16470 let fs = workspace.read(cx).app_state().fs.clone();
16471 let current_show = TabBarSettings::get_global(cx).show;
16472 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16473 setting.show = Some(!current_show);
16474 });
16475 }
16476
16477 pub fn toggle_indent_guides(
16478 &mut self,
16479 _: &ToggleIndentGuides,
16480 _: &mut Window,
16481 cx: &mut Context<Self>,
16482 ) {
16483 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16484 self.buffer
16485 .read(cx)
16486 .language_settings(cx)
16487 .indent_guides
16488 .enabled
16489 });
16490 self.show_indent_guides = Some(!currently_enabled);
16491 cx.notify();
16492 }
16493
16494 fn should_show_indent_guides(&self) -> Option<bool> {
16495 self.show_indent_guides
16496 }
16497
16498 pub fn toggle_line_numbers(
16499 &mut self,
16500 _: &ToggleLineNumbers,
16501 _: &mut Window,
16502 cx: &mut Context<Self>,
16503 ) {
16504 let mut editor_settings = EditorSettings::get_global(cx).clone();
16505 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16506 EditorSettings::override_global(editor_settings, cx);
16507 }
16508
16509 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16510 if let Some(show_line_numbers) = self.show_line_numbers {
16511 return show_line_numbers;
16512 }
16513 EditorSettings::get_global(cx).gutter.line_numbers
16514 }
16515
16516 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16517 self.use_relative_line_numbers
16518 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16519 }
16520
16521 pub fn toggle_relative_line_numbers(
16522 &mut self,
16523 _: &ToggleRelativeLineNumbers,
16524 _: &mut Window,
16525 cx: &mut Context<Self>,
16526 ) {
16527 let is_relative = self.should_use_relative_line_numbers(cx);
16528 self.set_relative_line_number(Some(!is_relative), cx)
16529 }
16530
16531 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16532 self.use_relative_line_numbers = is_relative;
16533 cx.notify();
16534 }
16535
16536 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16537 self.show_gutter = show_gutter;
16538 cx.notify();
16539 }
16540
16541 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
16542 self.show_scrollbars = show_scrollbars;
16543 cx.notify();
16544 }
16545
16546 pub fn set_show_minimap(&mut self, show_minimap: bool, cx: &mut Context<Self>) {
16547 self.show_minimap = show_minimap;
16548 cx.notify();
16549 }
16550
16551 pub fn disable_scrollbars_and_minimap(&mut self, cx: &mut Context<Self>) {
16552 self.set_show_scrollbars(false, cx);
16553 self.set_show_minimap(false, cx);
16554 }
16555
16556 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
16557 self.show_line_numbers = Some(show_line_numbers);
16558 cx.notify();
16559 }
16560
16561 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
16562 self.disable_expand_excerpt_buttons = true;
16563 cx.notify();
16564 }
16565
16566 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
16567 self.show_git_diff_gutter = Some(show_git_diff_gutter);
16568 cx.notify();
16569 }
16570
16571 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
16572 self.show_code_actions = Some(show_code_actions);
16573 cx.notify();
16574 }
16575
16576 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
16577 self.show_runnables = Some(show_runnables);
16578 cx.notify();
16579 }
16580
16581 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
16582 self.show_breakpoints = Some(show_breakpoints);
16583 cx.notify();
16584 }
16585
16586 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
16587 if self.display_map.read(cx).masked != masked {
16588 self.display_map.update(cx, |map, _| map.masked = masked);
16589 }
16590 cx.notify()
16591 }
16592
16593 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
16594 self.show_wrap_guides = Some(show_wrap_guides);
16595 cx.notify();
16596 }
16597
16598 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
16599 self.show_indent_guides = Some(show_indent_guides);
16600 cx.notify();
16601 }
16602
16603 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
16604 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
16605 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
16606 if let Some(dir) = file.abs_path(cx).parent() {
16607 return Some(dir.to_owned());
16608 }
16609 }
16610
16611 if let Some(project_path) = buffer.read(cx).project_path(cx) {
16612 return Some(project_path.path.to_path_buf());
16613 }
16614 }
16615
16616 None
16617 }
16618
16619 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
16620 self.active_excerpt(cx)?
16621 .1
16622 .read(cx)
16623 .file()
16624 .and_then(|f| f.as_local())
16625 }
16626
16627 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16628 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16629 let buffer = buffer.read(cx);
16630 if let Some(project_path) = buffer.project_path(cx) {
16631 let project = self.project.as_ref()?.read(cx);
16632 project.absolute_path(&project_path, cx)
16633 } else {
16634 buffer
16635 .file()
16636 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
16637 }
16638 })
16639 }
16640
16641 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16642 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16643 let project_path = buffer.read(cx).project_path(cx)?;
16644 let project = self.project.as_ref()?.read(cx);
16645 let entry = project.entry_for_path(&project_path, cx)?;
16646 let path = entry.path.to_path_buf();
16647 Some(path)
16648 })
16649 }
16650
16651 pub fn reveal_in_finder(
16652 &mut self,
16653 _: &RevealInFileManager,
16654 _window: &mut Window,
16655 cx: &mut Context<Self>,
16656 ) {
16657 if let Some(target) = self.target_file(cx) {
16658 cx.reveal_path(&target.abs_path(cx));
16659 }
16660 }
16661
16662 pub fn copy_path(
16663 &mut self,
16664 _: &zed_actions::workspace::CopyPath,
16665 _window: &mut Window,
16666 cx: &mut Context<Self>,
16667 ) {
16668 if let Some(path) = self.target_file_abs_path(cx) {
16669 if let Some(path) = path.to_str() {
16670 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16671 }
16672 }
16673 }
16674
16675 pub fn copy_relative_path(
16676 &mut self,
16677 _: &zed_actions::workspace::CopyRelativePath,
16678 _window: &mut Window,
16679 cx: &mut Context<Self>,
16680 ) {
16681 if let Some(path) = self.target_file_path(cx) {
16682 if let Some(path) = path.to_str() {
16683 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16684 }
16685 }
16686 }
16687
16688 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
16689 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
16690 buffer.read(cx).project_path(cx)
16691 } else {
16692 None
16693 }
16694 }
16695
16696 // Returns true if the editor handled a go-to-line request
16697 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
16698 maybe!({
16699 let breakpoint_store = self.breakpoint_store.as_ref()?;
16700
16701 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
16702 else {
16703 self.clear_row_highlights::<ActiveDebugLine>();
16704 return None;
16705 };
16706
16707 let position = active_stack_frame.position;
16708 let buffer_id = position.buffer_id?;
16709 let snapshot = self
16710 .project
16711 .as_ref()?
16712 .read(cx)
16713 .buffer_for_id(buffer_id, cx)?
16714 .read(cx)
16715 .snapshot();
16716
16717 let mut handled = false;
16718 for (id, ExcerptRange { context, .. }) in
16719 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
16720 {
16721 if context.start.cmp(&position, &snapshot).is_ge()
16722 || context.end.cmp(&position, &snapshot).is_lt()
16723 {
16724 continue;
16725 }
16726 let snapshot = self.buffer.read(cx).snapshot(cx);
16727 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
16728
16729 handled = true;
16730 self.clear_row_highlights::<ActiveDebugLine>();
16731 self.go_to_line::<ActiveDebugLine>(
16732 multibuffer_anchor,
16733 Some(cx.theme().colors().editor_debugger_active_line_background),
16734 window,
16735 cx,
16736 );
16737
16738 cx.notify();
16739 }
16740
16741 handled.then_some(())
16742 })
16743 .is_some()
16744 }
16745
16746 pub fn copy_file_name_without_extension(
16747 &mut self,
16748 _: &CopyFileNameWithoutExtension,
16749 _: &mut Window,
16750 cx: &mut Context<Self>,
16751 ) {
16752 if let Some(file) = self.target_file(cx) {
16753 if let Some(file_stem) = file.path().file_stem() {
16754 if let Some(name) = file_stem.to_str() {
16755 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16756 }
16757 }
16758 }
16759 }
16760
16761 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
16762 if let Some(file) = self.target_file(cx) {
16763 if let Some(file_name) = file.path().file_name() {
16764 if let Some(name) = file_name.to_str() {
16765 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16766 }
16767 }
16768 }
16769 }
16770
16771 pub fn toggle_git_blame(
16772 &mut self,
16773 _: &::git::Blame,
16774 window: &mut Window,
16775 cx: &mut Context<Self>,
16776 ) {
16777 self.show_git_blame_gutter = !self.show_git_blame_gutter;
16778
16779 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
16780 self.start_git_blame(true, window, cx);
16781 }
16782
16783 cx.notify();
16784 }
16785
16786 pub fn toggle_git_blame_inline(
16787 &mut self,
16788 _: &ToggleGitBlameInline,
16789 window: &mut Window,
16790 cx: &mut Context<Self>,
16791 ) {
16792 self.toggle_git_blame_inline_internal(true, window, cx);
16793 cx.notify();
16794 }
16795
16796 pub fn open_git_blame_commit(
16797 &mut self,
16798 _: &OpenGitBlameCommit,
16799 window: &mut Window,
16800 cx: &mut Context<Self>,
16801 ) {
16802 self.open_git_blame_commit_internal(window, cx);
16803 }
16804
16805 fn open_git_blame_commit_internal(
16806 &mut self,
16807 window: &mut Window,
16808 cx: &mut Context<Self>,
16809 ) -> Option<()> {
16810 let blame = self.blame.as_ref()?;
16811 let snapshot = self.snapshot(window, cx);
16812 let cursor = self.selections.newest::<Point>(cx).head();
16813 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
16814 let blame_entry = blame
16815 .update(cx, |blame, cx| {
16816 blame
16817 .blame_for_rows(
16818 &[RowInfo {
16819 buffer_id: Some(buffer.remote_id()),
16820 buffer_row: Some(point.row),
16821 ..Default::default()
16822 }],
16823 cx,
16824 )
16825 .next()
16826 })
16827 .flatten()?;
16828 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
16829 let repo = blame.read(cx).repository(cx)?;
16830 let workspace = self.workspace()?.downgrade();
16831 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
16832 None
16833 }
16834
16835 pub fn git_blame_inline_enabled(&self) -> bool {
16836 self.git_blame_inline_enabled
16837 }
16838
16839 pub fn toggle_selection_menu(
16840 &mut self,
16841 _: &ToggleSelectionMenu,
16842 _: &mut Window,
16843 cx: &mut Context<Self>,
16844 ) {
16845 self.show_selection_menu = self
16846 .show_selection_menu
16847 .map(|show_selections_menu| !show_selections_menu)
16848 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
16849
16850 cx.notify();
16851 }
16852
16853 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
16854 self.show_selection_menu
16855 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
16856 }
16857
16858 fn start_git_blame(
16859 &mut self,
16860 user_triggered: bool,
16861 window: &mut Window,
16862 cx: &mut Context<Self>,
16863 ) {
16864 if let Some(project) = self.project.as_ref() {
16865 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
16866 return;
16867 };
16868
16869 if buffer.read(cx).file().is_none() {
16870 return;
16871 }
16872
16873 let focused = self.focus_handle(cx).contains_focused(window, cx);
16874
16875 let project = project.clone();
16876 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
16877 self.blame_subscription =
16878 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
16879 self.blame = Some(blame);
16880 }
16881 }
16882
16883 fn toggle_git_blame_inline_internal(
16884 &mut self,
16885 user_triggered: bool,
16886 window: &mut Window,
16887 cx: &mut Context<Self>,
16888 ) {
16889 if self.git_blame_inline_enabled {
16890 self.git_blame_inline_enabled = false;
16891 self.show_git_blame_inline = false;
16892 self.show_git_blame_inline_delay_task.take();
16893 } else {
16894 self.git_blame_inline_enabled = true;
16895 self.start_git_blame_inline(user_triggered, window, cx);
16896 }
16897
16898 cx.notify();
16899 }
16900
16901 fn start_git_blame_inline(
16902 &mut self,
16903 user_triggered: bool,
16904 window: &mut Window,
16905 cx: &mut Context<Self>,
16906 ) {
16907 self.start_git_blame(user_triggered, window, cx);
16908
16909 if ProjectSettings::get_global(cx)
16910 .git
16911 .inline_blame_delay()
16912 .is_some()
16913 {
16914 self.start_inline_blame_timer(window, cx);
16915 } else {
16916 self.show_git_blame_inline = true
16917 }
16918 }
16919
16920 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
16921 self.blame.as_ref()
16922 }
16923
16924 pub fn show_git_blame_gutter(&self) -> bool {
16925 self.show_git_blame_gutter
16926 }
16927
16928 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
16929 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
16930 }
16931
16932 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
16933 self.show_git_blame_inline
16934 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
16935 && !self.newest_selection_head_on_empty_line(cx)
16936 && self.has_blame_entries(cx)
16937 }
16938
16939 fn has_blame_entries(&self, cx: &App) -> bool {
16940 self.blame()
16941 .map_or(false, |blame| blame.read(cx).has_generated_entries())
16942 }
16943
16944 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
16945 let cursor_anchor = self.selections.newest_anchor().head();
16946
16947 let snapshot = self.buffer.read(cx).snapshot(cx);
16948 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
16949
16950 snapshot.line_len(buffer_row) == 0
16951 }
16952
16953 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
16954 let buffer_and_selection = maybe!({
16955 let selection = self.selections.newest::<Point>(cx);
16956 let selection_range = selection.range();
16957
16958 let multi_buffer = self.buffer().read(cx);
16959 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16960 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
16961
16962 let (buffer, range, _) = if selection.reversed {
16963 buffer_ranges.first()
16964 } else {
16965 buffer_ranges.last()
16966 }?;
16967
16968 let selection = text::ToPoint::to_point(&range.start, &buffer).row
16969 ..text::ToPoint::to_point(&range.end, &buffer).row;
16970 Some((
16971 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
16972 selection,
16973 ))
16974 });
16975
16976 let Some((buffer, selection)) = buffer_and_selection else {
16977 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
16978 };
16979
16980 let Some(project) = self.project.as_ref() else {
16981 return Task::ready(Err(anyhow!("editor does not have project")));
16982 };
16983
16984 project.update(cx, |project, cx| {
16985 project.get_permalink_to_line(&buffer, selection, cx)
16986 })
16987 }
16988
16989 pub fn copy_permalink_to_line(
16990 &mut self,
16991 _: &CopyPermalinkToLine,
16992 window: &mut Window,
16993 cx: &mut Context<Self>,
16994 ) {
16995 let permalink_task = self.get_permalink_to_line(cx);
16996 let workspace = self.workspace();
16997
16998 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16999 Ok(permalink) => {
17000 cx.update(|_, cx| {
17001 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17002 })
17003 .ok();
17004 }
17005 Err(err) => {
17006 let message = format!("Failed to copy permalink: {err}");
17007
17008 Err::<(), anyhow::Error>(err).log_err();
17009
17010 if let Some(workspace) = workspace {
17011 workspace
17012 .update_in(cx, |workspace, _, cx| {
17013 struct CopyPermalinkToLine;
17014
17015 workspace.show_toast(
17016 Toast::new(
17017 NotificationId::unique::<CopyPermalinkToLine>(),
17018 message,
17019 ),
17020 cx,
17021 )
17022 })
17023 .ok();
17024 }
17025 }
17026 })
17027 .detach();
17028 }
17029
17030 pub fn copy_file_location(
17031 &mut self,
17032 _: &CopyFileLocation,
17033 _: &mut Window,
17034 cx: &mut Context<Self>,
17035 ) {
17036 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17037 if let Some(file) = self.target_file(cx) {
17038 if let Some(path) = file.path().to_str() {
17039 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17040 }
17041 }
17042 }
17043
17044 pub fn open_permalink_to_line(
17045 &mut self,
17046 _: &OpenPermalinkToLine,
17047 window: &mut Window,
17048 cx: &mut Context<Self>,
17049 ) {
17050 let permalink_task = self.get_permalink_to_line(cx);
17051 let workspace = self.workspace();
17052
17053 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17054 Ok(permalink) => {
17055 cx.update(|_, cx| {
17056 cx.open_url(permalink.as_ref());
17057 })
17058 .ok();
17059 }
17060 Err(err) => {
17061 let message = format!("Failed to open permalink: {err}");
17062
17063 Err::<(), anyhow::Error>(err).log_err();
17064
17065 if let Some(workspace) = workspace {
17066 workspace
17067 .update(cx, |workspace, cx| {
17068 struct OpenPermalinkToLine;
17069
17070 workspace.show_toast(
17071 Toast::new(
17072 NotificationId::unique::<OpenPermalinkToLine>(),
17073 message,
17074 ),
17075 cx,
17076 )
17077 })
17078 .ok();
17079 }
17080 }
17081 })
17082 .detach();
17083 }
17084
17085 pub fn insert_uuid_v4(
17086 &mut self,
17087 _: &InsertUuidV4,
17088 window: &mut Window,
17089 cx: &mut Context<Self>,
17090 ) {
17091 self.insert_uuid(UuidVersion::V4, window, cx);
17092 }
17093
17094 pub fn insert_uuid_v7(
17095 &mut self,
17096 _: &InsertUuidV7,
17097 window: &mut Window,
17098 cx: &mut Context<Self>,
17099 ) {
17100 self.insert_uuid(UuidVersion::V7, window, cx);
17101 }
17102
17103 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17104 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17105 self.transact(window, cx, |this, window, cx| {
17106 let edits = this
17107 .selections
17108 .all::<Point>(cx)
17109 .into_iter()
17110 .map(|selection| {
17111 let uuid = match version {
17112 UuidVersion::V4 => uuid::Uuid::new_v4(),
17113 UuidVersion::V7 => uuid::Uuid::now_v7(),
17114 };
17115
17116 (selection.range(), uuid.to_string())
17117 });
17118 this.edit(edits, cx);
17119 this.refresh_inline_completion(true, false, window, cx);
17120 });
17121 }
17122
17123 pub fn open_selections_in_multibuffer(
17124 &mut self,
17125 _: &OpenSelectionsInMultibuffer,
17126 window: &mut Window,
17127 cx: &mut Context<Self>,
17128 ) {
17129 let multibuffer = self.buffer.read(cx);
17130
17131 let Some(buffer) = multibuffer.as_singleton() else {
17132 return;
17133 };
17134
17135 let Some(workspace) = self.workspace() else {
17136 return;
17137 };
17138
17139 let locations = self
17140 .selections
17141 .disjoint_anchors()
17142 .iter()
17143 .map(|range| Location {
17144 buffer: buffer.clone(),
17145 range: range.start.text_anchor..range.end.text_anchor,
17146 })
17147 .collect::<Vec<_>>();
17148
17149 let title = multibuffer.title(cx).to_string();
17150
17151 cx.spawn_in(window, async move |_, cx| {
17152 workspace.update_in(cx, |workspace, window, cx| {
17153 Self::open_locations_in_multibuffer(
17154 workspace,
17155 locations,
17156 format!("Selections for '{title}'"),
17157 false,
17158 MultibufferSelectionMode::All,
17159 window,
17160 cx,
17161 );
17162 })
17163 })
17164 .detach();
17165 }
17166
17167 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17168 /// last highlight added will be used.
17169 ///
17170 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17171 pub fn highlight_rows<T: 'static>(
17172 &mut self,
17173 range: Range<Anchor>,
17174 color: Hsla,
17175 options: RowHighlightOptions,
17176 cx: &mut Context<Self>,
17177 ) {
17178 let snapshot = self.buffer().read(cx).snapshot(cx);
17179 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17180 let ix = row_highlights.binary_search_by(|highlight| {
17181 Ordering::Equal
17182 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17183 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17184 });
17185
17186 if let Err(mut ix) = ix {
17187 let index = post_inc(&mut self.highlight_order);
17188
17189 // If this range intersects with the preceding highlight, then merge it with
17190 // the preceding highlight. Otherwise insert a new highlight.
17191 let mut merged = false;
17192 if ix > 0 {
17193 let prev_highlight = &mut row_highlights[ix - 1];
17194 if prev_highlight
17195 .range
17196 .end
17197 .cmp(&range.start, &snapshot)
17198 .is_ge()
17199 {
17200 ix -= 1;
17201 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17202 prev_highlight.range.end = range.end;
17203 }
17204 merged = true;
17205 prev_highlight.index = index;
17206 prev_highlight.color = color;
17207 prev_highlight.options = options;
17208 }
17209 }
17210
17211 if !merged {
17212 row_highlights.insert(
17213 ix,
17214 RowHighlight {
17215 range: range.clone(),
17216 index,
17217 color,
17218 options,
17219 type_id: TypeId::of::<T>(),
17220 },
17221 );
17222 }
17223
17224 // If any of the following highlights intersect with this one, merge them.
17225 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17226 let highlight = &row_highlights[ix];
17227 if next_highlight
17228 .range
17229 .start
17230 .cmp(&highlight.range.end, &snapshot)
17231 .is_le()
17232 {
17233 if next_highlight
17234 .range
17235 .end
17236 .cmp(&highlight.range.end, &snapshot)
17237 .is_gt()
17238 {
17239 row_highlights[ix].range.end = next_highlight.range.end;
17240 }
17241 row_highlights.remove(ix + 1);
17242 } else {
17243 break;
17244 }
17245 }
17246 }
17247 }
17248
17249 /// Remove any highlighted row ranges of the given type that intersect the
17250 /// given ranges.
17251 pub fn remove_highlighted_rows<T: 'static>(
17252 &mut self,
17253 ranges_to_remove: Vec<Range<Anchor>>,
17254 cx: &mut Context<Self>,
17255 ) {
17256 let snapshot = self.buffer().read(cx).snapshot(cx);
17257 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17258 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17259 row_highlights.retain(|highlight| {
17260 while let Some(range_to_remove) = ranges_to_remove.peek() {
17261 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17262 Ordering::Less | Ordering::Equal => {
17263 ranges_to_remove.next();
17264 }
17265 Ordering::Greater => {
17266 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17267 Ordering::Less | Ordering::Equal => {
17268 return false;
17269 }
17270 Ordering::Greater => break,
17271 }
17272 }
17273 }
17274 }
17275
17276 true
17277 })
17278 }
17279
17280 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17281 pub fn clear_row_highlights<T: 'static>(&mut self) {
17282 self.highlighted_rows.remove(&TypeId::of::<T>());
17283 }
17284
17285 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17286 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17287 self.highlighted_rows
17288 .get(&TypeId::of::<T>())
17289 .map_or(&[] as &[_], |vec| vec.as_slice())
17290 .iter()
17291 .map(|highlight| (highlight.range.clone(), highlight.color))
17292 }
17293
17294 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17295 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17296 /// Allows to ignore certain kinds of highlights.
17297 pub fn highlighted_display_rows(
17298 &self,
17299 window: &mut Window,
17300 cx: &mut App,
17301 ) -> BTreeMap<DisplayRow, LineHighlight> {
17302 let snapshot = self.snapshot(window, cx);
17303 let mut used_highlight_orders = HashMap::default();
17304 self.highlighted_rows
17305 .iter()
17306 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17307 .fold(
17308 BTreeMap::<DisplayRow, LineHighlight>::new(),
17309 |mut unique_rows, highlight| {
17310 let start = highlight.range.start.to_display_point(&snapshot);
17311 let end = highlight.range.end.to_display_point(&snapshot);
17312 let start_row = start.row().0;
17313 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17314 && end.column() == 0
17315 {
17316 end.row().0.saturating_sub(1)
17317 } else {
17318 end.row().0
17319 };
17320 for row in start_row..=end_row {
17321 let used_index =
17322 used_highlight_orders.entry(row).or_insert(highlight.index);
17323 if highlight.index >= *used_index {
17324 *used_index = highlight.index;
17325 unique_rows.insert(
17326 DisplayRow(row),
17327 LineHighlight {
17328 include_gutter: highlight.options.include_gutter,
17329 border: None,
17330 background: highlight.color.into(),
17331 type_id: Some(highlight.type_id),
17332 },
17333 );
17334 }
17335 }
17336 unique_rows
17337 },
17338 )
17339 }
17340
17341 pub fn highlighted_display_row_for_autoscroll(
17342 &self,
17343 snapshot: &DisplaySnapshot,
17344 ) -> Option<DisplayRow> {
17345 self.highlighted_rows
17346 .values()
17347 .flat_map(|highlighted_rows| highlighted_rows.iter())
17348 .filter_map(|highlight| {
17349 if highlight.options.autoscroll {
17350 Some(highlight.range.start.to_display_point(snapshot).row())
17351 } else {
17352 None
17353 }
17354 })
17355 .min()
17356 }
17357
17358 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17359 self.highlight_background::<SearchWithinRange>(
17360 ranges,
17361 |colors| colors.editor_document_highlight_read_background,
17362 cx,
17363 )
17364 }
17365
17366 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17367 self.breadcrumb_header = Some(new_header);
17368 }
17369
17370 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17371 self.clear_background_highlights::<SearchWithinRange>(cx);
17372 }
17373
17374 pub fn highlight_background<T: 'static>(
17375 &mut self,
17376 ranges: &[Range<Anchor>],
17377 color_fetcher: fn(&ThemeColors) -> Hsla,
17378 cx: &mut Context<Self>,
17379 ) {
17380 self.background_highlights
17381 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17382 self.scrollbar_marker_state.dirty = true;
17383 cx.notify();
17384 }
17385
17386 pub fn clear_background_highlights<T: 'static>(
17387 &mut self,
17388 cx: &mut Context<Self>,
17389 ) -> Option<BackgroundHighlight> {
17390 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17391 if !text_highlights.1.is_empty() {
17392 self.scrollbar_marker_state.dirty = true;
17393 cx.notify();
17394 }
17395 Some(text_highlights)
17396 }
17397
17398 pub fn highlight_gutter<T: 'static>(
17399 &mut self,
17400 ranges: &[Range<Anchor>],
17401 color_fetcher: fn(&App) -> Hsla,
17402 cx: &mut Context<Self>,
17403 ) {
17404 self.gutter_highlights
17405 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17406 cx.notify();
17407 }
17408
17409 pub fn clear_gutter_highlights<T: 'static>(
17410 &mut self,
17411 cx: &mut Context<Self>,
17412 ) -> Option<GutterHighlight> {
17413 cx.notify();
17414 self.gutter_highlights.remove(&TypeId::of::<T>())
17415 }
17416
17417 #[cfg(feature = "test-support")]
17418 pub fn all_text_background_highlights(
17419 &self,
17420 window: &mut Window,
17421 cx: &mut Context<Self>,
17422 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17423 let snapshot = self.snapshot(window, cx);
17424 let buffer = &snapshot.buffer_snapshot;
17425 let start = buffer.anchor_before(0);
17426 let end = buffer.anchor_after(buffer.len());
17427 let theme = cx.theme().colors();
17428 self.background_highlights_in_range(start..end, &snapshot, theme)
17429 }
17430
17431 #[cfg(feature = "test-support")]
17432 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17433 let snapshot = self.buffer().read(cx).snapshot(cx);
17434
17435 let highlights = self
17436 .background_highlights
17437 .get(&TypeId::of::<items::BufferSearchHighlights>());
17438
17439 if let Some((_color, ranges)) = highlights {
17440 ranges
17441 .iter()
17442 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17443 .collect_vec()
17444 } else {
17445 vec![]
17446 }
17447 }
17448
17449 fn document_highlights_for_position<'a>(
17450 &'a self,
17451 position: Anchor,
17452 buffer: &'a MultiBufferSnapshot,
17453 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17454 let read_highlights = self
17455 .background_highlights
17456 .get(&TypeId::of::<DocumentHighlightRead>())
17457 .map(|h| &h.1);
17458 let write_highlights = self
17459 .background_highlights
17460 .get(&TypeId::of::<DocumentHighlightWrite>())
17461 .map(|h| &h.1);
17462 let left_position = position.bias_left(buffer);
17463 let right_position = position.bias_right(buffer);
17464 read_highlights
17465 .into_iter()
17466 .chain(write_highlights)
17467 .flat_map(move |ranges| {
17468 let start_ix = match ranges.binary_search_by(|probe| {
17469 let cmp = probe.end.cmp(&left_position, buffer);
17470 if cmp.is_ge() {
17471 Ordering::Greater
17472 } else {
17473 Ordering::Less
17474 }
17475 }) {
17476 Ok(i) | Err(i) => i,
17477 };
17478
17479 ranges[start_ix..]
17480 .iter()
17481 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17482 })
17483 }
17484
17485 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17486 self.background_highlights
17487 .get(&TypeId::of::<T>())
17488 .map_or(false, |(_, highlights)| !highlights.is_empty())
17489 }
17490
17491 pub fn background_highlights_in_range(
17492 &self,
17493 search_range: Range<Anchor>,
17494 display_snapshot: &DisplaySnapshot,
17495 theme: &ThemeColors,
17496 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17497 let mut results = Vec::new();
17498 for (color_fetcher, ranges) in self.background_highlights.values() {
17499 let color = color_fetcher(theme);
17500 let start_ix = match ranges.binary_search_by(|probe| {
17501 let cmp = probe
17502 .end
17503 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17504 if cmp.is_gt() {
17505 Ordering::Greater
17506 } else {
17507 Ordering::Less
17508 }
17509 }) {
17510 Ok(i) | Err(i) => i,
17511 };
17512 for range in &ranges[start_ix..] {
17513 if range
17514 .start
17515 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17516 .is_ge()
17517 {
17518 break;
17519 }
17520
17521 let start = range.start.to_display_point(display_snapshot);
17522 let end = range.end.to_display_point(display_snapshot);
17523 results.push((start..end, color))
17524 }
17525 }
17526 results
17527 }
17528
17529 pub fn background_highlight_row_ranges<T: 'static>(
17530 &self,
17531 search_range: Range<Anchor>,
17532 display_snapshot: &DisplaySnapshot,
17533 count: usize,
17534 ) -> Vec<RangeInclusive<DisplayPoint>> {
17535 let mut results = Vec::new();
17536 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
17537 return vec![];
17538 };
17539
17540 let start_ix = match ranges.binary_search_by(|probe| {
17541 let cmp = probe
17542 .end
17543 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17544 if cmp.is_gt() {
17545 Ordering::Greater
17546 } else {
17547 Ordering::Less
17548 }
17549 }) {
17550 Ok(i) | Err(i) => i,
17551 };
17552 let mut push_region = |start: Option<Point>, end: Option<Point>| {
17553 if let (Some(start_display), Some(end_display)) = (start, end) {
17554 results.push(
17555 start_display.to_display_point(display_snapshot)
17556 ..=end_display.to_display_point(display_snapshot),
17557 );
17558 }
17559 };
17560 let mut start_row: Option<Point> = None;
17561 let mut end_row: Option<Point> = None;
17562 if ranges.len() > count {
17563 return Vec::new();
17564 }
17565 for range in &ranges[start_ix..] {
17566 if range
17567 .start
17568 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17569 .is_ge()
17570 {
17571 break;
17572 }
17573 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
17574 if let Some(current_row) = &end_row {
17575 if end.row == current_row.row {
17576 continue;
17577 }
17578 }
17579 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
17580 if start_row.is_none() {
17581 assert_eq!(end_row, None);
17582 start_row = Some(start);
17583 end_row = Some(end);
17584 continue;
17585 }
17586 if let Some(current_end) = end_row.as_mut() {
17587 if start.row > current_end.row + 1 {
17588 push_region(start_row, end_row);
17589 start_row = Some(start);
17590 end_row = Some(end);
17591 } else {
17592 // Merge two hunks.
17593 *current_end = end;
17594 }
17595 } else {
17596 unreachable!();
17597 }
17598 }
17599 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
17600 push_region(start_row, end_row);
17601 results
17602 }
17603
17604 pub fn gutter_highlights_in_range(
17605 &self,
17606 search_range: Range<Anchor>,
17607 display_snapshot: &DisplaySnapshot,
17608 cx: &App,
17609 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17610 let mut results = Vec::new();
17611 for (color_fetcher, ranges) in self.gutter_highlights.values() {
17612 let color = color_fetcher(cx);
17613 let start_ix = match ranges.binary_search_by(|probe| {
17614 let cmp = probe
17615 .end
17616 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17617 if cmp.is_gt() {
17618 Ordering::Greater
17619 } else {
17620 Ordering::Less
17621 }
17622 }) {
17623 Ok(i) | Err(i) => i,
17624 };
17625 for range in &ranges[start_ix..] {
17626 if range
17627 .start
17628 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17629 .is_ge()
17630 {
17631 break;
17632 }
17633
17634 let start = range.start.to_display_point(display_snapshot);
17635 let end = range.end.to_display_point(display_snapshot);
17636 results.push((start..end, color))
17637 }
17638 }
17639 results
17640 }
17641
17642 /// Get the text ranges corresponding to the redaction query
17643 pub fn redacted_ranges(
17644 &self,
17645 search_range: Range<Anchor>,
17646 display_snapshot: &DisplaySnapshot,
17647 cx: &App,
17648 ) -> Vec<Range<DisplayPoint>> {
17649 display_snapshot
17650 .buffer_snapshot
17651 .redacted_ranges(search_range, |file| {
17652 if let Some(file) = file {
17653 file.is_private()
17654 && EditorSettings::get(
17655 Some(SettingsLocation {
17656 worktree_id: file.worktree_id(cx),
17657 path: file.path().as_ref(),
17658 }),
17659 cx,
17660 )
17661 .redact_private_values
17662 } else {
17663 false
17664 }
17665 })
17666 .map(|range| {
17667 range.start.to_display_point(display_snapshot)
17668 ..range.end.to_display_point(display_snapshot)
17669 })
17670 .collect()
17671 }
17672
17673 pub fn highlight_text<T: 'static>(
17674 &mut self,
17675 ranges: Vec<Range<Anchor>>,
17676 style: HighlightStyle,
17677 cx: &mut Context<Self>,
17678 ) {
17679 self.display_map.update(cx, |map, _| {
17680 map.highlight_text(TypeId::of::<T>(), ranges, style)
17681 });
17682 cx.notify();
17683 }
17684
17685 pub(crate) fn highlight_inlays<T: 'static>(
17686 &mut self,
17687 highlights: Vec<InlayHighlight>,
17688 style: HighlightStyle,
17689 cx: &mut Context<Self>,
17690 ) {
17691 self.display_map.update(cx, |map, _| {
17692 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
17693 });
17694 cx.notify();
17695 }
17696
17697 pub fn text_highlights<'a, T: 'static>(
17698 &'a self,
17699 cx: &'a App,
17700 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
17701 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
17702 }
17703
17704 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
17705 let cleared = self
17706 .display_map
17707 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
17708 if cleared {
17709 cx.notify();
17710 }
17711 }
17712
17713 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
17714 (self.read_only(cx) || self.blink_manager.read(cx).visible())
17715 && self.focus_handle.is_focused(window)
17716 }
17717
17718 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
17719 self.show_cursor_when_unfocused = is_enabled;
17720 cx.notify();
17721 }
17722
17723 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
17724 cx.notify();
17725 }
17726
17727 fn on_debug_session_event(
17728 &mut self,
17729 _session: Entity<Session>,
17730 event: &SessionEvent,
17731 cx: &mut Context<Self>,
17732 ) {
17733 match event {
17734 SessionEvent::InvalidateInlineValue => {
17735 self.refresh_inline_values(cx);
17736 }
17737 _ => {}
17738 }
17739 }
17740
17741 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
17742 let Some(project) = self.project.clone() else {
17743 return;
17744 };
17745 let Some(buffer) = self.buffer.read(cx).as_singleton() else {
17746 return;
17747 };
17748 if !self.inline_value_cache.enabled {
17749 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
17750 self.splice_inlays(&inlays, Vec::new(), cx);
17751 return;
17752 }
17753
17754 let current_execution_position = self
17755 .highlighted_rows
17756 .get(&TypeId::of::<ActiveDebugLine>())
17757 .and_then(|lines| lines.last().map(|line| line.range.start));
17758
17759 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
17760 let snapshot = editor
17761 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17762 .ok()?;
17763
17764 let inline_values = editor
17765 .update(cx, |_, cx| {
17766 let Some(current_execution_position) = current_execution_position else {
17767 return Some(Task::ready(Ok(Vec::new())));
17768 };
17769
17770 // todo(debugger) when introducing multi buffer inline values check execution position's buffer id to make sure the text
17771 // anchor is in the same buffer
17772 let range =
17773 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
17774 project.inline_values(buffer, range, cx)
17775 })
17776 .ok()
17777 .flatten()?
17778 .await
17779 .context("refreshing debugger inlays")
17780 .log_err()?;
17781
17782 let (excerpt_id, buffer_id) = snapshot
17783 .excerpts()
17784 .next()
17785 .map(|excerpt| (excerpt.0, excerpt.1.remote_id()))?;
17786 editor
17787 .update(cx, |editor, cx| {
17788 let new_inlays = inline_values
17789 .into_iter()
17790 .map(|debugger_value| {
17791 Inlay::debugger_hint(
17792 post_inc(&mut editor.next_inlay_id),
17793 Anchor::in_buffer(excerpt_id, buffer_id, debugger_value.position),
17794 debugger_value.text(),
17795 )
17796 })
17797 .collect::<Vec<_>>();
17798 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
17799 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
17800
17801 editor.splice_inlays(&inlay_ids, new_inlays, cx);
17802 })
17803 .ok()?;
17804 Some(())
17805 });
17806 }
17807
17808 fn on_buffer_event(
17809 &mut self,
17810 multibuffer: &Entity<MultiBuffer>,
17811 event: &multi_buffer::Event,
17812 window: &mut Window,
17813 cx: &mut Context<Self>,
17814 ) {
17815 match event {
17816 multi_buffer::Event::Edited {
17817 singleton_buffer_edited,
17818 edited_buffer: buffer_edited,
17819 } => {
17820 self.scrollbar_marker_state.dirty = true;
17821 self.active_indent_guides_state.dirty = true;
17822 self.refresh_active_diagnostics(cx);
17823 self.refresh_code_actions(window, cx);
17824 self.refresh_selected_text_highlights(true, window, cx);
17825 refresh_matching_bracket_highlights(self, window, cx);
17826 if self.has_active_inline_completion() {
17827 self.update_visible_inline_completion(window, cx);
17828 }
17829 if let Some(buffer) = buffer_edited {
17830 let buffer_id = buffer.read(cx).remote_id();
17831 if !self.registered_buffers.contains_key(&buffer_id) {
17832 if let Some(project) = self.project.as_ref() {
17833 project.update(cx, |project, cx| {
17834 self.registered_buffers.insert(
17835 buffer_id,
17836 project.register_buffer_with_language_servers(&buffer, cx),
17837 );
17838 })
17839 }
17840 }
17841 }
17842 cx.emit(EditorEvent::BufferEdited);
17843 cx.emit(SearchEvent::MatchesInvalidated);
17844 if *singleton_buffer_edited {
17845 if let Some(project) = &self.project {
17846 #[allow(clippy::mutable_key_type)]
17847 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
17848 multibuffer
17849 .all_buffers()
17850 .into_iter()
17851 .filter_map(|buffer| {
17852 buffer.update(cx, |buffer, cx| {
17853 let language = buffer.language()?;
17854 let should_discard = project.update(cx, |project, cx| {
17855 project.is_local()
17856 && !project.has_language_servers_for(buffer, cx)
17857 });
17858 should_discard.not().then_some(language.clone())
17859 })
17860 })
17861 .collect::<HashSet<_>>()
17862 });
17863 if !languages_affected.is_empty() {
17864 self.refresh_inlay_hints(
17865 InlayHintRefreshReason::BufferEdited(languages_affected),
17866 cx,
17867 );
17868 }
17869 }
17870 }
17871
17872 let Some(project) = &self.project else { return };
17873 let (telemetry, is_via_ssh) = {
17874 let project = project.read(cx);
17875 let telemetry = project.client().telemetry().clone();
17876 let is_via_ssh = project.is_via_ssh();
17877 (telemetry, is_via_ssh)
17878 };
17879 refresh_linked_ranges(self, window, cx);
17880 telemetry.log_edit_event("editor", is_via_ssh);
17881 }
17882 multi_buffer::Event::ExcerptsAdded {
17883 buffer,
17884 predecessor,
17885 excerpts,
17886 } => {
17887 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17888 let buffer_id = buffer.read(cx).remote_id();
17889 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
17890 if let Some(project) = &self.project {
17891 update_uncommitted_diff_for_buffer(
17892 cx.entity(),
17893 project,
17894 [buffer.clone()],
17895 self.buffer.clone(),
17896 cx,
17897 )
17898 .detach();
17899 }
17900 }
17901 cx.emit(EditorEvent::ExcerptsAdded {
17902 buffer: buffer.clone(),
17903 predecessor: *predecessor,
17904 excerpts: excerpts.clone(),
17905 });
17906 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17907 }
17908 multi_buffer::Event::ExcerptsRemoved {
17909 ids,
17910 removed_buffer_ids,
17911 } => {
17912 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
17913 let buffer = self.buffer.read(cx);
17914 self.registered_buffers
17915 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
17916 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17917 cx.emit(EditorEvent::ExcerptsRemoved {
17918 ids: ids.clone(),
17919 removed_buffer_ids: removed_buffer_ids.clone(),
17920 })
17921 }
17922 multi_buffer::Event::ExcerptsEdited {
17923 excerpt_ids,
17924 buffer_ids,
17925 } => {
17926 self.display_map.update(cx, |map, cx| {
17927 map.unfold_buffers(buffer_ids.iter().copied(), cx)
17928 });
17929 cx.emit(EditorEvent::ExcerptsEdited {
17930 ids: excerpt_ids.clone(),
17931 })
17932 }
17933 multi_buffer::Event::ExcerptsExpanded { ids } => {
17934 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17935 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
17936 }
17937 multi_buffer::Event::Reparsed(buffer_id) => {
17938 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17939 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17940
17941 cx.emit(EditorEvent::Reparsed(*buffer_id));
17942 }
17943 multi_buffer::Event::DiffHunksToggled => {
17944 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17945 }
17946 multi_buffer::Event::LanguageChanged(buffer_id) => {
17947 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
17948 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17949 cx.emit(EditorEvent::Reparsed(*buffer_id));
17950 cx.notify();
17951 }
17952 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
17953 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
17954 multi_buffer::Event::FileHandleChanged
17955 | multi_buffer::Event::Reloaded
17956 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
17957 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
17958 multi_buffer::Event::DiagnosticsUpdated => {
17959 self.refresh_active_diagnostics(cx);
17960 self.refresh_inline_diagnostics(true, window, cx);
17961 self.scrollbar_marker_state.dirty = true;
17962 cx.notify();
17963 }
17964 _ => {}
17965 };
17966 }
17967
17968 pub fn start_temporary_diff_override(&mut self) {
17969 self.load_diff_task.take();
17970 self.temporary_diff_override = true;
17971 }
17972
17973 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
17974 self.temporary_diff_override = false;
17975 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
17976 self.buffer.update(cx, |buffer, cx| {
17977 buffer.set_all_diff_hunks_collapsed(cx);
17978 });
17979
17980 if let Some(project) = self.project.clone() {
17981 self.load_diff_task = Some(
17982 update_uncommitted_diff_for_buffer(
17983 cx.entity(),
17984 &project,
17985 self.buffer.read(cx).all_buffers(),
17986 self.buffer.clone(),
17987 cx,
17988 )
17989 .shared(),
17990 );
17991 }
17992 }
17993
17994 fn on_display_map_changed(
17995 &mut self,
17996 _: Entity<DisplayMap>,
17997 _: &mut Window,
17998 cx: &mut Context<Self>,
17999 ) {
18000 cx.notify();
18001 }
18002
18003 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18004 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18005 self.update_edit_prediction_settings(cx);
18006 self.refresh_inline_completion(true, false, window, cx);
18007 self.refresh_inlay_hints(
18008 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18009 self.selections.newest_anchor().head(),
18010 &self.buffer.read(cx).snapshot(cx),
18011 cx,
18012 )),
18013 cx,
18014 );
18015
18016 let old_cursor_shape = self.cursor_shape;
18017
18018 {
18019 let editor_settings = EditorSettings::get_global(cx);
18020 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18021 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18022 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18023 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18024 }
18025
18026 if old_cursor_shape != self.cursor_shape {
18027 cx.emit(EditorEvent::CursorShapeChanged);
18028 }
18029
18030 let project_settings = ProjectSettings::get_global(cx);
18031 self.serialize_dirty_buffers =
18032 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18033
18034 if self.mode.is_full() {
18035 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18036 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18037 if self.show_inline_diagnostics != show_inline_diagnostics {
18038 self.show_inline_diagnostics = show_inline_diagnostics;
18039 self.refresh_inline_diagnostics(false, window, cx);
18040 }
18041
18042 if self.git_blame_inline_enabled != inline_blame_enabled {
18043 self.toggle_git_blame_inline_internal(false, window, cx);
18044 }
18045
18046 let minimap_settings = EditorSettings::get_global(cx).minimap;
18047 if self.minimap.as_ref().is_some() != minimap_settings.minimap_enabled() {
18048 self.minimap = self.create_minimap(minimap_settings, window, cx);
18049 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18050 minimap_entity.update(cx, |minimap_editor, cx| {
18051 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18052 })
18053 }
18054 }
18055
18056 cx.notify();
18057 }
18058
18059 pub fn set_searchable(&mut self, searchable: bool) {
18060 self.searchable = searchable;
18061 }
18062
18063 pub fn searchable(&self) -> bool {
18064 self.searchable
18065 }
18066
18067 fn open_proposed_changes_editor(
18068 &mut self,
18069 _: &OpenProposedChangesEditor,
18070 window: &mut Window,
18071 cx: &mut Context<Self>,
18072 ) {
18073 let Some(workspace) = self.workspace() else {
18074 cx.propagate();
18075 return;
18076 };
18077
18078 let selections = self.selections.all::<usize>(cx);
18079 let multi_buffer = self.buffer.read(cx);
18080 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18081 let mut new_selections_by_buffer = HashMap::default();
18082 for selection in selections {
18083 for (buffer, range, _) in
18084 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18085 {
18086 let mut range = range.to_point(buffer);
18087 range.start.column = 0;
18088 range.end.column = buffer.line_len(range.end.row);
18089 new_selections_by_buffer
18090 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18091 .or_insert(Vec::new())
18092 .push(range)
18093 }
18094 }
18095
18096 let proposed_changes_buffers = new_selections_by_buffer
18097 .into_iter()
18098 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18099 .collect::<Vec<_>>();
18100 let proposed_changes_editor = cx.new(|cx| {
18101 ProposedChangesEditor::new(
18102 "Proposed changes",
18103 proposed_changes_buffers,
18104 self.project.clone(),
18105 window,
18106 cx,
18107 )
18108 });
18109
18110 window.defer(cx, move |window, cx| {
18111 workspace.update(cx, |workspace, cx| {
18112 workspace.active_pane().update(cx, |pane, cx| {
18113 pane.add_item(
18114 Box::new(proposed_changes_editor),
18115 true,
18116 true,
18117 None,
18118 window,
18119 cx,
18120 );
18121 });
18122 });
18123 });
18124 }
18125
18126 pub fn open_excerpts_in_split(
18127 &mut self,
18128 _: &OpenExcerptsSplit,
18129 window: &mut Window,
18130 cx: &mut Context<Self>,
18131 ) {
18132 self.open_excerpts_common(None, true, window, cx)
18133 }
18134
18135 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18136 self.open_excerpts_common(None, false, window, cx)
18137 }
18138
18139 fn open_excerpts_common(
18140 &mut self,
18141 jump_data: Option<JumpData>,
18142 split: bool,
18143 window: &mut Window,
18144 cx: &mut Context<Self>,
18145 ) {
18146 let Some(workspace) = self.workspace() else {
18147 cx.propagate();
18148 return;
18149 };
18150
18151 if self.buffer.read(cx).is_singleton() {
18152 cx.propagate();
18153 return;
18154 }
18155
18156 let mut new_selections_by_buffer = HashMap::default();
18157 match &jump_data {
18158 Some(JumpData::MultiBufferPoint {
18159 excerpt_id,
18160 position,
18161 anchor,
18162 line_offset_from_top,
18163 }) => {
18164 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18165 if let Some(buffer) = multi_buffer_snapshot
18166 .buffer_id_for_excerpt(*excerpt_id)
18167 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18168 {
18169 let buffer_snapshot = buffer.read(cx).snapshot();
18170 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18171 language::ToPoint::to_point(anchor, &buffer_snapshot)
18172 } else {
18173 buffer_snapshot.clip_point(*position, Bias::Left)
18174 };
18175 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18176 new_selections_by_buffer.insert(
18177 buffer,
18178 (
18179 vec![jump_to_offset..jump_to_offset],
18180 Some(*line_offset_from_top),
18181 ),
18182 );
18183 }
18184 }
18185 Some(JumpData::MultiBufferRow {
18186 row,
18187 line_offset_from_top,
18188 }) => {
18189 let point = MultiBufferPoint::new(row.0, 0);
18190 if let Some((buffer, buffer_point, _)) =
18191 self.buffer.read(cx).point_to_buffer_point(point, cx)
18192 {
18193 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18194 new_selections_by_buffer
18195 .entry(buffer)
18196 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18197 .0
18198 .push(buffer_offset..buffer_offset)
18199 }
18200 }
18201 None => {
18202 let selections = self.selections.all::<usize>(cx);
18203 let multi_buffer = self.buffer.read(cx);
18204 for selection in selections {
18205 for (snapshot, range, _, anchor) in multi_buffer
18206 .snapshot(cx)
18207 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18208 {
18209 if let Some(anchor) = anchor {
18210 // selection is in a deleted hunk
18211 let Some(buffer_id) = anchor.buffer_id else {
18212 continue;
18213 };
18214 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18215 continue;
18216 };
18217 let offset = text::ToOffset::to_offset(
18218 &anchor.text_anchor,
18219 &buffer_handle.read(cx).snapshot(),
18220 );
18221 let range = offset..offset;
18222 new_selections_by_buffer
18223 .entry(buffer_handle)
18224 .or_insert((Vec::new(), None))
18225 .0
18226 .push(range)
18227 } else {
18228 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18229 else {
18230 continue;
18231 };
18232 new_selections_by_buffer
18233 .entry(buffer_handle)
18234 .or_insert((Vec::new(), None))
18235 .0
18236 .push(range)
18237 }
18238 }
18239 }
18240 }
18241 }
18242
18243 new_selections_by_buffer
18244 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18245
18246 if new_selections_by_buffer.is_empty() {
18247 return;
18248 }
18249
18250 // We defer the pane interaction because we ourselves are a workspace item
18251 // and activating a new item causes the pane to call a method on us reentrantly,
18252 // which panics if we're on the stack.
18253 window.defer(cx, move |window, cx| {
18254 workspace.update(cx, |workspace, cx| {
18255 let pane = if split {
18256 workspace.adjacent_pane(window, cx)
18257 } else {
18258 workspace.active_pane().clone()
18259 };
18260
18261 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18262 let editor = buffer
18263 .read(cx)
18264 .file()
18265 .is_none()
18266 .then(|| {
18267 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18268 // so `workspace.open_project_item` will never find them, always opening a new editor.
18269 // Instead, we try to activate the existing editor in the pane first.
18270 let (editor, pane_item_index) =
18271 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18272 let editor = item.downcast::<Editor>()?;
18273 let singleton_buffer =
18274 editor.read(cx).buffer().read(cx).as_singleton()?;
18275 if singleton_buffer == buffer {
18276 Some((editor, i))
18277 } else {
18278 None
18279 }
18280 })?;
18281 pane.update(cx, |pane, cx| {
18282 pane.activate_item(pane_item_index, true, true, window, cx)
18283 });
18284 Some(editor)
18285 })
18286 .flatten()
18287 .unwrap_or_else(|| {
18288 workspace.open_project_item::<Self>(
18289 pane.clone(),
18290 buffer,
18291 true,
18292 true,
18293 window,
18294 cx,
18295 )
18296 });
18297
18298 editor.update(cx, |editor, cx| {
18299 let autoscroll = match scroll_offset {
18300 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18301 None => Autoscroll::newest(),
18302 };
18303 let nav_history = editor.nav_history.take();
18304 editor.change_selections(Some(autoscroll), window, cx, |s| {
18305 s.select_ranges(ranges);
18306 });
18307 editor.nav_history = nav_history;
18308 });
18309 }
18310 })
18311 });
18312 }
18313
18314 // For now, don't allow opening excerpts in buffers that aren't backed by
18315 // regular project files.
18316 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18317 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18318 }
18319
18320 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18321 let snapshot = self.buffer.read(cx).read(cx);
18322 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18323 Some(
18324 ranges
18325 .iter()
18326 .map(move |range| {
18327 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18328 })
18329 .collect(),
18330 )
18331 }
18332
18333 fn selection_replacement_ranges(
18334 &self,
18335 range: Range<OffsetUtf16>,
18336 cx: &mut App,
18337 ) -> Vec<Range<OffsetUtf16>> {
18338 let selections = self.selections.all::<OffsetUtf16>(cx);
18339 let newest_selection = selections
18340 .iter()
18341 .max_by_key(|selection| selection.id)
18342 .unwrap();
18343 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
18344 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
18345 let snapshot = self.buffer.read(cx).read(cx);
18346 selections
18347 .into_iter()
18348 .map(|mut selection| {
18349 selection.start.0 =
18350 (selection.start.0 as isize).saturating_add(start_delta) as usize;
18351 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
18352 snapshot.clip_offset_utf16(selection.start, Bias::Left)
18353 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
18354 })
18355 .collect()
18356 }
18357
18358 fn report_editor_event(
18359 &self,
18360 event_type: &'static str,
18361 file_extension: Option<String>,
18362 cx: &App,
18363 ) {
18364 if cfg!(any(test, feature = "test-support")) {
18365 return;
18366 }
18367
18368 let Some(project) = &self.project else { return };
18369
18370 // If None, we are in a file without an extension
18371 let file = self
18372 .buffer
18373 .read(cx)
18374 .as_singleton()
18375 .and_then(|b| b.read(cx).file());
18376 let file_extension = file_extension.or(file
18377 .as_ref()
18378 .and_then(|file| Path::new(file.file_name(cx)).extension())
18379 .and_then(|e| e.to_str())
18380 .map(|a| a.to_string()));
18381
18382 let vim_mode = vim_enabled(cx);
18383
18384 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
18385 let copilot_enabled = edit_predictions_provider
18386 == language::language_settings::EditPredictionProvider::Copilot;
18387 let copilot_enabled_for_language = self
18388 .buffer
18389 .read(cx)
18390 .language_settings(cx)
18391 .show_edit_predictions;
18392
18393 let project = project.read(cx);
18394 telemetry::event!(
18395 event_type,
18396 file_extension,
18397 vim_mode,
18398 copilot_enabled,
18399 copilot_enabled_for_language,
18400 edit_predictions_provider,
18401 is_via_ssh = project.is_via_ssh(),
18402 );
18403 }
18404
18405 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
18406 /// with each line being an array of {text, highlight} objects.
18407 fn copy_highlight_json(
18408 &mut self,
18409 _: &CopyHighlightJson,
18410 window: &mut Window,
18411 cx: &mut Context<Self>,
18412 ) {
18413 #[derive(Serialize)]
18414 struct Chunk<'a> {
18415 text: String,
18416 highlight: Option<&'a str>,
18417 }
18418
18419 let snapshot = self.buffer.read(cx).snapshot(cx);
18420 let range = self
18421 .selected_text_range(false, window, cx)
18422 .and_then(|selection| {
18423 if selection.range.is_empty() {
18424 None
18425 } else {
18426 Some(selection.range)
18427 }
18428 })
18429 .unwrap_or_else(|| 0..snapshot.len());
18430
18431 let chunks = snapshot.chunks(range, true);
18432 let mut lines = Vec::new();
18433 let mut line: VecDeque<Chunk> = VecDeque::new();
18434
18435 let Some(style) = self.style.as_ref() else {
18436 return;
18437 };
18438
18439 for chunk in chunks {
18440 let highlight = chunk
18441 .syntax_highlight_id
18442 .and_then(|id| id.name(&style.syntax));
18443 let mut chunk_lines = chunk.text.split('\n').peekable();
18444 while let Some(text) = chunk_lines.next() {
18445 let mut merged_with_last_token = false;
18446 if let Some(last_token) = line.back_mut() {
18447 if last_token.highlight == highlight {
18448 last_token.text.push_str(text);
18449 merged_with_last_token = true;
18450 }
18451 }
18452
18453 if !merged_with_last_token {
18454 line.push_back(Chunk {
18455 text: text.into(),
18456 highlight,
18457 });
18458 }
18459
18460 if chunk_lines.peek().is_some() {
18461 if line.len() > 1 && line.front().unwrap().text.is_empty() {
18462 line.pop_front();
18463 }
18464 if line.len() > 1 && line.back().unwrap().text.is_empty() {
18465 line.pop_back();
18466 }
18467
18468 lines.push(mem::take(&mut line));
18469 }
18470 }
18471 }
18472
18473 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
18474 return;
18475 };
18476 cx.write_to_clipboard(ClipboardItem::new_string(lines));
18477 }
18478
18479 pub fn open_context_menu(
18480 &mut self,
18481 _: &OpenContextMenu,
18482 window: &mut Window,
18483 cx: &mut Context<Self>,
18484 ) {
18485 self.request_autoscroll(Autoscroll::newest(), cx);
18486 let position = self.selections.newest_display(cx).start;
18487 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
18488 }
18489
18490 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
18491 &self.inlay_hint_cache
18492 }
18493
18494 pub fn replay_insert_event(
18495 &mut self,
18496 text: &str,
18497 relative_utf16_range: Option<Range<isize>>,
18498 window: &mut Window,
18499 cx: &mut Context<Self>,
18500 ) {
18501 if !self.input_enabled {
18502 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18503 return;
18504 }
18505 if let Some(relative_utf16_range) = relative_utf16_range {
18506 let selections = self.selections.all::<OffsetUtf16>(cx);
18507 self.change_selections(None, window, cx, |s| {
18508 let new_ranges = selections.into_iter().map(|range| {
18509 let start = OffsetUtf16(
18510 range
18511 .head()
18512 .0
18513 .saturating_add_signed(relative_utf16_range.start),
18514 );
18515 let end = OffsetUtf16(
18516 range
18517 .head()
18518 .0
18519 .saturating_add_signed(relative_utf16_range.end),
18520 );
18521 start..end
18522 });
18523 s.select_ranges(new_ranges);
18524 });
18525 }
18526
18527 self.handle_input(text, window, cx);
18528 }
18529
18530 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
18531 let Some(provider) = self.semantics_provider.as_ref() else {
18532 return false;
18533 };
18534
18535 let mut supports = false;
18536 self.buffer().update(cx, |this, cx| {
18537 this.for_each_buffer(|buffer| {
18538 supports |= provider.supports_inlay_hints(buffer, cx);
18539 });
18540 });
18541
18542 supports
18543 }
18544
18545 pub fn is_focused(&self, window: &Window) -> bool {
18546 self.focus_handle.is_focused(window)
18547 }
18548
18549 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18550 cx.emit(EditorEvent::Focused);
18551
18552 if let Some(descendant) = self
18553 .last_focused_descendant
18554 .take()
18555 .and_then(|descendant| descendant.upgrade())
18556 {
18557 window.focus(&descendant);
18558 } else {
18559 if let Some(blame) = self.blame.as_ref() {
18560 blame.update(cx, GitBlame::focus)
18561 }
18562
18563 self.blink_manager.update(cx, BlinkManager::enable);
18564 self.show_cursor_names(window, cx);
18565 self.buffer.update(cx, |buffer, cx| {
18566 buffer.finalize_last_transaction(cx);
18567 if self.leader_id.is_none() {
18568 buffer.set_active_selections(
18569 &self.selections.disjoint_anchors(),
18570 self.selections.line_mode,
18571 self.cursor_shape,
18572 cx,
18573 );
18574 }
18575 });
18576 }
18577 }
18578
18579 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18580 cx.emit(EditorEvent::FocusedIn)
18581 }
18582
18583 fn handle_focus_out(
18584 &mut self,
18585 event: FocusOutEvent,
18586 _window: &mut Window,
18587 cx: &mut Context<Self>,
18588 ) {
18589 if event.blurred != self.focus_handle {
18590 self.last_focused_descendant = Some(event.blurred);
18591 }
18592 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
18593 }
18594
18595 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18596 self.blink_manager.update(cx, BlinkManager::disable);
18597 self.buffer
18598 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
18599
18600 if let Some(blame) = self.blame.as_ref() {
18601 blame.update(cx, GitBlame::blur)
18602 }
18603 if !self.hover_state.focused(window, cx) {
18604 hide_hover(self, cx);
18605 }
18606 if !self
18607 .context_menu
18608 .borrow()
18609 .as_ref()
18610 .is_some_and(|context_menu| context_menu.focused(window, cx))
18611 {
18612 self.hide_context_menu(window, cx);
18613 }
18614 self.discard_inline_completion(false, cx);
18615 cx.emit(EditorEvent::Blurred);
18616 cx.notify();
18617 }
18618
18619 pub fn register_action<A: Action>(
18620 &mut self,
18621 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
18622 ) -> Subscription {
18623 let id = self.next_editor_action_id.post_inc();
18624 let listener = Arc::new(listener);
18625 self.editor_actions.borrow_mut().insert(
18626 id,
18627 Box::new(move |window, _| {
18628 let listener = listener.clone();
18629 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
18630 let action = action.downcast_ref().unwrap();
18631 if phase == DispatchPhase::Bubble {
18632 listener(action, window, cx)
18633 }
18634 })
18635 }),
18636 );
18637
18638 let editor_actions = self.editor_actions.clone();
18639 Subscription::new(move || {
18640 editor_actions.borrow_mut().remove(&id);
18641 })
18642 }
18643
18644 pub fn file_header_size(&self) -> u32 {
18645 FILE_HEADER_HEIGHT
18646 }
18647
18648 pub fn restore(
18649 &mut self,
18650 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
18651 window: &mut Window,
18652 cx: &mut Context<Self>,
18653 ) {
18654 let workspace = self.workspace();
18655 let project = self.project.as_ref();
18656 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
18657 let mut tasks = Vec::new();
18658 for (buffer_id, changes) in revert_changes {
18659 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
18660 buffer.update(cx, |buffer, cx| {
18661 buffer.edit(
18662 changes
18663 .into_iter()
18664 .map(|(range, text)| (range, text.to_string())),
18665 None,
18666 cx,
18667 );
18668 });
18669
18670 if let Some(project) =
18671 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
18672 {
18673 project.update(cx, |project, cx| {
18674 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
18675 })
18676 }
18677 }
18678 }
18679 tasks
18680 });
18681 cx.spawn_in(window, async move |_, cx| {
18682 for (buffer, task) in save_tasks {
18683 let result = task.await;
18684 if result.is_err() {
18685 let Some(path) = buffer
18686 .read_with(cx, |buffer, cx| buffer.project_path(cx))
18687 .ok()
18688 else {
18689 continue;
18690 };
18691 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
18692 let Some(task) = cx
18693 .update_window_entity(&workspace, |workspace, window, cx| {
18694 workspace
18695 .open_path_preview(path, None, false, false, false, window, cx)
18696 })
18697 .ok()
18698 else {
18699 continue;
18700 };
18701 task.await.log_err();
18702 }
18703 }
18704 }
18705 })
18706 .detach();
18707 self.change_selections(None, window, cx, |selections| selections.refresh());
18708 }
18709
18710 pub fn to_pixel_point(
18711 &self,
18712 source: multi_buffer::Anchor,
18713 editor_snapshot: &EditorSnapshot,
18714 window: &mut Window,
18715 ) -> Option<gpui::Point<Pixels>> {
18716 let source_point = source.to_display_point(editor_snapshot);
18717 self.display_to_pixel_point(source_point, editor_snapshot, window)
18718 }
18719
18720 pub fn display_to_pixel_point(
18721 &self,
18722 source: DisplayPoint,
18723 editor_snapshot: &EditorSnapshot,
18724 window: &mut Window,
18725 ) -> Option<gpui::Point<Pixels>> {
18726 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
18727 let text_layout_details = self.text_layout_details(window);
18728 let scroll_top = text_layout_details
18729 .scroll_anchor
18730 .scroll_position(editor_snapshot)
18731 .y;
18732
18733 if source.row().as_f32() < scroll_top.floor() {
18734 return None;
18735 }
18736 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
18737 let source_y = line_height * (source.row().as_f32() - scroll_top);
18738 Some(gpui::Point::new(source_x, source_y))
18739 }
18740
18741 pub fn has_visible_completions_menu(&self) -> bool {
18742 !self.edit_prediction_preview_is_active()
18743 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
18744 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
18745 })
18746 }
18747
18748 pub fn register_addon<T: Addon>(&mut self, instance: T) {
18749 if self.mode.is_minimap() {
18750 return;
18751 }
18752 self.addons
18753 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
18754 }
18755
18756 pub fn unregister_addon<T: Addon>(&mut self) {
18757 self.addons.remove(&std::any::TypeId::of::<T>());
18758 }
18759
18760 pub fn addon<T: Addon>(&self) -> Option<&T> {
18761 let type_id = std::any::TypeId::of::<T>();
18762 self.addons
18763 .get(&type_id)
18764 .and_then(|item| item.to_any().downcast_ref::<T>())
18765 }
18766
18767 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
18768 let type_id = std::any::TypeId::of::<T>();
18769 self.addons
18770 .get_mut(&type_id)
18771 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
18772 }
18773
18774 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
18775 let text_layout_details = self.text_layout_details(window);
18776 let style = &text_layout_details.editor_style;
18777 let font_id = window.text_system().resolve_font(&style.text.font());
18778 let font_size = style.text.font_size.to_pixels(window.rem_size());
18779 let line_height = style.text.line_height_in_pixels(window.rem_size());
18780 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
18781
18782 gpui::Size::new(em_width, line_height)
18783 }
18784
18785 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
18786 self.load_diff_task.clone()
18787 }
18788
18789 fn read_metadata_from_db(
18790 &mut self,
18791 item_id: u64,
18792 workspace_id: WorkspaceId,
18793 window: &mut Window,
18794 cx: &mut Context<Editor>,
18795 ) {
18796 if self.is_singleton(cx)
18797 && !self.mode.is_minimap()
18798 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
18799 {
18800 let buffer_snapshot = OnceCell::new();
18801
18802 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
18803 if !folds.is_empty() {
18804 let snapshot =
18805 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18806 self.fold_ranges(
18807 folds
18808 .into_iter()
18809 .map(|(start, end)| {
18810 snapshot.clip_offset(start, Bias::Left)
18811 ..snapshot.clip_offset(end, Bias::Right)
18812 })
18813 .collect(),
18814 false,
18815 window,
18816 cx,
18817 );
18818 }
18819 }
18820
18821 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
18822 if !selections.is_empty() {
18823 let snapshot =
18824 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18825 self.change_selections(None, window, cx, |s| {
18826 s.select_ranges(selections.into_iter().map(|(start, end)| {
18827 snapshot.clip_offset(start, Bias::Left)
18828 ..snapshot.clip_offset(end, Bias::Right)
18829 }));
18830 });
18831 }
18832 };
18833 }
18834
18835 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
18836 }
18837}
18838
18839fn vim_enabled(cx: &App) -> bool {
18840 cx.global::<SettingsStore>()
18841 .raw_user_settings()
18842 .get("vim_mode")
18843 == Some(&serde_json::Value::Bool(true))
18844}
18845
18846// Consider user intent and default settings
18847fn choose_completion_range(
18848 completion: &Completion,
18849 intent: CompletionIntent,
18850 buffer: &Entity<Buffer>,
18851 cx: &mut Context<Editor>,
18852) -> Range<usize> {
18853 fn should_replace(
18854 completion: &Completion,
18855 insert_range: &Range<text::Anchor>,
18856 intent: CompletionIntent,
18857 completion_mode_setting: LspInsertMode,
18858 buffer: &Buffer,
18859 ) -> bool {
18860 // specific actions take precedence over settings
18861 match intent {
18862 CompletionIntent::CompleteWithInsert => return false,
18863 CompletionIntent::CompleteWithReplace => return true,
18864 CompletionIntent::Complete | CompletionIntent::Compose => {}
18865 }
18866
18867 match completion_mode_setting {
18868 LspInsertMode::Insert => false,
18869 LspInsertMode::Replace => true,
18870 LspInsertMode::ReplaceSubsequence => {
18871 let mut text_to_replace = buffer.chars_for_range(
18872 buffer.anchor_before(completion.replace_range.start)
18873 ..buffer.anchor_after(completion.replace_range.end),
18874 );
18875 let mut completion_text = completion.new_text.chars();
18876
18877 // is `text_to_replace` a subsequence of `completion_text`
18878 text_to_replace
18879 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
18880 }
18881 LspInsertMode::ReplaceSuffix => {
18882 let range_after_cursor = insert_range.end..completion.replace_range.end;
18883
18884 let text_after_cursor = buffer
18885 .text_for_range(
18886 buffer.anchor_before(range_after_cursor.start)
18887 ..buffer.anchor_after(range_after_cursor.end),
18888 )
18889 .collect::<String>();
18890 completion.new_text.ends_with(&text_after_cursor)
18891 }
18892 }
18893 }
18894
18895 let buffer = buffer.read(cx);
18896
18897 if let CompletionSource::Lsp {
18898 insert_range: Some(insert_range),
18899 ..
18900 } = &completion.source
18901 {
18902 let completion_mode_setting =
18903 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
18904 .completions
18905 .lsp_insert_mode;
18906
18907 if !should_replace(
18908 completion,
18909 &insert_range,
18910 intent,
18911 completion_mode_setting,
18912 buffer,
18913 ) {
18914 return insert_range.to_offset(buffer);
18915 }
18916 }
18917
18918 completion.replace_range.to_offset(buffer)
18919}
18920
18921fn insert_extra_newline_brackets(
18922 buffer: &MultiBufferSnapshot,
18923 range: Range<usize>,
18924 language: &language::LanguageScope,
18925) -> bool {
18926 let leading_whitespace_len = buffer
18927 .reversed_chars_at(range.start)
18928 .take_while(|c| c.is_whitespace() && *c != '\n')
18929 .map(|c| c.len_utf8())
18930 .sum::<usize>();
18931 let trailing_whitespace_len = buffer
18932 .chars_at(range.end)
18933 .take_while(|c| c.is_whitespace() && *c != '\n')
18934 .map(|c| c.len_utf8())
18935 .sum::<usize>();
18936 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
18937
18938 language.brackets().any(|(pair, enabled)| {
18939 let pair_start = pair.start.trim_end();
18940 let pair_end = pair.end.trim_start();
18941
18942 enabled
18943 && pair.newline
18944 && buffer.contains_str_at(range.end, pair_end)
18945 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
18946 })
18947}
18948
18949fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
18950 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
18951 [(buffer, range, _)] => (*buffer, range.clone()),
18952 _ => return false,
18953 };
18954 let pair = {
18955 let mut result: Option<BracketMatch> = None;
18956
18957 for pair in buffer
18958 .all_bracket_ranges(range.clone())
18959 .filter(move |pair| {
18960 pair.open_range.start <= range.start && pair.close_range.end >= range.end
18961 })
18962 {
18963 let len = pair.close_range.end - pair.open_range.start;
18964
18965 if let Some(existing) = &result {
18966 let existing_len = existing.close_range.end - existing.open_range.start;
18967 if len > existing_len {
18968 continue;
18969 }
18970 }
18971
18972 result = Some(pair);
18973 }
18974
18975 result
18976 };
18977 let Some(pair) = pair else {
18978 return false;
18979 };
18980 pair.newline_only
18981 && buffer
18982 .chars_for_range(pair.open_range.end..range.start)
18983 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
18984 .all(|c| c.is_whitespace() && c != '\n')
18985}
18986
18987fn update_uncommitted_diff_for_buffer(
18988 editor: Entity<Editor>,
18989 project: &Entity<Project>,
18990 buffers: impl IntoIterator<Item = Entity<Buffer>>,
18991 buffer: Entity<MultiBuffer>,
18992 cx: &mut App,
18993) -> Task<()> {
18994 let mut tasks = Vec::new();
18995 project.update(cx, |project, cx| {
18996 for buffer in buffers {
18997 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
18998 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
18999 }
19000 }
19001 });
19002 cx.spawn(async move |cx| {
19003 let diffs = future::join_all(tasks).await;
19004 if editor
19005 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19006 .unwrap_or(false)
19007 {
19008 return;
19009 }
19010
19011 buffer
19012 .update(cx, |buffer, cx| {
19013 for diff in diffs.into_iter().flatten() {
19014 buffer.add_diff(diff, cx);
19015 }
19016 })
19017 .ok();
19018 })
19019}
19020
19021fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
19022 let tab_size = tab_size.get() as usize;
19023 let mut width = offset;
19024
19025 for ch in text.chars() {
19026 width += if ch == '\t' {
19027 tab_size - (width % tab_size)
19028 } else {
19029 1
19030 };
19031 }
19032
19033 width - offset
19034}
19035
19036#[cfg(test)]
19037mod tests {
19038 use super::*;
19039
19040 #[test]
19041 fn test_string_size_with_expanded_tabs() {
19042 let nz = |val| NonZeroU32::new(val).unwrap();
19043 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
19044 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
19045 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
19046 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
19047 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
19048 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
19049 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
19050 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
19051 }
19052}
19053
19054/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
19055struct WordBreakingTokenizer<'a> {
19056 input: &'a str,
19057}
19058
19059impl<'a> WordBreakingTokenizer<'a> {
19060 fn new(input: &'a str) -> Self {
19061 Self { input }
19062 }
19063}
19064
19065fn is_char_ideographic(ch: char) -> bool {
19066 use unicode_script::Script::*;
19067 use unicode_script::UnicodeScript;
19068 matches!(ch.script(), Han | Tangut | Yi)
19069}
19070
19071fn is_grapheme_ideographic(text: &str) -> bool {
19072 text.chars().any(is_char_ideographic)
19073}
19074
19075fn is_grapheme_whitespace(text: &str) -> bool {
19076 text.chars().any(|x| x.is_whitespace())
19077}
19078
19079fn should_stay_with_preceding_ideograph(text: &str) -> bool {
19080 text.chars().next().map_or(false, |ch| {
19081 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
19082 })
19083}
19084
19085#[derive(PartialEq, Eq, Debug, Clone, Copy)]
19086enum WordBreakToken<'a> {
19087 Word { token: &'a str, grapheme_len: usize },
19088 InlineWhitespace { token: &'a str, grapheme_len: usize },
19089 Newline,
19090}
19091
19092impl<'a> Iterator for WordBreakingTokenizer<'a> {
19093 /// Yields a span, the count of graphemes in the token, and whether it was
19094 /// whitespace. Note that it also breaks at word boundaries.
19095 type Item = WordBreakToken<'a>;
19096
19097 fn next(&mut self) -> Option<Self::Item> {
19098 use unicode_segmentation::UnicodeSegmentation;
19099 if self.input.is_empty() {
19100 return None;
19101 }
19102
19103 let mut iter = self.input.graphemes(true).peekable();
19104 let mut offset = 0;
19105 let mut grapheme_len = 0;
19106 if let Some(first_grapheme) = iter.next() {
19107 let is_newline = first_grapheme == "\n";
19108 let is_whitespace = is_grapheme_whitespace(first_grapheme);
19109 offset += first_grapheme.len();
19110 grapheme_len += 1;
19111 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
19112 if let Some(grapheme) = iter.peek().copied() {
19113 if should_stay_with_preceding_ideograph(grapheme) {
19114 offset += grapheme.len();
19115 grapheme_len += 1;
19116 }
19117 }
19118 } else {
19119 let mut words = self.input[offset..].split_word_bound_indices().peekable();
19120 let mut next_word_bound = words.peek().copied();
19121 if next_word_bound.map_or(false, |(i, _)| i == 0) {
19122 next_word_bound = words.next();
19123 }
19124 while let Some(grapheme) = iter.peek().copied() {
19125 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19126 break;
19127 };
19128 if is_grapheme_whitespace(grapheme) != is_whitespace
19129 || (grapheme == "\n") != is_newline
19130 {
19131 break;
19132 };
19133 offset += grapheme.len();
19134 grapheme_len += 1;
19135 iter.next();
19136 }
19137 }
19138 let token = &self.input[..offset];
19139 self.input = &self.input[offset..];
19140 if token == "\n" {
19141 Some(WordBreakToken::Newline)
19142 } else if is_whitespace {
19143 Some(WordBreakToken::InlineWhitespace {
19144 token,
19145 grapheme_len,
19146 })
19147 } else {
19148 Some(WordBreakToken::Word {
19149 token,
19150 grapheme_len,
19151 })
19152 }
19153 } else {
19154 None
19155 }
19156 }
19157}
19158
19159#[test]
19160fn test_word_breaking_tokenizer() {
19161 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
19162 ("", &[]),
19163 (" ", &[whitespace(" ", 2)]),
19164 ("Ʒ", &[word("Ʒ", 1)]),
19165 ("Ǽ", &[word("Ǽ", 1)]),
19166 ("⋑", &[word("⋑", 1)]),
19167 ("⋑⋑", &[word("⋑⋑", 2)]),
19168 (
19169 "原理,进而",
19170 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
19171 ),
19172 (
19173 "hello world",
19174 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
19175 ),
19176 (
19177 "hello, world",
19178 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
19179 ),
19180 (
19181 " hello world",
19182 &[
19183 whitespace(" ", 2),
19184 word("hello", 5),
19185 whitespace(" ", 1),
19186 word("world", 5),
19187 ],
19188 ),
19189 (
19190 "这是什么 \n 钢笔",
19191 &[
19192 word("这", 1),
19193 word("是", 1),
19194 word("什", 1),
19195 word("么", 1),
19196 whitespace(" ", 1),
19197 newline(),
19198 whitespace(" ", 1),
19199 word("钢", 1),
19200 word("笔", 1),
19201 ],
19202 ),
19203 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
19204 ];
19205
19206 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19207 WordBreakToken::Word {
19208 token,
19209 grapheme_len,
19210 }
19211 }
19212
19213 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19214 WordBreakToken::InlineWhitespace {
19215 token,
19216 grapheme_len,
19217 }
19218 }
19219
19220 fn newline() -> WordBreakToken<'static> {
19221 WordBreakToken::Newline
19222 }
19223
19224 for (input, result) in tests {
19225 assert_eq!(
19226 WordBreakingTokenizer::new(input)
19227 .collect::<Vec<_>>()
19228 .as_slice(),
19229 *result,
19230 );
19231 }
19232}
19233
19234fn wrap_with_prefix(
19235 line_prefix: String,
19236 unwrapped_text: String,
19237 wrap_column: usize,
19238 tab_size: NonZeroU32,
19239 preserve_existing_whitespace: bool,
19240) -> String {
19241 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
19242 let mut wrapped_text = String::new();
19243 let mut current_line = line_prefix.clone();
19244
19245 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
19246 let mut current_line_len = line_prefix_len;
19247 let mut in_whitespace = false;
19248 for token in tokenizer {
19249 let have_preceding_whitespace = in_whitespace;
19250 match token {
19251 WordBreakToken::Word {
19252 token,
19253 grapheme_len,
19254 } => {
19255 in_whitespace = false;
19256 if current_line_len + grapheme_len > wrap_column
19257 && current_line_len != line_prefix_len
19258 {
19259 wrapped_text.push_str(current_line.trim_end());
19260 wrapped_text.push('\n');
19261 current_line.truncate(line_prefix.len());
19262 current_line_len = line_prefix_len;
19263 }
19264 current_line.push_str(token);
19265 current_line_len += grapheme_len;
19266 }
19267 WordBreakToken::InlineWhitespace {
19268 mut token,
19269 mut grapheme_len,
19270 } => {
19271 in_whitespace = true;
19272 if have_preceding_whitespace && !preserve_existing_whitespace {
19273 continue;
19274 }
19275 if !preserve_existing_whitespace {
19276 token = " ";
19277 grapheme_len = 1;
19278 }
19279 if current_line_len + grapheme_len > wrap_column {
19280 wrapped_text.push_str(current_line.trim_end());
19281 wrapped_text.push('\n');
19282 current_line.truncate(line_prefix.len());
19283 current_line_len = line_prefix_len;
19284 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
19285 current_line.push_str(token);
19286 current_line_len += grapheme_len;
19287 }
19288 }
19289 WordBreakToken::Newline => {
19290 in_whitespace = true;
19291 if preserve_existing_whitespace {
19292 wrapped_text.push_str(current_line.trim_end());
19293 wrapped_text.push('\n');
19294 current_line.truncate(line_prefix.len());
19295 current_line_len = line_prefix_len;
19296 } else if have_preceding_whitespace {
19297 continue;
19298 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
19299 {
19300 wrapped_text.push_str(current_line.trim_end());
19301 wrapped_text.push('\n');
19302 current_line.truncate(line_prefix.len());
19303 current_line_len = line_prefix_len;
19304 } else if current_line_len != line_prefix_len {
19305 current_line.push(' ');
19306 current_line_len += 1;
19307 }
19308 }
19309 }
19310 }
19311
19312 if !current_line.is_empty() {
19313 wrapped_text.push_str(¤t_line);
19314 }
19315 wrapped_text
19316}
19317
19318#[test]
19319fn test_wrap_with_prefix() {
19320 assert_eq!(
19321 wrap_with_prefix(
19322 "# ".to_string(),
19323 "abcdefg".to_string(),
19324 4,
19325 NonZeroU32::new(4).unwrap(),
19326 false,
19327 ),
19328 "# abcdefg"
19329 );
19330 assert_eq!(
19331 wrap_with_prefix(
19332 "".to_string(),
19333 "\thello world".to_string(),
19334 8,
19335 NonZeroU32::new(4).unwrap(),
19336 false,
19337 ),
19338 "hello\nworld"
19339 );
19340 assert_eq!(
19341 wrap_with_prefix(
19342 "// ".to_string(),
19343 "xx \nyy zz aa bb cc".to_string(),
19344 12,
19345 NonZeroU32::new(4).unwrap(),
19346 false,
19347 ),
19348 "// xx yy zz\n// aa bb cc"
19349 );
19350 assert_eq!(
19351 wrap_with_prefix(
19352 String::new(),
19353 "这是什么 \n 钢笔".to_string(),
19354 3,
19355 NonZeroU32::new(4).unwrap(),
19356 false,
19357 ),
19358 "这是什\n么 钢\n笔"
19359 );
19360}
19361
19362pub trait CollaborationHub {
19363 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
19364 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
19365 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
19366}
19367
19368impl CollaborationHub for Entity<Project> {
19369 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
19370 self.read(cx).collaborators()
19371 }
19372
19373 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
19374 self.read(cx).user_store().read(cx).participant_indices()
19375 }
19376
19377 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
19378 let this = self.read(cx);
19379 let user_ids = this.collaborators().values().map(|c| c.user_id);
19380 this.user_store().read_with(cx, |user_store, cx| {
19381 user_store.participant_names(user_ids, cx)
19382 })
19383 }
19384}
19385
19386pub trait SemanticsProvider {
19387 fn hover(
19388 &self,
19389 buffer: &Entity<Buffer>,
19390 position: text::Anchor,
19391 cx: &mut App,
19392 ) -> Option<Task<Vec<project::Hover>>>;
19393
19394 fn inline_values(
19395 &self,
19396 buffer_handle: Entity<Buffer>,
19397 range: Range<text::Anchor>,
19398 cx: &mut App,
19399 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19400
19401 fn inlay_hints(
19402 &self,
19403 buffer_handle: Entity<Buffer>,
19404 range: Range<text::Anchor>,
19405 cx: &mut App,
19406 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19407
19408 fn resolve_inlay_hint(
19409 &self,
19410 hint: InlayHint,
19411 buffer_handle: Entity<Buffer>,
19412 server_id: LanguageServerId,
19413 cx: &mut App,
19414 ) -> Option<Task<anyhow::Result<InlayHint>>>;
19415
19416 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
19417
19418 fn document_highlights(
19419 &self,
19420 buffer: &Entity<Buffer>,
19421 position: text::Anchor,
19422 cx: &mut App,
19423 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
19424
19425 fn definitions(
19426 &self,
19427 buffer: &Entity<Buffer>,
19428 position: text::Anchor,
19429 kind: GotoDefinitionKind,
19430 cx: &mut App,
19431 ) -> Option<Task<Result<Vec<LocationLink>>>>;
19432
19433 fn range_for_rename(
19434 &self,
19435 buffer: &Entity<Buffer>,
19436 position: text::Anchor,
19437 cx: &mut App,
19438 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
19439
19440 fn perform_rename(
19441 &self,
19442 buffer: &Entity<Buffer>,
19443 position: text::Anchor,
19444 new_name: String,
19445 cx: &mut App,
19446 ) -> Option<Task<Result<ProjectTransaction>>>;
19447}
19448
19449pub trait CompletionProvider {
19450 fn completions(
19451 &self,
19452 excerpt_id: ExcerptId,
19453 buffer: &Entity<Buffer>,
19454 buffer_position: text::Anchor,
19455 trigger: CompletionContext,
19456 window: &mut Window,
19457 cx: &mut Context<Editor>,
19458 ) -> Task<Result<Option<Vec<Completion>>>>;
19459
19460 fn resolve_completions(
19461 &self,
19462 buffer: Entity<Buffer>,
19463 completion_indices: Vec<usize>,
19464 completions: Rc<RefCell<Box<[Completion]>>>,
19465 cx: &mut Context<Editor>,
19466 ) -> Task<Result<bool>>;
19467
19468 fn apply_additional_edits_for_completion(
19469 &self,
19470 _buffer: Entity<Buffer>,
19471 _completions: Rc<RefCell<Box<[Completion]>>>,
19472 _completion_index: usize,
19473 _push_to_history: bool,
19474 _cx: &mut Context<Editor>,
19475 ) -> Task<Result<Option<language::Transaction>>> {
19476 Task::ready(Ok(None))
19477 }
19478
19479 fn is_completion_trigger(
19480 &self,
19481 buffer: &Entity<Buffer>,
19482 position: language::Anchor,
19483 text: &str,
19484 trigger_in_words: bool,
19485 cx: &mut Context<Editor>,
19486 ) -> bool;
19487
19488 fn sort_completions(&self) -> bool {
19489 true
19490 }
19491
19492 fn filter_completions(&self) -> bool {
19493 true
19494 }
19495}
19496
19497pub trait CodeActionProvider {
19498 fn id(&self) -> Arc<str>;
19499
19500 fn code_actions(
19501 &self,
19502 buffer: &Entity<Buffer>,
19503 range: Range<text::Anchor>,
19504 window: &mut Window,
19505 cx: &mut App,
19506 ) -> Task<Result<Vec<CodeAction>>>;
19507
19508 fn apply_code_action(
19509 &self,
19510 buffer_handle: Entity<Buffer>,
19511 action: CodeAction,
19512 excerpt_id: ExcerptId,
19513 push_to_history: bool,
19514 window: &mut Window,
19515 cx: &mut App,
19516 ) -> Task<Result<ProjectTransaction>>;
19517}
19518
19519impl CodeActionProvider for Entity<Project> {
19520 fn id(&self) -> Arc<str> {
19521 "project".into()
19522 }
19523
19524 fn code_actions(
19525 &self,
19526 buffer: &Entity<Buffer>,
19527 range: Range<text::Anchor>,
19528 _window: &mut Window,
19529 cx: &mut App,
19530 ) -> Task<Result<Vec<CodeAction>>> {
19531 self.update(cx, |project, cx| {
19532 let code_lens = project.code_lens(buffer, range.clone(), cx);
19533 let code_actions = project.code_actions(buffer, range, None, cx);
19534 cx.background_spawn(async move {
19535 let (code_lens, code_actions) = join(code_lens, code_actions).await;
19536 Ok(code_lens
19537 .context("code lens fetch")?
19538 .into_iter()
19539 .chain(code_actions.context("code action fetch")?)
19540 .collect())
19541 })
19542 })
19543 }
19544
19545 fn apply_code_action(
19546 &self,
19547 buffer_handle: Entity<Buffer>,
19548 action: CodeAction,
19549 _excerpt_id: ExcerptId,
19550 push_to_history: bool,
19551 _window: &mut Window,
19552 cx: &mut App,
19553 ) -> Task<Result<ProjectTransaction>> {
19554 self.update(cx, |project, cx| {
19555 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19556 })
19557 }
19558}
19559
19560fn snippet_completions(
19561 project: &Project,
19562 buffer: &Entity<Buffer>,
19563 buffer_position: text::Anchor,
19564 cx: &mut App,
19565) -> Task<Result<Vec<Completion>>> {
19566 let languages = buffer.read(cx).languages_at(buffer_position);
19567 let snippet_store = project.snippets().read(cx);
19568
19569 let scopes: Vec<_> = languages
19570 .iter()
19571 .filter_map(|language| {
19572 let language_name = language.lsp_id();
19573 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19574
19575 if snippets.is_empty() {
19576 None
19577 } else {
19578 Some((language.default_scope(), snippets))
19579 }
19580 })
19581 .collect();
19582
19583 if scopes.is_empty() {
19584 return Task::ready(Ok(vec![]));
19585 }
19586
19587 let snapshot = buffer.read(cx).text_snapshot();
19588 let chars: String = snapshot
19589 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19590 .collect();
19591 let executor = cx.background_executor().clone();
19592
19593 cx.background_spawn(async move {
19594 let mut all_results: Vec<Completion> = Vec::new();
19595 for (scope, snippets) in scopes.into_iter() {
19596 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19597 let mut last_word = chars
19598 .chars()
19599 .take_while(|c| classifier.is_word(*c))
19600 .collect::<String>();
19601 last_word = last_word.chars().rev().collect();
19602
19603 if last_word.is_empty() {
19604 return Ok(vec![]);
19605 }
19606
19607 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
19608 let to_lsp = |point: &text::Anchor| {
19609 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
19610 point_to_lsp(end)
19611 };
19612 let lsp_end = to_lsp(&buffer_position);
19613
19614 let candidates = snippets
19615 .iter()
19616 .enumerate()
19617 .flat_map(|(ix, snippet)| {
19618 snippet
19619 .prefix
19620 .iter()
19621 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
19622 })
19623 .collect::<Vec<StringMatchCandidate>>();
19624
19625 let mut matches = fuzzy::match_strings(
19626 &candidates,
19627 &last_word,
19628 last_word.chars().any(|c| c.is_uppercase()),
19629 100,
19630 &Default::default(),
19631 executor.clone(),
19632 )
19633 .await;
19634
19635 // Remove all candidates where the query's start does not match the start of any word in the candidate
19636 if let Some(query_start) = last_word.chars().next() {
19637 matches.retain(|string_match| {
19638 split_words(&string_match.string).any(|word| {
19639 // Check that the first codepoint of the word as lowercase matches the first
19640 // codepoint of the query as lowercase
19641 word.chars()
19642 .flat_map(|codepoint| codepoint.to_lowercase())
19643 .zip(query_start.to_lowercase())
19644 .all(|(word_cp, query_cp)| word_cp == query_cp)
19645 })
19646 });
19647 }
19648
19649 let matched_strings = matches
19650 .into_iter()
19651 .map(|m| m.string)
19652 .collect::<HashSet<_>>();
19653
19654 let mut result: Vec<Completion> = snippets
19655 .iter()
19656 .filter_map(|snippet| {
19657 let matching_prefix = snippet
19658 .prefix
19659 .iter()
19660 .find(|prefix| matched_strings.contains(*prefix))?;
19661 let start = as_offset - last_word.len();
19662 let start = snapshot.anchor_before(start);
19663 let range = start..buffer_position;
19664 let lsp_start = to_lsp(&start);
19665 let lsp_range = lsp::Range {
19666 start: lsp_start,
19667 end: lsp_end,
19668 };
19669 Some(Completion {
19670 replace_range: range,
19671 new_text: snippet.body.clone(),
19672 source: CompletionSource::Lsp {
19673 insert_range: None,
19674 server_id: LanguageServerId(usize::MAX),
19675 resolved: true,
19676 lsp_completion: Box::new(lsp::CompletionItem {
19677 label: snippet.prefix.first().unwrap().clone(),
19678 kind: Some(CompletionItemKind::SNIPPET),
19679 label_details: snippet.description.as_ref().map(|description| {
19680 lsp::CompletionItemLabelDetails {
19681 detail: Some(description.clone()),
19682 description: None,
19683 }
19684 }),
19685 insert_text_format: Some(InsertTextFormat::SNIPPET),
19686 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19687 lsp::InsertReplaceEdit {
19688 new_text: snippet.body.clone(),
19689 insert: lsp_range,
19690 replace: lsp_range,
19691 },
19692 )),
19693 filter_text: Some(snippet.body.clone()),
19694 sort_text: Some(char::MAX.to_string()),
19695 ..lsp::CompletionItem::default()
19696 }),
19697 lsp_defaults: None,
19698 },
19699 label: CodeLabel {
19700 text: matching_prefix.clone(),
19701 runs: Vec::new(),
19702 filter_range: 0..matching_prefix.len(),
19703 },
19704 icon_path: None,
19705 documentation: snippet.description.clone().map(|description| {
19706 CompletionDocumentation::SingleLine(description.into())
19707 }),
19708 insert_text_mode: None,
19709 confirm: None,
19710 })
19711 })
19712 .collect();
19713
19714 all_results.append(&mut result);
19715 }
19716
19717 Ok(all_results)
19718 })
19719}
19720
19721impl CompletionProvider for Entity<Project> {
19722 fn completions(
19723 &self,
19724 _excerpt_id: ExcerptId,
19725 buffer: &Entity<Buffer>,
19726 buffer_position: text::Anchor,
19727 options: CompletionContext,
19728 _window: &mut Window,
19729 cx: &mut Context<Editor>,
19730 ) -> Task<Result<Option<Vec<Completion>>>> {
19731 self.update(cx, |project, cx| {
19732 let snippets = snippet_completions(project, buffer, buffer_position, cx);
19733 let project_completions = project.completions(buffer, buffer_position, options, cx);
19734 cx.background_spawn(async move {
19735 let snippets_completions = snippets.await?;
19736 match project_completions.await? {
19737 Some(mut completions) => {
19738 completions.extend(snippets_completions);
19739 Ok(Some(completions))
19740 }
19741 None => {
19742 if snippets_completions.is_empty() {
19743 Ok(None)
19744 } else {
19745 Ok(Some(snippets_completions))
19746 }
19747 }
19748 }
19749 })
19750 })
19751 }
19752
19753 fn resolve_completions(
19754 &self,
19755 buffer: Entity<Buffer>,
19756 completion_indices: Vec<usize>,
19757 completions: Rc<RefCell<Box<[Completion]>>>,
19758 cx: &mut Context<Editor>,
19759 ) -> Task<Result<bool>> {
19760 self.update(cx, |project, cx| {
19761 project.lsp_store().update(cx, |lsp_store, cx| {
19762 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
19763 })
19764 })
19765 }
19766
19767 fn apply_additional_edits_for_completion(
19768 &self,
19769 buffer: Entity<Buffer>,
19770 completions: Rc<RefCell<Box<[Completion]>>>,
19771 completion_index: usize,
19772 push_to_history: bool,
19773 cx: &mut Context<Editor>,
19774 ) -> Task<Result<Option<language::Transaction>>> {
19775 self.update(cx, |project, cx| {
19776 project.lsp_store().update(cx, |lsp_store, cx| {
19777 lsp_store.apply_additional_edits_for_completion(
19778 buffer,
19779 completions,
19780 completion_index,
19781 push_to_history,
19782 cx,
19783 )
19784 })
19785 })
19786 }
19787
19788 fn is_completion_trigger(
19789 &self,
19790 buffer: &Entity<Buffer>,
19791 position: language::Anchor,
19792 text: &str,
19793 trigger_in_words: bool,
19794 cx: &mut Context<Editor>,
19795 ) -> bool {
19796 let mut chars = text.chars();
19797 let char = if let Some(char) = chars.next() {
19798 char
19799 } else {
19800 return false;
19801 };
19802 if chars.next().is_some() {
19803 return false;
19804 }
19805
19806 let buffer = buffer.read(cx);
19807 let snapshot = buffer.snapshot();
19808 if !snapshot.settings_at(position, cx).show_completions_on_input {
19809 return false;
19810 }
19811 let classifier = snapshot.char_classifier_at(position).for_completion(true);
19812 if trigger_in_words && classifier.is_word(char) {
19813 return true;
19814 }
19815
19816 buffer.completion_triggers().contains(text)
19817 }
19818}
19819
19820impl SemanticsProvider for Entity<Project> {
19821 fn hover(
19822 &self,
19823 buffer: &Entity<Buffer>,
19824 position: text::Anchor,
19825 cx: &mut App,
19826 ) -> Option<Task<Vec<project::Hover>>> {
19827 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
19828 }
19829
19830 fn document_highlights(
19831 &self,
19832 buffer: &Entity<Buffer>,
19833 position: text::Anchor,
19834 cx: &mut App,
19835 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
19836 Some(self.update(cx, |project, cx| {
19837 project.document_highlights(buffer, position, cx)
19838 }))
19839 }
19840
19841 fn definitions(
19842 &self,
19843 buffer: &Entity<Buffer>,
19844 position: text::Anchor,
19845 kind: GotoDefinitionKind,
19846 cx: &mut App,
19847 ) -> Option<Task<Result<Vec<LocationLink>>>> {
19848 Some(self.update(cx, |project, cx| match kind {
19849 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
19850 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
19851 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
19852 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
19853 }))
19854 }
19855
19856 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
19857 // TODO: make this work for remote projects
19858 self.update(cx, |project, cx| {
19859 if project
19860 .active_debug_session(cx)
19861 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
19862 {
19863 return true;
19864 }
19865
19866 buffer.update(cx, |buffer, cx| {
19867 project.any_language_server_supports_inlay_hints(buffer, cx)
19868 })
19869 })
19870 }
19871
19872 fn inline_values(
19873 &self,
19874 buffer_handle: Entity<Buffer>,
19875 range: Range<text::Anchor>,
19876 cx: &mut App,
19877 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
19878 self.update(cx, |project, cx| {
19879 let (session, active_stack_frame) = project.active_debug_session(cx)?;
19880
19881 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
19882 })
19883 }
19884
19885 fn inlay_hints(
19886 &self,
19887 buffer_handle: Entity<Buffer>,
19888 range: Range<text::Anchor>,
19889 cx: &mut App,
19890 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
19891 Some(self.update(cx, |project, cx| {
19892 project.inlay_hints(buffer_handle, range, cx)
19893 }))
19894 }
19895
19896 fn resolve_inlay_hint(
19897 &self,
19898 hint: InlayHint,
19899 buffer_handle: Entity<Buffer>,
19900 server_id: LanguageServerId,
19901 cx: &mut App,
19902 ) -> Option<Task<anyhow::Result<InlayHint>>> {
19903 Some(self.update(cx, |project, cx| {
19904 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
19905 }))
19906 }
19907
19908 fn range_for_rename(
19909 &self,
19910 buffer: &Entity<Buffer>,
19911 position: text::Anchor,
19912 cx: &mut App,
19913 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
19914 Some(self.update(cx, |project, cx| {
19915 let buffer = buffer.clone();
19916 let task = project.prepare_rename(buffer.clone(), position, cx);
19917 cx.spawn(async move |_, cx| {
19918 Ok(match task.await? {
19919 PrepareRenameResponse::Success(range) => Some(range),
19920 PrepareRenameResponse::InvalidPosition => None,
19921 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
19922 // Fallback on using TreeSitter info to determine identifier range
19923 buffer.update(cx, |buffer, _| {
19924 let snapshot = buffer.snapshot();
19925 let (range, kind) = snapshot.surrounding_word(position);
19926 if kind != Some(CharKind::Word) {
19927 return None;
19928 }
19929 Some(
19930 snapshot.anchor_before(range.start)
19931 ..snapshot.anchor_after(range.end),
19932 )
19933 })?
19934 }
19935 })
19936 })
19937 }))
19938 }
19939
19940 fn perform_rename(
19941 &self,
19942 buffer: &Entity<Buffer>,
19943 position: text::Anchor,
19944 new_name: String,
19945 cx: &mut App,
19946 ) -> Option<Task<Result<ProjectTransaction>>> {
19947 Some(self.update(cx, |project, cx| {
19948 project.perform_rename(buffer.clone(), position, new_name, cx)
19949 }))
19950 }
19951}
19952
19953fn inlay_hint_settings(
19954 location: Anchor,
19955 snapshot: &MultiBufferSnapshot,
19956 cx: &mut Context<Editor>,
19957) -> InlayHintSettings {
19958 let file = snapshot.file_at(location);
19959 let language = snapshot.language_at(location).map(|l| l.name());
19960 language_settings(language, file, cx).inlay_hints
19961}
19962
19963fn consume_contiguous_rows(
19964 contiguous_row_selections: &mut Vec<Selection<Point>>,
19965 selection: &Selection<Point>,
19966 display_map: &DisplaySnapshot,
19967 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
19968) -> (MultiBufferRow, MultiBufferRow) {
19969 contiguous_row_selections.push(selection.clone());
19970 let start_row = MultiBufferRow(selection.start.row);
19971 let mut end_row = ending_row(selection, display_map);
19972
19973 while let Some(next_selection) = selections.peek() {
19974 if next_selection.start.row <= end_row.0 {
19975 end_row = ending_row(next_selection, display_map);
19976 contiguous_row_selections.push(selections.next().unwrap().clone());
19977 } else {
19978 break;
19979 }
19980 }
19981 (start_row, end_row)
19982}
19983
19984fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
19985 if next_selection.end.column > 0 || next_selection.is_empty() {
19986 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
19987 } else {
19988 MultiBufferRow(next_selection.end.row)
19989 }
19990}
19991
19992impl EditorSnapshot {
19993 pub fn remote_selections_in_range<'a>(
19994 &'a self,
19995 range: &'a Range<Anchor>,
19996 collaboration_hub: &dyn CollaborationHub,
19997 cx: &'a App,
19998 ) -> impl 'a + Iterator<Item = RemoteSelection> {
19999 let participant_names = collaboration_hub.user_names(cx);
20000 let participant_indices = collaboration_hub.user_participant_indices(cx);
20001 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20002 let collaborators_by_replica_id = collaborators_by_peer_id
20003 .iter()
20004 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
20005 .collect::<HashMap<_, _>>();
20006 self.buffer_snapshot
20007 .selections_in_range(range, false)
20008 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20009 if replica_id == AGENT_REPLICA_ID {
20010 Some(RemoteSelection {
20011 replica_id,
20012 selection,
20013 cursor_shape,
20014 line_mode,
20015 collaborator_id: CollaboratorId::Agent,
20016 user_name: Some("Agent".into()),
20017 color: cx.theme().players().agent(),
20018 })
20019 } else {
20020 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20021 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20022 let user_name = participant_names.get(&collaborator.user_id).cloned();
20023 Some(RemoteSelection {
20024 replica_id,
20025 selection,
20026 cursor_shape,
20027 line_mode,
20028 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20029 user_name,
20030 color: if let Some(index) = participant_index {
20031 cx.theme().players().color_for_participant(index.0)
20032 } else {
20033 cx.theme().players().absent()
20034 },
20035 })
20036 }
20037 })
20038 }
20039
20040 pub fn hunks_for_ranges(
20041 &self,
20042 ranges: impl IntoIterator<Item = Range<Point>>,
20043 ) -> Vec<MultiBufferDiffHunk> {
20044 let mut hunks = Vec::new();
20045 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20046 HashMap::default();
20047 for query_range in ranges {
20048 let query_rows =
20049 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20050 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20051 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20052 ) {
20053 // Include deleted hunks that are adjacent to the query range, because
20054 // otherwise they would be missed.
20055 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20056 if hunk.status().is_deleted() {
20057 intersects_range |= hunk.row_range.start == query_rows.end;
20058 intersects_range |= hunk.row_range.end == query_rows.start;
20059 }
20060 if intersects_range {
20061 if !processed_buffer_rows
20062 .entry(hunk.buffer_id)
20063 .or_default()
20064 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20065 {
20066 continue;
20067 }
20068 hunks.push(hunk);
20069 }
20070 }
20071 }
20072
20073 hunks
20074 }
20075
20076 fn display_diff_hunks_for_rows<'a>(
20077 &'a self,
20078 display_rows: Range<DisplayRow>,
20079 folded_buffers: &'a HashSet<BufferId>,
20080 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20081 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20082 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20083
20084 self.buffer_snapshot
20085 .diff_hunks_in_range(buffer_start..buffer_end)
20086 .filter_map(|hunk| {
20087 if folded_buffers.contains(&hunk.buffer_id) {
20088 return None;
20089 }
20090
20091 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20092 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20093
20094 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20095 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20096
20097 let display_hunk = if hunk_display_start.column() != 0 {
20098 DisplayDiffHunk::Folded {
20099 display_row: hunk_display_start.row(),
20100 }
20101 } else {
20102 let mut end_row = hunk_display_end.row();
20103 if hunk_display_end.column() > 0 {
20104 end_row.0 += 1;
20105 }
20106 let is_created_file = hunk.is_created_file();
20107 DisplayDiffHunk::Unfolded {
20108 status: hunk.status(),
20109 diff_base_byte_range: hunk.diff_base_byte_range,
20110 display_row_range: hunk_display_start.row()..end_row,
20111 multi_buffer_range: Anchor::range_in_buffer(
20112 hunk.excerpt_id,
20113 hunk.buffer_id,
20114 hunk.buffer_range,
20115 ),
20116 is_created_file,
20117 }
20118 };
20119
20120 Some(display_hunk)
20121 })
20122 }
20123
20124 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20125 self.display_snapshot.buffer_snapshot.language_at(position)
20126 }
20127
20128 pub fn is_focused(&self) -> bool {
20129 self.is_focused
20130 }
20131
20132 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20133 self.placeholder_text.as_ref()
20134 }
20135
20136 pub fn scroll_position(&self) -> gpui::Point<f32> {
20137 self.scroll_anchor.scroll_position(&self.display_snapshot)
20138 }
20139
20140 fn gutter_dimensions(
20141 &self,
20142 font_id: FontId,
20143 font_size: Pixels,
20144 max_line_number_width: Pixels,
20145 cx: &App,
20146 ) -> Option<GutterDimensions> {
20147 if !self.show_gutter {
20148 return None;
20149 }
20150
20151 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20152 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20153
20154 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20155 matches!(
20156 ProjectSettings::get_global(cx).git.git_gutter,
20157 Some(GitGutterSetting::TrackedFiles)
20158 )
20159 });
20160 let gutter_settings = EditorSettings::get_global(cx).gutter;
20161 let show_line_numbers = self
20162 .show_line_numbers
20163 .unwrap_or(gutter_settings.line_numbers);
20164 let line_gutter_width = if show_line_numbers {
20165 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20166 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20167 max_line_number_width.max(min_width_for_number_on_gutter)
20168 } else {
20169 0.0.into()
20170 };
20171
20172 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20173 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20174
20175 let git_blame_entries_width =
20176 self.git_blame_gutter_max_author_length
20177 .map(|max_author_length| {
20178 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20179 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20180
20181 /// The number of characters to dedicate to gaps and margins.
20182 const SPACING_WIDTH: usize = 4;
20183
20184 let max_char_count = max_author_length.min(renderer.max_author_length())
20185 + ::git::SHORT_SHA_LENGTH
20186 + MAX_RELATIVE_TIMESTAMP.len()
20187 + SPACING_WIDTH;
20188
20189 em_advance * max_char_count
20190 });
20191
20192 let is_singleton = self.buffer_snapshot.is_singleton();
20193
20194 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
20195 left_padding += if !is_singleton {
20196 em_width * 4.0
20197 } else if show_runnables || show_breakpoints {
20198 em_width * 3.0
20199 } else if show_git_gutter && show_line_numbers {
20200 em_width * 2.0
20201 } else if show_git_gutter || show_line_numbers {
20202 em_width
20203 } else {
20204 px(0.)
20205 };
20206
20207 let shows_folds = is_singleton && gutter_settings.folds;
20208
20209 let right_padding = if shows_folds && show_line_numbers {
20210 em_width * 4.0
20211 } else if shows_folds || (!is_singleton && show_line_numbers) {
20212 em_width * 3.0
20213 } else if show_line_numbers {
20214 em_width
20215 } else {
20216 px(0.)
20217 };
20218
20219 Some(GutterDimensions {
20220 left_padding,
20221 right_padding,
20222 width: line_gutter_width + left_padding + right_padding,
20223 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
20224 git_blame_entries_width,
20225 })
20226 }
20227
20228 pub fn render_crease_toggle(
20229 &self,
20230 buffer_row: MultiBufferRow,
20231 row_contains_cursor: bool,
20232 editor: Entity<Editor>,
20233 window: &mut Window,
20234 cx: &mut App,
20235 ) -> Option<AnyElement> {
20236 let folded = self.is_line_folded(buffer_row);
20237 let mut is_foldable = false;
20238
20239 if let Some(crease) = self
20240 .crease_snapshot
20241 .query_row(buffer_row, &self.buffer_snapshot)
20242 {
20243 is_foldable = true;
20244 match crease {
20245 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20246 if let Some(render_toggle) = render_toggle {
20247 let toggle_callback =
20248 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20249 if folded {
20250 editor.update(cx, |editor, cx| {
20251 editor.fold_at(buffer_row, window, cx)
20252 });
20253 } else {
20254 editor.update(cx, |editor, cx| {
20255 editor.unfold_at(buffer_row, window, cx)
20256 });
20257 }
20258 });
20259 return Some((render_toggle)(
20260 buffer_row,
20261 folded,
20262 toggle_callback,
20263 window,
20264 cx,
20265 ));
20266 }
20267 }
20268 }
20269 }
20270
20271 is_foldable |= self.starts_indent(buffer_row);
20272
20273 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20274 Some(
20275 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20276 .toggle_state(folded)
20277 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20278 if folded {
20279 this.unfold_at(buffer_row, window, cx);
20280 } else {
20281 this.fold_at(buffer_row, window, cx);
20282 }
20283 }))
20284 .into_any_element(),
20285 )
20286 } else {
20287 None
20288 }
20289 }
20290
20291 pub fn render_crease_trailer(
20292 &self,
20293 buffer_row: MultiBufferRow,
20294 window: &mut Window,
20295 cx: &mut App,
20296 ) -> Option<AnyElement> {
20297 let folded = self.is_line_folded(buffer_row);
20298 if let Crease::Inline { render_trailer, .. } = self
20299 .crease_snapshot
20300 .query_row(buffer_row, &self.buffer_snapshot)?
20301 {
20302 let render_trailer = render_trailer.as_ref()?;
20303 Some(render_trailer(buffer_row, folded, window, cx))
20304 } else {
20305 None
20306 }
20307 }
20308}
20309
20310impl Deref for EditorSnapshot {
20311 type Target = DisplaySnapshot;
20312
20313 fn deref(&self) -> &Self::Target {
20314 &self.display_snapshot
20315 }
20316}
20317
20318#[derive(Clone, Debug, PartialEq, Eq)]
20319pub enum EditorEvent {
20320 InputIgnored {
20321 text: Arc<str>,
20322 },
20323 InputHandled {
20324 utf16_range_to_replace: Option<Range<isize>>,
20325 text: Arc<str>,
20326 },
20327 ExcerptsAdded {
20328 buffer: Entity<Buffer>,
20329 predecessor: ExcerptId,
20330 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
20331 },
20332 ExcerptsRemoved {
20333 ids: Vec<ExcerptId>,
20334 removed_buffer_ids: Vec<BufferId>,
20335 },
20336 BufferFoldToggled {
20337 ids: Vec<ExcerptId>,
20338 folded: bool,
20339 },
20340 ExcerptsEdited {
20341 ids: Vec<ExcerptId>,
20342 },
20343 ExcerptsExpanded {
20344 ids: Vec<ExcerptId>,
20345 },
20346 BufferEdited,
20347 Edited {
20348 transaction_id: clock::Lamport,
20349 },
20350 Reparsed(BufferId),
20351 Focused,
20352 FocusedIn,
20353 Blurred,
20354 DirtyChanged,
20355 Saved,
20356 TitleChanged,
20357 DiffBaseChanged,
20358 SelectionsChanged {
20359 local: bool,
20360 },
20361 ScrollPositionChanged {
20362 local: bool,
20363 autoscroll: bool,
20364 },
20365 Closed,
20366 TransactionUndone {
20367 transaction_id: clock::Lamport,
20368 },
20369 TransactionBegun {
20370 transaction_id: clock::Lamport,
20371 },
20372 Reloaded,
20373 CursorShapeChanged,
20374 PushedToNavHistory {
20375 anchor: Anchor,
20376 is_deactivate: bool,
20377 },
20378}
20379
20380impl EventEmitter<EditorEvent> for Editor {}
20381
20382impl Focusable for Editor {
20383 fn focus_handle(&self, _cx: &App) -> FocusHandle {
20384 self.focus_handle.clone()
20385 }
20386}
20387
20388impl Render for Editor {
20389 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20390 let settings = ThemeSettings::get_global(cx);
20391
20392 let mut text_style = match self.mode {
20393 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
20394 color: cx.theme().colors().editor_foreground,
20395 font_family: settings.ui_font.family.clone(),
20396 font_features: settings.ui_font.features.clone(),
20397 font_fallbacks: settings.ui_font.fallbacks.clone(),
20398 font_size: rems(0.875).into(),
20399 font_weight: settings.ui_font.weight,
20400 line_height: relative(settings.buffer_line_height.value()),
20401 ..Default::default()
20402 },
20403 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
20404 color: cx.theme().colors().editor_foreground,
20405 font_family: settings.buffer_font.family.clone(),
20406 font_features: settings.buffer_font.features.clone(),
20407 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20408 font_size: settings.buffer_font_size(cx).into(),
20409 font_weight: settings.buffer_font.weight,
20410 line_height: relative(settings.buffer_line_height.value()),
20411 ..Default::default()
20412 },
20413 };
20414 if let Some(text_style_refinement) = &self.text_style_refinement {
20415 text_style.refine(text_style_refinement)
20416 }
20417
20418 let background = match self.mode {
20419 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
20420 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
20421 EditorMode::Full { .. } => cx.theme().colors().editor_background,
20422 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
20423 };
20424
20425 let show_underlines = !self.mode.is_minimap();
20426
20427 EditorElement::new(
20428 &cx.entity(),
20429 EditorStyle {
20430 background,
20431 local_player: cx.theme().players().local(),
20432 text: text_style,
20433 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
20434 syntax: cx.theme().syntax().clone(),
20435 status: cx.theme().status().clone(),
20436 inlay_hints_style: make_inlay_hints_style(cx),
20437 inline_completion_styles: make_suggestion_styles(cx),
20438 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
20439 show_underlines,
20440 },
20441 )
20442 }
20443}
20444
20445impl EntityInputHandler for Editor {
20446 fn text_for_range(
20447 &mut self,
20448 range_utf16: Range<usize>,
20449 adjusted_range: &mut Option<Range<usize>>,
20450 _: &mut Window,
20451 cx: &mut Context<Self>,
20452 ) -> Option<String> {
20453 let snapshot = self.buffer.read(cx).read(cx);
20454 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
20455 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
20456 if (start.0..end.0) != range_utf16 {
20457 adjusted_range.replace(start.0..end.0);
20458 }
20459 Some(snapshot.text_for_range(start..end).collect())
20460 }
20461
20462 fn selected_text_range(
20463 &mut self,
20464 ignore_disabled_input: bool,
20465 _: &mut Window,
20466 cx: &mut Context<Self>,
20467 ) -> Option<UTF16Selection> {
20468 // Prevent the IME menu from appearing when holding down an alphabetic key
20469 // while input is disabled.
20470 if !ignore_disabled_input && !self.input_enabled {
20471 return None;
20472 }
20473
20474 let selection = self.selections.newest::<OffsetUtf16>(cx);
20475 let range = selection.range();
20476
20477 Some(UTF16Selection {
20478 range: range.start.0..range.end.0,
20479 reversed: selection.reversed,
20480 })
20481 }
20482
20483 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
20484 let snapshot = self.buffer.read(cx).read(cx);
20485 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
20486 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
20487 }
20488
20489 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20490 self.clear_highlights::<InputComposition>(cx);
20491 self.ime_transaction.take();
20492 }
20493
20494 fn replace_text_in_range(
20495 &mut self,
20496 range_utf16: Option<Range<usize>>,
20497 text: &str,
20498 window: &mut Window,
20499 cx: &mut Context<Self>,
20500 ) {
20501 if !self.input_enabled {
20502 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20503 return;
20504 }
20505
20506 self.transact(window, cx, |this, window, cx| {
20507 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
20508 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20509 Some(this.selection_replacement_ranges(range_utf16, cx))
20510 } else {
20511 this.marked_text_ranges(cx)
20512 };
20513
20514 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
20515 let newest_selection_id = this.selections.newest_anchor().id;
20516 this.selections
20517 .all::<OffsetUtf16>(cx)
20518 .iter()
20519 .zip(ranges_to_replace.iter())
20520 .find_map(|(selection, range)| {
20521 if selection.id == newest_selection_id {
20522 Some(
20523 (range.start.0 as isize - selection.head().0 as isize)
20524 ..(range.end.0 as isize - selection.head().0 as isize),
20525 )
20526 } else {
20527 None
20528 }
20529 })
20530 });
20531
20532 cx.emit(EditorEvent::InputHandled {
20533 utf16_range_to_replace: range_to_replace,
20534 text: text.into(),
20535 });
20536
20537 if let Some(new_selected_ranges) = new_selected_ranges {
20538 this.change_selections(None, window, cx, |selections| {
20539 selections.select_ranges(new_selected_ranges)
20540 });
20541 this.backspace(&Default::default(), window, cx);
20542 }
20543
20544 this.handle_input(text, window, cx);
20545 });
20546
20547 if let Some(transaction) = self.ime_transaction {
20548 self.buffer.update(cx, |buffer, cx| {
20549 buffer.group_until_transaction(transaction, cx);
20550 });
20551 }
20552
20553 self.unmark_text(window, cx);
20554 }
20555
20556 fn replace_and_mark_text_in_range(
20557 &mut self,
20558 range_utf16: Option<Range<usize>>,
20559 text: &str,
20560 new_selected_range_utf16: Option<Range<usize>>,
20561 window: &mut Window,
20562 cx: &mut Context<Self>,
20563 ) {
20564 if !self.input_enabled {
20565 return;
20566 }
20567
20568 let transaction = self.transact(window, cx, |this, window, cx| {
20569 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
20570 let snapshot = this.buffer.read(cx).read(cx);
20571 if let Some(relative_range_utf16) = range_utf16.as_ref() {
20572 for marked_range in &mut marked_ranges {
20573 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
20574 marked_range.start.0 += relative_range_utf16.start;
20575 marked_range.start =
20576 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
20577 marked_range.end =
20578 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
20579 }
20580 }
20581 Some(marked_ranges)
20582 } else if let Some(range_utf16) = range_utf16 {
20583 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20584 Some(this.selection_replacement_ranges(range_utf16, cx))
20585 } else {
20586 None
20587 };
20588
20589 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
20590 let newest_selection_id = this.selections.newest_anchor().id;
20591 this.selections
20592 .all::<OffsetUtf16>(cx)
20593 .iter()
20594 .zip(ranges_to_replace.iter())
20595 .find_map(|(selection, range)| {
20596 if selection.id == newest_selection_id {
20597 Some(
20598 (range.start.0 as isize - selection.head().0 as isize)
20599 ..(range.end.0 as isize - selection.head().0 as isize),
20600 )
20601 } else {
20602 None
20603 }
20604 })
20605 });
20606
20607 cx.emit(EditorEvent::InputHandled {
20608 utf16_range_to_replace: range_to_replace,
20609 text: text.into(),
20610 });
20611
20612 if let Some(ranges) = ranges_to_replace {
20613 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
20614 }
20615
20616 let marked_ranges = {
20617 let snapshot = this.buffer.read(cx).read(cx);
20618 this.selections
20619 .disjoint_anchors()
20620 .iter()
20621 .map(|selection| {
20622 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
20623 })
20624 .collect::<Vec<_>>()
20625 };
20626
20627 if text.is_empty() {
20628 this.unmark_text(window, cx);
20629 } else {
20630 this.highlight_text::<InputComposition>(
20631 marked_ranges.clone(),
20632 HighlightStyle {
20633 underline: Some(UnderlineStyle {
20634 thickness: px(1.),
20635 color: None,
20636 wavy: false,
20637 }),
20638 ..Default::default()
20639 },
20640 cx,
20641 );
20642 }
20643
20644 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
20645 let use_autoclose = this.use_autoclose;
20646 let use_auto_surround = this.use_auto_surround;
20647 this.set_use_autoclose(false);
20648 this.set_use_auto_surround(false);
20649 this.handle_input(text, window, cx);
20650 this.set_use_autoclose(use_autoclose);
20651 this.set_use_auto_surround(use_auto_surround);
20652
20653 if let Some(new_selected_range) = new_selected_range_utf16 {
20654 let snapshot = this.buffer.read(cx).read(cx);
20655 let new_selected_ranges = marked_ranges
20656 .into_iter()
20657 .map(|marked_range| {
20658 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
20659 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
20660 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
20661 snapshot.clip_offset_utf16(new_start, Bias::Left)
20662 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
20663 })
20664 .collect::<Vec<_>>();
20665
20666 drop(snapshot);
20667 this.change_selections(None, window, cx, |selections| {
20668 selections.select_ranges(new_selected_ranges)
20669 });
20670 }
20671 });
20672
20673 self.ime_transaction = self.ime_transaction.or(transaction);
20674 if let Some(transaction) = self.ime_transaction {
20675 self.buffer.update(cx, |buffer, cx| {
20676 buffer.group_until_transaction(transaction, cx);
20677 });
20678 }
20679
20680 if self.text_highlights::<InputComposition>(cx).is_none() {
20681 self.ime_transaction.take();
20682 }
20683 }
20684
20685 fn bounds_for_range(
20686 &mut self,
20687 range_utf16: Range<usize>,
20688 element_bounds: gpui::Bounds<Pixels>,
20689 window: &mut Window,
20690 cx: &mut Context<Self>,
20691 ) -> Option<gpui::Bounds<Pixels>> {
20692 let text_layout_details = self.text_layout_details(window);
20693 let gpui::Size {
20694 width: em_width,
20695 height: line_height,
20696 } = self.character_size(window);
20697
20698 let snapshot = self.snapshot(window, cx);
20699 let scroll_position = snapshot.scroll_position();
20700 let scroll_left = scroll_position.x * em_width;
20701
20702 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
20703 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
20704 + self.gutter_dimensions.width
20705 + self.gutter_dimensions.margin;
20706 let y = line_height * (start.row().as_f32() - scroll_position.y);
20707
20708 Some(Bounds {
20709 origin: element_bounds.origin + point(x, y),
20710 size: size(em_width, line_height),
20711 })
20712 }
20713
20714 fn character_index_for_point(
20715 &mut self,
20716 point: gpui::Point<Pixels>,
20717 _window: &mut Window,
20718 _cx: &mut Context<Self>,
20719 ) -> Option<usize> {
20720 let position_map = self.last_position_map.as_ref()?;
20721 if !position_map.text_hitbox.contains(&point) {
20722 return None;
20723 }
20724 let display_point = position_map.point_for_position(point).previous_valid;
20725 let anchor = position_map
20726 .snapshot
20727 .display_point_to_anchor(display_point, Bias::Left);
20728 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
20729 Some(utf16_offset.0)
20730 }
20731}
20732
20733trait SelectionExt {
20734 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
20735 fn spanned_rows(
20736 &self,
20737 include_end_if_at_line_start: bool,
20738 map: &DisplaySnapshot,
20739 ) -> Range<MultiBufferRow>;
20740}
20741
20742impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
20743 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
20744 let start = self
20745 .start
20746 .to_point(&map.buffer_snapshot)
20747 .to_display_point(map);
20748 let end = self
20749 .end
20750 .to_point(&map.buffer_snapshot)
20751 .to_display_point(map);
20752 if self.reversed {
20753 end..start
20754 } else {
20755 start..end
20756 }
20757 }
20758
20759 fn spanned_rows(
20760 &self,
20761 include_end_if_at_line_start: bool,
20762 map: &DisplaySnapshot,
20763 ) -> Range<MultiBufferRow> {
20764 let start = self.start.to_point(&map.buffer_snapshot);
20765 let mut end = self.end.to_point(&map.buffer_snapshot);
20766 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
20767 end.row -= 1;
20768 }
20769
20770 let buffer_start = map.prev_line_boundary(start).0;
20771 let buffer_end = map.next_line_boundary(end).0;
20772 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
20773 }
20774}
20775
20776impl<T: InvalidationRegion> InvalidationStack<T> {
20777 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
20778 where
20779 S: Clone + ToOffset,
20780 {
20781 while let Some(region) = self.last() {
20782 let all_selections_inside_invalidation_ranges =
20783 if selections.len() == region.ranges().len() {
20784 selections
20785 .iter()
20786 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
20787 .all(|(selection, invalidation_range)| {
20788 let head = selection.head().to_offset(buffer);
20789 invalidation_range.start <= head && invalidation_range.end >= head
20790 })
20791 } else {
20792 false
20793 };
20794
20795 if all_selections_inside_invalidation_ranges {
20796 break;
20797 } else {
20798 self.pop();
20799 }
20800 }
20801 }
20802}
20803
20804impl<T> Default for InvalidationStack<T> {
20805 fn default() -> Self {
20806 Self(Default::default())
20807 }
20808}
20809
20810impl<T> Deref for InvalidationStack<T> {
20811 type Target = Vec<T>;
20812
20813 fn deref(&self) -> &Self::Target {
20814 &self.0
20815 }
20816}
20817
20818impl<T> DerefMut for InvalidationStack<T> {
20819 fn deref_mut(&mut self) -> &mut Self::Target {
20820 &mut self.0
20821 }
20822}
20823
20824impl InvalidationRegion for SnippetState {
20825 fn ranges(&self) -> &[Range<Anchor>] {
20826 &self.ranges[self.active_index]
20827 }
20828}
20829
20830fn inline_completion_edit_text(
20831 current_snapshot: &BufferSnapshot,
20832 edits: &[(Range<Anchor>, String)],
20833 edit_preview: &EditPreview,
20834 include_deletions: bool,
20835 cx: &App,
20836) -> HighlightedText {
20837 let edits = edits
20838 .iter()
20839 .map(|(anchor, text)| {
20840 (
20841 anchor.start.text_anchor..anchor.end.text_anchor,
20842 text.clone(),
20843 )
20844 })
20845 .collect::<Vec<_>>();
20846
20847 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
20848}
20849
20850pub fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
20851 match severity {
20852 DiagnosticSeverity::ERROR => colors.error,
20853 DiagnosticSeverity::WARNING => colors.warning,
20854 DiagnosticSeverity::INFORMATION => colors.info,
20855 DiagnosticSeverity::HINT => colors.info,
20856 _ => colors.ignored,
20857 }
20858}
20859
20860pub fn styled_runs_for_code_label<'a>(
20861 label: &'a CodeLabel,
20862 syntax_theme: &'a theme::SyntaxTheme,
20863) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
20864 let fade_out = HighlightStyle {
20865 fade_out: Some(0.35),
20866 ..Default::default()
20867 };
20868
20869 let mut prev_end = label.filter_range.end;
20870 label
20871 .runs
20872 .iter()
20873 .enumerate()
20874 .flat_map(move |(ix, (range, highlight_id))| {
20875 let style = if let Some(style) = highlight_id.style(syntax_theme) {
20876 style
20877 } else {
20878 return Default::default();
20879 };
20880 let mut muted_style = style;
20881 muted_style.highlight(fade_out);
20882
20883 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
20884 if range.start >= label.filter_range.end {
20885 if range.start > prev_end {
20886 runs.push((prev_end..range.start, fade_out));
20887 }
20888 runs.push((range.clone(), muted_style));
20889 } else if range.end <= label.filter_range.end {
20890 runs.push((range.clone(), style));
20891 } else {
20892 runs.push((range.start..label.filter_range.end, style));
20893 runs.push((label.filter_range.end..range.end, muted_style));
20894 }
20895 prev_end = cmp::max(prev_end, range.end);
20896
20897 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
20898 runs.push((prev_end..label.text.len(), fade_out));
20899 }
20900
20901 runs
20902 })
20903}
20904
20905pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
20906 let mut prev_index = 0;
20907 let mut prev_codepoint: Option<char> = None;
20908 text.char_indices()
20909 .chain([(text.len(), '\0')])
20910 .filter_map(move |(index, codepoint)| {
20911 let prev_codepoint = prev_codepoint.replace(codepoint)?;
20912 let is_boundary = index == text.len()
20913 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
20914 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
20915 if is_boundary {
20916 let chunk = &text[prev_index..index];
20917 prev_index = index;
20918 Some(chunk)
20919 } else {
20920 None
20921 }
20922 })
20923}
20924
20925pub trait RangeToAnchorExt: Sized {
20926 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
20927
20928 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
20929 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
20930 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
20931 }
20932}
20933
20934impl<T: ToOffset> RangeToAnchorExt for Range<T> {
20935 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
20936 let start_offset = self.start.to_offset(snapshot);
20937 let end_offset = self.end.to_offset(snapshot);
20938 if start_offset == end_offset {
20939 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
20940 } else {
20941 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
20942 }
20943 }
20944}
20945
20946pub trait RowExt {
20947 fn as_f32(&self) -> f32;
20948
20949 fn next_row(&self) -> Self;
20950
20951 fn previous_row(&self) -> Self;
20952
20953 fn minus(&self, other: Self) -> u32;
20954}
20955
20956impl RowExt for DisplayRow {
20957 fn as_f32(&self) -> f32 {
20958 self.0 as f32
20959 }
20960
20961 fn next_row(&self) -> Self {
20962 Self(self.0 + 1)
20963 }
20964
20965 fn previous_row(&self) -> Self {
20966 Self(self.0.saturating_sub(1))
20967 }
20968
20969 fn minus(&self, other: Self) -> u32 {
20970 self.0 - other.0
20971 }
20972}
20973
20974impl RowExt for MultiBufferRow {
20975 fn as_f32(&self) -> f32 {
20976 self.0 as f32
20977 }
20978
20979 fn next_row(&self) -> Self {
20980 Self(self.0 + 1)
20981 }
20982
20983 fn previous_row(&self) -> Self {
20984 Self(self.0.saturating_sub(1))
20985 }
20986
20987 fn minus(&self, other: Self) -> u32 {
20988 self.0 - other.0
20989 }
20990}
20991
20992trait RowRangeExt {
20993 type Row;
20994
20995 fn len(&self) -> usize;
20996
20997 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
20998}
20999
21000impl RowRangeExt for Range<MultiBufferRow> {
21001 type Row = MultiBufferRow;
21002
21003 fn len(&self) -> usize {
21004 (self.end.0 - self.start.0) as usize
21005 }
21006
21007 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21008 (self.start.0..self.end.0).map(MultiBufferRow)
21009 }
21010}
21011
21012impl RowRangeExt for Range<DisplayRow> {
21013 type Row = DisplayRow;
21014
21015 fn len(&self) -> usize {
21016 (self.end.0 - self.start.0) as usize
21017 }
21018
21019 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21020 (self.start.0..self.end.0).map(DisplayRow)
21021 }
21022}
21023
21024/// If select range has more than one line, we
21025/// just point the cursor to range.start.
21026fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21027 if range.start.row == range.end.row {
21028 range
21029 } else {
21030 range.start..range.start
21031 }
21032}
21033pub struct KillRing(ClipboardItem);
21034impl Global for KillRing {}
21035
21036const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21037
21038enum BreakpointPromptEditAction {
21039 Log,
21040 Condition,
21041 HitCondition,
21042}
21043
21044struct BreakpointPromptEditor {
21045 pub(crate) prompt: Entity<Editor>,
21046 editor: WeakEntity<Editor>,
21047 breakpoint_anchor: Anchor,
21048 breakpoint: Breakpoint,
21049 edit_action: BreakpointPromptEditAction,
21050 block_ids: HashSet<CustomBlockId>,
21051 editor_margins: Arc<Mutex<EditorMargins>>,
21052 _subscriptions: Vec<Subscription>,
21053}
21054
21055impl BreakpointPromptEditor {
21056 const MAX_LINES: u8 = 4;
21057
21058 fn new(
21059 editor: WeakEntity<Editor>,
21060 breakpoint_anchor: Anchor,
21061 breakpoint: Breakpoint,
21062 edit_action: BreakpointPromptEditAction,
21063 window: &mut Window,
21064 cx: &mut Context<Self>,
21065 ) -> Self {
21066 let base_text = match edit_action {
21067 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21068 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21069 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21070 }
21071 .map(|msg| msg.to_string())
21072 .unwrap_or_default();
21073
21074 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21075 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21076
21077 let prompt = cx.new(|cx| {
21078 let mut prompt = Editor::new(
21079 EditorMode::AutoHeight {
21080 max_lines: Self::MAX_LINES as usize,
21081 },
21082 buffer,
21083 None,
21084 window,
21085 cx,
21086 );
21087 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21088 prompt.set_show_cursor_when_unfocused(false, cx);
21089 prompt.set_placeholder_text(
21090 match edit_action {
21091 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21092 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21093 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21094 },
21095 cx,
21096 );
21097
21098 prompt
21099 });
21100
21101 Self {
21102 prompt,
21103 editor,
21104 breakpoint_anchor,
21105 breakpoint,
21106 edit_action,
21107 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21108 block_ids: Default::default(),
21109 _subscriptions: vec![],
21110 }
21111 }
21112
21113 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21114 self.block_ids.extend(block_ids)
21115 }
21116
21117 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21118 if let Some(editor) = self.editor.upgrade() {
21119 let message = self
21120 .prompt
21121 .read(cx)
21122 .buffer
21123 .read(cx)
21124 .as_singleton()
21125 .expect("A multi buffer in breakpoint prompt isn't possible")
21126 .read(cx)
21127 .as_rope()
21128 .to_string();
21129
21130 editor.update(cx, |editor, cx| {
21131 editor.edit_breakpoint_at_anchor(
21132 self.breakpoint_anchor,
21133 self.breakpoint.clone(),
21134 match self.edit_action {
21135 BreakpointPromptEditAction::Log => {
21136 BreakpointEditAction::EditLogMessage(message.into())
21137 }
21138 BreakpointPromptEditAction::Condition => {
21139 BreakpointEditAction::EditCondition(message.into())
21140 }
21141 BreakpointPromptEditAction::HitCondition => {
21142 BreakpointEditAction::EditHitCondition(message.into())
21143 }
21144 },
21145 cx,
21146 );
21147
21148 editor.remove_blocks(self.block_ids.clone(), None, cx);
21149 cx.focus_self(window);
21150 });
21151 }
21152 }
21153
21154 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21155 self.editor
21156 .update(cx, |editor, cx| {
21157 editor.remove_blocks(self.block_ids.clone(), None, cx);
21158 window.focus(&editor.focus_handle);
21159 })
21160 .log_err();
21161 }
21162
21163 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21164 let settings = ThemeSettings::get_global(cx);
21165 let text_style = TextStyle {
21166 color: if self.prompt.read(cx).read_only(cx) {
21167 cx.theme().colors().text_disabled
21168 } else {
21169 cx.theme().colors().text
21170 },
21171 font_family: settings.buffer_font.family.clone(),
21172 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21173 font_size: settings.buffer_font_size(cx).into(),
21174 font_weight: settings.buffer_font.weight,
21175 line_height: relative(settings.buffer_line_height.value()),
21176 ..Default::default()
21177 };
21178 EditorElement::new(
21179 &self.prompt,
21180 EditorStyle {
21181 background: cx.theme().colors().editor_background,
21182 local_player: cx.theme().players().local(),
21183 text: text_style,
21184 ..Default::default()
21185 },
21186 )
21187 }
21188}
21189
21190impl Render for BreakpointPromptEditor {
21191 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21192 let editor_margins = *self.editor_margins.lock();
21193 let gutter_dimensions = editor_margins.gutter;
21194 h_flex()
21195 .key_context("Editor")
21196 .bg(cx.theme().colors().editor_background)
21197 .border_y_1()
21198 .border_color(cx.theme().status().info_border)
21199 .size_full()
21200 .py(window.line_height() / 2.5)
21201 .on_action(cx.listener(Self::confirm))
21202 .on_action(cx.listener(Self::cancel))
21203 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21204 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21205 }
21206}
21207
21208impl Focusable for BreakpointPromptEditor {
21209 fn focus_handle(&self, cx: &App) -> FocusHandle {
21210 self.prompt.focus_handle(cx)
21211 }
21212}
21213
21214fn all_edits_insertions_or_deletions(
21215 edits: &Vec<(Range<Anchor>, String)>,
21216 snapshot: &MultiBufferSnapshot,
21217) -> bool {
21218 let mut all_insertions = true;
21219 let mut all_deletions = true;
21220
21221 for (range, new_text) in edits.iter() {
21222 let range_is_empty = range.to_offset(&snapshot).is_empty();
21223 let text_is_empty = new_text.is_empty();
21224
21225 if range_is_empty != text_is_empty {
21226 if range_is_empty {
21227 all_deletions = false;
21228 } else {
21229 all_insertions = false;
21230 }
21231 } else {
21232 return false;
21233 }
21234
21235 if !all_insertions && !all_deletions {
21236 return false;
21237 }
21238 }
21239 all_insertions || all_deletions
21240}
21241
21242struct MissingEditPredictionKeybindingTooltip;
21243
21244impl Render for MissingEditPredictionKeybindingTooltip {
21245 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21246 ui::tooltip_container(window, cx, |container, _, cx| {
21247 container
21248 .flex_shrink_0()
21249 .max_w_80()
21250 .min_h(rems_from_px(124.))
21251 .justify_between()
21252 .child(
21253 v_flex()
21254 .flex_1()
21255 .text_ui_sm(cx)
21256 .child(Label::new("Conflict with Accept Keybinding"))
21257 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21258 )
21259 .child(
21260 h_flex()
21261 .pb_1()
21262 .gap_1()
21263 .items_end()
21264 .w_full()
21265 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21266 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21267 }))
21268 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21269 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21270 })),
21271 )
21272 })
21273 }
21274}
21275
21276#[derive(Debug, Clone, Copy, PartialEq)]
21277pub struct LineHighlight {
21278 pub background: Background,
21279 pub border: Option<gpui::Hsla>,
21280 pub include_gutter: bool,
21281 pub type_id: Option<TypeId>,
21282}
21283
21284fn render_diff_hunk_controls(
21285 row: u32,
21286 status: &DiffHunkStatus,
21287 hunk_range: Range<Anchor>,
21288 is_created_file: bool,
21289 line_height: Pixels,
21290 editor: &Entity<Editor>,
21291 _window: &mut Window,
21292 cx: &mut App,
21293) -> AnyElement {
21294 h_flex()
21295 .h(line_height)
21296 .mr_1()
21297 .gap_1()
21298 .px_0p5()
21299 .pb_1()
21300 .border_x_1()
21301 .border_b_1()
21302 .border_color(cx.theme().colors().border_variant)
21303 .rounded_b_lg()
21304 .bg(cx.theme().colors().editor_background)
21305 .gap_1()
21306 .occlude()
21307 .shadow_md()
21308 .child(if status.has_secondary_hunk() {
21309 Button::new(("stage", row as u64), "Stage")
21310 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21311 .tooltip({
21312 let focus_handle = editor.focus_handle(cx);
21313 move |window, cx| {
21314 Tooltip::for_action_in(
21315 "Stage Hunk",
21316 &::git::ToggleStaged,
21317 &focus_handle,
21318 window,
21319 cx,
21320 )
21321 }
21322 })
21323 .on_click({
21324 let editor = editor.clone();
21325 move |_event, _window, cx| {
21326 editor.update(cx, |editor, cx| {
21327 editor.stage_or_unstage_diff_hunks(
21328 true,
21329 vec![hunk_range.start..hunk_range.start],
21330 cx,
21331 );
21332 });
21333 }
21334 })
21335 } else {
21336 Button::new(("unstage", row as u64), "Unstage")
21337 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21338 .tooltip({
21339 let focus_handle = editor.focus_handle(cx);
21340 move |window, cx| {
21341 Tooltip::for_action_in(
21342 "Unstage Hunk",
21343 &::git::ToggleStaged,
21344 &focus_handle,
21345 window,
21346 cx,
21347 )
21348 }
21349 })
21350 .on_click({
21351 let editor = editor.clone();
21352 move |_event, _window, cx| {
21353 editor.update(cx, |editor, cx| {
21354 editor.stage_or_unstage_diff_hunks(
21355 false,
21356 vec![hunk_range.start..hunk_range.start],
21357 cx,
21358 );
21359 });
21360 }
21361 })
21362 })
21363 .child(
21364 Button::new(("restore", row as u64), "Restore")
21365 .tooltip({
21366 let focus_handle = editor.focus_handle(cx);
21367 move |window, cx| {
21368 Tooltip::for_action_in(
21369 "Restore Hunk",
21370 &::git::Restore,
21371 &focus_handle,
21372 window,
21373 cx,
21374 )
21375 }
21376 })
21377 .on_click({
21378 let editor = editor.clone();
21379 move |_event, window, cx| {
21380 editor.update(cx, |editor, cx| {
21381 let snapshot = editor.snapshot(window, cx);
21382 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
21383 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
21384 });
21385 }
21386 })
21387 .disabled(is_created_file),
21388 )
21389 .when(
21390 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
21391 |el| {
21392 el.child(
21393 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
21394 .shape(IconButtonShape::Square)
21395 .icon_size(IconSize::Small)
21396 // .disabled(!has_multiple_hunks)
21397 .tooltip({
21398 let focus_handle = editor.focus_handle(cx);
21399 move |window, cx| {
21400 Tooltip::for_action_in(
21401 "Next Hunk",
21402 &GoToHunk,
21403 &focus_handle,
21404 window,
21405 cx,
21406 )
21407 }
21408 })
21409 .on_click({
21410 let editor = editor.clone();
21411 move |_event, window, cx| {
21412 editor.update(cx, |editor, cx| {
21413 let snapshot = editor.snapshot(window, cx);
21414 let position =
21415 hunk_range.end.to_point(&snapshot.buffer_snapshot);
21416 editor.go_to_hunk_before_or_after_position(
21417 &snapshot,
21418 position,
21419 Direction::Next,
21420 window,
21421 cx,
21422 );
21423 editor.expand_selected_diff_hunks(cx);
21424 });
21425 }
21426 }),
21427 )
21428 .child(
21429 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
21430 .shape(IconButtonShape::Square)
21431 .icon_size(IconSize::Small)
21432 // .disabled(!has_multiple_hunks)
21433 .tooltip({
21434 let focus_handle = editor.focus_handle(cx);
21435 move |window, cx| {
21436 Tooltip::for_action_in(
21437 "Previous Hunk",
21438 &GoToPreviousHunk,
21439 &focus_handle,
21440 window,
21441 cx,
21442 )
21443 }
21444 })
21445 .on_click({
21446 let editor = editor.clone();
21447 move |_event, window, cx| {
21448 editor.update(cx, |editor, cx| {
21449 let snapshot = editor.snapshot(window, cx);
21450 let point =
21451 hunk_range.start.to_point(&snapshot.buffer_snapshot);
21452 editor.go_to_hunk_before_or_after_position(
21453 &snapshot,
21454 point,
21455 Direction::Prev,
21456 window,
21457 cx,
21458 );
21459 editor.expand_selected_diff_hunks(cx);
21460 });
21461 }
21462 }),
21463 )
21464 },
21465 )
21466 .into_any_element()
21467}