1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod editor_tests;
46#[cfg(test)]
47mod inline_completion_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
54use aho_corasick::AhoCorasick;
55use anyhow::{Context as _, Result, anyhow};
56use blink_manager::BlinkManager;
57use buffer_diff::DiffHunkStatus;
58use client::{Collaborator, ParticipantIndex};
59use clock::{AGENT_REPLICA_ID, ReplicaId};
60use collections::{BTreeMap, HashMap, HashSet, VecDeque};
61use convert_case::{Case, Casing};
62use display_map::*;
63pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
64pub use editor_settings::{
65 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, SearchSettings,
66 ShowScrollbar,
67};
68use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
69pub use editor_settings_controls::*;
70use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
71pub use element::{
72 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
73};
74use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
75use futures::{
76 FutureExt,
77 future::{self, Shared, join},
78};
79use fuzzy::StringMatchCandidate;
80
81use ::git::blame::BlameEntry;
82use ::git::{Restore, blame::ParsedCommitMessage};
83use code_context_menus::{
84 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
85 CompletionsMenu, ContextMenuOrigin,
86};
87use git::blame::{GitBlame, GlobalBlameRenderer};
88use gpui::{
89 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
90 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
91 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
92 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
93 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
94 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
95 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
96 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
97};
98use highlight_matching_bracket::refresh_matching_bracket_highlights;
99use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
100pub use hover_popover::hover_markdown_style;
101use hover_popover::{HoverState, hide_hover};
102use indent_guides::ActiveIndentGuidesState;
103use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
104pub use inline_completion::Direction;
105use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
106pub use items::MAX_TAB_TITLE_LEN;
107use itertools::Itertools;
108use language::{
109 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
110 CursorShape, DiagnosticEntry, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText,
111 IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal, TextObject,
112 TransactionId, TreeSitterOptions, WordsQuery,
113 language_settings::{
114 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
115 all_language_settings, language_settings,
116 },
117 point_from_lsp, text_diff_with_options,
118};
119use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
120use linked_editing_ranges::refresh_linked_ranges;
121use markdown::Markdown;
122use mouse_context_menu::MouseContextMenu;
123use persistence::DB;
124use project::{
125 ProjectPath,
126 debugger::{
127 breakpoint_store::{
128 BreakpointEditAction, BreakpointState, BreakpointStore, BreakpointStoreEvent,
129 },
130 session::{Session, SessionEvent},
131 },
132 project_settings::DiagnosticSeverity,
133};
134
135pub use git::blame::BlameRenderer;
136pub use proposed_changes_editor::{
137 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
138};
139use smallvec::smallvec;
140use std::{cell::OnceCell, iter::Peekable};
141use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
142
143pub use lsp::CompletionContext;
144use lsp::{
145 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
146 LanguageServerId, LanguageServerName,
147};
148
149use language::BufferSnapshot;
150pub use lsp_ext::lsp_tasks;
151use movement::TextLayoutDetails;
152pub use multi_buffer::{
153 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
154 RowInfo, ToOffset, ToPoint,
155};
156use multi_buffer::{
157 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
158 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
159};
160use parking_lot::Mutex;
161use project::{
162 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
163 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
164 TaskSourceKind,
165 debugger::breakpoint_store::Breakpoint,
166 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
167 project_settings::{GitGutterSetting, ProjectSettings},
168};
169use rand::prelude::*;
170use rpc::{ErrorExt, proto::*};
171use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
172use selections_collection::{
173 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
174};
175use serde::{Deserialize, Serialize};
176use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
177use smallvec::SmallVec;
178use snippet::Snippet;
179use std::sync::Arc;
180use std::{
181 any::TypeId,
182 borrow::Cow,
183 cell::RefCell,
184 cmp::{self, Ordering, Reverse},
185 mem,
186 num::NonZeroU32,
187 ops::{ControlFlow, Deref, DerefMut, Not as _, Range, RangeInclusive},
188 path::{Path, PathBuf},
189 rc::Rc,
190 time::{Duration, Instant},
191};
192pub use sum_tree::Bias;
193use sum_tree::TreeMap;
194use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
195use theme::{
196 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
197 observe_buffer_font_size_adjustment,
198};
199use ui::{
200 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
201 IconSize, Key, Tooltip, h_flex, prelude::*,
202};
203use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
204use workspace::{
205 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
206 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
207 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
208 item::{ItemHandle, PreviewTabsSettings},
209 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
210 searchable::SearchEvent,
211};
212
213use crate::hover_links::{find_url, find_url_from_range};
214use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
215
216pub const FILE_HEADER_HEIGHT: u32 = 2;
217pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
218pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
219const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
220const MAX_LINE_LEN: usize = 1024;
221const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
222const MAX_SELECTION_HISTORY_LEN: usize = 1024;
223pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
224#[doc(hidden)]
225pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
226const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
227
228pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
229pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
230pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
231
232pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
233pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
234pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
235pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
236
237pub type RenderDiffHunkControlsFn = Arc<
238 dyn Fn(
239 u32,
240 &DiffHunkStatus,
241 Range<Anchor>,
242 bool,
243 Pixels,
244 &Entity<Editor>,
245 &mut Window,
246 &mut App,
247 ) -> AnyElement,
248>;
249
250const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
251 alt: true,
252 shift: true,
253 control: false,
254 platform: false,
255 function: false,
256};
257
258struct InlineValueCache {
259 enabled: bool,
260 inlays: Vec<InlayId>,
261 refresh_task: Task<Option<()>>,
262}
263
264impl InlineValueCache {
265 fn new(enabled: bool) -> Self {
266 Self {
267 enabled,
268 inlays: Vec::new(),
269 refresh_task: Task::ready(None),
270 }
271 }
272}
273
274#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
275pub enum InlayId {
276 InlineCompletion(usize),
277 Hint(usize),
278 DebuggerValue(usize),
279}
280
281impl InlayId {
282 fn id(&self) -> usize {
283 match self {
284 Self::InlineCompletion(id) => *id,
285 Self::Hint(id) => *id,
286 Self::DebuggerValue(id) => *id,
287 }
288 }
289}
290
291pub enum ActiveDebugLine {}
292enum DocumentHighlightRead {}
293enum DocumentHighlightWrite {}
294enum InputComposition {}
295enum SelectedTextHighlight {}
296
297pub enum ConflictsOuter {}
298pub enum ConflictsOurs {}
299pub enum ConflictsTheirs {}
300pub enum ConflictsOursMarker {}
301pub enum ConflictsTheirsMarker {}
302
303#[derive(Debug, Copy, Clone, PartialEq, Eq)]
304pub enum Navigated {
305 Yes,
306 No,
307}
308
309impl Navigated {
310 pub fn from_bool(yes: bool) -> Navigated {
311 if yes { Navigated::Yes } else { Navigated::No }
312 }
313}
314
315#[derive(Debug, Clone, PartialEq, Eq)]
316enum DisplayDiffHunk {
317 Folded {
318 display_row: DisplayRow,
319 },
320 Unfolded {
321 is_created_file: bool,
322 diff_base_byte_range: Range<usize>,
323 display_row_range: Range<DisplayRow>,
324 multi_buffer_range: Range<Anchor>,
325 status: DiffHunkStatus,
326 },
327}
328
329pub enum HideMouseCursorOrigin {
330 TypingAction,
331 MovementAction,
332}
333
334pub fn init_settings(cx: &mut App) {
335 EditorSettings::register(cx);
336}
337
338pub fn init(cx: &mut App) {
339 init_settings(cx);
340
341 cx.set_global(GlobalBlameRenderer(Arc::new(())));
342
343 workspace::register_project_item::<Editor>(cx);
344 workspace::FollowableViewRegistry::register::<Editor>(cx);
345 workspace::register_serializable_item::<Editor>(cx);
346
347 cx.observe_new(
348 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
349 workspace.register_action(Editor::new_file);
350 workspace.register_action(Editor::new_file_vertical);
351 workspace.register_action(Editor::new_file_horizontal);
352 workspace.register_action(Editor::cancel_language_server_work);
353 },
354 )
355 .detach();
356
357 cx.on_action(move |_: &workspace::NewFile, cx| {
358 let app_state = workspace::AppState::global(cx);
359 if let Some(app_state) = app_state.upgrade() {
360 workspace::open_new(
361 Default::default(),
362 app_state,
363 cx,
364 |workspace, window, cx| {
365 Editor::new_file(workspace, &Default::default(), window, cx)
366 },
367 )
368 .detach();
369 }
370 });
371 cx.on_action(move |_: &workspace::NewWindow, cx| {
372 let app_state = workspace::AppState::global(cx);
373 if let Some(app_state) = app_state.upgrade() {
374 workspace::open_new(
375 Default::default(),
376 app_state,
377 cx,
378 |workspace, window, cx| {
379 cx.activate(true);
380 Editor::new_file(workspace, &Default::default(), window, cx)
381 },
382 )
383 .detach();
384 }
385 });
386}
387
388pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
389 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
390}
391
392pub trait DiagnosticRenderer {
393 fn render_group(
394 &self,
395 diagnostic_group: Vec<DiagnosticEntry<Point>>,
396 buffer_id: BufferId,
397 snapshot: EditorSnapshot,
398 editor: WeakEntity<Editor>,
399 cx: &mut App,
400 ) -> Vec<BlockProperties<Anchor>>;
401
402 fn render_hover(
403 &self,
404 diagnostic_group: Vec<DiagnosticEntry<Point>>,
405 range: Range<Point>,
406 buffer_id: BufferId,
407 cx: &mut App,
408 ) -> Option<Entity<markdown::Markdown>>;
409
410 fn open_link(
411 &self,
412 editor: &mut Editor,
413 link: SharedString,
414 window: &mut Window,
415 cx: &mut Context<Editor>,
416 );
417}
418
419pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
420
421impl GlobalDiagnosticRenderer {
422 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
423 cx.try_global::<Self>().map(|g| g.0.clone())
424 }
425}
426
427impl gpui::Global for GlobalDiagnosticRenderer {}
428pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
429 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
430}
431
432pub struct SearchWithinRange;
433
434trait InvalidationRegion {
435 fn ranges(&self) -> &[Range<Anchor>];
436}
437
438#[derive(Clone, Debug, PartialEq)]
439pub enum SelectPhase {
440 Begin {
441 position: DisplayPoint,
442 add: bool,
443 click_count: usize,
444 },
445 BeginColumnar {
446 position: DisplayPoint,
447 reset: bool,
448 goal_column: u32,
449 },
450 Extend {
451 position: DisplayPoint,
452 click_count: usize,
453 },
454 Update {
455 position: DisplayPoint,
456 goal_column: u32,
457 scroll_delta: gpui::Point<f32>,
458 },
459 End,
460}
461
462#[derive(Clone, Debug)]
463pub enum SelectMode {
464 Character,
465 Word(Range<Anchor>),
466 Line(Range<Anchor>),
467 All,
468}
469
470#[derive(Clone, PartialEq, Eq, Debug)]
471pub enum EditorMode {
472 SingleLine {
473 auto_width: bool,
474 },
475 AutoHeight {
476 max_lines: usize,
477 },
478 Full {
479 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
480 scale_ui_elements_with_buffer_font_size: bool,
481 /// When set to `true`, the editor will render a background for the active line.
482 show_active_line_background: bool,
483 /// When set to `true`, the editor's height will be determined by its content.
484 sized_by_content: bool,
485 },
486 Minimap {
487 parent: WeakEntity<Editor>,
488 },
489}
490
491impl EditorMode {
492 pub fn full() -> Self {
493 Self::Full {
494 scale_ui_elements_with_buffer_font_size: true,
495 show_active_line_background: true,
496 sized_by_content: false,
497 }
498 }
499
500 pub fn is_full(&self) -> bool {
501 matches!(self, Self::Full { .. })
502 }
503
504 fn is_minimap(&self) -> bool {
505 matches!(self, Self::Minimap { .. })
506 }
507}
508
509#[derive(Copy, Clone, Debug)]
510pub enum SoftWrap {
511 /// Prefer not to wrap at all.
512 ///
513 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
514 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
515 GitDiff,
516 /// Prefer a single line generally, unless an overly long line is encountered.
517 None,
518 /// Soft wrap lines that exceed the editor width.
519 EditorWidth,
520 /// Soft wrap lines at the preferred line length.
521 Column(u32),
522 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
523 Bounded(u32),
524}
525
526#[derive(Clone)]
527pub struct EditorStyle {
528 pub background: Hsla,
529 pub local_player: PlayerColor,
530 pub text: TextStyle,
531 pub scrollbar_width: Pixels,
532 pub syntax: Arc<SyntaxTheme>,
533 pub status: StatusColors,
534 pub inlay_hints_style: HighlightStyle,
535 pub inline_completion_styles: InlineCompletionStyles,
536 pub unnecessary_code_fade: f32,
537 pub show_underlines: bool,
538}
539
540impl Default for EditorStyle {
541 fn default() -> Self {
542 Self {
543 background: Hsla::default(),
544 local_player: PlayerColor::default(),
545 text: TextStyle::default(),
546 scrollbar_width: Pixels::default(),
547 syntax: Default::default(),
548 // HACK: Status colors don't have a real default.
549 // We should look into removing the status colors from the editor
550 // style and retrieve them directly from the theme.
551 status: StatusColors::dark(),
552 inlay_hints_style: HighlightStyle::default(),
553 inline_completion_styles: InlineCompletionStyles {
554 insertion: HighlightStyle::default(),
555 whitespace: HighlightStyle::default(),
556 },
557 unnecessary_code_fade: Default::default(),
558 show_underlines: true,
559 }
560 }
561}
562
563pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
564 let show_background = language_settings::language_settings(None, None, cx)
565 .inlay_hints
566 .show_background;
567
568 HighlightStyle {
569 color: Some(cx.theme().status().hint),
570 background_color: show_background.then(|| cx.theme().status().hint_background),
571 ..HighlightStyle::default()
572 }
573}
574
575pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
576 InlineCompletionStyles {
577 insertion: HighlightStyle {
578 color: Some(cx.theme().status().predictive),
579 ..HighlightStyle::default()
580 },
581 whitespace: HighlightStyle {
582 background_color: Some(cx.theme().status().created_background),
583 ..HighlightStyle::default()
584 },
585 }
586}
587
588type CompletionId = usize;
589
590pub(crate) enum EditDisplayMode {
591 TabAccept,
592 DiffPopover,
593 Inline,
594}
595
596enum InlineCompletion {
597 Edit {
598 edits: Vec<(Range<Anchor>, String)>,
599 edit_preview: Option<EditPreview>,
600 display_mode: EditDisplayMode,
601 snapshot: BufferSnapshot,
602 },
603 Move {
604 target: Anchor,
605 snapshot: BufferSnapshot,
606 },
607}
608
609struct InlineCompletionState {
610 inlay_ids: Vec<InlayId>,
611 completion: InlineCompletion,
612 completion_id: Option<SharedString>,
613 invalidation_range: Range<Anchor>,
614}
615
616enum EditPredictionSettings {
617 Disabled,
618 Enabled {
619 show_in_menu: bool,
620 preview_requires_modifier: bool,
621 },
622}
623
624enum InlineCompletionHighlight {}
625
626#[derive(Debug, Clone)]
627struct InlineDiagnostic {
628 message: SharedString,
629 group_id: usize,
630 is_primary: bool,
631 start: Point,
632 severity: lsp::DiagnosticSeverity,
633}
634
635pub enum MenuInlineCompletionsPolicy {
636 Never,
637 ByProvider,
638}
639
640pub enum EditPredictionPreview {
641 /// Modifier is not pressed
642 Inactive { released_too_fast: bool },
643 /// Modifier pressed
644 Active {
645 since: Instant,
646 previous_scroll_position: Option<ScrollAnchor>,
647 },
648}
649
650impl EditPredictionPreview {
651 pub fn released_too_fast(&self) -> bool {
652 match self {
653 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
654 EditPredictionPreview::Active { .. } => false,
655 }
656 }
657
658 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
659 if let EditPredictionPreview::Active {
660 previous_scroll_position,
661 ..
662 } = self
663 {
664 *previous_scroll_position = scroll_position;
665 }
666 }
667}
668
669pub struct ContextMenuOptions {
670 pub min_entries_visible: usize,
671 pub max_entries_visible: usize,
672 pub placement: Option<ContextMenuPlacement>,
673}
674
675#[derive(Debug, Clone, PartialEq, Eq)]
676pub enum ContextMenuPlacement {
677 Above,
678 Below,
679}
680
681#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
682struct EditorActionId(usize);
683
684impl EditorActionId {
685 pub fn post_inc(&mut self) -> Self {
686 let answer = self.0;
687
688 *self = Self(answer + 1);
689
690 Self(answer)
691 }
692}
693
694// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
695// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
696
697type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
698type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
699
700#[derive(Default)]
701struct ScrollbarMarkerState {
702 scrollbar_size: Size<Pixels>,
703 dirty: bool,
704 markers: Arc<[PaintQuad]>,
705 pending_refresh: Option<Task<Result<()>>>,
706}
707
708impl ScrollbarMarkerState {
709 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
710 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
711 }
712}
713
714#[derive(Clone, Debug)]
715struct RunnableTasks {
716 templates: Vec<(TaskSourceKind, TaskTemplate)>,
717 offset: multi_buffer::Anchor,
718 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
719 column: u32,
720 // Values of all named captures, including those starting with '_'
721 extra_variables: HashMap<String, String>,
722 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
723 context_range: Range<BufferOffset>,
724}
725
726impl RunnableTasks {
727 fn resolve<'a>(
728 &'a self,
729 cx: &'a task::TaskContext,
730 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
731 self.templates.iter().filter_map(|(kind, template)| {
732 template
733 .resolve_task(&kind.to_id_base(), cx)
734 .map(|task| (kind.clone(), task))
735 })
736 }
737}
738
739#[derive(Clone)]
740struct ResolvedTasks {
741 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
742 position: Anchor,
743}
744
745#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
746struct BufferOffset(usize);
747
748// Addons allow storing per-editor state in other crates (e.g. Vim)
749pub trait Addon: 'static {
750 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
751
752 fn render_buffer_header_controls(
753 &self,
754 _: &ExcerptInfo,
755 _: &Window,
756 _: &App,
757 ) -> Option<AnyElement> {
758 None
759 }
760
761 fn to_any(&self) -> &dyn std::any::Any;
762
763 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
764 None
765 }
766}
767
768/// A set of caret positions, registered when the editor was edited.
769pub struct ChangeList {
770 changes: Vec<Vec<Anchor>>,
771 /// Currently "selected" change.
772 position: Option<usize>,
773}
774
775impl ChangeList {
776 pub fn new() -> Self {
777 Self {
778 changes: Vec::new(),
779 position: None,
780 }
781 }
782
783 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
784 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
785 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
786 if self.changes.is_empty() {
787 return None;
788 }
789
790 let prev = self.position.unwrap_or(self.changes.len());
791 let next = if direction == Direction::Prev {
792 prev.saturating_sub(count)
793 } else {
794 (prev + count).min(self.changes.len() - 1)
795 };
796 self.position = Some(next);
797 self.changes.get(next).map(|anchors| anchors.as_slice())
798 }
799
800 /// Adds a new change to the list, resetting the change list position.
801 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
802 self.position.take();
803 if pop_state {
804 self.changes.pop();
805 }
806 self.changes.push(new_positions.clone());
807 }
808
809 pub fn last(&self) -> Option<&[Anchor]> {
810 self.changes.last().map(|anchors| anchors.as_slice())
811 }
812}
813
814#[derive(Clone)]
815struct InlineBlamePopoverState {
816 scroll_handle: ScrollHandle,
817 commit_message: Option<ParsedCommitMessage>,
818 markdown: Entity<Markdown>,
819}
820
821struct InlineBlamePopover {
822 position: gpui::Point<Pixels>,
823 show_task: Option<Task<()>>,
824 hide_task: Option<Task<()>>,
825 popover_bounds: Option<Bounds<Pixels>>,
826 popover_state: InlineBlamePopoverState,
827}
828
829/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
830/// a breakpoint on them.
831#[derive(Clone, Copy, Debug)]
832struct PhantomBreakpointIndicator {
833 display_row: DisplayRow,
834 /// There's a small debounce between hovering over the line and showing the indicator.
835 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
836 is_active: bool,
837 collides_with_existing_breakpoint: bool,
838}
839/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
840///
841/// See the [module level documentation](self) for more information.
842pub struct Editor {
843 focus_handle: FocusHandle,
844 last_focused_descendant: Option<WeakFocusHandle>,
845 /// The text buffer being edited
846 buffer: Entity<MultiBuffer>,
847 /// Map of how text in the buffer should be displayed.
848 /// Handles soft wraps, folds, fake inlay text insertions, etc.
849 pub display_map: Entity<DisplayMap>,
850 pub selections: SelectionsCollection,
851 pub scroll_manager: ScrollManager,
852 /// When inline assist editors are linked, they all render cursors because
853 /// typing enters text into each of them, even the ones that aren't focused.
854 pub(crate) show_cursor_when_unfocused: bool,
855 columnar_selection_tail: Option<Anchor>,
856 add_selections_state: Option<AddSelectionsState>,
857 select_next_state: Option<SelectNextState>,
858 select_prev_state: Option<SelectNextState>,
859 selection_history: SelectionHistory,
860 autoclose_regions: Vec<AutocloseRegion>,
861 snippet_stack: InvalidationStack<SnippetState>,
862 select_syntax_node_history: SelectSyntaxNodeHistory,
863 ime_transaction: Option<TransactionId>,
864 pub diagnostics_max_severity: DiagnosticSeverity,
865 active_diagnostics: ActiveDiagnostic,
866 show_inline_diagnostics: bool,
867 inline_diagnostics_update: Task<()>,
868 inline_diagnostics_enabled: bool,
869 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
870 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
871 hard_wrap: Option<usize>,
872
873 // TODO: make this a access method
874 pub project: Option<Entity<Project>>,
875 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
876 completion_provider: Option<Box<dyn CompletionProvider>>,
877 collaboration_hub: Option<Box<dyn CollaborationHub>>,
878 blink_manager: Entity<BlinkManager>,
879 show_cursor_names: bool,
880 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
881 pub show_local_selections: bool,
882 mode: EditorMode,
883 show_breadcrumbs: bool,
884 show_gutter: bool,
885 show_scrollbars: bool,
886 show_minimap: bool,
887 disable_expand_excerpt_buttons: bool,
888 show_line_numbers: Option<bool>,
889 use_relative_line_numbers: Option<bool>,
890 show_git_diff_gutter: Option<bool>,
891 show_code_actions: Option<bool>,
892 show_runnables: Option<bool>,
893 show_breakpoints: Option<bool>,
894 show_wrap_guides: Option<bool>,
895 show_indent_guides: Option<bool>,
896 placeholder_text: Option<Arc<str>>,
897 highlight_order: usize,
898 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
899 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
900 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
901 scrollbar_marker_state: ScrollbarMarkerState,
902 active_indent_guides_state: ActiveIndentGuidesState,
903 nav_history: Option<ItemNavHistory>,
904 context_menu: RefCell<Option<CodeContextMenu>>,
905 context_menu_options: Option<ContextMenuOptions>,
906 mouse_context_menu: Option<MouseContextMenu>,
907 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
908 inline_blame_popover: Option<InlineBlamePopover>,
909 signature_help_state: SignatureHelpState,
910 auto_signature_help: Option<bool>,
911 find_all_references_task_sources: Vec<Anchor>,
912 next_completion_id: CompletionId,
913 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
914 code_actions_task: Option<Task<Result<()>>>,
915 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
916 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
917 document_highlights_task: Option<Task<()>>,
918 linked_editing_range_task: Option<Task<Option<()>>>,
919 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
920 pending_rename: Option<RenameState>,
921 searchable: bool,
922 cursor_shape: CursorShape,
923 current_line_highlight: Option<CurrentLineHighlight>,
924 collapse_matches: bool,
925 autoindent_mode: Option<AutoindentMode>,
926 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
927 input_enabled: bool,
928 use_modal_editing: bool,
929 read_only: bool,
930 leader_id: Option<CollaboratorId>,
931 remote_id: Option<ViewId>,
932 pub hover_state: HoverState,
933 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
934 gutter_hovered: bool,
935 hovered_link_state: Option<HoveredLinkState>,
936 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
937 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
938 active_inline_completion: Option<InlineCompletionState>,
939 /// Used to prevent flickering as the user types while the menu is open
940 stale_inline_completion_in_menu: Option<InlineCompletionState>,
941 edit_prediction_settings: EditPredictionSettings,
942 inline_completions_hidden_for_vim_mode: bool,
943 show_inline_completions_override: Option<bool>,
944 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
945 edit_prediction_preview: EditPredictionPreview,
946 edit_prediction_indent_conflict: bool,
947 edit_prediction_requires_modifier_in_indent_conflict: bool,
948 inlay_hint_cache: InlayHintCache,
949 next_inlay_id: usize,
950 _subscriptions: Vec<Subscription>,
951 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
952 gutter_dimensions: GutterDimensions,
953 style: Option<EditorStyle>,
954 text_style_refinement: Option<TextStyleRefinement>,
955 next_editor_action_id: EditorActionId,
956 editor_actions:
957 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
958 use_autoclose: bool,
959 use_auto_surround: bool,
960 auto_replace_emoji_shortcode: bool,
961 jsx_tag_auto_close_enabled_in_any_buffer: bool,
962 show_git_blame_gutter: bool,
963 show_git_blame_inline: bool,
964 show_git_blame_inline_delay_task: Option<Task<()>>,
965 git_blame_inline_enabled: bool,
966 render_diff_hunk_controls: RenderDiffHunkControlsFn,
967 serialize_dirty_buffers: bool,
968 show_selection_menu: Option<bool>,
969 blame: Option<Entity<GitBlame>>,
970 blame_subscription: Option<Subscription>,
971 custom_context_menu: Option<
972 Box<
973 dyn 'static
974 + Fn(
975 &mut Self,
976 DisplayPoint,
977 &mut Window,
978 &mut Context<Self>,
979 ) -> Option<Entity<ui::ContextMenu>>,
980 >,
981 >,
982 last_bounds: Option<Bounds<Pixels>>,
983 last_position_map: Option<Rc<PositionMap>>,
984 expect_bounds_change: Option<Bounds<Pixels>>,
985 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
986 tasks_update_task: Option<Task<()>>,
987 breakpoint_store: Option<Entity<BreakpointStore>>,
988 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
989 in_project_search: bool,
990 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
991 breadcrumb_header: Option<String>,
992 focused_block: Option<FocusedBlock>,
993 next_scroll_position: NextScrollCursorCenterTopBottom,
994 addons: HashMap<TypeId, Box<dyn Addon>>,
995 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
996 load_diff_task: Option<Shared<Task<()>>>,
997 /// Whether we are temporarily displaying a diff other than git's
998 temporary_diff_override: bool,
999 selection_mark_mode: bool,
1000 toggle_fold_multiple_buffers: Task<()>,
1001 _scroll_cursor_center_top_bottom_task: Task<()>,
1002 serialize_selections: Task<()>,
1003 serialize_folds: Task<()>,
1004 mouse_cursor_hidden: bool,
1005 minimap: Option<Entity<Self>>,
1006 hide_mouse_mode: HideMouseMode,
1007 pub change_list: ChangeList,
1008 inline_value_cache: InlineValueCache,
1009}
1010
1011#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1012enum NextScrollCursorCenterTopBottom {
1013 #[default]
1014 Center,
1015 Top,
1016 Bottom,
1017}
1018
1019impl NextScrollCursorCenterTopBottom {
1020 fn next(&self) -> Self {
1021 match self {
1022 Self::Center => Self::Top,
1023 Self::Top => Self::Bottom,
1024 Self::Bottom => Self::Center,
1025 }
1026 }
1027}
1028
1029#[derive(Clone)]
1030pub struct EditorSnapshot {
1031 pub mode: EditorMode,
1032 show_gutter: bool,
1033 show_line_numbers: Option<bool>,
1034 show_git_diff_gutter: Option<bool>,
1035 show_runnables: Option<bool>,
1036 show_breakpoints: Option<bool>,
1037 git_blame_gutter_max_author_length: Option<usize>,
1038 pub display_snapshot: DisplaySnapshot,
1039 pub placeholder_text: Option<Arc<str>>,
1040 is_focused: bool,
1041 scroll_anchor: ScrollAnchor,
1042 ongoing_scroll: OngoingScroll,
1043 current_line_highlight: CurrentLineHighlight,
1044 gutter_hovered: bool,
1045}
1046
1047#[derive(Default, Debug, Clone, Copy)]
1048pub struct GutterDimensions {
1049 pub left_padding: Pixels,
1050 pub right_padding: Pixels,
1051 pub width: Pixels,
1052 pub margin: Pixels,
1053 pub git_blame_entries_width: Option<Pixels>,
1054}
1055
1056impl GutterDimensions {
1057 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1058 Self {
1059 margin: Self::default_gutter_margin(font_id, font_size, cx),
1060 ..Default::default()
1061 }
1062 }
1063
1064 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1065 -cx.text_system().descent(font_id, font_size)
1066 }
1067 /// The full width of the space taken up by the gutter.
1068 pub fn full_width(&self) -> Pixels {
1069 self.margin + self.width
1070 }
1071
1072 /// The width of the space reserved for the fold indicators,
1073 /// use alongside 'justify_end' and `gutter_width` to
1074 /// right align content with the line numbers
1075 pub fn fold_area_width(&self) -> Pixels {
1076 self.margin + self.right_padding
1077 }
1078}
1079
1080#[derive(Debug)]
1081pub struct RemoteSelection {
1082 pub replica_id: ReplicaId,
1083 pub selection: Selection<Anchor>,
1084 pub cursor_shape: CursorShape,
1085 pub collaborator_id: CollaboratorId,
1086 pub line_mode: bool,
1087 pub user_name: Option<SharedString>,
1088 pub color: PlayerColor,
1089}
1090
1091#[derive(Clone, Debug)]
1092struct SelectionHistoryEntry {
1093 selections: Arc<[Selection<Anchor>]>,
1094 select_next_state: Option<SelectNextState>,
1095 select_prev_state: Option<SelectNextState>,
1096 add_selections_state: Option<AddSelectionsState>,
1097}
1098
1099enum SelectionHistoryMode {
1100 Normal,
1101 Undoing,
1102 Redoing,
1103}
1104
1105#[derive(Clone, PartialEq, Eq, Hash)]
1106struct HoveredCursor {
1107 replica_id: u16,
1108 selection_id: usize,
1109}
1110
1111impl Default for SelectionHistoryMode {
1112 fn default() -> Self {
1113 Self::Normal
1114 }
1115}
1116
1117#[derive(Default)]
1118struct SelectionHistory {
1119 #[allow(clippy::type_complexity)]
1120 selections_by_transaction:
1121 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1122 mode: SelectionHistoryMode,
1123 undo_stack: VecDeque<SelectionHistoryEntry>,
1124 redo_stack: VecDeque<SelectionHistoryEntry>,
1125}
1126
1127impl SelectionHistory {
1128 fn insert_transaction(
1129 &mut self,
1130 transaction_id: TransactionId,
1131 selections: Arc<[Selection<Anchor>]>,
1132 ) {
1133 self.selections_by_transaction
1134 .insert(transaction_id, (selections, None));
1135 }
1136
1137 #[allow(clippy::type_complexity)]
1138 fn transaction(
1139 &self,
1140 transaction_id: TransactionId,
1141 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1142 self.selections_by_transaction.get(&transaction_id)
1143 }
1144
1145 #[allow(clippy::type_complexity)]
1146 fn transaction_mut(
1147 &mut self,
1148 transaction_id: TransactionId,
1149 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1150 self.selections_by_transaction.get_mut(&transaction_id)
1151 }
1152
1153 fn push(&mut self, entry: SelectionHistoryEntry) {
1154 if !entry.selections.is_empty() {
1155 match self.mode {
1156 SelectionHistoryMode::Normal => {
1157 self.push_undo(entry);
1158 self.redo_stack.clear();
1159 }
1160 SelectionHistoryMode::Undoing => self.push_redo(entry),
1161 SelectionHistoryMode::Redoing => self.push_undo(entry),
1162 }
1163 }
1164 }
1165
1166 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1167 if self
1168 .undo_stack
1169 .back()
1170 .map_or(true, |e| e.selections != entry.selections)
1171 {
1172 self.undo_stack.push_back(entry);
1173 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1174 self.undo_stack.pop_front();
1175 }
1176 }
1177 }
1178
1179 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1180 if self
1181 .redo_stack
1182 .back()
1183 .map_or(true, |e| e.selections != entry.selections)
1184 {
1185 self.redo_stack.push_back(entry);
1186 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1187 self.redo_stack.pop_front();
1188 }
1189 }
1190 }
1191}
1192
1193#[derive(Clone, Copy)]
1194pub struct RowHighlightOptions {
1195 pub autoscroll: bool,
1196 pub include_gutter: bool,
1197}
1198
1199impl Default for RowHighlightOptions {
1200 fn default() -> Self {
1201 Self {
1202 autoscroll: Default::default(),
1203 include_gutter: true,
1204 }
1205 }
1206}
1207
1208struct RowHighlight {
1209 index: usize,
1210 range: Range<Anchor>,
1211 color: Hsla,
1212 options: RowHighlightOptions,
1213 type_id: TypeId,
1214}
1215
1216#[derive(Clone, Debug)]
1217struct AddSelectionsState {
1218 above: bool,
1219 stack: Vec<usize>,
1220}
1221
1222#[derive(Clone)]
1223struct SelectNextState {
1224 query: AhoCorasick,
1225 wordwise: bool,
1226 done: bool,
1227}
1228
1229impl std::fmt::Debug for SelectNextState {
1230 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1231 f.debug_struct(std::any::type_name::<Self>())
1232 .field("wordwise", &self.wordwise)
1233 .field("done", &self.done)
1234 .finish()
1235 }
1236}
1237
1238#[derive(Debug)]
1239struct AutocloseRegion {
1240 selection_id: usize,
1241 range: Range<Anchor>,
1242 pair: BracketPair,
1243}
1244
1245#[derive(Debug)]
1246struct SnippetState {
1247 ranges: Vec<Vec<Range<Anchor>>>,
1248 active_index: usize,
1249 choices: Vec<Option<Vec<String>>>,
1250}
1251
1252#[doc(hidden)]
1253pub struct RenameState {
1254 pub range: Range<Anchor>,
1255 pub old_name: Arc<str>,
1256 pub editor: Entity<Editor>,
1257 block_id: CustomBlockId,
1258}
1259
1260struct InvalidationStack<T>(Vec<T>);
1261
1262struct RegisteredInlineCompletionProvider {
1263 provider: Arc<dyn InlineCompletionProviderHandle>,
1264 _subscription: Subscription,
1265}
1266
1267#[derive(Debug, PartialEq, Eq)]
1268pub struct ActiveDiagnosticGroup {
1269 pub active_range: Range<Anchor>,
1270 pub active_message: String,
1271 pub group_id: usize,
1272 pub blocks: HashSet<CustomBlockId>,
1273}
1274
1275#[derive(Debug, PartialEq, Eq)]
1276#[allow(clippy::large_enum_variant)]
1277pub(crate) enum ActiveDiagnostic {
1278 None,
1279 All,
1280 Group(ActiveDiagnosticGroup),
1281}
1282
1283#[derive(Serialize, Deserialize, Clone, Debug)]
1284pub struct ClipboardSelection {
1285 /// The number of bytes in this selection.
1286 pub len: usize,
1287 /// Whether this was a full-line selection.
1288 pub is_entire_line: bool,
1289 /// The indentation of the first line when this content was originally copied.
1290 pub first_line_indent: u32,
1291}
1292
1293// selections, scroll behavior, was newest selection reversed
1294type SelectSyntaxNodeHistoryState = (
1295 Box<[Selection<usize>]>,
1296 SelectSyntaxNodeScrollBehavior,
1297 bool,
1298);
1299
1300#[derive(Default)]
1301struct SelectSyntaxNodeHistory {
1302 stack: Vec<SelectSyntaxNodeHistoryState>,
1303 // disable temporarily to allow changing selections without losing the stack
1304 pub disable_clearing: bool,
1305}
1306
1307impl SelectSyntaxNodeHistory {
1308 pub fn try_clear(&mut self) {
1309 if !self.disable_clearing {
1310 self.stack.clear();
1311 }
1312 }
1313
1314 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1315 self.stack.push(selection);
1316 }
1317
1318 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1319 self.stack.pop()
1320 }
1321}
1322
1323enum SelectSyntaxNodeScrollBehavior {
1324 CursorTop,
1325 FitSelection,
1326 CursorBottom,
1327}
1328
1329#[derive(Debug)]
1330pub(crate) struct NavigationData {
1331 cursor_anchor: Anchor,
1332 cursor_position: Point,
1333 scroll_anchor: ScrollAnchor,
1334 scroll_top_row: u32,
1335}
1336
1337#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1338pub enum GotoDefinitionKind {
1339 Symbol,
1340 Declaration,
1341 Type,
1342 Implementation,
1343}
1344
1345#[derive(Debug, Clone)]
1346enum InlayHintRefreshReason {
1347 ModifiersChanged(bool),
1348 Toggle(bool),
1349 SettingsChange(InlayHintSettings),
1350 NewLinesShown,
1351 BufferEdited(HashSet<Arc<Language>>),
1352 RefreshRequested,
1353 ExcerptsRemoved(Vec<ExcerptId>),
1354}
1355
1356impl InlayHintRefreshReason {
1357 fn description(&self) -> &'static str {
1358 match self {
1359 Self::ModifiersChanged(_) => "modifiers changed",
1360 Self::Toggle(_) => "toggle",
1361 Self::SettingsChange(_) => "settings change",
1362 Self::NewLinesShown => "new lines shown",
1363 Self::BufferEdited(_) => "buffer edited",
1364 Self::RefreshRequested => "refresh requested",
1365 Self::ExcerptsRemoved(_) => "excerpts removed",
1366 }
1367 }
1368}
1369
1370pub enum FormatTarget {
1371 Buffers,
1372 Ranges(Vec<Range<MultiBufferPoint>>),
1373}
1374
1375pub(crate) struct FocusedBlock {
1376 id: BlockId,
1377 focus_handle: WeakFocusHandle,
1378}
1379
1380#[derive(Clone)]
1381enum JumpData {
1382 MultiBufferRow {
1383 row: MultiBufferRow,
1384 line_offset_from_top: u32,
1385 },
1386 MultiBufferPoint {
1387 excerpt_id: ExcerptId,
1388 position: Point,
1389 anchor: text::Anchor,
1390 line_offset_from_top: u32,
1391 },
1392}
1393
1394pub enum MultibufferSelectionMode {
1395 First,
1396 All,
1397}
1398
1399#[derive(Clone, Copy, Debug, Default)]
1400pub struct RewrapOptions {
1401 pub override_language_settings: bool,
1402 pub preserve_existing_whitespace: bool,
1403}
1404
1405impl Editor {
1406 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1407 let buffer = cx.new(|cx| Buffer::local("", cx));
1408 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1409 Self::new(
1410 EditorMode::SingleLine { auto_width: false },
1411 buffer,
1412 None,
1413 window,
1414 cx,
1415 )
1416 }
1417
1418 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1419 let buffer = cx.new(|cx| Buffer::local("", cx));
1420 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1421 Self::new(EditorMode::full(), buffer, None, window, cx)
1422 }
1423
1424 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1425 let buffer = cx.new(|cx| Buffer::local("", cx));
1426 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1427 Self::new(
1428 EditorMode::SingleLine { auto_width: true },
1429 buffer,
1430 None,
1431 window,
1432 cx,
1433 )
1434 }
1435
1436 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1437 let buffer = cx.new(|cx| Buffer::local("", cx));
1438 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1439 Self::new(
1440 EditorMode::AutoHeight { max_lines },
1441 buffer,
1442 None,
1443 window,
1444 cx,
1445 )
1446 }
1447
1448 pub fn for_buffer(
1449 buffer: Entity<Buffer>,
1450 project: Option<Entity<Project>>,
1451 window: &mut Window,
1452 cx: &mut Context<Self>,
1453 ) -> Self {
1454 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1455 Self::new(EditorMode::full(), buffer, project, window, cx)
1456 }
1457
1458 pub fn for_multibuffer(
1459 buffer: Entity<MultiBuffer>,
1460 project: Option<Entity<Project>>,
1461 window: &mut Window,
1462 cx: &mut Context<Self>,
1463 ) -> Self {
1464 Self::new(EditorMode::full(), buffer, project, window, cx)
1465 }
1466
1467 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1468 let mut clone = Self::new(
1469 self.mode.clone(),
1470 self.buffer.clone(),
1471 self.project.clone(),
1472 window,
1473 cx,
1474 );
1475 self.display_map.update(cx, |display_map, cx| {
1476 let snapshot = display_map.snapshot(cx);
1477 clone.display_map.update(cx, |display_map, cx| {
1478 display_map.set_state(&snapshot, cx);
1479 });
1480 });
1481 clone.folds_did_change(cx);
1482 clone.selections.clone_state(&self.selections);
1483 clone.scroll_manager.clone_state(&self.scroll_manager);
1484 clone.searchable = self.searchable;
1485 clone.read_only = self.read_only;
1486 clone
1487 }
1488
1489 pub fn new(
1490 mode: EditorMode,
1491 buffer: Entity<MultiBuffer>,
1492 project: Option<Entity<Project>>,
1493 window: &mut Window,
1494 cx: &mut Context<Self>,
1495 ) -> Self {
1496 Editor::new_internal(mode, buffer, project, None, window, cx)
1497 }
1498
1499 fn new_internal(
1500 mode: EditorMode,
1501 buffer: Entity<MultiBuffer>,
1502 project: Option<Entity<Project>>,
1503 display_map: Option<Entity<DisplayMap>>,
1504 window: &mut Window,
1505 cx: &mut Context<Self>,
1506 ) -> Self {
1507 debug_assert!(
1508 display_map.is_none() || mode.is_minimap(),
1509 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1510 );
1511
1512 let full_mode = mode.is_full();
1513 let diagnostics_max_severity = if full_mode {
1514 EditorSettings::get_global(cx)
1515 .diagnostics_max_severity
1516 .unwrap_or(DiagnosticSeverity::Hint)
1517 } else {
1518 DiagnosticSeverity::Off
1519 };
1520 let style = window.text_style();
1521 let font_size = style.font_size.to_pixels(window.rem_size());
1522 let editor = cx.entity().downgrade();
1523 let fold_placeholder = FoldPlaceholder {
1524 constrain_width: true,
1525 render: Arc::new(move |fold_id, fold_range, cx| {
1526 let editor = editor.clone();
1527 div()
1528 .id(fold_id)
1529 .bg(cx.theme().colors().ghost_element_background)
1530 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1531 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1532 .rounded_xs()
1533 .size_full()
1534 .cursor_pointer()
1535 .child("⋯")
1536 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1537 .on_click(move |_, _window, cx| {
1538 editor
1539 .update(cx, |editor, cx| {
1540 editor.unfold_ranges(
1541 &[fold_range.start..fold_range.end],
1542 true,
1543 false,
1544 cx,
1545 );
1546 cx.stop_propagation();
1547 })
1548 .ok();
1549 })
1550 .into_any()
1551 }),
1552 merge_adjacent: true,
1553 ..FoldPlaceholder::default()
1554 };
1555 let display_map = display_map.unwrap_or_else(|| {
1556 cx.new(|cx| {
1557 DisplayMap::new(
1558 buffer.clone(),
1559 style.font(),
1560 font_size,
1561 None,
1562 FILE_HEADER_HEIGHT,
1563 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1564 fold_placeholder,
1565 diagnostics_max_severity,
1566 cx,
1567 )
1568 })
1569 });
1570
1571 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1572
1573 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1574
1575 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1576 .then(|| language_settings::SoftWrap::None);
1577
1578 let mut project_subscriptions = Vec::new();
1579 if mode.is_full() {
1580 if let Some(project) = project.as_ref() {
1581 project_subscriptions.push(cx.subscribe_in(
1582 project,
1583 window,
1584 |editor, _, event, window, cx| match event {
1585 project::Event::RefreshCodeLens => {
1586 // we always query lens with actions, without storing them, always refreshing them
1587 }
1588 project::Event::RefreshInlayHints => {
1589 editor
1590 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1591 }
1592 project::Event::SnippetEdit(id, snippet_edits) => {
1593 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1594 let focus_handle = editor.focus_handle(cx);
1595 if focus_handle.is_focused(window) {
1596 let snapshot = buffer.read(cx).snapshot();
1597 for (range, snippet) in snippet_edits {
1598 let editor_range =
1599 language::range_from_lsp(*range).to_offset(&snapshot);
1600 editor
1601 .insert_snippet(
1602 &[editor_range],
1603 snippet.clone(),
1604 window,
1605 cx,
1606 )
1607 .ok();
1608 }
1609 }
1610 }
1611 }
1612 _ => {}
1613 },
1614 ));
1615 if let Some(task_inventory) = project
1616 .read(cx)
1617 .task_store()
1618 .read(cx)
1619 .task_inventory()
1620 .cloned()
1621 {
1622 project_subscriptions.push(cx.observe_in(
1623 &task_inventory,
1624 window,
1625 |editor, _, window, cx| {
1626 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1627 },
1628 ));
1629 };
1630
1631 project_subscriptions.push(cx.subscribe_in(
1632 &project.read(cx).breakpoint_store(),
1633 window,
1634 |editor, _, event, window, cx| match event {
1635 BreakpointStoreEvent::ClearDebugLines => {
1636 editor.clear_row_highlights::<ActiveDebugLine>();
1637 editor.refresh_inline_values(cx);
1638 }
1639 BreakpointStoreEvent::SetDebugLine => {
1640 if editor.go_to_active_debug_line(window, cx) {
1641 cx.stop_propagation();
1642 }
1643
1644 editor.refresh_inline_values(cx);
1645 }
1646 _ => {}
1647 },
1648 ));
1649 }
1650 }
1651
1652 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1653
1654 let inlay_hint_settings =
1655 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1656 let focus_handle = cx.focus_handle();
1657 cx.on_focus(&focus_handle, window, Self::handle_focus)
1658 .detach();
1659 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1660 .detach();
1661 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1662 .detach();
1663 cx.on_blur(&focus_handle, window, Self::handle_blur)
1664 .detach();
1665
1666 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1667 Some(false)
1668 } else {
1669 None
1670 };
1671
1672 let breakpoint_store = match (&mode, project.as_ref()) {
1673 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1674 _ => None,
1675 };
1676
1677 let mut code_action_providers = Vec::new();
1678 let mut load_uncommitted_diff = None;
1679 if let Some(project) = project.clone() {
1680 load_uncommitted_diff = Some(
1681 update_uncommitted_diff_for_buffer(
1682 cx.entity(),
1683 &project,
1684 buffer.read(cx).all_buffers(),
1685 buffer.clone(),
1686 cx,
1687 )
1688 .shared(),
1689 );
1690 code_action_providers.push(Rc::new(project) as Rc<_>);
1691 }
1692
1693 let mut this = Self {
1694 focus_handle,
1695 show_cursor_when_unfocused: false,
1696 last_focused_descendant: None,
1697 buffer: buffer.clone(),
1698 display_map: display_map.clone(),
1699 selections,
1700 scroll_manager: ScrollManager::new(cx),
1701 columnar_selection_tail: None,
1702 add_selections_state: None,
1703 select_next_state: None,
1704 select_prev_state: None,
1705 selection_history: SelectionHistory::default(),
1706 autoclose_regions: Vec::new(),
1707 snippet_stack: InvalidationStack::default(),
1708 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1709 ime_transaction: None,
1710 active_diagnostics: ActiveDiagnostic::None,
1711 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1712 inline_diagnostics_update: Task::ready(()),
1713 inline_diagnostics: Vec::new(),
1714 soft_wrap_mode_override,
1715 diagnostics_max_severity,
1716 hard_wrap: None,
1717 completion_provider: project.clone().map(|project| Box::new(project) as _),
1718 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1719 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1720 project,
1721 blink_manager: blink_manager.clone(),
1722 show_local_selections: true,
1723 show_scrollbars: full_mode,
1724 show_minimap: full_mode,
1725 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1726 show_gutter: mode.is_full(),
1727 show_line_numbers: None,
1728 use_relative_line_numbers: None,
1729 disable_expand_excerpt_buttons: false,
1730 show_git_diff_gutter: None,
1731 show_code_actions: None,
1732 show_runnables: None,
1733 show_breakpoints: None,
1734 show_wrap_guides: None,
1735 show_indent_guides,
1736 placeholder_text: None,
1737 highlight_order: 0,
1738 highlighted_rows: HashMap::default(),
1739 background_highlights: TreeMap::default(),
1740 gutter_highlights: TreeMap::default(),
1741 scrollbar_marker_state: ScrollbarMarkerState::default(),
1742 active_indent_guides_state: ActiveIndentGuidesState::default(),
1743 nav_history: None,
1744 context_menu: RefCell::new(None),
1745 context_menu_options: None,
1746 mouse_context_menu: None,
1747 completion_tasks: Vec::new(),
1748 inline_blame_popover: None,
1749 signature_help_state: SignatureHelpState::default(),
1750 auto_signature_help: None,
1751 find_all_references_task_sources: Vec::new(),
1752 next_completion_id: 0,
1753 next_inlay_id: 0,
1754 code_action_providers,
1755 available_code_actions: None,
1756 code_actions_task: None,
1757 quick_selection_highlight_task: None,
1758 debounced_selection_highlight_task: None,
1759 document_highlights_task: None,
1760 linked_editing_range_task: None,
1761 pending_rename: None,
1762 searchable: true,
1763 cursor_shape: EditorSettings::get_global(cx)
1764 .cursor_shape
1765 .unwrap_or_default(),
1766 current_line_highlight: None,
1767 autoindent_mode: Some(AutoindentMode::EachLine),
1768 collapse_matches: false,
1769 workspace: None,
1770 input_enabled: true,
1771 use_modal_editing: mode.is_full(),
1772 read_only: mode.is_minimap(),
1773 use_autoclose: true,
1774 use_auto_surround: true,
1775 auto_replace_emoji_shortcode: false,
1776 jsx_tag_auto_close_enabled_in_any_buffer: false,
1777 leader_id: None,
1778 remote_id: None,
1779 hover_state: HoverState::default(),
1780 pending_mouse_down: None,
1781 hovered_link_state: None,
1782 edit_prediction_provider: None,
1783 active_inline_completion: None,
1784 stale_inline_completion_in_menu: None,
1785 edit_prediction_preview: EditPredictionPreview::Inactive {
1786 released_too_fast: false,
1787 },
1788 inline_diagnostics_enabled: mode.is_full(),
1789 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1790 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1791
1792 gutter_hovered: false,
1793 pixel_position_of_newest_cursor: None,
1794 last_bounds: None,
1795 last_position_map: None,
1796 expect_bounds_change: None,
1797 gutter_dimensions: GutterDimensions::default(),
1798 style: None,
1799 show_cursor_names: false,
1800 hovered_cursors: HashMap::default(),
1801 next_editor_action_id: EditorActionId::default(),
1802 editor_actions: Rc::default(),
1803 inline_completions_hidden_for_vim_mode: false,
1804 show_inline_completions_override: None,
1805 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1806 edit_prediction_settings: EditPredictionSettings::Disabled,
1807 edit_prediction_indent_conflict: false,
1808 edit_prediction_requires_modifier_in_indent_conflict: true,
1809 custom_context_menu: None,
1810 show_git_blame_gutter: false,
1811 show_git_blame_inline: false,
1812 show_selection_menu: None,
1813 show_git_blame_inline_delay_task: None,
1814 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1815 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1816 serialize_dirty_buffers: !mode.is_minimap()
1817 && ProjectSettings::get_global(cx)
1818 .session
1819 .restore_unsaved_buffers,
1820 blame: None,
1821 blame_subscription: None,
1822 tasks: BTreeMap::default(),
1823
1824 breakpoint_store,
1825 gutter_breakpoint_indicator: (None, None),
1826 _subscriptions: vec![
1827 cx.observe(&buffer, Self::on_buffer_changed),
1828 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1829 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1830 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1831 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1832 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1833 cx.observe_window_activation(window, |editor, window, cx| {
1834 let active = window.is_window_active();
1835 editor.blink_manager.update(cx, |blink_manager, cx| {
1836 if active {
1837 blink_manager.enable(cx);
1838 } else {
1839 blink_manager.disable(cx);
1840 }
1841 });
1842 }),
1843 ],
1844 tasks_update_task: None,
1845 linked_edit_ranges: Default::default(),
1846 in_project_search: false,
1847 previous_search_ranges: None,
1848 breadcrumb_header: None,
1849 focused_block: None,
1850 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1851 addons: HashMap::default(),
1852 registered_buffers: HashMap::default(),
1853 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1854 selection_mark_mode: false,
1855 toggle_fold_multiple_buffers: Task::ready(()),
1856 serialize_selections: Task::ready(()),
1857 serialize_folds: Task::ready(()),
1858 text_style_refinement: None,
1859 load_diff_task: load_uncommitted_diff,
1860 temporary_diff_override: false,
1861 mouse_cursor_hidden: false,
1862 minimap: None,
1863 hide_mouse_mode: EditorSettings::get_global(cx)
1864 .hide_mouse
1865 .unwrap_or_default(),
1866 change_list: ChangeList::new(),
1867 mode,
1868 };
1869 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1870 this._subscriptions
1871 .push(cx.observe(breakpoints, |_, _, cx| {
1872 cx.notify();
1873 }));
1874 }
1875 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1876 this._subscriptions.extend(project_subscriptions);
1877
1878 this._subscriptions.push(cx.subscribe_in(
1879 &cx.entity(),
1880 window,
1881 |editor, _, e: &EditorEvent, window, cx| match e {
1882 EditorEvent::ScrollPositionChanged { local, .. } => {
1883 if *local {
1884 let new_anchor = editor.scroll_manager.anchor();
1885 let snapshot = editor.snapshot(window, cx);
1886 editor.update_restoration_data(cx, move |data| {
1887 data.scroll_position = (
1888 new_anchor.top_row(&snapshot.buffer_snapshot),
1889 new_anchor.offset,
1890 );
1891 });
1892 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
1893 editor.inline_blame_popover.take();
1894 }
1895 }
1896 EditorEvent::Edited { .. } => {
1897 if !vim_enabled(cx) {
1898 let (map, selections) = editor.selections.all_adjusted_display(cx);
1899 let pop_state = editor
1900 .change_list
1901 .last()
1902 .map(|previous| {
1903 previous.len() == selections.len()
1904 && previous.iter().enumerate().all(|(ix, p)| {
1905 p.to_display_point(&map).row()
1906 == selections[ix].head().row()
1907 })
1908 })
1909 .unwrap_or(false);
1910 let new_positions = selections
1911 .into_iter()
1912 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
1913 .collect();
1914 editor
1915 .change_list
1916 .push_to_change_list(pop_state, new_positions);
1917 }
1918 }
1919 _ => (),
1920 },
1921 ));
1922
1923 if let Some(dap_store) = this
1924 .project
1925 .as_ref()
1926 .map(|project| project.read(cx).dap_store())
1927 {
1928 let weak_editor = cx.weak_entity();
1929
1930 this._subscriptions
1931 .push(
1932 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
1933 let session_entity = cx.entity();
1934 weak_editor
1935 .update(cx, |editor, cx| {
1936 editor._subscriptions.push(
1937 cx.subscribe(&session_entity, Self::on_debug_session_event),
1938 );
1939 })
1940 .ok();
1941 }),
1942 );
1943
1944 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
1945 this._subscriptions
1946 .push(cx.subscribe(&session, Self::on_debug_session_event));
1947 }
1948 }
1949
1950 this.end_selection(window, cx);
1951 this.scroll_manager.show_scrollbars(window, cx);
1952 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1953
1954 if full_mode {
1955 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1956 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1957
1958 if this.git_blame_inline_enabled {
1959 this.start_git_blame_inline(false, window, cx);
1960 }
1961
1962 this.go_to_active_debug_line(window, cx);
1963
1964 if let Some(buffer) = buffer.read(cx).as_singleton() {
1965 if let Some(project) = this.project.as_ref() {
1966 let handle = project.update(cx, |project, cx| {
1967 project.register_buffer_with_language_servers(&buffer, cx)
1968 });
1969 this.registered_buffers
1970 .insert(buffer.read(cx).remote_id(), handle);
1971 }
1972 }
1973
1974 this.minimap = this.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
1975 }
1976
1977 this.report_editor_event("Editor Opened", None, cx);
1978 this
1979 }
1980
1981 pub fn deploy_mouse_context_menu(
1982 &mut self,
1983 position: gpui::Point<Pixels>,
1984 context_menu: Entity<ContextMenu>,
1985 window: &mut Window,
1986 cx: &mut Context<Self>,
1987 ) {
1988 self.mouse_context_menu = Some(MouseContextMenu::new(
1989 self,
1990 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
1991 context_menu,
1992 window,
1993 cx,
1994 ));
1995 }
1996
1997 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
1998 self.mouse_context_menu
1999 .as_ref()
2000 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2001 }
2002
2003 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2004 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2005 }
2006
2007 fn key_context_internal(
2008 &self,
2009 has_active_edit_prediction: bool,
2010 window: &Window,
2011 cx: &App,
2012 ) -> KeyContext {
2013 let mut key_context = KeyContext::new_with_defaults();
2014 key_context.add("Editor");
2015 let mode = match self.mode {
2016 EditorMode::SingleLine { .. } => "single_line",
2017 EditorMode::AutoHeight { .. } => "auto_height",
2018 EditorMode::Minimap { .. } => "minimap",
2019 EditorMode::Full { .. } => "full",
2020 };
2021
2022 if EditorSettings::jupyter_enabled(cx) {
2023 key_context.add("jupyter");
2024 }
2025
2026 key_context.set("mode", mode);
2027 if self.pending_rename.is_some() {
2028 key_context.add("renaming");
2029 }
2030
2031 match self.context_menu.borrow().as_ref() {
2032 Some(CodeContextMenu::Completions(_)) => {
2033 key_context.add("menu");
2034 key_context.add("showing_completions");
2035 }
2036 Some(CodeContextMenu::CodeActions(_)) => {
2037 key_context.add("menu");
2038 key_context.add("showing_code_actions")
2039 }
2040 None => {}
2041 }
2042
2043 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2044 if !self.focus_handle(cx).contains_focused(window, cx)
2045 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2046 {
2047 for addon in self.addons.values() {
2048 addon.extend_key_context(&mut key_context, cx)
2049 }
2050 }
2051
2052 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2053 if let Some(extension) = singleton_buffer
2054 .read(cx)
2055 .file()
2056 .and_then(|file| file.path().extension()?.to_str())
2057 {
2058 key_context.set("extension", extension.to_string());
2059 }
2060 } else {
2061 key_context.add("multibuffer");
2062 }
2063
2064 if has_active_edit_prediction {
2065 if self.edit_prediction_in_conflict() {
2066 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2067 } else {
2068 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2069 key_context.add("copilot_suggestion");
2070 }
2071 }
2072
2073 if self.selection_mark_mode {
2074 key_context.add("selection_mode");
2075 }
2076
2077 key_context
2078 }
2079
2080 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2081 self.mouse_cursor_hidden = match origin {
2082 HideMouseCursorOrigin::TypingAction => {
2083 matches!(
2084 self.hide_mouse_mode,
2085 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2086 )
2087 }
2088 HideMouseCursorOrigin::MovementAction => {
2089 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2090 }
2091 };
2092 }
2093
2094 pub fn edit_prediction_in_conflict(&self) -> bool {
2095 if !self.show_edit_predictions_in_menu() {
2096 return false;
2097 }
2098
2099 let showing_completions = self
2100 .context_menu
2101 .borrow()
2102 .as_ref()
2103 .map_or(false, |context| {
2104 matches!(context, CodeContextMenu::Completions(_))
2105 });
2106
2107 showing_completions
2108 || self.edit_prediction_requires_modifier()
2109 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2110 // bindings to insert tab characters.
2111 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2112 }
2113
2114 pub fn accept_edit_prediction_keybind(
2115 &self,
2116 window: &Window,
2117 cx: &App,
2118 ) -> AcceptEditPredictionBinding {
2119 let key_context = self.key_context_internal(true, window, cx);
2120 let in_conflict = self.edit_prediction_in_conflict();
2121
2122 AcceptEditPredictionBinding(
2123 window
2124 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2125 .into_iter()
2126 .filter(|binding| {
2127 !in_conflict
2128 || binding
2129 .keystrokes()
2130 .first()
2131 .map_or(false, |keystroke| keystroke.modifiers.modified())
2132 })
2133 .rev()
2134 .min_by_key(|binding| {
2135 binding
2136 .keystrokes()
2137 .first()
2138 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
2139 }),
2140 )
2141 }
2142
2143 pub fn new_file(
2144 workspace: &mut Workspace,
2145 _: &workspace::NewFile,
2146 window: &mut Window,
2147 cx: &mut Context<Workspace>,
2148 ) {
2149 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2150 "Failed to create buffer",
2151 window,
2152 cx,
2153 |e, _, _| match e.error_code() {
2154 ErrorCode::RemoteUpgradeRequired => Some(format!(
2155 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2156 e.error_tag("required").unwrap_or("the latest version")
2157 )),
2158 _ => None,
2159 },
2160 );
2161 }
2162
2163 pub fn new_in_workspace(
2164 workspace: &mut Workspace,
2165 window: &mut Window,
2166 cx: &mut Context<Workspace>,
2167 ) -> Task<Result<Entity<Editor>>> {
2168 let project = workspace.project().clone();
2169 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2170
2171 cx.spawn_in(window, async move |workspace, cx| {
2172 let buffer = create.await?;
2173 workspace.update_in(cx, |workspace, window, cx| {
2174 let editor =
2175 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2176 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2177 editor
2178 })
2179 })
2180 }
2181
2182 fn new_file_vertical(
2183 workspace: &mut Workspace,
2184 _: &workspace::NewFileSplitVertical,
2185 window: &mut Window,
2186 cx: &mut Context<Workspace>,
2187 ) {
2188 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2189 }
2190
2191 fn new_file_horizontal(
2192 workspace: &mut Workspace,
2193 _: &workspace::NewFileSplitHorizontal,
2194 window: &mut Window,
2195 cx: &mut Context<Workspace>,
2196 ) {
2197 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2198 }
2199
2200 fn new_file_in_direction(
2201 workspace: &mut Workspace,
2202 direction: SplitDirection,
2203 window: &mut Window,
2204 cx: &mut Context<Workspace>,
2205 ) {
2206 let project = workspace.project().clone();
2207 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2208
2209 cx.spawn_in(window, async move |workspace, cx| {
2210 let buffer = create.await?;
2211 workspace.update_in(cx, move |workspace, window, cx| {
2212 workspace.split_item(
2213 direction,
2214 Box::new(
2215 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2216 ),
2217 window,
2218 cx,
2219 )
2220 })?;
2221 anyhow::Ok(())
2222 })
2223 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2224 match e.error_code() {
2225 ErrorCode::RemoteUpgradeRequired => Some(format!(
2226 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2227 e.error_tag("required").unwrap_or("the latest version")
2228 )),
2229 _ => None,
2230 }
2231 });
2232 }
2233
2234 pub fn leader_id(&self) -> Option<CollaboratorId> {
2235 self.leader_id
2236 }
2237
2238 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2239 &self.buffer
2240 }
2241
2242 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2243 self.workspace.as_ref()?.0.upgrade()
2244 }
2245
2246 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2247 self.buffer().read(cx).title(cx)
2248 }
2249
2250 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2251 let git_blame_gutter_max_author_length = self
2252 .render_git_blame_gutter(cx)
2253 .then(|| {
2254 if let Some(blame) = self.blame.as_ref() {
2255 let max_author_length =
2256 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2257 Some(max_author_length)
2258 } else {
2259 None
2260 }
2261 })
2262 .flatten();
2263
2264 EditorSnapshot {
2265 mode: self.mode.clone(),
2266 show_gutter: self.show_gutter,
2267 show_line_numbers: self.show_line_numbers,
2268 show_git_diff_gutter: self.show_git_diff_gutter,
2269 show_runnables: self.show_runnables,
2270 show_breakpoints: self.show_breakpoints,
2271 git_blame_gutter_max_author_length,
2272 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2273 scroll_anchor: self.scroll_manager.anchor(),
2274 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2275 placeholder_text: self.placeholder_text.clone(),
2276 is_focused: self.focus_handle.is_focused(window),
2277 current_line_highlight: self
2278 .current_line_highlight
2279 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2280 gutter_hovered: self.gutter_hovered,
2281 }
2282 }
2283
2284 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2285 self.buffer.read(cx).language_at(point, cx)
2286 }
2287
2288 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2289 self.buffer.read(cx).read(cx).file_at(point).cloned()
2290 }
2291
2292 pub fn active_excerpt(
2293 &self,
2294 cx: &App,
2295 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2296 self.buffer
2297 .read(cx)
2298 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2299 }
2300
2301 pub fn mode(&self) -> &EditorMode {
2302 &self.mode
2303 }
2304
2305 pub fn set_mode(&mut self, mode: EditorMode) {
2306 self.mode = mode;
2307 }
2308
2309 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2310 self.collaboration_hub.as_deref()
2311 }
2312
2313 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2314 self.collaboration_hub = Some(hub);
2315 }
2316
2317 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2318 self.in_project_search = in_project_search;
2319 }
2320
2321 pub fn set_custom_context_menu(
2322 &mut self,
2323 f: impl 'static
2324 + Fn(
2325 &mut Self,
2326 DisplayPoint,
2327 &mut Window,
2328 &mut Context<Self>,
2329 ) -> Option<Entity<ui::ContextMenu>>,
2330 ) {
2331 self.custom_context_menu = Some(Box::new(f))
2332 }
2333
2334 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
2335 self.completion_provider = provider;
2336 }
2337
2338 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2339 self.semantics_provider.clone()
2340 }
2341
2342 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2343 self.semantics_provider = provider;
2344 }
2345
2346 pub fn set_edit_prediction_provider<T>(
2347 &mut self,
2348 provider: Option<Entity<T>>,
2349 window: &mut Window,
2350 cx: &mut Context<Self>,
2351 ) where
2352 T: EditPredictionProvider,
2353 {
2354 self.edit_prediction_provider =
2355 provider.map(|provider| RegisteredInlineCompletionProvider {
2356 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2357 if this.focus_handle.is_focused(window) {
2358 this.update_visible_inline_completion(window, cx);
2359 }
2360 }),
2361 provider: Arc::new(provider),
2362 });
2363 self.update_edit_prediction_settings(cx);
2364 self.refresh_inline_completion(false, false, window, cx);
2365 }
2366
2367 pub fn placeholder_text(&self) -> Option<&str> {
2368 self.placeholder_text.as_deref()
2369 }
2370
2371 pub fn set_placeholder_text(
2372 &mut self,
2373 placeholder_text: impl Into<Arc<str>>,
2374 cx: &mut Context<Self>,
2375 ) {
2376 let placeholder_text = Some(placeholder_text.into());
2377 if self.placeholder_text != placeholder_text {
2378 self.placeholder_text = placeholder_text;
2379 cx.notify();
2380 }
2381 }
2382
2383 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2384 self.cursor_shape = cursor_shape;
2385
2386 // Disrupt blink for immediate user feedback that the cursor shape has changed
2387 self.blink_manager.update(cx, BlinkManager::show_cursor);
2388
2389 cx.notify();
2390 }
2391
2392 pub fn set_current_line_highlight(
2393 &mut self,
2394 current_line_highlight: Option<CurrentLineHighlight>,
2395 ) {
2396 self.current_line_highlight = current_line_highlight;
2397 }
2398
2399 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2400 self.collapse_matches = collapse_matches;
2401 }
2402
2403 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2404 let buffers = self.buffer.read(cx).all_buffers();
2405 let Some(project) = self.project.as_ref() else {
2406 return;
2407 };
2408 project.update(cx, |project, cx| {
2409 for buffer in buffers {
2410 self.registered_buffers
2411 .entry(buffer.read(cx).remote_id())
2412 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2413 }
2414 })
2415 }
2416
2417 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2418 if self.collapse_matches {
2419 return range.start..range.start;
2420 }
2421 range.clone()
2422 }
2423
2424 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2425 if self.display_map.read(cx).clip_at_line_ends != clip {
2426 self.display_map
2427 .update(cx, |map, _| map.clip_at_line_ends = clip);
2428 }
2429 }
2430
2431 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2432 self.input_enabled = input_enabled;
2433 }
2434
2435 pub fn set_inline_completions_hidden_for_vim_mode(
2436 &mut self,
2437 hidden: bool,
2438 window: &mut Window,
2439 cx: &mut Context<Self>,
2440 ) {
2441 if hidden != self.inline_completions_hidden_for_vim_mode {
2442 self.inline_completions_hidden_for_vim_mode = hidden;
2443 if hidden {
2444 self.update_visible_inline_completion(window, cx);
2445 } else {
2446 self.refresh_inline_completion(true, false, window, cx);
2447 }
2448 }
2449 }
2450
2451 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2452 self.menu_inline_completions_policy = value;
2453 }
2454
2455 pub fn set_autoindent(&mut self, autoindent: bool) {
2456 if autoindent {
2457 self.autoindent_mode = Some(AutoindentMode::EachLine);
2458 } else {
2459 self.autoindent_mode = None;
2460 }
2461 }
2462
2463 pub fn read_only(&self, cx: &App) -> bool {
2464 self.read_only || self.buffer.read(cx).read_only()
2465 }
2466
2467 pub fn set_read_only(&mut self, read_only: bool) {
2468 self.read_only = read_only;
2469 }
2470
2471 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2472 self.use_autoclose = autoclose;
2473 }
2474
2475 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2476 self.use_auto_surround = auto_surround;
2477 }
2478
2479 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2480 self.auto_replace_emoji_shortcode = auto_replace;
2481 }
2482
2483 pub fn toggle_edit_predictions(
2484 &mut self,
2485 _: &ToggleEditPrediction,
2486 window: &mut Window,
2487 cx: &mut Context<Self>,
2488 ) {
2489 if self.show_inline_completions_override.is_some() {
2490 self.set_show_edit_predictions(None, window, cx);
2491 } else {
2492 let show_edit_predictions = !self.edit_predictions_enabled();
2493 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2494 }
2495 }
2496
2497 pub fn set_show_edit_predictions(
2498 &mut self,
2499 show_edit_predictions: Option<bool>,
2500 window: &mut Window,
2501 cx: &mut Context<Self>,
2502 ) {
2503 self.show_inline_completions_override = show_edit_predictions;
2504 self.update_edit_prediction_settings(cx);
2505
2506 if let Some(false) = show_edit_predictions {
2507 self.discard_inline_completion(false, cx);
2508 } else {
2509 self.refresh_inline_completion(false, true, window, cx);
2510 }
2511 }
2512
2513 fn inline_completions_disabled_in_scope(
2514 &self,
2515 buffer: &Entity<Buffer>,
2516 buffer_position: language::Anchor,
2517 cx: &App,
2518 ) -> bool {
2519 let snapshot = buffer.read(cx).snapshot();
2520 let settings = snapshot.settings_at(buffer_position, cx);
2521
2522 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2523 return false;
2524 };
2525
2526 scope.override_name().map_or(false, |scope_name| {
2527 settings
2528 .edit_predictions_disabled_in
2529 .iter()
2530 .any(|s| s == scope_name)
2531 })
2532 }
2533
2534 pub fn set_use_modal_editing(&mut self, to: bool) {
2535 self.use_modal_editing = to;
2536 }
2537
2538 pub fn use_modal_editing(&self) -> bool {
2539 self.use_modal_editing
2540 }
2541
2542 fn selections_did_change(
2543 &mut self,
2544 local: bool,
2545 old_cursor_position: &Anchor,
2546 show_completions: bool,
2547 window: &mut Window,
2548 cx: &mut Context<Self>,
2549 ) {
2550 window.invalidate_character_coordinates();
2551
2552 // Copy selections to primary selection buffer
2553 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2554 if local {
2555 let selections = self.selections.all::<usize>(cx);
2556 let buffer_handle = self.buffer.read(cx).read(cx);
2557
2558 let mut text = String::new();
2559 for (index, selection) in selections.iter().enumerate() {
2560 let text_for_selection = buffer_handle
2561 .text_for_range(selection.start..selection.end)
2562 .collect::<String>();
2563
2564 text.push_str(&text_for_selection);
2565 if index != selections.len() - 1 {
2566 text.push('\n');
2567 }
2568 }
2569
2570 if !text.is_empty() {
2571 cx.write_to_primary(ClipboardItem::new_string(text));
2572 }
2573 }
2574
2575 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2576 self.buffer.update(cx, |buffer, cx| {
2577 buffer.set_active_selections(
2578 &self.selections.disjoint_anchors(),
2579 self.selections.line_mode,
2580 self.cursor_shape,
2581 cx,
2582 )
2583 });
2584 }
2585 let display_map = self
2586 .display_map
2587 .update(cx, |display_map, cx| display_map.snapshot(cx));
2588 let buffer = &display_map.buffer_snapshot;
2589 self.add_selections_state = None;
2590 self.select_next_state = None;
2591 self.select_prev_state = None;
2592 self.select_syntax_node_history.try_clear();
2593 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2594 self.snippet_stack
2595 .invalidate(&self.selections.disjoint_anchors(), buffer);
2596 self.take_rename(false, window, cx);
2597
2598 let new_cursor_position = self.selections.newest_anchor().head();
2599
2600 self.push_to_nav_history(
2601 *old_cursor_position,
2602 Some(new_cursor_position.to_point(buffer)),
2603 false,
2604 cx,
2605 );
2606
2607 if local {
2608 let new_cursor_position = self.selections.newest_anchor().head();
2609 let mut context_menu = self.context_menu.borrow_mut();
2610 let completion_menu = match context_menu.as_ref() {
2611 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2612 _ => {
2613 *context_menu = None;
2614 None
2615 }
2616 };
2617 if let Some(buffer_id) = new_cursor_position.buffer_id {
2618 if !self.registered_buffers.contains_key(&buffer_id) {
2619 if let Some(project) = self.project.as_ref() {
2620 project.update(cx, |project, cx| {
2621 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2622 return;
2623 };
2624 self.registered_buffers.insert(
2625 buffer_id,
2626 project.register_buffer_with_language_servers(&buffer, cx),
2627 );
2628 })
2629 }
2630 }
2631 }
2632
2633 if let Some(completion_menu) = completion_menu {
2634 let cursor_position = new_cursor_position.to_offset(buffer);
2635 let (word_range, kind) =
2636 buffer.surrounding_word(completion_menu.initial_position, true);
2637 if kind == Some(CharKind::Word)
2638 && word_range.to_inclusive().contains(&cursor_position)
2639 {
2640 let mut completion_menu = completion_menu.clone();
2641 drop(context_menu);
2642
2643 let query = Self::completion_query(buffer, cursor_position);
2644 cx.spawn(async move |this, cx| {
2645 completion_menu
2646 .filter(query.as_deref(), cx.background_executor().clone())
2647 .await;
2648
2649 this.update(cx, |this, cx| {
2650 let mut context_menu = this.context_menu.borrow_mut();
2651 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2652 else {
2653 return;
2654 };
2655
2656 if menu.id > completion_menu.id {
2657 return;
2658 }
2659
2660 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2661 drop(context_menu);
2662 cx.notify();
2663 })
2664 })
2665 .detach();
2666
2667 if show_completions {
2668 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2669 }
2670 } else {
2671 drop(context_menu);
2672 self.hide_context_menu(window, cx);
2673 }
2674 } else {
2675 drop(context_menu);
2676 }
2677
2678 hide_hover(self, cx);
2679
2680 if old_cursor_position.to_display_point(&display_map).row()
2681 != new_cursor_position.to_display_point(&display_map).row()
2682 {
2683 self.available_code_actions.take();
2684 }
2685 self.refresh_code_actions(window, cx);
2686 self.refresh_document_highlights(cx);
2687 self.refresh_selected_text_highlights(false, window, cx);
2688 refresh_matching_bracket_highlights(self, window, cx);
2689 self.update_visible_inline_completion(window, cx);
2690 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2691 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2692 self.inline_blame_popover.take();
2693 if self.git_blame_inline_enabled {
2694 self.start_inline_blame_timer(window, cx);
2695 }
2696 }
2697
2698 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2699 cx.emit(EditorEvent::SelectionsChanged { local });
2700
2701 let selections = &self.selections.disjoint;
2702 if selections.len() == 1 {
2703 cx.emit(SearchEvent::ActiveMatchChanged)
2704 }
2705 if local {
2706 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2707 let inmemory_selections = selections
2708 .iter()
2709 .map(|s| {
2710 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2711 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2712 })
2713 .collect();
2714 self.update_restoration_data(cx, |data| {
2715 data.selections = inmemory_selections;
2716 });
2717
2718 if WorkspaceSettings::get(None, cx).restore_on_startup
2719 != RestoreOnStartupBehavior::None
2720 {
2721 if let Some(workspace_id) =
2722 self.workspace.as_ref().and_then(|workspace| workspace.1)
2723 {
2724 let snapshot = self.buffer().read(cx).snapshot(cx);
2725 let selections = selections.clone();
2726 let background_executor = cx.background_executor().clone();
2727 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2728 self.serialize_selections = cx.background_spawn(async move {
2729 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2730 let db_selections = selections
2731 .iter()
2732 .map(|selection| {
2733 (
2734 selection.start.to_offset(&snapshot),
2735 selection.end.to_offset(&snapshot),
2736 )
2737 })
2738 .collect();
2739
2740 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2741 .await
2742 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2743 .log_err();
2744 });
2745 }
2746 }
2747 }
2748 }
2749
2750 cx.notify();
2751 }
2752
2753 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2754 use text::ToOffset as _;
2755 use text::ToPoint as _;
2756
2757 if self.mode.is_minimap()
2758 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2759 {
2760 return;
2761 }
2762
2763 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2764 return;
2765 };
2766
2767 let snapshot = singleton.read(cx).snapshot();
2768 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2769 let display_snapshot = display_map.snapshot(cx);
2770
2771 display_snapshot
2772 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2773 .map(|fold| {
2774 fold.range.start.text_anchor.to_point(&snapshot)
2775 ..fold.range.end.text_anchor.to_point(&snapshot)
2776 })
2777 .collect()
2778 });
2779 self.update_restoration_data(cx, |data| {
2780 data.folds = inmemory_folds;
2781 });
2782
2783 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2784 return;
2785 };
2786 let background_executor = cx.background_executor().clone();
2787 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2788 let db_folds = self.display_map.update(cx, |display_map, cx| {
2789 display_map
2790 .snapshot(cx)
2791 .folds_in_range(0..snapshot.len())
2792 .map(|fold| {
2793 (
2794 fold.range.start.text_anchor.to_offset(&snapshot),
2795 fold.range.end.text_anchor.to_offset(&snapshot),
2796 )
2797 })
2798 .collect()
2799 });
2800 self.serialize_folds = cx.background_spawn(async move {
2801 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2802 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2803 .await
2804 .with_context(|| {
2805 format!(
2806 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2807 )
2808 })
2809 .log_err();
2810 });
2811 }
2812
2813 pub fn sync_selections(
2814 &mut self,
2815 other: Entity<Editor>,
2816 cx: &mut Context<Self>,
2817 ) -> gpui::Subscription {
2818 let other_selections = other.read(cx).selections.disjoint.to_vec();
2819 self.selections.change_with(cx, |selections| {
2820 selections.select_anchors(other_selections);
2821 });
2822
2823 let other_subscription =
2824 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2825 EditorEvent::SelectionsChanged { local: true } => {
2826 let other_selections = other.read(cx).selections.disjoint.to_vec();
2827 if other_selections.is_empty() {
2828 return;
2829 }
2830 this.selections.change_with(cx, |selections| {
2831 selections.select_anchors(other_selections);
2832 });
2833 }
2834 _ => {}
2835 });
2836
2837 let this_subscription =
2838 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2839 EditorEvent::SelectionsChanged { local: true } => {
2840 let these_selections = this.selections.disjoint.to_vec();
2841 if these_selections.is_empty() {
2842 return;
2843 }
2844 other.update(cx, |other_editor, cx| {
2845 other_editor.selections.change_with(cx, |selections| {
2846 selections.select_anchors(these_selections);
2847 })
2848 });
2849 }
2850 _ => {}
2851 });
2852
2853 Subscription::join(other_subscription, this_subscription)
2854 }
2855
2856 pub fn change_selections<R>(
2857 &mut self,
2858 autoscroll: Option<Autoscroll>,
2859 window: &mut Window,
2860 cx: &mut Context<Self>,
2861 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2862 ) -> R {
2863 self.change_selections_inner(autoscroll, true, window, cx, change)
2864 }
2865
2866 fn change_selections_inner<R>(
2867 &mut self,
2868 autoscroll: Option<Autoscroll>,
2869 request_completions: bool,
2870 window: &mut Window,
2871 cx: &mut Context<Self>,
2872 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2873 ) -> R {
2874 let old_cursor_position = self.selections.newest_anchor().head();
2875 self.push_to_selection_history();
2876
2877 let (changed, result) = self.selections.change_with(cx, change);
2878
2879 if changed {
2880 if let Some(autoscroll) = autoscroll {
2881 self.request_autoscroll(autoscroll, cx);
2882 }
2883 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2884
2885 if self.should_open_signature_help_automatically(
2886 &old_cursor_position,
2887 self.signature_help_state.backspace_pressed(),
2888 cx,
2889 ) {
2890 self.show_signature_help(&ShowSignatureHelp, window, cx);
2891 }
2892 self.signature_help_state.set_backspace_pressed(false);
2893 }
2894
2895 result
2896 }
2897
2898 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2899 where
2900 I: IntoIterator<Item = (Range<S>, T)>,
2901 S: ToOffset,
2902 T: Into<Arc<str>>,
2903 {
2904 if self.read_only(cx) {
2905 return;
2906 }
2907
2908 self.buffer
2909 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2910 }
2911
2912 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2913 where
2914 I: IntoIterator<Item = (Range<S>, T)>,
2915 S: ToOffset,
2916 T: Into<Arc<str>>,
2917 {
2918 if self.read_only(cx) {
2919 return;
2920 }
2921
2922 self.buffer.update(cx, |buffer, cx| {
2923 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2924 });
2925 }
2926
2927 pub fn edit_with_block_indent<I, S, T>(
2928 &mut self,
2929 edits: I,
2930 original_indent_columns: Vec<Option<u32>>,
2931 cx: &mut Context<Self>,
2932 ) where
2933 I: IntoIterator<Item = (Range<S>, T)>,
2934 S: ToOffset,
2935 T: Into<Arc<str>>,
2936 {
2937 if self.read_only(cx) {
2938 return;
2939 }
2940
2941 self.buffer.update(cx, |buffer, cx| {
2942 buffer.edit(
2943 edits,
2944 Some(AutoindentMode::Block {
2945 original_indent_columns,
2946 }),
2947 cx,
2948 )
2949 });
2950 }
2951
2952 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2953 self.hide_context_menu(window, cx);
2954
2955 match phase {
2956 SelectPhase::Begin {
2957 position,
2958 add,
2959 click_count,
2960 } => self.begin_selection(position, add, click_count, window, cx),
2961 SelectPhase::BeginColumnar {
2962 position,
2963 goal_column,
2964 reset,
2965 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
2966 SelectPhase::Extend {
2967 position,
2968 click_count,
2969 } => self.extend_selection(position, click_count, window, cx),
2970 SelectPhase::Update {
2971 position,
2972 goal_column,
2973 scroll_delta,
2974 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
2975 SelectPhase::End => self.end_selection(window, cx),
2976 }
2977 }
2978
2979 fn extend_selection(
2980 &mut self,
2981 position: DisplayPoint,
2982 click_count: usize,
2983 window: &mut Window,
2984 cx: &mut Context<Self>,
2985 ) {
2986 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2987 let tail = self.selections.newest::<usize>(cx).tail();
2988 self.begin_selection(position, false, click_count, window, cx);
2989
2990 let position = position.to_offset(&display_map, Bias::Left);
2991 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
2992
2993 let mut pending_selection = self
2994 .selections
2995 .pending_anchor()
2996 .expect("extend_selection not called with pending selection");
2997 if position >= tail {
2998 pending_selection.start = tail_anchor;
2999 } else {
3000 pending_selection.end = tail_anchor;
3001 pending_selection.reversed = true;
3002 }
3003
3004 let mut pending_mode = self.selections.pending_mode().unwrap();
3005 match &mut pending_mode {
3006 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3007 _ => {}
3008 }
3009
3010 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3011
3012 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3013 s.set_pending(pending_selection, pending_mode)
3014 });
3015 }
3016
3017 fn begin_selection(
3018 &mut self,
3019 position: DisplayPoint,
3020 add: bool,
3021 click_count: usize,
3022 window: &mut Window,
3023 cx: &mut Context<Self>,
3024 ) {
3025 if !self.focus_handle.is_focused(window) {
3026 self.last_focused_descendant = None;
3027 window.focus(&self.focus_handle);
3028 }
3029
3030 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3031 let buffer = &display_map.buffer_snapshot;
3032 let position = display_map.clip_point(position, Bias::Left);
3033
3034 let start;
3035 let end;
3036 let mode;
3037 let mut auto_scroll;
3038 match click_count {
3039 1 => {
3040 start = buffer.anchor_before(position.to_point(&display_map));
3041 end = start;
3042 mode = SelectMode::Character;
3043 auto_scroll = true;
3044 }
3045 2 => {
3046 let range = movement::surrounding_word(&display_map, position);
3047 start = buffer.anchor_before(range.start.to_point(&display_map));
3048 end = buffer.anchor_before(range.end.to_point(&display_map));
3049 mode = SelectMode::Word(start..end);
3050 auto_scroll = true;
3051 }
3052 3 => {
3053 let position = display_map
3054 .clip_point(position, Bias::Left)
3055 .to_point(&display_map);
3056 let line_start = display_map.prev_line_boundary(position).0;
3057 let next_line_start = buffer.clip_point(
3058 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3059 Bias::Left,
3060 );
3061 start = buffer.anchor_before(line_start);
3062 end = buffer.anchor_before(next_line_start);
3063 mode = SelectMode::Line(start..end);
3064 auto_scroll = true;
3065 }
3066 _ => {
3067 start = buffer.anchor_before(0);
3068 end = buffer.anchor_before(buffer.len());
3069 mode = SelectMode::All;
3070 auto_scroll = false;
3071 }
3072 }
3073 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3074
3075 let point_to_delete: Option<usize> = {
3076 let selected_points: Vec<Selection<Point>> =
3077 self.selections.disjoint_in_range(start..end, cx);
3078
3079 if !add || click_count > 1 {
3080 None
3081 } else if !selected_points.is_empty() {
3082 Some(selected_points[0].id)
3083 } else {
3084 let clicked_point_already_selected =
3085 self.selections.disjoint.iter().find(|selection| {
3086 selection.start.to_point(buffer) == start.to_point(buffer)
3087 || selection.end.to_point(buffer) == end.to_point(buffer)
3088 });
3089
3090 clicked_point_already_selected.map(|selection| selection.id)
3091 }
3092 };
3093
3094 let selections_count = self.selections.count();
3095
3096 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3097 if let Some(point_to_delete) = point_to_delete {
3098 s.delete(point_to_delete);
3099
3100 if selections_count == 1 {
3101 s.set_pending_anchor_range(start..end, mode);
3102 }
3103 } else {
3104 if !add {
3105 s.clear_disjoint();
3106 }
3107
3108 s.set_pending_anchor_range(start..end, mode);
3109 }
3110 });
3111 }
3112
3113 fn begin_columnar_selection(
3114 &mut self,
3115 position: DisplayPoint,
3116 goal_column: u32,
3117 reset: bool,
3118 window: &mut Window,
3119 cx: &mut Context<Self>,
3120 ) {
3121 if !self.focus_handle.is_focused(window) {
3122 self.last_focused_descendant = None;
3123 window.focus(&self.focus_handle);
3124 }
3125
3126 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3127
3128 if reset {
3129 let pointer_position = display_map
3130 .buffer_snapshot
3131 .anchor_before(position.to_point(&display_map));
3132
3133 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3134 s.clear_disjoint();
3135 s.set_pending_anchor_range(
3136 pointer_position..pointer_position,
3137 SelectMode::Character,
3138 );
3139 });
3140 }
3141
3142 let tail = self.selections.newest::<Point>(cx).tail();
3143 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3144
3145 if !reset {
3146 self.select_columns(
3147 tail.to_display_point(&display_map),
3148 position,
3149 goal_column,
3150 &display_map,
3151 window,
3152 cx,
3153 );
3154 }
3155 }
3156
3157 fn update_selection(
3158 &mut self,
3159 position: DisplayPoint,
3160 goal_column: u32,
3161 scroll_delta: gpui::Point<f32>,
3162 window: &mut Window,
3163 cx: &mut Context<Self>,
3164 ) {
3165 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3166
3167 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3168 let tail = tail.to_display_point(&display_map);
3169 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3170 } else if let Some(mut pending) = self.selections.pending_anchor() {
3171 let buffer = self.buffer.read(cx).snapshot(cx);
3172 let head;
3173 let tail;
3174 let mode = self.selections.pending_mode().unwrap();
3175 match &mode {
3176 SelectMode::Character => {
3177 head = position.to_point(&display_map);
3178 tail = pending.tail().to_point(&buffer);
3179 }
3180 SelectMode::Word(original_range) => {
3181 let original_display_range = original_range.start.to_display_point(&display_map)
3182 ..original_range.end.to_display_point(&display_map);
3183 let original_buffer_range = original_display_range.start.to_point(&display_map)
3184 ..original_display_range.end.to_point(&display_map);
3185 if movement::is_inside_word(&display_map, position)
3186 || original_display_range.contains(&position)
3187 {
3188 let word_range = movement::surrounding_word(&display_map, position);
3189 if word_range.start < original_display_range.start {
3190 head = word_range.start.to_point(&display_map);
3191 } else {
3192 head = word_range.end.to_point(&display_map);
3193 }
3194 } else {
3195 head = position.to_point(&display_map);
3196 }
3197
3198 if head <= original_buffer_range.start {
3199 tail = original_buffer_range.end;
3200 } else {
3201 tail = original_buffer_range.start;
3202 }
3203 }
3204 SelectMode::Line(original_range) => {
3205 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3206
3207 let position = display_map
3208 .clip_point(position, Bias::Left)
3209 .to_point(&display_map);
3210 let line_start = display_map.prev_line_boundary(position).0;
3211 let next_line_start = buffer.clip_point(
3212 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3213 Bias::Left,
3214 );
3215
3216 if line_start < original_range.start {
3217 head = line_start
3218 } else {
3219 head = next_line_start
3220 }
3221
3222 if head <= original_range.start {
3223 tail = original_range.end;
3224 } else {
3225 tail = original_range.start;
3226 }
3227 }
3228 SelectMode::All => {
3229 return;
3230 }
3231 };
3232
3233 if head < tail {
3234 pending.start = buffer.anchor_before(head);
3235 pending.end = buffer.anchor_before(tail);
3236 pending.reversed = true;
3237 } else {
3238 pending.start = buffer.anchor_before(tail);
3239 pending.end = buffer.anchor_before(head);
3240 pending.reversed = false;
3241 }
3242
3243 self.change_selections(None, window, cx, |s| {
3244 s.set_pending(pending, mode);
3245 });
3246 } else {
3247 log::error!("update_selection dispatched with no pending selection");
3248 return;
3249 }
3250
3251 self.apply_scroll_delta(scroll_delta, window, cx);
3252 cx.notify();
3253 }
3254
3255 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3256 self.columnar_selection_tail.take();
3257 if self.selections.pending_anchor().is_some() {
3258 let selections = self.selections.all::<usize>(cx);
3259 self.change_selections(None, window, cx, |s| {
3260 s.select(selections);
3261 s.clear_pending();
3262 });
3263 }
3264 }
3265
3266 fn select_columns(
3267 &mut self,
3268 tail: DisplayPoint,
3269 head: DisplayPoint,
3270 goal_column: u32,
3271 display_map: &DisplaySnapshot,
3272 window: &mut Window,
3273 cx: &mut Context<Self>,
3274 ) {
3275 let start_row = cmp::min(tail.row(), head.row());
3276 let end_row = cmp::max(tail.row(), head.row());
3277 let start_column = cmp::min(tail.column(), goal_column);
3278 let end_column = cmp::max(tail.column(), goal_column);
3279 let reversed = start_column < tail.column();
3280
3281 let selection_ranges = (start_row.0..=end_row.0)
3282 .map(DisplayRow)
3283 .filter_map(|row| {
3284 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
3285 let start = display_map
3286 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3287 .to_point(display_map);
3288 let end = display_map
3289 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3290 .to_point(display_map);
3291 if reversed {
3292 Some(end..start)
3293 } else {
3294 Some(start..end)
3295 }
3296 } else {
3297 None
3298 }
3299 })
3300 .collect::<Vec<_>>();
3301
3302 self.change_selections(None, window, cx, |s| {
3303 s.select_ranges(selection_ranges);
3304 });
3305 cx.notify();
3306 }
3307
3308 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3309 self.selections
3310 .all_adjusted(cx)
3311 .iter()
3312 .any(|selection| !selection.is_empty())
3313 }
3314
3315 pub fn has_pending_nonempty_selection(&self) -> bool {
3316 let pending_nonempty_selection = match self.selections.pending_anchor() {
3317 Some(Selection { start, end, .. }) => start != end,
3318 None => false,
3319 };
3320
3321 pending_nonempty_selection
3322 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3323 }
3324
3325 pub fn has_pending_selection(&self) -> bool {
3326 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3327 }
3328
3329 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3330 self.selection_mark_mode = false;
3331
3332 if self.clear_expanded_diff_hunks(cx) {
3333 cx.notify();
3334 return;
3335 }
3336 if self.dismiss_menus_and_popups(true, window, cx) {
3337 return;
3338 }
3339
3340 if self.mode.is_full()
3341 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3342 {
3343 return;
3344 }
3345
3346 cx.propagate();
3347 }
3348
3349 pub fn dismiss_menus_and_popups(
3350 &mut self,
3351 is_user_requested: bool,
3352 window: &mut Window,
3353 cx: &mut Context<Self>,
3354 ) -> bool {
3355 if self.take_rename(false, window, cx).is_some() {
3356 return true;
3357 }
3358
3359 if hide_hover(self, cx) {
3360 return true;
3361 }
3362
3363 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3364 return true;
3365 }
3366
3367 if self.hide_context_menu(window, cx).is_some() {
3368 return true;
3369 }
3370
3371 if self.mouse_context_menu.take().is_some() {
3372 return true;
3373 }
3374
3375 if is_user_requested && self.discard_inline_completion(true, cx) {
3376 return true;
3377 }
3378
3379 if self.snippet_stack.pop().is_some() {
3380 return true;
3381 }
3382
3383 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3384 self.dismiss_diagnostics(cx);
3385 return true;
3386 }
3387
3388 false
3389 }
3390
3391 fn linked_editing_ranges_for(
3392 &self,
3393 selection: Range<text::Anchor>,
3394 cx: &App,
3395 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3396 if self.linked_edit_ranges.is_empty() {
3397 return None;
3398 }
3399 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3400 selection.end.buffer_id.and_then(|end_buffer_id| {
3401 if selection.start.buffer_id != Some(end_buffer_id) {
3402 return None;
3403 }
3404 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3405 let snapshot = buffer.read(cx).snapshot();
3406 self.linked_edit_ranges
3407 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3408 .map(|ranges| (ranges, snapshot, buffer))
3409 })?;
3410 use text::ToOffset as TO;
3411 // find offset from the start of current range to current cursor position
3412 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3413
3414 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3415 let start_difference = start_offset - start_byte_offset;
3416 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3417 let end_difference = end_offset - start_byte_offset;
3418 // Current range has associated linked ranges.
3419 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3420 for range in linked_ranges.iter() {
3421 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3422 let end_offset = start_offset + end_difference;
3423 let start_offset = start_offset + start_difference;
3424 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3425 continue;
3426 }
3427 if self.selections.disjoint_anchor_ranges().any(|s| {
3428 if s.start.buffer_id != selection.start.buffer_id
3429 || s.end.buffer_id != selection.end.buffer_id
3430 {
3431 return false;
3432 }
3433 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3434 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3435 }) {
3436 continue;
3437 }
3438 let start = buffer_snapshot.anchor_after(start_offset);
3439 let end = buffer_snapshot.anchor_after(end_offset);
3440 linked_edits
3441 .entry(buffer.clone())
3442 .or_default()
3443 .push(start..end);
3444 }
3445 Some(linked_edits)
3446 }
3447
3448 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3449 let text: Arc<str> = text.into();
3450
3451 if self.read_only(cx) {
3452 return;
3453 }
3454
3455 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3456
3457 let selections = self.selections.all_adjusted(cx);
3458 let mut bracket_inserted = false;
3459 let mut edits = Vec::new();
3460 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3461 let mut new_selections = Vec::with_capacity(selections.len());
3462 let mut new_autoclose_regions = Vec::new();
3463 let snapshot = self.buffer.read(cx).read(cx);
3464 let mut clear_linked_edit_ranges = false;
3465
3466 for (selection, autoclose_region) in
3467 self.selections_with_autoclose_regions(selections, &snapshot)
3468 {
3469 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3470 // Determine if the inserted text matches the opening or closing
3471 // bracket of any of this language's bracket pairs.
3472 let mut bracket_pair = None;
3473 let mut is_bracket_pair_start = false;
3474 let mut is_bracket_pair_end = false;
3475 if !text.is_empty() {
3476 let mut bracket_pair_matching_end = None;
3477 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3478 // and they are removing the character that triggered IME popup.
3479 for (pair, enabled) in scope.brackets() {
3480 if !pair.close && !pair.surround {
3481 continue;
3482 }
3483
3484 if enabled && pair.start.ends_with(text.as_ref()) {
3485 let prefix_len = pair.start.len() - text.len();
3486 let preceding_text_matches_prefix = prefix_len == 0
3487 || (selection.start.column >= (prefix_len as u32)
3488 && snapshot.contains_str_at(
3489 Point::new(
3490 selection.start.row,
3491 selection.start.column - (prefix_len as u32),
3492 ),
3493 &pair.start[..prefix_len],
3494 ));
3495 if preceding_text_matches_prefix {
3496 bracket_pair = Some(pair.clone());
3497 is_bracket_pair_start = true;
3498 break;
3499 }
3500 }
3501 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3502 {
3503 // take first bracket pair matching end, but don't break in case a later bracket
3504 // pair matches start
3505 bracket_pair_matching_end = Some(pair.clone());
3506 }
3507 }
3508 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3509 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3510 is_bracket_pair_end = true;
3511 }
3512 }
3513
3514 if let Some(bracket_pair) = bracket_pair {
3515 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3516 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3517 let auto_surround =
3518 self.use_auto_surround && snapshot_settings.use_auto_surround;
3519 if selection.is_empty() {
3520 if is_bracket_pair_start {
3521 // If the inserted text is a suffix of an opening bracket and the
3522 // selection is preceded by the rest of the opening bracket, then
3523 // insert the closing bracket.
3524 let following_text_allows_autoclose = snapshot
3525 .chars_at(selection.start)
3526 .next()
3527 .map_or(true, |c| scope.should_autoclose_before(c));
3528
3529 let preceding_text_allows_autoclose = selection.start.column == 0
3530 || snapshot.reversed_chars_at(selection.start).next().map_or(
3531 true,
3532 |c| {
3533 bracket_pair.start != bracket_pair.end
3534 || !snapshot
3535 .char_classifier_at(selection.start)
3536 .is_word(c)
3537 },
3538 );
3539
3540 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3541 && bracket_pair.start.len() == 1
3542 {
3543 let target = bracket_pair.start.chars().next().unwrap();
3544 let current_line_count = snapshot
3545 .reversed_chars_at(selection.start)
3546 .take_while(|&c| c != '\n')
3547 .filter(|&c| c == target)
3548 .count();
3549 current_line_count % 2 == 1
3550 } else {
3551 false
3552 };
3553
3554 if autoclose
3555 && bracket_pair.close
3556 && following_text_allows_autoclose
3557 && preceding_text_allows_autoclose
3558 && !is_closing_quote
3559 {
3560 let anchor = snapshot.anchor_before(selection.end);
3561 new_selections.push((selection.map(|_| anchor), text.len()));
3562 new_autoclose_regions.push((
3563 anchor,
3564 text.len(),
3565 selection.id,
3566 bracket_pair.clone(),
3567 ));
3568 edits.push((
3569 selection.range(),
3570 format!("{}{}", text, bracket_pair.end).into(),
3571 ));
3572 bracket_inserted = true;
3573 continue;
3574 }
3575 }
3576
3577 if let Some(region) = autoclose_region {
3578 // If the selection is followed by an auto-inserted closing bracket,
3579 // then don't insert that closing bracket again; just move the selection
3580 // past the closing bracket.
3581 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3582 && text.as_ref() == region.pair.end.as_str();
3583 if should_skip {
3584 let anchor = snapshot.anchor_after(selection.end);
3585 new_selections
3586 .push((selection.map(|_| anchor), region.pair.end.len()));
3587 continue;
3588 }
3589 }
3590
3591 let always_treat_brackets_as_autoclosed = snapshot
3592 .language_settings_at(selection.start, cx)
3593 .always_treat_brackets_as_autoclosed;
3594 if always_treat_brackets_as_autoclosed
3595 && is_bracket_pair_end
3596 && snapshot.contains_str_at(selection.end, text.as_ref())
3597 {
3598 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3599 // and the inserted text is a closing bracket and the selection is followed
3600 // by the closing bracket then move the selection past the closing bracket.
3601 let anchor = snapshot.anchor_after(selection.end);
3602 new_selections.push((selection.map(|_| anchor), text.len()));
3603 continue;
3604 }
3605 }
3606 // If an opening bracket is 1 character long and is typed while
3607 // text is selected, then surround that text with the bracket pair.
3608 else if auto_surround
3609 && bracket_pair.surround
3610 && is_bracket_pair_start
3611 && bracket_pair.start.chars().count() == 1
3612 {
3613 edits.push((selection.start..selection.start, text.clone()));
3614 edits.push((
3615 selection.end..selection.end,
3616 bracket_pair.end.as_str().into(),
3617 ));
3618 bracket_inserted = true;
3619 new_selections.push((
3620 Selection {
3621 id: selection.id,
3622 start: snapshot.anchor_after(selection.start),
3623 end: snapshot.anchor_before(selection.end),
3624 reversed: selection.reversed,
3625 goal: selection.goal,
3626 },
3627 0,
3628 ));
3629 continue;
3630 }
3631 }
3632 }
3633
3634 if self.auto_replace_emoji_shortcode
3635 && selection.is_empty()
3636 && text.as_ref().ends_with(':')
3637 {
3638 if let Some(possible_emoji_short_code) =
3639 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3640 {
3641 if !possible_emoji_short_code.is_empty() {
3642 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3643 let emoji_shortcode_start = Point::new(
3644 selection.start.row,
3645 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3646 );
3647
3648 // Remove shortcode from buffer
3649 edits.push((
3650 emoji_shortcode_start..selection.start,
3651 "".to_string().into(),
3652 ));
3653 new_selections.push((
3654 Selection {
3655 id: selection.id,
3656 start: snapshot.anchor_after(emoji_shortcode_start),
3657 end: snapshot.anchor_before(selection.start),
3658 reversed: selection.reversed,
3659 goal: selection.goal,
3660 },
3661 0,
3662 ));
3663
3664 // Insert emoji
3665 let selection_start_anchor = snapshot.anchor_after(selection.start);
3666 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3667 edits.push((selection.start..selection.end, emoji.to_string().into()));
3668
3669 continue;
3670 }
3671 }
3672 }
3673 }
3674
3675 // If not handling any auto-close operation, then just replace the selected
3676 // text with the given input and move the selection to the end of the
3677 // newly inserted text.
3678 let anchor = snapshot.anchor_after(selection.end);
3679 if !self.linked_edit_ranges.is_empty() {
3680 let start_anchor = snapshot.anchor_before(selection.start);
3681
3682 let is_word_char = text.chars().next().map_or(true, |char| {
3683 let classifier = snapshot
3684 .char_classifier_at(start_anchor.to_offset(&snapshot))
3685 .ignore_punctuation(true);
3686 classifier.is_word(char)
3687 });
3688
3689 if is_word_char {
3690 if let Some(ranges) = self
3691 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3692 {
3693 for (buffer, edits) in ranges {
3694 linked_edits
3695 .entry(buffer.clone())
3696 .or_default()
3697 .extend(edits.into_iter().map(|range| (range, text.clone())));
3698 }
3699 }
3700 } else {
3701 clear_linked_edit_ranges = true;
3702 }
3703 }
3704
3705 new_selections.push((selection.map(|_| anchor), 0));
3706 edits.push((selection.start..selection.end, text.clone()));
3707 }
3708
3709 drop(snapshot);
3710
3711 self.transact(window, cx, |this, window, cx| {
3712 if clear_linked_edit_ranges {
3713 this.linked_edit_ranges.clear();
3714 }
3715 let initial_buffer_versions =
3716 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3717
3718 this.buffer.update(cx, |buffer, cx| {
3719 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3720 });
3721 for (buffer, edits) in linked_edits {
3722 buffer.update(cx, |buffer, cx| {
3723 let snapshot = buffer.snapshot();
3724 let edits = edits
3725 .into_iter()
3726 .map(|(range, text)| {
3727 use text::ToPoint as TP;
3728 let end_point = TP::to_point(&range.end, &snapshot);
3729 let start_point = TP::to_point(&range.start, &snapshot);
3730 (start_point..end_point, text)
3731 })
3732 .sorted_by_key(|(range, _)| range.start);
3733 buffer.edit(edits, None, cx);
3734 })
3735 }
3736 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3737 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3738 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3739 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3740 .zip(new_selection_deltas)
3741 .map(|(selection, delta)| Selection {
3742 id: selection.id,
3743 start: selection.start + delta,
3744 end: selection.end + delta,
3745 reversed: selection.reversed,
3746 goal: SelectionGoal::None,
3747 })
3748 .collect::<Vec<_>>();
3749
3750 let mut i = 0;
3751 for (position, delta, selection_id, pair) in new_autoclose_regions {
3752 let position = position.to_offset(&map.buffer_snapshot) + delta;
3753 let start = map.buffer_snapshot.anchor_before(position);
3754 let end = map.buffer_snapshot.anchor_after(position);
3755 while let Some(existing_state) = this.autoclose_regions.get(i) {
3756 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3757 Ordering::Less => i += 1,
3758 Ordering::Greater => break,
3759 Ordering::Equal => {
3760 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3761 Ordering::Less => i += 1,
3762 Ordering::Equal => break,
3763 Ordering::Greater => break,
3764 }
3765 }
3766 }
3767 }
3768 this.autoclose_regions.insert(
3769 i,
3770 AutocloseRegion {
3771 selection_id,
3772 range: start..end,
3773 pair,
3774 },
3775 );
3776 }
3777
3778 let had_active_inline_completion = this.has_active_inline_completion();
3779 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3780 s.select(new_selections)
3781 });
3782
3783 if !bracket_inserted {
3784 if let Some(on_type_format_task) =
3785 this.trigger_on_type_formatting(text.to_string(), window, cx)
3786 {
3787 on_type_format_task.detach_and_log_err(cx);
3788 }
3789 }
3790
3791 let editor_settings = EditorSettings::get_global(cx);
3792 if bracket_inserted
3793 && (editor_settings.auto_signature_help
3794 || editor_settings.show_signature_help_after_edits)
3795 {
3796 this.show_signature_help(&ShowSignatureHelp, window, cx);
3797 }
3798
3799 let trigger_in_words =
3800 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3801 if this.hard_wrap.is_some() {
3802 let latest: Range<Point> = this.selections.newest(cx).range();
3803 if latest.is_empty()
3804 && this
3805 .buffer()
3806 .read(cx)
3807 .snapshot(cx)
3808 .line_len(MultiBufferRow(latest.start.row))
3809 == latest.start.column
3810 {
3811 this.rewrap_impl(
3812 RewrapOptions {
3813 override_language_settings: true,
3814 preserve_existing_whitespace: true,
3815 },
3816 cx,
3817 )
3818 }
3819 }
3820 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3821 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3822 this.refresh_inline_completion(true, false, window, cx);
3823 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3824 });
3825 }
3826
3827 fn find_possible_emoji_shortcode_at_position(
3828 snapshot: &MultiBufferSnapshot,
3829 position: Point,
3830 ) -> Option<String> {
3831 let mut chars = Vec::new();
3832 let mut found_colon = false;
3833 for char in snapshot.reversed_chars_at(position).take(100) {
3834 // Found a possible emoji shortcode in the middle of the buffer
3835 if found_colon {
3836 if char.is_whitespace() {
3837 chars.reverse();
3838 return Some(chars.iter().collect());
3839 }
3840 // If the previous character is not a whitespace, we are in the middle of a word
3841 // and we only want to complete the shortcode if the word is made up of other emojis
3842 let mut containing_word = String::new();
3843 for ch in snapshot
3844 .reversed_chars_at(position)
3845 .skip(chars.len() + 1)
3846 .take(100)
3847 {
3848 if ch.is_whitespace() {
3849 break;
3850 }
3851 containing_word.push(ch);
3852 }
3853 let containing_word = containing_word.chars().rev().collect::<String>();
3854 if util::word_consists_of_emojis(containing_word.as_str()) {
3855 chars.reverse();
3856 return Some(chars.iter().collect());
3857 }
3858 }
3859
3860 if char.is_whitespace() || !char.is_ascii() {
3861 return None;
3862 }
3863 if char == ':' {
3864 found_colon = true;
3865 } else {
3866 chars.push(char);
3867 }
3868 }
3869 // Found a possible emoji shortcode at the beginning of the buffer
3870 chars.reverse();
3871 Some(chars.iter().collect())
3872 }
3873
3874 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3875 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3876 self.transact(window, cx, |this, window, cx| {
3877 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
3878 let selections = this.selections.all::<usize>(cx);
3879 let multi_buffer = this.buffer.read(cx);
3880 let buffer = multi_buffer.snapshot(cx);
3881 selections
3882 .iter()
3883 .map(|selection| {
3884 let start_point = selection.start.to_point(&buffer);
3885 let mut indent =
3886 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3887 indent.len = cmp::min(indent.len, start_point.column);
3888 let start = selection.start;
3889 let end = selection.end;
3890 let selection_is_empty = start == end;
3891 let language_scope = buffer.language_scope_at(start);
3892 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
3893 &language_scope
3894 {
3895 let insert_extra_newline =
3896 insert_extra_newline_brackets(&buffer, start..end, language)
3897 || insert_extra_newline_tree_sitter(&buffer, start..end);
3898
3899 // Comment extension on newline is allowed only for cursor selections
3900 let comment_delimiter = maybe!({
3901 if !selection_is_empty {
3902 return None;
3903 }
3904
3905 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3906 return None;
3907 }
3908
3909 let delimiters = language.line_comment_prefixes();
3910 let max_len_of_delimiter =
3911 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3912 let (snapshot, range) =
3913 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3914
3915 let mut index_of_first_non_whitespace = 0;
3916 let comment_candidate = snapshot
3917 .chars_for_range(range)
3918 .skip_while(|c| {
3919 let should_skip = c.is_whitespace();
3920 if should_skip {
3921 index_of_first_non_whitespace += 1;
3922 }
3923 should_skip
3924 })
3925 .take(max_len_of_delimiter)
3926 .collect::<String>();
3927 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3928 comment_candidate.starts_with(comment_prefix.as_ref())
3929 })?;
3930 let cursor_is_placed_after_comment_marker =
3931 index_of_first_non_whitespace + comment_prefix.len()
3932 <= start_point.column as usize;
3933 if cursor_is_placed_after_comment_marker {
3934 Some(comment_prefix.clone())
3935 } else {
3936 None
3937 }
3938 });
3939 (comment_delimiter, insert_extra_newline)
3940 } else {
3941 (None, false)
3942 };
3943
3944 let capacity_for_delimiter = comment_delimiter
3945 .as_deref()
3946 .map(str::len)
3947 .unwrap_or_default();
3948 let mut new_text =
3949 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3950 new_text.push('\n');
3951 new_text.extend(indent.chars());
3952 if let Some(delimiter) = &comment_delimiter {
3953 new_text.push_str(delimiter);
3954 }
3955 if insert_extra_newline {
3956 new_text = new_text.repeat(2);
3957 }
3958
3959 let anchor = buffer.anchor_after(end);
3960 let new_selection = selection.map(|_| anchor);
3961 (
3962 (start..end, new_text),
3963 (insert_extra_newline, new_selection),
3964 )
3965 })
3966 .unzip()
3967 };
3968
3969 this.edit_with_autoindent(edits, cx);
3970 let buffer = this.buffer.read(cx).snapshot(cx);
3971 let new_selections = selection_fixup_info
3972 .into_iter()
3973 .map(|(extra_newline_inserted, new_selection)| {
3974 let mut cursor = new_selection.end.to_point(&buffer);
3975 if extra_newline_inserted {
3976 cursor.row -= 1;
3977 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
3978 }
3979 new_selection.map(|_| cursor)
3980 })
3981 .collect();
3982
3983 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3984 s.select(new_selections)
3985 });
3986 this.refresh_inline_completion(true, false, window, cx);
3987 });
3988 }
3989
3990 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
3991 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3992
3993 let buffer = self.buffer.read(cx);
3994 let snapshot = buffer.snapshot(cx);
3995
3996 let mut edits = Vec::new();
3997 let mut rows = Vec::new();
3998
3999 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4000 let cursor = selection.head();
4001 let row = cursor.row;
4002
4003 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4004
4005 let newline = "\n".to_string();
4006 edits.push((start_of_line..start_of_line, newline));
4007
4008 rows.push(row + rows_inserted as u32);
4009 }
4010
4011 self.transact(window, cx, |editor, window, cx| {
4012 editor.edit(edits, cx);
4013
4014 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4015 let mut index = 0;
4016 s.move_cursors_with(|map, _, _| {
4017 let row = rows[index];
4018 index += 1;
4019
4020 let point = Point::new(row, 0);
4021 let boundary = map.next_line_boundary(point).1;
4022 let clipped = map.clip_point(boundary, Bias::Left);
4023
4024 (clipped, SelectionGoal::None)
4025 });
4026 });
4027
4028 let mut indent_edits = Vec::new();
4029 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4030 for row in rows {
4031 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4032 for (row, indent) in indents {
4033 if indent.len == 0 {
4034 continue;
4035 }
4036
4037 let text = match indent.kind {
4038 IndentKind::Space => " ".repeat(indent.len as usize),
4039 IndentKind::Tab => "\t".repeat(indent.len as usize),
4040 };
4041 let point = Point::new(row.0, 0);
4042 indent_edits.push((point..point, text));
4043 }
4044 }
4045 editor.edit(indent_edits, cx);
4046 });
4047 }
4048
4049 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4050 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4051
4052 let buffer = self.buffer.read(cx);
4053 let snapshot = buffer.snapshot(cx);
4054
4055 let mut edits = Vec::new();
4056 let mut rows = Vec::new();
4057 let mut rows_inserted = 0;
4058
4059 for selection in self.selections.all_adjusted(cx) {
4060 let cursor = selection.head();
4061 let row = cursor.row;
4062
4063 let point = Point::new(row + 1, 0);
4064 let start_of_line = snapshot.clip_point(point, Bias::Left);
4065
4066 let newline = "\n".to_string();
4067 edits.push((start_of_line..start_of_line, newline));
4068
4069 rows_inserted += 1;
4070 rows.push(row + rows_inserted);
4071 }
4072
4073 self.transact(window, cx, |editor, window, cx| {
4074 editor.edit(edits, cx);
4075
4076 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4077 let mut index = 0;
4078 s.move_cursors_with(|map, _, _| {
4079 let row = rows[index];
4080 index += 1;
4081
4082 let point = Point::new(row, 0);
4083 let boundary = map.next_line_boundary(point).1;
4084 let clipped = map.clip_point(boundary, Bias::Left);
4085
4086 (clipped, SelectionGoal::None)
4087 });
4088 });
4089
4090 let mut indent_edits = Vec::new();
4091 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4092 for row in rows {
4093 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4094 for (row, indent) in indents {
4095 if indent.len == 0 {
4096 continue;
4097 }
4098
4099 let text = match indent.kind {
4100 IndentKind::Space => " ".repeat(indent.len as usize),
4101 IndentKind::Tab => "\t".repeat(indent.len as usize),
4102 };
4103 let point = Point::new(row.0, 0);
4104 indent_edits.push((point..point, text));
4105 }
4106 }
4107 editor.edit(indent_edits, cx);
4108 });
4109 }
4110
4111 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4112 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4113 original_indent_columns: Vec::new(),
4114 });
4115 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4116 }
4117
4118 fn insert_with_autoindent_mode(
4119 &mut self,
4120 text: &str,
4121 autoindent_mode: Option<AutoindentMode>,
4122 window: &mut Window,
4123 cx: &mut Context<Self>,
4124 ) {
4125 if self.read_only(cx) {
4126 return;
4127 }
4128
4129 let text: Arc<str> = text.into();
4130 self.transact(window, cx, |this, window, cx| {
4131 let old_selections = this.selections.all_adjusted(cx);
4132 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4133 let anchors = {
4134 let snapshot = buffer.read(cx);
4135 old_selections
4136 .iter()
4137 .map(|s| {
4138 let anchor = snapshot.anchor_after(s.head());
4139 s.map(|_| anchor)
4140 })
4141 .collect::<Vec<_>>()
4142 };
4143 buffer.edit(
4144 old_selections
4145 .iter()
4146 .map(|s| (s.start..s.end, text.clone())),
4147 autoindent_mode,
4148 cx,
4149 );
4150 anchors
4151 });
4152
4153 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4154 s.select_anchors(selection_anchors);
4155 });
4156
4157 cx.notify();
4158 });
4159 }
4160
4161 fn trigger_completion_on_input(
4162 &mut self,
4163 text: &str,
4164 trigger_in_words: bool,
4165 window: &mut Window,
4166 cx: &mut Context<Self>,
4167 ) {
4168 let ignore_completion_provider = self
4169 .context_menu
4170 .borrow()
4171 .as_ref()
4172 .map(|menu| match menu {
4173 CodeContextMenu::Completions(completions_menu) => {
4174 completions_menu.ignore_completion_provider
4175 }
4176 CodeContextMenu::CodeActions(_) => false,
4177 })
4178 .unwrap_or(false);
4179
4180 if ignore_completion_provider {
4181 self.show_word_completions(&ShowWordCompletions, window, cx);
4182 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
4183 self.show_completions(
4184 &ShowCompletions {
4185 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4186 },
4187 window,
4188 cx,
4189 );
4190 } else {
4191 self.hide_context_menu(window, cx);
4192 }
4193 }
4194
4195 fn is_completion_trigger(
4196 &self,
4197 text: &str,
4198 trigger_in_words: bool,
4199 cx: &mut Context<Self>,
4200 ) -> bool {
4201 let position = self.selections.newest_anchor().head();
4202 let multibuffer = self.buffer.read(cx);
4203 let Some(buffer) = position
4204 .buffer_id
4205 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4206 else {
4207 return false;
4208 };
4209
4210 if let Some(completion_provider) = &self.completion_provider {
4211 completion_provider.is_completion_trigger(
4212 &buffer,
4213 position.text_anchor,
4214 text,
4215 trigger_in_words,
4216 cx,
4217 )
4218 } else {
4219 false
4220 }
4221 }
4222
4223 /// If any empty selections is touching the start of its innermost containing autoclose
4224 /// region, expand it to select the brackets.
4225 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4226 let selections = self.selections.all::<usize>(cx);
4227 let buffer = self.buffer.read(cx).read(cx);
4228 let new_selections = self
4229 .selections_with_autoclose_regions(selections, &buffer)
4230 .map(|(mut selection, region)| {
4231 if !selection.is_empty() {
4232 return selection;
4233 }
4234
4235 if let Some(region) = region {
4236 let mut range = region.range.to_offset(&buffer);
4237 if selection.start == range.start && range.start >= region.pair.start.len() {
4238 range.start -= region.pair.start.len();
4239 if buffer.contains_str_at(range.start, ®ion.pair.start)
4240 && buffer.contains_str_at(range.end, ®ion.pair.end)
4241 {
4242 range.end += region.pair.end.len();
4243 selection.start = range.start;
4244 selection.end = range.end;
4245
4246 return selection;
4247 }
4248 }
4249 }
4250
4251 let always_treat_brackets_as_autoclosed = buffer
4252 .language_settings_at(selection.start, cx)
4253 .always_treat_brackets_as_autoclosed;
4254
4255 if !always_treat_brackets_as_autoclosed {
4256 return selection;
4257 }
4258
4259 if let Some(scope) = buffer.language_scope_at(selection.start) {
4260 for (pair, enabled) in scope.brackets() {
4261 if !enabled || !pair.close {
4262 continue;
4263 }
4264
4265 if buffer.contains_str_at(selection.start, &pair.end) {
4266 let pair_start_len = pair.start.len();
4267 if buffer.contains_str_at(
4268 selection.start.saturating_sub(pair_start_len),
4269 &pair.start,
4270 ) {
4271 selection.start -= pair_start_len;
4272 selection.end += pair.end.len();
4273
4274 return selection;
4275 }
4276 }
4277 }
4278 }
4279
4280 selection
4281 })
4282 .collect();
4283
4284 drop(buffer);
4285 self.change_selections(None, window, cx, |selections| {
4286 selections.select(new_selections)
4287 });
4288 }
4289
4290 /// Iterate the given selections, and for each one, find the smallest surrounding
4291 /// autoclose region. This uses the ordering of the selections and the autoclose
4292 /// regions to avoid repeated comparisons.
4293 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4294 &'a self,
4295 selections: impl IntoIterator<Item = Selection<D>>,
4296 buffer: &'a MultiBufferSnapshot,
4297 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4298 let mut i = 0;
4299 let mut regions = self.autoclose_regions.as_slice();
4300 selections.into_iter().map(move |selection| {
4301 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4302
4303 let mut enclosing = None;
4304 while let Some(pair_state) = regions.get(i) {
4305 if pair_state.range.end.to_offset(buffer) < range.start {
4306 regions = ®ions[i + 1..];
4307 i = 0;
4308 } else if pair_state.range.start.to_offset(buffer) > range.end {
4309 break;
4310 } else {
4311 if pair_state.selection_id == selection.id {
4312 enclosing = Some(pair_state);
4313 }
4314 i += 1;
4315 }
4316 }
4317
4318 (selection, enclosing)
4319 })
4320 }
4321
4322 /// Remove any autoclose regions that no longer contain their selection.
4323 fn invalidate_autoclose_regions(
4324 &mut self,
4325 mut selections: &[Selection<Anchor>],
4326 buffer: &MultiBufferSnapshot,
4327 ) {
4328 self.autoclose_regions.retain(|state| {
4329 let mut i = 0;
4330 while let Some(selection) = selections.get(i) {
4331 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4332 selections = &selections[1..];
4333 continue;
4334 }
4335 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4336 break;
4337 }
4338 if selection.id == state.selection_id {
4339 return true;
4340 } else {
4341 i += 1;
4342 }
4343 }
4344 false
4345 });
4346 }
4347
4348 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4349 let offset = position.to_offset(buffer);
4350 let (word_range, kind) = buffer.surrounding_word(offset, true);
4351 if offset > word_range.start && kind == Some(CharKind::Word) {
4352 Some(
4353 buffer
4354 .text_for_range(word_range.start..offset)
4355 .collect::<String>(),
4356 )
4357 } else {
4358 None
4359 }
4360 }
4361
4362 pub fn toggle_inline_values(
4363 &mut self,
4364 _: &ToggleInlineValues,
4365 _: &mut Window,
4366 cx: &mut Context<Self>,
4367 ) {
4368 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4369
4370 self.refresh_inline_values(cx);
4371 }
4372
4373 pub fn toggle_inlay_hints(
4374 &mut self,
4375 _: &ToggleInlayHints,
4376 _: &mut Window,
4377 cx: &mut Context<Self>,
4378 ) {
4379 self.refresh_inlay_hints(
4380 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4381 cx,
4382 );
4383 }
4384
4385 pub fn inlay_hints_enabled(&self) -> bool {
4386 self.inlay_hint_cache.enabled
4387 }
4388
4389 pub fn inline_values_enabled(&self) -> bool {
4390 self.inline_value_cache.enabled
4391 }
4392
4393 #[cfg(any(test, feature = "test-support"))]
4394 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4395 self.display_map
4396 .read(cx)
4397 .current_inlays()
4398 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4399 .cloned()
4400 .collect()
4401 }
4402
4403 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4404 if self.semantics_provider.is_none() || !self.mode.is_full() {
4405 return;
4406 }
4407
4408 let reason_description = reason.description();
4409 let ignore_debounce = matches!(
4410 reason,
4411 InlayHintRefreshReason::SettingsChange(_)
4412 | InlayHintRefreshReason::Toggle(_)
4413 | InlayHintRefreshReason::ExcerptsRemoved(_)
4414 | InlayHintRefreshReason::ModifiersChanged(_)
4415 );
4416 let (invalidate_cache, required_languages) = match reason {
4417 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4418 match self.inlay_hint_cache.modifiers_override(enabled) {
4419 Some(enabled) => {
4420 if enabled {
4421 (InvalidationStrategy::RefreshRequested, None)
4422 } else {
4423 self.splice_inlays(
4424 &self
4425 .visible_inlay_hints(cx)
4426 .iter()
4427 .map(|inlay| inlay.id)
4428 .collect::<Vec<InlayId>>(),
4429 Vec::new(),
4430 cx,
4431 );
4432 return;
4433 }
4434 }
4435 None => return,
4436 }
4437 }
4438 InlayHintRefreshReason::Toggle(enabled) => {
4439 if self.inlay_hint_cache.toggle(enabled) {
4440 if enabled {
4441 (InvalidationStrategy::RefreshRequested, None)
4442 } else {
4443 self.splice_inlays(
4444 &self
4445 .visible_inlay_hints(cx)
4446 .iter()
4447 .map(|inlay| inlay.id)
4448 .collect::<Vec<InlayId>>(),
4449 Vec::new(),
4450 cx,
4451 );
4452 return;
4453 }
4454 } else {
4455 return;
4456 }
4457 }
4458 InlayHintRefreshReason::SettingsChange(new_settings) => {
4459 match self.inlay_hint_cache.update_settings(
4460 &self.buffer,
4461 new_settings,
4462 self.visible_inlay_hints(cx),
4463 cx,
4464 ) {
4465 ControlFlow::Break(Some(InlaySplice {
4466 to_remove,
4467 to_insert,
4468 })) => {
4469 self.splice_inlays(&to_remove, to_insert, cx);
4470 return;
4471 }
4472 ControlFlow::Break(None) => return,
4473 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4474 }
4475 }
4476 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4477 if let Some(InlaySplice {
4478 to_remove,
4479 to_insert,
4480 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4481 {
4482 self.splice_inlays(&to_remove, to_insert, cx);
4483 }
4484 self.display_map.update(cx, |display_map, _| {
4485 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4486 });
4487 return;
4488 }
4489 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4490 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4491 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4492 }
4493 InlayHintRefreshReason::RefreshRequested => {
4494 (InvalidationStrategy::RefreshRequested, None)
4495 }
4496 };
4497
4498 if let Some(InlaySplice {
4499 to_remove,
4500 to_insert,
4501 }) = self.inlay_hint_cache.spawn_hint_refresh(
4502 reason_description,
4503 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4504 invalidate_cache,
4505 ignore_debounce,
4506 cx,
4507 ) {
4508 self.splice_inlays(&to_remove, to_insert, cx);
4509 }
4510 }
4511
4512 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4513 self.display_map
4514 .read(cx)
4515 .current_inlays()
4516 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4517 .cloned()
4518 .collect()
4519 }
4520
4521 pub fn excerpts_for_inlay_hints_query(
4522 &self,
4523 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4524 cx: &mut Context<Editor>,
4525 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4526 let Some(project) = self.project.as_ref() else {
4527 return HashMap::default();
4528 };
4529 let project = project.read(cx);
4530 let multi_buffer = self.buffer().read(cx);
4531 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4532 let multi_buffer_visible_start = self
4533 .scroll_manager
4534 .anchor()
4535 .anchor
4536 .to_point(&multi_buffer_snapshot);
4537 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4538 multi_buffer_visible_start
4539 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4540 Bias::Left,
4541 );
4542 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4543 multi_buffer_snapshot
4544 .range_to_buffer_ranges(multi_buffer_visible_range)
4545 .into_iter()
4546 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4547 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4548 let buffer_file = project::File::from_dyn(buffer.file())?;
4549 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4550 let worktree_entry = buffer_worktree
4551 .read(cx)
4552 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4553 if worktree_entry.is_ignored {
4554 return None;
4555 }
4556
4557 let language = buffer.language()?;
4558 if let Some(restrict_to_languages) = restrict_to_languages {
4559 if !restrict_to_languages.contains(language) {
4560 return None;
4561 }
4562 }
4563 Some((
4564 excerpt_id,
4565 (
4566 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4567 buffer.version().clone(),
4568 excerpt_visible_range,
4569 ),
4570 ))
4571 })
4572 .collect()
4573 }
4574
4575 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4576 TextLayoutDetails {
4577 text_system: window.text_system().clone(),
4578 editor_style: self.style.clone().unwrap(),
4579 rem_size: window.rem_size(),
4580 scroll_anchor: self.scroll_manager.anchor(),
4581 visible_rows: self.visible_line_count(),
4582 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4583 }
4584 }
4585
4586 pub fn splice_inlays(
4587 &self,
4588 to_remove: &[InlayId],
4589 to_insert: Vec<Inlay>,
4590 cx: &mut Context<Self>,
4591 ) {
4592 self.display_map.update(cx, |display_map, cx| {
4593 display_map.splice_inlays(to_remove, to_insert, cx)
4594 });
4595 cx.notify();
4596 }
4597
4598 fn trigger_on_type_formatting(
4599 &self,
4600 input: String,
4601 window: &mut Window,
4602 cx: &mut Context<Self>,
4603 ) -> Option<Task<Result<()>>> {
4604 if input.len() != 1 {
4605 return None;
4606 }
4607
4608 let project = self.project.as_ref()?;
4609 let position = self.selections.newest_anchor().head();
4610 let (buffer, buffer_position) = self
4611 .buffer
4612 .read(cx)
4613 .text_anchor_for_position(position, cx)?;
4614
4615 let settings = language_settings::language_settings(
4616 buffer
4617 .read(cx)
4618 .language_at(buffer_position)
4619 .map(|l| l.name()),
4620 buffer.read(cx).file(),
4621 cx,
4622 );
4623 if !settings.use_on_type_format {
4624 return None;
4625 }
4626
4627 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4628 // hence we do LSP request & edit on host side only — add formats to host's history.
4629 let push_to_lsp_host_history = true;
4630 // If this is not the host, append its history with new edits.
4631 let push_to_client_history = project.read(cx).is_via_collab();
4632
4633 let on_type_formatting = project.update(cx, |project, cx| {
4634 project.on_type_format(
4635 buffer.clone(),
4636 buffer_position,
4637 input,
4638 push_to_lsp_host_history,
4639 cx,
4640 )
4641 });
4642 Some(cx.spawn_in(window, async move |editor, cx| {
4643 if let Some(transaction) = on_type_formatting.await? {
4644 if push_to_client_history {
4645 buffer
4646 .update(cx, |buffer, _| {
4647 buffer.push_transaction(transaction, Instant::now());
4648 buffer.finalize_last_transaction();
4649 })
4650 .ok();
4651 }
4652 editor.update(cx, |editor, cx| {
4653 editor.refresh_document_highlights(cx);
4654 })?;
4655 }
4656 Ok(())
4657 }))
4658 }
4659
4660 pub fn show_word_completions(
4661 &mut self,
4662 _: &ShowWordCompletions,
4663 window: &mut Window,
4664 cx: &mut Context<Self>,
4665 ) {
4666 self.open_completions_menu(true, None, window, cx);
4667 }
4668
4669 pub fn show_completions(
4670 &mut self,
4671 options: &ShowCompletions,
4672 window: &mut Window,
4673 cx: &mut Context<Self>,
4674 ) {
4675 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4676 }
4677
4678 fn open_completions_menu(
4679 &mut self,
4680 ignore_completion_provider: bool,
4681 trigger: Option<&str>,
4682 window: &mut Window,
4683 cx: &mut Context<Self>,
4684 ) {
4685 if self.pending_rename.is_some() {
4686 return;
4687 }
4688 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4689 return;
4690 }
4691
4692 let position = self.selections.newest_anchor().head();
4693 if position.diff_base_anchor.is_some() {
4694 return;
4695 }
4696 let (buffer, buffer_position) =
4697 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4698 output
4699 } else {
4700 return;
4701 };
4702 let buffer_snapshot = buffer.read(cx).snapshot();
4703 let show_completion_documentation = buffer_snapshot
4704 .settings_at(buffer_position, cx)
4705 .show_completion_documentation;
4706
4707 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4708
4709 let trigger_kind = match trigger {
4710 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4711 CompletionTriggerKind::TRIGGER_CHARACTER
4712 }
4713 _ => CompletionTriggerKind::INVOKED,
4714 };
4715 let completion_context = CompletionContext {
4716 trigger_character: trigger.and_then(|trigger| {
4717 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4718 Some(String::from(trigger))
4719 } else {
4720 None
4721 }
4722 }),
4723 trigger_kind,
4724 };
4725
4726 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4727 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4728 let word_to_exclude = buffer_snapshot
4729 .text_for_range(old_range.clone())
4730 .collect::<String>();
4731 (
4732 buffer_snapshot.anchor_before(old_range.start)
4733 ..buffer_snapshot.anchor_after(old_range.end),
4734 Some(word_to_exclude),
4735 )
4736 } else {
4737 (buffer_position..buffer_position, None)
4738 };
4739
4740 let completion_settings = language_settings(
4741 buffer_snapshot
4742 .language_at(buffer_position)
4743 .map(|language| language.name()),
4744 buffer_snapshot.file(),
4745 cx,
4746 )
4747 .completions;
4748
4749 // The document can be large, so stay in reasonable bounds when searching for words,
4750 // otherwise completion pop-up might be slow to appear.
4751 const WORD_LOOKUP_ROWS: u32 = 5_000;
4752 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4753 let min_word_search = buffer_snapshot.clip_point(
4754 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4755 Bias::Left,
4756 );
4757 let max_word_search = buffer_snapshot.clip_point(
4758 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4759 Bias::Right,
4760 );
4761 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4762 ..buffer_snapshot.point_to_offset(max_word_search);
4763
4764 let provider = self
4765 .completion_provider
4766 .as_ref()
4767 .filter(|_| !ignore_completion_provider);
4768 let skip_digits = query
4769 .as_ref()
4770 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4771
4772 let (mut words, provided_completions) = match provider {
4773 Some(provider) => {
4774 let completions = provider.completions(
4775 position.excerpt_id,
4776 &buffer,
4777 buffer_position,
4778 completion_context,
4779 window,
4780 cx,
4781 );
4782
4783 let words = match completion_settings.words {
4784 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4785 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4786 .background_spawn(async move {
4787 buffer_snapshot.words_in_range(WordsQuery {
4788 fuzzy_contents: None,
4789 range: word_search_range,
4790 skip_digits,
4791 })
4792 }),
4793 };
4794
4795 (words, completions)
4796 }
4797 None => (
4798 cx.background_spawn(async move {
4799 buffer_snapshot.words_in_range(WordsQuery {
4800 fuzzy_contents: None,
4801 range: word_search_range,
4802 skip_digits,
4803 })
4804 }),
4805 Task::ready(Ok(None)),
4806 ),
4807 };
4808
4809 let sort_completions = provider
4810 .as_ref()
4811 .map_or(false, |provider| provider.sort_completions());
4812
4813 let filter_completions = provider
4814 .as_ref()
4815 .map_or(true, |provider| provider.filter_completions());
4816
4817 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
4818
4819 let id = post_inc(&mut self.next_completion_id);
4820 let task = cx.spawn_in(window, async move |editor, cx| {
4821 async move {
4822 editor.update(cx, |this, _| {
4823 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4824 })?;
4825
4826 let mut completions = Vec::new();
4827 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
4828 completions.extend(provided_completions);
4829 if completion_settings.words == WordsCompletionMode::Fallback {
4830 words = Task::ready(BTreeMap::default());
4831 }
4832 }
4833
4834 let mut words = words.await;
4835 if let Some(word_to_exclude) = &word_to_exclude {
4836 words.remove(word_to_exclude);
4837 }
4838 for lsp_completion in &completions {
4839 words.remove(&lsp_completion.new_text);
4840 }
4841 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
4842 replace_range: old_range.clone(),
4843 new_text: word.clone(),
4844 label: CodeLabel::plain(word, None),
4845 icon_path: None,
4846 documentation: None,
4847 source: CompletionSource::BufferWord {
4848 word_range,
4849 resolved: false,
4850 },
4851 insert_text_mode: Some(InsertTextMode::AS_IS),
4852 confirm: None,
4853 }));
4854
4855 let menu = if completions.is_empty() {
4856 None
4857 } else {
4858 let mut menu = CompletionsMenu::new(
4859 id,
4860 sort_completions,
4861 show_completion_documentation,
4862 ignore_completion_provider,
4863 position,
4864 buffer.clone(),
4865 completions.into(),
4866 snippet_sort_order,
4867 );
4868
4869 menu.filter(
4870 if filter_completions {
4871 query.as_deref()
4872 } else {
4873 None
4874 },
4875 cx.background_executor().clone(),
4876 )
4877 .await;
4878
4879 menu.visible().then_some(menu)
4880 };
4881
4882 editor.update_in(cx, |editor, window, cx| {
4883 match editor.context_menu.borrow().as_ref() {
4884 None => {}
4885 Some(CodeContextMenu::Completions(prev_menu)) => {
4886 if prev_menu.id > id {
4887 return;
4888 }
4889 }
4890 _ => return,
4891 }
4892
4893 if editor.focus_handle.is_focused(window) && menu.is_some() {
4894 let mut menu = menu.unwrap();
4895 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
4896
4897 *editor.context_menu.borrow_mut() =
4898 Some(CodeContextMenu::Completions(menu));
4899
4900 if editor.show_edit_predictions_in_menu() {
4901 editor.update_visible_inline_completion(window, cx);
4902 } else {
4903 editor.discard_inline_completion(false, cx);
4904 }
4905
4906 cx.notify();
4907 } else if editor.completion_tasks.len() <= 1 {
4908 // If there are no more completion tasks and the last menu was
4909 // empty, we should hide it.
4910 let was_hidden = editor.hide_context_menu(window, cx).is_none();
4911 // If it was already hidden and we don't show inline
4912 // completions in the menu, we should also show the
4913 // inline-completion when available.
4914 if was_hidden && editor.show_edit_predictions_in_menu() {
4915 editor.update_visible_inline_completion(window, cx);
4916 }
4917 }
4918 })?;
4919
4920 anyhow::Ok(())
4921 }
4922 .log_err()
4923 .await
4924 });
4925
4926 self.completion_tasks.push((id, task));
4927 }
4928
4929 #[cfg(feature = "test-support")]
4930 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
4931 let menu = self.context_menu.borrow();
4932 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
4933 let completions = menu.completions.borrow();
4934 Some(completions.to_vec())
4935 } else {
4936 None
4937 }
4938 }
4939
4940 pub fn confirm_completion(
4941 &mut self,
4942 action: &ConfirmCompletion,
4943 window: &mut Window,
4944 cx: &mut Context<Self>,
4945 ) -> Option<Task<Result<()>>> {
4946 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4947 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
4948 }
4949
4950 pub fn confirm_completion_insert(
4951 &mut self,
4952 _: &ConfirmCompletionInsert,
4953 window: &mut Window,
4954 cx: &mut Context<Self>,
4955 ) -> Option<Task<Result<()>>> {
4956 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4957 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
4958 }
4959
4960 pub fn confirm_completion_replace(
4961 &mut self,
4962 _: &ConfirmCompletionReplace,
4963 window: &mut Window,
4964 cx: &mut Context<Self>,
4965 ) -> Option<Task<Result<()>>> {
4966 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4967 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
4968 }
4969
4970 pub fn compose_completion(
4971 &mut self,
4972 action: &ComposeCompletion,
4973 window: &mut Window,
4974 cx: &mut Context<Self>,
4975 ) -> Option<Task<Result<()>>> {
4976 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4977 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
4978 }
4979
4980 fn do_completion(
4981 &mut self,
4982 item_ix: Option<usize>,
4983 intent: CompletionIntent,
4984 window: &mut Window,
4985 cx: &mut Context<Editor>,
4986 ) -> Option<Task<Result<()>>> {
4987 use language::ToOffset as _;
4988
4989 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
4990 else {
4991 return None;
4992 };
4993
4994 let candidate_id = {
4995 let entries = completions_menu.entries.borrow();
4996 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
4997 if self.show_edit_predictions_in_menu() {
4998 self.discard_inline_completion(true, cx);
4999 }
5000 mat.candidate_id
5001 };
5002
5003 let buffer_handle = completions_menu.buffer;
5004 let completion = completions_menu
5005 .completions
5006 .borrow()
5007 .get(candidate_id)?
5008 .clone();
5009 cx.stop_propagation();
5010
5011 let snapshot = self.buffer.read(cx).snapshot(cx);
5012 let newest_anchor = self.selections.newest_anchor();
5013
5014 let snippet;
5015 let new_text;
5016 if completion.is_snippet() {
5017 // lsp returns function definition with placeholders in "new_text"
5018 // when configured from language server, even when renaming a function
5019 //
5020 // in such cases, we use the label instead
5021 // https://github.com/zed-industries/zed/issues/29982
5022 let snippet_source = completion
5023 .label()
5024 .filter(|label| {
5025 completion.kind() == Some(CompletionItemKind::FUNCTION)
5026 && label != &completion.new_text
5027 })
5028 .and_then(|label| {
5029 let cursor_offset = newest_anchor.head().to_offset(&snapshot);
5030 let next_char_is_not_whitespace = snapshot
5031 .chars_at(cursor_offset)
5032 .next()
5033 .map_or(true, |ch| !ch.is_whitespace());
5034 next_char_is_not_whitespace.then_some(label)
5035 })
5036 .unwrap_or(completion.new_text.clone());
5037
5038 snippet = Some(Snippet::parse(&snippet_source).log_err()?);
5039 new_text = snippet.as_ref().unwrap().text.clone();
5040 } else {
5041 snippet = None;
5042 new_text = completion.new_text.clone();
5043 };
5044
5045 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
5046 let buffer = buffer_handle.read(cx);
5047 let replace_range_multibuffer = {
5048 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5049 let multibuffer_anchor = snapshot
5050 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5051 .unwrap()
5052 ..snapshot
5053 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5054 .unwrap();
5055 multibuffer_anchor.start.to_offset(&snapshot)
5056 ..multibuffer_anchor.end.to_offset(&snapshot)
5057 };
5058 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5059 return None;
5060 }
5061
5062 let old_text = buffer
5063 .text_for_range(replace_range.clone())
5064 .collect::<String>();
5065 let lookbehind = newest_anchor
5066 .start
5067 .text_anchor
5068 .to_offset(buffer)
5069 .saturating_sub(replace_range.start);
5070 let lookahead = replace_range
5071 .end
5072 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5073 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5074 let suffix = &old_text[lookbehind.min(old_text.len())..];
5075
5076 let selections = self.selections.all::<usize>(cx);
5077 let mut ranges = Vec::new();
5078 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5079
5080 for selection in &selections {
5081 let range = if selection.id == newest_anchor.id {
5082 replace_range_multibuffer.clone()
5083 } else {
5084 let mut range = selection.range();
5085
5086 // if prefix is present, don't duplicate it
5087 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5088 range.start = range.start.saturating_sub(lookbehind);
5089
5090 // if suffix is also present, mimic the newest cursor and replace it
5091 if selection.id != newest_anchor.id
5092 && snapshot.contains_str_at(range.end, suffix)
5093 {
5094 range.end += lookahead;
5095 }
5096 }
5097 range
5098 };
5099
5100 ranges.push(range.clone());
5101
5102 if !self.linked_edit_ranges.is_empty() {
5103 let start_anchor = snapshot.anchor_before(range.start);
5104 let end_anchor = snapshot.anchor_after(range.end);
5105 if let Some(ranges) = self
5106 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5107 {
5108 for (buffer, edits) in ranges {
5109 linked_edits
5110 .entry(buffer.clone())
5111 .or_default()
5112 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5113 }
5114 }
5115 }
5116 }
5117
5118 cx.emit(EditorEvent::InputHandled {
5119 utf16_range_to_replace: None,
5120 text: new_text.clone().into(),
5121 });
5122
5123 self.transact(window, cx, |this, window, cx| {
5124 if let Some(mut snippet) = snippet {
5125 snippet.text = new_text.to_string();
5126 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5127 } else {
5128 this.buffer.update(cx, |buffer, cx| {
5129 let auto_indent = match completion.insert_text_mode {
5130 Some(InsertTextMode::AS_IS) => None,
5131 _ => this.autoindent_mode.clone(),
5132 };
5133 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5134 buffer.edit(edits, auto_indent, cx);
5135 });
5136 }
5137 for (buffer, edits) in linked_edits {
5138 buffer.update(cx, |buffer, cx| {
5139 let snapshot = buffer.snapshot();
5140 let edits = edits
5141 .into_iter()
5142 .map(|(range, text)| {
5143 use text::ToPoint as TP;
5144 let end_point = TP::to_point(&range.end, &snapshot);
5145 let start_point = TP::to_point(&range.start, &snapshot);
5146 (start_point..end_point, text)
5147 })
5148 .sorted_by_key(|(range, _)| range.start);
5149 buffer.edit(edits, None, cx);
5150 })
5151 }
5152
5153 this.refresh_inline_completion(true, false, window, cx);
5154 });
5155
5156 let show_new_completions_on_confirm = completion
5157 .confirm
5158 .as_ref()
5159 .map_or(false, |confirm| confirm(intent, window, cx));
5160 if show_new_completions_on_confirm {
5161 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5162 }
5163
5164 let provider = self.completion_provider.as_ref()?;
5165 drop(completion);
5166 let apply_edits = provider.apply_additional_edits_for_completion(
5167 buffer_handle,
5168 completions_menu.completions.clone(),
5169 candidate_id,
5170 true,
5171 cx,
5172 );
5173
5174 let editor_settings = EditorSettings::get_global(cx);
5175 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5176 // After the code completion is finished, users often want to know what signatures are needed.
5177 // so we should automatically call signature_help
5178 self.show_signature_help(&ShowSignatureHelp, window, cx);
5179 }
5180
5181 Some(cx.foreground_executor().spawn(async move {
5182 apply_edits.await?;
5183 Ok(())
5184 }))
5185 }
5186
5187 pub fn toggle_code_actions(
5188 &mut self,
5189 action: &ToggleCodeActions,
5190 window: &mut Window,
5191 cx: &mut Context<Self>,
5192 ) {
5193 let quick_launch = action.quick_launch;
5194 let mut context_menu = self.context_menu.borrow_mut();
5195 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5196 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
5197 // Toggle if we're selecting the same one
5198 *context_menu = None;
5199 cx.notify();
5200 return;
5201 } else {
5202 // Otherwise, clear it and start a new one
5203 *context_menu = None;
5204 cx.notify();
5205 }
5206 }
5207 drop(context_menu);
5208 let snapshot = self.snapshot(window, cx);
5209 let deployed_from_indicator = action.deployed_from_indicator;
5210 let mut task = self.code_actions_task.take();
5211 let action = action.clone();
5212 cx.spawn_in(window, async move |editor, cx| {
5213 while let Some(prev_task) = task {
5214 prev_task.await.log_err();
5215 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5216 }
5217
5218 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5219 if editor.focus_handle.is_focused(window) {
5220 let multibuffer_point = action
5221 .deployed_from_indicator
5222 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
5223 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
5224 let (buffer, buffer_row) = snapshot
5225 .buffer_snapshot
5226 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5227 .and_then(|(buffer_snapshot, range)| {
5228 editor
5229 .buffer
5230 .read(cx)
5231 .buffer(buffer_snapshot.remote_id())
5232 .map(|buffer| (buffer, range.start.row))
5233 })?;
5234 let (_, code_actions) = editor
5235 .available_code_actions
5236 .clone()
5237 .and_then(|(location, code_actions)| {
5238 let snapshot = location.buffer.read(cx).snapshot();
5239 let point_range = location.range.to_point(&snapshot);
5240 let point_range = point_range.start.row..=point_range.end.row;
5241 if point_range.contains(&buffer_row) {
5242 Some((location, code_actions))
5243 } else {
5244 None
5245 }
5246 })
5247 .unzip();
5248 let buffer_id = buffer.read(cx).remote_id();
5249 let tasks = editor
5250 .tasks
5251 .get(&(buffer_id, buffer_row))
5252 .map(|t| Arc::new(t.to_owned()));
5253 if tasks.is_none() && code_actions.is_none() {
5254 return None;
5255 }
5256
5257 editor.completion_tasks.clear();
5258 editor.discard_inline_completion(false, cx);
5259 let task_context =
5260 tasks
5261 .as_ref()
5262 .zip(editor.project.clone())
5263 .map(|(tasks, project)| {
5264 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5265 });
5266
5267 Some(cx.spawn_in(window, async move |editor, cx| {
5268 let task_context = match task_context {
5269 Some(task_context) => task_context.await,
5270 None => None,
5271 };
5272 let resolved_tasks =
5273 tasks
5274 .zip(task_context.clone())
5275 .map(|(tasks, task_context)| ResolvedTasks {
5276 templates: tasks.resolve(&task_context).collect(),
5277 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5278 multibuffer_point.row,
5279 tasks.column,
5280 )),
5281 });
5282 let debug_scenarios = editor.update(cx, |editor, cx| {
5283 if cx.has_flag::<DebuggerFeatureFlag>() {
5284 maybe!({
5285 let project = editor.project.as_ref()?;
5286 let dap_store = project.read(cx).dap_store();
5287 let mut scenarios = vec![];
5288 let resolved_tasks = resolved_tasks.as_ref()?;
5289 let buffer = buffer.read(cx);
5290 let language = buffer.language()?;
5291 let file = buffer.file();
5292 let debug_adapter =
5293 language_settings(language.name().into(), file, cx)
5294 .debuggers
5295 .first()
5296 .map(SharedString::from)
5297 .or_else(|| {
5298 language
5299 .config()
5300 .debuggers
5301 .first()
5302 .map(SharedString::from)
5303 })?;
5304
5305 dap_store.update(cx, |this, cx| {
5306 for (_, task) in &resolved_tasks.templates {
5307 if let Some(scenario) = this
5308 .debug_scenario_for_build_task(
5309 task.original_task().clone(),
5310 debug_adapter.clone().into(),
5311 task.display_label().to_owned().into(),
5312 cx,
5313 )
5314 {
5315 scenarios.push(scenario);
5316 }
5317 }
5318 });
5319 Some(scenarios)
5320 })
5321 .unwrap_or_default()
5322 } else {
5323 vec![]
5324 }
5325 })?;
5326 let spawn_straight_away = quick_launch
5327 && resolved_tasks
5328 .as_ref()
5329 .map_or(false, |tasks| tasks.templates.len() == 1)
5330 && code_actions
5331 .as_ref()
5332 .map_or(true, |actions| actions.is_empty())
5333 && debug_scenarios.is_empty();
5334 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5335 *editor.context_menu.borrow_mut() =
5336 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5337 buffer,
5338 actions: CodeActionContents::new(
5339 resolved_tasks,
5340 code_actions,
5341 debug_scenarios,
5342 task_context.unwrap_or_default(),
5343 ),
5344 selected_item: Default::default(),
5345 scroll_handle: UniformListScrollHandle::default(),
5346 deployed_from_indicator,
5347 }));
5348 if spawn_straight_away {
5349 if let Some(task) = editor.confirm_code_action(
5350 &ConfirmCodeAction { item_ix: Some(0) },
5351 window,
5352 cx,
5353 ) {
5354 cx.notify();
5355 return task;
5356 }
5357 }
5358 cx.notify();
5359 Task::ready(Ok(()))
5360 }) {
5361 task.await
5362 } else {
5363 Ok(())
5364 }
5365 }))
5366 } else {
5367 Some(Task::ready(Ok(())))
5368 }
5369 })?;
5370 if let Some(task) = spawned_test_task {
5371 task.await?;
5372 }
5373
5374 Ok::<_, anyhow::Error>(())
5375 })
5376 .detach_and_log_err(cx);
5377 }
5378
5379 pub fn confirm_code_action(
5380 &mut self,
5381 action: &ConfirmCodeAction,
5382 window: &mut Window,
5383 cx: &mut Context<Self>,
5384 ) -> Option<Task<Result<()>>> {
5385 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5386
5387 let actions_menu =
5388 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5389 menu
5390 } else {
5391 return None;
5392 };
5393
5394 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5395 let action = actions_menu.actions.get(action_ix)?;
5396 let title = action.label();
5397 let buffer = actions_menu.buffer;
5398 let workspace = self.workspace()?;
5399
5400 match action {
5401 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5402 workspace.update(cx, |workspace, cx| {
5403 workspace.schedule_resolved_task(
5404 task_source_kind,
5405 resolved_task,
5406 false,
5407 window,
5408 cx,
5409 );
5410
5411 Some(Task::ready(Ok(())))
5412 })
5413 }
5414 CodeActionsItem::CodeAction {
5415 excerpt_id,
5416 action,
5417 provider,
5418 } => {
5419 let apply_code_action =
5420 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5421 let workspace = workspace.downgrade();
5422 Some(cx.spawn_in(window, async move |editor, cx| {
5423 let project_transaction = apply_code_action.await?;
5424 Self::open_project_transaction(
5425 &editor,
5426 workspace,
5427 project_transaction,
5428 title,
5429 cx,
5430 )
5431 .await
5432 }))
5433 }
5434 CodeActionsItem::DebugScenario(scenario) => {
5435 let context = actions_menu.actions.context.clone();
5436
5437 workspace.update(cx, |workspace, cx| {
5438 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5439 });
5440 Some(Task::ready(Ok(())))
5441 }
5442 }
5443 }
5444
5445 pub async fn open_project_transaction(
5446 this: &WeakEntity<Editor>,
5447 workspace: WeakEntity<Workspace>,
5448 transaction: ProjectTransaction,
5449 title: String,
5450 cx: &mut AsyncWindowContext,
5451 ) -> Result<()> {
5452 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5453 cx.update(|_, cx| {
5454 entries.sort_unstable_by_key(|(buffer, _)| {
5455 buffer.read(cx).file().map(|f| f.path().clone())
5456 });
5457 })?;
5458
5459 // If the project transaction's edits are all contained within this editor, then
5460 // avoid opening a new editor to display them.
5461
5462 if let Some((buffer, transaction)) = entries.first() {
5463 if entries.len() == 1 {
5464 let excerpt = this.update(cx, |editor, cx| {
5465 editor
5466 .buffer()
5467 .read(cx)
5468 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5469 })?;
5470 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5471 if excerpted_buffer == *buffer {
5472 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5473 let excerpt_range = excerpt_range.to_offset(buffer);
5474 buffer
5475 .edited_ranges_for_transaction::<usize>(transaction)
5476 .all(|range| {
5477 excerpt_range.start <= range.start
5478 && excerpt_range.end >= range.end
5479 })
5480 })?;
5481
5482 if all_edits_within_excerpt {
5483 return Ok(());
5484 }
5485 }
5486 }
5487 }
5488 } else {
5489 return Ok(());
5490 }
5491
5492 let mut ranges_to_highlight = Vec::new();
5493 let excerpt_buffer = cx.new(|cx| {
5494 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5495 for (buffer_handle, transaction) in &entries {
5496 let edited_ranges = buffer_handle
5497 .read(cx)
5498 .edited_ranges_for_transaction::<Point>(transaction)
5499 .collect::<Vec<_>>();
5500 let (ranges, _) = multibuffer.set_excerpts_for_path(
5501 PathKey::for_buffer(buffer_handle, cx),
5502 buffer_handle.clone(),
5503 edited_ranges,
5504 DEFAULT_MULTIBUFFER_CONTEXT,
5505 cx,
5506 );
5507
5508 ranges_to_highlight.extend(ranges);
5509 }
5510 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5511 multibuffer
5512 })?;
5513
5514 workspace.update_in(cx, |workspace, window, cx| {
5515 let project = workspace.project().clone();
5516 let editor =
5517 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5518 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5519 editor.update(cx, |editor, cx| {
5520 editor.highlight_background::<Self>(
5521 &ranges_to_highlight,
5522 |theme| theme.editor_highlighted_line_background,
5523 cx,
5524 );
5525 });
5526 })?;
5527
5528 Ok(())
5529 }
5530
5531 pub fn clear_code_action_providers(&mut self) {
5532 self.code_action_providers.clear();
5533 self.available_code_actions.take();
5534 }
5535
5536 pub fn add_code_action_provider(
5537 &mut self,
5538 provider: Rc<dyn CodeActionProvider>,
5539 window: &mut Window,
5540 cx: &mut Context<Self>,
5541 ) {
5542 if self
5543 .code_action_providers
5544 .iter()
5545 .any(|existing_provider| existing_provider.id() == provider.id())
5546 {
5547 return;
5548 }
5549
5550 self.code_action_providers.push(provider);
5551 self.refresh_code_actions(window, cx);
5552 }
5553
5554 pub fn remove_code_action_provider(
5555 &mut self,
5556 id: Arc<str>,
5557 window: &mut Window,
5558 cx: &mut Context<Self>,
5559 ) {
5560 self.code_action_providers
5561 .retain(|provider| provider.id() != id);
5562 self.refresh_code_actions(window, cx);
5563 }
5564
5565 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5566 let newest_selection = self.selections.newest_anchor().clone();
5567 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5568 let buffer = self.buffer.read(cx);
5569 if newest_selection.head().diff_base_anchor.is_some() {
5570 return None;
5571 }
5572 let (start_buffer, start) =
5573 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5574 let (end_buffer, end) =
5575 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5576 if start_buffer != end_buffer {
5577 return None;
5578 }
5579
5580 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5581 cx.background_executor()
5582 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5583 .await;
5584
5585 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5586 let providers = this.code_action_providers.clone();
5587 let tasks = this
5588 .code_action_providers
5589 .iter()
5590 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5591 .collect::<Vec<_>>();
5592 (providers, tasks)
5593 })?;
5594
5595 let mut actions = Vec::new();
5596 for (provider, provider_actions) in
5597 providers.into_iter().zip(future::join_all(tasks).await)
5598 {
5599 if let Some(provider_actions) = provider_actions.log_err() {
5600 actions.extend(provider_actions.into_iter().map(|action| {
5601 AvailableCodeAction {
5602 excerpt_id: newest_selection.start.excerpt_id,
5603 action,
5604 provider: provider.clone(),
5605 }
5606 }));
5607 }
5608 }
5609
5610 this.update(cx, |this, cx| {
5611 this.available_code_actions = if actions.is_empty() {
5612 None
5613 } else {
5614 Some((
5615 Location {
5616 buffer: start_buffer,
5617 range: start..end,
5618 },
5619 actions.into(),
5620 ))
5621 };
5622 cx.notify();
5623 })
5624 }));
5625 None
5626 }
5627
5628 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5629 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5630 self.show_git_blame_inline = false;
5631
5632 self.show_git_blame_inline_delay_task =
5633 Some(cx.spawn_in(window, async move |this, cx| {
5634 cx.background_executor().timer(delay).await;
5635
5636 this.update(cx, |this, cx| {
5637 this.show_git_blame_inline = true;
5638 cx.notify();
5639 })
5640 .log_err();
5641 }));
5642 }
5643 }
5644
5645 fn show_blame_popover(
5646 &mut self,
5647 blame_entry: &BlameEntry,
5648 position: gpui::Point<Pixels>,
5649 cx: &mut Context<Self>,
5650 ) {
5651 if let Some(state) = &mut self.inline_blame_popover {
5652 state.hide_task.take();
5653 cx.notify();
5654 } else {
5655 let delay = EditorSettings::get_global(cx).hover_popover_delay;
5656 let show_task = cx.spawn(async move |editor, cx| {
5657 cx.background_executor()
5658 .timer(std::time::Duration::from_millis(delay))
5659 .await;
5660 editor
5661 .update(cx, |editor, cx| {
5662 if let Some(state) = &mut editor.inline_blame_popover {
5663 state.show_task = None;
5664 cx.notify();
5665 }
5666 })
5667 .ok();
5668 });
5669 let Some(blame) = self.blame.as_ref() else {
5670 return;
5671 };
5672 let blame = blame.read(cx);
5673 let details = blame.details_for_entry(&blame_entry);
5674 let markdown = cx.new(|cx| {
5675 Markdown::new(
5676 details
5677 .as_ref()
5678 .map(|message| message.message.clone())
5679 .unwrap_or_default(),
5680 None,
5681 None,
5682 cx,
5683 )
5684 });
5685 self.inline_blame_popover = Some(InlineBlamePopover {
5686 position,
5687 show_task: Some(show_task),
5688 hide_task: None,
5689 popover_bounds: None,
5690 popover_state: InlineBlamePopoverState {
5691 scroll_handle: ScrollHandle::new(),
5692 commit_message: details,
5693 markdown,
5694 },
5695 });
5696 }
5697 }
5698
5699 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
5700 if let Some(state) = &mut self.inline_blame_popover {
5701 if state.show_task.is_some() {
5702 self.inline_blame_popover.take();
5703 cx.notify();
5704 } else {
5705 let hide_task = cx.spawn(async move |editor, cx| {
5706 cx.background_executor()
5707 .timer(std::time::Duration::from_millis(100))
5708 .await;
5709 editor
5710 .update(cx, |editor, cx| {
5711 editor.inline_blame_popover.take();
5712 cx.notify();
5713 })
5714 .ok();
5715 });
5716 state.hide_task = Some(hide_task);
5717 }
5718 }
5719 }
5720
5721 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5722 if self.pending_rename.is_some() {
5723 return None;
5724 }
5725
5726 let provider = self.semantics_provider.clone()?;
5727 let buffer = self.buffer.read(cx);
5728 let newest_selection = self.selections.newest_anchor().clone();
5729 let cursor_position = newest_selection.head();
5730 let (cursor_buffer, cursor_buffer_position) =
5731 buffer.text_anchor_for_position(cursor_position, cx)?;
5732 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5733 if cursor_buffer != tail_buffer {
5734 return None;
5735 }
5736 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5737 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5738 cx.background_executor()
5739 .timer(Duration::from_millis(debounce))
5740 .await;
5741
5742 let highlights = if let Some(highlights) = cx
5743 .update(|cx| {
5744 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5745 })
5746 .ok()
5747 .flatten()
5748 {
5749 highlights.await.log_err()
5750 } else {
5751 None
5752 };
5753
5754 if let Some(highlights) = highlights {
5755 this.update(cx, |this, cx| {
5756 if this.pending_rename.is_some() {
5757 return;
5758 }
5759
5760 let buffer_id = cursor_position.buffer_id;
5761 let buffer = this.buffer.read(cx);
5762 if !buffer
5763 .text_anchor_for_position(cursor_position, cx)
5764 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5765 {
5766 return;
5767 }
5768
5769 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5770 let mut write_ranges = Vec::new();
5771 let mut read_ranges = Vec::new();
5772 for highlight in highlights {
5773 for (excerpt_id, excerpt_range) in
5774 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5775 {
5776 let start = highlight
5777 .range
5778 .start
5779 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5780 let end = highlight
5781 .range
5782 .end
5783 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5784 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5785 continue;
5786 }
5787
5788 let range = Anchor {
5789 buffer_id,
5790 excerpt_id,
5791 text_anchor: start,
5792 diff_base_anchor: None,
5793 }..Anchor {
5794 buffer_id,
5795 excerpt_id,
5796 text_anchor: end,
5797 diff_base_anchor: None,
5798 };
5799 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5800 write_ranges.push(range);
5801 } else {
5802 read_ranges.push(range);
5803 }
5804 }
5805 }
5806
5807 this.highlight_background::<DocumentHighlightRead>(
5808 &read_ranges,
5809 |theme| theme.editor_document_highlight_read_background,
5810 cx,
5811 );
5812 this.highlight_background::<DocumentHighlightWrite>(
5813 &write_ranges,
5814 |theme| theme.editor_document_highlight_write_background,
5815 cx,
5816 );
5817 cx.notify();
5818 })
5819 .log_err();
5820 }
5821 }));
5822 None
5823 }
5824
5825 fn prepare_highlight_query_from_selection(
5826 &mut self,
5827 cx: &mut Context<Editor>,
5828 ) -> Option<(String, Range<Anchor>)> {
5829 if matches!(self.mode, EditorMode::SingleLine { .. }) {
5830 return None;
5831 }
5832 if !EditorSettings::get_global(cx).selection_highlight {
5833 return None;
5834 }
5835 if self.selections.count() != 1 || self.selections.line_mode {
5836 return None;
5837 }
5838 let selection = self.selections.newest::<Point>(cx);
5839 if selection.is_empty() || selection.start.row != selection.end.row {
5840 return None;
5841 }
5842 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5843 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
5844 let query = multi_buffer_snapshot
5845 .text_for_range(selection_anchor_range.clone())
5846 .collect::<String>();
5847 if query.trim().is_empty() {
5848 return None;
5849 }
5850 Some((query, selection_anchor_range))
5851 }
5852
5853 fn update_selection_occurrence_highlights(
5854 &mut self,
5855 query_text: String,
5856 query_range: Range<Anchor>,
5857 multi_buffer_range_to_query: Range<Point>,
5858 use_debounce: bool,
5859 window: &mut Window,
5860 cx: &mut Context<Editor>,
5861 ) -> Task<()> {
5862 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5863 cx.spawn_in(window, async move |editor, cx| {
5864 if use_debounce {
5865 cx.background_executor()
5866 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
5867 .await;
5868 }
5869 let match_task = cx.background_spawn(async move {
5870 let buffer_ranges = multi_buffer_snapshot
5871 .range_to_buffer_ranges(multi_buffer_range_to_query)
5872 .into_iter()
5873 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
5874 let mut match_ranges = Vec::new();
5875 let Ok(regex) = project::search::SearchQuery::text(
5876 query_text.clone(),
5877 false,
5878 false,
5879 false,
5880 Default::default(),
5881 Default::default(),
5882 false,
5883 None,
5884 ) else {
5885 return Vec::default();
5886 };
5887 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
5888 match_ranges.extend(
5889 regex
5890 .search(&buffer_snapshot, Some(search_range.clone()))
5891 .await
5892 .into_iter()
5893 .filter_map(|match_range| {
5894 let match_start = buffer_snapshot
5895 .anchor_after(search_range.start + match_range.start);
5896 let match_end = buffer_snapshot
5897 .anchor_before(search_range.start + match_range.end);
5898 let match_anchor_range = Anchor::range_in_buffer(
5899 excerpt_id,
5900 buffer_snapshot.remote_id(),
5901 match_start..match_end,
5902 );
5903 (match_anchor_range != query_range).then_some(match_anchor_range)
5904 }),
5905 );
5906 }
5907 match_ranges
5908 });
5909 let match_ranges = match_task.await;
5910 editor
5911 .update_in(cx, |editor, _, cx| {
5912 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5913 if !match_ranges.is_empty() {
5914 editor.highlight_background::<SelectedTextHighlight>(
5915 &match_ranges,
5916 |theme| theme.editor_document_highlight_bracket_background,
5917 cx,
5918 )
5919 }
5920 })
5921 .log_err();
5922 })
5923 }
5924
5925 fn refresh_selected_text_highlights(
5926 &mut self,
5927 on_buffer_edit: bool,
5928 window: &mut Window,
5929 cx: &mut Context<Editor>,
5930 ) {
5931 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
5932 else {
5933 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5934 self.quick_selection_highlight_task.take();
5935 self.debounced_selection_highlight_task.take();
5936 return;
5937 };
5938 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5939 if on_buffer_edit
5940 || self
5941 .quick_selection_highlight_task
5942 .as_ref()
5943 .map_or(true, |(prev_anchor_range, _)| {
5944 prev_anchor_range != &query_range
5945 })
5946 {
5947 let multi_buffer_visible_start = self
5948 .scroll_manager
5949 .anchor()
5950 .anchor
5951 .to_point(&multi_buffer_snapshot);
5952 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5953 multi_buffer_visible_start
5954 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5955 Bias::Left,
5956 );
5957 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5958 self.quick_selection_highlight_task = Some((
5959 query_range.clone(),
5960 self.update_selection_occurrence_highlights(
5961 query_text.clone(),
5962 query_range.clone(),
5963 multi_buffer_visible_range,
5964 false,
5965 window,
5966 cx,
5967 ),
5968 ));
5969 }
5970 if on_buffer_edit
5971 || self
5972 .debounced_selection_highlight_task
5973 .as_ref()
5974 .map_or(true, |(prev_anchor_range, _)| {
5975 prev_anchor_range != &query_range
5976 })
5977 {
5978 let multi_buffer_start = multi_buffer_snapshot
5979 .anchor_before(0)
5980 .to_point(&multi_buffer_snapshot);
5981 let multi_buffer_end = multi_buffer_snapshot
5982 .anchor_after(multi_buffer_snapshot.len())
5983 .to_point(&multi_buffer_snapshot);
5984 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
5985 self.debounced_selection_highlight_task = Some((
5986 query_range.clone(),
5987 self.update_selection_occurrence_highlights(
5988 query_text,
5989 query_range,
5990 multi_buffer_full_range,
5991 true,
5992 window,
5993 cx,
5994 ),
5995 ));
5996 }
5997 }
5998
5999 pub fn refresh_inline_completion(
6000 &mut self,
6001 debounce: bool,
6002 user_requested: bool,
6003 window: &mut Window,
6004 cx: &mut Context<Self>,
6005 ) -> Option<()> {
6006 let provider = self.edit_prediction_provider()?;
6007 let cursor = self.selections.newest_anchor().head();
6008 let (buffer, cursor_buffer_position) =
6009 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6010
6011 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6012 self.discard_inline_completion(false, cx);
6013 return None;
6014 }
6015
6016 if !user_requested
6017 && (!self.should_show_edit_predictions()
6018 || !self.is_focused(window)
6019 || buffer.read(cx).is_empty())
6020 {
6021 self.discard_inline_completion(false, cx);
6022 return None;
6023 }
6024
6025 self.update_visible_inline_completion(window, cx);
6026 provider.refresh(
6027 self.project.clone(),
6028 buffer,
6029 cursor_buffer_position,
6030 debounce,
6031 cx,
6032 );
6033 Some(())
6034 }
6035
6036 fn show_edit_predictions_in_menu(&self) -> bool {
6037 match self.edit_prediction_settings {
6038 EditPredictionSettings::Disabled => false,
6039 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6040 }
6041 }
6042
6043 pub fn edit_predictions_enabled(&self) -> bool {
6044 match self.edit_prediction_settings {
6045 EditPredictionSettings::Disabled => false,
6046 EditPredictionSettings::Enabled { .. } => true,
6047 }
6048 }
6049
6050 fn edit_prediction_requires_modifier(&self) -> bool {
6051 match self.edit_prediction_settings {
6052 EditPredictionSettings::Disabled => false,
6053 EditPredictionSettings::Enabled {
6054 preview_requires_modifier,
6055 ..
6056 } => preview_requires_modifier,
6057 }
6058 }
6059
6060 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6061 if self.edit_prediction_provider.is_none() {
6062 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6063 } else {
6064 let selection = self.selections.newest_anchor();
6065 let cursor = selection.head();
6066
6067 if let Some((buffer, cursor_buffer_position)) =
6068 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6069 {
6070 self.edit_prediction_settings =
6071 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6072 }
6073 }
6074 }
6075
6076 fn edit_prediction_settings_at_position(
6077 &self,
6078 buffer: &Entity<Buffer>,
6079 buffer_position: language::Anchor,
6080 cx: &App,
6081 ) -> EditPredictionSettings {
6082 if !self.mode.is_full()
6083 || !self.show_inline_completions_override.unwrap_or(true)
6084 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6085 {
6086 return EditPredictionSettings::Disabled;
6087 }
6088
6089 let buffer = buffer.read(cx);
6090
6091 let file = buffer.file();
6092
6093 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6094 return EditPredictionSettings::Disabled;
6095 };
6096
6097 let by_provider = matches!(
6098 self.menu_inline_completions_policy,
6099 MenuInlineCompletionsPolicy::ByProvider
6100 );
6101
6102 let show_in_menu = by_provider
6103 && self
6104 .edit_prediction_provider
6105 .as_ref()
6106 .map_or(false, |provider| {
6107 provider.provider.show_completions_in_menu()
6108 });
6109
6110 let preview_requires_modifier =
6111 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6112
6113 EditPredictionSettings::Enabled {
6114 show_in_menu,
6115 preview_requires_modifier,
6116 }
6117 }
6118
6119 fn should_show_edit_predictions(&self) -> bool {
6120 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6121 }
6122
6123 pub fn edit_prediction_preview_is_active(&self) -> bool {
6124 matches!(
6125 self.edit_prediction_preview,
6126 EditPredictionPreview::Active { .. }
6127 )
6128 }
6129
6130 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6131 let cursor = self.selections.newest_anchor().head();
6132 if let Some((buffer, cursor_position)) =
6133 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6134 {
6135 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6136 } else {
6137 false
6138 }
6139 }
6140
6141 pub fn supports_minimap(&self) -> bool {
6142 self.mode.is_full()
6143 }
6144
6145 fn edit_predictions_enabled_in_buffer(
6146 &self,
6147 buffer: &Entity<Buffer>,
6148 buffer_position: language::Anchor,
6149 cx: &App,
6150 ) -> bool {
6151 maybe!({
6152 if self.read_only(cx) {
6153 return Some(false);
6154 }
6155 let provider = self.edit_prediction_provider()?;
6156 if !provider.is_enabled(&buffer, buffer_position, cx) {
6157 return Some(false);
6158 }
6159 let buffer = buffer.read(cx);
6160 let Some(file) = buffer.file() else {
6161 return Some(true);
6162 };
6163 let settings = all_language_settings(Some(file), cx);
6164 Some(settings.edit_predictions_enabled_for_file(file, cx))
6165 })
6166 .unwrap_or(false)
6167 }
6168
6169 fn cycle_inline_completion(
6170 &mut self,
6171 direction: Direction,
6172 window: &mut Window,
6173 cx: &mut Context<Self>,
6174 ) -> Option<()> {
6175 let provider = self.edit_prediction_provider()?;
6176 let cursor = self.selections.newest_anchor().head();
6177 let (buffer, cursor_buffer_position) =
6178 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6179 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6180 return None;
6181 }
6182
6183 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6184 self.update_visible_inline_completion(window, cx);
6185
6186 Some(())
6187 }
6188
6189 pub fn show_inline_completion(
6190 &mut self,
6191 _: &ShowEditPrediction,
6192 window: &mut Window,
6193 cx: &mut Context<Self>,
6194 ) {
6195 if !self.has_active_inline_completion() {
6196 self.refresh_inline_completion(false, true, window, cx);
6197 return;
6198 }
6199
6200 self.update_visible_inline_completion(window, cx);
6201 }
6202
6203 pub fn display_cursor_names(
6204 &mut self,
6205 _: &DisplayCursorNames,
6206 window: &mut Window,
6207 cx: &mut Context<Self>,
6208 ) {
6209 self.show_cursor_names(window, cx);
6210 }
6211
6212 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6213 self.show_cursor_names = true;
6214 cx.notify();
6215 cx.spawn_in(window, async move |this, cx| {
6216 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6217 this.update(cx, |this, cx| {
6218 this.show_cursor_names = false;
6219 cx.notify()
6220 })
6221 .ok()
6222 })
6223 .detach();
6224 }
6225
6226 pub fn next_edit_prediction(
6227 &mut self,
6228 _: &NextEditPrediction,
6229 window: &mut Window,
6230 cx: &mut Context<Self>,
6231 ) {
6232 if self.has_active_inline_completion() {
6233 self.cycle_inline_completion(Direction::Next, window, cx);
6234 } else {
6235 let is_copilot_disabled = self
6236 .refresh_inline_completion(false, true, window, cx)
6237 .is_none();
6238 if is_copilot_disabled {
6239 cx.propagate();
6240 }
6241 }
6242 }
6243
6244 pub fn previous_edit_prediction(
6245 &mut self,
6246 _: &PreviousEditPrediction,
6247 window: &mut Window,
6248 cx: &mut Context<Self>,
6249 ) {
6250 if self.has_active_inline_completion() {
6251 self.cycle_inline_completion(Direction::Prev, window, cx);
6252 } else {
6253 let is_copilot_disabled = self
6254 .refresh_inline_completion(false, true, window, cx)
6255 .is_none();
6256 if is_copilot_disabled {
6257 cx.propagate();
6258 }
6259 }
6260 }
6261
6262 pub fn accept_edit_prediction(
6263 &mut self,
6264 _: &AcceptEditPrediction,
6265 window: &mut Window,
6266 cx: &mut Context<Self>,
6267 ) {
6268 if self.show_edit_predictions_in_menu() {
6269 self.hide_context_menu(window, cx);
6270 }
6271
6272 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6273 return;
6274 };
6275
6276 self.report_inline_completion_event(
6277 active_inline_completion.completion_id.clone(),
6278 true,
6279 cx,
6280 );
6281
6282 match &active_inline_completion.completion {
6283 InlineCompletion::Move { target, .. } => {
6284 let target = *target;
6285
6286 if let Some(position_map) = &self.last_position_map {
6287 if position_map
6288 .visible_row_range
6289 .contains(&target.to_display_point(&position_map.snapshot).row())
6290 || !self.edit_prediction_requires_modifier()
6291 {
6292 self.unfold_ranges(&[target..target], true, false, cx);
6293 // Note that this is also done in vim's handler of the Tab action.
6294 self.change_selections(
6295 Some(Autoscroll::newest()),
6296 window,
6297 cx,
6298 |selections| {
6299 selections.select_anchor_ranges([target..target]);
6300 },
6301 );
6302 self.clear_row_highlights::<EditPredictionPreview>();
6303
6304 self.edit_prediction_preview
6305 .set_previous_scroll_position(None);
6306 } else {
6307 self.edit_prediction_preview
6308 .set_previous_scroll_position(Some(
6309 position_map.snapshot.scroll_anchor,
6310 ));
6311
6312 self.highlight_rows::<EditPredictionPreview>(
6313 target..target,
6314 cx.theme().colors().editor_highlighted_line_background,
6315 RowHighlightOptions {
6316 autoscroll: true,
6317 ..Default::default()
6318 },
6319 cx,
6320 );
6321 self.request_autoscroll(Autoscroll::fit(), cx);
6322 }
6323 }
6324 }
6325 InlineCompletion::Edit { edits, .. } => {
6326 if let Some(provider) = self.edit_prediction_provider() {
6327 provider.accept(cx);
6328 }
6329
6330 let snapshot = self.buffer.read(cx).snapshot(cx);
6331 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6332
6333 self.buffer.update(cx, |buffer, cx| {
6334 buffer.edit(edits.iter().cloned(), None, cx)
6335 });
6336
6337 self.change_selections(None, window, cx, |s| {
6338 s.select_anchor_ranges([last_edit_end..last_edit_end])
6339 });
6340
6341 self.update_visible_inline_completion(window, cx);
6342 if self.active_inline_completion.is_none() {
6343 self.refresh_inline_completion(true, true, window, cx);
6344 }
6345
6346 cx.notify();
6347 }
6348 }
6349
6350 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6351 }
6352
6353 pub fn accept_partial_inline_completion(
6354 &mut self,
6355 _: &AcceptPartialEditPrediction,
6356 window: &mut Window,
6357 cx: &mut Context<Self>,
6358 ) {
6359 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6360 return;
6361 };
6362 if self.selections.count() != 1 {
6363 return;
6364 }
6365
6366 self.report_inline_completion_event(
6367 active_inline_completion.completion_id.clone(),
6368 true,
6369 cx,
6370 );
6371
6372 match &active_inline_completion.completion {
6373 InlineCompletion::Move { target, .. } => {
6374 let target = *target;
6375 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6376 selections.select_anchor_ranges([target..target]);
6377 });
6378 }
6379 InlineCompletion::Edit { edits, .. } => {
6380 // Find an insertion that starts at the cursor position.
6381 let snapshot = self.buffer.read(cx).snapshot(cx);
6382 let cursor_offset = self.selections.newest::<usize>(cx).head();
6383 let insertion = edits.iter().find_map(|(range, text)| {
6384 let range = range.to_offset(&snapshot);
6385 if range.is_empty() && range.start == cursor_offset {
6386 Some(text)
6387 } else {
6388 None
6389 }
6390 });
6391
6392 if let Some(text) = insertion {
6393 let mut partial_completion = text
6394 .chars()
6395 .by_ref()
6396 .take_while(|c| c.is_alphabetic())
6397 .collect::<String>();
6398 if partial_completion.is_empty() {
6399 partial_completion = text
6400 .chars()
6401 .by_ref()
6402 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6403 .collect::<String>();
6404 }
6405
6406 cx.emit(EditorEvent::InputHandled {
6407 utf16_range_to_replace: None,
6408 text: partial_completion.clone().into(),
6409 });
6410
6411 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6412
6413 self.refresh_inline_completion(true, true, window, cx);
6414 cx.notify();
6415 } else {
6416 self.accept_edit_prediction(&Default::default(), window, cx);
6417 }
6418 }
6419 }
6420 }
6421
6422 fn discard_inline_completion(
6423 &mut self,
6424 should_report_inline_completion_event: bool,
6425 cx: &mut Context<Self>,
6426 ) -> bool {
6427 if should_report_inline_completion_event {
6428 let completion_id = self
6429 .active_inline_completion
6430 .as_ref()
6431 .and_then(|active_completion| active_completion.completion_id.clone());
6432
6433 self.report_inline_completion_event(completion_id, false, cx);
6434 }
6435
6436 if let Some(provider) = self.edit_prediction_provider() {
6437 provider.discard(cx);
6438 }
6439
6440 self.take_active_inline_completion(cx)
6441 }
6442
6443 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6444 let Some(provider) = self.edit_prediction_provider() else {
6445 return;
6446 };
6447
6448 let Some((_, buffer, _)) = self
6449 .buffer
6450 .read(cx)
6451 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6452 else {
6453 return;
6454 };
6455
6456 let extension = buffer
6457 .read(cx)
6458 .file()
6459 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6460
6461 let event_type = match accepted {
6462 true => "Edit Prediction Accepted",
6463 false => "Edit Prediction Discarded",
6464 };
6465 telemetry::event!(
6466 event_type,
6467 provider = provider.name(),
6468 prediction_id = id,
6469 suggestion_accepted = accepted,
6470 file_extension = extension,
6471 );
6472 }
6473
6474 pub fn has_active_inline_completion(&self) -> bool {
6475 self.active_inline_completion.is_some()
6476 }
6477
6478 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6479 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6480 return false;
6481 };
6482
6483 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6484 self.clear_highlights::<InlineCompletionHighlight>(cx);
6485 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6486 true
6487 }
6488
6489 /// Returns true when we're displaying the edit prediction popover below the cursor
6490 /// like we are not previewing and the LSP autocomplete menu is visible
6491 /// or we are in `when_holding_modifier` mode.
6492 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6493 if self.edit_prediction_preview_is_active()
6494 || !self.show_edit_predictions_in_menu()
6495 || !self.edit_predictions_enabled()
6496 {
6497 return false;
6498 }
6499
6500 if self.has_visible_completions_menu() {
6501 return true;
6502 }
6503
6504 has_completion && self.edit_prediction_requires_modifier()
6505 }
6506
6507 fn handle_modifiers_changed(
6508 &mut self,
6509 modifiers: Modifiers,
6510 position_map: &PositionMap,
6511 window: &mut Window,
6512 cx: &mut Context<Self>,
6513 ) {
6514 if self.show_edit_predictions_in_menu() {
6515 self.update_edit_prediction_preview(&modifiers, window, cx);
6516 }
6517
6518 self.update_selection_mode(&modifiers, position_map, window, cx);
6519
6520 let mouse_position = window.mouse_position();
6521 if !position_map.text_hitbox.is_hovered(window) {
6522 return;
6523 }
6524
6525 self.update_hovered_link(
6526 position_map.point_for_position(mouse_position),
6527 &position_map.snapshot,
6528 modifiers,
6529 window,
6530 cx,
6531 )
6532 }
6533
6534 fn update_selection_mode(
6535 &mut self,
6536 modifiers: &Modifiers,
6537 position_map: &PositionMap,
6538 window: &mut Window,
6539 cx: &mut Context<Self>,
6540 ) {
6541 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6542 return;
6543 }
6544
6545 let mouse_position = window.mouse_position();
6546 let point_for_position = position_map.point_for_position(mouse_position);
6547 let position = point_for_position.previous_valid;
6548
6549 self.select(
6550 SelectPhase::BeginColumnar {
6551 position,
6552 reset: false,
6553 goal_column: point_for_position.exact_unclipped.column(),
6554 },
6555 window,
6556 cx,
6557 );
6558 }
6559
6560 fn update_edit_prediction_preview(
6561 &mut self,
6562 modifiers: &Modifiers,
6563 window: &mut Window,
6564 cx: &mut Context<Self>,
6565 ) {
6566 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6567 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6568 return;
6569 };
6570
6571 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6572 if matches!(
6573 self.edit_prediction_preview,
6574 EditPredictionPreview::Inactive { .. }
6575 ) {
6576 self.edit_prediction_preview = EditPredictionPreview::Active {
6577 previous_scroll_position: None,
6578 since: Instant::now(),
6579 };
6580
6581 self.update_visible_inline_completion(window, cx);
6582 cx.notify();
6583 }
6584 } else if let EditPredictionPreview::Active {
6585 previous_scroll_position,
6586 since,
6587 } = self.edit_prediction_preview
6588 {
6589 if let (Some(previous_scroll_position), Some(position_map)) =
6590 (previous_scroll_position, self.last_position_map.as_ref())
6591 {
6592 self.set_scroll_position(
6593 previous_scroll_position
6594 .scroll_position(&position_map.snapshot.display_snapshot),
6595 window,
6596 cx,
6597 );
6598 }
6599
6600 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6601 released_too_fast: since.elapsed() < Duration::from_millis(200),
6602 };
6603 self.clear_row_highlights::<EditPredictionPreview>();
6604 self.update_visible_inline_completion(window, cx);
6605 cx.notify();
6606 }
6607 }
6608
6609 fn update_visible_inline_completion(
6610 &mut self,
6611 _window: &mut Window,
6612 cx: &mut Context<Self>,
6613 ) -> Option<()> {
6614 let selection = self.selections.newest_anchor();
6615 let cursor = selection.head();
6616 let multibuffer = self.buffer.read(cx).snapshot(cx);
6617 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6618 let excerpt_id = cursor.excerpt_id;
6619
6620 let show_in_menu = self.show_edit_predictions_in_menu();
6621 let completions_menu_has_precedence = !show_in_menu
6622 && (self.context_menu.borrow().is_some()
6623 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6624
6625 if completions_menu_has_precedence
6626 || !offset_selection.is_empty()
6627 || self
6628 .active_inline_completion
6629 .as_ref()
6630 .map_or(false, |completion| {
6631 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6632 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6633 !invalidation_range.contains(&offset_selection.head())
6634 })
6635 {
6636 self.discard_inline_completion(false, cx);
6637 return None;
6638 }
6639
6640 self.take_active_inline_completion(cx);
6641 let Some(provider) = self.edit_prediction_provider() else {
6642 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6643 return None;
6644 };
6645
6646 let (buffer, cursor_buffer_position) =
6647 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6648
6649 self.edit_prediction_settings =
6650 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6651
6652 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6653
6654 if self.edit_prediction_indent_conflict {
6655 let cursor_point = cursor.to_point(&multibuffer);
6656
6657 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6658
6659 if let Some((_, indent)) = indents.iter().next() {
6660 if indent.len == cursor_point.column {
6661 self.edit_prediction_indent_conflict = false;
6662 }
6663 }
6664 }
6665
6666 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6667 let edits = inline_completion
6668 .edits
6669 .into_iter()
6670 .flat_map(|(range, new_text)| {
6671 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6672 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6673 Some((start..end, new_text))
6674 })
6675 .collect::<Vec<_>>();
6676 if edits.is_empty() {
6677 return None;
6678 }
6679
6680 let first_edit_start = edits.first().unwrap().0.start;
6681 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6682 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6683
6684 let last_edit_end = edits.last().unwrap().0.end;
6685 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6686 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6687
6688 let cursor_row = cursor.to_point(&multibuffer).row;
6689
6690 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6691
6692 let mut inlay_ids = Vec::new();
6693 let invalidation_row_range;
6694 let move_invalidation_row_range = if cursor_row < edit_start_row {
6695 Some(cursor_row..edit_end_row)
6696 } else if cursor_row > edit_end_row {
6697 Some(edit_start_row..cursor_row)
6698 } else {
6699 None
6700 };
6701 let is_move =
6702 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6703 let completion = if is_move {
6704 invalidation_row_range =
6705 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6706 let target = first_edit_start;
6707 InlineCompletion::Move { target, snapshot }
6708 } else {
6709 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6710 && !self.inline_completions_hidden_for_vim_mode;
6711
6712 if show_completions_in_buffer {
6713 if edits
6714 .iter()
6715 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6716 {
6717 let mut inlays = Vec::new();
6718 for (range, new_text) in &edits {
6719 let inlay = Inlay::inline_completion(
6720 post_inc(&mut self.next_inlay_id),
6721 range.start,
6722 new_text.as_str(),
6723 );
6724 inlay_ids.push(inlay.id);
6725 inlays.push(inlay);
6726 }
6727
6728 self.splice_inlays(&[], inlays, cx);
6729 } else {
6730 let background_color = cx.theme().status().deleted_background;
6731 self.highlight_text::<InlineCompletionHighlight>(
6732 edits.iter().map(|(range, _)| range.clone()).collect(),
6733 HighlightStyle {
6734 background_color: Some(background_color),
6735 ..Default::default()
6736 },
6737 cx,
6738 );
6739 }
6740 }
6741
6742 invalidation_row_range = edit_start_row..edit_end_row;
6743
6744 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6745 if provider.show_tab_accept_marker() {
6746 EditDisplayMode::TabAccept
6747 } else {
6748 EditDisplayMode::Inline
6749 }
6750 } else {
6751 EditDisplayMode::DiffPopover
6752 };
6753
6754 InlineCompletion::Edit {
6755 edits,
6756 edit_preview: inline_completion.edit_preview,
6757 display_mode,
6758 snapshot,
6759 }
6760 };
6761
6762 let invalidation_range = multibuffer
6763 .anchor_before(Point::new(invalidation_row_range.start, 0))
6764 ..multibuffer.anchor_after(Point::new(
6765 invalidation_row_range.end,
6766 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6767 ));
6768
6769 self.stale_inline_completion_in_menu = None;
6770 self.active_inline_completion = Some(InlineCompletionState {
6771 inlay_ids,
6772 completion,
6773 completion_id: inline_completion.id,
6774 invalidation_range,
6775 });
6776
6777 cx.notify();
6778
6779 Some(())
6780 }
6781
6782 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6783 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6784 }
6785
6786 fn clear_tasks(&mut self) {
6787 self.tasks.clear()
6788 }
6789
6790 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6791 if self.tasks.insert(key, value).is_some() {
6792 // This case should hopefully be rare, but just in case...
6793 log::error!(
6794 "multiple different run targets found on a single line, only the last target will be rendered"
6795 )
6796 }
6797 }
6798
6799 /// Get all display points of breakpoints that will be rendered within editor
6800 ///
6801 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6802 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6803 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6804 fn active_breakpoints(
6805 &self,
6806 range: Range<DisplayRow>,
6807 window: &mut Window,
6808 cx: &mut Context<Self>,
6809 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
6810 let mut breakpoint_display_points = HashMap::default();
6811
6812 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
6813 return breakpoint_display_points;
6814 };
6815
6816 let snapshot = self.snapshot(window, cx);
6817
6818 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
6819 let Some(project) = self.project.as_ref() else {
6820 return breakpoint_display_points;
6821 };
6822
6823 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
6824 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
6825
6826 for (buffer_snapshot, range, excerpt_id) in
6827 multi_buffer_snapshot.range_to_buffer_ranges(range)
6828 {
6829 let Some(buffer) = project.read_with(cx, |this, cx| {
6830 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
6831 }) else {
6832 continue;
6833 };
6834 let breakpoints = breakpoint_store.read(cx).breakpoints(
6835 &buffer,
6836 Some(
6837 buffer_snapshot.anchor_before(range.start)
6838 ..buffer_snapshot.anchor_after(range.end),
6839 ),
6840 buffer_snapshot,
6841 cx,
6842 );
6843 for (anchor, breakpoint) in breakpoints {
6844 let multi_buffer_anchor =
6845 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), *anchor);
6846 let position = multi_buffer_anchor
6847 .to_point(&multi_buffer_snapshot)
6848 .to_display_point(&snapshot);
6849
6850 breakpoint_display_points
6851 .insert(position.row(), (multi_buffer_anchor, breakpoint.clone()));
6852 }
6853 }
6854
6855 breakpoint_display_points
6856 }
6857
6858 fn breakpoint_context_menu(
6859 &self,
6860 anchor: Anchor,
6861 window: &mut Window,
6862 cx: &mut Context<Self>,
6863 ) -> Entity<ui::ContextMenu> {
6864 let weak_editor = cx.weak_entity();
6865 let focus_handle = self.focus_handle(cx);
6866
6867 let row = self
6868 .buffer
6869 .read(cx)
6870 .snapshot(cx)
6871 .summary_for_anchor::<Point>(&anchor)
6872 .row;
6873
6874 let breakpoint = self
6875 .breakpoint_at_row(row, window, cx)
6876 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
6877
6878 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
6879 "Edit Log Breakpoint"
6880 } else {
6881 "Set Log Breakpoint"
6882 };
6883
6884 let condition_breakpoint_msg = if breakpoint
6885 .as_ref()
6886 .is_some_and(|bp| bp.1.condition.is_some())
6887 {
6888 "Edit Condition Breakpoint"
6889 } else {
6890 "Set Condition Breakpoint"
6891 };
6892
6893 let hit_condition_breakpoint_msg = if breakpoint
6894 .as_ref()
6895 .is_some_and(|bp| bp.1.hit_condition.is_some())
6896 {
6897 "Edit Hit Condition Breakpoint"
6898 } else {
6899 "Set Hit Condition Breakpoint"
6900 };
6901
6902 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
6903 "Unset Breakpoint"
6904 } else {
6905 "Set Breakpoint"
6906 };
6907
6908 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
6909 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
6910
6911 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
6912 BreakpointState::Enabled => Some("Disable"),
6913 BreakpointState::Disabled => Some("Enable"),
6914 });
6915
6916 let (anchor, breakpoint) =
6917 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
6918
6919 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6920 menu.on_blur_subscription(Subscription::new(|| {}))
6921 .context(focus_handle)
6922 .when(run_to_cursor, |this| {
6923 let weak_editor = weak_editor.clone();
6924 this.entry("Run to cursor", None, move |window, cx| {
6925 weak_editor
6926 .update(cx, |editor, cx| {
6927 editor.change_selections(None, window, cx, |s| {
6928 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
6929 });
6930 })
6931 .ok();
6932
6933 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
6934 })
6935 .separator()
6936 })
6937 .when_some(toggle_state_msg, |this, msg| {
6938 this.entry(msg, None, {
6939 let weak_editor = weak_editor.clone();
6940 let breakpoint = breakpoint.clone();
6941 move |_window, cx| {
6942 weak_editor
6943 .update(cx, |this, cx| {
6944 this.edit_breakpoint_at_anchor(
6945 anchor,
6946 breakpoint.as_ref().clone(),
6947 BreakpointEditAction::InvertState,
6948 cx,
6949 );
6950 })
6951 .log_err();
6952 }
6953 })
6954 })
6955 .entry(set_breakpoint_msg, None, {
6956 let weak_editor = weak_editor.clone();
6957 let breakpoint = breakpoint.clone();
6958 move |_window, cx| {
6959 weak_editor
6960 .update(cx, |this, cx| {
6961 this.edit_breakpoint_at_anchor(
6962 anchor,
6963 breakpoint.as_ref().clone(),
6964 BreakpointEditAction::Toggle,
6965 cx,
6966 );
6967 })
6968 .log_err();
6969 }
6970 })
6971 .entry(log_breakpoint_msg, None, {
6972 let breakpoint = breakpoint.clone();
6973 let weak_editor = weak_editor.clone();
6974 move |window, cx| {
6975 weak_editor
6976 .update(cx, |this, cx| {
6977 this.add_edit_breakpoint_block(
6978 anchor,
6979 breakpoint.as_ref(),
6980 BreakpointPromptEditAction::Log,
6981 window,
6982 cx,
6983 );
6984 })
6985 .log_err();
6986 }
6987 })
6988 .entry(condition_breakpoint_msg, None, {
6989 let breakpoint = breakpoint.clone();
6990 let weak_editor = weak_editor.clone();
6991 move |window, cx| {
6992 weak_editor
6993 .update(cx, |this, cx| {
6994 this.add_edit_breakpoint_block(
6995 anchor,
6996 breakpoint.as_ref(),
6997 BreakpointPromptEditAction::Condition,
6998 window,
6999 cx,
7000 );
7001 })
7002 .log_err();
7003 }
7004 })
7005 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7006 weak_editor
7007 .update(cx, |this, cx| {
7008 this.add_edit_breakpoint_block(
7009 anchor,
7010 breakpoint.as_ref(),
7011 BreakpointPromptEditAction::HitCondition,
7012 window,
7013 cx,
7014 );
7015 })
7016 .log_err();
7017 })
7018 })
7019 }
7020
7021 fn render_breakpoint(
7022 &self,
7023 position: Anchor,
7024 row: DisplayRow,
7025 breakpoint: &Breakpoint,
7026 cx: &mut Context<Self>,
7027 ) -> IconButton {
7028 // Is it a breakpoint that shows up when hovering over gutter?
7029 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7030 (false, false),
7031 |PhantomBreakpointIndicator {
7032 is_active,
7033 display_row,
7034 collides_with_existing_breakpoint,
7035 }| {
7036 (
7037 is_active && display_row == row,
7038 collides_with_existing_breakpoint,
7039 )
7040 },
7041 );
7042
7043 let (color, icon) = {
7044 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7045 (false, false) => ui::IconName::DebugBreakpoint,
7046 (true, false) => ui::IconName::DebugLogBreakpoint,
7047 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7048 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7049 };
7050
7051 let color = if is_phantom {
7052 Color::Hint
7053 } else {
7054 Color::Debugger
7055 };
7056
7057 (color, icon)
7058 };
7059
7060 let breakpoint = Arc::from(breakpoint.clone());
7061
7062 let alt_as_text = gpui::Keystroke {
7063 modifiers: Modifiers::secondary_key(),
7064 ..Default::default()
7065 };
7066 let primary_action_text = if breakpoint.is_disabled() {
7067 "enable"
7068 } else if is_phantom && !collides_with_existing {
7069 "set"
7070 } else {
7071 "unset"
7072 };
7073 let mut primary_text = format!("Click to {primary_action_text}");
7074 if collides_with_existing && !breakpoint.is_disabled() {
7075 use std::fmt::Write;
7076 write!(primary_text, ", {alt_as_text}-click to disable").ok();
7077 }
7078 let primary_text = SharedString::from(primary_text);
7079 let focus_handle = self.focus_handle.clone();
7080 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7081 .icon_size(IconSize::XSmall)
7082 .size(ui::ButtonSize::None)
7083 .icon_color(color)
7084 .style(ButtonStyle::Transparent)
7085 .on_click(cx.listener({
7086 let breakpoint = breakpoint.clone();
7087
7088 move |editor, event: &ClickEvent, window, cx| {
7089 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7090 BreakpointEditAction::InvertState
7091 } else {
7092 BreakpointEditAction::Toggle
7093 };
7094
7095 window.focus(&editor.focus_handle(cx));
7096 editor.edit_breakpoint_at_anchor(
7097 position,
7098 breakpoint.as_ref().clone(),
7099 edit_action,
7100 cx,
7101 );
7102 }
7103 }))
7104 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7105 editor.set_breakpoint_context_menu(
7106 row,
7107 Some(position),
7108 event.down.position,
7109 window,
7110 cx,
7111 );
7112 }))
7113 .tooltip(move |window, cx| {
7114 Tooltip::with_meta_in(
7115 primary_text.clone(),
7116 None,
7117 "Right-click for more options",
7118 &focus_handle,
7119 window,
7120 cx,
7121 )
7122 })
7123 }
7124
7125 fn build_tasks_context(
7126 project: &Entity<Project>,
7127 buffer: &Entity<Buffer>,
7128 buffer_row: u32,
7129 tasks: &Arc<RunnableTasks>,
7130 cx: &mut Context<Self>,
7131 ) -> Task<Option<task::TaskContext>> {
7132 let position = Point::new(buffer_row, tasks.column);
7133 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7134 let location = Location {
7135 buffer: buffer.clone(),
7136 range: range_start..range_start,
7137 };
7138 // Fill in the environmental variables from the tree-sitter captures
7139 let mut captured_task_variables = TaskVariables::default();
7140 for (capture_name, value) in tasks.extra_variables.clone() {
7141 captured_task_variables.insert(
7142 task::VariableName::Custom(capture_name.into()),
7143 value.clone(),
7144 );
7145 }
7146 project.update(cx, |project, cx| {
7147 project.task_store().update(cx, |task_store, cx| {
7148 task_store.task_context_for_location(captured_task_variables, location, cx)
7149 })
7150 })
7151 }
7152
7153 pub fn spawn_nearest_task(
7154 &mut self,
7155 action: &SpawnNearestTask,
7156 window: &mut Window,
7157 cx: &mut Context<Self>,
7158 ) {
7159 let Some((workspace, _)) = self.workspace.clone() else {
7160 return;
7161 };
7162 let Some(project) = self.project.clone() else {
7163 return;
7164 };
7165
7166 // Try to find a closest, enclosing node using tree-sitter that has a
7167 // task
7168 let Some((buffer, buffer_row, tasks)) = self
7169 .find_enclosing_node_task(cx)
7170 // Or find the task that's closest in row-distance.
7171 .or_else(|| self.find_closest_task(cx))
7172 else {
7173 return;
7174 };
7175
7176 let reveal_strategy = action.reveal;
7177 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7178 cx.spawn_in(window, async move |_, cx| {
7179 let context = task_context.await?;
7180 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7181
7182 let resolved = &mut resolved_task.resolved;
7183 resolved.reveal = reveal_strategy;
7184
7185 workspace
7186 .update_in(cx, |workspace, window, cx| {
7187 workspace.schedule_resolved_task(
7188 task_source_kind,
7189 resolved_task,
7190 false,
7191 window,
7192 cx,
7193 );
7194 })
7195 .ok()
7196 })
7197 .detach();
7198 }
7199
7200 fn find_closest_task(
7201 &mut self,
7202 cx: &mut Context<Self>,
7203 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7204 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7205
7206 let ((buffer_id, row), tasks) = self
7207 .tasks
7208 .iter()
7209 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7210
7211 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7212 let tasks = Arc::new(tasks.to_owned());
7213 Some((buffer, *row, tasks))
7214 }
7215
7216 fn find_enclosing_node_task(
7217 &mut self,
7218 cx: &mut Context<Self>,
7219 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7220 let snapshot = self.buffer.read(cx).snapshot(cx);
7221 let offset = self.selections.newest::<usize>(cx).head();
7222 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7223 let buffer_id = excerpt.buffer().remote_id();
7224
7225 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7226 let mut cursor = layer.node().walk();
7227
7228 while cursor.goto_first_child_for_byte(offset).is_some() {
7229 if cursor.node().end_byte() == offset {
7230 cursor.goto_next_sibling();
7231 }
7232 }
7233
7234 // Ascend to the smallest ancestor that contains the range and has a task.
7235 loop {
7236 let node = cursor.node();
7237 let node_range = node.byte_range();
7238 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7239
7240 // Check if this node contains our offset
7241 if node_range.start <= offset && node_range.end >= offset {
7242 // If it contains offset, check for task
7243 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7244 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7245 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7246 }
7247 }
7248
7249 if !cursor.goto_parent() {
7250 break;
7251 }
7252 }
7253 None
7254 }
7255
7256 fn render_run_indicator(
7257 &self,
7258 _style: &EditorStyle,
7259 is_active: bool,
7260 row: DisplayRow,
7261 breakpoint: Option<(Anchor, Breakpoint)>,
7262 cx: &mut Context<Self>,
7263 ) -> IconButton {
7264 let color = Color::Muted;
7265 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
7266
7267 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7268 .shape(ui::IconButtonShape::Square)
7269 .icon_size(IconSize::XSmall)
7270 .icon_color(color)
7271 .toggle_state(is_active)
7272 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7273 let quick_launch = e.down.button == MouseButton::Left;
7274 window.focus(&editor.focus_handle(cx));
7275 editor.toggle_code_actions(
7276 &ToggleCodeActions {
7277 deployed_from_indicator: Some(row),
7278 quick_launch,
7279 },
7280 window,
7281 cx,
7282 );
7283 }))
7284 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7285 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7286 }))
7287 }
7288
7289 pub fn context_menu_visible(&self) -> bool {
7290 !self.edit_prediction_preview_is_active()
7291 && self
7292 .context_menu
7293 .borrow()
7294 .as_ref()
7295 .map_or(false, |menu| menu.visible())
7296 }
7297
7298 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7299 self.context_menu
7300 .borrow()
7301 .as_ref()
7302 .map(|menu| menu.origin())
7303 }
7304
7305 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7306 self.context_menu_options = Some(options);
7307 }
7308
7309 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7310 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7311
7312 fn render_edit_prediction_popover(
7313 &mut self,
7314 text_bounds: &Bounds<Pixels>,
7315 content_origin: gpui::Point<Pixels>,
7316 right_margin: Pixels,
7317 editor_snapshot: &EditorSnapshot,
7318 visible_row_range: Range<DisplayRow>,
7319 scroll_top: f32,
7320 scroll_bottom: f32,
7321 line_layouts: &[LineWithInvisibles],
7322 line_height: Pixels,
7323 scroll_pixel_position: gpui::Point<Pixels>,
7324 newest_selection_head: Option<DisplayPoint>,
7325 editor_width: Pixels,
7326 style: &EditorStyle,
7327 window: &mut Window,
7328 cx: &mut App,
7329 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7330 if self.mode().is_minimap() {
7331 return None;
7332 }
7333 let active_inline_completion = self.active_inline_completion.as_ref()?;
7334
7335 if self.edit_prediction_visible_in_cursor_popover(true) {
7336 return None;
7337 }
7338
7339 match &active_inline_completion.completion {
7340 InlineCompletion::Move { target, .. } => {
7341 let target_display_point = target.to_display_point(editor_snapshot);
7342
7343 if self.edit_prediction_requires_modifier() {
7344 if !self.edit_prediction_preview_is_active() {
7345 return None;
7346 }
7347
7348 self.render_edit_prediction_modifier_jump_popover(
7349 text_bounds,
7350 content_origin,
7351 visible_row_range,
7352 line_layouts,
7353 line_height,
7354 scroll_pixel_position,
7355 newest_selection_head,
7356 target_display_point,
7357 window,
7358 cx,
7359 )
7360 } else {
7361 self.render_edit_prediction_eager_jump_popover(
7362 text_bounds,
7363 content_origin,
7364 editor_snapshot,
7365 visible_row_range,
7366 scroll_top,
7367 scroll_bottom,
7368 line_height,
7369 scroll_pixel_position,
7370 target_display_point,
7371 editor_width,
7372 window,
7373 cx,
7374 )
7375 }
7376 }
7377 InlineCompletion::Edit {
7378 display_mode: EditDisplayMode::Inline,
7379 ..
7380 } => None,
7381 InlineCompletion::Edit {
7382 display_mode: EditDisplayMode::TabAccept,
7383 edits,
7384 ..
7385 } => {
7386 let range = &edits.first()?.0;
7387 let target_display_point = range.end.to_display_point(editor_snapshot);
7388
7389 self.render_edit_prediction_end_of_line_popover(
7390 "Accept",
7391 editor_snapshot,
7392 visible_row_range,
7393 target_display_point,
7394 line_height,
7395 scroll_pixel_position,
7396 content_origin,
7397 editor_width,
7398 window,
7399 cx,
7400 )
7401 }
7402 InlineCompletion::Edit {
7403 edits,
7404 edit_preview,
7405 display_mode: EditDisplayMode::DiffPopover,
7406 snapshot,
7407 } => self.render_edit_prediction_diff_popover(
7408 text_bounds,
7409 content_origin,
7410 right_margin,
7411 editor_snapshot,
7412 visible_row_range,
7413 line_layouts,
7414 line_height,
7415 scroll_pixel_position,
7416 newest_selection_head,
7417 editor_width,
7418 style,
7419 edits,
7420 edit_preview,
7421 snapshot,
7422 window,
7423 cx,
7424 ),
7425 }
7426 }
7427
7428 fn render_edit_prediction_modifier_jump_popover(
7429 &mut self,
7430 text_bounds: &Bounds<Pixels>,
7431 content_origin: gpui::Point<Pixels>,
7432 visible_row_range: Range<DisplayRow>,
7433 line_layouts: &[LineWithInvisibles],
7434 line_height: Pixels,
7435 scroll_pixel_position: gpui::Point<Pixels>,
7436 newest_selection_head: Option<DisplayPoint>,
7437 target_display_point: DisplayPoint,
7438 window: &mut Window,
7439 cx: &mut App,
7440 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7441 let scrolled_content_origin =
7442 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7443
7444 const SCROLL_PADDING_Y: Pixels = px(12.);
7445
7446 if target_display_point.row() < visible_row_range.start {
7447 return self.render_edit_prediction_scroll_popover(
7448 |_| SCROLL_PADDING_Y,
7449 IconName::ArrowUp,
7450 visible_row_range,
7451 line_layouts,
7452 newest_selection_head,
7453 scrolled_content_origin,
7454 window,
7455 cx,
7456 );
7457 } else if target_display_point.row() >= visible_row_range.end {
7458 return self.render_edit_prediction_scroll_popover(
7459 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7460 IconName::ArrowDown,
7461 visible_row_range,
7462 line_layouts,
7463 newest_selection_head,
7464 scrolled_content_origin,
7465 window,
7466 cx,
7467 );
7468 }
7469
7470 const POLE_WIDTH: Pixels = px(2.);
7471
7472 let line_layout =
7473 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7474 let target_column = target_display_point.column() as usize;
7475
7476 let target_x = line_layout.x_for_index(target_column);
7477 let target_y =
7478 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7479
7480 let flag_on_right = target_x < text_bounds.size.width / 2.;
7481
7482 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7483 border_color.l += 0.001;
7484
7485 let mut element = v_flex()
7486 .items_end()
7487 .when(flag_on_right, |el| el.items_start())
7488 .child(if flag_on_right {
7489 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7490 .rounded_bl(px(0.))
7491 .rounded_tl(px(0.))
7492 .border_l_2()
7493 .border_color(border_color)
7494 } else {
7495 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7496 .rounded_br(px(0.))
7497 .rounded_tr(px(0.))
7498 .border_r_2()
7499 .border_color(border_color)
7500 })
7501 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7502 .into_any();
7503
7504 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7505
7506 let mut origin = scrolled_content_origin + point(target_x, target_y)
7507 - point(
7508 if flag_on_right {
7509 POLE_WIDTH
7510 } else {
7511 size.width - POLE_WIDTH
7512 },
7513 size.height - line_height,
7514 );
7515
7516 origin.x = origin.x.max(content_origin.x);
7517
7518 element.prepaint_at(origin, window, cx);
7519
7520 Some((element, origin))
7521 }
7522
7523 fn render_edit_prediction_scroll_popover(
7524 &mut self,
7525 to_y: impl Fn(Size<Pixels>) -> Pixels,
7526 scroll_icon: IconName,
7527 visible_row_range: Range<DisplayRow>,
7528 line_layouts: &[LineWithInvisibles],
7529 newest_selection_head: Option<DisplayPoint>,
7530 scrolled_content_origin: gpui::Point<Pixels>,
7531 window: &mut Window,
7532 cx: &mut App,
7533 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7534 let mut element = self
7535 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7536 .into_any();
7537
7538 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7539
7540 let cursor = newest_selection_head?;
7541 let cursor_row_layout =
7542 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7543 let cursor_column = cursor.column() as usize;
7544
7545 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7546
7547 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7548
7549 element.prepaint_at(origin, window, cx);
7550 Some((element, origin))
7551 }
7552
7553 fn render_edit_prediction_eager_jump_popover(
7554 &mut self,
7555 text_bounds: &Bounds<Pixels>,
7556 content_origin: gpui::Point<Pixels>,
7557 editor_snapshot: &EditorSnapshot,
7558 visible_row_range: Range<DisplayRow>,
7559 scroll_top: f32,
7560 scroll_bottom: f32,
7561 line_height: Pixels,
7562 scroll_pixel_position: gpui::Point<Pixels>,
7563 target_display_point: DisplayPoint,
7564 editor_width: Pixels,
7565 window: &mut Window,
7566 cx: &mut App,
7567 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7568 if target_display_point.row().as_f32() < scroll_top {
7569 let mut element = self
7570 .render_edit_prediction_line_popover(
7571 "Jump to Edit",
7572 Some(IconName::ArrowUp),
7573 window,
7574 cx,
7575 )?
7576 .into_any();
7577
7578 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7579 let offset = point(
7580 (text_bounds.size.width - size.width) / 2.,
7581 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7582 );
7583
7584 let origin = text_bounds.origin + offset;
7585 element.prepaint_at(origin, window, cx);
7586 Some((element, origin))
7587 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7588 let mut element = self
7589 .render_edit_prediction_line_popover(
7590 "Jump to Edit",
7591 Some(IconName::ArrowDown),
7592 window,
7593 cx,
7594 )?
7595 .into_any();
7596
7597 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7598 let offset = point(
7599 (text_bounds.size.width - size.width) / 2.,
7600 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7601 );
7602
7603 let origin = text_bounds.origin + offset;
7604 element.prepaint_at(origin, window, cx);
7605 Some((element, origin))
7606 } else {
7607 self.render_edit_prediction_end_of_line_popover(
7608 "Jump to Edit",
7609 editor_snapshot,
7610 visible_row_range,
7611 target_display_point,
7612 line_height,
7613 scroll_pixel_position,
7614 content_origin,
7615 editor_width,
7616 window,
7617 cx,
7618 )
7619 }
7620 }
7621
7622 fn render_edit_prediction_end_of_line_popover(
7623 self: &mut Editor,
7624 label: &'static str,
7625 editor_snapshot: &EditorSnapshot,
7626 visible_row_range: Range<DisplayRow>,
7627 target_display_point: DisplayPoint,
7628 line_height: Pixels,
7629 scroll_pixel_position: gpui::Point<Pixels>,
7630 content_origin: gpui::Point<Pixels>,
7631 editor_width: Pixels,
7632 window: &mut Window,
7633 cx: &mut App,
7634 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7635 let target_line_end = DisplayPoint::new(
7636 target_display_point.row(),
7637 editor_snapshot.line_len(target_display_point.row()),
7638 );
7639
7640 let mut element = self
7641 .render_edit_prediction_line_popover(label, None, window, cx)?
7642 .into_any();
7643
7644 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7645
7646 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7647
7648 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7649 let mut origin = start_point
7650 + line_origin
7651 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7652 origin.x = origin.x.max(content_origin.x);
7653
7654 let max_x = content_origin.x + editor_width - size.width;
7655
7656 if origin.x > max_x {
7657 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7658
7659 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7660 origin.y += offset;
7661 IconName::ArrowUp
7662 } else {
7663 origin.y -= offset;
7664 IconName::ArrowDown
7665 };
7666
7667 element = self
7668 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7669 .into_any();
7670
7671 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7672
7673 origin.x = content_origin.x + editor_width - size.width - px(2.);
7674 }
7675
7676 element.prepaint_at(origin, window, cx);
7677 Some((element, origin))
7678 }
7679
7680 fn render_edit_prediction_diff_popover(
7681 self: &Editor,
7682 text_bounds: &Bounds<Pixels>,
7683 content_origin: gpui::Point<Pixels>,
7684 right_margin: Pixels,
7685 editor_snapshot: &EditorSnapshot,
7686 visible_row_range: Range<DisplayRow>,
7687 line_layouts: &[LineWithInvisibles],
7688 line_height: Pixels,
7689 scroll_pixel_position: gpui::Point<Pixels>,
7690 newest_selection_head: Option<DisplayPoint>,
7691 editor_width: Pixels,
7692 style: &EditorStyle,
7693 edits: &Vec<(Range<Anchor>, String)>,
7694 edit_preview: &Option<language::EditPreview>,
7695 snapshot: &language::BufferSnapshot,
7696 window: &mut Window,
7697 cx: &mut App,
7698 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7699 let edit_start = edits
7700 .first()
7701 .unwrap()
7702 .0
7703 .start
7704 .to_display_point(editor_snapshot);
7705 let edit_end = edits
7706 .last()
7707 .unwrap()
7708 .0
7709 .end
7710 .to_display_point(editor_snapshot);
7711
7712 let is_visible = visible_row_range.contains(&edit_start.row())
7713 || visible_row_range.contains(&edit_end.row());
7714 if !is_visible {
7715 return None;
7716 }
7717
7718 let highlighted_edits =
7719 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7720
7721 let styled_text = highlighted_edits.to_styled_text(&style.text);
7722 let line_count = highlighted_edits.text.lines().count();
7723
7724 const BORDER_WIDTH: Pixels = px(1.);
7725
7726 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7727 let has_keybind = keybind.is_some();
7728
7729 let mut element = h_flex()
7730 .items_start()
7731 .child(
7732 h_flex()
7733 .bg(cx.theme().colors().editor_background)
7734 .border(BORDER_WIDTH)
7735 .shadow_sm()
7736 .border_color(cx.theme().colors().border)
7737 .rounded_l_lg()
7738 .when(line_count > 1, |el| el.rounded_br_lg())
7739 .pr_1()
7740 .child(styled_text),
7741 )
7742 .child(
7743 h_flex()
7744 .h(line_height + BORDER_WIDTH * 2.)
7745 .px_1p5()
7746 .gap_1()
7747 // Workaround: For some reason, there's a gap if we don't do this
7748 .ml(-BORDER_WIDTH)
7749 .shadow(smallvec![gpui::BoxShadow {
7750 color: gpui::black().opacity(0.05),
7751 offset: point(px(1.), px(1.)),
7752 blur_radius: px(2.),
7753 spread_radius: px(0.),
7754 }])
7755 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7756 .border(BORDER_WIDTH)
7757 .border_color(cx.theme().colors().border)
7758 .rounded_r_lg()
7759 .id("edit_prediction_diff_popover_keybind")
7760 .when(!has_keybind, |el| {
7761 let status_colors = cx.theme().status();
7762
7763 el.bg(status_colors.error_background)
7764 .border_color(status_colors.error.opacity(0.6))
7765 .child(Icon::new(IconName::Info).color(Color::Error))
7766 .cursor_default()
7767 .hoverable_tooltip(move |_window, cx| {
7768 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7769 })
7770 })
7771 .children(keybind),
7772 )
7773 .into_any();
7774
7775 let longest_row =
7776 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7777 let longest_line_width = if visible_row_range.contains(&longest_row) {
7778 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7779 } else {
7780 layout_line(
7781 longest_row,
7782 editor_snapshot,
7783 style,
7784 editor_width,
7785 |_| false,
7786 window,
7787 cx,
7788 )
7789 .width
7790 };
7791
7792 let viewport_bounds =
7793 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
7794 right: -right_margin,
7795 ..Default::default()
7796 });
7797
7798 let x_after_longest =
7799 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
7800 - scroll_pixel_position.x;
7801
7802 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7803
7804 // Fully visible if it can be displayed within the window (allow overlapping other
7805 // panes). However, this is only allowed if the popover starts within text_bounds.
7806 let can_position_to_the_right = x_after_longest < text_bounds.right()
7807 && x_after_longest + element_bounds.width < viewport_bounds.right();
7808
7809 let mut origin = if can_position_to_the_right {
7810 point(
7811 x_after_longest,
7812 text_bounds.origin.y + edit_start.row().as_f32() * line_height
7813 - scroll_pixel_position.y,
7814 )
7815 } else {
7816 let cursor_row = newest_selection_head.map(|head| head.row());
7817 let above_edit = edit_start
7818 .row()
7819 .0
7820 .checked_sub(line_count as u32)
7821 .map(DisplayRow);
7822 let below_edit = Some(edit_end.row() + 1);
7823 let above_cursor =
7824 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
7825 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
7826
7827 // Place the edit popover adjacent to the edit if there is a location
7828 // available that is onscreen and does not obscure the cursor. Otherwise,
7829 // place it adjacent to the cursor.
7830 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
7831 .into_iter()
7832 .flatten()
7833 .find(|&start_row| {
7834 let end_row = start_row + line_count as u32;
7835 visible_row_range.contains(&start_row)
7836 && visible_row_range.contains(&end_row)
7837 && cursor_row.map_or(true, |cursor_row| {
7838 !((start_row..end_row).contains(&cursor_row))
7839 })
7840 })?;
7841
7842 content_origin
7843 + point(
7844 -scroll_pixel_position.x,
7845 row_target.as_f32() * line_height - scroll_pixel_position.y,
7846 )
7847 };
7848
7849 origin.x -= BORDER_WIDTH;
7850
7851 window.defer_draw(element, origin, 1);
7852
7853 // Do not return an element, since it will already be drawn due to defer_draw.
7854 None
7855 }
7856
7857 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
7858 px(30.)
7859 }
7860
7861 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
7862 if self.read_only(cx) {
7863 cx.theme().players().read_only()
7864 } else {
7865 self.style.as_ref().unwrap().local_player
7866 }
7867 }
7868
7869 fn render_edit_prediction_accept_keybind(
7870 &self,
7871 window: &mut Window,
7872 cx: &App,
7873 ) -> Option<AnyElement> {
7874 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
7875 let accept_keystroke = accept_binding.keystroke()?;
7876
7877 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7878
7879 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
7880 Color::Accent
7881 } else {
7882 Color::Muted
7883 };
7884
7885 h_flex()
7886 .px_0p5()
7887 .when(is_platform_style_mac, |parent| parent.gap_0p5())
7888 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7889 .text_size(TextSize::XSmall.rems(cx))
7890 .child(h_flex().children(ui::render_modifiers(
7891 &accept_keystroke.modifiers,
7892 PlatformStyle::platform(),
7893 Some(modifiers_color),
7894 Some(IconSize::XSmall.rems().into()),
7895 true,
7896 )))
7897 .when(is_platform_style_mac, |parent| {
7898 parent.child(accept_keystroke.key.clone())
7899 })
7900 .when(!is_platform_style_mac, |parent| {
7901 parent.child(
7902 Key::new(
7903 util::capitalize(&accept_keystroke.key),
7904 Some(Color::Default),
7905 )
7906 .size(Some(IconSize::XSmall.rems().into())),
7907 )
7908 })
7909 .into_any()
7910 .into()
7911 }
7912
7913 fn render_edit_prediction_line_popover(
7914 &self,
7915 label: impl Into<SharedString>,
7916 icon: Option<IconName>,
7917 window: &mut Window,
7918 cx: &App,
7919 ) -> Option<Stateful<Div>> {
7920 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
7921
7922 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7923 let has_keybind = keybind.is_some();
7924
7925 let result = h_flex()
7926 .id("ep-line-popover")
7927 .py_0p5()
7928 .pl_1()
7929 .pr(padding_right)
7930 .gap_1()
7931 .rounded_md()
7932 .border_1()
7933 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7934 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7935 .shadow_sm()
7936 .when(!has_keybind, |el| {
7937 let status_colors = cx.theme().status();
7938
7939 el.bg(status_colors.error_background)
7940 .border_color(status_colors.error.opacity(0.6))
7941 .pl_2()
7942 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7943 .cursor_default()
7944 .hoverable_tooltip(move |_window, cx| {
7945 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7946 })
7947 })
7948 .children(keybind)
7949 .child(
7950 Label::new(label)
7951 .size(LabelSize::Small)
7952 .when(!has_keybind, |el| {
7953 el.color(cx.theme().status().error.into()).strikethrough()
7954 }),
7955 )
7956 .when(!has_keybind, |el| {
7957 el.child(
7958 h_flex().ml_1().child(
7959 Icon::new(IconName::Info)
7960 .size(IconSize::Small)
7961 .color(cx.theme().status().error.into()),
7962 ),
7963 )
7964 })
7965 .when_some(icon, |element, icon| {
7966 element.child(
7967 div()
7968 .mt(px(1.5))
7969 .child(Icon::new(icon).size(IconSize::Small)),
7970 )
7971 });
7972
7973 Some(result)
7974 }
7975
7976 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
7977 let accent_color = cx.theme().colors().text_accent;
7978 let editor_bg_color = cx.theme().colors().editor_background;
7979 editor_bg_color.blend(accent_color.opacity(0.1))
7980 }
7981
7982 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
7983 let accent_color = cx.theme().colors().text_accent;
7984 let editor_bg_color = cx.theme().colors().editor_background;
7985 editor_bg_color.blend(accent_color.opacity(0.6))
7986 }
7987
7988 fn render_edit_prediction_cursor_popover(
7989 &self,
7990 min_width: Pixels,
7991 max_width: Pixels,
7992 cursor_point: Point,
7993 style: &EditorStyle,
7994 accept_keystroke: Option<&gpui::Keystroke>,
7995 _window: &Window,
7996 cx: &mut Context<Editor>,
7997 ) -> Option<AnyElement> {
7998 let provider = self.edit_prediction_provider.as_ref()?;
7999
8000 if provider.provider.needs_terms_acceptance(cx) {
8001 return Some(
8002 h_flex()
8003 .min_w(min_width)
8004 .flex_1()
8005 .px_2()
8006 .py_1()
8007 .gap_3()
8008 .elevation_2(cx)
8009 .hover(|style| style.bg(cx.theme().colors().element_hover))
8010 .id("accept-terms")
8011 .cursor_pointer()
8012 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8013 .on_click(cx.listener(|this, _event, window, cx| {
8014 cx.stop_propagation();
8015 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8016 window.dispatch_action(
8017 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8018 cx,
8019 );
8020 }))
8021 .child(
8022 h_flex()
8023 .flex_1()
8024 .gap_2()
8025 .child(Icon::new(IconName::ZedPredict))
8026 .child(Label::new("Accept Terms of Service"))
8027 .child(div().w_full())
8028 .child(
8029 Icon::new(IconName::ArrowUpRight)
8030 .color(Color::Muted)
8031 .size(IconSize::Small),
8032 )
8033 .into_any_element(),
8034 )
8035 .into_any(),
8036 );
8037 }
8038
8039 let is_refreshing = provider.provider.is_refreshing(cx);
8040
8041 fn pending_completion_container() -> Div {
8042 h_flex()
8043 .h_full()
8044 .flex_1()
8045 .gap_2()
8046 .child(Icon::new(IconName::ZedPredict))
8047 }
8048
8049 let completion = match &self.active_inline_completion {
8050 Some(prediction) => {
8051 if !self.has_visible_completions_menu() {
8052 const RADIUS: Pixels = px(6.);
8053 const BORDER_WIDTH: Pixels = px(1.);
8054
8055 return Some(
8056 h_flex()
8057 .elevation_2(cx)
8058 .border(BORDER_WIDTH)
8059 .border_color(cx.theme().colors().border)
8060 .when(accept_keystroke.is_none(), |el| {
8061 el.border_color(cx.theme().status().error)
8062 })
8063 .rounded(RADIUS)
8064 .rounded_tl(px(0.))
8065 .overflow_hidden()
8066 .child(div().px_1p5().child(match &prediction.completion {
8067 InlineCompletion::Move { target, snapshot } => {
8068 use text::ToPoint as _;
8069 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8070 {
8071 Icon::new(IconName::ZedPredictDown)
8072 } else {
8073 Icon::new(IconName::ZedPredictUp)
8074 }
8075 }
8076 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8077 }))
8078 .child(
8079 h_flex()
8080 .gap_1()
8081 .py_1()
8082 .px_2()
8083 .rounded_r(RADIUS - BORDER_WIDTH)
8084 .border_l_1()
8085 .border_color(cx.theme().colors().border)
8086 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8087 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8088 el.child(
8089 Label::new("Hold")
8090 .size(LabelSize::Small)
8091 .when(accept_keystroke.is_none(), |el| {
8092 el.strikethrough()
8093 })
8094 .line_height_style(LineHeightStyle::UiLabel),
8095 )
8096 })
8097 .id("edit_prediction_cursor_popover_keybind")
8098 .when(accept_keystroke.is_none(), |el| {
8099 let status_colors = cx.theme().status();
8100
8101 el.bg(status_colors.error_background)
8102 .border_color(status_colors.error.opacity(0.6))
8103 .child(Icon::new(IconName::Info).color(Color::Error))
8104 .cursor_default()
8105 .hoverable_tooltip(move |_window, cx| {
8106 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8107 .into()
8108 })
8109 })
8110 .when_some(
8111 accept_keystroke.as_ref(),
8112 |el, accept_keystroke| {
8113 el.child(h_flex().children(ui::render_modifiers(
8114 &accept_keystroke.modifiers,
8115 PlatformStyle::platform(),
8116 Some(Color::Default),
8117 Some(IconSize::XSmall.rems().into()),
8118 false,
8119 )))
8120 },
8121 ),
8122 )
8123 .into_any(),
8124 );
8125 }
8126
8127 self.render_edit_prediction_cursor_popover_preview(
8128 prediction,
8129 cursor_point,
8130 style,
8131 cx,
8132 )?
8133 }
8134
8135 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8136 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8137 stale_completion,
8138 cursor_point,
8139 style,
8140 cx,
8141 )?,
8142
8143 None => {
8144 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8145 }
8146 },
8147
8148 None => pending_completion_container().child(Label::new("No Prediction")),
8149 };
8150
8151 let completion = if is_refreshing {
8152 completion
8153 .with_animation(
8154 "loading-completion",
8155 Animation::new(Duration::from_secs(2))
8156 .repeat()
8157 .with_easing(pulsating_between(0.4, 0.8)),
8158 |label, delta| label.opacity(delta),
8159 )
8160 .into_any_element()
8161 } else {
8162 completion.into_any_element()
8163 };
8164
8165 let has_completion = self.active_inline_completion.is_some();
8166
8167 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8168 Some(
8169 h_flex()
8170 .min_w(min_width)
8171 .max_w(max_width)
8172 .flex_1()
8173 .elevation_2(cx)
8174 .border_color(cx.theme().colors().border)
8175 .child(
8176 div()
8177 .flex_1()
8178 .py_1()
8179 .px_2()
8180 .overflow_hidden()
8181 .child(completion),
8182 )
8183 .when_some(accept_keystroke, |el, accept_keystroke| {
8184 if !accept_keystroke.modifiers.modified() {
8185 return el;
8186 }
8187
8188 el.child(
8189 h_flex()
8190 .h_full()
8191 .border_l_1()
8192 .rounded_r_lg()
8193 .border_color(cx.theme().colors().border)
8194 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8195 .gap_1()
8196 .py_1()
8197 .px_2()
8198 .child(
8199 h_flex()
8200 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8201 .when(is_platform_style_mac, |parent| parent.gap_1())
8202 .child(h_flex().children(ui::render_modifiers(
8203 &accept_keystroke.modifiers,
8204 PlatformStyle::platform(),
8205 Some(if !has_completion {
8206 Color::Muted
8207 } else {
8208 Color::Default
8209 }),
8210 None,
8211 false,
8212 ))),
8213 )
8214 .child(Label::new("Preview").into_any_element())
8215 .opacity(if has_completion { 1.0 } else { 0.4 }),
8216 )
8217 })
8218 .into_any(),
8219 )
8220 }
8221
8222 fn render_edit_prediction_cursor_popover_preview(
8223 &self,
8224 completion: &InlineCompletionState,
8225 cursor_point: Point,
8226 style: &EditorStyle,
8227 cx: &mut Context<Editor>,
8228 ) -> Option<Div> {
8229 use text::ToPoint as _;
8230
8231 fn render_relative_row_jump(
8232 prefix: impl Into<String>,
8233 current_row: u32,
8234 target_row: u32,
8235 ) -> Div {
8236 let (row_diff, arrow) = if target_row < current_row {
8237 (current_row - target_row, IconName::ArrowUp)
8238 } else {
8239 (target_row - current_row, IconName::ArrowDown)
8240 };
8241
8242 h_flex()
8243 .child(
8244 Label::new(format!("{}{}", prefix.into(), row_diff))
8245 .color(Color::Muted)
8246 .size(LabelSize::Small),
8247 )
8248 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8249 }
8250
8251 match &completion.completion {
8252 InlineCompletion::Move {
8253 target, snapshot, ..
8254 } => Some(
8255 h_flex()
8256 .px_2()
8257 .gap_2()
8258 .flex_1()
8259 .child(
8260 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8261 Icon::new(IconName::ZedPredictDown)
8262 } else {
8263 Icon::new(IconName::ZedPredictUp)
8264 },
8265 )
8266 .child(Label::new("Jump to Edit")),
8267 ),
8268
8269 InlineCompletion::Edit {
8270 edits,
8271 edit_preview,
8272 snapshot,
8273 display_mode: _,
8274 } => {
8275 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8276
8277 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8278 &snapshot,
8279 &edits,
8280 edit_preview.as_ref()?,
8281 true,
8282 cx,
8283 )
8284 .first_line_preview();
8285
8286 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8287 .with_default_highlights(&style.text, highlighted_edits.highlights);
8288
8289 let preview = h_flex()
8290 .gap_1()
8291 .min_w_16()
8292 .child(styled_text)
8293 .when(has_more_lines, |parent| parent.child("…"));
8294
8295 let left = if first_edit_row != cursor_point.row {
8296 render_relative_row_jump("", cursor_point.row, first_edit_row)
8297 .into_any_element()
8298 } else {
8299 Icon::new(IconName::ZedPredict).into_any_element()
8300 };
8301
8302 Some(
8303 h_flex()
8304 .h_full()
8305 .flex_1()
8306 .gap_2()
8307 .pr_1()
8308 .overflow_x_hidden()
8309 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8310 .child(left)
8311 .child(preview),
8312 )
8313 }
8314 }
8315 }
8316
8317 fn render_context_menu(
8318 &self,
8319 style: &EditorStyle,
8320 max_height_in_lines: u32,
8321 window: &mut Window,
8322 cx: &mut Context<Editor>,
8323 ) -> Option<AnyElement> {
8324 let menu = self.context_menu.borrow();
8325 let menu = menu.as_ref()?;
8326 if !menu.visible() {
8327 return None;
8328 };
8329 Some(menu.render(style, max_height_in_lines, window, cx))
8330 }
8331
8332 fn render_context_menu_aside(
8333 &mut self,
8334 max_size: Size<Pixels>,
8335 window: &mut Window,
8336 cx: &mut Context<Editor>,
8337 ) -> Option<AnyElement> {
8338 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8339 if menu.visible() {
8340 menu.render_aside(self, max_size, window, cx)
8341 } else {
8342 None
8343 }
8344 })
8345 }
8346
8347 fn hide_context_menu(
8348 &mut self,
8349 window: &mut Window,
8350 cx: &mut Context<Self>,
8351 ) -> Option<CodeContextMenu> {
8352 cx.notify();
8353 self.completion_tasks.clear();
8354 let context_menu = self.context_menu.borrow_mut().take();
8355 self.stale_inline_completion_in_menu.take();
8356 self.update_visible_inline_completion(window, cx);
8357 context_menu
8358 }
8359
8360 fn show_snippet_choices(
8361 &mut self,
8362 choices: &Vec<String>,
8363 selection: Range<Anchor>,
8364 cx: &mut Context<Self>,
8365 ) {
8366 if selection.start.buffer_id.is_none() {
8367 return;
8368 }
8369 let buffer_id = selection.start.buffer_id.unwrap();
8370 let buffer = self.buffer().read(cx).buffer(buffer_id);
8371 let id = post_inc(&mut self.next_completion_id);
8372 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8373
8374 if let Some(buffer) = buffer {
8375 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8376 CompletionsMenu::new_snippet_choices(
8377 id,
8378 true,
8379 choices,
8380 selection,
8381 buffer,
8382 snippet_sort_order,
8383 ),
8384 ));
8385 }
8386 }
8387
8388 pub fn insert_snippet(
8389 &mut self,
8390 insertion_ranges: &[Range<usize>],
8391 snippet: Snippet,
8392 window: &mut Window,
8393 cx: &mut Context<Self>,
8394 ) -> Result<()> {
8395 struct Tabstop<T> {
8396 is_end_tabstop: bool,
8397 ranges: Vec<Range<T>>,
8398 choices: Option<Vec<String>>,
8399 }
8400
8401 let tabstops = self.buffer.update(cx, |buffer, cx| {
8402 let snippet_text: Arc<str> = snippet.text.clone().into();
8403 let edits = insertion_ranges
8404 .iter()
8405 .cloned()
8406 .map(|range| (range, snippet_text.clone()));
8407 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8408
8409 let snapshot = &*buffer.read(cx);
8410 let snippet = &snippet;
8411 snippet
8412 .tabstops
8413 .iter()
8414 .map(|tabstop| {
8415 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8416 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8417 });
8418 let mut tabstop_ranges = tabstop
8419 .ranges
8420 .iter()
8421 .flat_map(|tabstop_range| {
8422 let mut delta = 0_isize;
8423 insertion_ranges.iter().map(move |insertion_range| {
8424 let insertion_start = insertion_range.start as isize + delta;
8425 delta +=
8426 snippet.text.len() as isize - insertion_range.len() as isize;
8427
8428 let start = ((insertion_start + tabstop_range.start) as usize)
8429 .min(snapshot.len());
8430 let end = ((insertion_start + tabstop_range.end) as usize)
8431 .min(snapshot.len());
8432 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8433 })
8434 })
8435 .collect::<Vec<_>>();
8436 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8437
8438 Tabstop {
8439 is_end_tabstop,
8440 ranges: tabstop_ranges,
8441 choices: tabstop.choices.clone(),
8442 }
8443 })
8444 .collect::<Vec<_>>()
8445 });
8446 if let Some(tabstop) = tabstops.first() {
8447 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8448 s.select_ranges(tabstop.ranges.iter().cloned());
8449 });
8450
8451 if let Some(choices) = &tabstop.choices {
8452 if let Some(selection) = tabstop.ranges.first() {
8453 self.show_snippet_choices(choices, selection.clone(), cx)
8454 }
8455 }
8456
8457 // If we're already at the last tabstop and it's at the end of the snippet,
8458 // we're done, we don't need to keep the state around.
8459 if !tabstop.is_end_tabstop {
8460 let choices = tabstops
8461 .iter()
8462 .map(|tabstop| tabstop.choices.clone())
8463 .collect();
8464
8465 let ranges = tabstops
8466 .into_iter()
8467 .map(|tabstop| tabstop.ranges)
8468 .collect::<Vec<_>>();
8469
8470 self.snippet_stack.push(SnippetState {
8471 active_index: 0,
8472 ranges,
8473 choices,
8474 });
8475 }
8476
8477 // Check whether the just-entered snippet ends with an auto-closable bracket.
8478 if self.autoclose_regions.is_empty() {
8479 let snapshot = self.buffer.read(cx).snapshot(cx);
8480 for selection in &mut self.selections.all::<Point>(cx) {
8481 let selection_head = selection.head();
8482 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8483 continue;
8484 };
8485
8486 let mut bracket_pair = None;
8487 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8488 let prev_chars = snapshot
8489 .reversed_chars_at(selection_head)
8490 .collect::<String>();
8491 for (pair, enabled) in scope.brackets() {
8492 if enabled
8493 && pair.close
8494 && prev_chars.starts_with(pair.start.as_str())
8495 && next_chars.starts_with(pair.end.as_str())
8496 {
8497 bracket_pair = Some(pair.clone());
8498 break;
8499 }
8500 }
8501 if let Some(pair) = bracket_pair {
8502 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8503 let autoclose_enabled =
8504 self.use_autoclose && snapshot_settings.use_autoclose;
8505 if autoclose_enabled {
8506 let start = snapshot.anchor_after(selection_head);
8507 let end = snapshot.anchor_after(selection_head);
8508 self.autoclose_regions.push(AutocloseRegion {
8509 selection_id: selection.id,
8510 range: start..end,
8511 pair,
8512 });
8513 }
8514 }
8515 }
8516 }
8517 }
8518 Ok(())
8519 }
8520
8521 pub fn move_to_next_snippet_tabstop(
8522 &mut self,
8523 window: &mut Window,
8524 cx: &mut Context<Self>,
8525 ) -> bool {
8526 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8527 }
8528
8529 pub fn move_to_prev_snippet_tabstop(
8530 &mut self,
8531 window: &mut Window,
8532 cx: &mut Context<Self>,
8533 ) -> bool {
8534 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8535 }
8536
8537 pub fn move_to_snippet_tabstop(
8538 &mut self,
8539 bias: Bias,
8540 window: &mut Window,
8541 cx: &mut Context<Self>,
8542 ) -> bool {
8543 if let Some(mut snippet) = self.snippet_stack.pop() {
8544 match bias {
8545 Bias::Left => {
8546 if snippet.active_index > 0 {
8547 snippet.active_index -= 1;
8548 } else {
8549 self.snippet_stack.push(snippet);
8550 return false;
8551 }
8552 }
8553 Bias::Right => {
8554 if snippet.active_index + 1 < snippet.ranges.len() {
8555 snippet.active_index += 1;
8556 } else {
8557 self.snippet_stack.push(snippet);
8558 return false;
8559 }
8560 }
8561 }
8562 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8563 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8564 s.select_anchor_ranges(current_ranges.iter().cloned())
8565 });
8566
8567 if let Some(choices) = &snippet.choices[snippet.active_index] {
8568 if let Some(selection) = current_ranges.first() {
8569 self.show_snippet_choices(&choices, selection.clone(), cx);
8570 }
8571 }
8572
8573 // If snippet state is not at the last tabstop, push it back on the stack
8574 if snippet.active_index + 1 < snippet.ranges.len() {
8575 self.snippet_stack.push(snippet);
8576 }
8577 return true;
8578 }
8579 }
8580
8581 false
8582 }
8583
8584 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8585 self.transact(window, cx, |this, window, cx| {
8586 this.select_all(&SelectAll, window, cx);
8587 this.insert("", window, cx);
8588 });
8589 }
8590
8591 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8592 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8593 self.transact(window, cx, |this, window, cx| {
8594 this.select_autoclose_pair(window, cx);
8595 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8596 if !this.linked_edit_ranges.is_empty() {
8597 let selections = this.selections.all::<MultiBufferPoint>(cx);
8598 let snapshot = this.buffer.read(cx).snapshot(cx);
8599
8600 for selection in selections.iter() {
8601 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8602 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8603 if selection_start.buffer_id != selection_end.buffer_id {
8604 continue;
8605 }
8606 if let Some(ranges) =
8607 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8608 {
8609 for (buffer, entries) in ranges {
8610 linked_ranges.entry(buffer).or_default().extend(entries);
8611 }
8612 }
8613 }
8614 }
8615
8616 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8617 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8618 for selection in &mut selections {
8619 if selection.is_empty() {
8620 let old_head = selection.head();
8621 let mut new_head =
8622 movement::left(&display_map, old_head.to_display_point(&display_map))
8623 .to_point(&display_map);
8624 if let Some((buffer, line_buffer_range)) = display_map
8625 .buffer_snapshot
8626 .buffer_line_for_row(MultiBufferRow(old_head.row))
8627 {
8628 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8629 let indent_len = match indent_size.kind {
8630 IndentKind::Space => {
8631 buffer.settings_at(line_buffer_range.start, cx).tab_size
8632 }
8633 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8634 };
8635 if old_head.column <= indent_size.len && old_head.column > 0 {
8636 let indent_len = indent_len.get();
8637 new_head = cmp::min(
8638 new_head,
8639 MultiBufferPoint::new(
8640 old_head.row,
8641 ((old_head.column - 1) / indent_len) * indent_len,
8642 ),
8643 );
8644 }
8645 }
8646
8647 selection.set_head(new_head, SelectionGoal::None);
8648 }
8649 }
8650
8651 this.signature_help_state.set_backspace_pressed(true);
8652 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8653 s.select(selections)
8654 });
8655 this.insert("", window, cx);
8656 let empty_str: Arc<str> = Arc::from("");
8657 for (buffer, edits) in linked_ranges {
8658 let snapshot = buffer.read(cx).snapshot();
8659 use text::ToPoint as TP;
8660
8661 let edits = edits
8662 .into_iter()
8663 .map(|range| {
8664 let end_point = TP::to_point(&range.end, &snapshot);
8665 let mut start_point = TP::to_point(&range.start, &snapshot);
8666
8667 if end_point == start_point {
8668 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8669 .saturating_sub(1);
8670 start_point =
8671 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8672 };
8673
8674 (start_point..end_point, empty_str.clone())
8675 })
8676 .sorted_by_key(|(range, _)| range.start)
8677 .collect::<Vec<_>>();
8678 buffer.update(cx, |this, cx| {
8679 this.edit(edits, None, cx);
8680 })
8681 }
8682 this.refresh_inline_completion(true, false, window, cx);
8683 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8684 });
8685 }
8686
8687 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8688 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8689 self.transact(window, cx, |this, window, cx| {
8690 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8691 s.move_with(|map, selection| {
8692 if selection.is_empty() {
8693 let cursor = movement::right(map, selection.head());
8694 selection.end = cursor;
8695 selection.reversed = true;
8696 selection.goal = SelectionGoal::None;
8697 }
8698 })
8699 });
8700 this.insert("", window, cx);
8701 this.refresh_inline_completion(true, false, window, cx);
8702 });
8703 }
8704
8705 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8706 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8707 if self.move_to_prev_snippet_tabstop(window, cx) {
8708 return;
8709 }
8710 self.outdent(&Outdent, window, cx);
8711 }
8712
8713 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8714 if self.move_to_next_snippet_tabstop(window, cx) {
8715 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8716 return;
8717 }
8718 if self.read_only(cx) {
8719 return;
8720 }
8721 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8722 let mut selections = self.selections.all_adjusted(cx);
8723 let buffer = self.buffer.read(cx);
8724 let snapshot = buffer.snapshot(cx);
8725 let rows_iter = selections.iter().map(|s| s.head().row);
8726 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8727
8728 let has_some_cursor_in_whitespace = selections
8729 .iter()
8730 .filter(|selection| selection.is_empty())
8731 .any(|selection| {
8732 let cursor = selection.head();
8733 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8734 cursor.column < current_indent.len
8735 });
8736
8737 let mut edits = Vec::new();
8738 let mut prev_edited_row = 0;
8739 let mut row_delta = 0;
8740 for selection in &mut selections {
8741 if selection.start.row != prev_edited_row {
8742 row_delta = 0;
8743 }
8744 prev_edited_row = selection.end.row;
8745
8746 // If the selection is non-empty, then increase the indentation of the selected lines.
8747 if !selection.is_empty() {
8748 row_delta =
8749 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8750 continue;
8751 }
8752
8753 // If the selection is empty and the cursor is in the leading whitespace before the
8754 // suggested indentation, then auto-indent the line.
8755 let cursor = selection.head();
8756 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8757 if let Some(suggested_indent) =
8758 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8759 {
8760 // If there exist any empty selection in the leading whitespace, then skip
8761 // indent for selections at the boundary.
8762 if has_some_cursor_in_whitespace
8763 && cursor.column == current_indent.len
8764 && current_indent.len == suggested_indent.len
8765 {
8766 continue;
8767 }
8768
8769 if cursor.column < suggested_indent.len
8770 && cursor.column <= current_indent.len
8771 && current_indent.len <= suggested_indent.len
8772 {
8773 selection.start = Point::new(cursor.row, suggested_indent.len);
8774 selection.end = selection.start;
8775 if row_delta == 0 {
8776 edits.extend(Buffer::edit_for_indent_size_adjustment(
8777 cursor.row,
8778 current_indent,
8779 suggested_indent,
8780 ));
8781 row_delta = suggested_indent.len - current_indent.len;
8782 }
8783 continue;
8784 }
8785 }
8786
8787 // Otherwise, insert a hard or soft tab.
8788 let settings = buffer.language_settings_at(cursor, cx);
8789 let tab_size = if settings.hard_tabs {
8790 IndentSize::tab()
8791 } else {
8792 let tab_size = settings.tab_size.get();
8793 let indent_remainder = snapshot
8794 .text_for_range(Point::new(cursor.row, 0)..cursor)
8795 .flat_map(str::chars)
8796 .fold(row_delta % tab_size, |counter: u32, c| {
8797 if c == '\t' {
8798 0
8799 } else {
8800 (counter + 1) % tab_size
8801 }
8802 });
8803
8804 let chars_to_next_tab_stop = tab_size - indent_remainder;
8805 IndentSize::spaces(chars_to_next_tab_stop)
8806 };
8807 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
8808 selection.end = selection.start;
8809 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
8810 row_delta += tab_size.len;
8811 }
8812
8813 self.transact(window, cx, |this, window, cx| {
8814 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8815 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8816 s.select(selections)
8817 });
8818 this.refresh_inline_completion(true, false, window, cx);
8819 });
8820 }
8821
8822 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
8823 if self.read_only(cx) {
8824 return;
8825 }
8826 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8827 let mut selections = self.selections.all::<Point>(cx);
8828 let mut prev_edited_row = 0;
8829 let mut row_delta = 0;
8830 let mut edits = Vec::new();
8831 let buffer = self.buffer.read(cx);
8832 let snapshot = buffer.snapshot(cx);
8833 for selection in &mut selections {
8834 if selection.start.row != prev_edited_row {
8835 row_delta = 0;
8836 }
8837 prev_edited_row = selection.end.row;
8838
8839 row_delta =
8840 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8841 }
8842
8843 self.transact(window, cx, |this, window, cx| {
8844 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8845 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8846 s.select(selections)
8847 });
8848 });
8849 }
8850
8851 fn indent_selection(
8852 buffer: &MultiBuffer,
8853 snapshot: &MultiBufferSnapshot,
8854 selection: &mut Selection<Point>,
8855 edits: &mut Vec<(Range<Point>, String)>,
8856 delta_for_start_row: u32,
8857 cx: &App,
8858 ) -> u32 {
8859 let settings = buffer.language_settings_at(selection.start, cx);
8860 let tab_size = settings.tab_size.get();
8861 let indent_kind = if settings.hard_tabs {
8862 IndentKind::Tab
8863 } else {
8864 IndentKind::Space
8865 };
8866 let mut start_row = selection.start.row;
8867 let mut end_row = selection.end.row + 1;
8868
8869 // If a selection ends at the beginning of a line, don't indent
8870 // that last line.
8871 if selection.end.column == 0 && selection.end.row > selection.start.row {
8872 end_row -= 1;
8873 }
8874
8875 // Avoid re-indenting a row that has already been indented by a
8876 // previous selection, but still update this selection's column
8877 // to reflect that indentation.
8878 if delta_for_start_row > 0 {
8879 start_row += 1;
8880 selection.start.column += delta_for_start_row;
8881 if selection.end.row == selection.start.row {
8882 selection.end.column += delta_for_start_row;
8883 }
8884 }
8885
8886 let mut delta_for_end_row = 0;
8887 let has_multiple_rows = start_row + 1 != end_row;
8888 for row in start_row..end_row {
8889 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
8890 let indent_delta = match (current_indent.kind, indent_kind) {
8891 (IndentKind::Space, IndentKind::Space) => {
8892 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
8893 IndentSize::spaces(columns_to_next_tab_stop)
8894 }
8895 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
8896 (_, IndentKind::Tab) => IndentSize::tab(),
8897 };
8898
8899 let start = if has_multiple_rows || current_indent.len < selection.start.column {
8900 0
8901 } else {
8902 selection.start.column
8903 };
8904 let row_start = Point::new(row, start);
8905 edits.push((
8906 row_start..row_start,
8907 indent_delta.chars().collect::<String>(),
8908 ));
8909
8910 // Update this selection's endpoints to reflect the indentation.
8911 if row == selection.start.row {
8912 selection.start.column += indent_delta.len;
8913 }
8914 if row == selection.end.row {
8915 selection.end.column += indent_delta.len;
8916 delta_for_end_row = indent_delta.len;
8917 }
8918 }
8919
8920 if selection.start.row == selection.end.row {
8921 delta_for_start_row + delta_for_end_row
8922 } else {
8923 delta_for_end_row
8924 }
8925 }
8926
8927 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
8928 if self.read_only(cx) {
8929 return;
8930 }
8931 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8932 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8933 let selections = self.selections.all::<Point>(cx);
8934 let mut deletion_ranges = Vec::new();
8935 let mut last_outdent = None;
8936 {
8937 let buffer = self.buffer.read(cx);
8938 let snapshot = buffer.snapshot(cx);
8939 for selection in &selections {
8940 let settings = buffer.language_settings_at(selection.start, cx);
8941 let tab_size = settings.tab_size.get();
8942 let mut rows = selection.spanned_rows(false, &display_map);
8943
8944 // Avoid re-outdenting a row that has already been outdented by a
8945 // previous selection.
8946 if let Some(last_row) = last_outdent {
8947 if last_row == rows.start {
8948 rows.start = rows.start.next_row();
8949 }
8950 }
8951 let has_multiple_rows = rows.len() > 1;
8952 for row in rows.iter_rows() {
8953 let indent_size = snapshot.indent_size_for_line(row);
8954 if indent_size.len > 0 {
8955 let deletion_len = match indent_size.kind {
8956 IndentKind::Space => {
8957 let columns_to_prev_tab_stop = indent_size.len % tab_size;
8958 if columns_to_prev_tab_stop == 0 {
8959 tab_size
8960 } else {
8961 columns_to_prev_tab_stop
8962 }
8963 }
8964 IndentKind::Tab => 1,
8965 };
8966 let start = if has_multiple_rows
8967 || deletion_len > selection.start.column
8968 || indent_size.len < selection.start.column
8969 {
8970 0
8971 } else {
8972 selection.start.column - deletion_len
8973 };
8974 deletion_ranges.push(
8975 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
8976 );
8977 last_outdent = Some(row);
8978 }
8979 }
8980 }
8981 }
8982
8983 self.transact(window, cx, |this, window, cx| {
8984 this.buffer.update(cx, |buffer, cx| {
8985 let empty_str: Arc<str> = Arc::default();
8986 buffer.edit(
8987 deletion_ranges
8988 .into_iter()
8989 .map(|range| (range, empty_str.clone())),
8990 None,
8991 cx,
8992 );
8993 });
8994 let selections = this.selections.all::<usize>(cx);
8995 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8996 s.select(selections)
8997 });
8998 });
8999 }
9000
9001 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9002 if self.read_only(cx) {
9003 return;
9004 }
9005 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9006 let selections = self
9007 .selections
9008 .all::<usize>(cx)
9009 .into_iter()
9010 .map(|s| s.range());
9011
9012 self.transact(window, cx, |this, window, cx| {
9013 this.buffer.update(cx, |buffer, cx| {
9014 buffer.autoindent_ranges(selections, cx);
9015 });
9016 let selections = this.selections.all::<usize>(cx);
9017 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9018 s.select(selections)
9019 });
9020 });
9021 }
9022
9023 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9024 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9025 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9026 let selections = self.selections.all::<Point>(cx);
9027
9028 let mut new_cursors = Vec::new();
9029 let mut edit_ranges = Vec::new();
9030 let mut selections = selections.iter().peekable();
9031 while let Some(selection) = selections.next() {
9032 let mut rows = selection.spanned_rows(false, &display_map);
9033 let goal_display_column = selection.head().to_display_point(&display_map).column();
9034
9035 // Accumulate contiguous regions of rows that we want to delete.
9036 while let Some(next_selection) = selections.peek() {
9037 let next_rows = next_selection.spanned_rows(false, &display_map);
9038 if next_rows.start <= rows.end {
9039 rows.end = next_rows.end;
9040 selections.next().unwrap();
9041 } else {
9042 break;
9043 }
9044 }
9045
9046 let buffer = &display_map.buffer_snapshot;
9047 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9048 let edit_end;
9049 let cursor_buffer_row;
9050 if buffer.max_point().row >= rows.end.0 {
9051 // If there's a line after the range, delete the \n from the end of the row range
9052 // and position the cursor on the next line.
9053 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9054 cursor_buffer_row = rows.end;
9055 } else {
9056 // If there isn't a line after the range, delete the \n from the line before the
9057 // start of the row range and position the cursor there.
9058 edit_start = edit_start.saturating_sub(1);
9059 edit_end = buffer.len();
9060 cursor_buffer_row = rows.start.previous_row();
9061 }
9062
9063 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9064 *cursor.column_mut() =
9065 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9066
9067 new_cursors.push((
9068 selection.id,
9069 buffer.anchor_after(cursor.to_point(&display_map)),
9070 ));
9071 edit_ranges.push(edit_start..edit_end);
9072 }
9073
9074 self.transact(window, cx, |this, window, cx| {
9075 let buffer = this.buffer.update(cx, |buffer, cx| {
9076 let empty_str: Arc<str> = Arc::default();
9077 buffer.edit(
9078 edit_ranges
9079 .into_iter()
9080 .map(|range| (range, empty_str.clone())),
9081 None,
9082 cx,
9083 );
9084 buffer.snapshot(cx)
9085 });
9086 let new_selections = new_cursors
9087 .into_iter()
9088 .map(|(id, cursor)| {
9089 let cursor = cursor.to_point(&buffer);
9090 Selection {
9091 id,
9092 start: cursor,
9093 end: cursor,
9094 reversed: false,
9095 goal: SelectionGoal::None,
9096 }
9097 })
9098 .collect();
9099
9100 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9101 s.select(new_selections);
9102 });
9103 });
9104 }
9105
9106 pub fn join_lines_impl(
9107 &mut self,
9108 insert_whitespace: bool,
9109 window: &mut Window,
9110 cx: &mut Context<Self>,
9111 ) {
9112 if self.read_only(cx) {
9113 return;
9114 }
9115 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9116 for selection in self.selections.all::<Point>(cx) {
9117 let start = MultiBufferRow(selection.start.row);
9118 // Treat single line selections as if they include the next line. Otherwise this action
9119 // would do nothing for single line selections individual cursors.
9120 let end = if selection.start.row == selection.end.row {
9121 MultiBufferRow(selection.start.row + 1)
9122 } else {
9123 MultiBufferRow(selection.end.row)
9124 };
9125
9126 if let Some(last_row_range) = row_ranges.last_mut() {
9127 if start <= last_row_range.end {
9128 last_row_range.end = end;
9129 continue;
9130 }
9131 }
9132 row_ranges.push(start..end);
9133 }
9134
9135 let snapshot = self.buffer.read(cx).snapshot(cx);
9136 let mut cursor_positions = Vec::new();
9137 for row_range in &row_ranges {
9138 let anchor = snapshot.anchor_before(Point::new(
9139 row_range.end.previous_row().0,
9140 snapshot.line_len(row_range.end.previous_row()),
9141 ));
9142 cursor_positions.push(anchor..anchor);
9143 }
9144
9145 self.transact(window, cx, |this, window, cx| {
9146 for row_range in row_ranges.into_iter().rev() {
9147 for row in row_range.iter_rows().rev() {
9148 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9149 let next_line_row = row.next_row();
9150 let indent = snapshot.indent_size_for_line(next_line_row);
9151 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9152
9153 let replace =
9154 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9155 " "
9156 } else {
9157 ""
9158 };
9159
9160 this.buffer.update(cx, |buffer, cx| {
9161 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9162 });
9163 }
9164 }
9165
9166 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9167 s.select_anchor_ranges(cursor_positions)
9168 });
9169 });
9170 }
9171
9172 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9173 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9174 self.join_lines_impl(true, window, cx);
9175 }
9176
9177 pub fn sort_lines_case_sensitive(
9178 &mut self,
9179 _: &SortLinesCaseSensitive,
9180 window: &mut Window,
9181 cx: &mut Context<Self>,
9182 ) {
9183 self.manipulate_lines(window, cx, |lines| lines.sort())
9184 }
9185
9186 pub fn sort_lines_case_insensitive(
9187 &mut self,
9188 _: &SortLinesCaseInsensitive,
9189 window: &mut Window,
9190 cx: &mut Context<Self>,
9191 ) {
9192 self.manipulate_lines(window, cx, |lines| {
9193 lines.sort_by_key(|line| line.to_lowercase())
9194 })
9195 }
9196
9197 pub fn unique_lines_case_insensitive(
9198 &mut self,
9199 _: &UniqueLinesCaseInsensitive,
9200 window: &mut Window,
9201 cx: &mut Context<Self>,
9202 ) {
9203 self.manipulate_lines(window, cx, |lines| {
9204 let mut seen = HashSet::default();
9205 lines.retain(|line| seen.insert(line.to_lowercase()));
9206 })
9207 }
9208
9209 pub fn unique_lines_case_sensitive(
9210 &mut self,
9211 _: &UniqueLinesCaseSensitive,
9212 window: &mut Window,
9213 cx: &mut Context<Self>,
9214 ) {
9215 self.manipulate_lines(window, cx, |lines| {
9216 let mut seen = HashSet::default();
9217 lines.retain(|line| seen.insert(*line));
9218 })
9219 }
9220
9221 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9222 let Some(project) = self.project.clone() else {
9223 return;
9224 };
9225 self.reload(project, window, cx)
9226 .detach_and_notify_err(window, cx);
9227 }
9228
9229 pub fn restore_file(
9230 &mut self,
9231 _: &::git::RestoreFile,
9232 window: &mut Window,
9233 cx: &mut Context<Self>,
9234 ) {
9235 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9236 let mut buffer_ids = HashSet::default();
9237 let snapshot = self.buffer().read(cx).snapshot(cx);
9238 for selection in self.selections.all::<usize>(cx) {
9239 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9240 }
9241
9242 let buffer = self.buffer().read(cx);
9243 let ranges = buffer_ids
9244 .into_iter()
9245 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9246 .collect::<Vec<_>>();
9247
9248 self.restore_hunks_in_ranges(ranges, window, cx);
9249 }
9250
9251 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9252 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9253 let selections = self
9254 .selections
9255 .all(cx)
9256 .into_iter()
9257 .map(|s| s.range())
9258 .collect();
9259 self.restore_hunks_in_ranges(selections, window, cx);
9260 }
9261
9262 pub fn restore_hunks_in_ranges(
9263 &mut self,
9264 ranges: Vec<Range<Point>>,
9265 window: &mut Window,
9266 cx: &mut Context<Editor>,
9267 ) {
9268 let mut revert_changes = HashMap::default();
9269 let chunk_by = self
9270 .snapshot(window, cx)
9271 .hunks_for_ranges(ranges)
9272 .into_iter()
9273 .chunk_by(|hunk| hunk.buffer_id);
9274 for (buffer_id, hunks) in &chunk_by {
9275 let hunks = hunks.collect::<Vec<_>>();
9276 for hunk in &hunks {
9277 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9278 }
9279 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9280 }
9281 drop(chunk_by);
9282 if !revert_changes.is_empty() {
9283 self.transact(window, cx, |editor, window, cx| {
9284 editor.restore(revert_changes, window, cx);
9285 });
9286 }
9287 }
9288
9289 pub fn open_active_item_in_terminal(
9290 &mut self,
9291 _: &OpenInTerminal,
9292 window: &mut Window,
9293 cx: &mut Context<Self>,
9294 ) {
9295 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9296 let project_path = buffer.read(cx).project_path(cx)?;
9297 let project = self.project.as_ref()?.read(cx);
9298 let entry = project.entry_for_path(&project_path, cx)?;
9299 let parent = match &entry.canonical_path {
9300 Some(canonical_path) => canonical_path.to_path_buf(),
9301 None => project.absolute_path(&project_path, cx)?,
9302 }
9303 .parent()?
9304 .to_path_buf();
9305 Some(parent)
9306 }) {
9307 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9308 }
9309 }
9310
9311 fn set_breakpoint_context_menu(
9312 &mut self,
9313 display_row: DisplayRow,
9314 position: Option<Anchor>,
9315 clicked_point: gpui::Point<Pixels>,
9316 window: &mut Window,
9317 cx: &mut Context<Self>,
9318 ) {
9319 if !cx.has_flag::<DebuggerFeatureFlag>() {
9320 return;
9321 }
9322 let source = self
9323 .buffer
9324 .read(cx)
9325 .snapshot(cx)
9326 .anchor_before(Point::new(display_row.0, 0u32));
9327
9328 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9329
9330 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9331 self,
9332 source,
9333 clicked_point,
9334 context_menu,
9335 window,
9336 cx,
9337 );
9338 }
9339
9340 fn add_edit_breakpoint_block(
9341 &mut self,
9342 anchor: Anchor,
9343 breakpoint: &Breakpoint,
9344 edit_action: BreakpointPromptEditAction,
9345 window: &mut Window,
9346 cx: &mut Context<Self>,
9347 ) {
9348 let weak_editor = cx.weak_entity();
9349 let bp_prompt = cx.new(|cx| {
9350 BreakpointPromptEditor::new(
9351 weak_editor,
9352 anchor,
9353 breakpoint.clone(),
9354 edit_action,
9355 window,
9356 cx,
9357 )
9358 });
9359
9360 let height = bp_prompt.update(cx, |this, cx| {
9361 this.prompt
9362 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9363 });
9364 let cloned_prompt = bp_prompt.clone();
9365 let blocks = vec![BlockProperties {
9366 style: BlockStyle::Sticky,
9367 placement: BlockPlacement::Above(anchor),
9368 height: Some(height),
9369 render: Arc::new(move |cx| {
9370 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9371 cloned_prompt.clone().into_any_element()
9372 }),
9373 priority: 0,
9374 render_in_minimap: true,
9375 }];
9376
9377 let focus_handle = bp_prompt.focus_handle(cx);
9378 window.focus(&focus_handle);
9379
9380 let block_ids = self.insert_blocks(blocks, None, cx);
9381 bp_prompt.update(cx, |prompt, _| {
9382 prompt.add_block_ids(block_ids);
9383 });
9384 }
9385
9386 pub(crate) fn breakpoint_at_row(
9387 &self,
9388 row: u32,
9389 window: &mut Window,
9390 cx: &mut Context<Self>,
9391 ) -> Option<(Anchor, Breakpoint)> {
9392 let snapshot = self.snapshot(window, cx);
9393 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9394
9395 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9396 }
9397
9398 pub(crate) fn breakpoint_at_anchor(
9399 &self,
9400 breakpoint_position: Anchor,
9401 snapshot: &EditorSnapshot,
9402 cx: &mut Context<Self>,
9403 ) -> Option<(Anchor, Breakpoint)> {
9404 let project = self.project.clone()?;
9405
9406 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9407 snapshot
9408 .buffer_snapshot
9409 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9410 })?;
9411
9412 let enclosing_excerpt = breakpoint_position.excerpt_id;
9413 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
9414 let buffer_snapshot = buffer.read(cx).snapshot();
9415
9416 let row = buffer_snapshot
9417 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9418 .row;
9419
9420 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9421 let anchor_end = snapshot
9422 .buffer_snapshot
9423 .anchor_after(Point::new(row, line_len));
9424
9425 let bp = self
9426 .breakpoint_store
9427 .as_ref()?
9428 .read_with(cx, |breakpoint_store, cx| {
9429 breakpoint_store
9430 .breakpoints(
9431 &buffer,
9432 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9433 &buffer_snapshot,
9434 cx,
9435 )
9436 .next()
9437 .and_then(|(anchor, bp)| {
9438 let breakpoint_row = buffer_snapshot
9439 .summary_for_anchor::<text::PointUtf16>(anchor)
9440 .row;
9441
9442 if breakpoint_row == row {
9443 snapshot
9444 .buffer_snapshot
9445 .anchor_in_excerpt(enclosing_excerpt, *anchor)
9446 .map(|anchor| (anchor, bp.clone()))
9447 } else {
9448 None
9449 }
9450 })
9451 });
9452 bp
9453 }
9454
9455 pub fn edit_log_breakpoint(
9456 &mut self,
9457 _: &EditLogBreakpoint,
9458 window: &mut Window,
9459 cx: &mut Context<Self>,
9460 ) {
9461 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9462 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9463 message: None,
9464 state: BreakpointState::Enabled,
9465 condition: None,
9466 hit_condition: None,
9467 });
9468
9469 self.add_edit_breakpoint_block(
9470 anchor,
9471 &breakpoint,
9472 BreakpointPromptEditAction::Log,
9473 window,
9474 cx,
9475 );
9476 }
9477 }
9478
9479 fn breakpoints_at_cursors(
9480 &self,
9481 window: &mut Window,
9482 cx: &mut Context<Self>,
9483 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9484 let snapshot = self.snapshot(window, cx);
9485 let cursors = self
9486 .selections
9487 .disjoint_anchors()
9488 .into_iter()
9489 .map(|selection| {
9490 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9491
9492 let breakpoint_position = self
9493 .breakpoint_at_row(cursor_position.row, window, cx)
9494 .map(|bp| bp.0)
9495 .unwrap_or_else(|| {
9496 snapshot
9497 .display_snapshot
9498 .buffer_snapshot
9499 .anchor_after(Point::new(cursor_position.row, 0))
9500 });
9501
9502 let breakpoint = self
9503 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9504 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9505
9506 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9507 })
9508 // 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.
9509 .collect::<HashMap<Anchor, _>>();
9510
9511 cursors.into_iter().collect()
9512 }
9513
9514 pub fn enable_breakpoint(
9515 &mut self,
9516 _: &crate::actions::EnableBreakpoint,
9517 window: &mut Window,
9518 cx: &mut Context<Self>,
9519 ) {
9520 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9521 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9522 continue;
9523 };
9524 self.edit_breakpoint_at_anchor(
9525 anchor,
9526 breakpoint,
9527 BreakpointEditAction::InvertState,
9528 cx,
9529 );
9530 }
9531 }
9532
9533 pub fn disable_breakpoint(
9534 &mut self,
9535 _: &crate::actions::DisableBreakpoint,
9536 window: &mut Window,
9537 cx: &mut Context<Self>,
9538 ) {
9539 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9540 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9541 continue;
9542 };
9543 self.edit_breakpoint_at_anchor(
9544 anchor,
9545 breakpoint,
9546 BreakpointEditAction::InvertState,
9547 cx,
9548 );
9549 }
9550 }
9551
9552 pub fn toggle_breakpoint(
9553 &mut self,
9554 _: &crate::actions::ToggleBreakpoint,
9555 window: &mut Window,
9556 cx: &mut Context<Self>,
9557 ) {
9558 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9559 if let Some(breakpoint) = breakpoint {
9560 self.edit_breakpoint_at_anchor(
9561 anchor,
9562 breakpoint,
9563 BreakpointEditAction::Toggle,
9564 cx,
9565 );
9566 } else {
9567 self.edit_breakpoint_at_anchor(
9568 anchor,
9569 Breakpoint::new_standard(),
9570 BreakpointEditAction::Toggle,
9571 cx,
9572 );
9573 }
9574 }
9575 }
9576
9577 pub fn edit_breakpoint_at_anchor(
9578 &mut self,
9579 breakpoint_position: Anchor,
9580 breakpoint: Breakpoint,
9581 edit_action: BreakpointEditAction,
9582 cx: &mut Context<Self>,
9583 ) {
9584 let Some(breakpoint_store) = &self.breakpoint_store else {
9585 return;
9586 };
9587
9588 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9589 if breakpoint_position == Anchor::min() {
9590 self.buffer()
9591 .read(cx)
9592 .excerpt_buffer_ids()
9593 .into_iter()
9594 .next()
9595 } else {
9596 None
9597 }
9598 }) else {
9599 return;
9600 };
9601
9602 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9603 return;
9604 };
9605
9606 breakpoint_store.update(cx, |breakpoint_store, cx| {
9607 breakpoint_store.toggle_breakpoint(
9608 buffer,
9609 (breakpoint_position.text_anchor, breakpoint),
9610 edit_action,
9611 cx,
9612 );
9613 });
9614
9615 cx.notify();
9616 }
9617
9618 #[cfg(any(test, feature = "test-support"))]
9619 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9620 self.breakpoint_store.clone()
9621 }
9622
9623 pub fn prepare_restore_change(
9624 &self,
9625 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9626 hunk: &MultiBufferDiffHunk,
9627 cx: &mut App,
9628 ) -> Option<()> {
9629 if hunk.is_created_file() {
9630 return None;
9631 }
9632 let buffer = self.buffer.read(cx);
9633 let diff = buffer.diff_for(hunk.buffer_id)?;
9634 let buffer = buffer.buffer(hunk.buffer_id)?;
9635 let buffer = buffer.read(cx);
9636 let original_text = diff
9637 .read(cx)
9638 .base_text()
9639 .as_rope()
9640 .slice(hunk.diff_base_byte_range.clone());
9641 let buffer_snapshot = buffer.snapshot();
9642 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9643 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9644 probe
9645 .0
9646 .start
9647 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9648 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9649 }) {
9650 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9651 Some(())
9652 } else {
9653 None
9654 }
9655 }
9656
9657 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9658 self.manipulate_lines(window, cx, |lines| lines.reverse())
9659 }
9660
9661 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9662 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9663 }
9664
9665 fn manipulate_lines<Fn>(
9666 &mut self,
9667 window: &mut Window,
9668 cx: &mut Context<Self>,
9669 mut callback: Fn,
9670 ) where
9671 Fn: FnMut(&mut Vec<&str>),
9672 {
9673 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9674
9675 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9676 let buffer = self.buffer.read(cx).snapshot(cx);
9677
9678 let mut edits = Vec::new();
9679
9680 let selections = self.selections.all::<Point>(cx);
9681 let mut selections = selections.iter().peekable();
9682 let mut contiguous_row_selections = Vec::new();
9683 let mut new_selections = Vec::new();
9684 let mut added_lines = 0;
9685 let mut removed_lines = 0;
9686
9687 while let Some(selection) = selections.next() {
9688 let (start_row, end_row) = consume_contiguous_rows(
9689 &mut contiguous_row_selections,
9690 selection,
9691 &display_map,
9692 &mut selections,
9693 );
9694
9695 let start_point = Point::new(start_row.0, 0);
9696 let end_point = Point::new(
9697 end_row.previous_row().0,
9698 buffer.line_len(end_row.previous_row()),
9699 );
9700 let text = buffer
9701 .text_for_range(start_point..end_point)
9702 .collect::<String>();
9703
9704 let mut lines = text.split('\n').collect_vec();
9705
9706 let lines_before = lines.len();
9707 callback(&mut lines);
9708 let lines_after = lines.len();
9709
9710 edits.push((start_point..end_point, lines.join("\n")));
9711
9712 // Selections must change based on added and removed line count
9713 let start_row =
9714 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9715 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9716 new_selections.push(Selection {
9717 id: selection.id,
9718 start: start_row,
9719 end: end_row,
9720 goal: SelectionGoal::None,
9721 reversed: selection.reversed,
9722 });
9723
9724 if lines_after > lines_before {
9725 added_lines += lines_after - lines_before;
9726 } else if lines_before > lines_after {
9727 removed_lines += lines_before - lines_after;
9728 }
9729 }
9730
9731 self.transact(window, cx, |this, window, cx| {
9732 let buffer = this.buffer.update(cx, |buffer, cx| {
9733 buffer.edit(edits, None, cx);
9734 buffer.snapshot(cx)
9735 });
9736
9737 // Recalculate offsets on newly edited buffer
9738 let new_selections = new_selections
9739 .iter()
9740 .map(|s| {
9741 let start_point = Point::new(s.start.0, 0);
9742 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9743 Selection {
9744 id: s.id,
9745 start: buffer.point_to_offset(start_point),
9746 end: buffer.point_to_offset(end_point),
9747 goal: s.goal,
9748 reversed: s.reversed,
9749 }
9750 })
9751 .collect();
9752
9753 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9754 s.select(new_selections);
9755 });
9756
9757 this.request_autoscroll(Autoscroll::fit(), cx);
9758 });
9759 }
9760
9761 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
9762 self.manipulate_text(window, cx, |text| {
9763 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
9764 if has_upper_case_characters {
9765 text.to_lowercase()
9766 } else {
9767 text.to_uppercase()
9768 }
9769 })
9770 }
9771
9772 pub fn convert_to_upper_case(
9773 &mut self,
9774 _: &ConvertToUpperCase,
9775 window: &mut Window,
9776 cx: &mut Context<Self>,
9777 ) {
9778 self.manipulate_text(window, cx, |text| text.to_uppercase())
9779 }
9780
9781 pub fn convert_to_lower_case(
9782 &mut self,
9783 _: &ConvertToLowerCase,
9784 window: &mut Window,
9785 cx: &mut Context<Self>,
9786 ) {
9787 self.manipulate_text(window, cx, |text| text.to_lowercase())
9788 }
9789
9790 pub fn convert_to_title_case(
9791 &mut self,
9792 _: &ConvertToTitleCase,
9793 window: &mut Window,
9794 cx: &mut Context<Self>,
9795 ) {
9796 self.manipulate_text(window, cx, |text| {
9797 text.split('\n')
9798 .map(|line| line.to_case(Case::Title))
9799 .join("\n")
9800 })
9801 }
9802
9803 pub fn convert_to_snake_case(
9804 &mut self,
9805 _: &ConvertToSnakeCase,
9806 window: &mut Window,
9807 cx: &mut Context<Self>,
9808 ) {
9809 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
9810 }
9811
9812 pub fn convert_to_kebab_case(
9813 &mut self,
9814 _: &ConvertToKebabCase,
9815 window: &mut Window,
9816 cx: &mut Context<Self>,
9817 ) {
9818 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
9819 }
9820
9821 pub fn convert_to_upper_camel_case(
9822 &mut self,
9823 _: &ConvertToUpperCamelCase,
9824 window: &mut Window,
9825 cx: &mut Context<Self>,
9826 ) {
9827 self.manipulate_text(window, cx, |text| {
9828 text.split('\n')
9829 .map(|line| line.to_case(Case::UpperCamel))
9830 .join("\n")
9831 })
9832 }
9833
9834 pub fn convert_to_lower_camel_case(
9835 &mut self,
9836 _: &ConvertToLowerCamelCase,
9837 window: &mut Window,
9838 cx: &mut Context<Self>,
9839 ) {
9840 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
9841 }
9842
9843 pub fn convert_to_opposite_case(
9844 &mut self,
9845 _: &ConvertToOppositeCase,
9846 window: &mut Window,
9847 cx: &mut Context<Self>,
9848 ) {
9849 self.manipulate_text(window, cx, |text| {
9850 text.chars()
9851 .fold(String::with_capacity(text.len()), |mut t, c| {
9852 if c.is_uppercase() {
9853 t.extend(c.to_lowercase());
9854 } else {
9855 t.extend(c.to_uppercase());
9856 }
9857 t
9858 })
9859 })
9860 }
9861
9862 pub fn convert_to_rot13(
9863 &mut self,
9864 _: &ConvertToRot13,
9865 window: &mut Window,
9866 cx: &mut Context<Self>,
9867 ) {
9868 self.manipulate_text(window, cx, |text| {
9869 text.chars()
9870 .map(|c| match c {
9871 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
9872 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
9873 _ => c,
9874 })
9875 .collect()
9876 })
9877 }
9878
9879 pub fn convert_to_rot47(
9880 &mut self,
9881 _: &ConvertToRot47,
9882 window: &mut Window,
9883 cx: &mut Context<Self>,
9884 ) {
9885 self.manipulate_text(window, cx, |text| {
9886 text.chars()
9887 .map(|c| {
9888 let code_point = c as u32;
9889 if code_point >= 33 && code_point <= 126 {
9890 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
9891 }
9892 c
9893 })
9894 .collect()
9895 })
9896 }
9897
9898 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
9899 where
9900 Fn: FnMut(&str) -> String,
9901 {
9902 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9903 let buffer = self.buffer.read(cx).snapshot(cx);
9904
9905 let mut new_selections = Vec::new();
9906 let mut edits = Vec::new();
9907 let mut selection_adjustment = 0i32;
9908
9909 for selection in self.selections.all::<usize>(cx) {
9910 let selection_is_empty = selection.is_empty();
9911
9912 let (start, end) = if selection_is_empty {
9913 let word_range = movement::surrounding_word(
9914 &display_map,
9915 selection.start.to_display_point(&display_map),
9916 );
9917 let start = word_range.start.to_offset(&display_map, Bias::Left);
9918 let end = word_range.end.to_offset(&display_map, Bias::Left);
9919 (start, end)
9920 } else {
9921 (selection.start, selection.end)
9922 };
9923
9924 let text = buffer.text_for_range(start..end).collect::<String>();
9925 let old_length = text.len() as i32;
9926 let text = callback(&text);
9927
9928 new_selections.push(Selection {
9929 start: (start as i32 - selection_adjustment) as usize,
9930 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
9931 goal: SelectionGoal::None,
9932 ..selection
9933 });
9934
9935 selection_adjustment += old_length - text.len() as i32;
9936
9937 edits.push((start..end, text));
9938 }
9939
9940 self.transact(window, cx, |this, window, cx| {
9941 this.buffer.update(cx, |buffer, cx| {
9942 buffer.edit(edits, None, cx);
9943 });
9944
9945 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9946 s.select(new_selections);
9947 });
9948
9949 this.request_autoscroll(Autoscroll::fit(), cx);
9950 });
9951 }
9952
9953 pub fn duplicate(
9954 &mut self,
9955 upwards: bool,
9956 whole_lines: bool,
9957 window: &mut Window,
9958 cx: &mut Context<Self>,
9959 ) {
9960 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9961
9962 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9963 let buffer = &display_map.buffer_snapshot;
9964 let selections = self.selections.all::<Point>(cx);
9965
9966 let mut edits = Vec::new();
9967 let mut selections_iter = selections.iter().peekable();
9968 while let Some(selection) = selections_iter.next() {
9969 let mut rows = selection.spanned_rows(false, &display_map);
9970 // duplicate line-wise
9971 if whole_lines || selection.start == selection.end {
9972 // Avoid duplicating the same lines twice.
9973 while let Some(next_selection) = selections_iter.peek() {
9974 let next_rows = next_selection.spanned_rows(false, &display_map);
9975 if next_rows.start < rows.end {
9976 rows.end = next_rows.end;
9977 selections_iter.next().unwrap();
9978 } else {
9979 break;
9980 }
9981 }
9982
9983 // Copy the text from the selected row region and splice it either at the start
9984 // or end of the region.
9985 let start = Point::new(rows.start.0, 0);
9986 let end = Point::new(
9987 rows.end.previous_row().0,
9988 buffer.line_len(rows.end.previous_row()),
9989 );
9990 let text = buffer
9991 .text_for_range(start..end)
9992 .chain(Some("\n"))
9993 .collect::<String>();
9994 let insert_location = if upwards {
9995 Point::new(rows.end.0, 0)
9996 } else {
9997 start
9998 };
9999 edits.push((insert_location..insert_location, text));
10000 } else {
10001 // duplicate character-wise
10002 let start = selection.start;
10003 let end = selection.end;
10004 let text = buffer.text_for_range(start..end).collect::<String>();
10005 edits.push((selection.end..selection.end, text));
10006 }
10007 }
10008
10009 self.transact(window, cx, |this, _, cx| {
10010 this.buffer.update(cx, |buffer, cx| {
10011 buffer.edit(edits, None, cx);
10012 });
10013
10014 this.request_autoscroll(Autoscroll::fit(), cx);
10015 });
10016 }
10017
10018 pub fn duplicate_line_up(
10019 &mut self,
10020 _: &DuplicateLineUp,
10021 window: &mut Window,
10022 cx: &mut Context<Self>,
10023 ) {
10024 self.duplicate(true, true, window, cx);
10025 }
10026
10027 pub fn duplicate_line_down(
10028 &mut self,
10029 _: &DuplicateLineDown,
10030 window: &mut Window,
10031 cx: &mut Context<Self>,
10032 ) {
10033 self.duplicate(false, true, window, cx);
10034 }
10035
10036 pub fn duplicate_selection(
10037 &mut self,
10038 _: &DuplicateSelection,
10039 window: &mut Window,
10040 cx: &mut Context<Self>,
10041 ) {
10042 self.duplicate(false, false, window, cx);
10043 }
10044
10045 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10046 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10047
10048 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10049 let buffer = self.buffer.read(cx).snapshot(cx);
10050
10051 let mut edits = Vec::new();
10052 let mut unfold_ranges = Vec::new();
10053 let mut refold_creases = Vec::new();
10054
10055 let selections = self.selections.all::<Point>(cx);
10056 let mut selections = selections.iter().peekable();
10057 let mut contiguous_row_selections = Vec::new();
10058 let mut new_selections = Vec::new();
10059
10060 while let Some(selection) = selections.next() {
10061 // Find all the selections that span a contiguous row range
10062 let (start_row, end_row) = consume_contiguous_rows(
10063 &mut contiguous_row_selections,
10064 selection,
10065 &display_map,
10066 &mut selections,
10067 );
10068
10069 // Move the text spanned by the row range to be before the line preceding the row range
10070 if start_row.0 > 0 {
10071 let range_to_move = Point::new(
10072 start_row.previous_row().0,
10073 buffer.line_len(start_row.previous_row()),
10074 )
10075 ..Point::new(
10076 end_row.previous_row().0,
10077 buffer.line_len(end_row.previous_row()),
10078 );
10079 let insertion_point = display_map
10080 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10081 .0;
10082
10083 // Don't move lines across excerpts
10084 if buffer
10085 .excerpt_containing(insertion_point..range_to_move.end)
10086 .is_some()
10087 {
10088 let text = buffer
10089 .text_for_range(range_to_move.clone())
10090 .flat_map(|s| s.chars())
10091 .skip(1)
10092 .chain(['\n'])
10093 .collect::<String>();
10094
10095 edits.push((
10096 buffer.anchor_after(range_to_move.start)
10097 ..buffer.anchor_before(range_to_move.end),
10098 String::new(),
10099 ));
10100 let insertion_anchor = buffer.anchor_after(insertion_point);
10101 edits.push((insertion_anchor..insertion_anchor, text));
10102
10103 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10104
10105 // Move selections up
10106 new_selections.extend(contiguous_row_selections.drain(..).map(
10107 |mut selection| {
10108 selection.start.row -= row_delta;
10109 selection.end.row -= row_delta;
10110 selection
10111 },
10112 ));
10113
10114 // Move folds up
10115 unfold_ranges.push(range_to_move.clone());
10116 for fold in display_map.folds_in_range(
10117 buffer.anchor_before(range_to_move.start)
10118 ..buffer.anchor_after(range_to_move.end),
10119 ) {
10120 let mut start = fold.range.start.to_point(&buffer);
10121 let mut end = fold.range.end.to_point(&buffer);
10122 start.row -= row_delta;
10123 end.row -= row_delta;
10124 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10125 }
10126 }
10127 }
10128
10129 // If we didn't move line(s), preserve the existing selections
10130 new_selections.append(&mut contiguous_row_selections);
10131 }
10132
10133 self.transact(window, cx, |this, window, cx| {
10134 this.unfold_ranges(&unfold_ranges, true, true, cx);
10135 this.buffer.update(cx, |buffer, cx| {
10136 for (range, text) in edits {
10137 buffer.edit([(range, text)], None, cx);
10138 }
10139 });
10140 this.fold_creases(refold_creases, true, window, cx);
10141 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10142 s.select(new_selections);
10143 })
10144 });
10145 }
10146
10147 pub fn move_line_down(
10148 &mut self,
10149 _: &MoveLineDown,
10150 window: &mut Window,
10151 cx: &mut Context<Self>,
10152 ) {
10153 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10154
10155 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10156 let buffer = self.buffer.read(cx).snapshot(cx);
10157
10158 let mut edits = Vec::new();
10159 let mut unfold_ranges = Vec::new();
10160 let mut refold_creases = Vec::new();
10161
10162 let selections = self.selections.all::<Point>(cx);
10163 let mut selections = selections.iter().peekable();
10164 let mut contiguous_row_selections = Vec::new();
10165 let mut new_selections = Vec::new();
10166
10167 while let Some(selection) = selections.next() {
10168 // Find all the selections that span a contiguous row range
10169 let (start_row, end_row) = consume_contiguous_rows(
10170 &mut contiguous_row_selections,
10171 selection,
10172 &display_map,
10173 &mut selections,
10174 );
10175
10176 // Move the text spanned by the row range to be after the last line of the row range
10177 if end_row.0 <= buffer.max_point().row {
10178 let range_to_move =
10179 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10180 let insertion_point = display_map
10181 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10182 .0;
10183
10184 // Don't move lines across excerpt boundaries
10185 if buffer
10186 .excerpt_containing(range_to_move.start..insertion_point)
10187 .is_some()
10188 {
10189 let mut text = String::from("\n");
10190 text.extend(buffer.text_for_range(range_to_move.clone()));
10191 text.pop(); // Drop trailing newline
10192 edits.push((
10193 buffer.anchor_after(range_to_move.start)
10194 ..buffer.anchor_before(range_to_move.end),
10195 String::new(),
10196 ));
10197 let insertion_anchor = buffer.anchor_after(insertion_point);
10198 edits.push((insertion_anchor..insertion_anchor, text));
10199
10200 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10201
10202 // Move selections down
10203 new_selections.extend(contiguous_row_selections.drain(..).map(
10204 |mut selection| {
10205 selection.start.row += row_delta;
10206 selection.end.row += row_delta;
10207 selection
10208 },
10209 ));
10210
10211 // Move folds down
10212 unfold_ranges.push(range_to_move.clone());
10213 for fold in display_map.folds_in_range(
10214 buffer.anchor_before(range_to_move.start)
10215 ..buffer.anchor_after(range_to_move.end),
10216 ) {
10217 let mut start = fold.range.start.to_point(&buffer);
10218 let mut end = fold.range.end.to_point(&buffer);
10219 start.row += row_delta;
10220 end.row += row_delta;
10221 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10222 }
10223 }
10224 }
10225
10226 // If we didn't move line(s), preserve the existing selections
10227 new_selections.append(&mut contiguous_row_selections);
10228 }
10229
10230 self.transact(window, cx, |this, window, cx| {
10231 this.unfold_ranges(&unfold_ranges, true, true, cx);
10232 this.buffer.update(cx, |buffer, cx| {
10233 for (range, text) in edits {
10234 buffer.edit([(range, text)], None, cx);
10235 }
10236 });
10237 this.fold_creases(refold_creases, true, window, cx);
10238 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10239 s.select(new_selections)
10240 });
10241 });
10242 }
10243
10244 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10245 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10246 let text_layout_details = &self.text_layout_details(window);
10247 self.transact(window, cx, |this, window, cx| {
10248 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10249 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10250 s.move_with(|display_map, selection| {
10251 if !selection.is_empty() {
10252 return;
10253 }
10254
10255 let mut head = selection.head();
10256 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10257 if head.column() == display_map.line_len(head.row()) {
10258 transpose_offset = display_map
10259 .buffer_snapshot
10260 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10261 }
10262
10263 if transpose_offset == 0 {
10264 return;
10265 }
10266
10267 *head.column_mut() += 1;
10268 head = display_map.clip_point(head, Bias::Right);
10269 let goal = SelectionGoal::HorizontalPosition(
10270 display_map
10271 .x_for_display_point(head, text_layout_details)
10272 .into(),
10273 );
10274 selection.collapse_to(head, goal);
10275
10276 let transpose_start = display_map
10277 .buffer_snapshot
10278 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10279 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10280 let transpose_end = display_map
10281 .buffer_snapshot
10282 .clip_offset(transpose_offset + 1, Bias::Right);
10283 if let Some(ch) =
10284 display_map.buffer_snapshot.chars_at(transpose_start).next()
10285 {
10286 edits.push((transpose_start..transpose_offset, String::new()));
10287 edits.push((transpose_end..transpose_end, ch.to_string()));
10288 }
10289 }
10290 });
10291 edits
10292 });
10293 this.buffer
10294 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10295 let selections = this.selections.all::<usize>(cx);
10296 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10297 s.select(selections);
10298 });
10299 });
10300 }
10301
10302 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10303 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10304 self.rewrap_impl(RewrapOptions::default(), cx)
10305 }
10306
10307 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10308 let buffer = self.buffer.read(cx).snapshot(cx);
10309 let selections = self.selections.all::<Point>(cx);
10310 let mut selections = selections.iter().peekable();
10311
10312 let mut edits = Vec::new();
10313 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10314
10315 while let Some(selection) = selections.next() {
10316 let mut start_row = selection.start.row;
10317 let mut end_row = selection.end.row;
10318
10319 // Skip selections that overlap with a range that has already been rewrapped.
10320 let selection_range = start_row..end_row;
10321 if rewrapped_row_ranges
10322 .iter()
10323 .any(|range| range.overlaps(&selection_range))
10324 {
10325 continue;
10326 }
10327
10328 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10329
10330 // Since not all lines in the selection may be at the same indent
10331 // level, choose the indent size that is the most common between all
10332 // of the lines.
10333 //
10334 // If there is a tie, we use the deepest indent.
10335 let (indent_size, indent_end) = {
10336 let mut indent_size_occurrences = HashMap::default();
10337 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10338
10339 for row in start_row..=end_row {
10340 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10341 rows_by_indent_size.entry(indent).or_default().push(row);
10342 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10343 }
10344
10345 let indent_size = indent_size_occurrences
10346 .into_iter()
10347 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10348 .map(|(indent, _)| indent)
10349 .unwrap_or_default();
10350 let row = rows_by_indent_size[&indent_size][0];
10351 let indent_end = Point::new(row, indent_size.len);
10352
10353 (indent_size, indent_end)
10354 };
10355
10356 let mut line_prefix = indent_size.chars().collect::<String>();
10357
10358 let mut inside_comment = false;
10359 if let Some(comment_prefix) =
10360 buffer
10361 .language_scope_at(selection.head())
10362 .and_then(|language| {
10363 language
10364 .line_comment_prefixes()
10365 .iter()
10366 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10367 .cloned()
10368 })
10369 {
10370 line_prefix.push_str(&comment_prefix);
10371 inside_comment = true;
10372 }
10373
10374 let language_settings = buffer.language_settings_at(selection.head(), cx);
10375 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10376 RewrapBehavior::InComments => inside_comment,
10377 RewrapBehavior::InSelections => !selection.is_empty(),
10378 RewrapBehavior::Anywhere => true,
10379 };
10380
10381 let should_rewrap = options.override_language_settings
10382 || allow_rewrap_based_on_language
10383 || self.hard_wrap.is_some();
10384 if !should_rewrap {
10385 continue;
10386 }
10387
10388 if selection.is_empty() {
10389 'expand_upwards: while start_row > 0 {
10390 let prev_row = start_row - 1;
10391 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10392 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10393 {
10394 start_row = prev_row;
10395 } else {
10396 break 'expand_upwards;
10397 }
10398 }
10399
10400 'expand_downwards: while end_row < buffer.max_point().row {
10401 let next_row = end_row + 1;
10402 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10403 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10404 {
10405 end_row = next_row;
10406 } else {
10407 break 'expand_downwards;
10408 }
10409 }
10410 }
10411
10412 let start = Point::new(start_row, 0);
10413 let start_offset = start.to_offset(&buffer);
10414 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10415 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10416 let Some(lines_without_prefixes) = selection_text
10417 .lines()
10418 .map(|line| {
10419 line.strip_prefix(&line_prefix)
10420 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10421 .ok_or_else(|| {
10422 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
10423 })
10424 })
10425 .collect::<Result<Vec<_>, _>>()
10426 .log_err()
10427 else {
10428 continue;
10429 };
10430
10431 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10432 buffer
10433 .language_settings_at(Point::new(start_row, 0), cx)
10434 .preferred_line_length as usize
10435 });
10436 let wrapped_text = wrap_with_prefix(
10437 line_prefix,
10438 lines_without_prefixes.join("\n"),
10439 wrap_column,
10440 tab_size,
10441 options.preserve_existing_whitespace,
10442 );
10443
10444 // TODO: should always use char-based diff while still supporting cursor behavior that
10445 // matches vim.
10446 let mut diff_options = DiffOptions::default();
10447 if options.override_language_settings {
10448 diff_options.max_word_diff_len = 0;
10449 diff_options.max_word_diff_line_count = 0;
10450 } else {
10451 diff_options.max_word_diff_len = usize::MAX;
10452 diff_options.max_word_diff_line_count = usize::MAX;
10453 }
10454
10455 for (old_range, new_text) in
10456 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10457 {
10458 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10459 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10460 edits.push((edit_start..edit_end, new_text));
10461 }
10462
10463 rewrapped_row_ranges.push(start_row..=end_row);
10464 }
10465
10466 self.buffer
10467 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10468 }
10469
10470 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10471 let mut text = String::new();
10472 let buffer = self.buffer.read(cx).snapshot(cx);
10473 let mut selections = self.selections.all::<Point>(cx);
10474 let mut clipboard_selections = Vec::with_capacity(selections.len());
10475 {
10476 let max_point = buffer.max_point();
10477 let mut is_first = true;
10478 for selection in &mut selections {
10479 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10480 if is_entire_line {
10481 selection.start = Point::new(selection.start.row, 0);
10482 if !selection.is_empty() && selection.end.column == 0 {
10483 selection.end = cmp::min(max_point, selection.end);
10484 } else {
10485 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10486 }
10487 selection.goal = SelectionGoal::None;
10488 }
10489 if is_first {
10490 is_first = false;
10491 } else {
10492 text += "\n";
10493 }
10494 let mut len = 0;
10495 for chunk in buffer.text_for_range(selection.start..selection.end) {
10496 text.push_str(chunk);
10497 len += chunk.len();
10498 }
10499 clipboard_selections.push(ClipboardSelection {
10500 len,
10501 is_entire_line,
10502 first_line_indent: buffer
10503 .indent_size_for_line(MultiBufferRow(selection.start.row))
10504 .len,
10505 });
10506 }
10507 }
10508
10509 self.transact(window, cx, |this, window, cx| {
10510 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10511 s.select(selections);
10512 });
10513 this.insert("", window, cx);
10514 });
10515 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10516 }
10517
10518 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10519 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10520 let item = self.cut_common(window, cx);
10521 cx.write_to_clipboard(item);
10522 }
10523
10524 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10525 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10526 self.change_selections(None, window, cx, |s| {
10527 s.move_with(|snapshot, sel| {
10528 if sel.is_empty() {
10529 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10530 }
10531 });
10532 });
10533 let item = self.cut_common(window, cx);
10534 cx.set_global(KillRing(item))
10535 }
10536
10537 pub fn kill_ring_yank(
10538 &mut self,
10539 _: &KillRingYank,
10540 window: &mut Window,
10541 cx: &mut Context<Self>,
10542 ) {
10543 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10544 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10545 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10546 (kill_ring.text().to_string(), kill_ring.metadata_json())
10547 } else {
10548 return;
10549 }
10550 } else {
10551 return;
10552 };
10553 self.do_paste(&text, metadata, false, window, cx);
10554 }
10555
10556 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10557 self.do_copy(true, cx);
10558 }
10559
10560 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10561 self.do_copy(false, cx);
10562 }
10563
10564 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10565 let selections = self.selections.all::<Point>(cx);
10566 let buffer = self.buffer.read(cx).read(cx);
10567 let mut text = String::new();
10568
10569 let mut clipboard_selections = Vec::with_capacity(selections.len());
10570 {
10571 let max_point = buffer.max_point();
10572 let mut is_first = true;
10573 for selection in &selections {
10574 let mut start = selection.start;
10575 let mut end = selection.end;
10576 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10577 if is_entire_line {
10578 start = Point::new(start.row, 0);
10579 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10580 }
10581
10582 let mut trimmed_selections = Vec::new();
10583 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10584 let row = MultiBufferRow(start.row);
10585 let first_indent = buffer.indent_size_for_line(row);
10586 if first_indent.len == 0 || start.column > first_indent.len {
10587 trimmed_selections.push(start..end);
10588 } else {
10589 trimmed_selections.push(
10590 Point::new(row.0, first_indent.len)
10591 ..Point::new(row.0, buffer.line_len(row)),
10592 );
10593 for row in start.row + 1..=end.row {
10594 let mut line_len = buffer.line_len(MultiBufferRow(row));
10595 if row == end.row {
10596 line_len = end.column;
10597 }
10598 if line_len == 0 {
10599 trimmed_selections
10600 .push(Point::new(row, 0)..Point::new(row, line_len));
10601 continue;
10602 }
10603 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10604 if row_indent_size.len >= first_indent.len {
10605 trimmed_selections.push(
10606 Point::new(row, first_indent.len)..Point::new(row, line_len),
10607 );
10608 } else {
10609 trimmed_selections.clear();
10610 trimmed_selections.push(start..end);
10611 break;
10612 }
10613 }
10614 }
10615 } else {
10616 trimmed_selections.push(start..end);
10617 }
10618
10619 for trimmed_range in trimmed_selections {
10620 if is_first {
10621 is_first = false;
10622 } else {
10623 text += "\n";
10624 }
10625 let mut len = 0;
10626 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10627 text.push_str(chunk);
10628 len += chunk.len();
10629 }
10630 clipboard_selections.push(ClipboardSelection {
10631 len,
10632 is_entire_line,
10633 first_line_indent: buffer
10634 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10635 .len,
10636 });
10637 }
10638 }
10639 }
10640
10641 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10642 text,
10643 clipboard_selections,
10644 ));
10645 }
10646
10647 pub fn do_paste(
10648 &mut self,
10649 text: &String,
10650 clipboard_selections: Option<Vec<ClipboardSelection>>,
10651 handle_entire_lines: bool,
10652 window: &mut Window,
10653 cx: &mut Context<Self>,
10654 ) {
10655 if self.read_only(cx) {
10656 return;
10657 }
10658
10659 let clipboard_text = Cow::Borrowed(text);
10660
10661 self.transact(window, cx, |this, window, cx| {
10662 if let Some(mut clipboard_selections) = clipboard_selections {
10663 let old_selections = this.selections.all::<usize>(cx);
10664 let all_selections_were_entire_line =
10665 clipboard_selections.iter().all(|s| s.is_entire_line);
10666 let first_selection_indent_column =
10667 clipboard_selections.first().map(|s| s.first_line_indent);
10668 if clipboard_selections.len() != old_selections.len() {
10669 clipboard_selections.drain(..);
10670 }
10671 let cursor_offset = this.selections.last::<usize>(cx).head();
10672 let mut auto_indent_on_paste = true;
10673
10674 this.buffer.update(cx, |buffer, cx| {
10675 let snapshot = buffer.read(cx);
10676 auto_indent_on_paste = snapshot
10677 .language_settings_at(cursor_offset, cx)
10678 .auto_indent_on_paste;
10679
10680 let mut start_offset = 0;
10681 let mut edits = Vec::new();
10682 let mut original_indent_columns = Vec::new();
10683 for (ix, selection) in old_selections.iter().enumerate() {
10684 let to_insert;
10685 let entire_line;
10686 let original_indent_column;
10687 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10688 let end_offset = start_offset + clipboard_selection.len;
10689 to_insert = &clipboard_text[start_offset..end_offset];
10690 entire_line = clipboard_selection.is_entire_line;
10691 start_offset = end_offset + 1;
10692 original_indent_column = Some(clipboard_selection.first_line_indent);
10693 } else {
10694 to_insert = clipboard_text.as_str();
10695 entire_line = all_selections_were_entire_line;
10696 original_indent_column = first_selection_indent_column
10697 }
10698
10699 // If the corresponding selection was empty when this slice of the
10700 // clipboard text was written, then the entire line containing the
10701 // selection was copied. If this selection is also currently empty,
10702 // then paste the line before the current line of the buffer.
10703 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10704 let column = selection.start.to_point(&snapshot).column as usize;
10705 let line_start = selection.start - column;
10706 line_start..line_start
10707 } else {
10708 selection.range()
10709 };
10710
10711 edits.push((range, to_insert));
10712 original_indent_columns.push(original_indent_column);
10713 }
10714 drop(snapshot);
10715
10716 buffer.edit(
10717 edits,
10718 if auto_indent_on_paste {
10719 Some(AutoindentMode::Block {
10720 original_indent_columns,
10721 })
10722 } else {
10723 None
10724 },
10725 cx,
10726 );
10727 });
10728
10729 let selections = this.selections.all::<usize>(cx);
10730 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10731 s.select(selections)
10732 });
10733 } else {
10734 this.insert(&clipboard_text, window, cx);
10735 }
10736 });
10737 }
10738
10739 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10740 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10741 if let Some(item) = cx.read_from_clipboard() {
10742 let entries = item.entries();
10743
10744 match entries.first() {
10745 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10746 // of all the pasted entries.
10747 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10748 .do_paste(
10749 clipboard_string.text(),
10750 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10751 true,
10752 window,
10753 cx,
10754 ),
10755 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10756 }
10757 }
10758 }
10759
10760 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10761 if self.read_only(cx) {
10762 return;
10763 }
10764
10765 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10766
10767 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10768 if let Some((selections, _)) =
10769 self.selection_history.transaction(transaction_id).cloned()
10770 {
10771 self.change_selections(None, window, cx, |s| {
10772 s.select_anchors(selections.to_vec());
10773 });
10774 } else {
10775 log::error!(
10776 "No entry in selection_history found for undo. \
10777 This may correspond to a bug where undo does not update the selection. \
10778 If this is occurring, please add details to \
10779 https://github.com/zed-industries/zed/issues/22692"
10780 );
10781 }
10782 self.request_autoscroll(Autoscroll::fit(), cx);
10783 self.unmark_text(window, cx);
10784 self.refresh_inline_completion(true, false, window, cx);
10785 cx.emit(EditorEvent::Edited { transaction_id });
10786 cx.emit(EditorEvent::TransactionUndone { transaction_id });
10787 }
10788 }
10789
10790 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
10791 if self.read_only(cx) {
10792 return;
10793 }
10794
10795 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10796
10797 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
10798 if let Some((_, Some(selections))) =
10799 self.selection_history.transaction(transaction_id).cloned()
10800 {
10801 self.change_selections(None, window, cx, |s| {
10802 s.select_anchors(selections.to_vec());
10803 });
10804 } else {
10805 log::error!(
10806 "No entry in selection_history found for redo. \
10807 This may correspond to a bug where undo does not update the selection. \
10808 If this is occurring, please add details to \
10809 https://github.com/zed-industries/zed/issues/22692"
10810 );
10811 }
10812 self.request_autoscroll(Autoscroll::fit(), cx);
10813 self.unmark_text(window, cx);
10814 self.refresh_inline_completion(true, false, window, cx);
10815 cx.emit(EditorEvent::Edited { transaction_id });
10816 }
10817 }
10818
10819 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
10820 self.buffer
10821 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
10822 }
10823
10824 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
10825 self.buffer
10826 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
10827 }
10828
10829 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
10830 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10831 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10832 s.move_with(|map, selection| {
10833 let cursor = if selection.is_empty() {
10834 movement::left(map, selection.start)
10835 } else {
10836 selection.start
10837 };
10838 selection.collapse_to(cursor, SelectionGoal::None);
10839 });
10840 })
10841 }
10842
10843 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
10844 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10845 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10846 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
10847 })
10848 }
10849
10850 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
10851 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10852 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10853 s.move_with(|map, selection| {
10854 let cursor = if selection.is_empty() {
10855 movement::right(map, selection.end)
10856 } else {
10857 selection.end
10858 };
10859 selection.collapse_to(cursor, SelectionGoal::None)
10860 });
10861 })
10862 }
10863
10864 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
10865 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10866 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10867 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
10868 })
10869 }
10870
10871 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
10872 if self.take_rename(true, window, cx).is_some() {
10873 return;
10874 }
10875
10876 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10877 cx.propagate();
10878 return;
10879 }
10880
10881 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10882
10883 let text_layout_details = &self.text_layout_details(window);
10884 let selection_count = self.selections.count();
10885 let first_selection = self.selections.first_anchor();
10886
10887 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10888 s.move_with(|map, selection| {
10889 if !selection.is_empty() {
10890 selection.goal = SelectionGoal::None;
10891 }
10892 let (cursor, goal) = movement::up(
10893 map,
10894 selection.start,
10895 selection.goal,
10896 false,
10897 text_layout_details,
10898 );
10899 selection.collapse_to(cursor, goal);
10900 });
10901 });
10902
10903 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10904 {
10905 cx.propagate();
10906 }
10907 }
10908
10909 pub fn move_up_by_lines(
10910 &mut self,
10911 action: &MoveUpByLines,
10912 window: &mut Window,
10913 cx: &mut Context<Self>,
10914 ) {
10915 if self.take_rename(true, window, cx).is_some() {
10916 return;
10917 }
10918
10919 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10920 cx.propagate();
10921 return;
10922 }
10923
10924 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10925
10926 let text_layout_details = &self.text_layout_details(window);
10927
10928 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10929 s.move_with(|map, selection| {
10930 if !selection.is_empty() {
10931 selection.goal = SelectionGoal::None;
10932 }
10933 let (cursor, goal) = movement::up_by_rows(
10934 map,
10935 selection.start,
10936 action.lines,
10937 selection.goal,
10938 false,
10939 text_layout_details,
10940 );
10941 selection.collapse_to(cursor, goal);
10942 });
10943 })
10944 }
10945
10946 pub fn move_down_by_lines(
10947 &mut self,
10948 action: &MoveDownByLines,
10949 window: &mut Window,
10950 cx: &mut Context<Self>,
10951 ) {
10952 if self.take_rename(true, window, cx).is_some() {
10953 return;
10954 }
10955
10956 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10957 cx.propagate();
10958 return;
10959 }
10960
10961 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10962
10963 let text_layout_details = &self.text_layout_details(window);
10964
10965 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10966 s.move_with(|map, selection| {
10967 if !selection.is_empty() {
10968 selection.goal = SelectionGoal::None;
10969 }
10970 let (cursor, goal) = movement::down_by_rows(
10971 map,
10972 selection.start,
10973 action.lines,
10974 selection.goal,
10975 false,
10976 text_layout_details,
10977 );
10978 selection.collapse_to(cursor, goal);
10979 });
10980 })
10981 }
10982
10983 pub fn select_down_by_lines(
10984 &mut self,
10985 action: &SelectDownByLines,
10986 window: &mut Window,
10987 cx: &mut Context<Self>,
10988 ) {
10989 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10990 let text_layout_details = &self.text_layout_details(window);
10991 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10992 s.move_heads_with(|map, head, goal| {
10993 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
10994 })
10995 })
10996 }
10997
10998 pub fn select_up_by_lines(
10999 &mut self,
11000 action: &SelectUpByLines,
11001 window: &mut Window,
11002 cx: &mut Context<Self>,
11003 ) {
11004 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11005 let text_layout_details = &self.text_layout_details(window);
11006 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11007 s.move_heads_with(|map, head, goal| {
11008 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11009 })
11010 })
11011 }
11012
11013 pub fn select_page_up(
11014 &mut self,
11015 _: &SelectPageUp,
11016 window: &mut Window,
11017 cx: &mut Context<Self>,
11018 ) {
11019 let Some(row_count) = self.visible_row_count() else {
11020 return;
11021 };
11022
11023 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11024
11025 let text_layout_details = &self.text_layout_details(window);
11026
11027 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11028 s.move_heads_with(|map, head, goal| {
11029 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11030 })
11031 })
11032 }
11033
11034 pub fn move_page_up(
11035 &mut self,
11036 action: &MovePageUp,
11037 window: &mut Window,
11038 cx: &mut Context<Self>,
11039 ) {
11040 if self.take_rename(true, window, cx).is_some() {
11041 return;
11042 }
11043
11044 if self
11045 .context_menu
11046 .borrow_mut()
11047 .as_mut()
11048 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
11049 .unwrap_or(false)
11050 {
11051 return;
11052 }
11053
11054 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11055 cx.propagate();
11056 return;
11057 }
11058
11059 let Some(row_count) = self.visible_row_count() else {
11060 return;
11061 };
11062
11063 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11064
11065 let autoscroll = if action.center_cursor {
11066 Autoscroll::center()
11067 } else {
11068 Autoscroll::fit()
11069 };
11070
11071 let text_layout_details = &self.text_layout_details(window);
11072
11073 self.change_selections(Some(autoscroll), window, cx, |s| {
11074 s.move_with(|map, selection| {
11075 if !selection.is_empty() {
11076 selection.goal = SelectionGoal::None;
11077 }
11078 let (cursor, goal) = movement::up_by_rows(
11079 map,
11080 selection.end,
11081 row_count,
11082 selection.goal,
11083 false,
11084 text_layout_details,
11085 );
11086 selection.collapse_to(cursor, goal);
11087 });
11088 });
11089 }
11090
11091 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11092 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11093 let text_layout_details = &self.text_layout_details(window);
11094 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11095 s.move_heads_with(|map, head, goal| {
11096 movement::up(map, head, goal, false, text_layout_details)
11097 })
11098 })
11099 }
11100
11101 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11102 self.take_rename(true, window, cx);
11103
11104 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11105 cx.propagate();
11106 return;
11107 }
11108
11109 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11110
11111 let text_layout_details = &self.text_layout_details(window);
11112 let selection_count = self.selections.count();
11113 let first_selection = self.selections.first_anchor();
11114
11115 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11116 s.move_with(|map, selection| {
11117 if !selection.is_empty() {
11118 selection.goal = SelectionGoal::None;
11119 }
11120 let (cursor, goal) = movement::down(
11121 map,
11122 selection.end,
11123 selection.goal,
11124 false,
11125 text_layout_details,
11126 );
11127 selection.collapse_to(cursor, goal);
11128 });
11129 });
11130
11131 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11132 {
11133 cx.propagate();
11134 }
11135 }
11136
11137 pub fn select_page_down(
11138 &mut self,
11139 _: &SelectPageDown,
11140 window: &mut Window,
11141 cx: &mut Context<Self>,
11142 ) {
11143 let Some(row_count) = self.visible_row_count() else {
11144 return;
11145 };
11146
11147 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11148
11149 let text_layout_details = &self.text_layout_details(window);
11150
11151 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11152 s.move_heads_with(|map, head, goal| {
11153 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11154 })
11155 })
11156 }
11157
11158 pub fn move_page_down(
11159 &mut self,
11160 action: &MovePageDown,
11161 window: &mut Window,
11162 cx: &mut Context<Self>,
11163 ) {
11164 if self.take_rename(true, window, cx).is_some() {
11165 return;
11166 }
11167
11168 if self
11169 .context_menu
11170 .borrow_mut()
11171 .as_mut()
11172 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
11173 .unwrap_or(false)
11174 {
11175 return;
11176 }
11177
11178 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11179 cx.propagate();
11180 return;
11181 }
11182
11183 let Some(row_count) = self.visible_row_count() else {
11184 return;
11185 };
11186
11187 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11188
11189 let autoscroll = if action.center_cursor {
11190 Autoscroll::center()
11191 } else {
11192 Autoscroll::fit()
11193 };
11194
11195 let text_layout_details = &self.text_layout_details(window);
11196 self.change_selections(Some(autoscroll), window, cx, |s| {
11197 s.move_with(|map, selection| {
11198 if !selection.is_empty() {
11199 selection.goal = SelectionGoal::None;
11200 }
11201 let (cursor, goal) = movement::down_by_rows(
11202 map,
11203 selection.end,
11204 row_count,
11205 selection.goal,
11206 false,
11207 text_layout_details,
11208 );
11209 selection.collapse_to(cursor, goal);
11210 });
11211 });
11212 }
11213
11214 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11215 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11216 let text_layout_details = &self.text_layout_details(window);
11217 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11218 s.move_heads_with(|map, head, goal| {
11219 movement::down(map, head, goal, false, text_layout_details)
11220 })
11221 });
11222 }
11223
11224 pub fn context_menu_first(
11225 &mut self,
11226 _: &ContextMenuFirst,
11227 _window: &mut Window,
11228 cx: &mut Context<Self>,
11229 ) {
11230 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11231 context_menu.select_first(self.completion_provider.as_deref(), cx);
11232 }
11233 }
11234
11235 pub fn context_menu_prev(
11236 &mut self,
11237 _: &ContextMenuPrevious,
11238 _window: &mut Window,
11239 cx: &mut Context<Self>,
11240 ) {
11241 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11242 context_menu.select_prev(self.completion_provider.as_deref(), cx);
11243 }
11244 }
11245
11246 pub fn context_menu_next(
11247 &mut self,
11248 _: &ContextMenuNext,
11249 _window: &mut Window,
11250 cx: &mut Context<Self>,
11251 ) {
11252 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11253 context_menu.select_next(self.completion_provider.as_deref(), cx);
11254 }
11255 }
11256
11257 pub fn context_menu_last(
11258 &mut self,
11259 _: &ContextMenuLast,
11260 _window: &mut Window,
11261 cx: &mut Context<Self>,
11262 ) {
11263 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11264 context_menu.select_last(self.completion_provider.as_deref(), cx);
11265 }
11266 }
11267
11268 pub fn move_to_previous_word_start(
11269 &mut self,
11270 _: &MoveToPreviousWordStart,
11271 window: &mut Window,
11272 cx: &mut Context<Self>,
11273 ) {
11274 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11275 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11276 s.move_cursors_with(|map, head, _| {
11277 (
11278 movement::previous_word_start(map, head),
11279 SelectionGoal::None,
11280 )
11281 });
11282 })
11283 }
11284
11285 pub fn move_to_previous_subword_start(
11286 &mut self,
11287 _: &MoveToPreviousSubwordStart,
11288 window: &mut Window,
11289 cx: &mut Context<Self>,
11290 ) {
11291 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11292 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11293 s.move_cursors_with(|map, head, _| {
11294 (
11295 movement::previous_subword_start(map, head),
11296 SelectionGoal::None,
11297 )
11298 });
11299 })
11300 }
11301
11302 pub fn select_to_previous_word_start(
11303 &mut self,
11304 _: &SelectToPreviousWordStart,
11305 window: &mut Window,
11306 cx: &mut Context<Self>,
11307 ) {
11308 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11309 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11310 s.move_heads_with(|map, head, _| {
11311 (
11312 movement::previous_word_start(map, head),
11313 SelectionGoal::None,
11314 )
11315 });
11316 })
11317 }
11318
11319 pub fn select_to_previous_subword_start(
11320 &mut self,
11321 _: &SelectToPreviousSubwordStart,
11322 window: &mut Window,
11323 cx: &mut Context<Self>,
11324 ) {
11325 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11326 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11327 s.move_heads_with(|map, head, _| {
11328 (
11329 movement::previous_subword_start(map, head),
11330 SelectionGoal::None,
11331 )
11332 });
11333 })
11334 }
11335
11336 pub fn delete_to_previous_word_start(
11337 &mut self,
11338 action: &DeleteToPreviousWordStart,
11339 window: &mut Window,
11340 cx: &mut Context<Self>,
11341 ) {
11342 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11343 self.transact(window, cx, |this, window, cx| {
11344 this.select_autoclose_pair(window, cx);
11345 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11346 s.move_with(|map, selection| {
11347 if selection.is_empty() {
11348 let cursor = if action.ignore_newlines {
11349 movement::previous_word_start(map, selection.head())
11350 } else {
11351 movement::previous_word_start_or_newline(map, selection.head())
11352 };
11353 selection.set_head(cursor, SelectionGoal::None);
11354 }
11355 });
11356 });
11357 this.insert("", window, cx);
11358 });
11359 }
11360
11361 pub fn delete_to_previous_subword_start(
11362 &mut self,
11363 _: &DeleteToPreviousSubwordStart,
11364 window: &mut Window,
11365 cx: &mut Context<Self>,
11366 ) {
11367 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11368 self.transact(window, cx, |this, window, cx| {
11369 this.select_autoclose_pair(window, cx);
11370 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11371 s.move_with(|map, selection| {
11372 if selection.is_empty() {
11373 let cursor = movement::previous_subword_start(map, selection.head());
11374 selection.set_head(cursor, SelectionGoal::None);
11375 }
11376 });
11377 });
11378 this.insert("", window, cx);
11379 });
11380 }
11381
11382 pub fn move_to_next_word_end(
11383 &mut self,
11384 _: &MoveToNextWordEnd,
11385 window: &mut Window,
11386 cx: &mut Context<Self>,
11387 ) {
11388 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11389 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11390 s.move_cursors_with(|map, head, _| {
11391 (movement::next_word_end(map, head), SelectionGoal::None)
11392 });
11393 })
11394 }
11395
11396 pub fn move_to_next_subword_end(
11397 &mut self,
11398 _: &MoveToNextSubwordEnd,
11399 window: &mut Window,
11400 cx: &mut Context<Self>,
11401 ) {
11402 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11403 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11404 s.move_cursors_with(|map, head, _| {
11405 (movement::next_subword_end(map, head), SelectionGoal::None)
11406 });
11407 })
11408 }
11409
11410 pub fn select_to_next_word_end(
11411 &mut self,
11412 _: &SelectToNextWordEnd,
11413 window: &mut Window,
11414 cx: &mut Context<Self>,
11415 ) {
11416 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11417 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11418 s.move_heads_with(|map, head, _| {
11419 (movement::next_word_end(map, head), SelectionGoal::None)
11420 });
11421 })
11422 }
11423
11424 pub fn select_to_next_subword_end(
11425 &mut self,
11426 _: &SelectToNextSubwordEnd,
11427 window: &mut Window,
11428 cx: &mut Context<Self>,
11429 ) {
11430 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11431 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11432 s.move_heads_with(|map, head, _| {
11433 (movement::next_subword_end(map, head), SelectionGoal::None)
11434 });
11435 })
11436 }
11437
11438 pub fn delete_to_next_word_end(
11439 &mut self,
11440 action: &DeleteToNextWordEnd,
11441 window: &mut Window,
11442 cx: &mut Context<Self>,
11443 ) {
11444 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11445 self.transact(window, cx, |this, window, cx| {
11446 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11447 s.move_with(|map, selection| {
11448 if selection.is_empty() {
11449 let cursor = if action.ignore_newlines {
11450 movement::next_word_end(map, selection.head())
11451 } else {
11452 movement::next_word_end_or_newline(map, selection.head())
11453 };
11454 selection.set_head(cursor, SelectionGoal::None);
11455 }
11456 });
11457 });
11458 this.insert("", window, cx);
11459 });
11460 }
11461
11462 pub fn delete_to_next_subword_end(
11463 &mut self,
11464 _: &DeleteToNextSubwordEnd,
11465 window: &mut Window,
11466 cx: &mut Context<Self>,
11467 ) {
11468 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11469 self.transact(window, cx, |this, window, cx| {
11470 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11471 s.move_with(|map, selection| {
11472 if selection.is_empty() {
11473 let cursor = movement::next_subword_end(map, selection.head());
11474 selection.set_head(cursor, SelectionGoal::None);
11475 }
11476 });
11477 });
11478 this.insert("", window, cx);
11479 });
11480 }
11481
11482 pub fn move_to_beginning_of_line(
11483 &mut self,
11484 action: &MoveToBeginningOfLine,
11485 window: &mut Window,
11486 cx: &mut Context<Self>,
11487 ) {
11488 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11489 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11490 s.move_cursors_with(|map, head, _| {
11491 (
11492 movement::indented_line_beginning(
11493 map,
11494 head,
11495 action.stop_at_soft_wraps,
11496 action.stop_at_indent,
11497 ),
11498 SelectionGoal::None,
11499 )
11500 });
11501 })
11502 }
11503
11504 pub fn select_to_beginning_of_line(
11505 &mut self,
11506 action: &SelectToBeginningOfLine,
11507 window: &mut Window,
11508 cx: &mut Context<Self>,
11509 ) {
11510 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11511 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11512 s.move_heads_with(|map, head, _| {
11513 (
11514 movement::indented_line_beginning(
11515 map,
11516 head,
11517 action.stop_at_soft_wraps,
11518 action.stop_at_indent,
11519 ),
11520 SelectionGoal::None,
11521 )
11522 });
11523 });
11524 }
11525
11526 pub fn delete_to_beginning_of_line(
11527 &mut self,
11528 action: &DeleteToBeginningOfLine,
11529 window: &mut Window,
11530 cx: &mut Context<Self>,
11531 ) {
11532 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11533 self.transact(window, cx, |this, window, cx| {
11534 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11535 s.move_with(|_, selection| {
11536 selection.reversed = true;
11537 });
11538 });
11539
11540 this.select_to_beginning_of_line(
11541 &SelectToBeginningOfLine {
11542 stop_at_soft_wraps: false,
11543 stop_at_indent: action.stop_at_indent,
11544 },
11545 window,
11546 cx,
11547 );
11548 this.backspace(&Backspace, window, cx);
11549 });
11550 }
11551
11552 pub fn move_to_end_of_line(
11553 &mut self,
11554 action: &MoveToEndOfLine,
11555 window: &mut Window,
11556 cx: &mut Context<Self>,
11557 ) {
11558 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11559 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11560 s.move_cursors_with(|map, head, _| {
11561 (
11562 movement::line_end(map, head, action.stop_at_soft_wraps),
11563 SelectionGoal::None,
11564 )
11565 });
11566 })
11567 }
11568
11569 pub fn select_to_end_of_line(
11570 &mut self,
11571 action: &SelectToEndOfLine,
11572 window: &mut Window,
11573 cx: &mut Context<Self>,
11574 ) {
11575 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11576 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11577 s.move_heads_with(|map, head, _| {
11578 (
11579 movement::line_end(map, head, action.stop_at_soft_wraps),
11580 SelectionGoal::None,
11581 )
11582 });
11583 })
11584 }
11585
11586 pub fn delete_to_end_of_line(
11587 &mut self,
11588 _: &DeleteToEndOfLine,
11589 window: &mut Window,
11590 cx: &mut Context<Self>,
11591 ) {
11592 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11593 self.transact(window, cx, |this, window, cx| {
11594 this.select_to_end_of_line(
11595 &SelectToEndOfLine {
11596 stop_at_soft_wraps: false,
11597 },
11598 window,
11599 cx,
11600 );
11601 this.delete(&Delete, window, cx);
11602 });
11603 }
11604
11605 pub fn cut_to_end_of_line(
11606 &mut self,
11607 _: &CutToEndOfLine,
11608 window: &mut Window,
11609 cx: &mut Context<Self>,
11610 ) {
11611 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11612 self.transact(window, cx, |this, window, cx| {
11613 this.select_to_end_of_line(
11614 &SelectToEndOfLine {
11615 stop_at_soft_wraps: false,
11616 },
11617 window,
11618 cx,
11619 );
11620 this.cut(&Cut, window, cx);
11621 });
11622 }
11623
11624 pub fn move_to_start_of_paragraph(
11625 &mut self,
11626 _: &MoveToStartOfParagraph,
11627 window: &mut Window,
11628 cx: &mut Context<Self>,
11629 ) {
11630 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11631 cx.propagate();
11632 return;
11633 }
11634 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11635 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11636 s.move_with(|map, selection| {
11637 selection.collapse_to(
11638 movement::start_of_paragraph(map, selection.head(), 1),
11639 SelectionGoal::None,
11640 )
11641 });
11642 })
11643 }
11644
11645 pub fn move_to_end_of_paragraph(
11646 &mut self,
11647 _: &MoveToEndOfParagraph,
11648 window: &mut Window,
11649 cx: &mut Context<Self>,
11650 ) {
11651 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11652 cx.propagate();
11653 return;
11654 }
11655 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11656 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11657 s.move_with(|map, selection| {
11658 selection.collapse_to(
11659 movement::end_of_paragraph(map, selection.head(), 1),
11660 SelectionGoal::None,
11661 )
11662 });
11663 })
11664 }
11665
11666 pub fn select_to_start_of_paragraph(
11667 &mut self,
11668 _: &SelectToStartOfParagraph,
11669 window: &mut Window,
11670 cx: &mut Context<Self>,
11671 ) {
11672 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11673 cx.propagate();
11674 return;
11675 }
11676 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11677 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11678 s.move_heads_with(|map, head, _| {
11679 (
11680 movement::start_of_paragraph(map, head, 1),
11681 SelectionGoal::None,
11682 )
11683 });
11684 })
11685 }
11686
11687 pub fn select_to_end_of_paragraph(
11688 &mut self,
11689 _: &SelectToEndOfParagraph,
11690 window: &mut Window,
11691 cx: &mut Context<Self>,
11692 ) {
11693 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11694 cx.propagate();
11695 return;
11696 }
11697 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11698 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11699 s.move_heads_with(|map, head, _| {
11700 (
11701 movement::end_of_paragraph(map, head, 1),
11702 SelectionGoal::None,
11703 )
11704 });
11705 })
11706 }
11707
11708 pub fn move_to_start_of_excerpt(
11709 &mut self,
11710 _: &MoveToStartOfExcerpt,
11711 window: &mut Window,
11712 cx: &mut Context<Self>,
11713 ) {
11714 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11715 cx.propagate();
11716 return;
11717 }
11718 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11719 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11720 s.move_with(|map, selection| {
11721 selection.collapse_to(
11722 movement::start_of_excerpt(
11723 map,
11724 selection.head(),
11725 workspace::searchable::Direction::Prev,
11726 ),
11727 SelectionGoal::None,
11728 )
11729 });
11730 })
11731 }
11732
11733 pub fn move_to_start_of_next_excerpt(
11734 &mut self,
11735 _: &MoveToStartOfNextExcerpt,
11736 window: &mut Window,
11737 cx: &mut Context<Self>,
11738 ) {
11739 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11740 cx.propagate();
11741 return;
11742 }
11743
11744 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11745 s.move_with(|map, selection| {
11746 selection.collapse_to(
11747 movement::start_of_excerpt(
11748 map,
11749 selection.head(),
11750 workspace::searchable::Direction::Next,
11751 ),
11752 SelectionGoal::None,
11753 )
11754 });
11755 })
11756 }
11757
11758 pub fn move_to_end_of_excerpt(
11759 &mut self,
11760 _: &MoveToEndOfExcerpt,
11761 window: &mut Window,
11762 cx: &mut Context<Self>,
11763 ) {
11764 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11765 cx.propagate();
11766 return;
11767 }
11768 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11769 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11770 s.move_with(|map, selection| {
11771 selection.collapse_to(
11772 movement::end_of_excerpt(
11773 map,
11774 selection.head(),
11775 workspace::searchable::Direction::Next,
11776 ),
11777 SelectionGoal::None,
11778 )
11779 });
11780 })
11781 }
11782
11783 pub fn move_to_end_of_previous_excerpt(
11784 &mut self,
11785 _: &MoveToEndOfPreviousExcerpt,
11786 window: &mut Window,
11787 cx: &mut Context<Self>,
11788 ) {
11789 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11790 cx.propagate();
11791 return;
11792 }
11793 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11794 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11795 s.move_with(|map, selection| {
11796 selection.collapse_to(
11797 movement::end_of_excerpt(
11798 map,
11799 selection.head(),
11800 workspace::searchable::Direction::Prev,
11801 ),
11802 SelectionGoal::None,
11803 )
11804 });
11805 })
11806 }
11807
11808 pub fn select_to_start_of_excerpt(
11809 &mut self,
11810 _: &SelectToStartOfExcerpt,
11811 window: &mut Window,
11812 cx: &mut Context<Self>,
11813 ) {
11814 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11815 cx.propagate();
11816 return;
11817 }
11818 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11819 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11820 s.move_heads_with(|map, head, _| {
11821 (
11822 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11823 SelectionGoal::None,
11824 )
11825 });
11826 })
11827 }
11828
11829 pub fn select_to_start_of_next_excerpt(
11830 &mut self,
11831 _: &SelectToStartOfNextExcerpt,
11832 window: &mut Window,
11833 cx: &mut Context<Self>,
11834 ) {
11835 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11836 cx.propagate();
11837 return;
11838 }
11839 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11840 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11841 s.move_heads_with(|map, head, _| {
11842 (
11843 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
11844 SelectionGoal::None,
11845 )
11846 });
11847 })
11848 }
11849
11850 pub fn select_to_end_of_excerpt(
11851 &mut self,
11852 _: &SelectToEndOfExcerpt,
11853 window: &mut Window,
11854 cx: &mut Context<Self>,
11855 ) {
11856 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11857 cx.propagate();
11858 return;
11859 }
11860 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11861 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11862 s.move_heads_with(|map, head, _| {
11863 (
11864 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
11865 SelectionGoal::None,
11866 )
11867 });
11868 })
11869 }
11870
11871 pub fn select_to_end_of_previous_excerpt(
11872 &mut self,
11873 _: &SelectToEndOfPreviousExcerpt,
11874 window: &mut Window,
11875 cx: &mut Context<Self>,
11876 ) {
11877 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11878 cx.propagate();
11879 return;
11880 }
11881 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11882 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11883 s.move_heads_with(|map, head, _| {
11884 (
11885 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11886 SelectionGoal::None,
11887 )
11888 });
11889 })
11890 }
11891
11892 pub fn move_to_beginning(
11893 &mut self,
11894 _: &MoveToBeginning,
11895 window: &mut Window,
11896 cx: &mut Context<Self>,
11897 ) {
11898 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11899 cx.propagate();
11900 return;
11901 }
11902 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11903 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11904 s.select_ranges(vec![0..0]);
11905 });
11906 }
11907
11908 pub fn select_to_beginning(
11909 &mut self,
11910 _: &SelectToBeginning,
11911 window: &mut Window,
11912 cx: &mut Context<Self>,
11913 ) {
11914 let mut selection = self.selections.last::<Point>(cx);
11915 selection.set_head(Point::zero(), SelectionGoal::None);
11916 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11917 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11918 s.select(vec![selection]);
11919 });
11920 }
11921
11922 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
11923 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11924 cx.propagate();
11925 return;
11926 }
11927 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11928 let cursor = self.buffer.read(cx).read(cx).len();
11929 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11930 s.select_ranges(vec![cursor..cursor])
11931 });
11932 }
11933
11934 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
11935 self.nav_history = nav_history;
11936 }
11937
11938 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
11939 self.nav_history.as_ref()
11940 }
11941
11942 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
11943 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
11944 }
11945
11946 fn push_to_nav_history(
11947 &mut self,
11948 cursor_anchor: Anchor,
11949 new_position: Option<Point>,
11950 is_deactivate: bool,
11951 cx: &mut Context<Self>,
11952 ) {
11953 if let Some(nav_history) = self.nav_history.as_mut() {
11954 let buffer = self.buffer.read(cx).read(cx);
11955 let cursor_position = cursor_anchor.to_point(&buffer);
11956 let scroll_state = self.scroll_manager.anchor();
11957 let scroll_top_row = scroll_state.top_row(&buffer);
11958 drop(buffer);
11959
11960 if let Some(new_position) = new_position {
11961 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
11962 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
11963 return;
11964 }
11965 }
11966
11967 nav_history.push(
11968 Some(NavigationData {
11969 cursor_anchor,
11970 cursor_position,
11971 scroll_anchor: scroll_state,
11972 scroll_top_row,
11973 }),
11974 cx,
11975 );
11976 cx.emit(EditorEvent::PushedToNavHistory {
11977 anchor: cursor_anchor,
11978 is_deactivate,
11979 })
11980 }
11981 }
11982
11983 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
11984 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11985 let buffer = self.buffer.read(cx).snapshot(cx);
11986 let mut selection = self.selections.first::<usize>(cx);
11987 selection.set_head(buffer.len(), SelectionGoal::None);
11988 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11989 s.select(vec![selection]);
11990 });
11991 }
11992
11993 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
11994 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11995 let end = self.buffer.read(cx).read(cx).len();
11996 self.change_selections(None, window, cx, |s| {
11997 s.select_ranges(vec![0..end]);
11998 });
11999 }
12000
12001 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12002 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12003 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12004 let mut selections = self.selections.all::<Point>(cx);
12005 let max_point = display_map.buffer_snapshot.max_point();
12006 for selection in &mut selections {
12007 let rows = selection.spanned_rows(true, &display_map);
12008 selection.start = Point::new(rows.start.0, 0);
12009 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12010 selection.reversed = false;
12011 }
12012 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12013 s.select(selections);
12014 });
12015 }
12016
12017 pub fn split_selection_into_lines(
12018 &mut self,
12019 _: &SplitSelectionIntoLines,
12020 window: &mut Window,
12021 cx: &mut Context<Self>,
12022 ) {
12023 let selections = self
12024 .selections
12025 .all::<Point>(cx)
12026 .into_iter()
12027 .map(|selection| selection.start..selection.end)
12028 .collect::<Vec<_>>();
12029 self.unfold_ranges(&selections, true, true, cx);
12030
12031 let mut new_selection_ranges = Vec::new();
12032 {
12033 let buffer = self.buffer.read(cx).read(cx);
12034 for selection in selections {
12035 for row in selection.start.row..selection.end.row {
12036 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12037 new_selection_ranges.push(cursor..cursor);
12038 }
12039
12040 let is_multiline_selection = selection.start.row != selection.end.row;
12041 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12042 // so this action feels more ergonomic when paired with other selection operations
12043 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12044 if !should_skip_last {
12045 new_selection_ranges.push(selection.end..selection.end);
12046 }
12047 }
12048 }
12049 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12050 s.select_ranges(new_selection_ranges);
12051 });
12052 }
12053
12054 pub fn add_selection_above(
12055 &mut self,
12056 _: &AddSelectionAbove,
12057 window: &mut Window,
12058 cx: &mut Context<Self>,
12059 ) {
12060 self.add_selection(true, window, cx);
12061 }
12062
12063 pub fn add_selection_below(
12064 &mut self,
12065 _: &AddSelectionBelow,
12066 window: &mut Window,
12067 cx: &mut Context<Self>,
12068 ) {
12069 self.add_selection(false, window, cx);
12070 }
12071
12072 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12073 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12074
12075 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12076 let mut selections = self.selections.all::<Point>(cx);
12077 let text_layout_details = self.text_layout_details(window);
12078 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12079 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12080 let range = oldest_selection.display_range(&display_map).sorted();
12081
12082 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12083 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12084 let positions = start_x.min(end_x)..start_x.max(end_x);
12085
12086 selections.clear();
12087 let mut stack = Vec::new();
12088 for row in range.start.row().0..=range.end.row().0 {
12089 if let Some(selection) = self.selections.build_columnar_selection(
12090 &display_map,
12091 DisplayRow(row),
12092 &positions,
12093 oldest_selection.reversed,
12094 &text_layout_details,
12095 ) {
12096 stack.push(selection.id);
12097 selections.push(selection);
12098 }
12099 }
12100
12101 if above {
12102 stack.reverse();
12103 }
12104
12105 AddSelectionsState { above, stack }
12106 });
12107
12108 let last_added_selection = *state.stack.last().unwrap();
12109 let mut new_selections = Vec::new();
12110 if above == state.above {
12111 let end_row = if above {
12112 DisplayRow(0)
12113 } else {
12114 display_map.max_point().row()
12115 };
12116
12117 'outer: for selection in selections {
12118 if selection.id == last_added_selection {
12119 let range = selection.display_range(&display_map).sorted();
12120 debug_assert_eq!(range.start.row(), range.end.row());
12121 let mut row = range.start.row();
12122 let positions =
12123 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12124 px(start)..px(end)
12125 } else {
12126 let start_x =
12127 display_map.x_for_display_point(range.start, &text_layout_details);
12128 let end_x =
12129 display_map.x_for_display_point(range.end, &text_layout_details);
12130 start_x.min(end_x)..start_x.max(end_x)
12131 };
12132
12133 while row != end_row {
12134 if above {
12135 row.0 -= 1;
12136 } else {
12137 row.0 += 1;
12138 }
12139
12140 if let Some(new_selection) = self.selections.build_columnar_selection(
12141 &display_map,
12142 row,
12143 &positions,
12144 selection.reversed,
12145 &text_layout_details,
12146 ) {
12147 state.stack.push(new_selection.id);
12148 if above {
12149 new_selections.push(new_selection);
12150 new_selections.push(selection);
12151 } else {
12152 new_selections.push(selection);
12153 new_selections.push(new_selection);
12154 }
12155
12156 continue 'outer;
12157 }
12158 }
12159 }
12160
12161 new_selections.push(selection);
12162 }
12163 } else {
12164 new_selections = selections;
12165 new_selections.retain(|s| s.id != last_added_selection);
12166 state.stack.pop();
12167 }
12168
12169 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12170 s.select(new_selections);
12171 });
12172 if state.stack.len() > 1 {
12173 self.add_selections_state = Some(state);
12174 }
12175 }
12176
12177 fn select_match_ranges(
12178 &mut self,
12179 range: Range<usize>,
12180 reversed: bool,
12181 replace_newest: bool,
12182 auto_scroll: Option<Autoscroll>,
12183 window: &mut Window,
12184 cx: &mut Context<Editor>,
12185 ) {
12186 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12187 self.change_selections(auto_scroll, window, cx, |s| {
12188 if replace_newest {
12189 s.delete(s.newest_anchor().id);
12190 }
12191 if reversed {
12192 s.insert_range(range.end..range.start);
12193 } else {
12194 s.insert_range(range);
12195 }
12196 });
12197 }
12198
12199 pub fn select_next_match_internal(
12200 &mut self,
12201 display_map: &DisplaySnapshot,
12202 replace_newest: bool,
12203 autoscroll: Option<Autoscroll>,
12204 window: &mut Window,
12205 cx: &mut Context<Self>,
12206 ) -> Result<()> {
12207 let buffer = &display_map.buffer_snapshot;
12208 let mut selections = self.selections.all::<usize>(cx);
12209 if let Some(mut select_next_state) = self.select_next_state.take() {
12210 let query = &select_next_state.query;
12211 if !select_next_state.done {
12212 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12213 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12214 let mut next_selected_range = None;
12215
12216 let bytes_after_last_selection =
12217 buffer.bytes_in_range(last_selection.end..buffer.len());
12218 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12219 let query_matches = query
12220 .stream_find_iter(bytes_after_last_selection)
12221 .map(|result| (last_selection.end, result))
12222 .chain(
12223 query
12224 .stream_find_iter(bytes_before_first_selection)
12225 .map(|result| (0, result)),
12226 );
12227
12228 for (start_offset, query_match) in query_matches {
12229 let query_match = query_match.unwrap(); // can only fail due to I/O
12230 let offset_range =
12231 start_offset + query_match.start()..start_offset + query_match.end();
12232 let display_range = offset_range.start.to_display_point(display_map)
12233 ..offset_range.end.to_display_point(display_map);
12234
12235 if !select_next_state.wordwise
12236 || (!movement::is_inside_word(display_map, display_range.start)
12237 && !movement::is_inside_word(display_map, display_range.end))
12238 {
12239 // TODO: This is n^2, because we might check all the selections
12240 if !selections
12241 .iter()
12242 .any(|selection| selection.range().overlaps(&offset_range))
12243 {
12244 next_selected_range = Some(offset_range);
12245 break;
12246 }
12247 }
12248 }
12249
12250 if let Some(next_selected_range) = next_selected_range {
12251 self.select_match_ranges(
12252 next_selected_range,
12253 last_selection.reversed,
12254 replace_newest,
12255 autoscroll,
12256 window,
12257 cx,
12258 );
12259 } else {
12260 select_next_state.done = true;
12261 }
12262 }
12263
12264 self.select_next_state = Some(select_next_state);
12265 } else {
12266 let mut only_carets = true;
12267 let mut same_text_selected = true;
12268 let mut selected_text = None;
12269
12270 let mut selections_iter = selections.iter().peekable();
12271 while let Some(selection) = selections_iter.next() {
12272 if selection.start != selection.end {
12273 only_carets = false;
12274 }
12275
12276 if same_text_selected {
12277 if selected_text.is_none() {
12278 selected_text =
12279 Some(buffer.text_for_range(selection.range()).collect::<String>());
12280 }
12281
12282 if let Some(next_selection) = selections_iter.peek() {
12283 if next_selection.range().len() == selection.range().len() {
12284 let next_selected_text = buffer
12285 .text_for_range(next_selection.range())
12286 .collect::<String>();
12287 if Some(next_selected_text) != selected_text {
12288 same_text_selected = false;
12289 selected_text = None;
12290 }
12291 } else {
12292 same_text_selected = false;
12293 selected_text = None;
12294 }
12295 }
12296 }
12297 }
12298
12299 if only_carets {
12300 for selection in &mut selections {
12301 let word_range = movement::surrounding_word(
12302 display_map,
12303 selection.start.to_display_point(display_map),
12304 );
12305 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12306 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12307 selection.goal = SelectionGoal::None;
12308 selection.reversed = false;
12309 self.select_match_ranges(
12310 selection.start..selection.end,
12311 selection.reversed,
12312 replace_newest,
12313 autoscroll,
12314 window,
12315 cx,
12316 );
12317 }
12318
12319 if selections.len() == 1 {
12320 let selection = selections
12321 .last()
12322 .expect("ensured that there's only one selection");
12323 let query = buffer
12324 .text_for_range(selection.start..selection.end)
12325 .collect::<String>();
12326 let is_empty = query.is_empty();
12327 let select_state = SelectNextState {
12328 query: AhoCorasick::new(&[query])?,
12329 wordwise: true,
12330 done: is_empty,
12331 };
12332 self.select_next_state = Some(select_state);
12333 } else {
12334 self.select_next_state = None;
12335 }
12336 } else if let Some(selected_text) = selected_text {
12337 self.select_next_state = Some(SelectNextState {
12338 query: AhoCorasick::new(&[selected_text])?,
12339 wordwise: false,
12340 done: false,
12341 });
12342 self.select_next_match_internal(
12343 display_map,
12344 replace_newest,
12345 autoscroll,
12346 window,
12347 cx,
12348 )?;
12349 }
12350 }
12351 Ok(())
12352 }
12353
12354 pub fn select_all_matches(
12355 &mut self,
12356 _action: &SelectAllMatches,
12357 window: &mut Window,
12358 cx: &mut Context<Self>,
12359 ) -> Result<()> {
12360 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12361
12362 self.push_to_selection_history();
12363 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12364
12365 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12366 let Some(select_next_state) = self.select_next_state.as_mut() else {
12367 return Ok(());
12368 };
12369 if select_next_state.done {
12370 return Ok(());
12371 }
12372
12373 let mut new_selections = Vec::new();
12374
12375 let reversed = self.selections.oldest::<usize>(cx).reversed;
12376 let buffer = &display_map.buffer_snapshot;
12377 let query_matches = select_next_state
12378 .query
12379 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12380
12381 for query_match in query_matches.into_iter() {
12382 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12383 let offset_range = if reversed {
12384 query_match.end()..query_match.start()
12385 } else {
12386 query_match.start()..query_match.end()
12387 };
12388 let display_range = offset_range.start.to_display_point(&display_map)
12389 ..offset_range.end.to_display_point(&display_map);
12390
12391 if !select_next_state.wordwise
12392 || (!movement::is_inside_word(&display_map, display_range.start)
12393 && !movement::is_inside_word(&display_map, display_range.end))
12394 {
12395 new_selections.push(offset_range.start..offset_range.end);
12396 }
12397 }
12398
12399 select_next_state.done = true;
12400 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12401 self.change_selections(None, window, cx, |selections| {
12402 selections.select_ranges(new_selections)
12403 });
12404
12405 Ok(())
12406 }
12407
12408 pub fn select_next(
12409 &mut self,
12410 action: &SelectNext,
12411 window: &mut Window,
12412 cx: &mut Context<Self>,
12413 ) -> Result<()> {
12414 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12415 self.push_to_selection_history();
12416 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12417 self.select_next_match_internal(
12418 &display_map,
12419 action.replace_newest,
12420 Some(Autoscroll::newest()),
12421 window,
12422 cx,
12423 )?;
12424 Ok(())
12425 }
12426
12427 pub fn select_previous(
12428 &mut self,
12429 action: &SelectPrevious,
12430 window: &mut Window,
12431 cx: &mut Context<Self>,
12432 ) -> Result<()> {
12433 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12434 self.push_to_selection_history();
12435 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12436 let buffer = &display_map.buffer_snapshot;
12437 let mut selections = self.selections.all::<usize>(cx);
12438 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12439 let query = &select_prev_state.query;
12440 if !select_prev_state.done {
12441 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12442 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12443 let mut next_selected_range = None;
12444 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12445 let bytes_before_last_selection =
12446 buffer.reversed_bytes_in_range(0..last_selection.start);
12447 let bytes_after_first_selection =
12448 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12449 let query_matches = query
12450 .stream_find_iter(bytes_before_last_selection)
12451 .map(|result| (last_selection.start, result))
12452 .chain(
12453 query
12454 .stream_find_iter(bytes_after_first_selection)
12455 .map(|result| (buffer.len(), result)),
12456 );
12457 for (end_offset, query_match) in query_matches {
12458 let query_match = query_match.unwrap(); // can only fail due to I/O
12459 let offset_range =
12460 end_offset - query_match.end()..end_offset - query_match.start();
12461 let display_range = offset_range.start.to_display_point(&display_map)
12462 ..offset_range.end.to_display_point(&display_map);
12463
12464 if !select_prev_state.wordwise
12465 || (!movement::is_inside_word(&display_map, display_range.start)
12466 && !movement::is_inside_word(&display_map, display_range.end))
12467 {
12468 next_selected_range = Some(offset_range);
12469 break;
12470 }
12471 }
12472
12473 if let Some(next_selected_range) = next_selected_range {
12474 self.select_match_ranges(
12475 next_selected_range,
12476 last_selection.reversed,
12477 action.replace_newest,
12478 Some(Autoscroll::newest()),
12479 window,
12480 cx,
12481 );
12482 } else {
12483 select_prev_state.done = true;
12484 }
12485 }
12486
12487 self.select_prev_state = Some(select_prev_state);
12488 } else {
12489 let mut only_carets = true;
12490 let mut same_text_selected = true;
12491 let mut selected_text = None;
12492
12493 let mut selections_iter = selections.iter().peekable();
12494 while let Some(selection) = selections_iter.next() {
12495 if selection.start != selection.end {
12496 only_carets = false;
12497 }
12498
12499 if same_text_selected {
12500 if selected_text.is_none() {
12501 selected_text =
12502 Some(buffer.text_for_range(selection.range()).collect::<String>());
12503 }
12504
12505 if let Some(next_selection) = selections_iter.peek() {
12506 if next_selection.range().len() == selection.range().len() {
12507 let next_selected_text = buffer
12508 .text_for_range(next_selection.range())
12509 .collect::<String>();
12510 if Some(next_selected_text) != selected_text {
12511 same_text_selected = false;
12512 selected_text = None;
12513 }
12514 } else {
12515 same_text_selected = false;
12516 selected_text = None;
12517 }
12518 }
12519 }
12520 }
12521
12522 if only_carets {
12523 for selection in &mut selections {
12524 let word_range = movement::surrounding_word(
12525 &display_map,
12526 selection.start.to_display_point(&display_map),
12527 );
12528 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12529 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12530 selection.goal = SelectionGoal::None;
12531 selection.reversed = false;
12532 self.select_match_ranges(
12533 selection.start..selection.end,
12534 selection.reversed,
12535 action.replace_newest,
12536 Some(Autoscroll::newest()),
12537 window,
12538 cx,
12539 );
12540 }
12541 if selections.len() == 1 {
12542 let selection = selections
12543 .last()
12544 .expect("ensured that there's only one selection");
12545 let query = buffer
12546 .text_for_range(selection.start..selection.end)
12547 .collect::<String>();
12548 let is_empty = query.is_empty();
12549 let select_state = SelectNextState {
12550 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12551 wordwise: true,
12552 done: is_empty,
12553 };
12554 self.select_prev_state = Some(select_state);
12555 } else {
12556 self.select_prev_state = None;
12557 }
12558 } else if let Some(selected_text) = selected_text {
12559 self.select_prev_state = Some(SelectNextState {
12560 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12561 wordwise: false,
12562 done: false,
12563 });
12564 self.select_previous(action, window, cx)?;
12565 }
12566 }
12567 Ok(())
12568 }
12569
12570 pub fn find_next_match(
12571 &mut self,
12572 _: &FindNextMatch,
12573 window: &mut Window,
12574 cx: &mut Context<Self>,
12575 ) -> Result<()> {
12576 let selections = self.selections.disjoint_anchors();
12577 match selections.first() {
12578 Some(first) if selections.len() >= 2 => {
12579 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12580 s.select_ranges([first.range()]);
12581 });
12582 }
12583 _ => self.select_next(
12584 &SelectNext {
12585 replace_newest: true,
12586 },
12587 window,
12588 cx,
12589 )?,
12590 }
12591 Ok(())
12592 }
12593
12594 pub fn find_previous_match(
12595 &mut self,
12596 _: &FindPreviousMatch,
12597 window: &mut Window,
12598 cx: &mut Context<Self>,
12599 ) -> Result<()> {
12600 let selections = self.selections.disjoint_anchors();
12601 match selections.last() {
12602 Some(last) if selections.len() >= 2 => {
12603 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12604 s.select_ranges([last.range()]);
12605 });
12606 }
12607 _ => self.select_previous(
12608 &SelectPrevious {
12609 replace_newest: true,
12610 },
12611 window,
12612 cx,
12613 )?,
12614 }
12615 Ok(())
12616 }
12617
12618 pub fn toggle_comments(
12619 &mut self,
12620 action: &ToggleComments,
12621 window: &mut Window,
12622 cx: &mut Context<Self>,
12623 ) {
12624 if self.read_only(cx) {
12625 return;
12626 }
12627 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12628 let text_layout_details = &self.text_layout_details(window);
12629 self.transact(window, cx, |this, window, cx| {
12630 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12631 let mut edits = Vec::new();
12632 let mut selection_edit_ranges = Vec::new();
12633 let mut last_toggled_row = None;
12634 let snapshot = this.buffer.read(cx).read(cx);
12635 let empty_str: Arc<str> = Arc::default();
12636 let mut suffixes_inserted = Vec::new();
12637 let ignore_indent = action.ignore_indent;
12638
12639 fn comment_prefix_range(
12640 snapshot: &MultiBufferSnapshot,
12641 row: MultiBufferRow,
12642 comment_prefix: &str,
12643 comment_prefix_whitespace: &str,
12644 ignore_indent: bool,
12645 ) -> Range<Point> {
12646 let indent_size = if ignore_indent {
12647 0
12648 } else {
12649 snapshot.indent_size_for_line(row).len
12650 };
12651
12652 let start = Point::new(row.0, indent_size);
12653
12654 let mut line_bytes = snapshot
12655 .bytes_in_range(start..snapshot.max_point())
12656 .flatten()
12657 .copied();
12658
12659 // If this line currently begins with the line comment prefix, then record
12660 // the range containing the prefix.
12661 if line_bytes
12662 .by_ref()
12663 .take(comment_prefix.len())
12664 .eq(comment_prefix.bytes())
12665 {
12666 // Include any whitespace that matches the comment prefix.
12667 let matching_whitespace_len = line_bytes
12668 .zip(comment_prefix_whitespace.bytes())
12669 .take_while(|(a, b)| a == b)
12670 .count() as u32;
12671 let end = Point::new(
12672 start.row,
12673 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12674 );
12675 start..end
12676 } else {
12677 start..start
12678 }
12679 }
12680
12681 fn comment_suffix_range(
12682 snapshot: &MultiBufferSnapshot,
12683 row: MultiBufferRow,
12684 comment_suffix: &str,
12685 comment_suffix_has_leading_space: bool,
12686 ) -> Range<Point> {
12687 let end = Point::new(row.0, snapshot.line_len(row));
12688 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12689
12690 let mut line_end_bytes = snapshot
12691 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12692 .flatten()
12693 .copied();
12694
12695 let leading_space_len = if suffix_start_column > 0
12696 && line_end_bytes.next() == Some(b' ')
12697 && comment_suffix_has_leading_space
12698 {
12699 1
12700 } else {
12701 0
12702 };
12703
12704 // If this line currently begins with the line comment prefix, then record
12705 // the range containing the prefix.
12706 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12707 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12708 start..end
12709 } else {
12710 end..end
12711 }
12712 }
12713
12714 // TODO: Handle selections that cross excerpts
12715 for selection in &mut selections {
12716 let start_column = snapshot
12717 .indent_size_for_line(MultiBufferRow(selection.start.row))
12718 .len;
12719 let language = if let Some(language) =
12720 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12721 {
12722 language
12723 } else {
12724 continue;
12725 };
12726
12727 selection_edit_ranges.clear();
12728
12729 // If multiple selections contain a given row, avoid processing that
12730 // row more than once.
12731 let mut start_row = MultiBufferRow(selection.start.row);
12732 if last_toggled_row == Some(start_row) {
12733 start_row = start_row.next_row();
12734 }
12735 let end_row =
12736 if selection.end.row > selection.start.row && selection.end.column == 0 {
12737 MultiBufferRow(selection.end.row - 1)
12738 } else {
12739 MultiBufferRow(selection.end.row)
12740 };
12741 last_toggled_row = Some(end_row);
12742
12743 if start_row > end_row {
12744 continue;
12745 }
12746
12747 // If the language has line comments, toggle those.
12748 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12749
12750 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12751 if ignore_indent {
12752 full_comment_prefixes = full_comment_prefixes
12753 .into_iter()
12754 .map(|s| Arc::from(s.trim_end()))
12755 .collect();
12756 }
12757
12758 if !full_comment_prefixes.is_empty() {
12759 let first_prefix = full_comment_prefixes
12760 .first()
12761 .expect("prefixes is non-empty");
12762 let prefix_trimmed_lengths = full_comment_prefixes
12763 .iter()
12764 .map(|p| p.trim_end_matches(' ').len())
12765 .collect::<SmallVec<[usize; 4]>>();
12766
12767 let mut all_selection_lines_are_comments = true;
12768
12769 for row in start_row.0..=end_row.0 {
12770 let row = MultiBufferRow(row);
12771 if start_row < end_row && snapshot.is_line_blank(row) {
12772 continue;
12773 }
12774
12775 let prefix_range = full_comment_prefixes
12776 .iter()
12777 .zip(prefix_trimmed_lengths.iter().copied())
12778 .map(|(prefix, trimmed_prefix_len)| {
12779 comment_prefix_range(
12780 snapshot.deref(),
12781 row,
12782 &prefix[..trimmed_prefix_len],
12783 &prefix[trimmed_prefix_len..],
12784 ignore_indent,
12785 )
12786 })
12787 .max_by_key(|range| range.end.column - range.start.column)
12788 .expect("prefixes is non-empty");
12789
12790 if prefix_range.is_empty() {
12791 all_selection_lines_are_comments = false;
12792 }
12793
12794 selection_edit_ranges.push(prefix_range);
12795 }
12796
12797 if all_selection_lines_are_comments {
12798 edits.extend(
12799 selection_edit_ranges
12800 .iter()
12801 .cloned()
12802 .map(|range| (range, empty_str.clone())),
12803 );
12804 } else {
12805 let min_column = selection_edit_ranges
12806 .iter()
12807 .map(|range| range.start.column)
12808 .min()
12809 .unwrap_or(0);
12810 edits.extend(selection_edit_ranges.iter().map(|range| {
12811 let position = Point::new(range.start.row, min_column);
12812 (position..position, first_prefix.clone())
12813 }));
12814 }
12815 } else if let Some((full_comment_prefix, comment_suffix)) =
12816 language.block_comment_delimiters()
12817 {
12818 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
12819 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
12820 let prefix_range = comment_prefix_range(
12821 snapshot.deref(),
12822 start_row,
12823 comment_prefix,
12824 comment_prefix_whitespace,
12825 ignore_indent,
12826 );
12827 let suffix_range = comment_suffix_range(
12828 snapshot.deref(),
12829 end_row,
12830 comment_suffix.trim_start_matches(' '),
12831 comment_suffix.starts_with(' '),
12832 );
12833
12834 if prefix_range.is_empty() || suffix_range.is_empty() {
12835 edits.push((
12836 prefix_range.start..prefix_range.start,
12837 full_comment_prefix.clone(),
12838 ));
12839 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
12840 suffixes_inserted.push((end_row, comment_suffix.len()));
12841 } else {
12842 edits.push((prefix_range, empty_str.clone()));
12843 edits.push((suffix_range, empty_str.clone()));
12844 }
12845 } else {
12846 continue;
12847 }
12848 }
12849
12850 drop(snapshot);
12851 this.buffer.update(cx, |buffer, cx| {
12852 buffer.edit(edits, None, cx);
12853 });
12854
12855 // Adjust selections so that they end before any comment suffixes that
12856 // were inserted.
12857 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
12858 let mut selections = this.selections.all::<Point>(cx);
12859 let snapshot = this.buffer.read(cx).read(cx);
12860 for selection in &mut selections {
12861 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
12862 match row.cmp(&MultiBufferRow(selection.end.row)) {
12863 Ordering::Less => {
12864 suffixes_inserted.next();
12865 continue;
12866 }
12867 Ordering::Greater => break,
12868 Ordering::Equal => {
12869 if selection.end.column == snapshot.line_len(row) {
12870 if selection.is_empty() {
12871 selection.start.column -= suffix_len as u32;
12872 }
12873 selection.end.column -= suffix_len as u32;
12874 }
12875 break;
12876 }
12877 }
12878 }
12879 }
12880
12881 drop(snapshot);
12882 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12883 s.select(selections)
12884 });
12885
12886 let selections = this.selections.all::<Point>(cx);
12887 let selections_on_single_row = selections.windows(2).all(|selections| {
12888 selections[0].start.row == selections[1].start.row
12889 && selections[0].end.row == selections[1].end.row
12890 && selections[0].start.row == selections[0].end.row
12891 });
12892 let selections_selecting = selections
12893 .iter()
12894 .any(|selection| selection.start != selection.end);
12895 let advance_downwards = action.advance_downwards
12896 && selections_on_single_row
12897 && !selections_selecting
12898 && !matches!(this.mode, EditorMode::SingleLine { .. });
12899
12900 if advance_downwards {
12901 let snapshot = this.buffer.read(cx).snapshot(cx);
12902
12903 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12904 s.move_cursors_with(|display_snapshot, display_point, _| {
12905 let mut point = display_point.to_point(display_snapshot);
12906 point.row += 1;
12907 point = snapshot.clip_point(point, Bias::Left);
12908 let display_point = point.to_display_point(display_snapshot);
12909 let goal = SelectionGoal::HorizontalPosition(
12910 display_snapshot
12911 .x_for_display_point(display_point, text_layout_details)
12912 .into(),
12913 );
12914 (display_point, goal)
12915 })
12916 });
12917 }
12918 });
12919 }
12920
12921 pub fn select_enclosing_symbol(
12922 &mut self,
12923 _: &SelectEnclosingSymbol,
12924 window: &mut Window,
12925 cx: &mut Context<Self>,
12926 ) {
12927 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12928
12929 let buffer = self.buffer.read(cx).snapshot(cx);
12930 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
12931
12932 fn update_selection(
12933 selection: &Selection<usize>,
12934 buffer_snap: &MultiBufferSnapshot,
12935 ) -> Option<Selection<usize>> {
12936 let cursor = selection.head();
12937 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
12938 for symbol in symbols.iter().rev() {
12939 let start = symbol.range.start.to_offset(buffer_snap);
12940 let end = symbol.range.end.to_offset(buffer_snap);
12941 let new_range = start..end;
12942 if start < selection.start || end > selection.end {
12943 return Some(Selection {
12944 id: selection.id,
12945 start: new_range.start,
12946 end: new_range.end,
12947 goal: SelectionGoal::None,
12948 reversed: selection.reversed,
12949 });
12950 }
12951 }
12952 None
12953 }
12954
12955 let mut selected_larger_symbol = false;
12956 let new_selections = old_selections
12957 .iter()
12958 .map(|selection| match update_selection(selection, &buffer) {
12959 Some(new_selection) => {
12960 if new_selection.range() != selection.range() {
12961 selected_larger_symbol = true;
12962 }
12963 new_selection
12964 }
12965 None => selection.clone(),
12966 })
12967 .collect::<Vec<_>>();
12968
12969 if selected_larger_symbol {
12970 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12971 s.select(new_selections);
12972 });
12973 }
12974 }
12975
12976 pub fn select_larger_syntax_node(
12977 &mut self,
12978 _: &SelectLargerSyntaxNode,
12979 window: &mut Window,
12980 cx: &mut Context<Self>,
12981 ) {
12982 let Some(visible_row_count) = self.visible_row_count() else {
12983 return;
12984 };
12985 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
12986 if old_selections.is_empty() {
12987 return;
12988 }
12989
12990 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12991
12992 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12993 let buffer = self.buffer.read(cx).snapshot(cx);
12994
12995 let mut selected_larger_node = false;
12996 let mut new_selections = old_selections
12997 .iter()
12998 .map(|selection| {
12999 let old_range = selection.start..selection.end;
13000
13001 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13002 // manually select word at selection
13003 if ["string_content", "inline"].contains(&node.kind()) {
13004 let word_range = {
13005 let display_point = buffer
13006 .offset_to_point(old_range.start)
13007 .to_display_point(&display_map);
13008 let Range { start, end } =
13009 movement::surrounding_word(&display_map, display_point);
13010 start.to_point(&display_map).to_offset(&buffer)
13011 ..end.to_point(&display_map).to_offset(&buffer)
13012 };
13013 // ignore if word is already selected
13014 if !word_range.is_empty() && old_range != word_range {
13015 let last_word_range = {
13016 let display_point = buffer
13017 .offset_to_point(old_range.end)
13018 .to_display_point(&display_map);
13019 let Range { start, end } =
13020 movement::surrounding_word(&display_map, display_point);
13021 start.to_point(&display_map).to_offset(&buffer)
13022 ..end.to_point(&display_map).to_offset(&buffer)
13023 };
13024 // only select word if start and end point belongs to same word
13025 if word_range == last_word_range {
13026 selected_larger_node = true;
13027 return Selection {
13028 id: selection.id,
13029 start: word_range.start,
13030 end: word_range.end,
13031 goal: SelectionGoal::None,
13032 reversed: selection.reversed,
13033 };
13034 }
13035 }
13036 }
13037 }
13038
13039 let mut new_range = old_range.clone();
13040 while let Some((_node, containing_range)) =
13041 buffer.syntax_ancestor(new_range.clone())
13042 {
13043 new_range = match containing_range {
13044 MultiOrSingleBufferOffsetRange::Single(_) => break,
13045 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13046 };
13047 if !display_map.intersects_fold(new_range.start)
13048 && !display_map.intersects_fold(new_range.end)
13049 {
13050 break;
13051 }
13052 }
13053
13054 selected_larger_node |= new_range != old_range;
13055 Selection {
13056 id: selection.id,
13057 start: new_range.start,
13058 end: new_range.end,
13059 goal: SelectionGoal::None,
13060 reversed: selection.reversed,
13061 }
13062 })
13063 .collect::<Vec<_>>();
13064
13065 if !selected_larger_node {
13066 return; // don't put this call in the history
13067 }
13068
13069 // scroll based on transformation done to the last selection created by the user
13070 let (last_old, last_new) = old_selections
13071 .last()
13072 .zip(new_selections.last().cloned())
13073 .expect("old_selections isn't empty");
13074
13075 // revert selection
13076 let is_selection_reversed = {
13077 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13078 new_selections.last_mut().expect("checked above").reversed =
13079 should_newest_selection_be_reversed;
13080 should_newest_selection_be_reversed
13081 };
13082
13083 if selected_larger_node {
13084 self.select_syntax_node_history.disable_clearing = true;
13085 self.change_selections(None, window, cx, |s| {
13086 s.select(new_selections.clone());
13087 });
13088 self.select_syntax_node_history.disable_clearing = false;
13089 }
13090
13091 let start_row = last_new.start.to_display_point(&display_map).row().0;
13092 let end_row = last_new.end.to_display_point(&display_map).row().0;
13093 let selection_height = end_row - start_row + 1;
13094 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13095
13096 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13097 let scroll_behavior = if fits_on_the_screen {
13098 self.request_autoscroll(Autoscroll::fit(), cx);
13099 SelectSyntaxNodeScrollBehavior::FitSelection
13100 } else if is_selection_reversed {
13101 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13102 SelectSyntaxNodeScrollBehavior::CursorTop
13103 } else {
13104 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13105 SelectSyntaxNodeScrollBehavior::CursorBottom
13106 };
13107
13108 self.select_syntax_node_history.push((
13109 old_selections,
13110 scroll_behavior,
13111 is_selection_reversed,
13112 ));
13113 }
13114
13115 pub fn select_smaller_syntax_node(
13116 &mut self,
13117 _: &SelectSmallerSyntaxNode,
13118 window: &mut Window,
13119 cx: &mut Context<Self>,
13120 ) {
13121 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13122
13123 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13124 self.select_syntax_node_history.pop()
13125 {
13126 if let Some(selection) = selections.last_mut() {
13127 selection.reversed = is_selection_reversed;
13128 }
13129
13130 self.select_syntax_node_history.disable_clearing = true;
13131 self.change_selections(None, window, cx, |s| {
13132 s.select(selections.to_vec());
13133 });
13134 self.select_syntax_node_history.disable_clearing = false;
13135
13136 match scroll_behavior {
13137 SelectSyntaxNodeScrollBehavior::CursorTop => {
13138 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13139 }
13140 SelectSyntaxNodeScrollBehavior::FitSelection => {
13141 self.request_autoscroll(Autoscroll::fit(), cx);
13142 }
13143 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13144 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13145 }
13146 }
13147 }
13148 }
13149
13150 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13151 if !EditorSettings::get_global(cx).gutter.runnables {
13152 self.clear_tasks();
13153 return Task::ready(());
13154 }
13155 let project = self.project.as_ref().map(Entity::downgrade);
13156 let task_sources = self.lsp_task_sources(cx);
13157 cx.spawn_in(window, async move |editor, cx| {
13158 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13159 let Some(project) = project.and_then(|p| p.upgrade()) else {
13160 return;
13161 };
13162 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13163 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13164 }) else {
13165 return;
13166 };
13167
13168 let hide_runnables = project
13169 .update(cx, |project, cx| {
13170 // Do not display any test indicators in non-dev server remote projects.
13171 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13172 })
13173 .unwrap_or(true);
13174 if hide_runnables {
13175 return;
13176 }
13177 let new_rows =
13178 cx.background_spawn({
13179 let snapshot = display_snapshot.clone();
13180 async move {
13181 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13182 }
13183 })
13184 .await;
13185 let Ok(lsp_tasks) =
13186 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13187 else {
13188 return;
13189 };
13190 let lsp_tasks = lsp_tasks.await;
13191
13192 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13193 lsp_tasks
13194 .into_iter()
13195 .flat_map(|(kind, tasks)| {
13196 tasks.into_iter().filter_map(move |(location, task)| {
13197 Some((kind.clone(), location?, task))
13198 })
13199 })
13200 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13201 let buffer = location.target.buffer;
13202 let buffer_snapshot = buffer.read(cx).snapshot();
13203 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13204 |(excerpt_id, snapshot, _)| {
13205 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13206 display_snapshot
13207 .buffer_snapshot
13208 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13209 } else {
13210 None
13211 }
13212 },
13213 );
13214 if let Some(offset) = offset {
13215 let task_buffer_range =
13216 location.target.range.to_point(&buffer_snapshot);
13217 let context_buffer_range =
13218 task_buffer_range.to_offset(&buffer_snapshot);
13219 let context_range = BufferOffset(context_buffer_range.start)
13220 ..BufferOffset(context_buffer_range.end);
13221
13222 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13223 .or_insert_with(|| RunnableTasks {
13224 templates: Vec::new(),
13225 offset,
13226 column: task_buffer_range.start.column,
13227 extra_variables: HashMap::default(),
13228 context_range,
13229 })
13230 .templates
13231 .push((kind, task.original_task().clone()));
13232 }
13233
13234 acc
13235 })
13236 }) else {
13237 return;
13238 };
13239
13240 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
13241 editor
13242 .update(cx, |editor, _| {
13243 editor.clear_tasks();
13244 for (key, mut value) in rows {
13245 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13246 value.templates.extend(lsp_tasks.templates);
13247 }
13248
13249 editor.insert_tasks(key, value);
13250 }
13251 for (key, value) in lsp_tasks_by_rows {
13252 editor.insert_tasks(key, value);
13253 }
13254 })
13255 .ok();
13256 })
13257 }
13258 fn fetch_runnable_ranges(
13259 snapshot: &DisplaySnapshot,
13260 range: Range<Anchor>,
13261 ) -> Vec<language::RunnableRange> {
13262 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13263 }
13264
13265 fn runnable_rows(
13266 project: Entity<Project>,
13267 snapshot: DisplaySnapshot,
13268 runnable_ranges: Vec<RunnableRange>,
13269 mut cx: AsyncWindowContext,
13270 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13271 runnable_ranges
13272 .into_iter()
13273 .filter_map(|mut runnable| {
13274 let tasks = cx
13275 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13276 .ok()?;
13277 if tasks.is_empty() {
13278 return None;
13279 }
13280
13281 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13282
13283 let row = snapshot
13284 .buffer_snapshot
13285 .buffer_line_for_row(MultiBufferRow(point.row))?
13286 .1
13287 .start
13288 .row;
13289
13290 let context_range =
13291 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13292 Some((
13293 (runnable.buffer_id, row),
13294 RunnableTasks {
13295 templates: tasks,
13296 offset: snapshot
13297 .buffer_snapshot
13298 .anchor_before(runnable.run_range.start),
13299 context_range,
13300 column: point.column,
13301 extra_variables: runnable.extra_captures,
13302 },
13303 ))
13304 })
13305 .collect()
13306 }
13307
13308 fn templates_with_tags(
13309 project: &Entity<Project>,
13310 runnable: &mut Runnable,
13311 cx: &mut App,
13312 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13313 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13314 let (worktree_id, file) = project
13315 .buffer_for_id(runnable.buffer, cx)
13316 .and_then(|buffer| buffer.read(cx).file())
13317 .map(|file| (file.worktree_id(cx), file.clone()))
13318 .unzip();
13319
13320 (
13321 project.task_store().read(cx).task_inventory().cloned(),
13322 worktree_id,
13323 file,
13324 )
13325 });
13326
13327 let mut templates_with_tags = mem::take(&mut runnable.tags)
13328 .into_iter()
13329 .flat_map(|RunnableTag(tag)| {
13330 inventory
13331 .as_ref()
13332 .into_iter()
13333 .flat_map(|inventory| {
13334 inventory.read(cx).list_tasks(
13335 file.clone(),
13336 Some(runnable.language.clone()),
13337 worktree_id,
13338 cx,
13339 )
13340 })
13341 .filter(move |(_, template)| {
13342 template.tags.iter().any(|source_tag| source_tag == &tag)
13343 })
13344 })
13345 .sorted_by_key(|(kind, _)| kind.to_owned())
13346 .collect::<Vec<_>>();
13347 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13348 // Strongest source wins; if we have worktree tag binding, prefer that to
13349 // global and language bindings;
13350 // if we have a global binding, prefer that to language binding.
13351 let first_mismatch = templates_with_tags
13352 .iter()
13353 .position(|(tag_source, _)| tag_source != leading_tag_source);
13354 if let Some(index) = first_mismatch {
13355 templates_with_tags.truncate(index);
13356 }
13357 }
13358
13359 templates_with_tags
13360 }
13361
13362 pub fn move_to_enclosing_bracket(
13363 &mut self,
13364 _: &MoveToEnclosingBracket,
13365 window: &mut Window,
13366 cx: &mut Context<Self>,
13367 ) {
13368 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13369 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13370 s.move_offsets_with(|snapshot, selection| {
13371 let Some(enclosing_bracket_ranges) =
13372 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13373 else {
13374 return;
13375 };
13376
13377 let mut best_length = usize::MAX;
13378 let mut best_inside = false;
13379 let mut best_in_bracket_range = false;
13380 let mut best_destination = None;
13381 for (open, close) in enclosing_bracket_ranges {
13382 let close = close.to_inclusive();
13383 let length = close.end() - open.start;
13384 let inside = selection.start >= open.end && selection.end <= *close.start();
13385 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13386 || close.contains(&selection.head());
13387
13388 // If best is next to a bracket and current isn't, skip
13389 if !in_bracket_range && best_in_bracket_range {
13390 continue;
13391 }
13392
13393 // Prefer smaller lengths unless best is inside and current isn't
13394 if length > best_length && (best_inside || !inside) {
13395 continue;
13396 }
13397
13398 best_length = length;
13399 best_inside = inside;
13400 best_in_bracket_range = in_bracket_range;
13401 best_destination = Some(
13402 if close.contains(&selection.start) && close.contains(&selection.end) {
13403 if inside { open.end } else { open.start }
13404 } else if inside {
13405 *close.start()
13406 } else {
13407 *close.end()
13408 },
13409 );
13410 }
13411
13412 if let Some(destination) = best_destination {
13413 selection.collapse_to(destination, SelectionGoal::None);
13414 }
13415 })
13416 });
13417 }
13418
13419 pub fn undo_selection(
13420 &mut self,
13421 _: &UndoSelection,
13422 window: &mut Window,
13423 cx: &mut Context<Self>,
13424 ) {
13425 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13426 self.end_selection(window, cx);
13427 self.selection_history.mode = SelectionHistoryMode::Undoing;
13428 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13429 self.change_selections(None, window, cx, |s| {
13430 s.select_anchors(entry.selections.to_vec())
13431 });
13432 self.select_next_state = entry.select_next_state;
13433 self.select_prev_state = entry.select_prev_state;
13434 self.add_selections_state = entry.add_selections_state;
13435 self.request_autoscroll(Autoscroll::newest(), cx);
13436 }
13437 self.selection_history.mode = SelectionHistoryMode::Normal;
13438 }
13439
13440 pub fn redo_selection(
13441 &mut self,
13442 _: &RedoSelection,
13443 window: &mut Window,
13444 cx: &mut Context<Self>,
13445 ) {
13446 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13447 self.end_selection(window, cx);
13448 self.selection_history.mode = SelectionHistoryMode::Redoing;
13449 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13450 self.change_selections(None, window, cx, |s| {
13451 s.select_anchors(entry.selections.to_vec())
13452 });
13453 self.select_next_state = entry.select_next_state;
13454 self.select_prev_state = entry.select_prev_state;
13455 self.add_selections_state = entry.add_selections_state;
13456 self.request_autoscroll(Autoscroll::newest(), cx);
13457 }
13458 self.selection_history.mode = SelectionHistoryMode::Normal;
13459 }
13460
13461 pub fn expand_excerpts(
13462 &mut self,
13463 action: &ExpandExcerpts,
13464 _: &mut Window,
13465 cx: &mut Context<Self>,
13466 ) {
13467 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13468 }
13469
13470 pub fn expand_excerpts_down(
13471 &mut self,
13472 action: &ExpandExcerptsDown,
13473 _: &mut Window,
13474 cx: &mut Context<Self>,
13475 ) {
13476 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13477 }
13478
13479 pub fn expand_excerpts_up(
13480 &mut self,
13481 action: &ExpandExcerptsUp,
13482 _: &mut Window,
13483 cx: &mut Context<Self>,
13484 ) {
13485 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13486 }
13487
13488 pub fn expand_excerpts_for_direction(
13489 &mut self,
13490 lines: u32,
13491 direction: ExpandExcerptDirection,
13492
13493 cx: &mut Context<Self>,
13494 ) {
13495 let selections = self.selections.disjoint_anchors();
13496
13497 let lines = if lines == 0 {
13498 EditorSettings::get_global(cx).expand_excerpt_lines
13499 } else {
13500 lines
13501 };
13502
13503 self.buffer.update(cx, |buffer, cx| {
13504 let snapshot = buffer.snapshot(cx);
13505 let mut excerpt_ids = selections
13506 .iter()
13507 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13508 .collect::<Vec<_>>();
13509 excerpt_ids.sort();
13510 excerpt_ids.dedup();
13511 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13512 })
13513 }
13514
13515 pub fn expand_excerpt(
13516 &mut self,
13517 excerpt: ExcerptId,
13518 direction: ExpandExcerptDirection,
13519 window: &mut Window,
13520 cx: &mut Context<Self>,
13521 ) {
13522 let current_scroll_position = self.scroll_position(cx);
13523 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13524 let mut should_scroll_up = false;
13525
13526 if direction == ExpandExcerptDirection::Down {
13527 let multi_buffer = self.buffer.read(cx);
13528 let snapshot = multi_buffer.snapshot(cx);
13529 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13530 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13531 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13532 let buffer_snapshot = buffer.read(cx).snapshot();
13533 let excerpt_end_row =
13534 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13535 let last_row = buffer_snapshot.max_point().row;
13536 let lines_below = last_row.saturating_sub(excerpt_end_row);
13537 should_scroll_up = lines_below >= lines_to_expand;
13538 }
13539 }
13540 }
13541 }
13542
13543 self.buffer.update(cx, |buffer, cx| {
13544 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13545 });
13546
13547 if should_scroll_up {
13548 let new_scroll_position =
13549 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13550 self.set_scroll_position(new_scroll_position, window, cx);
13551 }
13552 }
13553
13554 pub fn go_to_singleton_buffer_point(
13555 &mut self,
13556 point: Point,
13557 window: &mut Window,
13558 cx: &mut Context<Self>,
13559 ) {
13560 self.go_to_singleton_buffer_range(point..point, window, cx);
13561 }
13562
13563 pub fn go_to_singleton_buffer_range(
13564 &mut self,
13565 range: Range<Point>,
13566 window: &mut Window,
13567 cx: &mut Context<Self>,
13568 ) {
13569 let multibuffer = self.buffer().read(cx);
13570 let Some(buffer) = multibuffer.as_singleton() else {
13571 return;
13572 };
13573 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13574 return;
13575 };
13576 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13577 return;
13578 };
13579 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13580 s.select_anchor_ranges([start..end])
13581 });
13582 }
13583
13584 pub fn go_to_diagnostic(
13585 &mut self,
13586 _: &GoToDiagnostic,
13587 window: &mut Window,
13588 cx: &mut Context<Self>,
13589 ) {
13590 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13591 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13592 }
13593
13594 pub fn go_to_prev_diagnostic(
13595 &mut self,
13596 _: &GoToPreviousDiagnostic,
13597 window: &mut Window,
13598 cx: &mut Context<Self>,
13599 ) {
13600 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13601 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13602 }
13603
13604 pub fn go_to_diagnostic_impl(
13605 &mut self,
13606 direction: Direction,
13607 window: &mut Window,
13608 cx: &mut Context<Self>,
13609 ) {
13610 let buffer = self.buffer.read(cx).snapshot(cx);
13611 let selection = self.selections.newest::<usize>(cx);
13612
13613 let mut active_group_id = None;
13614 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13615 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13616 active_group_id = Some(active_group.group_id);
13617 }
13618 }
13619
13620 fn filtered(
13621 snapshot: EditorSnapshot,
13622 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13623 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13624 diagnostics
13625 .filter(|entry| entry.range.start != entry.range.end)
13626 .filter(|entry| !entry.diagnostic.is_unnecessary)
13627 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13628 }
13629
13630 let snapshot = self.snapshot(window, cx);
13631 let before = filtered(
13632 snapshot.clone(),
13633 buffer
13634 .diagnostics_in_range(0..selection.start)
13635 .filter(|entry| entry.range.start <= selection.start),
13636 );
13637 let after = filtered(
13638 snapshot,
13639 buffer
13640 .diagnostics_in_range(selection.start..buffer.len())
13641 .filter(|entry| entry.range.start >= selection.start),
13642 );
13643
13644 let mut found: Option<DiagnosticEntry<usize>> = None;
13645 if direction == Direction::Prev {
13646 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13647 {
13648 for diagnostic in prev_diagnostics.into_iter().rev() {
13649 if diagnostic.range.start != selection.start
13650 || active_group_id
13651 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13652 {
13653 found = Some(diagnostic);
13654 break 'outer;
13655 }
13656 }
13657 }
13658 } else {
13659 for diagnostic in after.chain(before) {
13660 if diagnostic.range.start != selection.start
13661 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13662 {
13663 found = Some(diagnostic);
13664 break;
13665 }
13666 }
13667 }
13668 let Some(next_diagnostic) = found else {
13669 return;
13670 };
13671
13672 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13673 return;
13674 };
13675 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13676 s.select_ranges(vec![
13677 next_diagnostic.range.start..next_diagnostic.range.start,
13678 ])
13679 });
13680 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13681 self.refresh_inline_completion(false, true, window, cx);
13682 }
13683
13684 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13685 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13686 let snapshot = self.snapshot(window, cx);
13687 let selection = self.selections.newest::<Point>(cx);
13688 self.go_to_hunk_before_or_after_position(
13689 &snapshot,
13690 selection.head(),
13691 Direction::Next,
13692 window,
13693 cx,
13694 );
13695 }
13696
13697 pub fn go_to_hunk_before_or_after_position(
13698 &mut self,
13699 snapshot: &EditorSnapshot,
13700 position: Point,
13701 direction: Direction,
13702 window: &mut Window,
13703 cx: &mut Context<Editor>,
13704 ) {
13705 let row = if direction == Direction::Next {
13706 self.hunk_after_position(snapshot, position)
13707 .map(|hunk| hunk.row_range.start)
13708 } else {
13709 self.hunk_before_position(snapshot, position)
13710 };
13711
13712 if let Some(row) = row {
13713 let destination = Point::new(row.0, 0);
13714 let autoscroll = Autoscroll::center();
13715
13716 self.unfold_ranges(&[destination..destination], false, false, cx);
13717 self.change_selections(Some(autoscroll), window, cx, |s| {
13718 s.select_ranges([destination..destination]);
13719 });
13720 }
13721 }
13722
13723 fn hunk_after_position(
13724 &mut self,
13725 snapshot: &EditorSnapshot,
13726 position: Point,
13727 ) -> Option<MultiBufferDiffHunk> {
13728 snapshot
13729 .buffer_snapshot
13730 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13731 .find(|hunk| hunk.row_range.start.0 > position.row)
13732 .or_else(|| {
13733 snapshot
13734 .buffer_snapshot
13735 .diff_hunks_in_range(Point::zero()..position)
13736 .find(|hunk| hunk.row_range.end.0 < position.row)
13737 })
13738 }
13739
13740 fn go_to_prev_hunk(
13741 &mut self,
13742 _: &GoToPreviousHunk,
13743 window: &mut Window,
13744 cx: &mut Context<Self>,
13745 ) {
13746 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13747 let snapshot = self.snapshot(window, cx);
13748 let selection = self.selections.newest::<Point>(cx);
13749 self.go_to_hunk_before_or_after_position(
13750 &snapshot,
13751 selection.head(),
13752 Direction::Prev,
13753 window,
13754 cx,
13755 );
13756 }
13757
13758 fn hunk_before_position(
13759 &mut self,
13760 snapshot: &EditorSnapshot,
13761 position: Point,
13762 ) -> Option<MultiBufferRow> {
13763 snapshot
13764 .buffer_snapshot
13765 .diff_hunk_before(position)
13766 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
13767 }
13768
13769 fn go_to_next_change(
13770 &mut self,
13771 _: &GoToNextChange,
13772 window: &mut Window,
13773 cx: &mut Context<Self>,
13774 ) {
13775 if let Some(selections) = self
13776 .change_list
13777 .next_change(1, Direction::Next)
13778 .map(|s| s.to_vec())
13779 {
13780 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13781 let map = s.display_map();
13782 s.select_display_ranges(selections.iter().map(|a| {
13783 let point = a.to_display_point(&map);
13784 point..point
13785 }))
13786 })
13787 }
13788 }
13789
13790 fn go_to_previous_change(
13791 &mut self,
13792 _: &GoToPreviousChange,
13793 window: &mut Window,
13794 cx: &mut Context<Self>,
13795 ) {
13796 if let Some(selections) = self
13797 .change_list
13798 .next_change(1, Direction::Prev)
13799 .map(|s| s.to_vec())
13800 {
13801 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13802 let map = s.display_map();
13803 s.select_display_ranges(selections.iter().map(|a| {
13804 let point = a.to_display_point(&map);
13805 point..point
13806 }))
13807 })
13808 }
13809 }
13810
13811 fn go_to_line<T: 'static>(
13812 &mut self,
13813 position: Anchor,
13814 highlight_color: Option<Hsla>,
13815 window: &mut Window,
13816 cx: &mut Context<Self>,
13817 ) {
13818 let snapshot = self.snapshot(window, cx).display_snapshot;
13819 let position = position.to_point(&snapshot.buffer_snapshot);
13820 let start = snapshot
13821 .buffer_snapshot
13822 .clip_point(Point::new(position.row, 0), Bias::Left);
13823 let end = start + Point::new(1, 0);
13824 let start = snapshot.buffer_snapshot.anchor_before(start);
13825 let end = snapshot.buffer_snapshot.anchor_before(end);
13826
13827 self.highlight_rows::<T>(
13828 start..end,
13829 highlight_color
13830 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
13831 Default::default(),
13832 cx,
13833 );
13834 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
13835 }
13836
13837 pub fn go_to_definition(
13838 &mut self,
13839 _: &GoToDefinition,
13840 window: &mut Window,
13841 cx: &mut Context<Self>,
13842 ) -> Task<Result<Navigated>> {
13843 let definition =
13844 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
13845 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
13846 cx.spawn_in(window, async move |editor, cx| {
13847 if definition.await? == Navigated::Yes {
13848 return Ok(Navigated::Yes);
13849 }
13850 match fallback_strategy {
13851 GoToDefinitionFallback::None => Ok(Navigated::No),
13852 GoToDefinitionFallback::FindAllReferences => {
13853 match editor.update_in(cx, |editor, window, cx| {
13854 editor.find_all_references(&FindAllReferences, window, cx)
13855 })? {
13856 Some(references) => references.await,
13857 None => Ok(Navigated::No),
13858 }
13859 }
13860 }
13861 })
13862 }
13863
13864 pub fn go_to_declaration(
13865 &mut self,
13866 _: &GoToDeclaration,
13867 window: &mut Window,
13868 cx: &mut Context<Self>,
13869 ) -> Task<Result<Navigated>> {
13870 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
13871 }
13872
13873 pub fn go_to_declaration_split(
13874 &mut self,
13875 _: &GoToDeclaration,
13876 window: &mut Window,
13877 cx: &mut Context<Self>,
13878 ) -> Task<Result<Navigated>> {
13879 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
13880 }
13881
13882 pub fn go_to_implementation(
13883 &mut self,
13884 _: &GoToImplementation,
13885 window: &mut Window,
13886 cx: &mut Context<Self>,
13887 ) -> Task<Result<Navigated>> {
13888 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
13889 }
13890
13891 pub fn go_to_implementation_split(
13892 &mut self,
13893 _: &GoToImplementationSplit,
13894 window: &mut Window,
13895 cx: &mut Context<Self>,
13896 ) -> Task<Result<Navigated>> {
13897 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
13898 }
13899
13900 pub fn go_to_type_definition(
13901 &mut self,
13902 _: &GoToTypeDefinition,
13903 window: &mut Window,
13904 cx: &mut Context<Self>,
13905 ) -> Task<Result<Navigated>> {
13906 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
13907 }
13908
13909 pub fn go_to_definition_split(
13910 &mut self,
13911 _: &GoToDefinitionSplit,
13912 window: &mut Window,
13913 cx: &mut Context<Self>,
13914 ) -> Task<Result<Navigated>> {
13915 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
13916 }
13917
13918 pub fn go_to_type_definition_split(
13919 &mut self,
13920 _: &GoToTypeDefinitionSplit,
13921 window: &mut Window,
13922 cx: &mut Context<Self>,
13923 ) -> Task<Result<Navigated>> {
13924 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
13925 }
13926
13927 fn go_to_definition_of_kind(
13928 &mut self,
13929 kind: GotoDefinitionKind,
13930 split: bool,
13931 window: &mut Window,
13932 cx: &mut Context<Self>,
13933 ) -> Task<Result<Navigated>> {
13934 let Some(provider) = self.semantics_provider.clone() else {
13935 return Task::ready(Ok(Navigated::No));
13936 };
13937 let head = self.selections.newest::<usize>(cx).head();
13938 let buffer = self.buffer.read(cx);
13939 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
13940 text_anchor
13941 } else {
13942 return Task::ready(Ok(Navigated::No));
13943 };
13944
13945 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
13946 return Task::ready(Ok(Navigated::No));
13947 };
13948
13949 cx.spawn_in(window, async move |editor, cx| {
13950 let definitions = definitions.await?;
13951 let navigated = editor
13952 .update_in(cx, |editor, window, cx| {
13953 editor.navigate_to_hover_links(
13954 Some(kind),
13955 definitions
13956 .into_iter()
13957 .filter(|location| {
13958 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
13959 })
13960 .map(HoverLink::Text)
13961 .collect::<Vec<_>>(),
13962 split,
13963 window,
13964 cx,
13965 )
13966 })?
13967 .await?;
13968 anyhow::Ok(navigated)
13969 })
13970 }
13971
13972 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
13973 let selection = self.selections.newest_anchor();
13974 let head = selection.head();
13975 let tail = selection.tail();
13976
13977 let Some((buffer, start_position)) =
13978 self.buffer.read(cx).text_anchor_for_position(head, cx)
13979 else {
13980 return;
13981 };
13982
13983 let end_position = if head != tail {
13984 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
13985 return;
13986 };
13987 Some(pos)
13988 } else {
13989 None
13990 };
13991
13992 let url_finder = cx.spawn_in(window, async move |editor, cx| {
13993 let url = if let Some(end_pos) = end_position {
13994 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
13995 } else {
13996 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
13997 };
13998
13999 if let Some(url) = url {
14000 editor.update(cx, |_, cx| {
14001 cx.open_url(&url);
14002 })
14003 } else {
14004 Ok(())
14005 }
14006 });
14007
14008 url_finder.detach();
14009 }
14010
14011 pub fn open_selected_filename(
14012 &mut self,
14013 _: &OpenSelectedFilename,
14014 window: &mut Window,
14015 cx: &mut Context<Self>,
14016 ) {
14017 let Some(workspace) = self.workspace() else {
14018 return;
14019 };
14020
14021 let position = self.selections.newest_anchor().head();
14022
14023 let Some((buffer, buffer_position)) =
14024 self.buffer.read(cx).text_anchor_for_position(position, cx)
14025 else {
14026 return;
14027 };
14028
14029 let project = self.project.clone();
14030
14031 cx.spawn_in(window, async move |_, cx| {
14032 let result = find_file(&buffer, project, buffer_position, cx).await;
14033
14034 if let Some((_, path)) = result {
14035 workspace
14036 .update_in(cx, |workspace, window, cx| {
14037 workspace.open_resolved_path(path, window, cx)
14038 })?
14039 .await?;
14040 }
14041 anyhow::Ok(())
14042 })
14043 .detach();
14044 }
14045
14046 pub(crate) fn navigate_to_hover_links(
14047 &mut self,
14048 kind: Option<GotoDefinitionKind>,
14049 mut definitions: Vec<HoverLink>,
14050 split: bool,
14051 window: &mut Window,
14052 cx: &mut Context<Editor>,
14053 ) -> Task<Result<Navigated>> {
14054 // If there is one definition, just open it directly
14055 if definitions.len() == 1 {
14056 let definition = definitions.pop().unwrap();
14057
14058 enum TargetTaskResult {
14059 Location(Option<Location>),
14060 AlreadyNavigated,
14061 }
14062
14063 let target_task = match definition {
14064 HoverLink::Text(link) => {
14065 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14066 }
14067 HoverLink::InlayHint(lsp_location, server_id) => {
14068 let computation =
14069 self.compute_target_location(lsp_location, server_id, window, cx);
14070 cx.background_spawn(async move {
14071 let location = computation.await?;
14072 Ok(TargetTaskResult::Location(location))
14073 })
14074 }
14075 HoverLink::Url(url) => {
14076 cx.open_url(&url);
14077 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14078 }
14079 HoverLink::File(path) => {
14080 if let Some(workspace) = self.workspace() {
14081 cx.spawn_in(window, async move |_, cx| {
14082 workspace
14083 .update_in(cx, |workspace, window, cx| {
14084 workspace.open_resolved_path(path, window, cx)
14085 })?
14086 .await
14087 .map(|_| TargetTaskResult::AlreadyNavigated)
14088 })
14089 } else {
14090 Task::ready(Ok(TargetTaskResult::Location(None)))
14091 }
14092 }
14093 };
14094 cx.spawn_in(window, async move |editor, cx| {
14095 let target = match target_task.await.context("target resolution task")? {
14096 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14097 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14098 TargetTaskResult::Location(Some(target)) => target,
14099 };
14100
14101 editor.update_in(cx, |editor, window, cx| {
14102 let Some(workspace) = editor.workspace() else {
14103 return Navigated::No;
14104 };
14105 let pane = workspace.read(cx).active_pane().clone();
14106
14107 let range = target.range.to_point(target.buffer.read(cx));
14108 let range = editor.range_for_match(&range);
14109 let range = collapse_multiline_range(range);
14110
14111 if !split
14112 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14113 {
14114 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14115 } else {
14116 window.defer(cx, move |window, cx| {
14117 let target_editor: Entity<Self> =
14118 workspace.update(cx, |workspace, cx| {
14119 let pane = if split {
14120 workspace.adjacent_pane(window, cx)
14121 } else {
14122 workspace.active_pane().clone()
14123 };
14124
14125 workspace.open_project_item(
14126 pane,
14127 target.buffer.clone(),
14128 true,
14129 true,
14130 window,
14131 cx,
14132 )
14133 });
14134 target_editor.update(cx, |target_editor, cx| {
14135 // When selecting a definition in a different buffer, disable the nav history
14136 // to avoid creating a history entry at the previous cursor location.
14137 pane.update(cx, |pane, _| pane.disable_history());
14138 target_editor.go_to_singleton_buffer_range(range, window, cx);
14139 pane.update(cx, |pane, _| pane.enable_history());
14140 });
14141 });
14142 }
14143 Navigated::Yes
14144 })
14145 })
14146 } else if !definitions.is_empty() {
14147 cx.spawn_in(window, async move |editor, cx| {
14148 let (title, location_tasks, workspace) = editor
14149 .update_in(cx, |editor, window, cx| {
14150 let tab_kind = match kind {
14151 Some(GotoDefinitionKind::Implementation) => "Implementations",
14152 _ => "Definitions",
14153 };
14154 let title = definitions
14155 .iter()
14156 .find_map(|definition| match definition {
14157 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14158 let buffer = origin.buffer.read(cx);
14159 format!(
14160 "{} for {}",
14161 tab_kind,
14162 buffer
14163 .text_for_range(origin.range.clone())
14164 .collect::<String>()
14165 )
14166 }),
14167 HoverLink::InlayHint(_, _) => None,
14168 HoverLink::Url(_) => None,
14169 HoverLink::File(_) => None,
14170 })
14171 .unwrap_or(tab_kind.to_string());
14172 let location_tasks = definitions
14173 .into_iter()
14174 .map(|definition| match definition {
14175 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14176 HoverLink::InlayHint(lsp_location, server_id) => editor
14177 .compute_target_location(lsp_location, server_id, window, cx),
14178 HoverLink::Url(_) => Task::ready(Ok(None)),
14179 HoverLink::File(_) => Task::ready(Ok(None)),
14180 })
14181 .collect::<Vec<_>>();
14182 (title, location_tasks, editor.workspace().clone())
14183 })
14184 .context("location tasks preparation")?;
14185
14186 let locations = future::join_all(location_tasks)
14187 .await
14188 .into_iter()
14189 .filter_map(|location| location.transpose())
14190 .collect::<Result<_>>()
14191 .context("location tasks")?;
14192
14193 let Some(workspace) = workspace else {
14194 return Ok(Navigated::No);
14195 };
14196 let opened = workspace
14197 .update_in(cx, |workspace, window, cx| {
14198 Self::open_locations_in_multibuffer(
14199 workspace,
14200 locations,
14201 title,
14202 split,
14203 MultibufferSelectionMode::First,
14204 window,
14205 cx,
14206 )
14207 })
14208 .ok();
14209
14210 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14211 })
14212 } else {
14213 Task::ready(Ok(Navigated::No))
14214 }
14215 }
14216
14217 fn compute_target_location(
14218 &self,
14219 lsp_location: lsp::Location,
14220 server_id: LanguageServerId,
14221 window: &mut Window,
14222 cx: &mut Context<Self>,
14223 ) -> Task<anyhow::Result<Option<Location>>> {
14224 let Some(project) = self.project.clone() else {
14225 return Task::ready(Ok(None));
14226 };
14227
14228 cx.spawn_in(window, async move |editor, cx| {
14229 let location_task = editor.update(cx, |_, cx| {
14230 project.update(cx, |project, cx| {
14231 let language_server_name = project
14232 .language_server_statuses(cx)
14233 .find(|(id, _)| server_id == *id)
14234 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14235 language_server_name.map(|language_server_name| {
14236 project.open_local_buffer_via_lsp(
14237 lsp_location.uri.clone(),
14238 server_id,
14239 language_server_name,
14240 cx,
14241 )
14242 })
14243 })
14244 })?;
14245 let location = match location_task {
14246 Some(task) => Some({
14247 let target_buffer_handle = task.await.context("open local buffer")?;
14248 let range = target_buffer_handle.update(cx, |target_buffer, _| {
14249 let target_start = target_buffer
14250 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14251 let target_end = target_buffer
14252 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14253 target_buffer.anchor_after(target_start)
14254 ..target_buffer.anchor_before(target_end)
14255 })?;
14256 Location {
14257 buffer: target_buffer_handle,
14258 range,
14259 }
14260 }),
14261 None => None,
14262 };
14263 Ok(location)
14264 })
14265 }
14266
14267 pub fn find_all_references(
14268 &mut self,
14269 _: &FindAllReferences,
14270 window: &mut Window,
14271 cx: &mut Context<Self>,
14272 ) -> Option<Task<Result<Navigated>>> {
14273 let selection = self.selections.newest::<usize>(cx);
14274 let multi_buffer = self.buffer.read(cx);
14275 let head = selection.head();
14276
14277 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14278 let head_anchor = multi_buffer_snapshot.anchor_at(
14279 head,
14280 if head < selection.tail() {
14281 Bias::Right
14282 } else {
14283 Bias::Left
14284 },
14285 );
14286
14287 match self
14288 .find_all_references_task_sources
14289 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14290 {
14291 Ok(_) => {
14292 log::info!(
14293 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14294 );
14295 return None;
14296 }
14297 Err(i) => {
14298 self.find_all_references_task_sources.insert(i, head_anchor);
14299 }
14300 }
14301
14302 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14303 let workspace = self.workspace()?;
14304 let project = workspace.read(cx).project().clone();
14305 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14306 Some(cx.spawn_in(window, async move |editor, cx| {
14307 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14308 if let Ok(i) = editor
14309 .find_all_references_task_sources
14310 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14311 {
14312 editor.find_all_references_task_sources.remove(i);
14313 }
14314 });
14315
14316 let locations = references.await?;
14317 if locations.is_empty() {
14318 return anyhow::Ok(Navigated::No);
14319 }
14320
14321 workspace.update_in(cx, |workspace, window, cx| {
14322 let title = locations
14323 .first()
14324 .as_ref()
14325 .map(|location| {
14326 let buffer = location.buffer.read(cx);
14327 format!(
14328 "References to `{}`",
14329 buffer
14330 .text_for_range(location.range.clone())
14331 .collect::<String>()
14332 )
14333 })
14334 .unwrap();
14335 Self::open_locations_in_multibuffer(
14336 workspace,
14337 locations,
14338 title,
14339 false,
14340 MultibufferSelectionMode::First,
14341 window,
14342 cx,
14343 );
14344 Navigated::Yes
14345 })
14346 }))
14347 }
14348
14349 /// Opens a multibuffer with the given project locations in it
14350 pub fn open_locations_in_multibuffer(
14351 workspace: &mut Workspace,
14352 mut locations: Vec<Location>,
14353 title: String,
14354 split: bool,
14355 multibuffer_selection_mode: MultibufferSelectionMode,
14356 window: &mut Window,
14357 cx: &mut Context<Workspace>,
14358 ) {
14359 // If there are multiple definitions, open them in a multibuffer
14360 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14361 let mut locations = locations.into_iter().peekable();
14362 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14363 let capability = workspace.project().read(cx).capability();
14364
14365 let excerpt_buffer = cx.new(|cx| {
14366 let mut multibuffer = MultiBuffer::new(capability);
14367 while let Some(location) = locations.next() {
14368 let buffer = location.buffer.read(cx);
14369 let mut ranges_for_buffer = Vec::new();
14370 let range = location.range.to_point(buffer);
14371 ranges_for_buffer.push(range.clone());
14372
14373 while let Some(next_location) = locations.peek() {
14374 if next_location.buffer == location.buffer {
14375 ranges_for_buffer.push(next_location.range.to_point(buffer));
14376 locations.next();
14377 } else {
14378 break;
14379 }
14380 }
14381
14382 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14383 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14384 PathKey::for_buffer(&location.buffer, cx),
14385 location.buffer.clone(),
14386 ranges_for_buffer,
14387 DEFAULT_MULTIBUFFER_CONTEXT,
14388 cx,
14389 );
14390 ranges.extend(new_ranges)
14391 }
14392
14393 multibuffer.with_title(title)
14394 });
14395
14396 let editor = cx.new(|cx| {
14397 Editor::for_multibuffer(
14398 excerpt_buffer,
14399 Some(workspace.project().clone()),
14400 window,
14401 cx,
14402 )
14403 });
14404 editor.update(cx, |editor, cx| {
14405 match multibuffer_selection_mode {
14406 MultibufferSelectionMode::First => {
14407 if let Some(first_range) = ranges.first() {
14408 editor.change_selections(None, window, cx, |selections| {
14409 selections.clear_disjoint();
14410 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14411 });
14412 }
14413 editor.highlight_background::<Self>(
14414 &ranges,
14415 |theme| theme.editor_highlighted_line_background,
14416 cx,
14417 );
14418 }
14419 MultibufferSelectionMode::All => {
14420 editor.change_selections(None, window, cx, |selections| {
14421 selections.clear_disjoint();
14422 selections.select_anchor_ranges(ranges);
14423 });
14424 }
14425 }
14426 editor.register_buffers_with_language_servers(cx);
14427 });
14428
14429 let item = Box::new(editor);
14430 let item_id = item.item_id();
14431
14432 if split {
14433 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14434 } else {
14435 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14436 let (preview_item_id, preview_item_idx) =
14437 workspace.active_pane().update(cx, |pane, _| {
14438 (pane.preview_item_id(), pane.preview_item_idx())
14439 });
14440
14441 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14442
14443 if let Some(preview_item_id) = preview_item_id {
14444 workspace.active_pane().update(cx, |pane, cx| {
14445 pane.remove_item(preview_item_id, false, false, window, cx);
14446 });
14447 }
14448 } else {
14449 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14450 }
14451 }
14452 workspace.active_pane().update(cx, |pane, cx| {
14453 pane.set_preview_item_id(Some(item_id), cx);
14454 });
14455 }
14456
14457 pub fn rename(
14458 &mut self,
14459 _: &Rename,
14460 window: &mut Window,
14461 cx: &mut Context<Self>,
14462 ) -> Option<Task<Result<()>>> {
14463 use language::ToOffset as _;
14464
14465 let provider = self.semantics_provider.clone()?;
14466 let selection = self.selections.newest_anchor().clone();
14467 let (cursor_buffer, cursor_buffer_position) = self
14468 .buffer
14469 .read(cx)
14470 .text_anchor_for_position(selection.head(), cx)?;
14471 let (tail_buffer, cursor_buffer_position_end) = self
14472 .buffer
14473 .read(cx)
14474 .text_anchor_for_position(selection.tail(), cx)?;
14475 if tail_buffer != cursor_buffer {
14476 return None;
14477 }
14478
14479 let snapshot = cursor_buffer.read(cx).snapshot();
14480 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14481 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14482 let prepare_rename = provider
14483 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14484 .unwrap_or_else(|| Task::ready(Ok(None)));
14485 drop(snapshot);
14486
14487 Some(cx.spawn_in(window, async move |this, cx| {
14488 let rename_range = if let Some(range) = prepare_rename.await? {
14489 Some(range)
14490 } else {
14491 this.update(cx, |this, cx| {
14492 let buffer = this.buffer.read(cx).snapshot(cx);
14493 let mut buffer_highlights = this
14494 .document_highlights_for_position(selection.head(), &buffer)
14495 .filter(|highlight| {
14496 highlight.start.excerpt_id == selection.head().excerpt_id
14497 && highlight.end.excerpt_id == selection.head().excerpt_id
14498 });
14499 buffer_highlights
14500 .next()
14501 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14502 })?
14503 };
14504 if let Some(rename_range) = rename_range {
14505 this.update_in(cx, |this, window, cx| {
14506 let snapshot = cursor_buffer.read(cx).snapshot();
14507 let rename_buffer_range = rename_range.to_offset(&snapshot);
14508 let cursor_offset_in_rename_range =
14509 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14510 let cursor_offset_in_rename_range_end =
14511 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14512
14513 this.take_rename(false, window, cx);
14514 let buffer = this.buffer.read(cx).read(cx);
14515 let cursor_offset = selection.head().to_offset(&buffer);
14516 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14517 let rename_end = rename_start + rename_buffer_range.len();
14518 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14519 let mut old_highlight_id = None;
14520 let old_name: Arc<str> = buffer
14521 .chunks(rename_start..rename_end, true)
14522 .map(|chunk| {
14523 if old_highlight_id.is_none() {
14524 old_highlight_id = chunk.syntax_highlight_id;
14525 }
14526 chunk.text
14527 })
14528 .collect::<String>()
14529 .into();
14530
14531 drop(buffer);
14532
14533 // Position the selection in the rename editor so that it matches the current selection.
14534 this.show_local_selections = false;
14535 let rename_editor = cx.new(|cx| {
14536 let mut editor = Editor::single_line(window, cx);
14537 editor.buffer.update(cx, |buffer, cx| {
14538 buffer.edit([(0..0, old_name.clone())], None, cx)
14539 });
14540 let rename_selection_range = match cursor_offset_in_rename_range
14541 .cmp(&cursor_offset_in_rename_range_end)
14542 {
14543 Ordering::Equal => {
14544 editor.select_all(&SelectAll, window, cx);
14545 return editor;
14546 }
14547 Ordering::Less => {
14548 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14549 }
14550 Ordering::Greater => {
14551 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14552 }
14553 };
14554 if rename_selection_range.end > old_name.len() {
14555 editor.select_all(&SelectAll, window, cx);
14556 } else {
14557 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14558 s.select_ranges([rename_selection_range]);
14559 });
14560 }
14561 editor
14562 });
14563 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14564 if e == &EditorEvent::Focused {
14565 cx.emit(EditorEvent::FocusedIn)
14566 }
14567 })
14568 .detach();
14569
14570 let write_highlights =
14571 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14572 let read_highlights =
14573 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14574 let ranges = write_highlights
14575 .iter()
14576 .flat_map(|(_, ranges)| ranges.iter())
14577 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14578 .cloned()
14579 .collect();
14580
14581 this.highlight_text::<Rename>(
14582 ranges,
14583 HighlightStyle {
14584 fade_out: Some(0.6),
14585 ..Default::default()
14586 },
14587 cx,
14588 );
14589 let rename_focus_handle = rename_editor.focus_handle(cx);
14590 window.focus(&rename_focus_handle);
14591 let block_id = this.insert_blocks(
14592 [BlockProperties {
14593 style: BlockStyle::Flex,
14594 placement: BlockPlacement::Below(range.start),
14595 height: Some(1),
14596 render: Arc::new({
14597 let rename_editor = rename_editor.clone();
14598 move |cx: &mut BlockContext| {
14599 let mut text_style = cx.editor_style.text.clone();
14600 if let Some(highlight_style) = old_highlight_id
14601 .and_then(|h| h.style(&cx.editor_style.syntax))
14602 {
14603 text_style = text_style.highlight(highlight_style);
14604 }
14605 div()
14606 .block_mouse_down()
14607 .pl(cx.anchor_x)
14608 .child(EditorElement::new(
14609 &rename_editor,
14610 EditorStyle {
14611 background: cx.theme().system().transparent,
14612 local_player: cx.editor_style.local_player,
14613 text: text_style,
14614 scrollbar_width: cx.editor_style.scrollbar_width,
14615 syntax: cx.editor_style.syntax.clone(),
14616 status: cx.editor_style.status.clone(),
14617 inlay_hints_style: HighlightStyle {
14618 font_weight: Some(FontWeight::BOLD),
14619 ..make_inlay_hints_style(cx.app)
14620 },
14621 inline_completion_styles: make_suggestion_styles(
14622 cx.app,
14623 ),
14624 ..EditorStyle::default()
14625 },
14626 ))
14627 .into_any_element()
14628 }
14629 }),
14630 priority: 0,
14631 render_in_minimap: true,
14632 }],
14633 Some(Autoscroll::fit()),
14634 cx,
14635 )[0];
14636 this.pending_rename = Some(RenameState {
14637 range,
14638 old_name,
14639 editor: rename_editor,
14640 block_id,
14641 });
14642 })?;
14643 }
14644
14645 Ok(())
14646 }))
14647 }
14648
14649 pub fn confirm_rename(
14650 &mut self,
14651 _: &ConfirmRename,
14652 window: &mut Window,
14653 cx: &mut Context<Self>,
14654 ) -> Option<Task<Result<()>>> {
14655 let rename = self.take_rename(false, window, cx)?;
14656 let workspace = self.workspace()?.downgrade();
14657 let (buffer, start) = self
14658 .buffer
14659 .read(cx)
14660 .text_anchor_for_position(rename.range.start, cx)?;
14661 let (end_buffer, _) = self
14662 .buffer
14663 .read(cx)
14664 .text_anchor_for_position(rename.range.end, cx)?;
14665 if buffer != end_buffer {
14666 return None;
14667 }
14668
14669 let old_name = rename.old_name;
14670 let new_name = rename.editor.read(cx).text(cx);
14671
14672 let rename = self.semantics_provider.as_ref()?.perform_rename(
14673 &buffer,
14674 start,
14675 new_name.clone(),
14676 cx,
14677 )?;
14678
14679 Some(cx.spawn_in(window, async move |editor, cx| {
14680 let project_transaction = rename.await?;
14681 Self::open_project_transaction(
14682 &editor,
14683 workspace,
14684 project_transaction,
14685 format!("Rename: {} → {}", old_name, new_name),
14686 cx,
14687 )
14688 .await?;
14689
14690 editor.update(cx, |editor, cx| {
14691 editor.refresh_document_highlights(cx);
14692 })?;
14693 Ok(())
14694 }))
14695 }
14696
14697 fn take_rename(
14698 &mut self,
14699 moving_cursor: bool,
14700 window: &mut Window,
14701 cx: &mut Context<Self>,
14702 ) -> Option<RenameState> {
14703 let rename = self.pending_rename.take()?;
14704 if rename.editor.focus_handle(cx).is_focused(window) {
14705 window.focus(&self.focus_handle);
14706 }
14707
14708 self.remove_blocks(
14709 [rename.block_id].into_iter().collect(),
14710 Some(Autoscroll::fit()),
14711 cx,
14712 );
14713 self.clear_highlights::<Rename>(cx);
14714 self.show_local_selections = true;
14715
14716 if moving_cursor {
14717 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14718 editor.selections.newest::<usize>(cx).head()
14719 });
14720
14721 // Update the selection to match the position of the selection inside
14722 // the rename editor.
14723 let snapshot = self.buffer.read(cx).read(cx);
14724 let rename_range = rename.range.to_offset(&snapshot);
14725 let cursor_in_editor = snapshot
14726 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14727 .min(rename_range.end);
14728 drop(snapshot);
14729
14730 self.change_selections(None, window, cx, |s| {
14731 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14732 });
14733 } else {
14734 self.refresh_document_highlights(cx);
14735 }
14736
14737 Some(rename)
14738 }
14739
14740 pub fn pending_rename(&self) -> Option<&RenameState> {
14741 self.pending_rename.as_ref()
14742 }
14743
14744 fn format(
14745 &mut self,
14746 _: &Format,
14747 window: &mut Window,
14748 cx: &mut Context<Self>,
14749 ) -> Option<Task<Result<()>>> {
14750 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14751
14752 let project = match &self.project {
14753 Some(project) => project.clone(),
14754 None => return None,
14755 };
14756
14757 Some(self.perform_format(
14758 project,
14759 FormatTrigger::Manual,
14760 FormatTarget::Buffers,
14761 window,
14762 cx,
14763 ))
14764 }
14765
14766 fn format_selections(
14767 &mut self,
14768 _: &FormatSelections,
14769 window: &mut Window,
14770 cx: &mut Context<Self>,
14771 ) -> Option<Task<Result<()>>> {
14772 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14773
14774 let project = match &self.project {
14775 Some(project) => project.clone(),
14776 None => return None,
14777 };
14778
14779 let ranges = self
14780 .selections
14781 .all_adjusted(cx)
14782 .into_iter()
14783 .map(|selection| selection.range())
14784 .collect_vec();
14785
14786 Some(self.perform_format(
14787 project,
14788 FormatTrigger::Manual,
14789 FormatTarget::Ranges(ranges),
14790 window,
14791 cx,
14792 ))
14793 }
14794
14795 fn perform_format(
14796 &mut self,
14797 project: Entity<Project>,
14798 trigger: FormatTrigger,
14799 target: FormatTarget,
14800 window: &mut Window,
14801 cx: &mut Context<Self>,
14802 ) -> Task<Result<()>> {
14803 let buffer = self.buffer.clone();
14804 let (buffers, target) = match target {
14805 FormatTarget::Buffers => {
14806 let mut buffers = buffer.read(cx).all_buffers();
14807 if trigger == FormatTrigger::Save {
14808 buffers.retain(|buffer| buffer.read(cx).is_dirty());
14809 }
14810 (buffers, LspFormatTarget::Buffers)
14811 }
14812 FormatTarget::Ranges(selection_ranges) => {
14813 let multi_buffer = buffer.read(cx);
14814 let snapshot = multi_buffer.read(cx);
14815 let mut buffers = HashSet::default();
14816 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
14817 BTreeMap::new();
14818 for selection_range in selection_ranges {
14819 for (buffer, buffer_range, _) in
14820 snapshot.range_to_buffer_ranges(selection_range)
14821 {
14822 let buffer_id = buffer.remote_id();
14823 let start = buffer.anchor_before(buffer_range.start);
14824 let end = buffer.anchor_after(buffer_range.end);
14825 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
14826 buffer_id_to_ranges
14827 .entry(buffer_id)
14828 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
14829 .or_insert_with(|| vec![start..end]);
14830 }
14831 }
14832 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
14833 }
14834 };
14835
14836 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
14837 let selections_prev = transaction_id_prev
14838 .and_then(|transaction_id_prev| {
14839 // default to selections as they were after the last edit, if we have them,
14840 // instead of how they are now.
14841 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
14842 // will take you back to where you made the last edit, instead of staying where you scrolled
14843 self.selection_history
14844 .transaction(transaction_id_prev)
14845 .map(|t| t.0.clone())
14846 })
14847 .unwrap_or_else(|| {
14848 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
14849 self.selections.disjoint_anchors()
14850 });
14851
14852 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
14853 let format = project.update(cx, |project, cx| {
14854 project.format(buffers, target, true, trigger, cx)
14855 });
14856
14857 cx.spawn_in(window, async move |editor, cx| {
14858 let transaction = futures::select_biased! {
14859 transaction = format.log_err().fuse() => transaction,
14860 () = timeout => {
14861 log::warn!("timed out waiting for formatting");
14862 None
14863 }
14864 };
14865
14866 buffer
14867 .update(cx, |buffer, cx| {
14868 if let Some(transaction) = transaction {
14869 if !buffer.is_singleton() {
14870 buffer.push_transaction(&transaction.0, cx);
14871 }
14872 }
14873 cx.notify();
14874 })
14875 .ok();
14876
14877 if let Some(transaction_id_now) =
14878 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
14879 {
14880 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
14881 if has_new_transaction {
14882 _ = editor.update(cx, |editor, _| {
14883 editor
14884 .selection_history
14885 .insert_transaction(transaction_id_now, selections_prev);
14886 });
14887 }
14888 }
14889
14890 Ok(())
14891 })
14892 }
14893
14894 fn organize_imports(
14895 &mut self,
14896 _: &OrganizeImports,
14897 window: &mut Window,
14898 cx: &mut Context<Self>,
14899 ) -> Option<Task<Result<()>>> {
14900 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14901 let project = match &self.project {
14902 Some(project) => project.clone(),
14903 None => return None,
14904 };
14905 Some(self.perform_code_action_kind(
14906 project,
14907 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
14908 window,
14909 cx,
14910 ))
14911 }
14912
14913 fn perform_code_action_kind(
14914 &mut self,
14915 project: Entity<Project>,
14916 kind: CodeActionKind,
14917 window: &mut Window,
14918 cx: &mut Context<Self>,
14919 ) -> Task<Result<()>> {
14920 let buffer = self.buffer.clone();
14921 let buffers = buffer.read(cx).all_buffers();
14922 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
14923 let apply_action = project.update(cx, |project, cx| {
14924 project.apply_code_action_kind(buffers, kind, true, cx)
14925 });
14926 cx.spawn_in(window, async move |_, cx| {
14927 let transaction = futures::select_biased! {
14928 () = timeout => {
14929 log::warn!("timed out waiting for executing code action");
14930 None
14931 }
14932 transaction = apply_action.log_err().fuse() => transaction,
14933 };
14934 buffer
14935 .update(cx, |buffer, cx| {
14936 // check if we need this
14937 if let Some(transaction) = transaction {
14938 if !buffer.is_singleton() {
14939 buffer.push_transaction(&transaction.0, cx);
14940 }
14941 }
14942 cx.notify();
14943 })
14944 .ok();
14945 Ok(())
14946 })
14947 }
14948
14949 fn restart_language_server(
14950 &mut self,
14951 _: &RestartLanguageServer,
14952 _: &mut Window,
14953 cx: &mut Context<Self>,
14954 ) {
14955 if let Some(project) = self.project.clone() {
14956 self.buffer.update(cx, |multi_buffer, cx| {
14957 project.update(cx, |project, cx| {
14958 project.restart_language_servers_for_buffers(
14959 multi_buffer.all_buffers().into_iter().collect(),
14960 cx,
14961 );
14962 });
14963 })
14964 }
14965 }
14966
14967 fn stop_language_server(
14968 &mut self,
14969 _: &StopLanguageServer,
14970 _: &mut Window,
14971 cx: &mut Context<Self>,
14972 ) {
14973 if let Some(project) = self.project.clone() {
14974 self.buffer.update(cx, |multi_buffer, cx| {
14975 project.update(cx, |project, cx| {
14976 project.stop_language_servers_for_buffers(
14977 multi_buffer.all_buffers().into_iter().collect(),
14978 cx,
14979 );
14980 cx.emit(project::Event::RefreshInlayHints);
14981 });
14982 });
14983 }
14984 }
14985
14986 fn cancel_language_server_work(
14987 workspace: &mut Workspace,
14988 _: &actions::CancelLanguageServerWork,
14989 _: &mut Window,
14990 cx: &mut Context<Workspace>,
14991 ) {
14992 let project = workspace.project();
14993 let buffers = workspace
14994 .active_item(cx)
14995 .and_then(|item| item.act_as::<Editor>(cx))
14996 .map_or(HashSet::default(), |editor| {
14997 editor.read(cx).buffer.read(cx).all_buffers()
14998 });
14999 project.update(cx, |project, cx| {
15000 project.cancel_language_server_work_for_buffers(buffers, cx);
15001 });
15002 }
15003
15004 fn show_character_palette(
15005 &mut self,
15006 _: &ShowCharacterPalette,
15007 window: &mut Window,
15008 _: &mut Context<Self>,
15009 ) {
15010 window.show_character_palette();
15011 }
15012
15013 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15014 if self.mode.is_minimap() {
15015 return;
15016 }
15017
15018 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15019 let buffer = self.buffer.read(cx).snapshot(cx);
15020 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15021 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15022 let is_valid = buffer
15023 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15024 .any(|entry| {
15025 entry.diagnostic.is_primary
15026 && !entry.range.is_empty()
15027 && entry.range.start == primary_range_start
15028 && entry.diagnostic.message == active_diagnostics.active_message
15029 });
15030
15031 if !is_valid {
15032 self.dismiss_diagnostics(cx);
15033 }
15034 }
15035 }
15036
15037 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15038 match &self.active_diagnostics {
15039 ActiveDiagnostic::Group(group) => Some(group),
15040 _ => None,
15041 }
15042 }
15043
15044 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15045 self.dismiss_diagnostics(cx);
15046 self.active_diagnostics = ActiveDiagnostic::All;
15047 }
15048
15049 fn activate_diagnostics(
15050 &mut self,
15051 buffer_id: BufferId,
15052 diagnostic: DiagnosticEntry<usize>,
15053 window: &mut Window,
15054 cx: &mut Context<Self>,
15055 ) {
15056 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15057 return;
15058 }
15059 self.dismiss_diagnostics(cx);
15060 let snapshot = self.snapshot(window, cx);
15061 let buffer = self.buffer.read(cx).snapshot(cx);
15062 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15063 return;
15064 };
15065
15066 let diagnostic_group = buffer
15067 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15068 .collect::<Vec<_>>();
15069
15070 let blocks =
15071 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15072
15073 let blocks = self.display_map.update(cx, |display_map, cx| {
15074 display_map.insert_blocks(blocks, cx).into_iter().collect()
15075 });
15076 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15077 active_range: buffer.anchor_before(diagnostic.range.start)
15078 ..buffer.anchor_after(diagnostic.range.end),
15079 active_message: diagnostic.diagnostic.message.clone(),
15080 group_id: diagnostic.diagnostic.group_id,
15081 blocks,
15082 });
15083 cx.notify();
15084 }
15085
15086 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15087 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15088 return;
15089 };
15090
15091 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15092 if let ActiveDiagnostic::Group(group) = prev {
15093 self.display_map.update(cx, |display_map, cx| {
15094 display_map.remove_blocks(group.blocks, cx);
15095 });
15096 cx.notify();
15097 }
15098 }
15099
15100 /// Disable inline diagnostics rendering for this editor.
15101 pub fn disable_inline_diagnostics(&mut self) {
15102 self.inline_diagnostics_enabled = false;
15103 self.inline_diagnostics_update = Task::ready(());
15104 self.inline_diagnostics.clear();
15105 }
15106
15107 pub fn diagnostics_enabled(&self) -> bool {
15108 self.mode.is_full()
15109 }
15110
15111 pub fn inline_diagnostics_enabled(&self) -> bool {
15112 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15113 }
15114
15115 pub fn show_inline_diagnostics(&self) -> bool {
15116 self.show_inline_diagnostics
15117 }
15118
15119 pub fn toggle_inline_diagnostics(
15120 &mut self,
15121 _: &ToggleInlineDiagnostics,
15122 window: &mut Window,
15123 cx: &mut Context<Editor>,
15124 ) {
15125 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15126 self.refresh_inline_diagnostics(false, window, cx);
15127 }
15128
15129 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15130 self.diagnostics_max_severity = severity;
15131 self.display_map.update(cx, |display_map, _| {
15132 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15133 });
15134 }
15135
15136 pub fn toggle_diagnostics(
15137 &mut self,
15138 _: &ToggleDiagnostics,
15139 window: &mut Window,
15140 cx: &mut Context<Editor>,
15141 ) {
15142 if !self.diagnostics_enabled() {
15143 return;
15144 }
15145
15146 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15147 EditorSettings::get_global(cx)
15148 .diagnostics_max_severity
15149 .filter(|severity| severity != &DiagnosticSeverity::Off)
15150 .unwrap_or(DiagnosticSeverity::Hint)
15151 } else {
15152 DiagnosticSeverity::Off
15153 };
15154 self.set_max_diagnostics_severity(new_severity, cx);
15155 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15156 self.active_diagnostics = ActiveDiagnostic::None;
15157 self.inline_diagnostics_update = Task::ready(());
15158 self.inline_diagnostics.clear();
15159 } else {
15160 self.refresh_inline_diagnostics(false, window, cx);
15161 }
15162
15163 cx.notify();
15164 }
15165
15166 pub fn toggle_minimap(
15167 &mut self,
15168 _: &ToggleMinimap,
15169 window: &mut Window,
15170 cx: &mut Context<Editor>,
15171 ) {
15172 if self.supports_minimap() {
15173 self.set_show_minimap(!self.show_minimap, window, cx);
15174 }
15175 }
15176
15177 fn refresh_inline_diagnostics(
15178 &mut self,
15179 debounce: bool,
15180 window: &mut Window,
15181 cx: &mut Context<Self>,
15182 ) {
15183 let max_severity = ProjectSettings::get_global(cx)
15184 .diagnostics
15185 .inline
15186 .max_severity
15187 .unwrap_or(self.diagnostics_max_severity);
15188
15189 if self.mode.is_minimap()
15190 || !self.inline_diagnostics_enabled()
15191 || !self.show_inline_diagnostics
15192 || max_severity == DiagnosticSeverity::Off
15193 {
15194 self.inline_diagnostics_update = Task::ready(());
15195 self.inline_diagnostics.clear();
15196 return;
15197 }
15198
15199 let debounce_ms = ProjectSettings::get_global(cx)
15200 .diagnostics
15201 .inline
15202 .update_debounce_ms;
15203 let debounce = if debounce && debounce_ms > 0 {
15204 Some(Duration::from_millis(debounce_ms))
15205 } else {
15206 None
15207 };
15208 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15209 let editor = editor.upgrade().unwrap();
15210
15211 if let Some(debounce) = debounce {
15212 cx.background_executor().timer(debounce).await;
15213 }
15214 let Some(snapshot) = editor
15215 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15216 .ok()
15217 else {
15218 return;
15219 };
15220
15221 let new_inline_diagnostics = cx
15222 .background_spawn(async move {
15223 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15224 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15225 let message = diagnostic_entry
15226 .diagnostic
15227 .message
15228 .split_once('\n')
15229 .map(|(line, _)| line)
15230 .map(SharedString::new)
15231 .unwrap_or_else(|| {
15232 SharedString::from(diagnostic_entry.diagnostic.message)
15233 });
15234 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15235 let (Ok(i) | Err(i)) = inline_diagnostics
15236 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15237 inline_diagnostics.insert(
15238 i,
15239 (
15240 start_anchor,
15241 InlineDiagnostic {
15242 message,
15243 group_id: diagnostic_entry.diagnostic.group_id,
15244 start: diagnostic_entry.range.start.to_point(&snapshot),
15245 is_primary: diagnostic_entry.diagnostic.is_primary,
15246 severity: diagnostic_entry.diagnostic.severity,
15247 },
15248 ),
15249 );
15250 }
15251 inline_diagnostics
15252 })
15253 .await;
15254
15255 editor
15256 .update(cx, |editor, cx| {
15257 editor.inline_diagnostics = new_inline_diagnostics;
15258 cx.notify();
15259 })
15260 .ok();
15261 });
15262 }
15263
15264 pub fn set_selections_from_remote(
15265 &mut self,
15266 selections: Vec<Selection<Anchor>>,
15267 pending_selection: Option<Selection<Anchor>>,
15268 window: &mut Window,
15269 cx: &mut Context<Self>,
15270 ) {
15271 let old_cursor_position = self.selections.newest_anchor().head();
15272 self.selections.change_with(cx, |s| {
15273 s.select_anchors(selections);
15274 if let Some(pending_selection) = pending_selection {
15275 s.set_pending(pending_selection, SelectMode::Character);
15276 } else {
15277 s.clear_pending();
15278 }
15279 });
15280 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15281 }
15282
15283 fn push_to_selection_history(&mut self) {
15284 self.selection_history.push(SelectionHistoryEntry {
15285 selections: self.selections.disjoint_anchors(),
15286 select_next_state: self.select_next_state.clone(),
15287 select_prev_state: self.select_prev_state.clone(),
15288 add_selections_state: self.add_selections_state.clone(),
15289 });
15290 }
15291
15292 pub fn transact(
15293 &mut self,
15294 window: &mut Window,
15295 cx: &mut Context<Self>,
15296 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15297 ) -> Option<TransactionId> {
15298 self.start_transaction_at(Instant::now(), window, cx);
15299 update(self, window, cx);
15300 self.end_transaction_at(Instant::now(), cx)
15301 }
15302
15303 pub fn start_transaction_at(
15304 &mut self,
15305 now: Instant,
15306 window: &mut Window,
15307 cx: &mut Context<Self>,
15308 ) {
15309 self.end_selection(window, cx);
15310 if let Some(tx_id) = self
15311 .buffer
15312 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15313 {
15314 self.selection_history
15315 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15316 cx.emit(EditorEvent::TransactionBegun {
15317 transaction_id: tx_id,
15318 })
15319 }
15320 }
15321
15322 pub fn end_transaction_at(
15323 &mut self,
15324 now: Instant,
15325 cx: &mut Context<Self>,
15326 ) -> Option<TransactionId> {
15327 if let Some(transaction_id) = self
15328 .buffer
15329 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15330 {
15331 if let Some((_, end_selections)) =
15332 self.selection_history.transaction_mut(transaction_id)
15333 {
15334 *end_selections = Some(self.selections.disjoint_anchors());
15335 } else {
15336 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15337 }
15338
15339 cx.emit(EditorEvent::Edited { transaction_id });
15340 Some(transaction_id)
15341 } else {
15342 None
15343 }
15344 }
15345
15346 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15347 if self.selection_mark_mode {
15348 self.change_selections(None, window, cx, |s| {
15349 s.move_with(|_, sel| {
15350 sel.collapse_to(sel.head(), SelectionGoal::None);
15351 });
15352 })
15353 }
15354 self.selection_mark_mode = true;
15355 cx.notify();
15356 }
15357
15358 pub fn swap_selection_ends(
15359 &mut self,
15360 _: &actions::SwapSelectionEnds,
15361 window: &mut Window,
15362 cx: &mut Context<Self>,
15363 ) {
15364 self.change_selections(None, window, cx, |s| {
15365 s.move_with(|_, sel| {
15366 if sel.start != sel.end {
15367 sel.reversed = !sel.reversed
15368 }
15369 });
15370 });
15371 self.request_autoscroll(Autoscroll::newest(), cx);
15372 cx.notify();
15373 }
15374
15375 pub fn toggle_fold(
15376 &mut self,
15377 _: &actions::ToggleFold,
15378 window: &mut Window,
15379 cx: &mut Context<Self>,
15380 ) {
15381 if self.is_singleton(cx) {
15382 let selection = self.selections.newest::<Point>(cx);
15383
15384 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15385 let range = if selection.is_empty() {
15386 let point = selection.head().to_display_point(&display_map);
15387 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15388 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15389 .to_point(&display_map);
15390 start..end
15391 } else {
15392 selection.range()
15393 };
15394 if display_map.folds_in_range(range).next().is_some() {
15395 self.unfold_lines(&Default::default(), window, cx)
15396 } else {
15397 self.fold(&Default::default(), window, cx)
15398 }
15399 } else {
15400 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15401 let buffer_ids: HashSet<_> = self
15402 .selections
15403 .disjoint_anchor_ranges()
15404 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15405 .collect();
15406
15407 let should_unfold = buffer_ids
15408 .iter()
15409 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15410
15411 for buffer_id in buffer_ids {
15412 if should_unfold {
15413 self.unfold_buffer(buffer_id, cx);
15414 } else {
15415 self.fold_buffer(buffer_id, cx);
15416 }
15417 }
15418 }
15419 }
15420
15421 pub fn toggle_fold_recursive(
15422 &mut self,
15423 _: &actions::ToggleFoldRecursive,
15424 window: &mut Window,
15425 cx: &mut Context<Self>,
15426 ) {
15427 let selection = self.selections.newest::<Point>(cx);
15428
15429 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15430 let range = if selection.is_empty() {
15431 let point = selection.head().to_display_point(&display_map);
15432 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15433 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15434 .to_point(&display_map);
15435 start..end
15436 } else {
15437 selection.range()
15438 };
15439 if display_map.folds_in_range(range).next().is_some() {
15440 self.unfold_recursive(&Default::default(), window, cx)
15441 } else {
15442 self.fold_recursive(&Default::default(), window, cx)
15443 }
15444 }
15445
15446 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15447 if self.is_singleton(cx) {
15448 let mut to_fold = Vec::new();
15449 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15450 let selections = self.selections.all_adjusted(cx);
15451
15452 for selection in selections {
15453 let range = selection.range().sorted();
15454 let buffer_start_row = range.start.row;
15455
15456 if range.start.row != range.end.row {
15457 let mut found = false;
15458 let mut row = range.start.row;
15459 while row <= range.end.row {
15460 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15461 {
15462 found = true;
15463 row = crease.range().end.row + 1;
15464 to_fold.push(crease);
15465 } else {
15466 row += 1
15467 }
15468 }
15469 if found {
15470 continue;
15471 }
15472 }
15473
15474 for row in (0..=range.start.row).rev() {
15475 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15476 if crease.range().end.row >= buffer_start_row {
15477 to_fold.push(crease);
15478 if row <= range.start.row {
15479 break;
15480 }
15481 }
15482 }
15483 }
15484 }
15485
15486 self.fold_creases(to_fold, true, window, cx);
15487 } else {
15488 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15489 let buffer_ids = self
15490 .selections
15491 .disjoint_anchor_ranges()
15492 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15493 .collect::<HashSet<_>>();
15494 for buffer_id in buffer_ids {
15495 self.fold_buffer(buffer_id, cx);
15496 }
15497 }
15498 }
15499
15500 fn fold_at_level(
15501 &mut self,
15502 fold_at: &FoldAtLevel,
15503 window: &mut Window,
15504 cx: &mut Context<Self>,
15505 ) {
15506 if !self.buffer.read(cx).is_singleton() {
15507 return;
15508 }
15509
15510 let fold_at_level = fold_at.0;
15511 let snapshot = self.buffer.read(cx).snapshot(cx);
15512 let mut to_fold = Vec::new();
15513 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15514
15515 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15516 while start_row < end_row {
15517 match self
15518 .snapshot(window, cx)
15519 .crease_for_buffer_row(MultiBufferRow(start_row))
15520 {
15521 Some(crease) => {
15522 let nested_start_row = crease.range().start.row + 1;
15523 let nested_end_row = crease.range().end.row;
15524
15525 if current_level < fold_at_level {
15526 stack.push((nested_start_row, nested_end_row, current_level + 1));
15527 } else if current_level == fold_at_level {
15528 to_fold.push(crease);
15529 }
15530
15531 start_row = nested_end_row + 1;
15532 }
15533 None => start_row += 1,
15534 }
15535 }
15536 }
15537
15538 self.fold_creases(to_fold, true, window, cx);
15539 }
15540
15541 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15542 if self.buffer.read(cx).is_singleton() {
15543 let mut fold_ranges = Vec::new();
15544 let snapshot = self.buffer.read(cx).snapshot(cx);
15545
15546 for row in 0..snapshot.max_row().0 {
15547 if let Some(foldable_range) = self
15548 .snapshot(window, cx)
15549 .crease_for_buffer_row(MultiBufferRow(row))
15550 {
15551 fold_ranges.push(foldable_range);
15552 }
15553 }
15554
15555 self.fold_creases(fold_ranges, true, window, cx);
15556 } else {
15557 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15558 editor
15559 .update_in(cx, |editor, _, cx| {
15560 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15561 editor.fold_buffer(buffer_id, cx);
15562 }
15563 })
15564 .ok();
15565 });
15566 }
15567 }
15568
15569 pub fn fold_function_bodies(
15570 &mut self,
15571 _: &actions::FoldFunctionBodies,
15572 window: &mut Window,
15573 cx: &mut Context<Self>,
15574 ) {
15575 let snapshot = self.buffer.read(cx).snapshot(cx);
15576
15577 let ranges = snapshot
15578 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15579 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15580 .collect::<Vec<_>>();
15581
15582 let creases = ranges
15583 .into_iter()
15584 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15585 .collect();
15586
15587 self.fold_creases(creases, true, window, cx);
15588 }
15589
15590 pub fn fold_recursive(
15591 &mut self,
15592 _: &actions::FoldRecursive,
15593 window: &mut Window,
15594 cx: &mut Context<Self>,
15595 ) {
15596 let mut to_fold = Vec::new();
15597 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15598 let selections = self.selections.all_adjusted(cx);
15599
15600 for selection in selections {
15601 let range = selection.range().sorted();
15602 let buffer_start_row = range.start.row;
15603
15604 if range.start.row != range.end.row {
15605 let mut found = false;
15606 for row in range.start.row..=range.end.row {
15607 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15608 found = true;
15609 to_fold.push(crease);
15610 }
15611 }
15612 if found {
15613 continue;
15614 }
15615 }
15616
15617 for row in (0..=range.start.row).rev() {
15618 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15619 if crease.range().end.row >= buffer_start_row {
15620 to_fold.push(crease);
15621 } else {
15622 break;
15623 }
15624 }
15625 }
15626 }
15627
15628 self.fold_creases(to_fold, true, window, cx);
15629 }
15630
15631 pub fn fold_at(
15632 &mut self,
15633 buffer_row: MultiBufferRow,
15634 window: &mut Window,
15635 cx: &mut Context<Self>,
15636 ) {
15637 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15638
15639 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15640 let autoscroll = self
15641 .selections
15642 .all::<Point>(cx)
15643 .iter()
15644 .any(|selection| crease.range().overlaps(&selection.range()));
15645
15646 self.fold_creases(vec![crease], autoscroll, window, cx);
15647 }
15648 }
15649
15650 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15651 if self.is_singleton(cx) {
15652 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15653 let buffer = &display_map.buffer_snapshot;
15654 let selections = self.selections.all::<Point>(cx);
15655 let ranges = selections
15656 .iter()
15657 .map(|s| {
15658 let range = s.display_range(&display_map).sorted();
15659 let mut start = range.start.to_point(&display_map);
15660 let mut end = range.end.to_point(&display_map);
15661 start.column = 0;
15662 end.column = buffer.line_len(MultiBufferRow(end.row));
15663 start..end
15664 })
15665 .collect::<Vec<_>>();
15666
15667 self.unfold_ranges(&ranges, true, true, cx);
15668 } else {
15669 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15670 let buffer_ids = self
15671 .selections
15672 .disjoint_anchor_ranges()
15673 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15674 .collect::<HashSet<_>>();
15675 for buffer_id in buffer_ids {
15676 self.unfold_buffer(buffer_id, cx);
15677 }
15678 }
15679 }
15680
15681 pub fn unfold_recursive(
15682 &mut self,
15683 _: &UnfoldRecursive,
15684 _window: &mut Window,
15685 cx: &mut Context<Self>,
15686 ) {
15687 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15688 let selections = self.selections.all::<Point>(cx);
15689 let ranges = selections
15690 .iter()
15691 .map(|s| {
15692 let mut range = s.display_range(&display_map).sorted();
15693 *range.start.column_mut() = 0;
15694 *range.end.column_mut() = display_map.line_len(range.end.row());
15695 let start = range.start.to_point(&display_map);
15696 let end = range.end.to_point(&display_map);
15697 start..end
15698 })
15699 .collect::<Vec<_>>();
15700
15701 self.unfold_ranges(&ranges, true, true, cx);
15702 }
15703
15704 pub fn unfold_at(
15705 &mut self,
15706 buffer_row: MultiBufferRow,
15707 _window: &mut Window,
15708 cx: &mut Context<Self>,
15709 ) {
15710 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15711
15712 let intersection_range = Point::new(buffer_row.0, 0)
15713 ..Point::new(
15714 buffer_row.0,
15715 display_map.buffer_snapshot.line_len(buffer_row),
15716 );
15717
15718 let autoscroll = self
15719 .selections
15720 .all::<Point>(cx)
15721 .iter()
15722 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15723
15724 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15725 }
15726
15727 pub fn unfold_all(
15728 &mut self,
15729 _: &actions::UnfoldAll,
15730 _window: &mut Window,
15731 cx: &mut Context<Self>,
15732 ) {
15733 if self.buffer.read(cx).is_singleton() {
15734 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15735 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15736 } else {
15737 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15738 editor
15739 .update(cx, |editor, cx| {
15740 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15741 editor.unfold_buffer(buffer_id, cx);
15742 }
15743 })
15744 .ok();
15745 });
15746 }
15747 }
15748
15749 pub fn fold_selected_ranges(
15750 &mut self,
15751 _: &FoldSelectedRanges,
15752 window: &mut Window,
15753 cx: &mut Context<Self>,
15754 ) {
15755 let selections = self.selections.all_adjusted(cx);
15756 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15757 let ranges = selections
15758 .into_iter()
15759 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
15760 .collect::<Vec<_>>();
15761 self.fold_creases(ranges, true, window, cx);
15762 }
15763
15764 pub fn fold_ranges<T: ToOffset + Clone>(
15765 &mut self,
15766 ranges: Vec<Range<T>>,
15767 auto_scroll: bool,
15768 window: &mut Window,
15769 cx: &mut Context<Self>,
15770 ) {
15771 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15772 let ranges = ranges
15773 .into_iter()
15774 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
15775 .collect::<Vec<_>>();
15776 self.fold_creases(ranges, auto_scroll, window, cx);
15777 }
15778
15779 pub fn fold_creases<T: ToOffset + Clone>(
15780 &mut self,
15781 creases: Vec<Crease<T>>,
15782 auto_scroll: bool,
15783 _window: &mut Window,
15784 cx: &mut Context<Self>,
15785 ) {
15786 if creases.is_empty() {
15787 return;
15788 }
15789
15790 let mut buffers_affected = HashSet::default();
15791 let multi_buffer = self.buffer().read(cx);
15792 for crease in &creases {
15793 if let Some((_, buffer, _)) =
15794 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
15795 {
15796 buffers_affected.insert(buffer.read(cx).remote_id());
15797 };
15798 }
15799
15800 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
15801
15802 if auto_scroll {
15803 self.request_autoscroll(Autoscroll::fit(), cx);
15804 }
15805
15806 cx.notify();
15807
15808 self.scrollbar_marker_state.dirty = true;
15809 self.folds_did_change(cx);
15810 }
15811
15812 /// Removes any folds whose ranges intersect any of the given ranges.
15813 pub fn unfold_ranges<T: ToOffset + Clone>(
15814 &mut self,
15815 ranges: &[Range<T>],
15816 inclusive: bool,
15817 auto_scroll: bool,
15818 cx: &mut Context<Self>,
15819 ) {
15820 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15821 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
15822 });
15823 self.folds_did_change(cx);
15824 }
15825
15826 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15827 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
15828 return;
15829 }
15830 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15831 self.display_map.update(cx, |display_map, cx| {
15832 display_map.fold_buffers([buffer_id], cx)
15833 });
15834 cx.emit(EditorEvent::BufferFoldToggled {
15835 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
15836 folded: true,
15837 });
15838 cx.notify();
15839 }
15840
15841 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15842 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
15843 return;
15844 }
15845 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15846 self.display_map.update(cx, |display_map, cx| {
15847 display_map.unfold_buffers([buffer_id], cx);
15848 });
15849 cx.emit(EditorEvent::BufferFoldToggled {
15850 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
15851 folded: false,
15852 });
15853 cx.notify();
15854 }
15855
15856 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
15857 self.display_map.read(cx).is_buffer_folded(buffer)
15858 }
15859
15860 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
15861 self.display_map.read(cx).folded_buffers()
15862 }
15863
15864 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15865 self.display_map.update(cx, |display_map, cx| {
15866 display_map.disable_header_for_buffer(buffer_id, cx);
15867 });
15868 cx.notify();
15869 }
15870
15871 /// Removes any folds with the given ranges.
15872 pub fn remove_folds_with_type<T: ToOffset + Clone>(
15873 &mut self,
15874 ranges: &[Range<T>],
15875 type_id: TypeId,
15876 auto_scroll: bool,
15877 cx: &mut Context<Self>,
15878 ) {
15879 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15880 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
15881 });
15882 self.folds_did_change(cx);
15883 }
15884
15885 fn remove_folds_with<T: ToOffset + Clone>(
15886 &mut self,
15887 ranges: &[Range<T>],
15888 auto_scroll: bool,
15889 cx: &mut Context<Self>,
15890 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
15891 ) {
15892 if ranges.is_empty() {
15893 return;
15894 }
15895
15896 let mut buffers_affected = HashSet::default();
15897 let multi_buffer = self.buffer().read(cx);
15898 for range in ranges {
15899 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
15900 buffers_affected.insert(buffer.read(cx).remote_id());
15901 };
15902 }
15903
15904 self.display_map.update(cx, update);
15905
15906 if auto_scroll {
15907 self.request_autoscroll(Autoscroll::fit(), cx);
15908 }
15909
15910 cx.notify();
15911 self.scrollbar_marker_state.dirty = true;
15912 self.active_indent_guides_state.dirty = true;
15913 }
15914
15915 pub fn update_fold_widths(
15916 &mut self,
15917 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
15918 cx: &mut Context<Self>,
15919 ) -> bool {
15920 self.display_map
15921 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
15922 }
15923
15924 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
15925 self.display_map.read(cx).fold_placeholder.clone()
15926 }
15927
15928 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
15929 self.buffer.update(cx, |buffer, cx| {
15930 buffer.set_all_diff_hunks_expanded(cx);
15931 });
15932 }
15933
15934 pub fn expand_all_diff_hunks(
15935 &mut self,
15936 _: &ExpandAllDiffHunks,
15937 _window: &mut Window,
15938 cx: &mut Context<Self>,
15939 ) {
15940 self.buffer.update(cx, |buffer, cx| {
15941 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
15942 });
15943 }
15944
15945 pub fn toggle_selected_diff_hunks(
15946 &mut self,
15947 _: &ToggleSelectedDiffHunks,
15948 _window: &mut Window,
15949 cx: &mut Context<Self>,
15950 ) {
15951 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15952 self.toggle_diff_hunks_in_ranges(ranges, cx);
15953 }
15954
15955 pub fn diff_hunks_in_ranges<'a>(
15956 &'a self,
15957 ranges: &'a [Range<Anchor>],
15958 buffer: &'a MultiBufferSnapshot,
15959 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
15960 ranges.iter().flat_map(move |range| {
15961 let end_excerpt_id = range.end.excerpt_id;
15962 let range = range.to_point(buffer);
15963 let mut peek_end = range.end;
15964 if range.end.row < buffer.max_row().0 {
15965 peek_end = Point::new(range.end.row + 1, 0);
15966 }
15967 buffer
15968 .diff_hunks_in_range(range.start..peek_end)
15969 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
15970 })
15971 }
15972
15973 pub fn has_stageable_diff_hunks_in_ranges(
15974 &self,
15975 ranges: &[Range<Anchor>],
15976 snapshot: &MultiBufferSnapshot,
15977 ) -> bool {
15978 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
15979 hunks.any(|hunk| hunk.status().has_secondary_hunk())
15980 }
15981
15982 pub fn toggle_staged_selected_diff_hunks(
15983 &mut self,
15984 _: &::git::ToggleStaged,
15985 _: &mut Window,
15986 cx: &mut Context<Self>,
15987 ) {
15988 let snapshot = self.buffer.read(cx).snapshot(cx);
15989 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15990 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
15991 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15992 }
15993
15994 pub fn set_render_diff_hunk_controls(
15995 &mut self,
15996 render_diff_hunk_controls: RenderDiffHunkControlsFn,
15997 cx: &mut Context<Self>,
15998 ) {
15999 self.render_diff_hunk_controls = render_diff_hunk_controls;
16000 cx.notify();
16001 }
16002
16003 pub fn stage_and_next(
16004 &mut self,
16005 _: &::git::StageAndNext,
16006 window: &mut Window,
16007 cx: &mut Context<Self>,
16008 ) {
16009 self.do_stage_or_unstage_and_next(true, window, cx);
16010 }
16011
16012 pub fn unstage_and_next(
16013 &mut self,
16014 _: &::git::UnstageAndNext,
16015 window: &mut Window,
16016 cx: &mut Context<Self>,
16017 ) {
16018 self.do_stage_or_unstage_and_next(false, window, cx);
16019 }
16020
16021 pub fn stage_or_unstage_diff_hunks(
16022 &mut self,
16023 stage: bool,
16024 ranges: Vec<Range<Anchor>>,
16025 cx: &mut Context<Self>,
16026 ) {
16027 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16028 cx.spawn(async move |this, cx| {
16029 task.await?;
16030 this.update(cx, |this, cx| {
16031 let snapshot = this.buffer.read(cx).snapshot(cx);
16032 let chunk_by = this
16033 .diff_hunks_in_ranges(&ranges, &snapshot)
16034 .chunk_by(|hunk| hunk.buffer_id);
16035 for (buffer_id, hunks) in &chunk_by {
16036 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16037 }
16038 })
16039 })
16040 .detach_and_log_err(cx);
16041 }
16042
16043 fn save_buffers_for_ranges_if_needed(
16044 &mut self,
16045 ranges: &[Range<Anchor>],
16046 cx: &mut Context<Editor>,
16047 ) -> Task<Result<()>> {
16048 let multibuffer = self.buffer.read(cx);
16049 let snapshot = multibuffer.read(cx);
16050 let buffer_ids: HashSet<_> = ranges
16051 .iter()
16052 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16053 .collect();
16054 drop(snapshot);
16055
16056 let mut buffers = HashSet::default();
16057 for buffer_id in buffer_ids {
16058 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16059 let buffer = buffer_entity.read(cx);
16060 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16061 {
16062 buffers.insert(buffer_entity);
16063 }
16064 }
16065 }
16066
16067 if let Some(project) = &self.project {
16068 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16069 } else {
16070 Task::ready(Ok(()))
16071 }
16072 }
16073
16074 fn do_stage_or_unstage_and_next(
16075 &mut self,
16076 stage: bool,
16077 window: &mut Window,
16078 cx: &mut Context<Self>,
16079 ) {
16080 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16081
16082 if ranges.iter().any(|range| range.start != range.end) {
16083 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16084 return;
16085 }
16086
16087 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16088 let snapshot = self.snapshot(window, cx);
16089 let position = self.selections.newest::<Point>(cx).head();
16090 let mut row = snapshot
16091 .buffer_snapshot
16092 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16093 .find(|hunk| hunk.row_range.start.0 > position.row)
16094 .map(|hunk| hunk.row_range.start);
16095
16096 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16097 // Outside of the project diff editor, wrap around to the beginning.
16098 if !all_diff_hunks_expanded {
16099 row = row.or_else(|| {
16100 snapshot
16101 .buffer_snapshot
16102 .diff_hunks_in_range(Point::zero()..position)
16103 .find(|hunk| hunk.row_range.end.0 < position.row)
16104 .map(|hunk| hunk.row_range.start)
16105 });
16106 }
16107
16108 if let Some(row) = row {
16109 let destination = Point::new(row.0, 0);
16110 let autoscroll = Autoscroll::center();
16111
16112 self.unfold_ranges(&[destination..destination], false, false, cx);
16113 self.change_selections(Some(autoscroll), window, cx, |s| {
16114 s.select_ranges([destination..destination]);
16115 });
16116 }
16117 }
16118
16119 fn do_stage_or_unstage(
16120 &self,
16121 stage: bool,
16122 buffer_id: BufferId,
16123 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16124 cx: &mut App,
16125 ) -> Option<()> {
16126 let project = self.project.as_ref()?;
16127 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16128 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16129 let buffer_snapshot = buffer.read(cx).snapshot();
16130 let file_exists = buffer_snapshot
16131 .file()
16132 .is_some_and(|file| file.disk_state().exists());
16133 diff.update(cx, |diff, cx| {
16134 diff.stage_or_unstage_hunks(
16135 stage,
16136 &hunks
16137 .map(|hunk| buffer_diff::DiffHunk {
16138 buffer_range: hunk.buffer_range,
16139 diff_base_byte_range: hunk.diff_base_byte_range,
16140 secondary_status: hunk.secondary_status,
16141 range: Point::zero()..Point::zero(), // unused
16142 })
16143 .collect::<Vec<_>>(),
16144 &buffer_snapshot,
16145 file_exists,
16146 cx,
16147 )
16148 });
16149 None
16150 }
16151
16152 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16153 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16154 self.buffer
16155 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16156 }
16157
16158 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16159 self.buffer.update(cx, |buffer, cx| {
16160 let ranges = vec![Anchor::min()..Anchor::max()];
16161 if !buffer.all_diff_hunks_expanded()
16162 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16163 {
16164 buffer.collapse_diff_hunks(ranges, cx);
16165 true
16166 } else {
16167 false
16168 }
16169 })
16170 }
16171
16172 fn toggle_diff_hunks_in_ranges(
16173 &mut self,
16174 ranges: Vec<Range<Anchor>>,
16175 cx: &mut Context<Editor>,
16176 ) {
16177 self.buffer.update(cx, |buffer, cx| {
16178 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16179 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16180 })
16181 }
16182
16183 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16184 self.buffer.update(cx, |buffer, cx| {
16185 let snapshot = buffer.snapshot(cx);
16186 let excerpt_id = range.end.excerpt_id;
16187 let point_range = range.to_point(&snapshot);
16188 let expand = !buffer.single_hunk_is_expanded(range, cx);
16189 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16190 })
16191 }
16192
16193 pub(crate) fn apply_all_diff_hunks(
16194 &mut self,
16195 _: &ApplyAllDiffHunks,
16196 window: &mut Window,
16197 cx: &mut Context<Self>,
16198 ) {
16199 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16200
16201 let buffers = self.buffer.read(cx).all_buffers();
16202 for branch_buffer in buffers {
16203 branch_buffer.update(cx, |branch_buffer, cx| {
16204 branch_buffer.merge_into_base(Vec::new(), cx);
16205 });
16206 }
16207
16208 if let Some(project) = self.project.clone() {
16209 self.save(true, project, window, cx).detach_and_log_err(cx);
16210 }
16211 }
16212
16213 pub(crate) fn apply_selected_diff_hunks(
16214 &mut self,
16215 _: &ApplyDiffHunk,
16216 window: &mut Window,
16217 cx: &mut Context<Self>,
16218 ) {
16219 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16220 let snapshot = self.snapshot(window, cx);
16221 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16222 let mut ranges_by_buffer = HashMap::default();
16223 self.transact(window, cx, |editor, _window, cx| {
16224 for hunk in hunks {
16225 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16226 ranges_by_buffer
16227 .entry(buffer.clone())
16228 .or_insert_with(Vec::new)
16229 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16230 }
16231 }
16232
16233 for (buffer, ranges) in ranges_by_buffer {
16234 buffer.update(cx, |buffer, cx| {
16235 buffer.merge_into_base(ranges, cx);
16236 });
16237 }
16238 });
16239
16240 if let Some(project) = self.project.clone() {
16241 self.save(true, project, window, cx).detach_and_log_err(cx);
16242 }
16243 }
16244
16245 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16246 if hovered != self.gutter_hovered {
16247 self.gutter_hovered = hovered;
16248 cx.notify();
16249 }
16250 }
16251
16252 pub fn insert_blocks(
16253 &mut self,
16254 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16255 autoscroll: Option<Autoscroll>,
16256 cx: &mut Context<Self>,
16257 ) -> Vec<CustomBlockId> {
16258 let blocks = self
16259 .display_map
16260 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16261 if let Some(autoscroll) = autoscroll {
16262 self.request_autoscroll(autoscroll, cx);
16263 }
16264 cx.notify();
16265 blocks
16266 }
16267
16268 pub fn resize_blocks(
16269 &mut self,
16270 heights: HashMap<CustomBlockId, u32>,
16271 autoscroll: Option<Autoscroll>,
16272 cx: &mut Context<Self>,
16273 ) {
16274 self.display_map
16275 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16276 if let Some(autoscroll) = autoscroll {
16277 self.request_autoscroll(autoscroll, cx);
16278 }
16279 cx.notify();
16280 }
16281
16282 pub fn replace_blocks(
16283 &mut self,
16284 renderers: HashMap<CustomBlockId, RenderBlock>,
16285 autoscroll: Option<Autoscroll>,
16286 cx: &mut Context<Self>,
16287 ) {
16288 self.display_map
16289 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16290 if let Some(autoscroll) = autoscroll {
16291 self.request_autoscroll(autoscroll, cx);
16292 }
16293 cx.notify();
16294 }
16295
16296 pub fn remove_blocks(
16297 &mut self,
16298 block_ids: HashSet<CustomBlockId>,
16299 autoscroll: Option<Autoscroll>,
16300 cx: &mut Context<Self>,
16301 ) {
16302 self.display_map.update(cx, |display_map, cx| {
16303 display_map.remove_blocks(block_ids, cx)
16304 });
16305 if let Some(autoscroll) = autoscroll {
16306 self.request_autoscroll(autoscroll, cx);
16307 }
16308 cx.notify();
16309 }
16310
16311 pub fn row_for_block(
16312 &self,
16313 block_id: CustomBlockId,
16314 cx: &mut Context<Self>,
16315 ) -> Option<DisplayRow> {
16316 self.display_map
16317 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16318 }
16319
16320 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16321 self.focused_block = Some(focused_block);
16322 }
16323
16324 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16325 self.focused_block.take()
16326 }
16327
16328 pub fn insert_creases(
16329 &mut self,
16330 creases: impl IntoIterator<Item = Crease<Anchor>>,
16331 cx: &mut Context<Self>,
16332 ) -> Vec<CreaseId> {
16333 self.display_map
16334 .update(cx, |map, cx| map.insert_creases(creases, cx))
16335 }
16336
16337 pub fn remove_creases(
16338 &mut self,
16339 ids: impl IntoIterator<Item = CreaseId>,
16340 cx: &mut Context<Self>,
16341 ) -> Vec<(CreaseId, Range<Anchor>)> {
16342 self.display_map
16343 .update(cx, |map, cx| map.remove_creases(ids, cx))
16344 }
16345
16346 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16347 self.display_map
16348 .update(cx, |map, cx| map.snapshot(cx))
16349 .longest_row()
16350 }
16351
16352 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16353 self.display_map
16354 .update(cx, |map, cx| map.snapshot(cx))
16355 .max_point()
16356 }
16357
16358 pub fn text(&self, cx: &App) -> String {
16359 self.buffer.read(cx).read(cx).text()
16360 }
16361
16362 pub fn is_empty(&self, cx: &App) -> bool {
16363 self.buffer.read(cx).read(cx).is_empty()
16364 }
16365
16366 pub fn text_option(&self, cx: &App) -> Option<String> {
16367 let text = self.text(cx);
16368 let text = text.trim();
16369
16370 if text.is_empty() {
16371 return None;
16372 }
16373
16374 Some(text.to_string())
16375 }
16376
16377 pub fn set_text(
16378 &mut self,
16379 text: impl Into<Arc<str>>,
16380 window: &mut Window,
16381 cx: &mut Context<Self>,
16382 ) {
16383 self.transact(window, cx, |this, _, cx| {
16384 this.buffer
16385 .read(cx)
16386 .as_singleton()
16387 .expect("you can only call set_text on editors for singleton buffers")
16388 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16389 });
16390 }
16391
16392 pub fn display_text(&self, cx: &mut App) -> String {
16393 self.display_map
16394 .update(cx, |map, cx| map.snapshot(cx))
16395 .text()
16396 }
16397
16398 fn create_minimap(
16399 &self,
16400 minimap_settings: MinimapSettings,
16401 window: &mut Window,
16402 cx: &mut Context<Self>,
16403 ) -> Option<Entity<Self>> {
16404 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
16405 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
16406 }
16407
16408 fn initialize_new_minimap(
16409 &self,
16410 minimap_settings: MinimapSettings,
16411 window: &mut Window,
16412 cx: &mut Context<Self>,
16413 ) -> Entity<Self> {
16414 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
16415
16416 let mut minimap = Editor::new_internal(
16417 EditorMode::Minimap {
16418 parent: cx.weak_entity(),
16419 },
16420 self.buffer.clone(),
16421 self.project.clone(),
16422 Some(self.display_map.clone()),
16423 window,
16424 cx,
16425 );
16426 minimap.scroll_manager.clone_state(&self.scroll_manager);
16427 minimap.set_text_style_refinement(TextStyleRefinement {
16428 font_size: Some(MINIMAP_FONT_SIZE),
16429 font_weight: Some(MINIMAP_FONT_WEIGHT),
16430 ..Default::default()
16431 });
16432 minimap.update_minimap_configuration(minimap_settings, cx);
16433 cx.new(|_| minimap)
16434 }
16435
16436 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
16437 let current_line_highlight = minimap_settings
16438 .current_line_highlight
16439 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
16440 self.set_current_line_highlight(Some(current_line_highlight));
16441 }
16442
16443 pub fn minimap(&self) -> Option<&Entity<Self>> {
16444 self.minimap.as_ref().filter(|_| self.show_minimap)
16445 }
16446
16447 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16448 let mut wrap_guides = smallvec::smallvec![];
16449
16450 if self.show_wrap_guides == Some(false) {
16451 return wrap_guides;
16452 }
16453
16454 let settings = self.buffer.read(cx).language_settings(cx);
16455 if settings.show_wrap_guides {
16456 match self.soft_wrap_mode(cx) {
16457 SoftWrap::Column(soft_wrap) => {
16458 wrap_guides.push((soft_wrap as usize, true));
16459 }
16460 SoftWrap::Bounded(soft_wrap) => {
16461 wrap_guides.push((soft_wrap as usize, true));
16462 }
16463 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16464 }
16465 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16466 }
16467
16468 wrap_guides
16469 }
16470
16471 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16472 let settings = self.buffer.read(cx).language_settings(cx);
16473 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16474 match mode {
16475 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16476 SoftWrap::None
16477 }
16478 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16479 language_settings::SoftWrap::PreferredLineLength => {
16480 SoftWrap::Column(settings.preferred_line_length)
16481 }
16482 language_settings::SoftWrap::Bounded => {
16483 SoftWrap::Bounded(settings.preferred_line_length)
16484 }
16485 }
16486 }
16487
16488 pub fn set_soft_wrap_mode(
16489 &mut self,
16490 mode: language_settings::SoftWrap,
16491
16492 cx: &mut Context<Self>,
16493 ) {
16494 self.soft_wrap_mode_override = Some(mode);
16495 cx.notify();
16496 }
16497
16498 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16499 self.hard_wrap = hard_wrap;
16500 cx.notify();
16501 }
16502
16503 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16504 self.text_style_refinement = Some(style);
16505 }
16506
16507 /// called by the Element so we know what style we were most recently rendered with.
16508 pub(crate) fn set_style(
16509 &mut self,
16510 style: EditorStyle,
16511 window: &mut Window,
16512 cx: &mut Context<Self>,
16513 ) {
16514 // We intentionally do not inform the display map about the minimap style
16515 // so that wrapping is not recalculated and stays consistent for the editor
16516 // and its linked minimap.
16517 if !self.mode.is_minimap() {
16518 let rem_size = window.rem_size();
16519 self.display_map.update(cx, |map, cx| {
16520 map.set_font(
16521 style.text.font(),
16522 style.text.font_size.to_pixels(rem_size),
16523 cx,
16524 )
16525 });
16526 }
16527 self.style = Some(style);
16528 }
16529
16530 pub fn style(&self) -> Option<&EditorStyle> {
16531 self.style.as_ref()
16532 }
16533
16534 // Called by the element. This method is not designed to be called outside of the editor
16535 // element's layout code because it does not notify when rewrapping is computed synchronously.
16536 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16537 self.display_map
16538 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16539 }
16540
16541 pub fn set_soft_wrap(&mut self) {
16542 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16543 }
16544
16545 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16546 if self.soft_wrap_mode_override.is_some() {
16547 self.soft_wrap_mode_override.take();
16548 } else {
16549 let soft_wrap = match self.soft_wrap_mode(cx) {
16550 SoftWrap::GitDiff => return,
16551 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16552 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16553 language_settings::SoftWrap::None
16554 }
16555 };
16556 self.soft_wrap_mode_override = Some(soft_wrap);
16557 }
16558 cx.notify();
16559 }
16560
16561 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16562 let Some(workspace) = self.workspace() else {
16563 return;
16564 };
16565 let fs = workspace.read(cx).app_state().fs.clone();
16566 let current_show = TabBarSettings::get_global(cx).show;
16567 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16568 setting.show = Some(!current_show);
16569 });
16570 }
16571
16572 pub fn toggle_indent_guides(
16573 &mut self,
16574 _: &ToggleIndentGuides,
16575 _: &mut Window,
16576 cx: &mut Context<Self>,
16577 ) {
16578 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16579 self.buffer
16580 .read(cx)
16581 .language_settings(cx)
16582 .indent_guides
16583 .enabled
16584 });
16585 self.show_indent_guides = Some(!currently_enabled);
16586 cx.notify();
16587 }
16588
16589 fn should_show_indent_guides(&self) -> Option<bool> {
16590 self.show_indent_guides
16591 }
16592
16593 pub fn toggle_line_numbers(
16594 &mut self,
16595 _: &ToggleLineNumbers,
16596 _: &mut Window,
16597 cx: &mut Context<Self>,
16598 ) {
16599 let mut editor_settings = EditorSettings::get_global(cx).clone();
16600 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16601 EditorSettings::override_global(editor_settings, cx);
16602 }
16603
16604 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16605 if let Some(show_line_numbers) = self.show_line_numbers {
16606 return show_line_numbers;
16607 }
16608 EditorSettings::get_global(cx).gutter.line_numbers
16609 }
16610
16611 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16612 self.use_relative_line_numbers
16613 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16614 }
16615
16616 pub fn toggle_relative_line_numbers(
16617 &mut self,
16618 _: &ToggleRelativeLineNumbers,
16619 _: &mut Window,
16620 cx: &mut Context<Self>,
16621 ) {
16622 let is_relative = self.should_use_relative_line_numbers(cx);
16623 self.set_relative_line_number(Some(!is_relative), cx)
16624 }
16625
16626 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16627 self.use_relative_line_numbers = is_relative;
16628 cx.notify();
16629 }
16630
16631 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16632 self.show_gutter = show_gutter;
16633 cx.notify();
16634 }
16635
16636 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
16637 self.show_scrollbars = show_scrollbars;
16638 cx.notify();
16639 }
16640
16641 pub fn set_show_minimap(
16642 &mut self,
16643 show_minimap: bool,
16644 window: &mut Window,
16645 cx: &mut Context<Self>,
16646 ) {
16647 if self.show_minimap != show_minimap {
16648 self.show_minimap = show_minimap;
16649 if show_minimap {
16650 let minimap_settings = EditorSettings::get_global(cx).minimap;
16651 self.minimap = self.create_minimap(minimap_settings, window, cx);
16652 } else {
16653 self.minimap = None;
16654 }
16655 cx.notify();
16656 }
16657 }
16658
16659 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16660 self.set_show_scrollbars(false, cx);
16661 self.set_show_minimap(false, window, cx);
16662 }
16663
16664 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
16665 self.show_line_numbers = Some(show_line_numbers);
16666 cx.notify();
16667 }
16668
16669 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
16670 self.disable_expand_excerpt_buttons = true;
16671 cx.notify();
16672 }
16673
16674 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
16675 self.show_git_diff_gutter = Some(show_git_diff_gutter);
16676 cx.notify();
16677 }
16678
16679 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
16680 self.show_code_actions = Some(show_code_actions);
16681 cx.notify();
16682 }
16683
16684 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
16685 self.show_runnables = Some(show_runnables);
16686 cx.notify();
16687 }
16688
16689 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
16690 self.show_breakpoints = Some(show_breakpoints);
16691 cx.notify();
16692 }
16693
16694 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
16695 if self.display_map.read(cx).masked != masked {
16696 self.display_map.update(cx, |map, _| map.masked = masked);
16697 }
16698 cx.notify()
16699 }
16700
16701 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
16702 self.show_wrap_guides = Some(show_wrap_guides);
16703 cx.notify();
16704 }
16705
16706 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
16707 self.show_indent_guides = Some(show_indent_guides);
16708 cx.notify();
16709 }
16710
16711 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
16712 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
16713 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
16714 if let Some(dir) = file.abs_path(cx).parent() {
16715 return Some(dir.to_owned());
16716 }
16717 }
16718
16719 if let Some(project_path) = buffer.read(cx).project_path(cx) {
16720 return Some(project_path.path.to_path_buf());
16721 }
16722 }
16723
16724 None
16725 }
16726
16727 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
16728 self.active_excerpt(cx)?
16729 .1
16730 .read(cx)
16731 .file()
16732 .and_then(|f| f.as_local())
16733 }
16734
16735 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16736 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16737 let buffer = buffer.read(cx);
16738 if let Some(project_path) = buffer.project_path(cx) {
16739 let project = self.project.as_ref()?.read(cx);
16740 project.absolute_path(&project_path, cx)
16741 } else {
16742 buffer
16743 .file()
16744 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
16745 }
16746 })
16747 }
16748
16749 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16750 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16751 let project_path = buffer.read(cx).project_path(cx)?;
16752 let project = self.project.as_ref()?.read(cx);
16753 let entry = project.entry_for_path(&project_path, cx)?;
16754 let path = entry.path.to_path_buf();
16755 Some(path)
16756 })
16757 }
16758
16759 pub fn reveal_in_finder(
16760 &mut self,
16761 _: &RevealInFileManager,
16762 _window: &mut Window,
16763 cx: &mut Context<Self>,
16764 ) {
16765 if let Some(target) = self.target_file(cx) {
16766 cx.reveal_path(&target.abs_path(cx));
16767 }
16768 }
16769
16770 pub fn copy_path(
16771 &mut self,
16772 _: &zed_actions::workspace::CopyPath,
16773 _window: &mut Window,
16774 cx: &mut Context<Self>,
16775 ) {
16776 if let Some(path) = self.target_file_abs_path(cx) {
16777 if let Some(path) = path.to_str() {
16778 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16779 }
16780 }
16781 }
16782
16783 pub fn copy_relative_path(
16784 &mut self,
16785 _: &zed_actions::workspace::CopyRelativePath,
16786 _window: &mut Window,
16787 cx: &mut Context<Self>,
16788 ) {
16789 if let Some(path) = self.target_file_path(cx) {
16790 if let Some(path) = path.to_str() {
16791 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16792 }
16793 }
16794 }
16795
16796 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
16797 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
16798 buffer.read(cx).project_path(cx)
16799 } else {
16800 None
16801 }
16802 }
16803
16804 // Returns true if the editor handled a go-to-line request
16805 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
16806 maybe!({
16807 let breakpoint_store = self.breakpoint_store.as_ref()?;
16808
16809 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
16810 else {
16811 self.clear_row_highlights::<ActiveDebugLine>();
16812 return None;
16813 };
16814
16815 let position = active_stack_frame.position;
16816 let buffer_id = position.buffer_id?;
16817 let snapshot = self
16818 .project
16819 .as_ref()?
16820 .read(cx)
16821 .buffer_for_id(buffer_id, cx)?
16822 .read(cx)
16823 .snapshot();
16824
16825 let mut handled = false;
16826 for (id, ExcerptRange { context, .. }) in
16827 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
16828 {
16829 if context.start.cmp(&position, &snapshot).is_ge()
16830 || context.end.cmp(&position, &snapshot).is_lt()
16831 {
16832 continue;
16833 }
16834 let snapshot = self.buffer.read(cx).snapshot(cx);
16835 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
16836
16837 handled = true;
16838 self.clear_row_highlights::<ActiveDebugLine>();
16839 self.go_to_line::<ActiveDebugLine>(
16840 multibuffer_anchor,
16841 Some(cx.theme().colors().editor_debugger_active_line_background),
16842 window,
16843 cx,
16844 );
16845
16846 cx.notify();
16847 }
16848
16849 handled.then_some(())
16850 })
16851 .is_some()
16852 }
16853
16854 pub fn copy_file_name_without_extension(
16855 &mut self,
16856 _: &CopyFileNameWithoutExtension,
16857 _: &mut Window,
16858 cx: &mut Context<Self>,
16859 ) {
16860 if let Some(file) = self.target_file(cx) {
16861 if let Some(file_stem) = file.path().file_stem() {
16862 if let Some(name) = file_stem.to_str() {
16863 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16864 }
16865 }
16866 }
16867 }
16868
16869 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
16870 if let Some(file) = self.target_file(cx) {
16871 if let Some(file_name) = file.path().file_name() {
16872 if let Some(name) = file_name.to_str() {
16873 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16874 }
16875 }
16876 }
16877 }
16878
16879 pub fn toggle_git_blame(
16880 &mut self,
16881 _: &::git::Blame,
16882 window: &mut Window,
16883 cx: &mut Context<Self>,
16884 ) {
16885 self.show_git_blame_gutter = !self.show_git_blame_gutter;
16886
16887 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
16888 self.start_git_blame(true, window, cx);
16889 }
16890
16891 cx.notify();
16892 }
16893
16894 pub fn toggle_git_blame_inline(
16895 &mut self,
16896 _: &ToggleGitBlameInline,
16897 window: &mut Window,
16898 cx: &mut Context<Self>,
16899 ) {
16900 self.toggle_git_blame_inline_internal(true, window, cx);
16901 cx.notify();
16902 }
16903
16904 pub fn open_git_blame_commit(
16905 &mut self,
16906 _: &OpenGitBlameCommit,
16907 window: &mut Window,
16908 cx: &mut Context<Self>,
16909 ) {
16910 self.open_git_blame_commit_internal(window, cx);
16911 }
16912
16913 fn open_git_blame_commit_internal(
16914 &mut self,
16915 window: &mut Window,
16916 cx: &mut Context<Self>,
16917 ) -> Option<()> {
16918 let blame = self.blame.as_ref()?;
16919 let snapshot = self.snapshot(window, cx);
16920 let cursor = self.selections.newest::<Point>(cx).head();
16921 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
16922 let blame_entry = blame
16923 .update(cx, |blame, cx| {
16924 blame
16925 .blame_for_rows(
16926 &[RowInfo {
16927 buffer_id: Some(buffer.remote_id()),
16928 buffer_row: Some(point.row),
16929 ..Default::default()
16930 }],
16931 cx,
16932 )
16933 .next()
16934 })
16935 .flatten()?;
16936 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
16937 let repo = blame.read(cx).repository(cx)?;
16938 let workspace = self.workspace()?.downgrade();
16939 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
16940 None
16941 }
16942
16943 pub fn git_blame_inline_enabled(&self) -> bool {
16944 self.git_blame_inline_enabled
16945 }
16946
16947 pub fn toggle_selection_menu(
16948 &mut self,
16949 _: &ToggleSelectionMenu,
16950 _: &mut Window,
16951 cx: &mut Context<Self>,
16952 ) {
16953 self.show_selection_menu = self
16954 .show_selection_menu
16955 .map(|show_selections_menu| !show_selections_menu)
16956 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
16957
16958 cx.notify();
16959 }
16960
16961 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
16962 self.show_selection_menu
16963 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
16964 }
16965
16966 fn start_git_blame(
16967 &mut self,
16968 user_triggered: bool,
16969 window: &mut Window,
16970 cx: &mut Context<Self>,
16971 ) {
16972 if let Some(project) = self.project.as_ref() {
16973 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
16974 return;
16975 };
16976
16977 if buffer.read(cx).file().is_none() {
16978 return;
16979 }
16980
16981 let focused = self.focus_handle(cx).contains_focused(window, cx);
16982
16983 let project = project.clone();
16984 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
16985 self.blame_subscription =
16986 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
16987 self.blame = Some(blame);
16988 }
16989 }
16990
16991 fn toggle_git_blame_inline_internal(
16992 &mut self,
16993 user_triggered: bool,
16994 window: &mut Window,
16995 cx: &mut Context<Self>,
16996 ) {
16997 if self.git_blame_inline_enabled {
16998 self.git_blame_inline_enabled = false;
16999 self.show_git_blame_inline = false;
17000 self.show_git_blame_inline_delay_task.take();
17001 } else {
17002 self.git_blame_inline_enabled = true;
17003 self.start_git_blame_inline(user_triggered, window, cx);
17004 }
17005
17006 cx.notify();
17007 }
17008
17009 fn start_git_blame_inline(
17010 &mut self,
17011 user_triggered: bool,
17012 window: &mut Window,
17013 cx: &mut Context<Self>,
17014 ) {
17015 self.start_git_blame(user_triggered, window, cx);
17016
17017 if ProjectSettings::get_global(cx)
17018 .git
17019 .inline_blame_delay()
17020 .is_some()
17021 {
17022 self.start_inline_blame_timer(window, cx);
17023 } else {
17024 self.show_git_blame_inline = true
17025 }
17026 }
17027
17028 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17029 self.blame.as_ref()
17030 }
17031
17032 pub fn show_git_blame_gutter(&self) -> bool {
17033 self.show_git_blame_gutter
17034 }
17035
17036 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17037 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17038 }
17039
17040 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17041 self.show_git_blame_inline
17042 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17043 && !self.newest_selection_head_on_empty_line(cx)
17044 && self.has_blame_entries(cx)
17045 }
17046
17047 fn has_blame_entries(&self, cx: &App) -> bool {
17048 self.blame()
17049 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17050 }
17051
17052 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17053 let cursor_anchor = self.selections.newest_anchor().head();
17054
17055 let snapshot = self.buffer.read(cx).snapshot(cx);
17056 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17057
17058 snapshot.line_len(buffer_row) == 0
17059 }
17060
17061 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17062 let buffer_and_selection = maybe!({
17063 let selection = self.selections.newest::<Point>(cx);
17064 let selection_range = selection.range();
17065
17066 let multi_buffer = self.buffer().read(cx);
17067 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17068 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17069
17070 let (buffer, range, _) = if selection.reversed {
17071 buffer_ranges.first()
17072 } else {
17073 buffer_ranges.last()
17074 }?;
17075
17076 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17077 ..text::ToPoint::to_point(&range.end, &buffer).row;
17078 Some((
17079 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17080 selection,
17081 ))
17082 });
17083
17084 let Some((buffer, selection)) = buffer_and_selection else {
17085 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17086 };
17087
17088 let Some(project) = self.project.as_ref() else {
17089 return Task::ready(Err(anyhow!("editor does not have project")));
17090 };
17091
17092 project.update(cx, |project, cx| {
17093 project.get_permalink_to_line(&buffer, selection, cx)
17094 })
17095 }
17096
17097 pub fn copy_permalink_to_line(
17098 &mut self,
17099 _: &CopyPermalinkToLine,
17100 window: &mut Window,
17101 cx: &mut Context<Self>,
17102 ) {
17103 let permalink_task = self.get_permalink_to_line(cx);
17104 let workspace = self.workspace();
17105
17106 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17107 Ok(permalink) => {
17108 cx.update(|_, cx| {
17109 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17110 })
17111 .ok();
17112 }
17113 Err(err) => {
17114 let message = format!("Failed to copy permalink: {err}");
17115
17116 Err::<(), anyhow::Error>(err).log_err();
17117
17118 if let Some(workspace) = workspace {
17119 workspace
17120 .update_in(cx, |workspace, _, cx| {
17121 struct CopyPermalinkToLine;
17122
17123 workspace.show_toast(
17124 Toast::new(
17125 NotificationId::unique::<CopyPermalinkToLine>(),
17126 message,
17127 ),
17128 cx,
17129 )
17130 })
17131 .ok();
17132 }
17133 }
17134 })
17135 .detach();
17136 }
17137
17138 pub fn copy_file_location(
17139 &mut self,
17140 _: &CopyFileLocation,
17141 _: &mut Window,
17142 cx: &mut Context<Self>,
17143 ) {
17144 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17145 if let Some(file) = self.target_file(cx) {
17146 if let Some(path) = file.path().to_str() {
17147 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17148 }
17149 }
17150 }
17151
17152 pub fn open_permalink_to_line(
17153 &mut self,
17154 _: &OpenPermalinkToLine,
17155 window: &mut Window,
17156 cx: &mut Context<Self>,
17157 ) {
17158 let permalink_task = self.get_permalink_to_line(cx);
17159 let workspace = self.workspace();
17160
17161 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17162 Ok(permalink) => {
17163 cx.update(|_, cx| {
17164 cx.open_url(permalink.as_ref());
17165 })
17166 .ok();
17167 }
17168 Err(err) => {
17169 let message = format!("Failed to open permalink: {err}");
17170
17171 Err::<(), anyhow::Error>(err).log_err();
17172
17173 if let Some(workspace) = workspace {
17174 workspace
17175 .update(cx, |workspace, cx| {
17176 struct OpenPermalinkToLine;
17177
17178 workspace.show_toast(
17179 Toast::new(
17180 NotificationId::unique::<OpenPermalinkToLine>(),
17181 message,
17182 ),
17183 cx,
17184 )
17185 })
17186 .ok();
17187 }
17188 }
17189 })
17190 .detach();
17191 }
17192
17193 pub fn insert_uuid_v4(
17194 &mut self,
17195 _: &InsertUuidV4,
17196 window: &mut Window,
17197 cx: &mut Context<Self>,
17198 ) {
17199 self.insert_uuid(UuidVersion::V4, window, cx);
17200 }
17201
17202 pub fn insert_uuid_v7(
17203 &mut self,
17204 _: &InsertUuidV7,
17205 window: &mut Window,
17206 cx: &mut Context<Self>,
17207 ) {
17208 self.insert_uuid(UuidVersion::V7, window, cx);
17209 }
17210
17211 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17212 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17213 self.transact(window, cx, |this, window, cx| {
17214 let edits = this
17215 .selections
17216 .all::<Point>(cx)
17217 .into_iter()
17218 .map(|selection| {
17219 let uuid = match version {
17220 UuidVersion::V4 => uuid::Uuid::new_v4(),
17221 UuidVersion::V7 => uuid::Uuid::now_v7(),
17222 };
17223
17224 (selection.range(), uuid.to_string())
17225 });
17226 this.edit(edits, cx);
17227 this.refresh_inline_completion(true, false, window, cx);
17228 });
17229 }
17230
17231 pub fn open_selections_in_multibuffer(
17232 &mut self,
17233 _: &OpenSelectionsInMultibuffer,
17234 window: &mut Window,
17235 cx: &mut Context<Self>,
17236 ) {
17237 let multibuffer = self.buffer.read(cx);
17238
17239 let Some(buffer) = multibuffer.as_singleton() else {
17240 return;
17241 };
17242
17243 let Some(workspace) = self.workspace() else {
17244 return;
17245 };
17246
17247 let locations = self
17248 .selections
17249 .disjoint_anchors()
17250 .iter()
17251 .map(|range| Location {
17252 buffer: buffer.clone(),
17253 range: range.start.text_anchor..range.end.text_anchor,
17254 })
17255 .collect::<Vec<_>>();
17256
17257 let title = multibuffer.title(cx).to_string();
17258
17259 cx.spawn_in(window, async move |_, cx| {
17260 workspace.update_in(cx, |workspace, window, cx| {
17261 Self::open_locations_in_multibuffer(
17262 workspace,
17263 locations,
17264 format!("Selections for '{title}'"),
17265 false,
17266 MultibufferSelectionMode::All,
17267 window,
17268 cx,
17269 );
17270 })
17271 })
17272 .detach();
17273 }
17274
17275 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17276 /// last highlight added will be used.
17277 ///
17278 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17279 pub fn highlight_rows<T: 'static>(
17280 &mut self,
17281 range: Range<Anchor>,
17282 color: Hsla,
17283 options: RowHighlightOptions,
17284 cx: &mut Context<Self>,
17285 ) {
17286 let snapshot = self.buffer().read(cx).snapshot(cx);
17287 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17288 let ix = row_highlights.binary_search_by(|highlight| {
17289 Ordering::Equal
17290 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17291 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17292 });
17293
17294 if let Err(mut ix) = ix {
17295 let index = post_inc(&mut self.highlight_order);
17296
17297 // If this range intersects with the preceding highlight, then merge it with
17298 // the preceding highlight. Otherwise insert a new highlight.
17299 let mut merged = false;
17300 if ix > 0 {
17301 let prev_highlight = &mut row_highlights[ix - 1];
17302 if prev_highlight
17303 .range
17304 .end
17305 .cmp(&range.start, &snapshot)
17306 .is_ge()
17307 {
17308 ix -= 1;
17309 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17310 prev_highlight.range.end = range.end;
17311 }
17312 merged = true;
17313 prev_highlight.index = index;
17314 prev_highlight.color = color;
17315 prev_highlight.options = options;
17316 }
17317 }
17318
17319 if !merged {
17320 row_highlights.insert(
17321 ix,
17322 RowHighlight {
17323 range: range.clone(),
17324 index,
17325 color,
17326 options,
17327 type_id: TypeId::of::<T>(),
17328 },
17329 );
17330 }
17331
17332 // If any of the following highlights intersect with this one, merge them.
17333 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17334 let highlight = &row_highlights[ix];
17335 if next_highlight
17336 .range
17337 .start
17338 .cmp(&highlight.range.end, &snapshot)
17339 .is_le()
17340 {
17341 if next_highlight
17342 .range
17343 .end
17344 .cmp(&highlight.range.end, &snapshot)
17345 .is_gt()
17346 {
17347 row_highlights[ix].range.end = next_highlight.range.end;
17348 }
17349 row_highlights.remove(ix + 1);
17350 } else {
17351 break;
17352 }
17353 }
17354 }
17355 }
17356
17357 /// Remove any highlighted row ranges of the given type that intersect the
17358 /// given ranges.
17359 pub fn remove_highlighted_rows<T: 'static>(
17360 &mut self,
17361 ranges_to_remove: Vec<Range<Anchor>>,
17362 cx: &mut Context<Self>,
17363 ) {
17364 let snapshot = self.buffer().read(cx).snapshot(cx);
17365 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17366 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17367 row_highlights.retain(|highlight| {
17368 while let Some(range_to_remove) = ranges_to_remove.peek() {
17369 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17370 Ordering::Less | Ordering::Equal => {
17371 ranges_to_remove.next();
17372 }
17373 Ordering::Greater => {
17374 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17375 Ordering::Less | Ordering::Equal => {
17376 return false;
17377 }
17378 Ordering::Greater => break,
17379 }
17380 }
17381 }
17382 }
17383
17384 true
17385 })
17386 }
17387
17388 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17389 pub fn clear_row_highlights<T: 'static>(&mut self) {
17390 self.highlighted_rows.remove(&TypeId::of::<T>());
17391 }
17392
17393 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17394 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17395 self.highlighted_rows
17396 .get(&TypeId::of::<T>())
17397 .map_or(&[] as &[_], |vec| vec.as_slice())
17398 .iter()
17399 .map(|highlight| (highlight.range.clone(), highlight.color))
17400 }
17401
17402 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17403 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17404 /// Allows to ignore certain kinds of highlights.
17405 pub fn highlighted_display_rows(
17406 &self,
17407 window: &mut Window,
17408 cx: &mut App,
17409 ) -> BTreeMap<DisplayRow, LineHighlight> {
17410 let snapshot = self.snapshot(window, cx);
17411 let mut used_highlight_orders = HashMap::default();
17412 self.highlighted_rows
17413 .iter()
17414 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17415 .fold(
17416 BTreeMap::<DisplayRow, LineHighlight>::new(),
17417 |mut unique_rows, highlight| {
17418 let start = highlight.range.start.to_display_point(&snapshot);
17419 let end = highlight.range.end.to_display_point(&snapshot);
17420 let start_row = start.row().0;
17421 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17422 && end.column() == 0
17423 {
17424 end.row().0.saturating_sub(1)
17425 } else {
17426 end.row().0
17427 };
17428 for row in start_row..=end_row {
17429 let used_index =
17430 used_highlight_orders.entry(row).or_insert(highlight.index);
17431 if highlight.index >= *used_index {
17432 *used_index = highlight.index;
17433 unique_rows.insert(
17434 DisplayRow(row),
17435 LineHighlight {
17436 include_gutter: highlight.options.include_gutter,
17437 border: None,
17438 background: highlight.color.into(),
17439 type_id: Some(highlight.type_id),
17440 },
17441 );
17442 }
17443 }
17444 unique_rows
17445 },
17446 )
17447 }
17448
17449 pub fn highlighted_display_row_for_autoscroll(
17450 &self,
17451 snapshot: &DisplaySnapshot,
17452 ) -> Option<DisplayRow> {
17453 self.highlighted_rows
17454 .values()
17455 .flat_map(|highlighted_rows| highlighted_rows.iter())
17456 .filter_map(|highlight| {
17457 if highlight.options.autoscroll {
17458 Some(highlight.range.start.to_display_point(snapshot).row())
17459 } else {
17460 None
17461 }
17462 })
17463 .min()
17464 }
17465
17466 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17467 self.highlight_background::<SearchWithinRange>(
17468 ranges,
17469 |colors| colors.editor_document_highlight_read_background,
17470 cx,
17471 )
17472 }
17473
17474 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17475 self.breadcrumb_header = Some(new_header);
17476 }
17477
17478 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17479 self.clear_background_highlights::<SearchWithinRange>(cx);
17480 }
17481
17482 pub fn highlight_background<T: 'static>(
17483 &mut self,
17484 ranges: &[Range<Anchor>],
17485 color_fetcher: fn(&ThemeColors) -> Hsla,
17486 cx: &mut Context<Self>,
17487 ) {
17488 self.background_highlights
17489 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17490 self.scrollbar_marker_state.dirty = true;
17491 cx.notify();
17492 }
17493
17494 pub fn clear_background_highlights<T: 'static>(
17495 &mut self,
17496 cx: &mut Context<Self>,
17497 ) -> Option<BackgroundHighlight> {
17498 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17499 if !text_highlights.1.is_empty() {
17500 self.scrollbar_marker_state.dirty = true;
17501 cx.notify();
17502 }
17503 Some(text_highlights)
17504 }
17505
17506 pub fn highlight_gutter<T: 'static>(
17507 &mut self,
17508 ranges: &[Range<Anchor>],
17509 color_fetcher: fn(&App) -> Hsla,
17510 cx: &mut Context<Self>,
17511 ) {
17512 self.gutter_highlights
17513 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17514 cx.notify();
17515 }
17516
17517 pub fn clear_gutter_highlights<T: 'static>(
17518 &mut self,
17519 cx: &mut Context<Self>,
17520 ) -> Option<GutterHighlight> {
17521 cx.notify();
17522 self.gutter_highlights.remove(&TypeId::of::<T>())
17523 }
17524
17525 #[cfg(feature = "test-support")]
17526 pub fn all_text_background_highlights(
17527 &self,
17528 window: &mut Window,
17529 cx: &mut Context<Self>,
17530 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17531 let snapshot = self.snapshot(window, cx);
17532 let buffer = &snapshot.buffer_snapshot;
17533 let start = buffer.anchor_before(0);
17534 let end = buffer.anchor_after(buffer.len());
17535 let theme = cx.theme().colors();
17536 self.background_highlights_in_range(start..end, &snapshot, theme)
17537 }
17538
17539 #[cfg(feature = "test-support")]
17540 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17541 let snapshot = self.buffer().read(cx).snapshot(cx);
17542
17543 let highlights = self
17544 .background_highlights
17545 .get(&TypeId::of::<items::BufferSearchHighlights>());
17546
17547 if let Some((_color, ranges)) = highlights {
17548 ranges
17549 .iter()
17550 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17551 .collect_vec()
17552 } else {
17553 vec![]
17554 }
17555 }
17556
17557 fn document_highlights_for_position<'a>(
17558 &'a self,
17559 position: Anchor,
17560 buffer: &'a MultiBufferSnapshot,
17561 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17562 let read_highlights = self
17563 .background_highlights
17564 .get(&TypeId::of::<DocumentHighlightRead>())
17565 .map(|h| &h.1);
17566 let write_highlights = self
17567 .background_highlights
17568 .get(&TypeId::of::<DocumentHighlightWrite>())
17569 .map(|h| &h.1);
17570 let left_position = position.bias_left(buffer);
17571 let right_position = position.bias_right(buffer);
17572 read_highlights
17573 .into_iter()
17574 .chain(write_highlights)
17575 .flat_map(move |ranges| {
17576 let start_ix = match ranges.binary_search_by(|probe| {
17577 let cmp = probe.end.cmp(&left_position, buffer);
17578 if cmp.is_ge() {
17579 Ordering::Greater
17580 } else {
17581 Ordering::Less
17582 }
17583 }) {
17584 Ok(i) | Err(i) => i,
17585 };
17586
17587 ranges[start_ix..]
17588 .iter()
17589 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17590 })
17591 }
17592
17593 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17594 self.background_highlights
17595 .get(&TypeId::of::<T>())
17596 .map_or(false, |(_, highlights)| !highlights.is_empty())
17597 }
17598
17599 pub fn background_highlights_in_range(
17600 &self,
17601 search_range: Range<Anchor>,
17602 display_snapshot: &DisplaySnapshot,
17603 theme: &ThemeColors,
17604 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17605 let mut results = Vec::new();
17606 for (color_fetcher, ranges) in self.background_highlights.values() {
17607 let color = color_fetcher(theme);
17608 let start_ix = match ranges.binary_search_by(|probe| {
17609 let cmp = probe
17610 .end
17611 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17612 if cmp.is_gt() {
17613 Ordering::Greater
17614 } else {
17615 Ordering::Less
17616 }
17617 }) {
17618 Ok(i) | Err(i) => i,
17619 };
17620 for range in &ranges[start_ix..] {
17621 if range
17622 .start
17623 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17624 .is_ge()
17625 {
17626 break;
17627 }
17628
17629 let start = range.start.to_display_point(display_snapshot);
17630 let end = range.end.to_display_point(display_snapshot);
17631 results.push((start..end, color))
17632 }
17633 }
17634 results
17635 }
17636
17637 pub fn background_highlight_row_ranges<T: 'static>(
17638 &self,
17639 search_range: Range<Anchor>,
17640 display_snapshot: &DisplaySnapshot,
17641 count: usize,
17642 ) -> Vec<RangeInclusive<DisplayPoint>> {
17643 let mut results = Vec::new();
17644 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
17645 return vec![];
17646 };
17647
17648 let start_ix = match ranges.binary_search_by(|probe| {
17649 let cmp = probe
17650 .end
17651 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17652 if cmp.is_gt() {
17653 Ordering::Greater
17654 } else {
17655 Ordering::Less
17656 }
17657 }) {
17658 Ok(i) | Err(i) => i,
17659 };
17660 let mut push_region = |start: Option<Point>, end: Option<Point>| {
17661 if let (Some(start_display), Some(end_display)) = (start, end) {
17662 results.push(
17663 start_display.to_display_point(display_snapshot)
17664 ..=end_display.to_display_point(display_snapshot),
17665 );
17666 }
17667 };
17668 let mut start_row: Option<Point> = None;
17669 let mut end_row: Option<Point> = None;
17670 if ranges.len() > count {
17671 return Vec::new();
17672 }
17673 for range in &ranges[start_ix..] {
17674 if range
17675 .start
17676 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17677 .is_ge()
17678 {
17679 break;
17680 }
17681 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
17682 if let Some(current_row) = &end_row {
17683 if end.row == current_row.row {
17684 continue;
17685 }
17686 }
17687 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
17688 if start_row.is_none() {
17689 assert_eq!(end_row, None);
17690 start_row = Some(start);
17691 end_row = Some(end);
17692 continue;
17693 }
17694 if let Some(current_end) = end_row.as_mut() {
17695 if start.row > current_end.row + 1 {
17696 push_region(start_row, end_row);
17697 start_row = Some(start);
17698 end_row = Some(end);
17699 } else {
17700 // Merge two hunks.
17701 *current_end = end;
17702 }
17703 } else {
17704 unreachable!();
17705 }
17706 }
17707 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
17708 push_region(start_row, end_row);
17709 results
17710 }
17711
17712 pub fn gutter_highlights_in_range(
17713 &self,
17714 search_range: Range<Anchor>,
17715 display_snapshot: &DisplaySnapshot,
17716 cx: &App,
17717 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17718 let mut results = Vec::new();
17719 for (color_fetcher, ranges) in self.gutter_highlights.values() {
17720 let color = color_fetcher(cx);
17721 let start_ix = match ranges.binary_search_by(|probe| {
17722 let cmp = probe
17723 .end
17724 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17725 if cmp.is_gt() {
17726 Ordering::Greater
17727 } else {
17728 Ordering::Less
17729 }
17730 }) {
17731 Ok(i) | Err(i) => i,
17732 };
17733 for range in &ranges[start_ix..] {
17734 if range
17735 .start
17736 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17737 .is_ge()
17738 {
17739 break;
17740 }
17741
17742 let start = range.start.to_display_point(display_snapshot);
17743 let end = range.end.to_display_point(display_snapshot);
17744 results.push((start..end, color))
17745 }
17746 }
17747 results
17748 }
17749
17750 /// Get the text ranges corresponding to the redaction query
17751 pub fn redacted_ranges(
17752 &self,
17753 search_range: Range<Anchor>,
17754 display_snapshot: &DisplaySnapshot,
17755 cx: &App,
17756 ) -> Vec<Range<DisplayPoint>> {
17757 display_snapshot
17758 .buffer_snapshot
17759 .redacted_ranges(search_range, |file| {
17760 if let Some(file) = file {
17761 file.is_private()
17762 && EditorSettings::get(
17763 Some(SettingsLocation {
17764 worktree_id: file.worktree_id(cx),
17765 path: file.path().as_ref(),
17766 }),
17767 cx,
17768 )
17769 .redact_private_values
17770 } else {
17771 false
17772 }
17773 })
17774 .map(|range| {
17775 range.start.to_display_point(display_snapshot)
17776 ..range.end.to_display_point(display_snapshot)
17777 })
17778 .collect()
17779 }
17780
17781 pub fn highlight_text<T: 'static>(
17782 &mut self,
17783 ranges: Vec<Range<Anchor>>,
17784 style: HighlightStyle,
17785 cx: &mut Context<Self>,
17786 ) {
17787 self.display_map.update(cx, |map, _| {
17788 map.highlight_text(TypeId::of::<T>(), ranges, style)
17789 });
17790 cx.notify();
17791 }
17792
17793 pub(crate) fn highlight_inlays<T: 'static>(
17794 &mut self,
17795 highlights: Vec<InlayHighlight>,
17796 style: HighlightStyle,
17797 cx: &mut Context<Self>,
17798 ) {
17799 self.display_map.update(cx, |map, _| {
17800 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
17801 });
17802 cx.notify();
17803 }
17804
17805 pub fn text_highlights<'a, T: 'static>(
17806 &'a self,
17807 cx: &'a App,
17808 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
17809 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
17810 }
17811
17812 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
17813 let cleared = self
17814 .display_map
17815 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
17816 if cleared {
17817 cx.notify();
17818 }
17819 }
17820
17821 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
17822 (self.read_only(cx) || self.blink_manager.read(cx).visible())
17823 && self.focus_handle.is_focused(window)
17824 }
17825
17826 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
17827 self.show_cursor_when_unfocused = is_enabled;
17828 cx.notify();
17829 }
17830
17831 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
17832 cx.notify();
17833 }
17834
17835 fn on_debug_session_event(
17836 &mut self,
17837 _session: Entity<Session>,
17838 event: &SessionEvent,
17839 cx: &mut Context<Self>,
17840 ) {
17841 match event {
17842 SessionEvent::InvalidateInlineValue => {
17843 self.refresh_inline_values(cx);
17844 }
17845 _ => {}
17846 }
17847 }
17848
17849 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
17850 let Some(project) = self.project.clone() else {
17851 return;
17852 };
17853 let Some(buffer) = self.buffer.read(cx).as_singleton() else {
17854 return;
17855 };
17856 if !self.inline_value_cache.enabled {
17857 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
17858 self.splice_inlays(&inlays, Vec::new(), cx);
17859 return;
17860 }
17861
17862 let current_execution_position = self
17863 .highlighted_rows
17864 .get(&TypeId::of::<ActiveDebugLine>())
17865 .and_then(|lines| lines.last().map(|line| line.range.start));
17866
17867 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
17868 let snapshot = editor
17869 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17870 .ok()?;
17871
17872 let inline_values = editor
17873 .update(cx, |_, cx| {
17874 let Some(current_execution_position) = current_execution_position else {
17875 return Some(Task::ready(Ok(Vec::new())));
17876 };
17877
17878 // todo(debugger) when introducing multi buffer inline values check execution position's buffer id to make sure the text
17879 // anchor is in the same buffer
17880 let range =
17881 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
17882 project.inline_values(buffer, range, cx)
17883 })
17884 .ok()
17885 .flatten()?
17886 .await
17887 .context("refreshing debugger inlays")
17888 .log_err()?;
17889
17890 let (excerpt_id, buffer_id) = snapshot
17891 .excerpts()
17892 .next()
17893 .map(|excerpt| (excerpt.0, excerpt.1.remote_id()))?;
17894 editor
17895 .update(cx, |editor, cx| {
17896 let new_inlays = inline_values
17897 .into_iter()
17898 .map(|debugger_value| {
17899 Inlay::debugger_hint(
17900 post_inc(&mut editor.next_inlay_id),
17901 Anchor::in_buffer(excerpt_id, buffer_id, debugger_value.position),
17902 debugger_value.text(),
17903 )
17904 })
17905 .collect::<Vec<_>>();
17906 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
17907 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
17908
17909 editor.splice_inlays(&inlay_ids, new_inlays, cx);
17910 })
17911 .ok()?;
17912 Some(())
17913 });
17914 }
17915
17916 fn on_buffer_event(
17917 &mut self,
17918 multibuffer: &Entity<MultiBuffer>,
17919 event: &multi_buffer::Event,
17920 window: &mut Window,
17921 cx: &mut Context<Self>,
17922 ) {
17923 match event {
17924 multi_buffer::Event::Edited {
17925 singleton_buffer_edited,
17926 edited_buffer: buffer_edited,
17927 } => {
17928 self.scrollbar_marker_state.dirty = true;
17929 self.active_indent_guides_state.dirty = true;
17930 self.refresh_active_diagnostics(cx);
17931 self.refresh_code_actions(window, cx);
17932 self.refresh_selected_text_highlights(true, window, cx);
17933 refresh_matching_bracket_highlights(self, window, cx);
17934 if self.has_active_inline_completion() {
17935 self.update_visible_inline_completion(window, cx);
17936 }
17937 if let Some(buffer) = buffer_edited {
17938 let buffer_id = buffer.read(cx).remote_id();
17939 if !self.registered_buffers.contains_key(&buffer_id) {
17940 if let Some(project) = self.project.as_ref() {
17941 project.update(cx, |project, cx| {
17942 self.registered_buffers.insert(
17943 buffer_id,
17944 project.register_buffer_with_language_servers(&buffer, cx),
17945 );
17946 })
17947 }
17948 }
17949 }
17950 cx.emit(EditorEvent::BufferEdited);
17951 cx.emit(SearchEvent::MatchesInvalidated);
17952 if *singleton_buffer_edited {
17953 if let Some(project) = &self.project {
17954 #[allow(clippy::mutable_key_type)]
17955 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
17956 multibuffer
17957 .all_buffers()
17958 .into_iter()
17959 .filter_map(|buffer| {
17960 buffer.update(cx, |buffer, cx| {
17961 let language = buffer.language()?;
17962 let should_discard = project.update(cx, |project, cx| {
17963 project.is_local()
17964 && !project.has_language_servers_for(buffer, cx)
17965 });
17966 should_discard.not().then_some(language.clone())
17967 })
17968 })
17969 .collect::<HashSet<_>>()
17970 });
17971 if !languages_affected.is_empty() {
17972 self.refresh_inlay_hints(
17973 InlayHintRefreshReason::BufferEdited(languages_affected),
17974 cx,
17975 );
17976 }
17977 }
17978 }
17979
17980 let Some(project) = &self.project else { return };
17981 let (telemetry, is_via_ssh) = {
17982 let project = project.read(cx);
17983 let telemetry = project.client().telemetry().clone();
17984 let is_via_ssh = project.is_via_ssh();
17985 (telemetry, is_via_ssh)
17986 };
17987 refresh_linked_ranges(self, window, cx);
17988 telemetry.log_edit_event("editor", is_via_ssh);
17989 }
17990 multi_buffer::Event::ExcerptsAdded {
17991 buffer,
17992 predecessor,
17993 excerpts,
17994 } => {
17995 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17996 let buffer_id = buffer.read(cx).remote_id();
17997 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
17998 if let Some(project) = &self.project {
17999 update_uncommitted_diff_for_buffer(
18000 cx.entity(),
18001 project,
18002 [buffer.clone()],
18003 self.buffer.clone(),
18004 cx,
18005 )
18006 .detach();
18007 }
18008 }
18009 cx.emit(EditorEvent::ExcerptsAdded {
18010 buffer: buffer.clone(),
18011 predecessor: *predecessor,
18012 excerpts: excerpts.clone(),
18013 });
18014 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18015 }
18016 multi_buffer::Event::ExcerptsRemoved {
18017 ids,
18018 removed_buffer_ids,
18019 } => {
18020 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18021 let buffer = self.buffer.read(cx);
18022 self.registered_buffers
18023 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18024 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18025 cx.emit(EditorEvent::ExcerptsRemoved {
18026 ids: ids.clone(),
18027 removed_buffer_ids: removed_buffer_ids.clone(),
18028 })
18029 }
18030 multi_buffer::Event::ExcerptsEdited {
18031 excerpt_ids,
18032 buffer_ids,
18033 } => {
18034 self.display_map.update(cx, |map, cx| {
18035 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18036 });
18037 cx.emit(EditorEvent::ExcerptsEdited {
18038 ids: excerpt_ids.clone(),
18039 })
18040 }
18041 multi_buffer::Event::ExcerptsExpanded { ids } => {
18042 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18043 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18044 }
18045 multi_buffer::Event::Reparsed(buffer_id) => {
18046 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18047 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18048
18049 cx.emit(EditorEvent::Reparsed(*buffer_id));
18050 }
18051 multi_buffer::Event::DiffHunksToggled => {
18052 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18053 }
18054 multi_buffer::Event::LanguageChanged(buffer_id) => {
18055 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18056 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18057 cx.emit(EditorEvent::Reparsed(*buffer_id));
18058 cx.notify();
18059 }
18060 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18061 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18062 multi_buffer::Event::FileHandleChanged
18063 | multi_buffer::Event::Reloaded
18064 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18065 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18066 multi_buffer::Event::DiagnosticsUpdated => {
18067 self.refresh_active_diagnostics(cx);
18068 self.refresh_inline_diagnostics(true, window, cx);
18069 self.scrollbar_marker_state.dirty = true;
18070 cx.notify();
18071 }
18072 _ => {}
18073 };
18074 }
18075
18076 pub fn start_temporary_diff_override(&mut self) {
18077 self.load_diff_task.take();
18078 self.temporary_diff_override = true;
18079 }
18080
18081 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18082 self.temporary_diff_override = false;
18083 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18084 self.buffer.update(cx, |buffer, cx| {
18085 buffer.set_all_diff_hunks_collapsed(cx);
18086 });
18087
18088 if let Some(project) = self.project.clone() {
18089 self.load_diff_task = Some(
18090 update_uncommitted_diff_for_buffer(
18091 cx.entity(),
18092 &project,
18093 self.buffer.read(cx).all_buffers(),
18094 self.buffer.clone(),
18095 cx,
18096 )
18097 .shared(),
18098 );
18099 }
18100 }
18101
18102 fn on_display_map_changed(
18103 &mut self,
18104 _: Entity<DisplayMap>,
18105 _: &mut Window,
18106 cx: &mut Context<Self>,
18107 ) {
18108 cx.notify();
18109 }
18110
18111 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18112 let new_severity = if self.diagnostics_enabled() {
18113 EditorSettings::get_global(cx)
18114 .diagnostics_max_severity
18115 .unwrap_or(DiagnosticSeverity::Hint)
18116 } else {
18117 DiagnosticSeverity::Off
18118 };
18119 self.set_max_diagnostics_severity(new_severity, cx);
18120 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18121 self.update_edit_prediction_settings(cx);
18122 self.refresh_inline_completion(true, false, window, cx);
18123 self.refresh_inlay_hints(
18124 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18125 self.selections.newest_anchor().head(),
18126 &self.buffer.read(cx).snapshot(cx),
18127 cx,
18128 )),
18129 cx,
18130 );
18131
18132 let old_cursor_shape = self.cursor_shape;
18133
18134 {
18135 let editor_settings = EditorSettings::get_global(cx);
18136 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18137 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18138 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18139 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18140 }
18141
18142 if old_cursor_shape != self.cursor_shape {
18143 cx.emit(EditorEvent::CursorShapeChanged);
18144 }
18145
18146 let project_settings = ProjectSettings::get_global(cx);
18147 self.serialize_dirty_buffers =
18148 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18149
18150 if self.mode.is_full() {
18151 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18152 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18153 if self.show_inline_diagnostics != show_inline_diagnostics {
18154 self.show_inline_diagnostics = show_inline_diagnostics;
18155 self.refresh_inline_diagnostics(false, window, cx);
18156 }
18157
18158 if self.git_blame_inline_enabled != inline_blame_enabled {
18159 self.toggle_git_blame_inline_internal(false, window, cx);
18160 }
18161
18162 let minimap_settings = EditorSettings::get_global(cx).minimap;
18163 if self.show_minimap != minimap_settings.minimap_enabled() {
18164 self.set_show_minimap(!self.show_minimap, window, cx);
18165 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18166 minimap_entity.update(cx, |minimap_editor, cx| {
18167 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18168 })
18169 }
18170 }
18171
18172 cx.notify();
18173 }
18174
18175 pub fn set_searchable(&mut self, searchable: bool) {
18176 self.searchable = searchable;
18177 }
18178
18179 pub fn searchable(&self) -> bool {
18180 self.searchable
18181 }
18182
18183 fn open_proposed_changes_editor(
18184 &mut self,
18185 _: &OpenProposedChangesEditor,
18186 window: &mut Window,
18187 cx: &mut Context<Self>,
18188 ) {
18189 let Some(workspace) = self.workspace() else {
18190 cx.propagate();
18191 return;
18192 };
18193
18194 let selections = self.selections.all::<usize>(cx);
18195 let multi_buffer = self.buffer.read(cx);
18196 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18197 let mut new_selections_by_buffer = HashMap::default();
18198 for selection in selections {
18199 for (buffer, range, _) in
18200 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18201 {
18202 let mut range = range.to_point(buffer);
18203 range.start.column = 0;
18204 range.end.column = buffer.line_len(range.end.row);
18205 new_selections_by_buffer
18206 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18207 .or_insert(Vec::new())
18208 .push(range)
18209 }
18210 }
18211
18212 let proposed_changes_buffers = new_selections_by_buffer
18213 .into_iter()
18214 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18215 .collect::<Vec<_>>();
18216 let proposed_changes_editor = cx.new(|cx| {
18217 ProposedChangesEditor::new(
18218 "Proposed changes",
18219 proposed_changes_buffers,
18220 self.project.clone(),
18221 window,
18222 cx,
18223 )
18224 });
18225
18226 window.defer(cx, move |window, cx| {
18227 workspace.update(cx, |workspace, cx| {
18228 workspace.active_pane().update(cx, |pane, cx| {
18229 pane.add_item(
18230 Box::new(proposed_changes_editor),
18231 true,
18232 true,
18233 None,
18234 window,
18235 cx,
18236 );
18237 });
18238 });
18239 });
18240 }
18241
18242 pub fn open_excerpts_in_split(
18243 &mut self,
18244 _: &OpenExcerptsSplit,
18245 window: &mut Window,
18246 cx: &mut Context<Self>,
18247 ) {
18248 self.open_excerpts_common(None, true, window, cx)
18249 }
18250
18251 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18252 self.open_excerpts_common(None, false, window, cx)
18253 }
18254
18255 fn open_excerpts_common(
18256 &mut self,
18257 jump_data: Option<JumpData>,
18258 split: bool,
18259 window: &mut Window,
18260 cx: &mut Context<Self>,
18261 ) {
18262 let Some(workspace) = self.workspace() else {
18263 cx.propagate();
18264 return;
18265 };
18266
18267 if self.buffer.read(cx).is_singleton() {
18268 cx.propagate();
18269 return;
18270 }
18271
18272 let mut new_selections_by_buffer = HashMap::default();
18273 match &jump_data {
18274 Some(JumpData::MultiBufferPoint {
18275 excerpt_id,
18276 position,
18277 anchor,
18278 line_offset_from_top,
18279 }) => {
18280 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18281 if let Some(buffer) = multi_buffer_snapshot
18282 .buffer_id_for_excerpt(*excerpt_id)
18283 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18284 {
18285 let buffer_snapshot = buffer.read(cx).snapshot();
18286 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18287 language::ToPoint::to_point(anchor, &buffer_snapshot)
18288 } else {
18289 buffer_snapshot.clip_point(*position, Bias::Left)
18290 };
18291 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18292 new_selections_by_buffer.insert(
18293 buffer,
18294 (
18295 vec![jump_to_offset..jump_to_offset],
18296 Some(*line_offset_from_top),
18297 ),
18298 );
18299 }
18300 }
18301 Some(JumpData::MultiBufferRow {
18302 row,
18303 line_offset_from_top,
18304 }) => {
18305 let point = MultiBufferPoint::new(row.0, 0);
18306 if let Some((buffer, buffer_point, _)) =
18307 self.buffer.read(cx).point_to_buffer_point(point, cx)
18308 {
18309 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18310 new_selections_by_buffer
18311 .entry(buffer)
18312 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18313 .0
18314 .push(buffer_offset..buffer_offset)
18315 }
18316 }
18317 None => {
18318 let selections = self.selections.all::<usize>(cx);
18319 let multi_buffer = self.buffer.read(cx);
18320 for selection in selections {
18321 for (snapshot, range, _, anchor) in multi_buffer
18322 .snapshot(cx)
18323 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18324 {
18325 if let Some(anchor) = anchor {
18326 // selection is in a deleted hunk
18327 let Some(buffer_id) = anchor.buffer_id else {
18328 continue;
18329 };
18330 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18331 continue;
18332 };
18333 let offset = text::ToOffset::to_offset(
18334 &anchor.text_anchor,
18335 &buffer_handle.read(cx).snapshot(),
18336 );
18337 let range = offset..offset;
18338 new_selections_by_buffer
18339 .entry(buffer_handle)
18340 .or_insert((Vec::new(), None))
18341 .0
18342 .push(range)
18343 } else {
18344 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18345 else {
18346 continue;
18347 };
18348 new_selections_by_buffer
18349 .entry(buffer_handle)
18350 .or_insert((Vec::new(), None))
18351 .0
18352 .push(range)
18353 }
18354 }
18355 }
18356 }
18357 }
18358
18359 new_selections_by_buffer
18360 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18361
18362 if new_selections_by_buffer.is_empty() {
18363 return;
18364 }
18365
18366 // We defer the pane interaction because we ourselves are a workspace item
18367 // and activating a new item causes the pane to call a method on us reentrantly,
18368 // which panics if we're on the stack.
18369 window.defer(cx, move |window, cx| {
18370 workspace.update(cx, |workspace, cx| {
18371 let pane = if split {
18372 workspace.adjacent_pane(window, cx)
18373 } else {
18374 workspace.active_pane().clone()
18375 };
18376
18377 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18378 let editor = buffer
18379 .read(cx)
18380 .file()
18381 .is_none()
18382 .then(|| {
18383 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18384 // so `workspace.open_project_item` will never find them, always opening a new editor.
18385 // Instead, we try to activate the existing editor in the pane first.
18386 let (editor, pane_item_index) =
18387 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18388 let editor = item.downcast::<Editor>()?;
18389 let singleton_buffer =
18390 editor.read(cx).buffer().read(cx).as_singleton()?;
18391 if singleton_buffer == buffer {
18392 Some((editor, i))
18393 } else {
18394 None
18395 }
18396 })?;
18397 pane.update(cx, |pane, cx| {
18398 pane.activate_item(pane_item_index, true, true, window, cx)
18399 });
18400 Some(editor)
18401 })
18402 .flatten()
18403 .unwrap_or_else(|| {
18404 workspace.open_project_item::<Self>(
18405 pane.clone(),
18406 buffer,
18407 true,
18408 true,
18409 window,
18410 cx,
18411 )
18412 });
18413
18414 editor.update(cx, |editor, cx| {
18415 let autoscroll = match scroll_offset {
18416 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18417 None => Autoscroll::newest(),
18418 };
18419 let nav_history = editor.nav_history.take();
18420 editor.change_selections(Some(autoscroll), window, cx, |s| {
18421 s.select_ranges(ranges);
18422 });
18423 editor.nav_history = nav_history;
18424 });
18425 }
18426 })
18427 });
18428 }
18429
18430 // For now, don't allow opening excerpts in buffers that aren't backed by
18431 // regular project files.
18432 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18433 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18434 }
18435
18436 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18437 let snapshot = self.buffer.read(cx).read(cx);
18438 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18439 Some(
18440 ranges
18441 .iter()
18442 .map(move |range| {
18443 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18444 })
18445 .collect(),
18446 )
18447 }
18448
18449 fn selection_replacement_ranges(
18450 &self,
18451 range: Range<OffsetUtf16>,
18452 cx: &mut App,
18453 ) -> Vec<Range<OffsetUtf16>> {
18454 let selections = self.selections.all::<OffsetUtf16>(cx);
18455 let newest_selection = selections
18456 .iter()
18457 .max_by_key(|selection| selection.id)
18458 .unwrap();
18459 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
18460 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
18461 let snapshot = self.buffer.read(cx).read(cx);
18462 selections
18463 .into_iter()
18464 .map(|mut selection| {
18465 selection.start.0 =
18466 (selection.start.0 as isize).saturating_add(start_delta) as usize;
18467 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
18468 snapshot.clip_offset_utf16(selection.start, Bias::Left)
18469 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
18470 })
18471 .collect()
18472 }
18473
18474 fn report_editor_event(
18475 &self,
18476 event_type: &'static str,
18477 file_extension: Option<String>,
18478 cx: &App,
18479 ) {
18480 if cfg!(any(test, feature = "test-support")) {
18481 return;
18482 }
18483
18484 let Some(project) = &self.project else { return };
18485
18486 // If None, we are in a file without an extension
18487 let file = self
18488 .buffer
18489 .read(cx)
18490 .as_singleton()
18491 .and_then(|b| b.read(cx).file());
18492 let file_extension = file_extension.or(file
18493 .as_ref()
18494 .and_then(|file| Path::new(file.file_name(cx)).extension())
18495 .and_then(|e| e.to_str())
18496 .map(|a| a.to_string()));
18497
18498 let vim_mode = vim_enabled(cx);
18499
18500 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
18501 let copilot_enabled = edit_predictions_provider
18502 == language::language_settings::EditPredictionProvider::Copilot;
18503 let copilot_enabled_for_language = self
18504 .buffer
18505 .read(cx)
18506 .language_settings(cx)
18507 .show_edit_predictions;
18508
18509 let project = project.read(cx);
18510 telemetry::event!(
18511 event_type,
18512 file_extension,
18513 vim_mode,
18514 copilot_enabled,
18515 copilot_enabled_for_language,
18516 edit_predictions_provider,
18517 is_via_ssh = project.is_via_ssh(),
18518 );
18519 }
18520
18521 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
18522 /// with each line being an array of {text, highlight} objects.
18523 fn copy_highlight_json(
18524 &mut self,
18525 _: &CopyHighlightJson,
18526 window: &mut Window,
18527 cx: &mut Context<Self>,
18528 ) {
18529 #[derive(Serialize)]
18530 struct Chunk<'a> {
18531 text: String,
18532 highlight: Option<&'a str>,
18533 }
18534
18535 let snapshot = self.buffer.read(cx).snapshot(cx);
18536 let range = self
18537 .selected_text_range(false, window, cx)
18538 .and_then(|selection| {
18539 if selection.range.is_empty() {
18540 None
18541 } else {
18542 Some(selection.range)
18543 }
18544 })
18545 .unwrap_or_else(|| 0..snapshot.len());
18546
18547 let chunks = snapshot.chunks(range, true);
18548 let mut lines = Vec::new();
18549 let mut line: VecDeque<Chunk> = VecDeque::new();
18550
18551 let Some(style) = self.style.as_ref() else {
18552 return;
18553 };
18554
18555 for chunk in chunks {
18556 let highlight = chunk
18557 .syntax_highlight_id
18558 .and_then(|id| id.name(&style.syntax));
18559 let mut chunk_lines = chunk.text.split('\n').peekable();
18560 while let Some(text) = chunk_lines.next() {
18561 let mut merged_with_last_token = false;
18562 if let Some(last_token) = line.back_mut() {
18563 if last_token.highlight == highlight {
18564 last_token.text.push_str(text);
18565 merged_with_last_token = true;
18566 }
18567 }
18568
18569 if !merged_with_last_token {
18570 line.push_back(Chunk {
18571 text: text.into(),
18572 highlight,
18573 });
18574 }
18575
18576 if chunk_lines.peek().is_some() {
18577 if line.len() > 1 && line.front().unwrap().text.is_empty() {
18578 line.pop_front();
18579 }
18580 if line.len() > 1 && line.back().unwrap().text.is_empty() {
18581 line.pop_back();
18582 }
18583
18584 lines.push(mem::take(&mut line));
18585 }
18586 }
18587 }
18588
18589 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
18590 return;
18591 };
18592 cx.write_to_clipboard(ClipboardItem::new_string(lines));
18593 }
18594
18595 pub fn open_context_menu(
18596 &mut self,
18597 _: &OpenContextMenu,
18598 window: &mut Window,
18599 cx: &mut Context<Self>,
18600 ) {
18601 self.request_autoscroll(Autoscroll::newest(), cx);
18602 let position = self.selections.newest_display(cx).start;
18603 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
18604 }
18605
18606 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
18607 &self.inlay_hint_cache
18608 }
18609
18610 pub fn replay_insert_event(
18611 &mut self,
18612 text: &str,
18613 relative_utf16_range: Option<Range<isize>>,
18614 window: &mut Window,
18615 cx: &mut Context<Self>,
18616 ) {
18617 if !self.input_enabled {
18618 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18619 return;
18620 }
18621 if let Some(relative_utf16_range) = relative_utf16_range {
18622 let selections = self.selections.all::<OffsetUtf16>(cx);
18623 self.change_selections(None, window, cx, |s| {
18624 let new_ranges = selections.into_iter().map(|range| {
18625 let start = OffsetUtf16(
18626 range
18627 .head()
18628 .0
18629 .saturating_add_signed(relative_utf16_range.start),
18630 );
18631 let end = OffsetUtf16(
18632 range
18633 .head()
18634 .0
18635 .saturating_add_signed(relative_utf16_range.end),
18636 );
18637 start..end
18638 });
18639 s.select_ranges(new_ranges);
18640 });
18641 }
18642
18643 self.handle_input(text, window, cx);
18644 }
18645
18646 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
18647 let Some(provider) = self.semantics_provider.as_ref() else {
18648 return false;
18649 };
18650
18651 let mut supports = false;
18652 self.buffer().update(cx, |this, cx| {
18653 this.for_each_buffer(|buffer| {
18654 supports |= provider.supports_inlay_hints(buffer, cx);
18655 });
18656 });
18657
18658 supports
18659 }
18660
18661 pub fn is_focused(&self, window: &Window) -> bool {
18662 self.focus_handle.is_focused(window)
18663 }
18664
18665 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18666 cx.emit(EditorEvent::Focused);
18667
18668 if let Some(descendant) = self
18669 .last_focused_descendant
18670 .take()
18671 .and_then(|descendant| descendant.upgrade())
18672 {
18673 window.focus(&descendant);
18674 } else {
18675 if let Some(blame) = self.blame.as_ref() {
18676 blame.update(cx, GitBlame::focus)
18677 }
18678
18679 self.blink_manager.update(cx, BlinkManager::enable);
18680 self.show_cursor_names(window, cx);
18681 self.buffer.update(cx, |buffer, cx| {
18682 buffer.finalize_last_transaction(cx);
18683 if self.leader_id.is_none() {
18684 buffer.set_active_selections(
18685 &self.selections.disjoint_anchors(),
18686 self.selections.line_mode,
18687 self.cursor_shape,
18688 cx,
18689 );
18690 }
18691 });
18692 }
18693 }
18694
18695 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18696 cx.emit(EditorEvent::FocusedIn)
18697 }
18698
18699 fn handle_focus_out(
18700 &mut self,
18701 event: FocusOutEvent,
18702 _window: &mut Window,
18703 cx: &mut Context<Self>,
18704 ) {
18705 if event.blurred != self.focus_handle {
18706 self.last_focused_descendant = Some(event.blurred);
18707 }
18708 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
18709 }
18710
18711 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18712 self.blink_manager.update(cx, BlinkManager::disable);
18713 self.buffer
18714 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
18715
18716 if let Some(blame) = self.blame.as_ref() {
18717 blame.update(cx, GitBlame::blur)
18718 }
18719 if !self.hover_state.focused(window, cx) {
18720 hide_hover(self, cx);
18721 }
18722 if !self
18723 .context_menu
18724 .borrow()
18725 .as_ref()
18726 .is_some_and(|context_menu| context_menu.focused(window, cx))
18727 {
18728 self.hide_context_menu(window, cx);
18729 }
18730 self.discard_inline_completion(false, cx);
18731 cx.emit(EditorEvent::Blurred);
18732 cx.notify();
18733 }
18734
18735 pub fn register_action<A: Action>(
18736 &mut self,
18737 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
18738 ) -> Subscription {
18739 let id = self.next_editor_action_id.post_inc();
18740 let listener = Arc::new(listener);
18741 self.editor_actions.borrow_mut().insert(
18742 id,
18743 Box::new(move |window, _| {
18744 let listener = listener.clone();
18745 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
18746 let action = action.downcast_ref().unwrap();
18747 if phase == DispatchPhase::Bubble {
18748 listener(action, window, cx)
18749 }
18750 })
18751 }),
18752 );
18753
18754 let editor_actions = self.editor_actions.clone();
18755 Subscription::new(move || {
18756 editor_actions.borrow_mut().remove(&id);
18757 })
18758 }
18759
18760 pub fn file_header_size(&self) -> u32 {
18761 FILE_HEADER_HEIGHT
18762 }
18763
18764 pub fn restore(
18765 &mut self,
18766 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
18767 window: &mut Window,
18768 cx: &mut Context<Self>,
18769 ) {
18770 let workspace = self.workspace();
18771 let project = self.project.as_ref();
18772 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
18773 let mut tasks = Vec::new();
18774 for (buffer_id, changes) in revert_changes {
18775 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
18776 buffer.update(cx, |buffer, cx| {
18777 buffer.edit(
18778 changes
18779 .into_iter()
18780 .map(|(range, text)| (range, text.to_string())),
18781 None,
18782 cx,
18783 );
18784 });
18785
18786 if let Some(project) =
18787 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
18788 {
18789 project.update(cx, |project, cx| {
18790 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
18791 })
18792 }
18793 }
18794 }
18795 tasks
18796 });
18797 cx.spawn_in(window, async move |_, cx| {
18798 for (buffer, task) in save_tasks {
18799 let result = task.await;
18800 if result.is_err() {
18801 let Some(path) = buffer
18802 .read_with(cx, |buffer, cx| buffer.project_path(cx))
18803 .ok()
18804 else {
18805 continue;
18806 };
18807 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
18808 let Some(task) = cx
18809 .update_window_entity(&workspace, |workspace, window, cx| {
18810 workspace
18811 .open_path_preview(path, None, false, false, false, window, cx)
18812 })
18813 .ok()
18814 else {
18815 continue;
18816 };
18817 task.await.log_err();
18818 }
18819 }
18820 }
18821 })
18822 .detach();
18823 self.change_selections(None, window, cx, |selections| selections.refresh());
18824 }
18825
18826 pub fn to_pixel_point(
18827 &self,
18828 source: multi_buffer::Anchor,
18829 editor_snapshot: &EditorSnapshot,
18830 window: &mut Window,
18831 ) -> Option<gpui::Point<Pixels>> {
18832 let source_point = source.to_display_point(editor_snapshot);
18833 self.display_to_pixel_point(source_point, editor_snapshot, window)
18834 }
18835
18836 pub fn display_to_pixel_point(
18837 &self,
18838 source: DisplayPoint,
18839 editor_snapshot: &EditorSnapshot,
18840 window: &mut Window,
18841 ) -> Option<gpui::Point<Pixels>> {
18842 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
18843 let text_layout_details = self.text_layout_details(window);
18844 let scroll_top = text_layout_details
18845 .scroll_anchor
18846 .scroll_position(editor_snapshot)
18847 .y;
18848
18849 if source.row().as_f32() < scroll_top.floor() {
18850 return None;
18851 }
18852 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
18853 let source_y = line_height * (source.row().as_f32() - scroll_top);
18854 Some(gpui::Point::new(source_x, source_y))
18855 }
18856
18857 pub fn has_visible_completions_menu(&self) -> bool {
18858 !self.edit_prediction_preview_is_active()
18859 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
18860 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
18861 })
18862 }
18863
18864 pub fn register_addon<T: Addon>(&mut self, instance: T) {
18865 if self.mode.is_minimap() {
18866 return;
18867 }
18868 self.addons
18869 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
18870 }
18871
18872 pub fn unregister_addon<T: Addon>(&mut self) {
18873 self.addons.remove(&std::any::TypeId::of::<T>());
18874 }
18875
18876 pub fn addon<T: Addon>(&self) -> Option<&T> {
18877 let type_id = std::any::TypeId::of::<T>();
18878 self.addons
18879 .get(&type_id)
18880 .and_then(|item| item.to_any().downcast_ref::<T>())
18881 }
18882
18883 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
18884 let type_id = std::any::TypeId::of::<T>();
18885 self.addons
18886 .get_mut(&type_id)
18887 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
18888 }
18889
18890 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
18891 let text_layout_details = self.text_layout_details(window);
18892 let style = &text_layout_details.editor_style;
18893 let font_id = window.text_system().resolve_font(&style.text.font());
18894 let font_size = style.text.font_size.to_pixels(window.rem_size());
18895 let line_height = style.text.line_height_in_pixels(window.rem_size());
18896 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
18897
18898 gpui::Size::new(em_width, line_height)
18899 }
18900
18901 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
18902 self.load_diff_task.clone()
18903 }
18904
18905 fn read_metadata_from_db(
18906 &mut self,
18907 item_id: u64,
18908 workspace_id: WorkspaceId,
18909 window: &mut Window,
18910 cx: &mut Context<Editor>,
18911 ) {
18912 if self.is_singleton(cx)
18913 && !self.mode.is_minimap()
18914 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
18915 {
18916 let buffer_snapshot = OnceCell::new();
18917
18918 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
18919 if !folds.is_empty() {
18920 let snapshot =
18921 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18922 self.fold_ranges(
18923 folds
18924 .into_iter()
18925 .map(|(start, end)| {
18926 snapshot.clip_offset(start, Bias::Left)
18927 ..snapshot.clip_offset(end, Bias::Right)
18928 })
18929 .collect(),
18930 false,
18931 window,
18932 cx,
18933 );
18934 }
18935 }
18936
18937 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
18938 if !selections.is_empty() {
18939 let snapshot =
18940 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18941 self.change_selections(None, window, cx, |s| {
18942 s.select_ranges(selections.into_iter().map(|(start, end)| {
18943 snapshot.clip_offset(start, Bias::Left)
18944 ..snapshot.clip_offset(end, Bias::Right)
18945 }));
18946 });
18947 }
18948 };
18949 }
18950
18951 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
18952 }
18953}
18954
18955fn vim_enabled(cx: &App) -> bool {
18956 cx.global::<SettingsStore>()
18957 .raw_user_settings()
18958 .get("vim_mode")
18959 == Some(&serde_json::Value::Bool(true))
18960}
18961
18962// Consider user intent and default settings
18963fn choose_completion_range(
18964 completion: &Completion,
18965 intent: CompletionIntent,
18966 buffer: &Entity<Buffer>,
18967 cx: &mut Context<Editor>,
18968) -> Range<usize> {
18969 fn should_replace(
18970 completion: &Completion,
18971 insert_range: &Range<text::Anchor>,
18972 intent: CompletionIntent,
18973 completion_mode_setting: LspInsertMode,
18974 buffer: &Buffer,
18975 ) -> bool {
18976 // specific actions take precedence over settings
18977 match intent {
18978 CompletionIntent::CompleteWithInsert => return false,
18979 CompletionIntent::CompleteWithReplace => return true,
18980 CompletionIntent::Complete | CompletionIntent::Compose => {}
18981 }
18982
18983 match completion_mode_setting {
18984 LspInsertMode::Insert => false,
18985 LspInsertMode::Replace => true,
18986 LspInsertMode::ReplaceSubsequence => {
18987 let mut text_to_replace = buffer.chars_for_range(
18988 buffer.anchor_before(completion.replace_range.start)
18989 ..buffer.anchor_after(completion.replace_range.end),
18990 );
18991 let mut completion_text = completion.new_text.chars();
18992
18993 // is `text_to_replace` a subsequence of `completion_text`
18994 text_to_replace
18995 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
18996 }
18997 LspInsertMode::ReplaceSuffix => {
18998 let range_after_cursor = insert_range.end..completion.replace_range.end;
18999
19000 let text_after_cursor = buffer
19001 .text_for_range(
19002 buffer.anchor_before(range_after_cursor.start)
19003 ..buffer.anchor_after(range_after_cursor.end),
19004 )
19005 .collect::<String>();
19006 completion.new_text.ends_with(&text_after_cursor)
19007 }
19008 }
19009 }
19010
19011 let buffer = buffer.read(cx);
19012
19013 if let CompletionSource::Lsp {
19014 insert_range: Some(insert_range),
19015 ..
19016 } = &completion.source
19017 {
19018 let completion_mode_setting =
19019 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
19020 .completions
19021 .lsp_insert_mode;
19022
19023 if !should_replace(
19024 completion,
19025 &insert_range,
19026 intent,
19027 completion_mode_setting,
19028 buffer,
19029 ) {
19030 return insert_range.to_offset(buffer);
19031 }
19032 }
19033
19034 completion.replace_range.to_offset(buffer)
19035}
19036
19037fn insert_extra_newline_brackets(
19038 buffer: &MultiBufferSnapshot,
19039 range: Range<usize>,
19040 language: &language::LanguageScope,
19041) -> bool {
19042 let leading_whitespace_len = buffer
19043 .reversed_chars_at(range.start)
19044 .take_while(|c| c.is_whitespace() && *c != '\n')
19045 .map(|c| c.len_utf8())
19046 .sum::<usize>();
19047 let trailing_whitespace_len = buffer
19048 .chars_at(range.end)
19049 .take_while(|c| c.is_whitespace() && *c != '\n')
19050 .map(|c| c.len_utf8())
19051 .sum::<usize>();
19052 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
19053
19054 language.brackets().any(|(pair, enabled)| {
19055 let pair_start = pair.start.trim_end();
19056 let pair_end = pair.end.trim_start();
19057
19058 enabled
19059 && pair.newline
19060 && buffer.contains_str_at(range.end, pair_end)
19061 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
19062 })
19063}
19064
19065fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
19066 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
19067 [(buffer, range, _)] => (*buffer, range.clone()),
19068 _ => return false,
19069 };
19070 let pair = {
19071 let mut result: Option<BracketMatch> = None;
19072
19073 for pair in buffer
19074 .all_bracket_ranges(range.clone())
19075 .filter(move |pair| {
19076 pair.open_range.start <= range.start && pair.close_range.end >= range.end
19077 })
19078 {
19079 let len = pair.close_range.end - pair.open_range.start;
19080
19081 if let Some(existing) = &result {
19082 let existing_len = existing.close_range.end - existing.open_range.start;
19083 if len > existing_len {
19084 continue;
19085 }
19086 }
19087
19088 result = Some(pair);
19089 }
19090
19091 result
19092 };
19093 let Some(pair) = pair else {
19094 return false;
19095 };
19096 pair.newline_only
19097 && buffer
19098 .chars_for_range(pair.open_range.end..range.start)
19099 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
19100 .all(|c| c.is_whitespace() && c != '\n')
19101}
19102
19103fn update_uncommitted_diff_for_buffer(
19104 editor: Entity<Editor>,
19105 project: &Entity<Project>,
19106 buffers: impl IntoIterator<Item = Entity<Buffer>>,
19107 buffer: Entity<MultiBuffer>,
19108 cx: &mut App,
19109) -> Task<()> {
19110 let mut tasks = Vec::new();
19111 project.update(cx, |project, cx| {
19112 for buffer in buffers {
19113 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
19114 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
19115 }
19116 }
19117 });
19118 cx.spawn(async move |cx| {
19119 let diffs = future::join_all(tasks).await;
19120 if editor
19121 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19122 .unwrap_or(false)
19123 {
19124 return;
19125 }
19126
19127 buffer
19128 .update(cx, |buffer, cx| {
19129 for diff in diffs.into_iter().flatten() {
19130 buffer.add_diff(diff, cx);
19131 }
19132 })
19133 .ok();
19134 })
19135}
19136
19137fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
19138 let tab_size = tab_size.get() as usize;
19139 let mut width = offset;
19140
19141 for ch in text.chars() {
19142 width += if ch == '\t' {
19143 tab_size - (width % tab_size)
19144 } else {
19145 1
19146 };
19147 }
19148
19149 width - offset
19150}
19151
19152#[cfg(test)]
19153mod tests {
19154 use super::*;
19155
19156 #[test]
19157 fn test_string_size_with_expanded_tabs() {
19158 let nz = |val| NonZeroU32::new(val).unwrap();
19159 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
19160 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
19161 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
19162 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
19163 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
19164 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
19165 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
19166 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
19167 }
19168}
19169
19170/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
19171struct WordBreakingTokenizer<'a> {
19172 input: &'a str,
19173}
19174
19175impl<'a> WordBreakingTokenizer<'a> {
19176 fn new(input: &'a str) -> Self {
19177 Self { input }
19178 }
19179}
19180
19181fn is_char_ideographic(ch: char) -> bool {
19182 use unicode_script::Script::*;
19183 use unicode_script::UnicodeScript;
19184 matches!(ch.script(), Han | Tangut | Yi)
19185}
19186
19187fn is_grapheme_ideographic(text: &str) -> bool {
19188 text.chars().any(is_char_ideographic)
19189}
19190
19191fn is_grapheme_whitespace(text: &str) -> bool {
19192 text.chars().any(|x| x.is_whitespace())
19193}
19194
19195fn should_stay_with_preceding_ideograph(text: &str) -> bool {
19196 text.chars().next().map_or(false, |ch| {
19197 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
19198 })
19199}
19200
19201#[derive(PartialEq, Eq, Debug, Clone, Copy)]
19202enum WordBreakToken<'a> {
19203 Word { token: &'a str, grapheme_len: usize },
19204 InlineWhitespace { token: &'a str, grapheme_len: usize },
19205 Newline,
19206}
19207
19208impl<'a> Iterator for WordBreakingTokenizer<'a> {
19209 /// Yields a span, the count of graphemes in the token, and whether it was
19210 /// whitespace. Note that it also breaks at word boundaries.
19211 type Item = WordBreakToken<'a>;
19212
19213 fn next(&mut self) -> Option<Self::Item> {
19214 use unicode_segmentation::UnicodeSegmentation;
19215 if self.input.is_empty() {
19216 return None;
19217 }
19218
19219 let mut iter = self.input.graphemes(true).peekable();
19220 let mut offset = 0;
19221 let mut grapheme_len = 0;
19222 if let Some(first_grapheme) = iter.next() {
19223 let is_newline = first_grapheme == "\n";
19224 let is_whitespace = is_grapheme_whitespace(first_grapheme);
19225 offset += first_grapheme.len();
19226 grapheme_len += 1;
19227 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
19228 if let Some(grapheme) = iter.peek().copied() {
19229 if should_stay_with_preceding_ideograph(grapheme) {
19230 offset += grapheme.len();
19231 grapheme_len += 1;
19232 }
19233 }
19234 } else {
19235 let mut words = self.input[offset..].split_word_bound_indices().peekable();
19236 let mut next_word_bound = words.peek().copied();
19237 if next_word_bound.map_or(false, |(i, _)| i == 0) {
19238 next_word_bound = words.next();
19239 }
19240 while let Some(grapheme) = iter.peek().copied() {
19241 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19242 break;
19243 };
19244 if is_grapheme_whitespace(grapheme) != is_whitespace
19245 || (grapheme == "\n") != is_newline
19246 {
19247 break;
19248 };
19249 offset += grapheme.len();
19250 grapheme_len += 1;
19251 iter.next();
19252 }
19253 }
19254 let token = &self.input[..offset];
19255 self.input = &self.input[offset..];
19256 if token == "\n" {
19257 Some(WordBreakToken::Newline)
19258 } else if is_whitespace {
19259 Some(WordBreakToken::InlineWhitespace {
19260 token,
19261 grapheme_len,
19262 })
19263 } else {
19264 Some(WordBreakToken::Word {
19265 token,
19266 grapheme_len,
19267 })
19268 }
19269 } else {
19270 None
19271 }
19272 }
19273}
19274
19275#[test]
19276fn test_word_breaking_tokenizer() {
19277 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
19278 ("", &[]),
19279 (" ", &[whitespace(" ", 2)]),
19280 ("Ʒ", &[word("Ʒ", 1)]),
19281 ("Ǽ", &[word("Ǽ", 1)]),
19282 ("⋑", &[word("⋑", 1)]),
19283 ("⋑⋑", &[word("⋑⋑", 2)]),
19284 (
19285 "原理,进而",
19286 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
19287 ),
19288 (
19289 "hello world",
19290 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
19291 ),
19292 (
19293 "hello, world",
19294 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
19295 ),
19296 (
19297 " hello world",
19298 &[
19299 whitespace(" ", 2),
19300 word("hello", 5),
19301 whitespace(" ", 1),
19302 word("world", 5),
19303 ],
19304 ),
19305 (
19306 "这是什么 \n 钢笔",
19307 &[
19308 word("这", 1),
19309 word("是", 1),
19310 word("什", 1),
19311 word("么", 1),
19312 whitespace(" ", 1),
19313 newline(),
19314 whitespace(" ", 1),
19315 word("钢", 1),
19316 word("笔", 1),
19317 ],
19318 ),
19319 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
19320 ];
19321
19322 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19323 WordBreakToken::Word {
19324 token,
19325 grapheme_len,
19326 }
19327 }
19328
19329 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19330 WordBreakToken::InlineWhitespace {
19331 token,
19332 grapheme_len,
19333 }
19334 }
19335
19336 fn newline() -> WordBreakToken<'static> {
19337 WordBreakToken::Newline
19338 }
19339
19340 for (input, result) in tests {
19341 assert_eq!(
19342 WordBreakingTokenizer::new(input)
19343 .collect::<Vec<_>>()
19344 .as_slice(),
19345 *result,
19346 );
19347 }
19348}
19349
19350fn wrap_with_prefix(
19351 line_prefix: String,
19352 unwrapped_text: String,
19353 wrap_column: usize,
19354 tab_size: NonZeroU32,
19355 preserve_existing_whitespace: bool,
19356) -> String {
19357 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
19358 let mut wrapped_text = String::new();
19359 let mut current_line = line_prefix.clone();
19360
19361 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
19362 let mut current_line_len = line_prefix_len;
19363 let mut in_whitespace = false;
19364 for token in tokenizer {
19365 let have_preceding_whitespace = in_whitespace;
19366 match token {
19367 WordBreakToken::Word {
19368 token,
19369 grapheme_len,
19370 } => {
19371 in_whitespace = false;
19372 if current_line_len + grapheme_len > wrap_column
19373 && current_line_len != line_prefix_len
19374 {
19375 wrapped_text.push_str(current_line.trim_end());
19376 wrapped_text.push('\n');
19377 current_line.truncate(line_prefix.len());
19378 current_line_len = line_prefix_len;
19379 }
19380 current_line.push_str(token);
19381 current_line_len += grapheme_len;
19382 }
19383 WordBreakToken::InlineWhitespace {
19384 mut token,
19385 mut grapheme_len,
19386 } => {
19387 in_whitespace = true;
19388 if have_preceding_whitespace && !preserve_existing_whitespace {
19389 continue;
19390 }
19391 if !preserve_existing_whitespace {
19392 token = " ";
19393 grapheme_len = 1;
19394 }
19395 if current_line_len + grapheme_len > wrap_column {
19396 wrapped_text.push_str(current_line.trim_end());
19397 wrapped_text.push('\n');
19398 current_line.truncate(line_prefix.len());
19399 current_line_len = line_prefix_len;
19400 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
19401 current_line.push_str(token);
19402 current_line_len += grapheme_len;
19403 }
19404 }
19405 WordBreakToken::Newline => {
19406 in_whitespace = true;
19407 if preserve_existing_whitespace {
19408 wrapped_text.push_str(current_line.trim_end());
19409 wrapped_text.push('\n');
19410 current_line.truncate(line_prefix.len());
19411 current_line_len = line_prefix_len;
19412 } else if have_preceding_whitespace {
19413 continue;
19414 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
19415 {
19416 wrapped_text.push_str(current_line.trim_end());
19417 wrapped_text.push('\n');
19418 current_line.truncate(line_prefix.len());
19419 current_line_len = line_prefix_len;
19420 } else if current_line_len != line_prefix_len {
19421 current_line.push(' ');
19422 current_line_len += 1;
19423 }
19424 }
19425 }
19426 }
19427
19428 if !current_line.is_empty() {
19429 wrapped_text.push_str(¤t_line);
19430 }
19431 wrapped_text
19432}
19433
19434#[test]
19435fn test_wrap_with_prefix() {
19436 assert_eq!(
19437 wrap_with_prefix(
19438 "# ".to_string(),
19439 "abcdefg".to_string(),
19440 4,
19441 NonZeroU32::new(4).unwrap(),
19442 false,
19443 ),
19444 "# abcdefg"
19445 );
19446 assert_eq!(
19447 wrap_with_prefix(
19448 "".to_string(),
19449 "\thello world".to_string(),
19450 8,
19451 NonZeroU32::new(4).unwrap(),
19452 false,
19453 ),
19454 "hello\nworld"
19455 );
19456 assert_eq!(
19457 wrap_with_prefix(
19458 "// ".to_string(),
19459 "xx \nyy zz aa bb cc".to_string(),
19460 12,
19461 NonZeroU32::new(4).unwrap(),
19462 false,
19463 ),
19464 "// xx yy zz\n// aa bb cc"
19465 );
19466 assert_eq!(
19467 wrap_with_prefix(
19468 String::new(),
19469 "这是什么 \n 钢笔".to_string(),
19470 3,
19471 NonZeroU32::new(4).unwrap(),
19472 false,
19473 ),
19474 "这是什\n么 钢\n笔"
19475 );
19476}
19477
19478pub trait CollaborationHub {
19479 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
19480 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
19481 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
19482}
19483
19484impl CollaborationHub for Entity<Project> {
19485 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
19486 self.read(cx).collaborators()
19487 }
19488
19489 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
19490 self.read(cx).user_store().read(cx).participant_indices()
19491 }
19492
19493 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
19494 let this = self.read(cx);
19495 let user_ids = this.collaborators().values().map(|c| c.user_id);
19496 this.user_store().read_with(cx, |user_store, cx| {
19497 user_store.participant_names(user_ids, cx)
19498 })
19499 }
19500}
19501
19502pub trait SemanticsProvider {
19503 fn hover(
19504 &self,
19505 buffer: &Entity<Buffer>,
19506 position: text::Anchor,
19507 cx: &mut App,
19508 ) -> Option<Task<Vec<project::Hover>>>;
19509
19510 fn inline_values(
19511 &self,
19512 buffer_handle: Entity<Buffer>,
19513 range: Range<text::Anchor>,
19514 cx: &mut App,
19515 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19516
19517 fn inlay_hints(
19518 &self,
19519 buffer_handle: Entity<Buffer>,
19520 range: Range<text::Anchor>,
19521 cx: &mut App,
19522 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19523
19524 fn resolve_inlay_hint(
19525 &self,
19526 hint: InlayHint,
19527 buffer_handle: Entity<Buffer>,
19528 server_id: LanguageServerId,
19529 cx: &mut App,
19530 ) -> Option<Task<anyhow::Result<InlayHint>>>;
19531
19532 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
19533
19534 fn document_highlights(
19535 &self,
19536 buffer: &Entity<Buffer>,
19537 position: text::Anchor,
19538 cx: &mut App,
19539 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
19540
19541 fn definitions(
19542 &self,
19543 buffer: &Entity<Buffer>,
19544 position: text::Anchor,
19545 kind: GotoDefinitionKind,
19546 cx: &mut App,
19547 ) -> Option<Task<Result<Vec<LocationLink>>>>;
19548
19549 fn range_for_rename(
19550 &self,
19551 buffer: &Entity<Buffer>,
19552 position: text::Anchor,
19553 cx: &mut App,
19554 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
19555
19556 fn perform_rename(
19557 &self,
19558 buffer: &Entity<Buffer>,
19559 position: text::Anchor,
19560 new_name: String,
19561 cx: &mut App,
19562 ) -> Option<Task<Result<ProjectTransaction>>>;
19563}
19564
19565pub trait CompletionProvider {
19566 fn completions(
19567 &self,
19568 excerpt_id: ExcerptId,
19569 buffer: &Entity<Buffer>,
19570 buffer_position: text::Anchor,
19571 trigger: CompletionContext,
19572 window: &mut Window,
19573 cx: &mut Context<Editor>,
19574 ) -> Task<Result<Option<Vec<Completion>>>>;
19575
19576 fn resolve_completions(
19577 &self,
19578 buffer: Entity<Buffer>,
19579 completion_indices: Vec<usize>,
19580 completions: Rc<RefCell<Box<[Completion]>>>,
19581 cx: &mut Context<Editor>,
19582 ) -> Task<Result<bool>>;
19583
19584 fn apply_additional_edits_for_completion(
19585 &self,
19586 _buffer: Entity<Buffer>,
19587 _completions: Rc<RefCell<Box<[Completion]>>>,
19588 _completion_index: usize,
19589 _push_to_history: bool,
19590 _cx: &mut Context<Editor>,
19591 ) -> Task<Result<Option<language::Transaction>>> {
19592 Task::ready(Ok(None))
19593 }
19594
19595 fn is_completion_trigger(
19596 &self,
19597 buffer: &Entity<Buffer>,
19598 position: language::Anchor,
19599 text: &str,
19600 trigger_in_words: bool,
19601 cx: &mut Context<Editor>,
19602 ) -> bool;
19603
19604 fn sort_completions(&self) -> bool {
19605 true
19606 }
19607
19608 fn filter_completions(&self) -> bool {
19609 true
19610 }
19611}
19612
19613pub trait CodeActionProvider {
19614 fn id(&self) -> Arc<str>;
19615
19616 fn code_actions(
19617 &self,
19618 buffer: &Entity<Buffer>,
19619 range: Range<text::Anchor>,
19620 window: &mut Window,
19621 cx: &mut App,
19622 ) -> Task<Result<Vec<CodeAction>>>;
19623
19624 fn apply_code_action(
19625 &self,
19626 buffer_handle: Entity<Buffer>,
19627 action: CodeAction,
19628 excerpt_id: ExcerptId,
19629 push_to_history: bool,
19630 window: &mut Window,
19631 cx: &mut App,
19632 ) -> Task<Result<ProjectTransaction>>;
19633}
19634
19635impl CodeActionProvider for Entity<Project> {
19636 fn id(&self) -> Arc<str> {
19637 "project".into()
19638 }
19639
19640 fn code_actions(
19641 &self,
19642 buffer: &Entity<Buffer>,
19643 range: Range<text::Anchor>,
19644 _window: &mut Window,
19645 cx: &mut App,
19646 ) -> Task<Result<Vec<CodeAction>>> {
19647 self.update(cx, |project, cx| {
19648 let code_lens = project.code_lens(buffer, range.clone(), cx);
19649 let code_actions = project.code_actions(buffer, range, None, cx);
19650 cx.background_spawn(async move {
19651 let (code_lens, code_actions) = join(code_lens, code_actions).await;
19652 Ok(code_lens
19653 .context("code lens fetch")?
19654 .into_iter()
19655 .chain(code_actions.context("code action fetch")?)
19656 .collect())
19657 })
19658 })
19659 }
19660
19661 fn apply_code_action(
19662 &self,
19663 buffer_handle: Entity<Buffer>,
19664 action: CodeAction,
19665 _excerpt_id: ExcerptId,
19666 push_to_history: bool,
19667 _window: &mut Window,
19668 cx: &mut App,
19669 ) -> Task<Result<ProjectTransaction>> {
19670 self.update(cx, |project, cx| {
19671 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19672 })
19673 }
19674}
19675
19676fn snippet_completions(
19677 project: &Project,
19678 buffer: &Entity<Buffer>,
19679 buffer_position: text::Anchor,
19680 cx: &mut App,
19681) -> Task<Result<Vec<Completion>>> {
19682 let languages = buffer.read(cx).languages_at(buffer_position);
19683 let snippet_store = project.snippets().read(cx);
19684
19685 let scopes: Vec<_> = languages
19686 .iter()
19687 .filter_map(|language| {
19688 let language_name = language.lsp_id();
19689 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19690
19691 if snippets.is_empty() {
19692 None
19693 } else {
19694 Some((language.default_scope(), snippets))
19695 }
19696 })
19697 .collect();
19698
19699 if scopes.is_empty() {
19700 return Task::ready(Ok(vec![]));
19701 }
19702
19703 let snapshot = buffer.read(cx).text_snapshot();
19704 let chars: String = snapshot
19705 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19706 .collect();
19707 let executor = cx.background_executor().clone();
19708
19709 cx.background_spawn(async move {
19710 let mut all_results: Vec<Completion> = Vec::new();
19711 for (scope, snippets) in scopes.into_iter() {
19712 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19713 let mut last_word = chars
19714 .chars()
19715 .take_while(|c| classifier.is_word(*c))
19716 .collect::<String>();
19717 last_word = last_word.chars().rev().collect();
19718
19719 if last_word.is_empty() {
19720 return Ok(vec![]);
19721 }
19722
19723 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
19724 let to_lsp = |point: &text::Anchor| {
19725 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
19726 point_to_lsp(end)
19727 };
19728 let lsp_end = to_lsp(&buffer_position);
19729
19730 let candidates = snippets
19731 .iter()
19732 .enumerate()
19733 .flat_map(|(ix, snippet)| {
19734 snippet
19735 .prefix
19736 .iter()
19737 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
19738 })
19739 .collect::<Vec<StringMatchCandidate>>();
19740
19741 let mut matches = fuzzy::match_strings(
19742 &candidates,
19743 &last_word,
19744 last_word.chars().any(|c| c.is_uppercase()),
19745 100,
19746 &Default::default(),
19747 executor.clone(),
19748 )
19749 .await;
19750
19751 // Remove all candidates where the query's start does not match the start of any word in the candidate
19752 if let Some(query_start) = last_word.chars().next() {
19753 matches.retain(|string_match| {
19754 split_words(&string_match.string).any(|word| {
19755 // Check that the first codepoint of the word as lowercase matches the first
19756 // codepoint of the query as lowercase
19757 word.chars()
19758 .flat_map(|codepoint| codepoint.to_lowercase())
19759 .zip(query_start.to_lowercase())
19760 .all(|(word_cp, query_cp)| word_cp == query_cp)
19761 })
19762 });
19763 }
19764
19765 let matched_strings = matches
19766 .into_iter()
19767 .map(|m| m.string)
19768 .collect::<HashSet<_>>();
19769
19770 let mut result: Vec<Completion> = snippets
19771 .iter()
19772 .filter_map(|snippet| {
19773 let matching_prefix = snippet
19774 .prefix
19775 .iter()
19776 .find(|prefix| matched_strings.contains(*prefix))?;
19777 let start = as_offset - last_word.len();
19778 let start = snapshot.anchor_before(start);
19779 let range = start..buffer_position;
19780 let lsp_start = to_lsp(&start);
19781 let lsp_range = lsp::Range {
19782 start: lsp_start,
19783 end: lsp_end,
19784 };
19785 Some(Completion {
19786 replace_range: range,
19787 new_text: snippet.body.clone(),
19788 source: CompletionSource::Lsp {
19789 insert_range: None,
19790 server_id: LanguageServerId(usize::MAX),
19791 resolved: true,
19792 lsp_completion: Box::new(lsp::CompletionItem {
19793 label: snippet.prefix.first().unwrap().clone(),
19794 kind: Some(CompletionItemKind::SNIPPET),
19795 label_details: snippet.description.as_ref().map(|description| {
19796 lsp::CompletionItemLabelDetails {
19797 detail: Some(description.clone()),
19798 description: None,
19799 }
19800 }),
19801 insert_text_format: Some(InsertTextFormat::SNIPPET),
19802 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19803 lsp::InsertReplaceEdit {
19804 new_text: snippet.body.clone(),
19805 insert: lsp_range,
19806 replace: lsp_range,
19807 },
19808 )),
19809 filter_text: Some(snippet.body.clone()),
19810 sort_text: Some(char::MAX.to_string()),
19811 ..lsp::CompletionItem::default()
19812 }),
19813 lsp_defaults: None,
19814 },
19815 label: CodeLabel {
19816 text: matching_prefix.clone(),
19817 runs: Vec::new(),
19818 filter_range: 0..matching_prefix.len(),
19819 },
19820 icon_path: None,
19821 documentation: snippet.description.clone().map(|description| {
19822 CompletionDocumentation::SingleLine(description.into())
19823 }),
19824 insert_text_mode: None,
19825 confirm: None,
19826 })
19827 })
19828 .collect();
19829
19830 all_results.append(&mut result);
19831 }
19832
19833 Ok(all_results)
19834 })
19835}
19836
19837impl CompletionProvider for Entity<Project> {
19838 fn completions(
19839 &self,
19840 _excerpt_id: ExcerptId,
19841 buffer: &Entity<Buffer>,
19842 buffer_position: text::Anchor,
19843 options: CompletionContext,
19844 _window: &mut Window,
19845 cx: &mut Context<Editor>,
19846 ) -> Task<Result<Option<Vec<Completion>>>> {
19847 self.update(cx, |project, cx| {
19848 let snippets = snippet_completions(project, buffer, buffer_position, cx);
19849 let project_completions = project.completions(buffer, buffer_position, options, cx);
19850 cx.background_spawn(async move {
19851 let snippets_completions = snippets.await?;
19852 match project_completions.await? {
19853 Some(mut completions) => {
19854 completions.extend(snippets_completions);
19855 Ok(Some(completions))
19856 }
19857 None => {
19858 if snippets_completions.is_empty() {
19859 Ok(None)
19860 } else {
19861 Ok(Some(snippets_completions))
19862 }
19863 }
19864 }
19865 })
19866 })
19867 }
19868
19869 fn resolve_completions(
19870 &self,
19871 buffer: Entity<Buffer>,
19872 completion_indices: Vec<usize>,
19873 completions: Rc<RefCell<Box<[Completion]>>>,
19874 cx: &mut Context<Editor>,
19875 ) -> Task<Result<bool>> {
19876 self.update(cx, |project, cx| {
19877 project.lsp_store().update(cx, |lsp_store, cx| {
19878 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
19879 })
19880 })
19881 }
19882
19883 fn apply_additional_edits_for_completion(
19884 &self,
19885 buffer: Entity<Buffer>,
19886 completions: Rc<RefCell<Box<[Completion]>>>,
19887 completion_index: usize,
19888 push_to_history: bool,
19889 cx: &mut Context<Editor>,
19890 ) -> Task<Result<Option<language::Transaction>>> {
19891 self.update(cx, |project, cx| {
19892 project.lsp_store().update(cx, |lsp_store, cx| {
19893 lsp_store.apply_additional_edits_for_completion(
19894 buffer,
19895 completions,
19896 completion_index,
19897 push_to_history,
19898 cx,
19899 )
19900 })
19901 })
19902 }
19903
19904 fn is_completion_trigger(
19905 &self,
19906 buffer: &Entity<Buffer>,
19907 position: language::Anchor,
19908 text: &str,
19909 trigger_in_words: bool,
19910 cx: &mut Context<Editor>,
19911 ) -> bool {
19912 let mut chars = text.chars();
19913 let char = if let Some(char) = chars.next() {
19914 char
19915 } else {
19916 return false;
19917 };
19918 if chars.next().is_some() {
19919 return false;
19920 }
19921
19922 let buffer = buffer.read(cx);
19923 let snapshot = buffer.snapshot();
19924 if !snapshot.settings_at(position, cx).show_completions_on_input {
19925 return false;
19926 }
19927 let classifier = snapshot.char_classifier_at(position).for_completion(true);
19928 if trigger_in_words && classifier.is_word(char) {
19929 return true;
19930 }
19931
19932 buffer.completion_triggers().contains(text)
19933 }
19934}
19935
19936impl SemanticsProvider for Entity<Project> {
19937 fn hover(
19938 &self,
19939 buffer: &Entity<Buffer>,
19940 position: text::Anchor,
19941 cx: &mut App,
19942 ) -> Option<Task<Vec<project::Hover>>> {
19943 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
19944 }
19945
19946 fn document_highlights(
19947 &self,
19948 buffer: &Entity<Buffer>,
19949 position: text::Anchor,
19950 cx: &mut App,
19951 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
19952 Some(self.update(cx, |project, cx| {
19953 project.document_highlights(buffer, position, cx)
19954 }))
19955 }
19956
19957 fn definitions(
19958 &self,
19959 buffer: &Entity<Buffer>,
19960 position: text::Anchor,
19961 kind: GotoDefinitionKind,
19962 cx: &mut App,
19963 ) -> Option<Task<Result<Vec<LocationLink>>>> {
19964 Some(self.update(cx, |project, cx| match kind {
19965 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
19966 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
19967 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
19968 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
19969 }))
19970 }
19971
19972 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
19973 // TODO: make this work for remote projects
19974 self.update(cx, |project, cx| {
19975 if project
19976 .active_debug_session(cx)
19977 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
19978 {
19979 return true;
19980 }
19981
19982 buffer.update(cx, |buffer, cx| {
19983 project.any_language_server_supports_inlay_hints(buffer, cx)
19984 })
19985 })
19986 }
19987
19988 fn inline_values(
19989 &self,
19990 buffer_handle: Entity<Buffer>,
19991 range: Range<text::Anchor>,
19992 cx: &mut App,
19993 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
19994 self.update(cx, |project, cx| {
19995 let (session, active_stack_frame) = project.active_debug_session(cx)?;
19996
19997 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
19998 })
19999 }
20000
20001 fn inlay_hints(
20002 &self,
20003 buffer_handle: Entity<Buffer>,
20004 range: Range<text::Anchor>,
20005 cx: &mut App,
20006 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20007 Some(self.update(cx, |project, cx| {
20008 project.inlay_hints(buffer_handle, range, cx)
20009 }))
20010 }
20011
20012 fn resolve_inlay_hint(
20013 &self,
20014 hint: InlayHint,
20015 buffer_handle: Entity<Buffer>,
20016 server_id: LanguageServerId,
20017 cx: &mut App,
20018 ) -> Option<Task<anyhow::Result<InlayHint>>> {
20019 Some(self.update(cx, |project, cx| {
20020 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
20021 }))
20022 }
20023
20024 fn range_for_rename(
20025 &self,
20026 buffer: &Entity<Buffer>,
20027 position: text::Anchor,
20028 cx: &mut App,
20029 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
20030 Some(self.update(cx, |project, cx| {
20031 let buffer = buffer.clone();
20032 let task = project.prepare_rename(buffer.clone(), position, cx);
20033 cx.spawn(async move |_, cx| {
20034 Ok(match task.await? {
20035 PrepareRenameResponse::Success(range) => Some(range),
20036 PrepareRenameResponse::InvalidPosition => None,
20037 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
20038 // Fallback on using TreeSitter info to determine identifier range
20039 buffer.update(cx, |buffer, _| {
20040 let snapshot = buffer.snapshot();
20041 let (range, kind) = snapshot.surrounding_word(position);
20042 if kind != Some(CharKind::Word) {
20043 return None;
20044 }
20045 Some(
20046 snapshot.anchor_before(range.start)
20047 ..snapshot.anchor_after(range.end),
20048 )
20049 })?
20050 }
20051 })
20052 })
20053 }))
20054 }
20055
20056 fn perform_rename(
20057 &self,
20058 buffer: &Entity<Buffer>,
20059 position: text::Anchor,
20060 new_name: String,
20061 cx: &mut App,
20062 ) -> Option<Task<Result<ProjectTransaction>>> {
20063 Some(self.update(cx, |project, cx| {
20064 project.perform_rename(buffer.clone(), position, new_name, cx)
20065 }))
20066 }
20067}
20068
20069fn inlay_hint_settings(
20070 location: Anchor,
20071 snapshot: &MultiBufferSnapshot,
20072 cx: &mut Context<Editor>,
20073) -> InlayHintSettings {
20074 let file = snapshot.file_at(location);
20075 let language = snapshot.language_at(location).map(|l| l.name());
20076 language_settings(language, file, cx).inlay_hints
20077}
20078
20079fn consume_contiguous_rows(
20080 contiguous_row_selections: &mut Vec<Selection<Point>>,
20081 selection: &Selection<Point>,
20082 display_map: &DisplaySnapshot,
20083 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
20084) -> (MultiBufferRow, MultiBufferRow) {
20085 contiguous_row_selections.push(selection.clone());
20086 let start_row = MultiBufferRow(selection.start.row);
20087 let mut end_row = ending_row(selection, display_map);
20088
20089 while let Some(next_selection) = selections.peek() {
20090 if next_selection.start.row <= end_row.0 {
20091 end_row = ending_row(next_selection, display_map);
20092 contiguous_row_selections.push(selections.next().unwrap().clone());
20093 } else {
20094 break;
20095 }
20096 }
20097 (start_row, end_row)
20098}
20099
20100fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
20101 if next_selection.end.column > 0 || next_selection.is_empty() {
20102 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
20103 } else {
20104 MultiBufferRow(next_selection.end.row)
20105 }
20106}
20107
20108impl EditorSnapshot {
20109 pub fn remote_selections_in_range<'a>(
20110 &'a self,
20111 range: &'a Range<Anchor>,
20112 collaboration_hub: &dyn CollaborationHub,
20113 cx: &'a App,
20114 ) -> impl 'a + Iterator<Item = RemoteSelection> {
20115 let participant_names = collaboration_hub.user_names(cx);
20116 let participant_indices = collaboration_hub.user_participant_indices(cx);
20117 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20118 let collaborators_by_replica_id = collaborators_by_peer_id
20119 .iter()
20120 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
20121 .collect::<HashMap<_, _>>();
20122 self.buffer_snapshot
20123 .selections_in_range(range, false)
20124 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20125 if replica_id == AGENT_REPLICA_ID {
20126 Some(RemoteSelection {
20127 replica_id,
20128 selection,
20129 cursor_shape,
20130 line_mode,
20131 collaborator_id: CollaboratorId::Agent,
20132 user_name: Some("Agent".into()),
20133 color: cx.theme().players().agent(),
20134 })
20135 } else {
20136 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20137 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20138 let user_name = participant_names.get(&collaborator.user_id).cloned();
20139 Some(RemoteSelection {
20140 replica_id,
20141 selection,
20142 cursor_shape,
20143 line_mode,
20144 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20145 user_name,
20146 color: if let Some(index) = participant_index {
20147 cx.theme().players().color_for_participant(index.0)
20148 } else {
20149 cx.theme().players().absent()
20150 },
20151 })
20152 }
20153 })
20154 }
20155
20156 pub fn hunks_for_ranges(
20157 &self,
20158 ranges: impl IntoIterator<Item = Range<Point>>,
20159 ) -> Vec<MultiBufferDiffHunk> {
20160 let mut hunks = Vec::new();
20161 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20162 HashMap::default();
20163 for query_range in ranges {
20164 let query_rows =
20165 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20166 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20167 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20168 ) {
20169 // Include deleted hunks that are adjacent to the query range, because
20170 // otherwise they would be missed.
20171 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20172 if hunk.status().is_deleted() {
20173 intersects_range |= hunk.row_range.start == query_rows.end;
20174 intersects_range |= hunk.row_range.end == query_rows.start;
20175 }
20176 if intersects_range {
20177 if !processed_buffer_rows
20178 .entry(hunk.buffer_id)
20179 .or_default()
20180 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20181 {
20182 continue;
20183 }
20184 hunks.push(hunk);
20185 }
20186 }
20187 }
20188
20189 hunks
20190 }
20191
20192 fn display_diff_hunks_for_rows<'a>(
20193 &'a self,
20194 display_rows: Range<DisplayRow>,
20195 folded_buffers: &'a HashSet<BufferId>,
20196 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20197 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20198 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20199
20200 self.buffer_snapshot
20201 .diff_hunks_in_range(buffer_start..buffer_end)
20202 .filter_map(|hunk| {
20203 if folded_buffers.contains(&hunk.buffer_id) {
20204 return None;
20205 }
20206
20207 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20208 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20209
20210 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20211 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20212
20213 let display_hunk = if hunk_display_start.column() != 0 {
20214 DisplayDiffHunk::Folded {
20215 display_row: hunk_display_start.row(),
20216 }
20217 } else {
20218 let mut end_row = hunk_display_end.row();
20219 if hunk_display_end.column() > 0 {
20220 end_row.0 += 1;
20221 }
20222 let is_created_file = hunk.is_created_file();
20223 DisplayDiffHunk::Unfolded {
20224 status: hunk.status(),
20225 diff_base_byte_range: hunk.diff_base_byte_range,
20226 display_row_range: hunk_display_start.row()..end_row,
20227 multi_buffer_range: Anchor::range_in_buffer(
20228 hunk.excerpt_id,
20229 hunk.buffer_id,
20230 hunk.buffer_range,
20231 ),
20232 is_created_file,
20233 }
20234 };
20235
20236 Some(display_hunk)
20237 })
20238 }
20239
20240 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20241 self.display_snapshot.buffer_snapshot.language_at(position)
20242 }
20243
20244 pub fn is_focused(&self) -> bool {
20245 self.is_focused
20246 }
20247
20248 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20249 self.placeholder_text.as_ref()
20250 }
20251
20252 pub fn scroll_position(&self) -> gpui::Point<f32> {
20253 self.scroll_anchor.scroll_position(&self.display_snapshot)
20254 }
20255
20256 fn gutter_dimensions(
20257 &self,
20258 font_id: FontId,
20259 font_size: Pixels,
20260 max_line_number_width: Pixels,
20261 cx: &App,
20262 ) -> Option<GutterDimensions> {
20263 if !self.show_gutter {
20264 return None;
20265 }
20266
20267 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20268 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20269
20270 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20271 matches!(
20272 ProjectSettings::get_global(cx).git.git_gutter,
20273 Some(GitGutterSetting::TrackedFiles)
20274 )
20275 });
20276 let gutter_settings = EditorSettings::get_global(cx).gutter;
20277 let show_line_numbers = self
20278 .show_line_numbers
20279 .unwrap_or(gutter_settings.line_numbers);
20280 let line_gutter_width = if show_line_numbers {
20281 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20282 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20283 max_line_number_width.max(min_width_for_number_on_gutter)
20284 } else {
20285 0.0.into()
20286 };
20287
20288 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20289 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20290
20291 let git_blame_entries_width =
20292 self.git_blame_gutter_max_author_length
20293 .map(|max_author_length| {
20294 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20295 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20296
20297 /// The number of characters to dedicate to gaps and margins.
20298 const SPACING_WIDTH: usize = 4;
20299
20300 let max_char_count = max_author_length.min(renderer.max_author_length())
20301 + ::git::SHORT_SHA_LENGTH
20302 + MAX_RELATIVE_TIMESTAMP.len()
20303 + SPACING_WIDTH;
20304
20305 em_advance * max_char_count
20306 });
20307
20308 let is_singleton = self.buffer_snapshot.is_singleton();
20309
20310 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
20311 left_padding += if !is_singleton {
20312 em_width * 4.0
20313 } else if show_runnables || show_breakpoints {
20314 em_width * 3.0
20315 } else if show_git_gutter && show_line_numbers {
20316 em_width * 2.0
20317 } else if show_git_gutter || show_line_numbers {
20318 em_width
20319 } else {
20320 px(0.)
20321 };
20322
20323 let shows_folds = is_singleton && gutter_settings.folds;
20324
20325 let right_padding = if shows_folds && show_line_numbers {
20326 em_width * 4.0
20327 } else if shows_folds || (!is_singleton && show_line_numbers) {
20328 em_width * 3.0
20329 } else if show_line_numbers {
20330 em_width
20331 } else {
20332 px(0.)
20333 };
20334
20335 Some(GutterDimensions {
20336 left_padding,
20337 right_padding,
20338 width: line_gutter_width + left_padding + right_padding,
20339 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
20340 git_blame_entries_width,
20341 })
20342 }
20343
20344 pub fn render_crease_toggle(
20345 &self,
20346 buffer_row: MultiBufferRow,
20347 row_contains_cursor: bool,
20348 editor: Entity<Editor>,
20349 window: &mut Window,
20350 cx: &mut App,
20351 ) -> Option<AnyElement> {
20352 let folded = self.is_line_folded(buffer_row);
20353 let mut is_foldable = false;
20354
20355 if let Some(crease) = self
20356 .crease_snapshot
20357 .query_row(buffer_row, &self.buffer_snapshot)
20358 {
20359 is_foldable = true;
20360 match crease {
20361 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20362 if let Some(render_toggle) = render_toggle {
20363 let toggle_callback =
20364 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20365 if folded {
20366 editor.update(cx, |editor, cx| {
20367 editor.fold_at(buffer_row, window, cx)
20368 });
20369 } else {
20370 editor.update(cx, |editor, cx| {
20371 editor.unfold_at(buffer_row, window, cx)
20372 });
20373 }
20374 });
20375 return Some((render_toggle)(
20376 buffer_row,
20377 folded,
20378 toggle_callback,
20379 window,
20380 cx,
20381 ));
20382 }
20383 }
20384 }
20385 }
20386
20387 is_foldable |= self.starts_indent(buffer_row);
20388
20389 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20390 Some(
20391 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20392 .toggle_state(folded)
20393 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20394 if folded {
20395 this.unfold_at(buffer_row, window, cx);
20396 } else {
20397 this.fold_at(buffer_row, window, cx);
20398 }
20399 }))
20400 .into_any_element(),
20401 )
20402 } else {
20403 None
20404 }
20405 }
20406
20407 pub fn render_crease_trailer(
20408 &self,
20409 buffer_row: MultiBufferRow,
20410 window: &mut Window,
20411 cx: &mut App,
20412 ) -> Option<AnyElement> {
20413 let folded = self.is_line_folded(buffer_row);
20414 if let Crease::Inline { render_trailer, .. } = self
20415 .crease_snapshot
20416 .query_row(buffer_row, &self.buffer_snapshot)?
20417 {
20418 let render_trailer = render_trailer.as_ref()?;
20419 Some(render_trailer(buffer_row, folded, window, cx))
20420 } else {
20421 None
20422 }
20423 }
20424}
20425
20426impl Deref for EditorSnapshot {
20427 type Target = DisplaySnapshot;
20428
20429 fn deref(&self) -> &Self::Target {
20430 &self.display_snapshot
20431 }
20432}
20433
20434#[derive(Clone, Debug, PartialEq, Eq)]
20435pub enum EditorEvent {
20436 InputIgnored {
20437 text: Arc<str>,
20438 },
20439 InputHandled {
20440 utf16_range_to_replace: Option<Range<isize>>,
20441 text: Arc<str>,
20442 },
20443 ExcerptsAdded {
20444 buffer: Entity<Buffer>,
20445 predecessor: ExcerptId,
20446 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
20447 },
20448 ExcerptsRemoved {
20449 ids: Vec<ExcerptId>,
20450 removed_buffer_ids: Vec<BufferId>,
20451 },
20452 BufferFoldToggled {
20453 ids: Vec<ExcerptId>,
20454 folded: bool,
20455 },
20456 ExcerptsEdited {
20457 ids: Vec<ExcerptId>,
20458 },
20459 ExcerptsExpanded {
20460 ids: Vec<ExcerptId>,
20461 },
20462 BufferEdited,
20463 Edited {
20464 transaction_id: clock::Lamport,
20465 },
20466 Reparsed(BufferId),
20467 Focused,
20468 FocusedIn,
20469 Blurred,
20470 DirtyChanged,
20471 Saved,
20472 TitleChanged,
20473 DiffBaseChanged,
20474 SelectionsChanged {
20475 local: bool,
20476 },
20477 ScrollPositionChanged {
20478 local: bool,
20479 autoscroll: bool,
20480 },
20481 Closed,
20482 TransactionUndone {
20483 transaction_id: clock::Lamport,
20484 },
20485 TransactionBegun {
20486 transaction_id: clock::Lamport,
20487 },
20488 Reloaded,
20489 CursorShapeChanged,
20490 PushedToNavHistory {
20491 anchor: Anchor,
20492 is_deactivate: bool,
20493 },
20494}
20495
20496impl EventEmitter<EditorEvent> for Editor {}
20497
20498impl Focusable for Editor {
20499 fn focus_handle(&self, _cx: &App) -> FocusHandle {
20500 self.focus_handle.clone()
20501 }
20502}
20503
20504impl Render for Editor {
20505 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20506 let settings = ThemeSettings::get_global(cx);
20507
20508 let mut text_style = match self.mode {
20509 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
20510 color: cx.theme().colors().editor_foreground,
20511 font_family: settings.ui_font.family.clone(),
20512 font_features: settings.ui_font.features.clone(),
20513 font_fallbacks: settings.ui_font.fallbacks.clone(),
20514 font_size: rems(0.875).into(),
20515 font_weight: settings.ui_font.weight,
20516 line_height: relative(settings.buffer_line_height.value()),
20517 ..Default::default()
20518 },
20519 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
20520 color: cx.theme().colors().editor_foreground,
20521 font_family: settings.buffer_font.family.clone(),
20522 font_features: settings.buffer_font.features.clone(),
20523 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20524 font_size: settings.buffer_font_size(cx).into(),
20525 font_weight: settings.buffer_font.weight,
20526 line_height: relative(settings.buffer_line_height.value()),
20527 ..Default::default()
20528 },
20529 };
20530 if let Some(text_style_refinement) = &self.text_style_refinement {
20531 text_style.refine(text_style_refinement)
20532 }
20533
20534 let background = match self.mode {
20535 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
20536 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
20537 EditorMode::Full { .. } => cx.theme().colors().editor_background,
20538 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
20539 };
20540
20541 EditorElement::new(
20542 &cx.entity(),
20543 EditorStyle {
20544 background,
20545 local_player: cx.theme().players().local(),
20546 text: text_style,
20547 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
20548 syntax: cx.theme().syntax().clone(),
20549 status: cx.theme().status().clone(),
20550 inlay_hints_style: make_inlay_hints_style(cx),
20551 inline_completion_styles: make_suggestion_styles(cx),
20552 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
20553 show_underlines: !self.mode.is_minimap(),
20554 },
20555 )
20556 }
20557}
20558
20559impl EntityInputHandler for Editor {
20560 fn text_for_range(
20561 &mut self,
20562 range_utf16: Range<usize>,
20563 adjusted_range: &mut Option<Range<usize>>,
20564 _: &mut Window,
20565 cx: &mut Context<Self>,
20566 ) -> Option<String> {
20567 let snapshot = self.buffer.read(cx).read(cx);
20568 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
20569 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
20570 if (start.0..end.0) != range_utf16 {
20571 adjusted_range.replace(start.0..end.0);
20572 }
20573 Some(snapshot.text_for_range(start..end).collect())
20574 }
20575
20576 fn selected_text_range(
20577 &mut self,
20578 ignore_disabled_input: bool,
20579 _: &mut Window,
20580 cx: &mut Context<Self>,
20581 ) -> Option<UTF16Selection> {
20582 // Prevent the IME menu from appearing when holding down an alphabetic key
20583 // while input is disabled.
20584 if !ignore_disabled_input && !self.input_enabled {
20585 return None;
20586 }
20587
20588 let selection = self.selections.newest::<OffsetUtf16>(cx);
20589 let range = selection.range();
20590
20591 Some(UTF16Selection {
20592 range: range.start.0..range.end.0,
20593 reversed: selection.reversed,
20594 })
20595 }
20596
20597 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
20598 let snapshot = self.buffer.read(cx).read(cx);
20599 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
20600 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
20601 }
20602
20603 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20604 self.clear_highlights::<InputComposition>(cx);
20605 self.ime_transaction.take();
20606 }
20607
20608 fn replace_text_in_range(
20609 &mut self,
20610 range_utf16: Option<Range<usize>>,
20611 text: &str,
20612 window: &mut Window,
20613 cx: &mut Context<Self>,
20614 ) {
20615 if !self.input_enabled {
20616 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20617 return;
20618 }
20619
20620 self.transact(window, cx, |this, window, cx| {
20621 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
20622 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20623 Some(this.selection_replacement_ranges(range_utf16, cx))
20624 } else {
20625 this.marked_text_ranges(cx)
20626 };
20627
20628 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
20629 let newest_selection_id = this.selections.newest_anchor().id;
20630 this.selections
20631 .all::<OffsetUtf16>(cx)
20632 .iter()
20633 .zip(ranges_to_replace.iter())
20634 .find_map(|(selection, range)| {
20635 if selection.id == newest_selection_id {
20636 Some(
20637 (range.start.0 as isize - selection.head().0 as isize)
20638 ..(range.end.0 as isize - selection.head().0 as isize),
20639 )
20640 } else {
20641 None
20642 }
20643 })
20644 });
20645
20646 cx.emit(EditorEvent::InputHandled {
20647 utf16_range_to_replace: range_to_replace,
20648 text: text.into(),
20649 });
20650
20651 if let Some(new_selected_ranges) = new_selected_ranges {
20652 this.change_selections(None, window, cx, |selections| {
20653 selections.select_ranges(new_selected_ranges)
20654 });
20655 this.backspace(&Default::default(), window, cx);
20656 }
20657
20658 this.handle_input(text, window, cx);
20659 });
20660
20661 if let Some(transaction) = self.ime_transaction {
20662 self.buffer.update(cx, |buffer, cx| {
20663 buffer.group_until_transaction(transaction, cx);
20664 });
20665 }
20666
20667 self.unmark_text(window, cx);
20668 }
20669
20670 fn replace_and_mark_text_in_range(
20671 &mut self,
20672 range_utf16: Option<Range<usize>>,
20673 text: &str,
20674 new_selected_range_utf16: Option<Range<usize>>,
20675 window: &mut Window,
20676 cx: &mut Context<Self>,
20677 ) {
20678 if !self.input_enabled {
20679 return;
20680 }
20681
20682 let transaction = self.transact(window, cx, |this, window, cx| {
20683 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
20684 let snapshot = this.buffer.read(cx).read(cx);
20685 if let Some(relative_range_utf16) = range_utf16.as_ref() {
20686 for marked_range in &mut marked_ranges {
20687 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
20688 marked_range.start.0 += relative_range_utf16.start;
20689 marked_range.start =
20690 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
20691 marked_range.end =
20692 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
20693 }
20694 }
20695 Some(marked_ranges)
20696 } else if let Some(range_utf16) = range_utf16 {
20697 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20698 Some(this.selection_replacement_ranges(range_utf16, cx))
20699 } else {
20700 None
20701 };
20702
20703 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
20704 let newest_selection_id = this.selections.newest_anchor().id;
20705 this.selections
20706 .all::<OffsetUtf16>(cx)
20707 .iter()
20708 .zip(ranges_to_replace.iter())
20709 .find_map(|(selection, range)| {
20710 if selection.id == newest_selection_id {
20711 Some(
20712 (range.start.0 as isize - selection.head().0 as isize)
20713 ..(range.end.0 as isize - selection.head().0 as isize),
20714 )
20715 } else {
20716 None
20717 }
20718 })
20719 });
20720
20721 cx.emit(EditorEvent::InputHandled {
20722 utf16_range_to_replace: range_to_replace,
20723 text: text.into(),
20724 });
20725
20726 if let Some(ranges) = ranges_to_replace {
20727 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
20728 }
20729
20730 let marked_ranges = {
20731 let snapshot = this.buffer.read(cx).read(cx);
20732 this.selections
20733 .disjoint_anchors()
20734 .iter()
20735 .map(|selection| {
20736 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
20737 })
20738 .collect::<Vec<_>>()
20739 };
20740
20741 if text.is_empty() {
20742 this.unmark_text(window, cx);
20743 } else {
20744 this.highlight_text::<InputComposition>(
20745 marked_ranges.clone(),
20746 HighlightStyle {
20747 underline: Some(UnderlineStyle {
20748 thickness: px(1.),
20749 color: None,
20750 wavy: false,
20751 }),
20752 ..Default::default()
20753 },
20754 cx,
20755 );
20756 }
20757
20758 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
20759 let use_autoclose = this.use_autoclose;
20760 let use_auto_surround = this.use_auto_surround;
20761 this.set_use_autoclose(false);
20762 this.set_use_auto_surround(false);
20763 this.handle_input(text, window, cx);
20764 this.set_use_autoclose(use_autoclose);
20765 this.set_use_auto_surround(use_auto_surround);
20766
20767 if let Some(new_selected_range) = new_selected_range_utf16 {
20768 let snapshot = this.buffer.read(cx).read(cx);
20769 let new_selected_ranges = marked_ranges
20770 .into_iter()
20771 .map(|marked_range| {
20772 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
20773 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
20774 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
20775 snapshot.clip_offset_utf16(new_start, Bias::Left)
20776 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
20777 })
20778 .collect::<Vec<_>>();
20779
20780 drop(snapshot);
20781 this.change_selections(None, window, cx, |selections| {
20782 selections.select_ranges(new_selected_ranges)
20783 });
20784 }
20785 });
20786
20787 self.ime_transaction = self.ime_transaction.or(transaction);
20788 if let Some(transaction) = self.ime_transaction {
20789 self.buffer.update(cx, |buffer, cx| {
20790 buffer.group_until_transaction(transaction, cx);
20791 });
20792 }
20793
20794 if self.text_highlights::<InputComposition>(cx).is_none() {
20795 self.ime_transaction.take();
20796 }
20797 }
20798
20799 fn bounds_for_range(
20800 &mut self,
20801 range_utf16: Range<usize>,
20802 element_bounds: gpui::Bounds<Pixels>,
20803 window: &mut Window,
20804 cx: &mut Context<Self>,
20805 ) -> Option<gpui::Bounds<Pixels>> {
20806 let text_layout_details = self.text_layout_details(window);
20807 let gpui::Size {
20808 width: em_width,
20809 height: line_height,
20810 } = self.character_size(window);
20811
20812 let snapshot = self.snapshot(window, cx);
20813 let scroll_position = snapshot.scroll_position();
20814 let scroll_left = scroll_position.x * em_width;
20815
20816 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
20817 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
20818 + self.gutter_dimensions.width
20819 + self.gutter_dimensions.margin;
20820 let y = line_height * (start.row().as_f32() - scroll_position.y);
20821
20822 Some(Bounds {
20823 origin: element_bounds.origin + point(x, y),
20824 size: size(em_width, line_height),
20825 })
20826 }
20827
20828 fn character_index_for_point(
20829 &mut self,
20830 point: gpui::Point<Pixels>,
20831 _window: &mut Window,
20832 _cx: &mut Context<Self>,
20833 ) -> Option<usize> {
20834 let position_map = self.last_position_map.as_ref()?;
20835 if !position_map.text_hitbox.contains(&point) {
20836 return None;
20837 }
20838 let display_point = position_map.point_for_position(point).previous_valid;
20839 let anchor = position_map
20840 .snapshot
20841 .display_point_to_anchor(display_point, Bias::Left);
20842 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
20843 Some(utf16_offset.0)
20844 }
20845}
20846
20847trait SelectionExt {
20848 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
20849 fn spanned_rows(
20850 &self,
20851 include_end_if_at_line_start: bool,
20852 map: &DisplaySnapshot,
20853 ) -> Range<MultiBufferRow>;
20854}
20855
20856impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
20857 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
20858 let start = self
20859 .start
20860 .to_point(&map.buffer_snapshot)
20861 .to_display_point(map);
20862 let end = self
20863 .end
20864 .to_point(&map.buffer_snapshot)
20865 .to_display_point(map);
20866 if self.reversed {
20867 end..start
20868 } else {
20869 start..end
20870 }
20871 }
20872
20873 fn spanned_rows(
20874 &self,
20875 include_end_if_at_line_start: bool,
20876 map: &DisplaySnapshot,
20877 ) -> Range<MultiBufferRow> {
20878 let start = self.start.to_point(&map.buffer_snapshot);
20879 let mut end = self.end.to_point(&map.buffer_snapshot);
20880 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
20881 end.row -= 1;
20882 }
20883
20884 let buffer_start = map.prev_line_boundary(start).0;
20885 let buffer_end = map.next_line_boundary(end).0;
20886 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
20887 }
20888}
20889
20890impl<T: InvalidationRegion> InvalidationStack<T> {
20891 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
20892 where
20893 S: Clone + ToOffset,
20894 {
20895 while let Some(region) = self.last() {
20896 let all_selections_inside_invalidation_ranges =
20897 if selections.len() == region.ranges().len() {
20898 selections
20899 .iter()
20900 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
20901 .all(|(selection, invalidation_range)| {
20902 let head = selection.head().to_offset(buffer);
20903 invalidation_range.start <= head && invalidation_range.end >= head
20904 })
20905 } else {
20906 false
20907 };
20908
20909 if all_selections_inside_invalidation_ranges {
20910 break;
20911 } else {
20912 self.pop();
20913 }
20914 }
20915 }
20916}
20917
20918impl<T> Default for InvalidationStack<T> {
20919 fn default() -> Self {
20920 Self(Default::default())
20921 }
20922}
20923
20924impl<T> Deref for InvalidationStack<T> {
20925 type Target = Vec<T>;
20926
20927 fn deref(&self) -> &Self::Target {
20928 &self.0
20929 }
20930}
20931
20932impl<T> DerefMut for InvalidationStack<T> {
20933 fn deref_mut(&mut self) -> &mut Self::Target {
20934 &mut self.0
20935 }
20936}
20937
20938impl InvalidationRegion for SnippetState {
20939 fn ranges(&self) -> &[Range<Anchor>] {
20940 &self.ranges[self.active_index]
20941 }
20942}
20943
20944fn inline_completion_edit_text(
20945 current_snapshot: &BufferSnapshot,
20946 edits: &[(Range<Anchor>, String)],
20947 edit_preview: &EditPreview,
20948 include_deletions: bool,
20949 cx: &App,
20950) -> HighlightedText {
20951 let edits = edits
20952 .iter()
20953 .map(|(anchor, text)| {
20954 (
20955 anchor.start.text_anchor..anchor.end.text_anchor,
20956 text.clone(),
20957 )
20958 })
20959 .collect::<Vec<_>>();
20960
20961 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
20962}
20963
20964pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
20965 match severity {
20966 lsp::DiagnosticSeverity::ERROR => colors.error,
20967 lsp::DiagnosticSeverity::WARNING => colors.warning,
20968 lsp::DiagnosticSeverity::INFORMATION => colors.info,
20969 lsp::DiagnosticSeverity::HINT => colors.info,
20970 _ => colors.ignored,
20971 }
20972}
20973
20974pub fn styled_runs_for_code_label<'a>(
20975 label: &'a CodeLabel,
20976 syntax_theme: &'a theme::SyntaxTheme,
20977) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
20978 let fade_out = HighlightStyle {
20979 fade_out: Some(0.35),
20980 ..Default::default()
20981 };
20982
20983 let mut prev_end = label.filter_range.end;
20984 label
20985 .runs
20986 .iter()
20987 .enumerate()
20988 .flat_map(move |(ix, (range, highlight_id))| {
20989 let style = if let Some(style) = highlight_id.style(syntax_theme) {
20990 style
20991 } else {
20992 return Default::default();
20993 };
20994 let mut muted_style = style;
20995 muted_style.highlight(fade_out);
20996
20997 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
20998 if range.start >= label.filter_range.end {
20999 if range.start > prev_end {
21000 runs.push((prev_end..range.start, fade_out));
21001 }
21002 runs.push((range.clone(), muted_style));
21003 } else if range.end <= label.filter_range.end {
21004 runs.push((range.clone(), style));
21005 } else {
21006 runs.push((range.start..label.filter_range.end, style));
21007 runs.push((label.filter_range.end..range.end, muted_style));
21008 }
21009 prev_end = cmp::max(prev_end, range.end);
21010
21011 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
21012 runs.push((prev_end..label.text.len(), fade_out));
21013 }
21014
21015 runs
21016 })
21017}
21018
21019pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
21020 let mut prev_index = 0;
21021 let mut prev_codepoint: Option<char> = None;
21022 text.char_indices()
21023 .chain([(text.len(), '\0')])
21024 .filter_map(move |(index, codepoint)| {
21025 let prev_codepoint = prev_codepoint.replace(codepoint)?;
21026 let is_boundary = index == text.len()
21027 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
21028 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
21029 if is_boundary {
21030 let chunk = &text[prev_index..index];
21031 prev_index = index;
21032 Some(chunk)
21033 } else {
21034 None
21035 }
21036 })
21037}
21038
21039pub trait RangeToAnchorExt: Sized {
21040 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
21041
21042 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
21043 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
21044 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
21045 }
21046}
21047
21048impl<T: ToOffset> RangeToAnchorExt for Range<T> {
21049 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
21050 let start_offset = self.start.to_offset(snapshot);
21051 let end_offset = self.end.to_offset(snapshot);
21052 if start_offset == end_offset {
21053 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
21054 } else {
21055 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
21056 }
21057 }
21058}
21059
21060pub trait RowExt {
21061 fn as_f32(&self) -> f32;
21062
21063 fn next_row(&self) -> Self;
21064
21065 fn previous_row(&self) -> Self;
21066
21067 fn minus(&self, other: Self) -> u32;
21068}
21069
21070impl RowExt for DisplayRow {
21071 fn as_f32(&self) -> f32 {
21072 self.0 as f32
21073 }
21074
21075 fn next_row(&self) -> Self {
21076 Self(self.0 + 1)
21077 }
21078
21079 fn previous_row(&self) -> Self {
21080 Self(self.0.saturating_sub(1))
21081 }
21082
21083 fn minus(&self, other: Self) -> u32 {
21084 self.0 - other.0
21085 }
21086}
21087
21088impl RowExt for MultiBufferRow {
21089 fn as_f32(&self) -> f32 {
21090 self.0 as f32
21091 }
21092
21093 fn next_row(&self) -> Self {
21094 Self(self.0 + 1)
21095 }
21096
21097 fn previous_row(&self) -> Self {
21098 Self(self.0.saturating_sub(1))
21099 }
21100
21101 fn minus(&self, other: Self) -> u32 {
21102 self.0 - other.0
21103 }
21104}
21105
21106trait RowRangeExt {
21107 type Row;
21108
21109 fn len(&self) -> usize;
21110
21111 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
21112}
21113
21114impl RowRangeExt for Range<MultiBufferRow> {
21115 type Row = MultiBufferRow;
21116
21117 fn len(&self) -> usize {
21118 (self.end.0 - self.start.0) as usize
21119 }
21120
21121 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21122 (self.start.0..self.end.0).map(MultiBufferRow)
21123 }
21124}
21125
21126impl RowRangeExt for Range<DisplayRow> {
21127 type Row = DisplayRow;
21128
21129 fn len(&self) -> usize {
21130 (self.end.0 - self.start.0) as usize
21131 }
21132
21133 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21134 (self.start.0..self.end.0).map(DisplayRow)
21135 }
21136}
21137
21138/// If select range has more than one line, we
21139/// just point the cursor to range.start.
21140fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21141 if range.start.row == range.end.row {
21142 range
21143 } else {
21144 range.start..range.start
21145 }
21146}
21147pub struct KillRing(ClipboardItem);
21148impl Global for KillRing {}
21149
21150const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21151
21152enum BreakpointPromptEditAction {
21153 Log,
21154 Condition,
21155 HitCondition,
21156}
21157
21158struct BreakpointPromptEditor {
21159 pub(crate) prompt: Entity<Editor>,
21160 editor: WeakEntity<Editor>,
21161 breakpoint_anchor: Anchor,
21162 breakpoint: Breakpoint,
21163 edit_action: BreakpointPromptEditAction,
21164 block_ids: HashSet<CustomBlockId>,
21165 editor_margins: Arc<Mutex<EditorMargins>>,
21166 _subscriptions: Vec<Subscription>,
21167}
21168
21169impl BreakpointPromptEditor {
21170 const MAX_LINES: u8 = 4;
21171
21172 fn new(
21173 editor: WeakEntity<Editor>,
21174 breakpoint_anchor: Anchor,
21175 breakpoint: Breakpoint,
21176 edit_action: BreakpointPromptEditAction,
21177 window: &mut Window,
21178 cx: &mut Context<Self>,
21179 ) -> Self {
21180 let base_text = match edit_action {
21181 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21182 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21183 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21184 }
21185 .map(|msg| msg.to_string())
21186 .unwrap_or_default();
21187
21188 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21189 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21190
21191 let prompt = cx.new(|cx| {
21192 let mut prompt = Editor::new(
21193 EditorMode::AutoHeight {
21194 max_lines: Self::MAX_LINES as usize,
21195 },
21196 buffer,
21197 None,
21198 window,
21199 cx,
21200 );
21201 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21202 prompt.set_show_cursor_when_unfocused(false, cx);
21203 prompt.set_placeholder_text(
21204 match edit_action {
21205 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21206 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21207 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21208 },
21209 cx,
21210 );
21211
21212 prompt
21213 });
21214
21215 Self {
21216 prompt,
21217 editor,
21218 breakpoint_anchor,
21219 breakpoint,
21220 edit_action,
21221 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21222 block_ids: Default::default(),
21223 _subscriptions: vec![],
21224 }
21225 }
21226
21227 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21228 self.block_ids.extend(block_ids)
21229 }
21230
21231 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21232 if let Some(editor) = self.editor.upgrade() {
21233 let message = self
21234 .prompt
21235 .read(cx)
21236 .buffer
21237 .read(cx)
21238 .as_singleton()
21239 .expect("A multi buffer in breakpoint prompt isn't possible")
21240 .read(cx)
21241 .as_rope()
21242 .to_string();
21243
21244 editor.update(cx, |editor, cx| {
21245 editor.edit_breakpoint_at_anchor(
21246 self.breakpoint_anchor,
21247 self.breakpoint.clone(),
21248 match self.edit_action {
21249 BreakpointPromptEditAction::Log => {
21250 BreakpointEditAction::EditLogMessage(message.into())
21251 }
21252 BreakpointPromptEditAction::Condition => {
21253 BreakpointEditAction::EditCondition(message.into())
21254 }
21255 BreakpointPromptEditAction::HitCondition => {
21256 BreakpointEditAction::EditHitCondition(message.into())
21257 }
21258 },
21259 cx,
21260 );
21261
21262 editor.remove_blocks(self.block_ids.clone(), None, cx);
21263 cx.focus_self(window);
21264 });
21265 }
21266 }
21267
21268 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21269 self.editor
21270 .update(cx, |editor, cx| {
21271 editor.remove_blocks(self.block_ids.clone(), None, cx);
21272 window.focus(&editor.focus_handle);
21273 })
21274 .log_err();
21275 }
21276
21277 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21278 let settings = ThemeSettings::get_global(cx);
21279 let text_style = TextStyle {
21280 color: if self.prompt.read(cx).read_only(cx) {
21281 cx.theme().colors().text_disabled
21282 } else {
21283 cx.theme().colors().text
21284 },
21285 font_family: settings.buffer_font.family.clone(),
21286 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21287 font_size: settings.buffer_font_size(cx).into(),
21288 font_weight: settings.buffer_font.weight,
21289 line_height: relative(settings.buffer_line_height.value()),
21290 ..Default::default()
21291 };
21292 EditorElement::new(
21293 &self.prompt,
21294 EditorStyle {
21295 background: cx.theme().colors().editor_background,
21296 local_player: cx.theme().players().local(),
21297 text: text_style,
21298 ..Default::default()
21299 },
21300 )
21301 }
21302}
21303
21304impl Render for BreakpointPromptEditor {
21305 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21306 let editor_margins = *self.editor_margins.lock();
21307 let gutter_dimensions = editor_margins.gutter;
21308 h_flex()
21309 .key_context("Editor")
21310 .bg(cx.theme().colors().editor_background)
21311 .border_y_1()
21312 .border_color(cx.theme().status().info_border)
21313 .size_full()
21314 .py(window.line_height() / 2.5)
21315 .on_action(cx.listener(Self::confirm))
21316 .on_action(cx.listener(Self::cancel))
21317 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21318 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21319 }
21320}
21321
21322impl Focusable for BreakpointPromptEditor {
21323 fn focus_handle(&self, cx: &App) -> FocusHandle {
21324 self.prompt.focus_handle(cx)
21325 }
21326}
21327
21328fn all_edits_insertions_or_deletions(
21329 edits: &Vec<(Range<Anchor>, String)>,
21330 snapshot: &MultiBufferSnapshot,
21331) -> bool {
21332 let mut all_insertions = true;
21333 let mut all_deletions = true;
21334
21335 for (range, new_text) in edits.iter() {
21336 let range_is_empty = range.to_offset(&snapshot).is_empty();
21337 let text_is_empty = new_text.is_empty();
21338
21339 if range_is_empty != text_is_empty {
21340 if range_is_empty {
21341 all_deletions = false;
21342 } else {
21343 all_insertions = false;
21344 }
21345 } else {
21346 return false;
21347 }
21348
21349 if !all_insertions && !all_deletions {
21350 return false;
21351 }
21352 }
21353 all_insertions || all_deletions
21354}
21355
21356struct MissingEditPredictionKeybindingTooltip;
21357
21358impl Render for MissingEditPredictionKeybindingTooltip {
21359 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21360 ui::tooltip_container(window, cx, |container, _, cx| {
21361 container
21362 .flex_shrink_0()
21363 .max_w_80()
21364 .min_h(rems_from_px(124.))
21365 .justify_between()
21366 .child(
21367 v_flex()
21368 .flex_1()
21369 .text_ui_sm(cx)
21370 .child(Label::new("Conflict with Accept Keybinding"))
21371 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21372 )
21373 .child(
21374 h_flex()
21375 .pb_1()
21376 .gap_1()
21377 .items_end()
21378 .w_full()
21379 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21380 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21381 }))
21382 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21383 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21384 })),
21385 )
21386 })
21387 }
21388}
21389
21390#[derive(Debug, Clone, Copy, PartialEq)]
21391pub struct LineHighlight {
21392 pub background: Background,
21393 pub border: Option<gpui::Hsla>,
21394 pub include_gutter: bool,
21395 pub type_id: Option<TypeId>,
21396}
21397
21398fn render_diff_hunk_controls(
21399 row: u32,
21400 status: &DiffHunkStatus,
21401 hunk_range: Range<Anchor>,
21402 is_created_file: bool,
21403 line_height: Pixels,
21404 editor: &Entity<Editor>,
21405 _window: &mut Window,
21406 cx: &mut App,
21407) -> AnyElement {
21408 h_flex()
21409 .h(line_height)
21410 .mr_1()
21411 .gap_1()
21412 .px_0p5()
21413 .pb_1()
21414 .border_x_1()
21415 .border_b_1()
21416 .border_color(cx.theme().colors().border_variant)
21417 .rounded_b_lg()
21418 .bg(cx.theme().colors().editor_background)
21419 .gap_1()
21420 .occlude()
21421 .shadow_md()
21422 .child(if status.has_secondary_hunk() {
21423 Button::new(("stage", row as u64), "Stage")
21424 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21425 .tooltip({
21426 let focus_handle = editor.focus_handle(cx);
21427 move |window, cx| {
21428 Tooltip::for_action_in(
21429 "Stage Hunk",
21430 &::git::ToggleStaged,
21431 &focus_handle,
21432 window,
21433 cx,
21434 )
21435 }
21436 })
21437 .on_click({
21438 let editor = editor.clone();
21439 move |_event, _window, cx| {
21440 editor.update(cx, |editor, cx| {
21441 editor.stage_or_unstage_diff_hunks(
21442 true,
21443 vec![hunk_range.start..hunk_range.start],
21444 cx,
21445 );
21446 });
21447 }
21448 })
21449 } else {
21450 Button::new(("unstage", row as u64), "Unstage")
21451 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21452 .tooltip({
21453 let focus_handle = editor.focus_handle(cx);
21454 move |window, cx| {
21455 Tooltip::for_action_in(
21456 "Unstage Hunk",
21457 &::git::ToggleStaged,
21458 &focus_handle,
21459 window,
21460 cx,
21461 )
21462 }
21463 })
21464 .on_click({
21465 let editor = editor.clone();
21466 move |_event, _window, cx| {
21467 editor.update(cx, |editor, cx| {
21468 editor.stage_or_unstage_diff_hunks(
21469 false,
21470 vec![hunk_range.start..hunk_range.start],
21471 cx,
21472 );
21473 });
21474 }
21475 })
21476 })
21477 .child(
21478 Button::new(("restore", row as u64), "Restore")
21479 .tooltip({
21480 let focus_handle = editor.focus_handle(cx);
21481 move |window, cx| {
21482 Tooltip::for_action_in(
21483 "Restore Hunk",
21484 &::git::Restore,
21485 &focus_handle,
21486 window,
21487 cx,
21488 )
21489 }
21490 })
21491 .on_click({
21492 let editor = editor.clone();
21493 move |_event, window, cx| {
21494 editor.update(cx, |editor, cx| {
21495 let snapshot = editor.snapshot(window, cx);
21496 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
21497 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
21498 });
21499 }
21500 })
21501 .disabled(is_created_file),
21502 )
21503 .when(
21504 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
21505 |el| {
21506 el.child(
21507 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
21508 .shape(IconButtonShape::Square)
21509 .icon_size(IconSize::Small)
21510 // .disabled(!has_multiple_hunks)
21511 .tooltip({
21512 let focus_handle = editor.focus_handle(cx);
21513 move |window, cx| {
21514 Tooltip::for_action_in(
21515 "Next Hunk",
21516 &GoToHunk,
21517 &focus_handle,
21518 window,
21519 cx,
21520 )
21521 }
21522 })
21523 .on_click({
21524 let editor = editor.clone();
21525 move |_event, window, cx| {
21526 editor.update(cx, |editor, cx| {
21527 let snapshot = editor.snapshot(window, cx);
21528 let position =
21529 hunk_range.end.to_point(&snapshot.buffer_snapshot);
21530 editor.go_to_hunk_before_or_after_position(
21531 &snapshot,
21532 position,
21533 Direction::Next,
21534 window,
21535 cx,
21536 );
21537 editor.expand_selected_diff_hunks(cx);
21538 });
21539 }
21540 }),
21541 )
21542 .child(
21543 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
21544 .shape(IconButtonShape::Square)
21545 .icon_size(IconSize::Small)
21546 // .disabled(!has_multiple_hunks)
21547 .tooltip({
21548 let focus_handle = editor.focus_handle(cx);
21549 move |window, cx| {
21550 Tooltip::for_action_in(
21551 "Previous Hunk",
21552 &GoToPreviousHunk,
21553 &focus_handle,
21554 window,
21555 cx,
21556 )
21557 }
21558 })
21559 .on_click({
21560 let editor = editor.clone();
21561 move |_event, window, cx| {
21562 editor.update(cx, |editor, cx| {
21563 let snapshot = editor.snapshot(window, cx);
21564 let point =
21565 hunk_range.start.to_point(&snapshot.buffer_snapshot);
21566 editor.go_to_hunk_before_or_after_position(
21567 &snapshot,
21568 point,
21569 Direction::Prev,
21570 window,
21571 cx,
21572 );
21573 editor.expand_selected_diff_hunks(cx);
21574 });
21575 }
21576 }),
21577 )
21578 },
21579 )
21580 .into_any_element()
21581}