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::ReplicaId;
60use collections::{BTreeMap, HashMap, HashSet, VecDeque};
61use convert_case::{Case, Casing};
62use display_map::*;
63pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
64use editor_settings::GoToDefinitionFallback;
65pub use editor_settings::{
66 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, SearchSettings,
67 ShowScrollbar,
68};
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::Restore;
82use code_context_menus::{
83 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
84 CompletionsMenu, ContextMenuOrigin,
85};
86use git::blame::{GitBlame, GlobalBlameRenderer};
87use gpui::{
88 Action, Animation, AnimationExt, AnyElement, AnyWeakEntity, App, AppContext,
89 AsyncWindowContext, AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry,
90 ClipboardItem, Context, DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter,
91 FocusHandle, FocusOutEvent, Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla,
92 KeyContext, Modifiers, MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render,
93 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
94 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
95 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
96};
97use highlight_matching_bracket::refresh_matching_bracket_highlights;
98use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
99pub use hover_popover::hover_markdown_style;
100use hover_popover::{HoverState, hide_hover};
101use indent_guides::ActiveIndentGuidesState;
102use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
103pub use inline_completion::Direction;
104use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
105pub use items::MAX_TAB_TITLE_LEN;
106use itertools::Itertools;
107use language::{
108 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
109 CursorShape, DiagnosticEntry, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText,
110 IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal, TextObject,
111 TransactionId, TreeSitterOptions, WordsQuery,
112 language_settings::{
113 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
114 all_language_settings, language_settings,
115 },
116 point_from_lsp, text_diff_with_options,
117};
118use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
119use linked_editing_ranges::refresh_linked_ranges;
120use mouse_context_menu::MouseContextMenu;
121use persistence::DB;
122use project::{
123 ProjectPath,
124 debugger::breakpoint_store::{
125 BreakpointEditAction, BreakpointState, BreakpointStore, BreakpointStoreEvent,
126 },
127};
128
129pub use git::blame::BlameRenderer;
130pub use proposed_changes_editor::{
131 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
132};
133use smallvec::smallvec;
134use std::{cell::OnceCell, iter::Peekable};
135use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
136
137pub use lsp::CompletionContext;
138use lsp::{
139 CodeActionKind, CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity,
140 InsertTextFormat, InsertTextMode, LanguageServerId, LanguageServerName,
141};
142
143use language::BufferSnapshot;
144pub use lsp_ext::lsp_tasks;
145use movement::TextLayoutDetails;
146pub use multi_buffer::{
147 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
148 RowInfo, ToOffset, ToPoint,
149};
150use multi_buffer::{
151 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
152 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
153};
154use parking_lot::Mutex;
155use project::{
156 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
157 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
158 TaskSourceKind,
159 debugger::breakpoint_store::Breakpoint,
160 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
161 project_settings::{GitGutterSetting, ProjectSettings},
162};
163use rand::prelude::*;
164use rpc::{ErrorExt, proto::*};
165use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
166use selections_collection::{
167 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
168};
169use serde::{Deserialize, Serialize};
170use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
171use smallvec::SmallVec;
172use snippet::Snippet;
173use std::sync::Arc;
174use std::{
175 any::TypeId,
176 borrow::Cow,
177 cell::RefCell,
178 cmp::{self, Ordering, Reverse},
179 mem,
180 num::NonZeroU32,
181 ops::{ControlFlow, Deref, DerefMut, Not as _, Range, RangeInclusive},
182 path::{Path, PathBuf},
183 rc::Rc,
184 time::{Duration, Instant},
185};
186pub use sum_tree::Bias;
187use sum_tree::TreeMap;
188use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
189use theme::{
190 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
191 observe_buffer_font_size_adjustment,
192};
193use ui::{
194 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
195 IconSize, Key, Tooltip, h_flex, prelude::*,
196};
197use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
198use workspace::{
199 Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
200 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
201 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
202 item::{ItemHandle, PreviewTabsSettings},
203 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
204 searchable::SearchEvent,
205};
206
207use crate::hover_links::{find_url, find_url_from_range};
208use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
209
210pub const FILE_HEADER_HEIGHT: u32 = 2;
211pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
212pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
213const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
214const MAX_LINE_LEN: usize = 1024;
215const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
216const MAX_SELECTION_HISTORY_LEN: usize = 1024;
217pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
218#[doc(hidden)]
219pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
220const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
221
222pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
223pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
224pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
225
226pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
227pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
228pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
229
230pub type RenderDiffHunkControlsFn = Arc<
231 dyn Fn(
232 u32,
233 &DiffHunkStatus,
234 Range<Anchor>,
235 bool,
236 Pixels,
237 &Entity<Editor>,
238 &mut Window,
239 &mut App,
240 ) -> AnyElement,
241>;
242
243const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
244 alt: true,
245 shift: true,
246 control: false,
247 platform: false,
248 function: false,
249};
250
251#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
252pub enum InlayId {
253 InlineCompletion(usize),
254 Hint(usize),
255}
256
257impl InlayId {
258 fn id(&self) -> usize {
259 match self {
260 Self::InlineCompletion(id) => *id,
261 Self::Hint(id) => *id,
262 }
263 }
264}
265
266pub enum DebugCurrentRowHighlight {}
267enum DocumentHighlightRead {}
268enum DocumentHighlightWrite {}
269enum InputComposition {}
270enum SelectedTextHighlight {}
271
272#[derive(Debug, Copy, Clone, PartialEq, Eq)]
273pub enum Navigated {
274 Yes,
275 No,
276}
277
278impl Navigated {
279 pub fn from_bool(yes: bool) -> Navigated {
280 if yes { Navigated::Yes } else { Navigated::No }
281 }
282}
283
284#[derive(Debug, Clone, PartialEq, Eq)]
285enum DisplayDiffHunk {
286 Folded {
287 display_row: DisplayRow,
288 },
289 Unfolded {
290 is_created_file: bool,
291 diff_base_byte_range: Range<usize>,
292 display_row_range: Range<DisplayRow>,
293 multi_buffer_range: Range<Anchor>,
294 status: DiffHunkStatus,
295 },
296}
297
298pub enum HideMouseCursorOrigin {
299 TypingAction,
300 MovementAction,
301}
302
303pub fn init_settings(cx: &mut App) {
304 EditorSettings::register(cx);
305}
306
307pub fn init(cx: &mut App) {
308 init_settings(cx);
309
310 cx.set_global(GlobalBlameRenderer(Arc::new(())));
311
312 workspace::register_project_item::<Editor>(cx);
313 workspace::FollowableViewRegistry::register::<Editor>(cx);
314 workspace::register_serializable_item::<Editor>(cx);
315
316 cx.observe_new(
317 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
318 workspace.register_action(Editor::new_file);
319 workspace.register_action(Editor::new_file_vertical);
320 workspace.register_action(Editor::new_file_horizontal);
321 workspace.register_action(Editor::cancel_language_server_work);
322 },
323 )
324 .detach();
325
326 cx.on_action(move |_: &workspace::NewFile, cx| {
327 let app_state = workspace::AppState::global(cx);
328 if let Some(app_state) = app_state.upgrade() {
329 workspace::open_new(
330 Default::default(),
331 app_state,
332 cx,
333 |workspace, window, cx| {
334 Editor::new_file(workspace, &Default::default(), window, cx)
335 },
336 )
337 .detach();
338 }
339 });
340 cx.on_action(move |_: &workspace::NewWindow, cx| {
341 let app_state = workspace::AppState::global(cx);
342 if let Some(app_state) = app_state.upgrade() {
343 workspace::open_new(
344 Default::default(),
345 app_state,
346 cx,
347 |workspace, window, cx| {
348 cx.activate(true);
349 Editor::new_file(workspace, &Default::default(), window, cx)
350 },
351 )
352 .detach();
353 }
354 });
355}
356
357pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
358 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
359}
360
361pub trait DiagnosticRenderer {
362 fn render_group(
363 &self,
364 diagnostic_group: Vec<DiagnosticEntry<Point>>,
365 buffer_id: BufferId,
366 snapshot: EditorSnapshot,
367 editor: WeakEntity<Editor>,
368 cx: &mut App,
369 ) -> Vec<BlockProperties<Anchor>>;
370}
371
372pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
373
374impl gpui::Global for GlobalDiagnosticRenderer {}
375pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
376 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
377}
378
379pub struct SearchWithinRange;
380
381trait InvalidationRegion {
382 fn ranges(&self) -> &[Range<Anchor>];
383}
384
385#[derive(Clone, Debug, PartialEq)]
386pub enum SelectPhase {
387 Begin {
388 position: DisplayPoint,
389 add: bool,
390 click_count: usize,
391 },
392 BeginColumnar {
393 position: DisplayPoint,
394 reset: bool,
395 goal_column: u32,
396 },
397 Extend {
398 position: DisplayPoint,
399 click_count: usize,
400 },
401 Update {
402 position: DisplayPoint,
403 goal_column: u32,
404 scroll_delta: gpui::Point<f32>,
405 },
406 End,
407}
408
409#[derive(Clone, Debug)]
410pub enum SelectMode {
411 Character,
412 Word(Range<Anchor>),
413 Line(Range<Anchor>),
414 All,
415}
416
417#[derive(Copy, Clone, PartialEq, Eq, Debug)]
418pub enum EditorMode {
419 SingleLine {
420 auto_width: bool,
421 },
422 AutoHeight {
423 max_lines: usize,
424 },
425 Full {
426 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
427 scale_ui_elements_with_buffer_font_size: bool,
428 /// When set to `true`, the editor will render a background for the active line.
429 show_active_line_background: bool,
430 /// When set to `true`, the editor's height will be determined by its content.
431 sized_by_content: bool,
432 },
433}
434
435impl EditorMode {
436 pub fn full() -> Self {
437 Self::Full {
438 scale_ui_elements_with_buffer_font_size: true,
439 show_active_line_background: true,
440 sized_by_content: false,
441 }
442 }
443
444 pub fn is_full(&self) -> bool {
445 matches!(self, Self::Full { .. })
446 }
447}
448
449#[derive(Copy, Clone, Debug)]
450pub enum SoftWrap {
451 /// Prefer not to wrap at all.
452 ///
453 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
454 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
455 GitDiff,
456 /// Prefer a single line generally, unless an overly long line is encountered.
457 None,
458 /// Soft wrap lines that exceed the editor width.
459 EditorWidth,
460 /// Soft wrap lines at the preferred line length.
461 Column(u32),
462 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
463 Bounded(u32),
464}
465
466#[derive(Clone)]
467pub struct EditorStyle {
468 pub background: Hsla,
469 pub local_player: PlayerColor,
470 pub text: TextStyle,
471 pub scrollbar_width: Pixels,
472 pub syntax: Arc<SyntaxTheme>,
473 pub status: StatusColors,
474 pub inlay_hints_style: HighlightStyle,
475 pub inline_completion_styles: InlineCompletionStyles,
476 pub unnecessary_code_fade: f32,
477}
478
479impl Default for EditorStyle {
480 fn default() -> Self {
481 Self {
482 background: Hsla::default(),
483 local_player: PlayerColor::default(),
484 text: TextStyle::default(),
485 scrollbar_width: Pixels::default(),
486 syntax: Default::default(),
487 // HACK: Status colors don't have a real default.
488 // We should look into removing the status colors from the editor
489 // style and retrieve them directly from the theme.
490 status: StatusColors::dark(),
491 inlay_hints_style: HighlightStyle::default(),
492 inline_completion_styles: InlineCompletionStyles {
493 insertion: HighlightStyle::default(),
494 whitespace: HighlightStyle::default(),
495 },
496 unnecessary_code_fade: Default::default(),
497 }
498 }
499}
500
501pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
502 let show_background = language_settings::language_settings(None, None, cx)
503 .inlay_hints
504 .show_background;
505
506 HighlightStyle {
507 color: Some(cx.theme().status().hint),
508 background_color: show_background.then(|| cx.theme().status().hint_background),
509 ..HighlightStyle::default()
510 }
511}
512
513pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
514 InlineCompletionStyles {
515 insertion: HighlightStyle {
516 color: Some(cx.theme().status().predictive),
517 ..HighlightStyle::default()
518 },
519 whitespace: HighlightStyle {
520 background_color: Some(cx.theme().status().created_background),
521 ..HighlightStyle::default()
522 },
523 }
524}
525
526type CompletionId = usize;
527
528pub(crate) enum EditDisplayMode {
529 TabAccept,
530 DiffPopover,
531 Inline,
532}
533
534enum InlineCompletion {
535 Edit {
536 edits: Vec<(Range<Anchor>, String)>,
537 edit_preview: Option<EditPreview>,
538 display_mode: EditDisplayMode,
539 snapshot: BufferSnapshot,
540 },
541 Move {
542 target: Anchor,
543 snapshot: BufferSnapshot,
544 },
545}
546
547struct InlineCompletionState {
548 inlay_ids: Vec<InlayId>,
549 completion: InlineCompletion,
550 completion_id: Option<SharedString>,
551 invalidation_range: Range<Anchor>,
552}
553
554enum EditPredictionSettings {
555 Disabled,
556 Enabled {
557 show_in_menu: bool,
558 preview_requires_modifier: bool,
559 },
560}
561
562enum InlineCompletionHighlight {}
563
564#[derive(Debug, Clone)]
565struct InlineDiagnostic {
566 message: SharedString,
567 group_id: usize,
568 is_primary: bool,
569 start: Point,
570 severity: DiagnosticSeverity,
571}
572
573pub enum MenuInlineCompletionsPolicy {
574 Never,
575 ByProvider,
576}
577
578pub enum EditPredictionPreview {
579 /// Modifier is not pressed
580 Inactive { released_too_fast: bool },
581 /// Modifier pressed
582 Active {
583 since: Instant,
584 previous_scroll_position: Option<ScrollAnchor>,
585 },
586}
587
588impl EditPredictionPreview {
589 pub fn released_too_fast(&self) -> bool {
590 match self {
591 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
592 EditPredictionPreview::Active { .. } => false,
593 }
594 }
595
596 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
597 if let EditPredictionPreview::Active {
598 previous_scroll_position,
599 ..
600 } = self
601 {
602 *previous_scroll_position = scroll_position;
603 }
604 }
605}
606
607pub struct ContextMenuOptions {
608 pub min_entries_visible: usize,
609 pub max_entries_visible: usize,
610 pub placement: Option<ContextMenuPlacement>,
611}
612
613#[derive(Debug, Clone, PartialEq, Eq)]
614pub enum ContextMenuPlacement {
615 Above,
616 Below,
617}
618
619#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
620struct EditorActionId(usize);
621
622impl EditorActionId {
623 pub fn post_inc(&mut self) -> Self {
624 let answer = self.0;
625
626 *self = Self(answer + 1);
627
628 Self(answer)
629 }
630}
631
632// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
633// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
634
635type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
636type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
637
638#[derive(Default)]
639struct ScrollbarMarkerState {
640 scrollbar_size: Size<Pixels>,
641 dirty: bool,
642 markers: Arc<[PaintQuad]>,
643 pending_refresh: Option<Task<Result<()>>>,
644}
645
646impl ScrollbarMarkerState {
647 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
648 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
649 }
650}
651
652#[derive(Clone, Debug)]
653struct RunnableTasks {
654 templates: Vec<(TaskSourceKind, TaskTemplate)>,
655 offset: multi_buffer::Anchor,
656 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
657 column: u32,
658 // Values of all named captures, including those starting with '_'
659 extra_variables: HashMap<String, String>,
660 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
661 context_range: Range<BufferOffset>,
662}
663
664impl RunnableTasks {
665 fn resolve<'a>(
666 &'a self,
667 cx: &'a task::TaskContext,
668 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
669 self.templates.iter().filter_map(|(kind, template)| {
670 template
671 .resolve_task(&kind.to_id_base(), cx)
672 .map(|task| (kind.clone(), task))
673 })
674 }
675}
676
677#[derive(Clone)]
678struct ResolvedTasks {
679 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
680 position: Anchor,
681}
682
683#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
684struct BufferOffset(usize);
685
686// Addons allow storing per-editor state in other crates (e.g. Vim)
687pub trait Addon: 'static {
688 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
689
690 fn render_buffer_header_controls(
691 &self,
692 _: &ExcerptInfo,
693 _: &Window,
694 _: &App,
695 ) -> Option<AnyElement> {
696 None
697 }
698
699 fn to_any(&self) -> &dyn std::any::Any;
700}
701
702/// A set of caret positions, registered when the editor was edited.
703pub struct ChangeList {
704 changes: Vec<Vec<Anchor>>,
705 /// Currently "selected" change.
706 position: Option<usize>,
707}
708
709impl ChangeList {
710 pub fn new() -> Self {
711 Self {
712 changes: Vec::new(),
713 position: None,
714 }
715 }
716
717 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
718 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
719 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
720 if self.changes.is_empty() {
721 return None;
722 }
723
724 let prev = self.position.unwrap_or(self.changes.len());
725 let next = if direction == Direction::Prev {
726 prev.saturating_sub(count)
727 } else {
728 (prev + count).min(self.changes.len() - 1)
729 };
730 self.position = Some(next);
731 self.changes.get(next).map(|anchors| anchors.as_slice())
732 }
733
734 /// Adds a new change to the list, resetting the change list position.
735 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
736 self.position.take();
737 if pop_state {
738 self.changes.pop();
739 }
740 self.changes.push(new_positions.clone());
741 }
742
743 pub fn last(&self) -> Option<&[Anchor]> {
744 self.changes.last().map(|anchors| anchors.as_slice())
745 }
746}
747
748/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
749///
750/// See the [module level documentation](self) for more information.
751pub struct Editor {
752 focus_handle: FocusHandle,
753 last_focused_descendant: Option<WeakFocusHandle>,
754 /// The text buffer being edited
755 buffer: Entity<MultiBuffer>,
756 /// Map of how text in the buffer should be displayed.
757 /// Handles soft wraps, folds, fake inlay text insertions, etc.
758 pub display_map: Entity<DisplayMap>,
759 pub selections: SelectionsCollection,
760 pub scroll_manager: ScrollManager,
761 /// When inline assist editors are linked, they all render cursors because
762 /// typing enters text into each of them, even the ones that aren't focused.
763 pub(crate) show_cursor_when_unfocused: bool,
764 columnar_selection_tail: Option<Anchor>,
765 add_selections_state: Option<AddSelectionsState>,
766 select_next_state: Option<SelectNextState>,
767 select_prev_state: Option<SelectNextState>,
768 selection_history: SelectionHistory,
769 autoclose_regions: Vec<AutocloseRegion>,
770 snippet_stack: InvalidationStack<SnippetState>,
771 select_syntax_node_history: SelectSyntaxNodeHistory,
772 ime_transaction: Option<TransactionId>,
773 active_diagnostics: ActiveDiagnostic,
774 show_inline_diagnostics: bool,
775 inline_diagnostics_update: Task<()>,
776 inline_diagnostics_enabled: bool,
777 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
778 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
779 hard_wrap: Option<usize>,
780
781 // TODO: make this a access method
782 pub project: Option<Entity<Project>>,
783 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
784 completion_provider: Option<Box<dyn CompletionProvider>>,
785 collaboration_hub: Option<Box<dyn CollaborationHub>>,
786 blink_manager: Entity<BlinkManager>,
787 show_cursor_names: bool,
788 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
789 pub show_local_selections: bool,
790 mode: EditorMode,
791 show_breadcrumbs: bool,
792 show_gutter: bool,
793 show_scrollbars: bool,
794 disable_scrolling: bool,
795 disable_expand_excerpt_buttons: bool,
796 show_line_numbers: Option<bool>,
797 use_relative_line_numbers: Option<bool>,
798 show_git_diff_gutter: Option<bool>,
799 show_code_actions: Option<bool>,
800 show_runnables: Option<bool>,
801 show_breakpoints: Option<bool>,
802 show_wrap_guides: Option<bool>,
803 show_indent_guides: Option<bool>,
804 placeholder_text: Option<Arc<str>>,
805 highlight_order: usize,
806 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
807 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
808 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
809 scrollbar_marker_state: ScrollbarMarkerState,
810 active_indent_guides_state: ActiveIndentGuidesState,
811 nav_history: Option<ItemNavHistory>,
812 context_menu: RefCell<Option<CodeContextMenu>>,
813 context_menu_options: Option<ContextMenuOptions>,
814 mouse_context_menu: Option<MouseContextMenu>,
815 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
816 signature_help_state: SignatureHelpState,
817 auto_signature_help: Option<bool>,
818 find_all_references_task_sources: Vec<Anchor>,
819 next_completion_id: CompletionId,
820 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
821 code_actions_task: Option<Task<Result<()>>>,
822 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
823 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
824 document_highlights_task: Option<Task<()>>,
825 linked_editing_range_task: Option<Task<Option<()>>>,
826 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
827 pending_rename: Option<RenameState>,
828 searchable: bool,
829 cursor_shape: CursorShape,
830 current_line_highlight: Option<CurrentLineHighlight>,
831 collapse_matches: bool,
832 autoindent_mode: Option<AutoindentMode>,
833 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
834 input_enabled: bool,
835 use_modal_editing: bool,
836 read_only: bool,
837 leader_peer_id: Option<PeerId>,
838 remote_id: Option<ViewId>,
839 hover_state: HoverState,
840 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
841 gutter_hovered: bool,
842 hovered_link_state: Option<HoveredLinkState>,
843 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
844 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
845 active_inline_completion: Option<InlineCompletionState>,
846 /// Used to prevent flickering as the user types while the menu is open
847 stale_inline_completion_in_menu: Option<InlineCompletionState>,
848 edit_prediction_settings: EditPredictionSettings,
849 inline_completions_hidden_for_vim_mode: bool,
850 show_inline_completions_override: Option<bool>,
851 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
852 edit_prediction_preview: EditPredictionPreview,
853 edit_prediction_indent_conflict: bool,
854 edit_prediction_requires_modifier_in_indent_conflict: bool,
855 inlay_hint_cache: InlayHintCache,
856 next_inlay_id: usize,
857 _subscriptions: Vec<Subscription>,
858 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
859 gutter_dimensions: GutterDimensions,
860 style: Option<EditorStyle>,
861 text_style_refinement: Option<TextStyleRefinement>,
862 next_editor_action_id: EditorActionId,
863 editor_actions:
864 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
865 use_autoclose: bool,
866 use_auto_surround: bool,
867 auto_replace_emoji_shortcode: bool,
868 jsx_tag_auto_close_enabled_in_any_buffer: bool,
869 show_git_blame_gutter: bool,
870 show_git_blame_inline: bool,
871 show_git_blame_inline_delay_task: Option<Task<()>>,
872 pub git_blame_inline_tooltip: Option<AnyWeakEntity>,
873 git_blame_inline_enabled: bool,
874 render_diff_hunk_controls: RenderDiffHunkControlsFn,
875 serialize_dirty_buffers: bool,
876 show_selection_menu: Option<bool>,
877 blame: Option<Entity<GitBlame>>,
878 blame_subscription: Option<Subscription>,
879 custom_context_menu: Option<
880 Box<
881 dyn 'static
882 + Fn(
883 &mut Self,
884 DisplayPoint,
885 &mut Window,
886 &mut Context<Self>,
887 ) -> Option<Entity<ui::ContextMenu>>,
888 >,
889 >,
890 last_bounds: Option<Bounds<Pixels>>,
891 last_position_map: Option<Rc<PositionMap>>,
892 expect_bounds_change: Option<Bounds<Pixels>>,
893 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
894 tasks_update_task: Option<Task<()>>,
895 breakpoint_store: Option<Entity<BreakpointStore>>,
896 /// Allow's a user to create a breakpoint by selecting this indicator
897 /// It should be None while a user is not hovering over the gutter
898 /// Otherwise it represents the point that the breakpoint will be shown
899 gutter_breakpoint_indicator: (Option<(DisplayPoint, bool)>, Option<Task<()>>),
900 in_project_search: bool,
901 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
902 breadcrumb_header: Option<String>,
903 focused_block: Option<FocusedBlock>,
904 next_scroll_position: NextScrollCursorCenterTopBottom,
905 addons: HashMap<TypeId, Box<dyn Addon>>,
906 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
907 load_diff_task: Option<Shared<Task<()>>>,
908 selection_mark_mode: bool,
909 toggle_fold_multiple_buffers: Task<()>,
910 _scroll_cursor_center_top_bottom_task: Task<()>,
911 serialize_selections: Task<()>,
912 serialize_folds: Task<()>,
913 mouse_cursor_hidden: bool,
914 hide_mouse_mode: HideMouseMode,
915 pub change_list: ChangeList,
916}
917
918#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
919enum NextScrollCursorCenterTopBottom {
920 #[default]
921 Center,
922 Top,
923 Bottom,
924}
925
926impl NextScrollCursorCenterTopBottom {
927 fn next(&self) -> Self {
928 match self {
929 Self::Center => Self::Top,
930 Self::Top => Self::Bottom,
931 Self::Bottom => Self::Center,
932 }
933 }
934}
935
936#[derive(Clone)]
937pub struct EditorSnapshot {
938 pub mode: EditorMode,
939 show_gutter: bool,
940 show_line_numbers: Option<bool>,
941 show_git_diff_gutter: Option<bool>,
942 show_code_actions: Option<bool>,
943 show_runnables: Option<bool>,
944 show_breakpoints: Option<bool>,
945 git_blame_gutter_max_author_length: Option<usize>,
946 pub display_snapshot: DisplaySnapshot,
947 pub placeholder_text: Option<Arc<str>>,
948 is_focused: bool,
949 scroll_anchor: ScrollAnchor,
950 ongoing_scroll: OngoingScroll,
951 current_line_highlight: CurrentLineHighlight,
952 gutter_hovered: bool,
953}
954
955#[derive(Default, Debug, Clone, Copy)]
956pub struct GutterDimensions {
957 pub left_padding: Pixels,
958 pub right_padding: Pixels,
959 pub width: Pixels,
960 pub margin: Pixels,
961 pub git_blame_entries_width: Option<Pixels>,
962}
963
964impl GutterDimensions {
965 /// The full width of the space taken up by the gutter.
966 pub fn full_width(&self) -> Pixels {
967 self.margin + self.width
968 }
969
970 /// The width of the space reserved for the fold indicators,
971 /// use alongside 'justify_end' and `gutter_width` to
972 /// right align content with the line numbers
973 pub fn fold_area_width(&self) -> Pixels {
974 self.margin + self.right_padding
975 }
976}
977
978#[derive(Debug)]
979pub struct RemoteSelection {
980 pub replica_id: ReplicaId,
981 pub selection: Selection<Anchor>,
982 pub cursor_shape: CursorShape,
983 pub peer_id: PeerId,
984 pub line_mode: bool,
985 pub participant_index: Option<ParticipantIndex>,
986 pub user_name: Option<SharedString>,
987}
988
989#[derive(Clone, Debug)]
990struct SelectionHistoryEntry {
991 selections: Arc<[Selection<Anchor>]>,
992 select_next_state: Option<SelectNextState>,
993 select_prev_state: Option<SelectNextState>,
994 add_selections_state: Option<AddSelectionsState>,
995}
996
997enum SelectionHistoryMode {
998 Normal,
999 Undoing,
1000 Redoing,
1001}
1002
1003#[derive(Clone, PartialEq, Eq, Hash)]
1004struct HoveredCursor {
1005 replica_id: u16,
1006 selection_id: usize,
1007}
1008
1009impl Default for SelectionHistoryMode {
1010 fn default() -> Self {
1011 Self::Normal
1012 }
1013}
1014
1015#[derive(Default)]
1016struct SelectionHistory {
1017 #[allow(clippy::type_complexity)]
1018 selections_by_transaction:
1019 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1020 mode: SelectionHistoryMode,
1021 undo_stack: VecDeque<SelectionHistoryEntry>,
1022 redo_stack: VecDeque<SelectionHistoryEntry>,
1023}
1024
1025impl SelectionHistory {
1026 fn insert_transaction(
1027 &mut self,
1028 transaction_id: TransactionId,
1029 selections: Arc<[Selection<Anchor>]>,
1030 ) {
1031 self.selections_by_transaction
1032 .insert(transaction_id, (selections, None));
1033 }
1034
1035 #[allow(clippy::type_complexity)]
1036 fn transaction(
1037 &self,
1038 transaction_id: TransactionId,
1039 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1040 self.selections_by_transaction.get(&transaction_id)
1041 }
1042
1043 #[allow(clippy::type_complexity)]
1044 fn transaction_mut(
1045 &mut self,
1046 transaction_id: TransactionId,
1047 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1048 self.selections_by_transaction.get_mut(&transaction_id)
1049 }
1050
1051 fn push(&mut self, entry: SelectionHistoryEntry) {
1052 if !entry.selections.is_empty() {
1053 match self.mode {
1054 SelectionHistoryMode::Normal => {
1055 self.push_undo(entry);
1056 self.redo_stack.clear();
1057 }
1058 SelectionHistoryMode::Undoing => self.push_redo(entry),
1059 SelectionHistoryMode::Redoing => self.push_undo(entry),
1060 }
1061 }
1062 }
1063
1064 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1065 if self
1066 .undo_stack
1067 .back()
1068 .map_or(true, |e| e.selections != entry.selections)
1069 {
1070 self.undo_stack.push_back(entry);
1071 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1072 self.undo_stack.pop_front();
1073 }
1074 }
1075 }
1076
1077 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1078 if self
1079 .redo_stack
1080 .back()
1081 .map_or(true, |e| e.selections != entry.selections)
1082 {
1083 self.redo_stack.push_back(entry);
1084 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1085 self.redo_stack.pop_front();
1086 }
1087 }
1088 }
1089}
1090
1091struct RowHighlight {
1092 index: usize,
1093 range: Range<Anchor>,
1094 color: Hsla,
1095 should_autoscroll: bool,
1096}
1097
1098#[derive(Clone, Debug)]
1099struct AddSelectionsState {
1100 above: bool,
1101 stack: Vec<usize>,
1102}
1103
1104#[derive(Clone)]
1105struct SelectNextState {
1106 query: AhoCorasick,
1107 wordwise: bool,
1108 done: bool,
1109}
1110
1111impl std::fmt::Debug for SelectNextState {
1112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1113 f.debug_struct(std::any::type_name::<Self>())
1114 .field("wordwise", &self.wordwise)
1115 .field("done", &self.done)
1116 .finish()
1117 }
1118}
1119
1120#[derive(Debug)]
1121struct AutocloseRegion {
1122 selection_id: usize,
1123 range: Range<Anchor>,
1124 pair: BracketPair,
1125}
1126
1127#[derive(Debug)]
1128struct SnippetState {
1129 ranges: Vec<Vec<Range<Anchor>>>,
1130 active_index: usize,
1131 choices: Vec<Option<Vec<String>>>,
1132}
1133
1134#[doc(hidden)]
1135pub struct RenameState {
1136 pub range: Range<Anchor>,
1137 pub old_name: Arc<str>,
1138 pub editor: Entity<Editor>,
1139 block_id: CustomBlockId,
1140}
1141
1142struct InvalidationStack<T>(Vec<T>);
1143
1144struct RegisteredInlineCompletionProvider {
1145 provider: Arc<dyn InlineCompletionProviderHandle>,
1146 _subscription: Subscription,
1147}
1148
1149#[derive(Debug, PartialEq, Eq)]
1150pub struct ActiveDiagnosticGroup {
1151 pub active_range: Range<Anchor>,
1152 pub active_message: String,
1153 pub group_id: usize,
1154 pub blocks: HashSet<CustomBlockId>,
1155}
1156
1157#[derive(Debug, PartialEq, Eq)]
1158#[allow(clippy::large_enum_variant)]
1159pub(crate) enum ActiveDiagnostic {
1160 None,
1161 All,
1162 Group(ActiveDiagnosticGroup),
1163}
1164
1165#[derive(Serialize, Deserialize, Clone, Debug)]
1166pub struct ClipboardSelection {
1167 /// The number of bytes in this selection.
1168 pub len: usize,
1169 /// Whether this was a full-line selection.
1170 pub is_entire_line: bool,
1171 /// The indentation of the first line when this content was originally copied.
1172 pub first_line_indent: u32,
1173}
1174
1175// selections, scroll behavior, was newest selection reversed
1176type SelectSyntaxNodeHistoryState = (
1177 Box<[Selection<usize>]>,
1178 SelectSyntaxNodeScrollBehavior,
1179 bool,
1180);
1181
1182#[derive(Default)]
1183struct SelectSyntaxNodeHistory {
1184 stack: Vec<SelectSyntaxNodeHistoryState>,
1185 // disable temporarily to allow changing selections without losing the stack
1186 pub disable_clearing: bool,
1187}
1188
1189impl SelectSyntaxNodeHistory {
1190 pub fn try_clear(&mut self) {
1191 if !self.disable_clearing {
1192 self.stack.clear();
1193 }
1194 }
1195
1196 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1197 self.stack.push(selection);
1198 }
1199
1200 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1201 self.stack.pop()
1202 }
1203}
1204
1205enum SelectSyntaxNodeScrollBehavior {
1206 CursorTop,
1207 FitSelection,
1208 CursorBottom,
1209}
1210
1211#[derive(Debug)]
1212pub(crate) struct NavigationData {
1213 cursor_anchor: Anchor,
1214 cursor_position: Point,
1215 scroll_anchor: ScrollAnchor,
1216 scroll_top_row: u32,
1217}
1218
1219#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1220pub enum GotoDefinitionKind {
1221 Symbol,
1222 Declaration,
1223 Type,
1224 Implementation,
1225}
1226
1227#[derive(Debug, Clone)]
1228enum InlayHintRefreshReason {
1229 ModifiersChanged(bool),
1230 Toggle(bool),
1231 SettingsChange(InlayHintSettings),
1232 NewLinesShown,
1233 BufferEdited(HashSet<Arc<Language>>),
1234 RefreshRequested,
1235 ExcerptsRemoved(Vec<ExcerptId>),
1236}
1237
1238impl InlayHintRefreshReason {
1239 fn description(&self) -> &'static str {
1240 match self {
1241 Self::ModifiersChanged(_) => "modifiers changed",
1242 Self::Toggle(_) => "toggle",
1243 Self::SettingsChange(_) => "settings change",
1244 Self::NewLinesShown => "new lines shown",
1245 Self::BufferEdited(_) => "buffer edited",
1246 Self::RefreshRequested => "refresh requested",
1247 Self::ExcerptsRemoved(_) => "excerpts removed",
1248 }
1249 }
1250}
1251
1252pub enum FormatTarget {
1253 Buffers,
1254 Ranges(Vec<Range<MultiBufferPoint>>),
1255}
1256
1257pub(crate) struct FocusedBlock {
1258 id: BlockId,
1259 focus_handle: WeakFocusHandle,
1260}
1261
1262#[derive(Clone)]
1263enum JumpData {
1264 MultiBufferRow {
1265 row: MultiBufferRow,
1266 line_offset_from_top: u32,
1267 },
1268 MultiBufferPoint {
1269 excerpt_id: ExcerptId,
1270 position: Point,
1271 anchor: text::Anchor,
1272 line_offset_from_top: u32,
1273 },
1274}
1275
1276pub enum MultibufferSelectionMode {
1277 First,
1278 All,
1279}
1280
1281#[derive(Clone, Copy, Debug, Default)]
1282pub struct RewrapOptions {
1283 pub override_language_settings: bool,
1284 pub preserve_existing_whitespace: bool,
1285}
1286
1287impl Editor {
1288 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1289 let buffer = cx.new(|cx| Buffer::local("", cx));
1290 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1291 Self::new(
1292 EditorMode::SingleLine { auto_width: false },
1293 buffer,
1294 None,
1295 window,
1296 cx,
1297 )
1298 }
1299
1300 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1301 let buffer = cx.new(|cx| Buffer::local("", cx));
1302 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1303 Self::new(EditorMode::full(), buffer, None, window, cx)
1304 }
1305
1306 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1307 let buffer = cx.new(|cx| Buffer::local("", cx));
1308 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1309 Self::new(
1310 EditorMode::SingleLine { auto_width: true },
1311 buffer,
1312 None,
1313 window,
1314 cx,
1315 )
1316 }
1317
1318 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1319 let buffer = cx.new(|cx| Buffer::local("", cx));
1320 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1321 Self::new(
1322 EditorMode::AutoHeight { max_lines },
1323 buffer,
1324 None,
1325 window,
1326 cx,
1327 )
1328 }
1329
1330 pub fn for_buffer(
1331 buffer: Entity<Buffer>,
1332 project: Option<Entity<Project>>,
1333 window: &mut Window,
1334 cx: &mut Context<Self>,
1335 ) -> Self {
1336 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1337 Self::new(EditorMode::full(), buffer, project, window, cx)
1338 }
1339
1340 pub fn for_multibuffer(
1341 buffer: Entity<MultiBuffer>,
1342 project: Option<Entity<Project>>,
1343 window: &mut Window,
1344 cx: &mut Context<Self>,
1345 ) -> Self {
1346 Self::new(EditorMode::full(), buffer, project, window, cx)
1347 }
1348
1349 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1350 let mut clone = Self::new(
1351 self.mode,
1352 self.buffer.clone(),
1353 self.project.clone(),
1354 window,
1355 cx,
1356 );
1357 self.display_map.update(cx, |display_map, cx| {
1358 let snapshot = display_map.snapshot(cx);
1359 clone.display_map.update(cx, |display_map, cx| {
1360 display_map.set_state(&snapshot, cx);
1361 });
1362 });
1363 clone.folds_did_change(cx);
1364 clone.selections.clone_state(&self.selections);
1365 clone.scroll_manager.clone_state(&self.scroll_manager);
1366 clone.searchable = self.searchable;
1367 clone.read_only = self.read_only;
1368 clone
1369 }
1370
1371 pub fn new(
1372 mode: EditorMode,
1373 buffer: Entity<MultiBuffer>,
1374 project: Option<Entity<Project>>,
1375 window: &mut Window,
1376 cx: &mut Context<Self>,
1377 ) -> Self {
1378 let style = window.text_style();
1379 let font_size = style.font_size.to_pixels(window.rem_size());
1380 let editor = cx.entity().downgrade();
1381 let fold_placeholder = FoldPlaceholder {
1382 constrain_width: true,
1383 render: Arc::new(move |fold_id, fold_range, cx| {
1384 let editor = editor.clone();
1385 div()
1386 .id(fold_id)
1387 .bg(cx.theme().colors().ghost_element_background)
1388 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1389 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1390 .rounded_xs()
1391 .size_full()
1392 .cursor_pointer()
1393 .child("⋯")
1394 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1395 .on_click(move |_, _window, cx| {
1396 editor
1397 .update(cx, |editor, cx| {
1398 editor.unfold_ranges(
1399 &[fold_range.start..fold_range.end],
1400 true,
1401 false,
1402 cx,
1403 );
1404 cx.stop_propagation();
1405 })
1406 .ok();
1407 })
1408 .into_any()
1409 }),
1410 merge_adjacent: true,
1411 ..Default::default()
1412 };
1413 let display_map = cx.new(|cx| {
1414 DisplayMap::new(
1415 buffer.clone(),
1416 style.font(),
1417 font_size,
1418 None,
1419 FILE_HEADER_HEIGHT,
1420 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1421 fold_placeholder,
1422 cx,
1423 )
1424 });
1425
1426 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1427
1428 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1429
1430 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1431 .then(|| language_settings::SoftWrap::None);
1432
1433 let mut project_subscriptions = Vec::new();
1434 if mode.is_full() {
1435 if let Some(project) = project.as_ref() {
1436 project_subscriptions.push(cx.subscribe_in(
1437 project,
1438 window,
1439 |editor, _, event, window, cx| match event {
1440 project::Event::RefreshCodeLens => {
1441 // we always query lens with actions, without storing them, always refreshing them
1442 }
1443 project::Event::RefreshInlayHints => {
1444 editor
1445 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1446 }
1447 project::Event::SnippetEdit(id, snippet_edits) => {
1448 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1449 let focus_handle = editor.focus_handle(cx);
1450 if focus_handle.is_focused(window) {
1451 let snapshot = buffer.read(cx).snapshot();
1452 for (range, snippet) in snippet_edits {
1453 let editor_range =
1454 language::range_from_lsp(*range).to_offset(&snapshot);
1455 editor
1456 .insert_snippet(
1457 &[editor_range],
1458 snippet.clone(),
1459 window,
1460 cx,
1461 )
1462 .ok();
1463 }
1464 }
1465 }
1466 }
1467 _ => {}
1468 },
1469 ));
1470 if let Some(task_inventory) = project
1471 .read(cx)
1472 .task_store()
1473 .read(cx)
1474 .task_inventory()
1475 .cloned()
1476 {
1477 project_subscriptions.push(cx.observe_in(
1478 &task_inventory,
1479 window,
1480 |editor, _, window, cx| {
1481 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1482 },
1483 ));
1484 };
1485
1486 project_subscriptions.push(cx.subscribe_in(
1487 &project.read(cx).breakpoint_store(),
1488 window,
1489 |editor, _, event, window, cx| match event {
1490 BreakpointStoreEvent::ActiveDebugLineChanged => {
1491 if editor.go_to_active_debug_line(window, cx) {
1492 cx.stop_propagation();
1493 }
1494 }
1495 _ => {}
1496 },
1497 ));
1498 }
1499 }
1500
1501 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1502
1503 let inlay_hint_settings =
1504 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1505 let focus_handle = cx.focus_handle();
1506 cx.on_focus(&focus_handle, window, Self::handle_focus)
1507 .detach();
1508 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1509 .detach();
1510 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1511 .detach();
1512 cx.on_blur(&focus_handle, window, Self::handle_blur)
1513 .detach();
1514
1515 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1516 Some(false)
1517 } else {
1518 None
1519 };
1520
1521 let breakpoint_store = match (mode, project.as_ref()) {
1522 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1523 _ => None,
1524 };
1525
1526 let mut code_action_providers = Vec::new();
1527 let mut load_uncommitted_diff = None;
1528 if let Some(project) = project.clone() {
1529 load_uncommitted_diff = Some(
1530 get_uncommitted_diff_for_buffer(
1531 &project,
1532 buffer.read(cx).all_buffers(),
1533 buffer.clone(),
1534 cx,
1535 )
1536 .shared(),
1537 );
1538 code_action_providers.push(Rc::new(project) as Rc<_>);
1539 }
1540
1541 let mut this = Self {
1542 focus_handle,
1543 show_cursor_when_unfocused: false,
1544 last_focused_descendant: None,
1545 buffer: buffer.clone(),
1546 display_map: display_map.clone(),
1547 selections,
1548 scroll_manager: ScrollManager::new(cx),
1549 columnar_selection_tail: None,
1550 add_selections_state: None,
1551 select_next_state: None,
1552 select_prev_state: None,
1553 selection_history: Default::default(),
1554 autoclose_regions: Default::default(),
1555 snippet_stack: Default::default(),
1556 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1557 ime_transaction: Default::default(),
1558 active_diagnostics: ActiveDiagnostic::None,
1559 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1560 inline_diagnostics_update: Task::ready(()),
1561 inline_diagnostics: Vec::new(),
1562 soft_wrap_mode_override,
1563 hard_wrap: None,
1564 completion_provider: project.clone().map(|project| Box::new(project) as _),
1565 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1566 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1567 project,
1568 blink_manager: blink_manager.clone(),
1569 show_local_selections: true,
1570 show_scrollbars: true,
1571 disable_scrolling: false,
1572 mode,
1573 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1574 show_gutter: mode.is_full(),
1575 show_line_numbers: None,
1576 use_relative_line_numbers: None,
1577 disable_expand_excerpt_buttons: false,
1578 show_git_diff_gutter: None,
1579 show_code_actions: None,
1580 show_runnables: None,
1581 show_breakpoints: None,
1582 show_wrap_guides: None,
1583 show_indent_guides,
1584 placeholder_text: None,
1585 highlight_order: 0,
1586 highlighted_rows: HashMap::default(),
1587 background_highlights: Default::default(),
1588 gutter_highlights: TreeMap::default(),
1589 scrollbar_marker_state: ScrollbarMarkerState::default(),
1590 active_indent_guides_state: ActiveIndentGuidesState::default(),
1591 nav_history: None,
1592 context_menu: RefCell::new(None),
1593 context_menu_options: None,
1594 mouse_context_menu: None,
1595 completion_tasks: Default::default(),
1596 signature_help_state: SignatureHelpState::default(),
1597 auto_signature_help: None,
1598 find_all_references_task_sources: Vec::new(),
1599 next_completion_id: 0,
1600 next_inlay_id: 0,
1601 code_action_providers,
1602 available_code_actions: Default::default(),
1603 code_actions_task: Default::default(),
1604 quick_selection_highlight_task: Default::default(),
1605 debounced_selection_highlight_task: Default::default(),
1606 document_highlights_task: Default::default(),
1607 linked_editing_range_task: Default::default(),
1608 pending_rename: Default::default(),
1609 searchable: true,
1610 cursor_shape: EditorSettings::get_global(cx)
1611 .cursor_shape
1612 .unwrap_or_default(),
1613 current_line_highlight: None,
1614 autoindent_mode: Some(AutoindentMode::EachLine),
1615 collapse_matches: false,
1616 workspace: None,
1617 input_enabled: true,
1618 use_modal_editing: mode.is_full(),
1619 read_only: false,
1620 use_autoclose: true,
1621 use_auto_surround: true,
1622 auto_replace_emoji_shortcode: false,
1623 jsx_tag_auto_close_enabled_in_any_buffer: false,
1624 leader_peer_id: None,
1625 remote_id: None,
1626 hover_state: Default::default(),
1627 pending_mouse_down: None,
1628 hovered_link_state: Default::default(),
1629 edit_prediction_provider: None,
1630 active_inline_completion: None,
1631 stale_inline_completion_in_menu: None,
1632 edit_prediction_preview: EditPredictionPreview::Inactive {
1633 released_too_fast: false,
1634 },
1635 inline_diagnostics_enabled: mode.is_full(),
1636 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1637
1638 gutter_hovered: false,
1639 pixel_position_of_newest_cursor: None,
1640 last_bounds: None,
1641 last_position_map: None,
1642 expect_bounds_change: None,
1643 gutter_dimensions: GutterDimensions::default(),
1644 style: None,
1645 show_cursor_names: false,
1646 hovered_cursors: Default::default(),
1647 next_editor_action_id: EditorActionId::default(),
1648 editor_actions: Rc::default(),
1649 inline_completions_hidden_for_vim_mode: false,
1650 show_inline_completions_override: None,
1651 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1652 edit_prediction_settings: EditPredictionSettings::Disabled,
1653 edit_prediction_indent_conflict: false,
1654 edit_prediction_requires_modifier_in_indent_conflict: true,
1655 custom_context_menu: None,
1656 show_git_blame_gutter: false,
1657 show_git_blame_inline: false,
1658 show_selection_menu: None,
1659 show_git_blame_inline_delay_task: None,
1660 git_blame_inline_tooltip: None,
1661 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1662 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1663 serialize_dirty_buffers: ProjectSettings::get_global(cx)
1664 .session
1665 .restore_unsaved_buffers,
1666 blame: None,
1667 blame_subscription: None,
1668 tasks: Default::default(),
1669
1670 breakpoint_store,
1671 gutter_breakpoint_indicator: (None, None),
1672 _subscriptions: vec![
1673 cx.observe(&buffer, Self::on_buffer_changed),
1674 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1675 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1676 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1677 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1678 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1679 cx.observe_window_activation(window, |editor, window, cx| {
1680 let active = window.is_window_active();
1681 editor.blink_manager.update(cx, |blink_manager, cx| {
1682 if active {
1683 blink_manager.enable(cx);
1684 } else {
1685 blink_manager.disable(cx);
1686 }
1687 });
1688 }),
1689 ],
1690 tasks_update_task: None,
1691 linked_edit_ranges: Default::default(),
1692 in_project_search: false,
1693 previous_search_ranges: None,
1694 breadcrumb_header: None,
1695 focused_block: None,
1696 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1697 addons: HashMap::default(),
1698 registered_buffers: HashMap::default(),
1699 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1700 selection_mark_mode: false,
1701 toggle_fold_multiple_buffers: Task::ready(()),
1702 serialize_selections: Task::ready(()),
1703 serialize_folds: Task::ready(()),
1704 text_style_refinement: None,
1705 load_diff_task: load_uncommitted_diff,
1706 mouse_cursor_hidden: false,
1707 hide_mouse_mode: EditorSettings::get_global(cx)
1708 .hide_mouse
1709 .unwrap_or_default(),
1710 change_list: ChangeList::new(),
1711 };
1712 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1713 this._subscriptions
1714 .push(cx.observe(breakpoints, |_, _, cx| {
1715 cx.notify();
1716 }));
1717 }
1718 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1719 this._subscriptions.extend(project_subscriptions);
1720
1721 this._subscriptions.push(cx.subscribe_in(
1722 &cx.entity(),
1723 window,
1724 |editor, _, e: &EditorEvent, window, cx| match e {
1725 EditorEvent::ScrollPositionChanged { local, .. } => {
1726 if *local {
1727 let new_anchor = editor.scroll_manager.anchor();
1728 let snapshot = editor.snapshot(window, cx);
1729 editor.update_restoration_data(cx, move |data| {
1730 data.scroll_position = (
1731 new_anchor.top_row(&snapshot.buffer_snapshot),
1732 new_anchor.offset,
1733 );
1734 });
1735 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
1736 }
1737 }
1738 EditorEvent::Edited { .. } => {
1739 if !vim_enabled(cx) {
1740 let (map, selections) = editor.selections.all_adjusted_display(cx);
1741 let pop_state = editor
1742 .change_list
1743 .last()
1744 .map(|previous| {
1745 previous.len() == selections.len()
1746 && previous.iter().enumerate().all(|(ix, p)| {
1747 p.to_display_point(&map).row()
1748 == selections[ix].head().row()
1749 })
1750 })
1751 .unwrap_or(false);
1752 let new_positions = selections
1753 .into_iter()
1754 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
1755 .collect();
1756 editor
1757 .change_list
1758 .push_to_change_list(pop_state, new_positions);
1759 }
1760 }
1761 _ => (),
1762 },
1763 ));
1764
1765 this.end_selection(window, cx);
1766 this.scroll_manager.show_scrollbars(window, cx);
1767 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1768
1769 if mode.is_full() {
1770 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1771 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1772
1773 if this.git_blame_inline_enabled {
1774 this.git_blame_inline_enabled = true;
1775 this.start_git_blame_inline(false, window, cx);
1776 }
1777
1778 this.go_to_active_debug_line(window, cx);
1779
1780 if let Some(buffer) = buffer.read(cx).as_singleton() {
1781 if let Some(project) = this.project.as_ref() {
1782 let handle = project.update(cx, |project, cx| {
1783 project.register_buffer_with_language_servers(&buffer, cx)
1784 });
1785 this.registered_buffers
1786 .insert(buffer.read(cx).remote_id(), handle);
1787 }
1788 }
1789 }
1790
1791 this.report_editor_event("Editor Opened", None, cx);
1792 this
1793 }
1794
1795 pub fn deploy_mouse_context_menu(
1796 &mut self,
1797 position: gpui::Point<Pixels>,
1798 context_menu: Entity<ContextMenu>,
1799 window: &mut Window,
1800 cx: &mut Context<Self>,
1801 ) {
1802 self.mouse_context_menu = Some(MouseContextMenu::new(
1803 self,
1804 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
1805 context_menu,
1806 window,
1807 cx,
1808 ));
1809 }
1810
1811 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
1812 self.mouse_context_menu
1813 .as_ref()
1814 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
1815 }
1816
1817 fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
1818 self.key_context_internal(self.has_active_inline_completion(), window, cx)
1819 }
1820
1821 fn key_context_internal(
1822 &self,
1823 has_active_edit_prediction: bool,
1824 window: &Window,
1825 cx: &App,
1826 ) -> KeyContext {
1827 let mut key_context = KeyContext::new_with_defaults();
1828 key_context.add("Editor");
1829 let mode = match self.mode {
1830 EditorMode::SingleLine { .. } => "single_line",
1831 EditorMode::AutoHeight { .. } => "auto_height",
1832 EditorMode::Full { .. } => "full",
1833 };
1834
1835 if EditorSettings::jupyter_enabled(cx) {
1836 key_context.add("jupyter");
1837 }
1838
1839 key_context.set("mode", mode);
1840 if self.pending_rename.is_some() {
1841 key_context.add("renaming");
1842 }
1843
1844 match self.context_menu.borrow().as_ref() {
1845 Some(CodeContextMenu::Completions(_)) => {
1846 key_context.add("menu");
1847 key_context.add("showing_completions");
1848 }
1849 Some(CodeContextMenu::CodeActions(_)) => {
1850 key_context.add("menu");
1851 key_context.add("showing_code_actions")
1852 }
1853 None => {}
1854 }
1855
1856 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
1857 if !self.focus_handle(cx).contains_focused(window, cx)
1858 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
1859 {
1860 for addon in self.addons.values() {
1861 addon.extend_key_context(&mut key_context, cx)
1862 }
1863 }
1864
1865 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
1866 if let Some(extension) = singleton_buffer
1867 .read(cx)
1868 .file()
1869 .and_then(|file| file.path().extension()?.to_str())
1870 {
1871 key_context.set("extension", extension.to_string());
1872 }
1873 } else {
1874 key_context.add("multibuffer");
1875 }
1876
1877 if has_active_edit_prediction {
1878 if self.edit_prediction_in_conflict() {
1879 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
1880 } else {
1881 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
1882 key_context.add("copilot_suggestion");
1883 }
1884 }
1885
1886 if self.selection_mark_mode {
1887 key_context.add("selection_mode");
1888 }
1889
1890 key_context
1891 }
1892
1893 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
1894 self.mouse_cursor_hidden = match origin {
1895 HideMouseCursorOrigin::TypingAction => {
1896 matches!(
1897 self.hide_mouse_mode,
1898 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
1899 )
1900 }
1901 HideMouseCursorOrigin::MovementAction => {
1902 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
1903 }
1904 };
1905 }
1906
1907 pub fn edit_prediction_in_conflict(&self) -> bool {
1908 if !self.show_edit_predictions_in_menu() {
1909 return false;
1910 }
1911
1912 let showing_completions = self
1913 .context_menu
1914 .borrow()
1915 .as_ref()
1916 .map_or(false, |context| {
1917 matches!(context, CodeContextMenu::Completions(_))
1918 });
1919
1920 showing_completions
1921 || self.edit_prediction_requires_modifier()
1922 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
1923 // bindings to insert tab characters.
1924 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
1925 }
1926
1927 pub fn accept_edit_prediction_keybind(
1928 &self,
1929 window: &Window,
1930 cx: &App,
1931 ) -> AcceptEditPredictionBinding {
1932 let key_context = self.key_context_internal(true, window, cx);
1933 let in_conflict = self.edit_prediction_in_conflict();
1934
1935 AcceptEditPredictionBinding(
1936 window
1937 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
1938 .into_iter()
1939 .filter(|binding| {
1940 !in_conflict
1941 || binding
1942 .keystrokes()
1943 .first()
1944 .map_or(false, |keystroke| keystroke.modifiers.modified())
1945 })
1946 .rev()
1947 .min_by_key(|binding| {
1948 binding
1949 .keystrokes()
1950 .first()
1951 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
1952 }),
1953 )
1954 }
1955
1956 pub fn new_file(
1957 workspace: &mut Workspace,
1958 _: &workspace::NewFile,
1959 window: &mut Window,
1960 cx: &mut Context<Workspace>,
1961 ) {
1962 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
1963 "Failed to create buffer",
1964 window,
1965 cx,
1966 |e, _, _| match e.error_code() {
1967 ErrorCode::RemoteUpgradeRequired => Some(format!(
1968 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1969 e.error_tag("required").unwrap_or("the latest version")
1970 )),
1971 _ => None,
1972 },
1973 );
1974 }
1975
1976 pub fn new_in_workspace(
1977 workspace: &mut Workspace,
1978 window: &mut Window,
1979 cx: &mut Context<Workspace>,
1980 ) -> Task<Result<Entity<Editor>>> {
1981 let project = workspace.project().clone();
1982 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1983
1984 cx.spawn_in(window, async move |workspace, cx| {
1985 let buffer = create.await?;
1986 workspace.update_in(cx, |workspace, window, cx| {
1987 let editor =
1988 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
1989 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
1990 editor
1991 })
1992 })
1993 }
1994
1995 fn new_file_vertical(
1996 workspace: &mut Workspace,
1997 _: &workspace::NewFileSplitVertical,
1998 window: &mut Window,
1999 cx: &mut Context<Workspace>,
2000 ) {
2001 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2002 }
2003
2004 fn new_file_horizontal(
2005 workspace: &mut Workspace,
2006 _: &workspace::NewFileSplitHorizontal,
2007 window: &mut Window,
2008 cx: &mut Context<Workspace>,
2009 ) {
2010 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2011 }
2012
2013 fn new_file_in_direction(
2014 workspace: &mut Workspace,
2015 direction: SplitDirection,
2016 window: &mut Window,
2017 cx: &mut Context<Workspace>,
2018 ) {
2019 let project = workspace.project().clone();
2020 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2021
2022 cx.spawn_in(window, async move |workspace, cx| {
2023 let buffer = create.await?;
2024 workspace.update_in(cx, move |workspace, window, cx| {
2025 workspace.split_item(
2026 direction,
2027 Box::new(
2028 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2029 ),
2030 window,
2031 cx,
2032 )
2033 })?;
2034 anyhow::Ok(())
2035 })
2036 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2037 match e.error_code() {
2038 ErrorCode::RemoteUpgradeRequired => Some(format!(
2039 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2040 e.error_tag("required").unwrap_or("the latest version")
2041 )),
2042 _ => None,
2043 }
2044 });
2045 }
2046
2047 pub fn leader_peer_id(&self) -> Option<PeerId> {
2048 self.leader_peer_id
2049 }
2050
2051 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2052 &self.buffer
2053 }
2054
2055 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2056 self.workspace.as_ref()?.0.upgrade()
2057 }
2058
2059 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2060 self.buffer().read(cx).title(cx)
2061 }
2062
2063 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2064 let git_blame_gutter_max_author_length = self
2065 .render_git_blame_gutter(cx)
2066 .then(|| {
2067 if let Some(blame) = self.blame.as_ref() {
2068 let max_author_length =
2069 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2070 Some(max_author_length)
2071 } else {
2072 None
2073 }
2074 })
2075 .flatten();
2076
2077 EditorSnapshot {
2078 mode: self.mode,
2079 show_gutter: self.show_gutter,
2080 show_line_numbers: self.show_line_numbers,
2081 show_git_diff_gutter: self.show_git_diff_gutter,
2082 show_code_actions: self.show_code_actions,
2083 show_runnables: self.show_runnables,
2084 show_breakpoints: self.show_breakpoints,
2085 git_blame_gutter_max_author_length,
2086 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2087 scroll_anchor: self.scroll_manager.anchor(),
2088 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2089 placeholder_text: self.placeholder_text.clone(),
2090 is_focused: self.focus_handle.is_focused(window),
2091 current_line_highlight: self
2092 .current_line_highlight
2093 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2094 gutter_hovered: self.gutter_hovered,
2095 }
2096 }
2097
2098 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2099 self.buffer.read(cx).language_at(point, cx)
2100 }
2101
2102 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2103 self.buffer.read(cx).read(cx).file_at(point).cloned()
2104 }
2105
2106 pub fn active_excerpt(
2107 &self,
2108 cx: &App,
2109 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2110 self.buffer
2111 .read(cx)
2112 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2113 }
2114
2115 pub fn mode(&self) -> EditorMode {
2116 self.mode
2117 }
2118
2119 pub fn set_mode(&mut self, mode: EditorMode) {
2120 self.mode = mode;
2121 }
2122
2123 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2124 self.collaboration_hub.as_deref()
2125 }
2126
2127 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2128 self.collaboration_hub = Some(hub);
2129 }
2130
2131 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2132 self.in_project_search = in_project_search;
2133 }
2134
2135 pub fn set_custom_context_menu(
2136 &mut self,
2137 f: impl 'static
2138 + Fn(
2139 &mut Self,
2140 DisplayPoint,
2141 &mut Window,
2142 &mut Context<Self>,
2143 ) -> Option<Entity<ui::ContextMenu>>,
2144 ) {
2145 self.custom_context_menu = Some(Box::new(f))
2146 }
2147
2148 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
2149 self.completion_provider = provider;
2150 }
2151
2152 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2153 self.semantics_provider.clone()
2154 }
2155
2156 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2157 self.semantics_provider = provider;
2158 }
2159
2160 pub fn set_edit_prediction_provider<T>(
2161 &mut self,
2162 provider: Option<Entity<T>>,
2163 window: &mut Window,
2164 cx: &mut Context<Self>,
2165 ) where
2166 T: EditPredictionProvider,
2167 {
2168 self.edit_prediction_provider =
2169 provider.map(|provider| RegisteredInlineCompletionProvider {
2170 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2171 if this.focus_handle.is_focused(window) {
2172 this.update_visible_inline_completion(window, cx);
2173 }
2174 }),
2175 provider: Arc::new(provider),
2176 });
2177 self.update_edit_prediction_settings(cx);
2178 self.refresh_inline_completion(false, false, window, cx);
2179 }
2180
2181 pub fn placeholder_text(&self) -> Option<&str> {
2182 self.placeholder_text.as_deref()
2183 }
2184
2185 pub fn set_placeholder_text(
2186 &mut self,
2187 placeholder_text: impl Into<Arc<str>>,
2188 cx: &mut Context<Self>,
2189 ) {
2190 let placeholder_text = Some(placeholder_text.into());
2191 if self.placeholder_text != placeholder_text {
2192 self.placeholder_text = placeholder_text;
2193 cx.notify();
2194 }
2195 }
2196
2197 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2198 self.cursor_shape = cursor_shape;
2199
2200 // Disrupt blink for immediate user feedback that the cursor shape has changed
2201 self.blink_manager.update(cx, BlinkManager::show_cursor);
2202
2203 cx.notify();
2204 }
2205
2206 pub fn set_current_line_highlight(
2207 &mut self,
2208 current_line_highlight: Option<CurrentLineHighlight>,
2209 ) {
2210 self.current_line_highlight = current_line_highlight;
2211 }
2212
2213 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2214 self.collapse_matches = collapse_matches;
2215 }
2216
2217 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2218 let buffers = self.buffer.read(cx).all_buffers();
2219 let Some(project) = self.project.as_ref() else {
2220 return;
2221 };
2222 project.update(cx, |project, cx| {
2223 for buffer in buffers {
2224 self.registered_buffers
2225 .entry(buffer.read(cx).remote_id())
2226 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2227 }
2228 })
2229 }
2230
2231 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2232 if self.collapse_matches {
2233 return range.start..range.start;
2234 }
2235 range.clone()
2236 }
2237
2238 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2239 if self.display_map.read(cx).clip_at_line_ends != clip {
2240 self.display_map
2241 .update(cx, |map, _| map.clip_at_line_ends = clip);
2242 }
2243 }
2244
2245 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2246 self.input_enabled = input_enabled;
2247 }
2248
2249 pub fn set_inline_completions_hidden_for_vim_mode(
2250 &mut self,
2251 hidden: bool,
2252 window: &mut Window,
2253 cx: &mut Context<Self>,
2254 ) {
2255 if hidden != self.inline_completions_hidden_for_vim_mode {
2256 self.inline_completions_hidden_for_vim_mode = hidden;
2257 if hidden {
2258 self.update_visible_inline_completion(window, cx);
2259 } else {
2260 self.refresh_inline_completion(true, false, window, cx);
2261 }
2262 }
2263 }
2264
2265 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2266 self.menu_inline_completions_policy = value;
2267 }
2268
2269 pub fn set_autoindent(&mut self, autoindent: bool) {
2270 if autoindent {
2271 self.autoindent_mode = Some(AutoindentMode::EachLine);
2272 } else {
2273 self.autoindent_mode = None;
2274 }
2275 }
2276
2277 pub fn read_only(&self, cx: &App) -> bool {
2278 self.read_only || self.buffer.read(cx).read_only()
2279 }
2280
2281 pub fn set_read_only(&mut self, read_only: bool) {
2282 self.read_only = read_only;
2283 }
2284
2285 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2286 self.use_autoclose = autoclose;
2287 }
2288
2289 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2290 self.use_auto_surround = auto_surround;
2291 }
2292
2293 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2294 self.auto_replace_emoji_shortcode = auto_replace;
2295 }
2296
2297 pub fn toggle_edit_predictions(
2298 &mut self,
2299 _: &ToggleEditPrediction,
2300 window: &mut Window,
2301 cx: &mut Context<Self>,
2302 ) {
2303 if self.show_inline_completions_override.is_some() {
2304 self.set_show_edit_predictions(None, window, cx);
2305 } else {
2306 let show_edit_predictions = !self.edit_predictions_enabled();
2307 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2308 }
2309 }
2310
2311 pub fn set_show_edit_predictions(
2312 &mut self,
2313 show_edit_predictions: Option<bool>,
2314 window: &mut Window,
2315 cx: &mut Context<Self>,
2316 ) {
2317 self.show_inline_completions_override = show_edit_predictions;
2318 self.update_edit_prediction_settings(cx);
2319
2320 if let Some(false) = show_edit_predictions {
2321 self.discard_inline_completion(false, cx);
2322 } else {
2323 self.refresh_inline_completion(false, true, window, cx);
2324 }
2325 }
2326
2327 fn inline_completions_disabled_in_scope(
2328 &self,
2329 buffer: &Entity<Buffer>,
2330 buffer_position: language::Anchor,
2331 cx: &App,
2332 ) -> bool {
2333 let snapshot = buffer.read(cx).snapshot();
2334 let settings = snapshot.settings_at(buffer_position, cx);
2335
2336 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2337 return false;
2338 };
2339
2340 scope.override_name().map_or(false, |scope_name| {
2341 settings
2342 .edit_predictions_disabled_in
2343 .iter()
2344 .any(|s| s == scope_name)
2345 })
2346 }
2347
2348 pub fn set_use_modal_editing(&mut self, to: bool) {
2349 self.use_modal_editing = to;
2350 }
2351
2352 pub fn use_modal_editing(&self) -> bool {
2353 self.use_modal_editing
2354 }
2355
2356 fn selections_did_change(
2357 &mut self,
2358 local: bool,
2359 old_cursor_position: &Anchor,
2360 show_completions: bool,
2361 window: &mut Window,
2362 cx: &mut Context<Self>,
2363 ) {
2364 window.invalidate_character_coordinates();
2365
2366 // Copy selections to primary selection buffer
2367 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2368 if local {
2369 let selections = self.selections.all::<usize>(cx);
2370 let buffer_handle = self.buffer.read(cx).read(cx);
2371
2372 let mut text = String::new();
2373 for (index, selection) in selections.iter().enumerate() {
2374 let text_for_selection = buffer_handle
2375 .text_for_range(selection.start..selection.end)
2376 .collect::<String>();
2377
2378 text.push_str(&text_for_selection);
2379 if index != selections.len() - 1 {
2380 text.push('\n');
2381 }
2382 }
2383
2384 if !text.is_empty() {
2385 cx.write_to_primary(ClipboardItem::new_string(text));
2386 }
2387 }
2388
2389 if self.focus_handle.is_focused(window) && self.leader_peer_id.is_none() {
2390 self.buffer.update(cx, |buffer, cx| {
2391 buffer.set_active_selections(
2392 &self.selections.disjoint_anchors(),
2393 self.selections.line_mode,
2394 self.cursor_shape,
2395 cx,
2396 )
2397 });
2398 }
2399 let display_map = self
2400 .display_map
2401 .update(cx, |display_map, cx| display_map.snapshot(cx));
2402 let buffer = &display_map.buffer_snapshot;
2403 self.add_selections_state = None;
2404 self.select_next_state = None;
2405 self.select_prev_state = None;
2406 self.select_syntax_node_history.try_clear();
2407 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2408 self.snippet_stack
2409 .invalidate(&self.selections.disjoint_anchors(), buffer);
2410 self.take_rename(false, window, cx);
2411
2412 let new_cursor_position = self.selections.newest_anchor().head();
2413
2414 self.push_to_nav_history(
2415 *old_cursor_position,
2416 Some(new_cursor_position.to_point(buffer)),
2417 false,
2418 cx,
2419 );
2420
2421 if local {
2422 let new_cursor_position = self.selections.newest_anchor().head();
2423 let mut context_menu = self.context_menu.borrow_mut();
2424 let completion_menu = match context_menu.as_ref() {
2425 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2426 _ => {
2427 *context_menu = None;
2428 None
2429 }
2430 };
2431 if let Some(buffer_id) = new_cursor_position.buffer_id {
2432 if !self.registered_buffers.contains_key(&buffer_id) {
2433 if let Some(project) = self.project.as_ref() {
2434 project.update(cx, |project, cx| {
2435 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2436 return;
2437 };
2438 self.registered_buffers.insert(
2439 buffer_id,
2440 project.register_buffer_with_language_servers(&buffer, cx),
2441 );
2442 })
2443 }
2444 }
2445 }
2446
2447 if let Some(completion_menu) = completion_menu {
2448 let cursor_position = new_cursor_position.to_offset(buffer);
2449 let (word_range, kind) =
2450 buffer.surrounding_word(completion_menu.initial_position, true);
2451 if kind == Some(CharKind::Word)
2452 && word_range.to_inclusive().contains(&cursor_position)
2453 {
2454 let mut completion_menu = completion_menu.clone();
2455 drop(context_menu);
2456
2457 let query = Self::completion_query(buffer, cursor_position);
2458 cx.spawn(async move |this, cx| {
2459 completion_menu
2460 .filter(query.as_deref(), cx.background_executor().clone())
2461 .await;
2462
2463 this.update(cx, |this, cx| {
2464 let mut context_menu = this.context_menu.borrow_mut();
2465 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2466 else {
2467 return;
2468 };
2469
2470 if menu.id > completion_menu.id {
2471 return;
2472 }
2473
2474 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2475 drop(context_menu);
2476 cx.notify();
2477 })
2478 })
2479 .detach();
2480
2481 if show_completions {
2482 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2483 }
2484 } else {
2485 drop(context_menu);
2486 self.hide_context_menu(window, cx);
2487 }
2488 } else {
2489 drop(context_menu);
2490 }
2491
2492 hide_hover(self, cx);
2493
2494 if old_cursor_position.to_display_point(&display_map).row()
2495 != new_cursor_position.to_display_point(&display_map).row()
2496 {
2497 self.available_code_actions.take();
2498 }
2499 self.refresh_code_actions(window, cx);
2500 self.refresh_document_highlights(cx);
2501 self.refresh_selected_text_highlights(window, cx);
2502 refresh_matching_bracket_highlights(self, window, cx);
2503 self.update_visible_inline_completion(window, cx);
2504 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2505 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2506 if self.git_blame_inline_enabled {
2507 self.start_inline_blame_timer(window, cx);
2508 }
2509 }
2510
2511 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2512 cx.emit(EditorEvent::SelectionsChanged { local });
2513
2514 let selections = &self.selections.disjoint;
2515 if selections.len() == 1 {
2516 cx.emit(SearchEvent::ActiveMatchChanged)
2517 }
2518 if local {
2519 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2520 let inmemory_selections = selections
2521 .iter()
2522 .map(|s| {
2523 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2524 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2525 })
2526 .collect();
2527 self.update_restoration_data(cx, |data| {
2528 data.selections = inmemory_selections;
2529 });
2530
2531 if WorkspaceSettings::get(None, cx).restore_on_startup
2532 != RestoreOnStartupBehavior::None
2533 {
2534 if let Some(workspace_id) =
2535 self.workspace.as_ref().and_then(|workspace| workspace.1)
2536 {
2537 let snapshot = self.buffer().read(cx).snapshot(cx);
2538 let selections = selections.clone();
2539 let background_executor = cx.background_executor().clone();
2540 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2541 self.serialize_selections = cx.background_spawn(async move {
2542 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2543 let db_selections = selections
2544 .iter()
2545 .map(|selection| {
2546 (
2547 selection.start.to_offset(&snapshot),
2548 selection.end.to_offset(&snapshot),
2549 )
2550 })
2551 .collect();
2552
2553 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2554 .await
2555 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2556 .log_err();
2557 });
2558 }
2559 }
2560 }
2561 }
2562
2563 cx.notify();
2564 }
2565
2566 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2567 use text::ToOffset as _;
2568 use text::ToPoint as _;
2569
2570 if WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None {
2571 return;
2572 }
2573
2574 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2575 return;
2576 };
2577
2578 let snapshot = singleton.read(cx).snapshot();
2579 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2580 let display_snapshot = display_map.snapshot(cx);
2581
2582 display_snapshot
2583 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2584 .map(|fold| {
2585 fold.range.start.text_anchor.to_point(&snapshot)
2586 ..fold.range.end.text_anchor.to_point(&snapshot)
2587 })
2588 .collect()
2589 });
2590 self.update_restoration_data(cx, |data| {
2591 data.folds = inmemory_folds;
2592 });
2593
2594 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2595 return;
2596 };
2597 let background_executor = cx.background_executor().clone();
2598 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2599 let db_folds = self.display_map.update(cx, |display_map, cx| {
2600 display_map
2601 .snapshot(cx)
2602 .folds_in_range(0..snapshot.len())
2603 .map(|fold| {
2604 (
2605 fold.range.start.text_anchor.to_offset(&snapshot),
2606 fold.range.end.text_anchor.to_offset(&snapshot),
2607 )
2608 })
2609 .collect()
2610 });
2611 self.serialize_folds = cx.background_spawn(async move {
2612 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2613 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2614 .await
2615 .with_context(|| {
2616 format!(
2617 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2618 )
2619 })
2620 .log_err();
2621 });
2622 }
2623
2624 pub fn sync_selections(
2625 &mut self,
2626 other: Entity<Editor>,
2627 cx: &mut Context<Self>,
2628 ) -> gpui::Subscription {
2629 let other_selections = other.read(cx).selections.disjoint.to_vec();
2630 self.selections.change_with(cx, |selections| {
2631 selections.select_anchors(other_selections);
2632 });
2633
2634 let other_subscription =
2635 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2636 EditorEvent::SelectionsChanged { local: true } => {
2637 let other_selections = other.read(cx).selections.disjoint.to_vec();
2638 if other_selections.is_empty() {
2639 return;
2640 }
2641 this.selections.change_with(cx, |selections| {
2642 selections.select_anchors(other_selections);
2643 });
2644 }
2645 _ => {}
2646 });
2647
2648 let this_subscription =
2649 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2650 EditorEvent::SelectionsChanged { local: true } => {
2651 let these_selections = this.selections.disjoint.to_vec();
2652 if these_selections.is_empty() {
2653 return;
2654 }
2655 other.update(cx, |other_editor, cx| {
2656 other_editor.selections.change_with(cx, |selections| {
2657 selections.select_anchors(these_selections);
2658 })
2659 });
2660 }
2661 _ => {}
2662 });
2663
2664 Subscription::join(other_subscription, this_subscription)
2665 }
2666
2667 pub fn change_selections<R>(
2668 &mut self,
2669 autoscroll: Option<Autoscroll>,
2670 window: &mut Window,
2671 cx: &mut Context<Self>,
2672 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2673 ) -> R {
2674 self.change_selections_inner(autoscroll, true, window, cx, change)
2675 }
2676
2677 fn change_selections_inner<R>(
2678 &mut self,
2679 autoscroll: Option<Autoscroll>,
2680 request_completions: bool,
2681 window: &mut Window,
2682 cx: &mut Context<Self>,
2683 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2684 ) -> R {
2685 let old_cursor_position = self.selections.newest_anchor().head();
2686 self.push_to_selection_history();
2687
2688 let (changed, result) = self.selections.change_with(cx, change);
2689
2690 if changed {
2691 if let Some(autoscroll) = autoscroll {
2692 self.request_autoscroll(autoscroll, cx);
2693 }
2694 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2695
2696 if self.should_open_signature_help_automatically(
2697 &old_cursor_position,
2698 self.signature_help_state.backspace_pressed(),
2699 cx,
2700 ) {
2701 self.show_signature_help(&ShowSignatureHelp, window, cx);
2702 }
2703 self.signature_help_state.set_backspace_pressed(false);
2704 }
2705
2706 result
2707 }
2708
2709 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2710 where
2711 I: IntoIterator<Item = (Range<S>, T)>,
2712 S: ToOffset,
2713 T: Into<Arc<str>>,
2714 {
2715 if self.read_only(cx) {
2716 return;
2717 }
2718
2719 self.buffer
2720 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2721 }
2722
2723 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2724 where
2725 I: IntoIterator<Item = (Range<S>, T)>,
2726 S: ToOffset,
2727 T: Into<Arc<str>>,
2728 {
2729 if self.read_only(cx) {
2730 return;
2731 }
2732
2733 self.buffer.update(cx, |buffer, cx| {
2734 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2735 });
2736 }
2737
2738 pub fn edit_with_block_indent<I, S, T>(
2739 &mut self,
2740 edits: I,
2741 original_indent_columns: Vec<Option<u32>>,
2742 cx: &mut Context<Self>,
2743 ) where
2744 I: IntoIterator<Item = (Range<S>, T)>,
2745 S: ToOffset,
2746 T: Into<Arc<str>>,
2747 {
2748 if self.read_only(cx) {
2749 return;
2750 }
2751
2752 self.buffer.update(cx, |buffer, cx| {
2753 buffer.edit(
2754 edits,
2755 Some(AutoindentMode::Block {
2756 original_indent_columns,
2757 }),
2758 cx,
2759 )
2760 });
2761 }
2762
2763 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2764 self.hide_context_menu(window, cx);
2765
2766 match phase {
2767 SelectPhase::Begin {
2768 position,
2769 add,
2770 click_count,
2771 } => self.begin_selection(position, add, click_count, window, cx),
2772 SelectPhase::BeginColumnar {
2773 position,
2774 goal_column,
2775 reset,
2776 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
2777 SelectPhase::Extend {
2778 position,
2779 click_count,
2780 } => self.extend_selection(position, click_count, window, cx),
2781 SelectPhase::Update {
2782 position,
2783 goal_column,
2784 scroll_delta,
2785 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
2786 SelectPhase::End => self.end_selection(window, cx),
2787 }
2788 }
2789
2790 fn extend_selection(
2791 &mut self,
2792 position: DisplayPoint,
2793 click_count: usize,
2794 window: &mut Window,
2795 cx: &mut Context<Self>,
2796 ) {
2797 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2798 let tail = self.selections.newest::<usize>(cx).tail();
2799 self.begin_selection(position, false, click_count, window, cx);
2800
2801 let position = position.to_offset(&display_map, Bias::Left);
2802 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
2803
2804 let mut pending_selection = self
2805 .selections
2806 .pending_anchor()
2807 .expect("extend_selection not called with pending selection");
2808 if position >= tail {
2809 pending_selection.start = tail_anchor;
2810 } else {
2811 pending_selection.end = tail_anchor;
2812 pending_selection.reversed = true;
2813 }
2814
2815 let mut pending_mode = self.selections.pending_mode().unwrap();
2816 match &mut pending_mode {
2817 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
2818 _ => {}
2819 }
2820
2821 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
2822 s.set_pending(pending_selection, pending_mode)
2823 });
2824 }
2825
2826 fn begin_selection(
2827 &mut self,
2828 position: DisplayPoint,
2829 add: bool,
2830 click_count: usize,
2831 window: &mut Window,
2832 cx: &mut Context<Self>,
2833 ) {
2834 if !self.focus_handle.is_focused(window) {
2835 self.last_focused_descendant = None;
2836 window.focus(&self.focus_handle);
2837 }
2838
2839 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2840 let buffer = &display_map.buffer_snapshot;
2841 let newest_selection = self.selections.newest_anchor().clone();
2842 let position = display_map.clip_point(position, Bias::Left);
2843
2844 let start;
2845 let end;
2846 let mode;
2847 let mut auto_scroll;
2848 match click_count {
2849 1 => {
2850 start = buffer.anchor_before(position.to_point(&display_map));
2851 end = start;
2852 mode = SelectMode::Character;
2853 auto_scroll = true;
2854 }
2855 2 => {
2856 let range = movement::surrounding_word(&display_map, position);
2857 start = buffer.anchor_before(range.start.to_point(&display_map));
2858 end = buffer.anchor_before(range.end.to_point(&display_map));
2859 mode = SelectMode::Word(start..end);
2860 auto_scroll = true;
2861 }
2862 3 => {
2863 let position = display_map
2864 .clip_point(position, Bias::Left)
2865 .to_point(&display_map);
2866 let line_start = display_map.prev_line_boundary(position).0;
2867 let next_line_start = buffer.clip_point(
2868 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2869 Bias::Left,
2870 );
2871 start = buffer.anchor_before(line_start);
2872 end = buffer.anchor_before(next_line_start);
2873 mode = SelectMode::Line(start..end);
2874 auto_scroll = true;
2875 }
2876 _ => {
2877 start = buffer.anchor_before(0);
2878 end = buffer.anchor_before(buffer.len());
2879 mode = SelectMode::All;
2880 auto_scroll = false;
2881 }
2882 }
2883 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
2884
2885 let point_to_delete: Option<usize> = {
2886 let selected_points: Vec<Selection<Point>> =
2887 self.selections.disjoint_in_range(start..end, cx);
2888
2889 if !add || click_count > 1 {
2890 None
2891 } else if !selected_points.is_empty() {
2892 Some(selected_points[0].id)
2893 } else {
2894 let clicked_point_already_selected =
2895 self.selections.disjoint.iter().find(|selection| {
2896 selection.start.to_point(buffer) == start.to_point(buffer)
2897 || selection.end.to_point(buffer) == end.to_point(buffer)
2898 });
2899
2900 clicked_point_already_selected.map(|selection| selection.id)
2901 }
2902 };
2903
2904 let selections_count = self.selections.count();
2905
2906 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
2907 if let Some(point_to_delete) = point_to_delete {
2908 s.delete(point_to_delete);
2909
2910 if selections_count == 1 {
2911 s.set_pending_anchor_range(start..end, mode);
2912 }
2913 } else {
2914 if !add {
2915 s.clear_disjoint();
2916 } else if click_count > 1 {
2917 s.delete(newest_selection.id)
2918 }
2919
2920 s.set_pending_anchor_range(start..end, mode);
2921 }
2922 });
2923 }
2924
2925 fn begin_columnar_selection(
2926 &mut self,
2927 position: DisplayPoint,
2928 goal_column: u32,
2929 reset: bool,
2930 window: &mut Window,
2931 cx: &mut Context<Self>,
2932 ) {
2933 if !self.focus_handle.is_focused(window) {
2934 self.last_focused_descendant = None;
2935 window.focus(&self.focus_handle);
2936 }
2937
2938 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2939
2940 if reset {
2941 let pointer_position = display_map
2942 .buffer_snapshot
2943 .anchor_before(position.to_point(&display_map));
2944
2945 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
2946 s.clear_disjoint();
2947 s.set_pending_anchor_range(
2948 pointer_position..pointer_position,
2949 SelectMode::Character,
2950 );
2951 });
2952 }
2953
2954 let tail = self.selections.newest::<Point>(cx).tail();
2955 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
2956
2957 if !reset {
2958 self.select_columns(
2959 tail.to_display_point(&display_map),
2960 position,
2961 goal_column,
2962 &display_map,
2963 window,
2964 cx,
2965 );
2966 }
2967 }
2968
2969 fn update_selection(
2970 &mut self,
2971 position: DisplayPoint,
2972 goal_column: u32,
2973 scroll_delta: gpui::Point<f32>,
2974 window: &mut Window,
2975 cx: &mut Context<Self>,
2976 ) {
2977 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2978
2979 if let Some(tail) = self.columnar_selection_tail.as_ref() {
2980 let tail = tail.to_display_point(&display_map);
2981 self.select_columns(tail, position, goal_column, &display_map, window, cx);
2982 } else if let Some(mut pending) = self.selections.pending_anchor() {
2983 let buffer = self.buffer.read(cx).snapshot(cx);
2984 let head;
2985 let tail;
2986 let mode = self.selections.pending_mode().unwrap();
2987 match &mode {
2988 SelectMode::Character => {
2989 head = position.to_point(&display_map);
2990 tail = pending.tail().to_point(&buffer);
2991 }
2992 SelectMode::Word(original_range) => {
2993 let original_display_range = original_range.start.to_display_point(&display_map)
2994 ..original_range.end.to_display_point(&display_map);
2995 let original_buffer_range = original_display_range.start.to_point(&display_map)
2996 ..original_display_range.end.to_point(&display_map);
2997 if movement::is_inside_word(&display_map, position)
2998 || original_display_range.contains(&position)
2999 {
3000 let word_range = movement::surrounding_word(&display_map, position);
3001 if word_range.start < original_display_range.start {
3002 head = word_range.start.to_point(&display_map);
3003 } else {
3004 head = word_range.end.to_point(&display_map);
3005 }
3006 } else {
3007 head = position.to_point(&display_map);
3008 }
3009
3010 if head <= original_buffer_range.start {
3011 tail = original_buffer_range.end;
3012 } else {
3013 tail = original_buffer_range.start;
3014 }
3015 }
3016 SelectMode::Line(original_range) => {
3017 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3018
3019 let position = display_map
3020 .clip_point(position, Bias::Left)
3021 .to_point(&display_map);
3022 let line_start = display_map.prev_line_boundary(position).0;
3023 let next_line_start = buffer.clip_point(
3024 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3025 Bias::Left,
3026 );
3027
3028 if line_start < original_range.start {
3029 head = line_start
3030 } else {
3031 head = next_line_start
3032 }
3033
3034 if head <= original_range.start {
3035 tail = original_range.end;
3036 } else {
3037 tail = original_range.start;
3038 }
3039 }
3040 SelectMode::All => {
3041 return;
3042 }
3043 };
3044
3045 if head < tail {
3046 pending.start = buffer.anchor_before(head);
3047 pending.end = buffer.anchor_before(tail);
3048 pending.reversed = true;
3049 } else {
3050 pending.start = buffer.anchor_before(tail);
3051 pending.end = buffer.anchor_before(head);
3052 pending.reversed = false;
3053 }
3054
3055 self.change_selections(None, window, cx, |s| {
3056 s.set_pending(pending, mode);
3057 });
3058 } else {
3059 log::error!("update_selection dispatched with no pending selection");
3060 return;
3061 }
3062
3063 self.apply_scroll_delta(scroll_delta, window, cx);
3064 cx.notify();
3065 }
3066
3067 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3068 self.columnar_selection_tail.take();
3069 if self.selections.pending_anchor().is_some() {
3070 let selections = self.selections.all::<usize>(cx);
3071 self.change_selections(None, window, cx, |s| {
3072 s.select(selections);
3073 s.clear_pending();
3074 });
3075 }
3076 }
3077
3078 fn select_columns(
3079 &mut self,
3080 tail: DisplayPoint,
3081 head: DisplayPoint,
3082 goal_column: u32,
3083 display_map: &DisplaySnapshot,
3084 window: &mut Window,
3085 cx: &mut Context<Self>,
3086 ) {
3087 let start_row = cmp::min(tail.row(), head.row());
3088 let end_row = cmp::max(tail.row(), head.row());
3089 let start_column = cmp::min(tail.column(), goal_column);
3090 let end_column = cmp::max(tail.column(), goal_column);
3091 let reversed = start_column < tail.column();
3092
3093 let selection_ranges = (start_row.0..=end_row.0)
3094 .map(DisplayRow)
3095 .filter_map(|row| {
3096 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
3097 let start = display_map
3098 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3099 .to_point(display_map);
3100 let end = display_map
3101 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3102 .to_point(display_map);
3103 if reversed {
3104 Some(end..start)
3105 } else {
3106 Some(start..end)
3107 }
3108 } else {
3109 None
3110 }
3111 })
3112 .collect::<Vec<_>>();
3113
3114 self.change_selections(None, window, cx, |s| {
3115 s.select_ranges(selection_ranges);
3116 });
3117 cx.notify();
3118 }
3119
3120 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3121 self.selections
3122 .all_adjusted(cx)
3123 .iter()
3124 .any(|selection| !selection.is_empty())
3125 }
3126
3127 pub fn has_pending_nonempty_selection(&self) -> bool {
3128 let pending_nonempty_selection = match self.selections.pending_anchor() {
3129 Some(Selection { start, end, .. }) => start != end,
3130 None => false,
3131 };
3132
3133 pending_nonempty_selection
3134 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3135 }
3136
3137 pub fn has_pending_selection(&self) -> bool {
3138 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3139 }
3140
3141 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3142 self.selection_mark_mode = false;
3143
3144 if self.clear_expanded_diff_hunks(cx) {
3145 cx.notify();
3146 return;
3147 }
3148 if self.dismiss_menus_and_popups(true, window, cx) {
3149 return;
3150 }
3151
3152 if self.mode.is_full()
3153 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3154 {
3155 return;
3156 }
3157
3158 cx.propagate();
3159 }
3160
3161 pub fn dismiss_menus_and_popups(
3162 &mut self,
3163 is_user_requested: bool,
3164 window: &mut Window,
3165 cx: &mut Context<Self>,
3166 ) -> bool {
3167 if self.take_rename(false, window, cx).is_some() {
3168 return true;
3169 }
3170
3171 if hide_hover(self, cx) {
3172 return true;
3173 }
3174
3175 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3176 return true;
3177 }
3178
3179 if self.hide_context_menu(window, cx).is_some() {
3180 return true;
3181 }
3182
3183 if self.mouse_context_menu.take().is_some() {
3184 return true;
3185 }
3186
3187 if is_user_requested && self.discard_inline_completion(true, cx) {
3188 return true;
3189 }
3190
3191 if self.snippet_stack.pop().is_some() {
3192 return true;
3193 }
3194
3195 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3196 self.dismiss_diagnostics(cx);
3197 return true;
3198 }
3199
3200 false
3201 }
3202
3203 fn linked_editing_ranges_for(
3204 &self,
3205 selection: Range<text::Anchor>,
3206 cx: &App,
3207 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3208 if self.linked_edit_ranges.is_empty() {
3209 return None;
3210 }
3211 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3212 selection.end.buffer_id.and_then(|end_buffer_id| {
3213 if selection.start.buffer_id != Some(end_buffer_id) {
3214 return None;
3215 }
3216 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3217 let snapshot = buffer.read(cx).snapshot();
3218 self.linked_edit_ranges
3219 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3220 .map(|ranges| (ranges, snapshot, buffer))
3221 })?;
3222 use text::ToOffset as TO;
3223 // find offset from the start of current range to current cursor position
3224 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3225
3226 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3227 let start_difference = start_offset - start_byte_offset;
3228 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3229 let end_difference = end_offset - start_byte_offset;
3230 // Current range has associated linked ranges.
3231 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3232 for range in linked_ranges.iter() {
3233 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3234 let end_offset = start_offset + end_difference;
3235 let start_offset = start_offset + start_difference;
3236 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3237 continue;
3238 }
3239 if self.selections.disjoint_anchor_ranges().any(|s| {
3240 if s.start.buffer_id != selection.start.buffer_id
3241 || s.end.buffer_id != selection.end.buffer_id
3242 {
3243 return false;
3244 }
3245 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3246 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3247 }) {
3248 continue;
3249 }
3250 let start = buffer_snapshot.anchor_after(start_offset);
3251 let end = buffer_snapshot.anchor_after(end_offset);
3252 linked_edits
3253 .entry(buffer.clone())
3254 .or_default()
3255 .push(start..end);
3256 }
3257 Some(linked_edits)
3258 }
3259
3260 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3261 let text: Arc<str> = text.into();
3262
3263 if self.read_only(cx) {
3264 return;
3265 }
3266
3267 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3268
3269 let selections = self.selections.all_adjusted(cx);
3270 let mut bracket_inserted = false;
3271 let mut edits = Vec::new();
3272 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3273 let mut new_selections = Vec::with_capacity(selections.len());
3274 let mut new_autoclose_regions = Vec::new();
3275 let snapshot = self.buffer.read(cx).read(cx);
3276 let mut clear_linked_edit_ranges = false;
3277
3278 for (selection, autoclose_region) in
3279 self.selections_with_autoclose_regions(selections, &snapshot)
3280 {
3281 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3282 // Determine if the inserted text matches the opening or closing
3283 // bracket of any of this language's bracket pairs.
3284 let mut bracket_pair = None;
3285 let mut is_bracket_pair_start = false;
3286 let mut is_bracket_pair_end = false;
3287 if !text.is_empty() {
3288 let mut bracket_pair_matching_end = None;
3289 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3290 // and they are removing the character that triggered IME popup.
3291 for (pair, enabled) in scope.brackets() {
3292 if !pair.close && !pair.surround {
3293 continue;
3294 }
3295
3296 if enabled && pair.start.ends_with(text.as_ref()) {
3297 let prefix_len = pair.start.len() - text.len();
3298 let preceding_text_matches_prefix = prefix_len == 0
3299 || (selection.start.column >= (prefix_len as u32)
3300 && snapshot.contains_str_at(
3301 Point::new(
3302 selection.start.row,
3303 selection.start.column - (prefix_len as u32),
3304 ),
3305 &pair.start[..prefix_len],
3306 ));
3307 if preceding_text_matches_prefix {
3308 bracket_pair = Some(pair.clone());
3309 is_bracket_pair_start = true;
3310 break;
3311 }
3312 }
3313 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3314 {
3315 // take first bracket pair matching end, but don't break in case a later bracket
3316 // pair matches start
3317 bracket_pair_matching_end = Some(pair.clone());
3318 }
3319 }
3320 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3321 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3322 is_bracket_pair_end = true;
3323 }
3324 }
3325
3326 if let Some(bracket_pair) = bracket_pair {
3327 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3328 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3329 let auto_surround =
3330 self.use_auto_surround && snapshot_settings.use_auto_surround;
3331 if selection.is_empty() {
3332 if is_bracket_pair_start {
3333 // If the inserted text is a suffix of an opening bracket and the
3334 // selection is preceded by the rest of the opening bracket, then
3335 // insert the closing bracket.
3336 let following_text_allows_autoclose = snapshot
3337 .chars_at(selection.start)
3338 .next()
3339 .map_or(true, |c| scope.should_autoclose_before(c));
3340
3341 let preceding_text_allows_autoclose = selection.start.column == 0
3342 || snapshot.reversed_chars_at(selection.start).next().map_or(
3343 true,
3344 |c| {
3345 bracket_pair.start != bracket_pair.end
3346 || !snapshot
3347 .char_classifier_at(selection.start)
3348 .is_word(c)
3349 },
3350 );
3351
3352 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3353 && bracket_pair.start.len() == 1
3354 {
3355 let target = bracket_pair.start.chars().next().unwrap();
3356 let current_line_count = snapshot
3357 .reversed_chars_at(selection.start)
3358 .take_while(|&c| c != '\n')
3359 .filter(|&c| c == target)
3360 .count();
3361 current_line_count % 2 == 1
3362 } else {
3363 false
3364 };
3365
3366 if autoclose
3367 && bracket_pair.close
3368 && following_text_allows_autoclose
3369 && preceding_text_allows_autoclose
3370 && !is_closing_quote
3371 {
3372 let anchor = snapshot.anchor_before(selection.end);
3373 new_selections.push((selection.map(|_| anchor), text.len()));
3374 new_autoclose_regions.push((
3375 anchor,
3376 text.len(),
3377 selection.id,
3378 bracket_pair.clone(),
3379 ));
3380 edits.push((
3381 selection.range(),
3382 format!("{}{}", text, bracket_pair.end).into(),
3383 ));
3384 bracket_inserted = true;
3385 continue;
3386 }
3387 }
3388
3389 if let Some(region) = autoclose_region {
3390 // If the selection is followed by an auto-inserted closing bracket,
3391 // then don't insert that closing bracket again; just move the selection
3392 // past the closing bracket.
3393 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3394 && text.as_ref() == region.pair.end.as_str();
3395 if should_skip {
3396 let anchor = snapshot.anchor_after(selection.end);
3397 new_selections
3398 .push((selection.map(|_| anchor), region.pair.end.len()));
3399 continue;
3400 }
3401 }
3402
3403 let always_treat_brackets_as_autoclosed = snapshot
3404 .language_settings_at(selection.start, cx)
3405 .always_treat_brackets_as_autoclosed;
3406 if always_treat_brackets_as_autoclosed
3407 && is_bracket_pair_end
3408 && snapshot.contains_str_at(selection.end, text.as_ref())
3409 {
3410 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3411 // and the inserted text is a closing bracket and the selection is followed
3412 // by the closing bracket then move the selection past the closing bracket.
3413 let anchor = snapshot.anchor_after(selection.end);
3414 new_selections.push((selection.map(|_| anchor), text.len()));
3415 continue;
3416 }
3417 }
3418 // If an opening bracket is 1 character long and is typed while
3419 // text is selected, then surround that text with the bracket pair.
3420 else if auto_surround
3421 && bracket_pair.surround
3422 && is_bracket_pair_start
3423 && bracket_pair.start.chars().count() == 1
3424 {
3425 edits.push((selection.start..selection.start, text.clone()));
3426 edits.push((
3427 selection.end..selection.end,
3428 bracket_pair.end.as_str().into(),
3429 ));
3430 bracket_inserted = true;
3431 new_selections.push((
3432 Selection {
3433 id: selection.id,
3434 start: snapshot.anchor_after(selection.start),
3435 end: snapshot.anchor_before(selection.end),
3436 reversed: selection.reversed,
3437 goal: selection.goal,
3438 },
3439 0,
3440 ));
3441 continue;
3442 }
3443 }
3444 }
3445
3446 if self.auto_replace_emoji_shortcode
3447 && selection.is_empty()
3448 && text.as_ref().ends_with(':')
3449 {
3450 if let Some(possible_emoji_short_code) =
3451 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3452 {
3453 if !possible_emoji_short_code.is_empty() {
3454 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3455 let emoji_shortcode_start = Point::new(
3456 selection.start.row,
3457 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3458 );
3459
3460 // Remove shortcode from buffer
3461 edits.push((
3462 emoji_shortcode_start..selection.start,
3463 "".to_string().into(),
3464 ));
3465 new_selections.push((
3466 Selection {
3467 id: selection.id,
3468 start: snapshot.anchor_after(emoji_shortcode_start),
3469 end: snapshot.anchor_before(selection.start),
3470 reversed: selection.reversed,
3471 goal: selection.goal,
3472 },
3473 0,
3474 ));
3475
3476 // Insert emoji
3477 let selection_start_anchor = snapshot.anchor_after(selection.start);
3478 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3479 edits.push((selection.start..selection.end, emoji.to_string().into()));
3480
3481 continue;
3482 }
3483 }
3484 }
3485 }
3486
3487 // If not handling any auto-close operation, then just replace the selected
3488 // text with the given input and move the selection to the end of the
3489 // newly inserted text.
3490 let anchor = snapshot.anchor_after(selection.end);
3491 if !self.linked_edit_ranges.is_empty() {
3492 let start_anchor = snapshot.anchor_before(selection.start);
3493
3494 let is_word_char = text.chars().next().map_or(true, |char| {
3495 let classifier = snapshot.char_classifier_at(start_anchor.to_offset(&snapshot));
3496 classifier.is_word(char)
3497 });
3498
3499 if is_word_char {
3500 if let Some(ranges) = self
3501 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3502 {
3503 for (buffer, edits) in ranges {
3504 linked_edits
3505 .entry(buffer.clone())
3506 .or_default()
3507 .extend(edits.into_iter().map(|range| (range, text.clone())));
3508 }
3509 }
3510 } else {
3511 clear_linked_edit_ranges = true;
3512 }
3513 }
3514
3515 new_selections.push((selection.map(|_| anchor), 0));
3516 edits.push((selection.start..selection.end, text.clone()));
3517 }
3518
3519 drop(snapshot);
3520
3521 self.transact(window, cx, |this, window, cx| {
3522 if clear_linked_edit_ranges {
3523 this.linked_edit_ranges.clear();
3524 }
3525 let initial_buffer_versions =
3526 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3527
3528 this.buffer.update(cx, |buffer, cx| {
3529 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3530 });
3531 for (buffer, edits) in linked_edits {
3532 buffer.update(cx, |buffer, cx| {
3533 let snapshot = buffer.snapshot();
3534 let edits = edits
3535 .into_iter()
3536 .map(|(range, text)| {
3537 use text::ToPoint as TP;
3538 let end_point = TP::to_point(&range.end, &snapshot);
3539 let start_point = TP::to_point(&range.start, &snapshot);
3540 (start_point..end_point, text)
3541 })
3542 .sorted_by_key(|(range, _)| range.start);
3543 buffer.edit(edits, None, cx);
3544 })
3545 }
3546 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3547 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3548 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3549 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3550 .zip(new_selection_deltas)
3551 .map(|(selection, delta)| Selection {
3552 id: selection.id,
3553 start: selection.start + delta,
3554 end: selection.end + delta,
3555 reversed: selection.reversed,
3556 goal: SelectionGoal::None,
3557 })
3558 .collect::<Vec<_>>();
3559
3560 let mut i = 0;
3561 for (position, delta, selection_id, pair) in new_autoclose_regions {
3562 let position = position.to_offset(&map.buffer_snapshot) + delta;
3563 let start = map.buffer_snapshot.anchor_before(position);
3564 let end = map.buffer_snapshot.anchor_after(position);
3565 while let Some(existing_state) = this.autoclose_regions.get(i) {
3566 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3567 Ordering::Less => i += 1,
3568 Ordering::Greater => break,
3569 Ordering::Equal => {
3570 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3571 Ordering::Less => i += 1,
3572 Ordering::Equal => break,
3573 Ordering::Greater => break,
3574 }
3575 }
3576 }
3577 }
3578 this.autoclose_regions.insert(
3579 i,
3580 AutocloseRegion {
3581 selection_id,
3582 range: start..end,
3583 pair,
3584 },
3585 );
3586 }
3587
3588 let had_active_inline_completion = this.has_active_inline_completion();
3589 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3590 s.select(new_selections)
3591 });
3592
3593 if !bracket_inserted {
3594 if let Some(on_type_format_task) =
3595 this.trigger_on_type_formatting(text.to_string(), window, cx)
3596 {
3597 on_type_format_task.detach_and_log_err(cx);
3598 }
3599 }
3600
3601 let editor_settings = EditorSettings::get_global(cx);
3602 if bracket_inserted
3603 && (editor_settings.auto_signature_help
3604 || editor_settings.show_signature_help_after_edits)
3605 {
3606 this.show_signature_help(&ShowSignatureHelp, window, cx);
3607 }
3608
3609 let trigger_in_words =
3610 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3611 if this.hard_wrap.is_some() {
3612 let latest: Range<Point> = this.selections.newest(cx).range();
3613 if latest.is_empty()
3614 && this
3615 .buffer()
3616 .read(cx)
3617 .snapshot(cx)
3618 .line_len(MultiBufferRow(latest.start.row))
3619 == latest.start.column
3620 {
3621 this.rewrap_impl(
3622 RewrapOptions {
3623 override_language_settings: true,
3624 preserve_existing_whitespace: true,
3625 },
3626 cx,
3627 )
3628 }
3629 }
3630 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3631 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3632 this.refresh_inline_completion(true, false, window, cx);
3633 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3634 });
3635 }
3636
3637 fn find_possible_emoji_shortcode_at_position(
3638 snapshot: &MultiBufferSnapshot,
3639 position: Point,
3640 ) -> Option<String> {
3641 let mut chars = Vec::new();
3642 let mut found_colon = false;
3643 for char in snapshot.reversed_chars_at(position).take(100) {
3644 // Found a possible emoji shortcode in the middle of the buffer
3645 if found_colon {
3646 if char.is_whitespace() {
3647 chars.reverse();
3648 return Some(chars.iter().collect());
3649 }
3650 // If the previous character is not a whitespace, we are in the middle of a word
3651 // and we only want to complete the shortcode if the word is made up of other emojis
3652 let mut containing_word = String::new();
3653 for ch in snapshot
3654 .reversed_chars_at(position)
3655 .skip(chars.len() + 1)
3656 .take(100)
3657 {
3658 if ch.is_whitespace() {
3659 break;
3660 }
3661 containing_word.push(ch);
3662 }
3663 let containing_word = containing_word.chars().rev().collect::<String>();
3664 if util::word_consists_of_emojis(containing_word.as_str()) {
3665 chars.reverse();
3666 return Some(chars.iter().collect());
3667 }
3668 }
3669
3670 if char.is_whitespace() || !char.is_ascii() {
3671 return None;
3672 }
3673 if char == ':' {
3674 found_colon = true;
3675 } else {
3676 chars.push(char);
3677 }
3678 }
3679 // Found a possible emoji shortcode at the beginning of the buffer
3680 chars.reverse();
3681 Some(chars.iter().collect())
3682 }
3683
3684 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3685 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3686 self.transact(window, cx, |this, window, cx| {
3687 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
3688 let selections = this.selections.all::<usize>(cx);
3689 let multi_buffer = this.buffer.read(cx);
3690 let buffer = multi_buffer.snapshot(cx);
3691 selections
3692 .iter()
3693 .map(|selection| {
3694 let start_point = selection.start.to_point(&buffer);
3695 let mut indent =
3696 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3697 indent.len = cmp::min(indent.len, start_point.column);
3698 let start = selection.start;
3699 let end = selection.end;
3700 let selection_is_empty = start == end;
3701 let language_scope = buffer.language_scope_at(start);
3702 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
3703 &language_scope
3704 {
3705 let insert_extra_newline =
3706 insert_extra_newline_brackets(&buffer, start..end, language)
3707 || insert_extra_newline_tree_sitter(&buffer, start..end);
3708
3709 // Comment extension on newline is allowed only for cursor selections
3710 let comment_delimiter = maybe!({
3711 if !selection_is_empty {
3712 return None;
3713 }
3714
3715 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3716 return None;
3717 }
3718
3719 let delimiters = language.line_comment_prefixes();
3720 let max_len_of_delimiter =
3721 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3722 let (snapshot, range) =
3723 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3724
3725 let mut index_of_first_non_whitespace = 0;
3726 let comment_candidate = snapshot
3727 .chars_for_range(range)
3728 .skip_while(|c| {
3729 let should_skip = c.is_whitespace();
3730 if should_skip {
3731 index_of_first_non_whitespace += 1;
3732 }
3733 should_skip
3734 })
3735 .take(max_len_of_delimiter)
3736 .collect::<String>();
3737 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3738 comment_candidate.starts_with(comment_prefix.as_ref())
3739 })?;
3740 let cursor_is_placed_after_comment_marker =
3741 index_of_first_non_whitespace + comment_prefix.len()
3742 <= start_point.column as usize;
3743 if cursor_is_placed_after_comment_marker {
3744 Some(comment_prefix.clone())
3745 } else {
3746 None
3747 }
3748 });
3749 (comment_delimiter, insert_extra_newline)
3750 } else {
3751 (None, false)
3752 };
3753
3754 let capacity_for_delimiter = comment_delimiter
3755 .as_deref()
3756 .map(str::len)
3757 .unwrap_or_default();
3758 let mut new_text =
3759 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3760 new_text.push('\n');
3761 new_text.extend(indent.chars());
3762 if let Some(delimiter) = &comment_delimiter {
3763 new_text.push_str(delimiter);
3764 }
3765 if insert_extra_newline {
3766 new_text = new_text.repeat(2);
3767 }
3768
3769 let anchor = buffer.anchor_after(end);
3770 let new_selection = selection.map(|_| anchor);
3771 (
3772 (start..end, new_text),
3773 (insert_extra_newline, new_selection),
3774 )
3775 })
3776 .unzip()
3777 };
3778
3779 this.edit_with_autoindent(edits, cx);
3780 let buffer = this.buffer.read(cx).snapshot(cx);
3781 let new_selections = selection_fixup_info
3782 .into_iter()
3783 .map(|(extra_newline_inserted, new_selection)| {
3784 let mut cursor = new_selection.end.to_point(&buffer);
3785 if extra_newline_inserted {
3786 cursor.row -= 1;
3787 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
3788 }
3789 new_selection.map(|_| cursor)
3790 })
3791 .collect();
3792
3793 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3794 s.select(new_selections)
3795 });
3796 this.refresh_inline_completion(true, false, window, cx);
3797 });
3798 }
3799
3800 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
3801 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3802
3803 let buffer = self.buffer.read(cx);
3804 let snapshot = buffer.snapshot(cx);
3805
3806 let mut edits = Vec::new();
3807 let mut rows = Vec::new();
3808
3809 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
3810 let cursor = selection.head();
3811 let row = cursor.row;
3812
3813 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
3814
3815 let newline = "\n".to_string();
3816 edits.push((start_of_line..start_of_line, newline));
3817
3818 rows.push(row + rows_inserted as u32);
3819 }
3820
3821 self.transact(window, cx, |editor, window, cx| {
3822 editor.edit(edits, cx);
3823
3824 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3825 let mut index = 0;
3826 s.move_cursors_with(|map, _, _| {
3827 let row = rows[index];
3828 index += 1;
3829
3830 let point = Point::new(row, 0);
3831 let boundary = map.next_line_boundary(point).1;
3832 let clipped = map.clip_point(boundary, Bias::Left);
3833
3834 (clipped, SelectionGoal::None)
3835 });
3836 });
3837
3838 let mut indent_edits = Vec::new();
3839 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3840 for row in rows {
3841 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3842 for (row, indent) in indents {
3843 if indent.len == 0 {
3844 continue;
3845 }
3846
3847 let text = match indent.kind {
3848 IndentKind::Space => " ".repeat(indent.len as usize),
3849 IndentKind::Tab => "\t".repeat(indent.len as usize),
3850 };
3851 let point = Point::new(row.0, 0);
3852 indent_edits.push((point..point, text));
3853 }
3854 }
3855 editor.edit(indent_edits, cx);
3856 });
3857 }
3858
3859 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
3860 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3861
3862 let buffer = self.buffer.read(cx);
3863 let snapshot = buffer.snapshot(cx);
3864
3865 let mut edits = Vec::new();
3866 let mut rows = Vec::new();
3867 let mut rows_inserted = 0;
3868
3869 for selection in self.selections.all_adjusted(cx) {
3870 let cursor = selection.head();
3871 let row = cursor.row;
3872
3873 let point = Point::new(row + 1, 0);
3874 let start_of_line = snapshot.clip_point(point, Bias::Left);
3875
3876 let newline = "\n".to_string();
3877 edits.push((start_of_line..start_of_line, newline));
3878
3879 rows_inserted += 1;
3880 rows.push(row + rows_inserted);
3881 }
3882
3883 self.transact(window, cx, |editor, window, cx| {
3884 editor.edit(edits, cx);
3885
3886 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3887 let mut index = 0;
3888 s.move_cursors_with(|map, _, _| {
3889 let row = rows[index];
3890 index += 1;
3891
3892 let point = Point::new(row, 0);
3893 let boundary = map.next_line_boundary(point).1;
3894 let clipped = map.clip_point(boundary, Bias::Left);
3895
3896 (clipped, SelectionGoal::None)
3897 });
3898 });
3899
3900 let mut indent_edits = Vec::new();
3901 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3902 for row in rows {
3903 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3904 for (row, indent) in indents {
3905 if indent.len == 0 {
3906 continue;
3907 }
3908
3909 let text = match indent.kind {
3910 IndentKind::Space => " ".repeat(indent.len as usize),
3911 IndentKind::Tab => "\t".repeat(indent.len as usize),
3912 };
3913 let point = Point::new(row.0, 0);
3914 indent_edits.push((point..point, text));
3915 }
3916 }
3917 editor.edit(indent_edits, cx);
3918 });
3919 }
3920
3921 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3922 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
3923 original_indent_columns: Vec::new(),
3924 });
3925 self.insert_with_autoindent_mode(text, autoindent, window, cx);
3926 }
3927
3928 fn insert_with_autoindent_mode(
3929 &mut self,
3930 text: &str,
3931 autoindent_mode: Option<AutoindentMode>,
3932 window: &mut Window,
3933 cx: &mut Context<Self>,
3934 ) {
3935 if self.read_only(cx) {
3936 return;
3937 }
3938
3939 let text: Arc<str> = text.into();
3940 self.transact(window, cx, |this, window, cx| {
3941 let old_selections = this.selections.all_adjusted(cx);
3942 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
3943 let anchors = {
3944 let snapshot = buffer.read(cx);
3945 old_selections
3946 .iter()
3947 .map(|s| {
3948 let anchor = snapshot.anchor_after(s.head());
3949 s.map(|_| anchor)
3950 })
3951 .collect::<Vec<_>>()
3952 };
3953 buffer.edit(
3954 old_selections
3955 .iter()
3956 .map(|s| (s.start..s.end, text.clone())),
3957 autoindent_mode,
3958 cx,
3959 );
3960 anchors
3961 });
3962
3963 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3964 s.select_anchors(selection_anchors);
3965 });
3966
3967 cx.notify();
3968 });
3969 }
3970
3971 fn trigger_completion_on_input(
3972 &mut self,
3973 text: &str,
3974 trigger_in_words: bool,
3975 window: &mut Window,
3976 cx: &mut Context<Self>,
3977 ) {
3978 let ignore_completion_provider = self
3979 .context_menu
3980 .borrow()
3981 .as_ref()
3982 .map(|menu| match menu {
3983 CodeContextMenu::Completions(completions_menu) => {
3984 completions_menu.ignore_completion_provider
3985 }
3986 CodeContextMenu::CodeActions(_) => false,
3987 })
3988 .unwrap_or(false);
3989
3990 if ignore_completion_provider {
3991 self.show_word_completions(&ShowWordCompletions, window, cx);
3992 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
3993 self.show_completions(
3994 &ShowCompletions {
3995 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
3996 },
3997 window,
3998 cx,
3999 );
4000 } else {
4001 self.hide_context_menu(window, cx);
4002 }
4003 }
4004
4005 fn is_completion_trigger(
4006 &self,
4007 text: &str,
4008 trigger_in_words: bool,
4009 cx: &mut Context<Self>,
4010 ) -> bool {
4011 let position = self.selections.newest_anchor().head();
4012 let multibuffer = self.buffer.read(cx);
4013 let Some(buffer) = position
4014 .buffer_id
4015 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4016 else {
4017 return false;
4018 };
4019
4020 if let Some(completion_provider) = &self.completion_provider {
4021 completion_provider.is_completion_trigger(
4022 &buffer,
4023 position.text_anchor,
4024 text,
4025 trigger_in_words,
4026 cx,
4027 )
4028 } else {
4029 false
4030 }
4031 }
4032
4033 /// If any empty selections is touching the start of its innermost containing autoclose
4034 /// region, expand it to select the brackets.
4035 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4036 let selections = self.selections.all::<usize>(cx);
4037 let buffer = self.buffer.read(cx).read(cx);
4038 let new_selections = self
4039 .selections_with_autoclose_regions(selections, &buffer)
4040 .map(|(mut selection, region)| {
4041 if !selection.is_empty() {
4042 return selection;
4043 }
4044
4045 if let Some(region) = region {
4046 let mut range = region.range.to_offset(&buffer);
4047 if selection.start == range.start && range.start >= region.pair.start.len() {
4048 range.start -= region.pair.start.len();
4049 if buffer.contains_str_at(range.start, ®ion.pair.start)
4050 && buffer.contains_str_at(range.end, ®ion.pair.end)
4051 {
4052 range.end += region.pair.end.len();
4053 selection.start = range.start;
4054 selection.end = range.end;
4055
4056 return selection;
4057 }
4058 }
4059 }
4060
4061 let always_treat_brackets_as_autoclosed = buffer
4062 .language_settings_at(selection.start, cx)
4063 .always_treat_brackets_as_autoclosed;
4064
4065 if !always_treat_brackets_as_autoclosed {
4066 return selection;
4067 }
4068
4069 if let Some(scope) = buffer.language_scope_at(selection.start) {
4070 for (pair, enabled) in scope.brackets() {
4071 if !enabled || !pair.close {
4072 continue;
4073 }
4074
4075 if buffer.contains_str_at(selection.start, &pair.end) {
4076 let pair_start_len = pair.start.len();
4077 if buffer.contains_str_at(
4078 selection.start.saturating_sub(pair_start_len),
4079 &pair.start,
4080 ) {
4081 selection.start -= pair_start_len;
4082 selection.end += pair.end.len();
4083
4084 return selection;
4085 }
4086 }
4087 }
4088 }
4089
4090 selection
4091 })
4092 .collect();
4093
4094 drop(buffer);
4095 self.change_selections(None, window, cx, |selections| {
4096 selections.select(new_selections)
4097 });
4098 }
4099
4100 /// Iterate the given selections, and for each one, find the smallest surrounding
4101 /// autoclose region. This uses the ordering of the selections and the autoclose
4102 /// regions to avoid repeated comparisons.
4103 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4104 &'a self,
4105 selections: impl IntoIterator<Item = Selection<D>>,
4106 buffer: &'a MultiBufferSnapshot,
4107 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4108 let mut i = 0;
4109 let mut regions = self.autoclose_regions.as_slice();
4110 selections.into_iter().map(move |selection| {
4111 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4112
4113 let mut enclosing = None;
4114 while let Some(pair_state) = regions.get(i) {
4115 if pair_state.range.end.to_offset(buffer) < range.start {
4116 regions = ®ions[i + 1..];
4117 i = 0;
4118 } else if pair_state.range.start.to_offset(buffer) > range.end {
4119 break;
4120 } else {
4121 if pair_state.selection_id == selection.id {
4122 enclosing = Some(pair_state);
4123 }
4124 i += 1;
4125 }
4126 }
4127
4128 (selection, enclosing)
4129 })
4130 }
4131
4132 /// Remove any autoclose regions that no longer contain their selection.
4133 fn invalidate_autoclose_regions(
4134 &mut self,
4135 mut selections: &[Selection<Anchor>],
4136 buffer: &MultiBufferSnapshot,
4137 ) {
4138 self.autoclose_regions.retain(|state| {
4139 let mut i = 0;
4140 while let Some(selection) = selections.get(i) {
4141 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4142 selections = &selections[1..];
4143 continue;
4144 }
4145 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4146 break;
4147 }
4148 if selection.id == state.selection_id {
4149 return true;
4150 } else {
4151 i += 1;
4152 }
4153 }
4154 false
4155 });
4156 }
4157
4158 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4159 let offset = position.to_offset(buffer);
4160 let (word_range, kind) = buffer.surrounding_word(offset, true);
4161 if offset > word_range.start && kind == Some(CharKind::Word) {
4162 Some(
4163 buffer
4164 .text_for_range(word_range.start..offset)
4165 .collect::<String>(),
4166 )
4167 } else {
4168 None
4169 }
4170 }
4171
4172 pub fn toggle_inlay_hints(
4173 &mut self,
4174 _: &ToggleInlayHints,
4175 _: &mut Window,
4176 cx: &mut Context<Self>,
4177 ) {
4178 self.refresh_inlay_hints(
4179 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4180 cx,
4181 );
4182 }
4183
4184 pub fn inlay_hints_enabled(&self) -> bool {
4185 self.inlay_hint_cache.enabled
4186 }
4187
4188 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4189 if self.semantics_provider.is_none() || !self.mode.is_full() {
4190 return;
4191 }
4192
4193 let reason_description = reason.description();
4194 let ignore_debounce = matches!(
4195 reason,
4196 InlayHintRefreshReason::SettingsChange(_)
4197 | InlayHintRefreshReason::Toggle(_)
4198 | InlayHintRefreshReason::ExcerptsRemoved(_)
4199 | InlayHintRefreshReason::ModifiersChanged(_)
4200 );
4201 let (invalidate_cache, required_languages) = match reason {
4202 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4203 match self.inlay_hint_cache.modifiers_override(enabled) {
4204 Some(enabled) => {
4205 if enabled {
4206 (InvalidationStrategy::RefreshRequested, None)
4207 } else {
4208 self.splice_inlays(
4209 &self
4210 .visible_inlay_hints(cx)
4211 .iter()
4212 .map(|inlay| inlay.id)
4213 .collect::<Vec<InlayId>>(),
4214 Vec::new(),
4215 cx,
4216 );
4217 return;
4218 }
4219 }
4220 None => return,
4221 }
4222 }
4223 InlayHintRefreshReason::Toggle(enabled) => {
4224 if self.inlay_hint_cache.toggle(enabled) {
4225 if enabled {
4226 (InvalidationStrategy::RefreshRequested, None)
4227 } else {
4228 self.splice_inlays(
4229 &self
4230 .visible_inlay_hints(cx)
4231 .iter()
4232 .map(|inlay| inlay.id)
4233 .collect::<Vec<InlayId>>(),
4234 Vec::new(),
4235 cx,
4236 );
4237 return;
4238 }
4239 } else {
4240 return;
4241 }
4242 }
4243 InlayHintRefreshReason::SettingsChange(new_settings) => {
4244 match self.inlay_hint_cache.update_settings(
4245 &self.buffer,
4246 new_settings,
4247 self.visible_inlay_hints(cx),
4248 cx,
4249 ) {
4250 ControlFlow::Break(Some(InlaySplice {
4251 to_remove,
4252 to_insert,
4253 })) => {
4254 self.splice_inlays(&to_remove, to_insert, cx);
4255 return;
4256 }
4257 ControlFlow::Break(None) => return,
4258 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4259 }
4260 }
4261 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4262 if let Some(InlaySplice {
4263 to_remove,
4264 to_insert,
4265 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4266 {
4267 self.splice_inlays(&to_remove, to_insert, cx);
4268 }
4269 self.display_map.update(cx, |display_map, _| {
4270 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4271 });
4272 return;
4273 }
4274 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4275 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4276 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4277 }
4278 InlayHintRefreshReason::RefreshRequested => {
4279 (InvalidationStrategy::RefreshRequested, None)
4280 }
4281 };
4282
4283 if let Some(InlaySplice {
4284 to_remove,
4285 to_insert,
4286 }) = self.inlay_hint_cache.spawn_hint_refresh(
4287 reason_description,
4288 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4289 invalidate_cache,
4290 ignore_debounce,
4291 cx,
4292 ) {
4293 self.splice_inlays(&to_remove, to_insert, cx);
4294 }
4295 }
4296
4297 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4298 self.display_map
4299 .read(cx)
4300 .current_inlays()
4301 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4302 .cloned()
4303 .collect()
4304 }
4305
4306 pub fn excerpts_for_inlay_hints_query(
4307 &self,
4308 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4309 cx: &mut Context<Editor>,
4310 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4311 let Some(project) = self.project.as_ref() else {
4312 return HashMap::default();
4313 };
4314 let project = project.read(cx);
4315 let multi_buffer = self.buffer().read(cx);
4316 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4317 let multi_buffer_visible_start = self
4318 .scroll_manager
4319 .anchor()
4320 .anchor
4321 .to_point(&multi_buffer_snapshot);
4322 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4323 multi_buffer_visible_start
4324 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4325 Bias::Left,
4326 );
4327 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4328 multi_buffer_snapshot
4329 .range_to_buffer_ranges(multi_buffer_visible_range)
4330 .into_iter()
4331 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4332 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4333 let buffer_file = project::File::from_dyn(buffer.file())?;
4334 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4335 let worktree_entry = buffer_worktree
4336 .read(cx)
4337 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4338 if worktree_entry.is_ignored {
4339 return None;
4340 }
4341
4342 let language = buffer.language()?;
4343 if let Some(restrict_to_languages) = restrict_to_languages {
4344 if !restrict_to_languages.contains(language) {
4345 return None;
4346 }
4347 }
4348 Some((
4349 excerpt_id,
4350 (
4351 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4352 buffer.version().clone(),
4353 excerpt_visible_range,
4354 ),
4355 ))
4356 })
4357 .collect()
4358 }
4359
4360 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4361 TextLayoutDetails {
4362 text_system: window.text_system().clone(),
4363 editor_style: self.style.clone().unwrap(),
4364 rem_size: window.rem_size(),
4365 scroll_anchor: self.scroll_manager.anchor(),
4366 visible_rows: self.visible_line_count(),
4367 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4368 }
4369 }
4370
4371 pub fn splice_inlays(
4372 &self,
4373 to_remove: &[InlayId],
4374 to_insert: Vec<Inlay>,
4375 cx: &mut Context<Self>,
4376 ) {
4377 self.display_map.update(cx, |display_map, cx| {
4378 display_map.splice_inlays(to_remove, to_insert, cx)
4379 });
4380 cx.notify();
4381 }
4382
4383 fn trigger_on_type_formatting(
4384 &self,
4385 input: String,
4386 window: &mut Window,
4387 cx: &mut Context<Self>,
4388 ) -> Option<Task<Result<()>>> {
4389 if input.len() != 1 {
4390 return None;
4391 }
4392
4393 let project = self.project.as_ref()?;
4394 let position = self.selections.newest_anchor().head();
4395 let (buffer, buffer_position) = self
4396 .buffer
4397 .read(cx)
4398 .text_anchor_for_position(position, cx)?;
4399
4400 let settings = language_settings::language_settings(
4401 buffer
4402 .read(cx)
4403 .language_at(buffer_position)
4404 .map(|l| l.name()),
4405 buffer.read(cx).file(),
4406 cx,
4407 );
4408 if !settings.use_on_type_format {
4409 return None;
4410 }
4411
4412 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4413 // hence we do LSP request & edit on host side only — add formats to host's history.
4414 let push_to_lsp_host_history = true;
4415 // If this is not the host, append its history with new edits.
4416 let push_to_client_history = project.read(cx).is_via_collab();
4417
4418 let on_type_formatting = project.update(cx, |project, cx| {
4419 project.on_type_format(
4420 buffer.clone(),
4421 buffer_position,
4422 input,
4423 push_to_lsp_host_history,
4424 cx,
4425 )
4426 });
4427 Some(cx.spawn_in(window, async move |editor, cx| {
4428 if let Some(transaction) = on_type_formatting.await? {
4429 if push_to_client_history {
4430 buffer
4431 .update(cx, |buffer, _| {
4432 buffer.push_transaction(transaction, Instant::now());
4433 buffer.finalize_last_transaction();
4434 })
4435 .ok();
4436 }
4437 editor.update(cx, |editor, cx| {
4438 editor.refresh_document_highlights(cx);
4439 })?;
4440 }
4441 Ok(())
4442 }))
4443 }
4444
4445 pub fn show_word_completions(
4446 &mut self,
4447 _: &ShowWordCompletions,
4448 window: &mut Window,
4449 cx: &mut Context<Self>,
4450 ) {
4451 self.open_completions_menu(true, None, window, cx);
4452 }
4453
4454 pub fn show_completions(
4455 &mut self,
4456 options: &ShowCompletions,
4457 window: &mut Window,
4458 cx: &mut Context<Self>,
4459 ) {
4460 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4461 }
4462
4463 fn open_completions_menu(
4464 &mut self,
4465 ignore_completion_provider: bool,
4466 trigger: Option<&str>,
4467 window: &mut Window,
4468 cx: &mut Context<Self>,
4469 ) {
4470 if self.pending_rename.is_some() {
4471 return;
4472 }
4473 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4474 return;
4475 }
4476
4477 let position = self.selections.newest_anchor().head();
4478 if position.diff_base_anchor.is_some() {
4479 return;
4480 }
4481 let (buffer, buffer_position) =
4482 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4483 output
4484 } else {
4485 return;
4486 };
4487 let buffer_snapshot = buffer.read(cx).snapshot();
4488 let show_completion_documentation = buffer_snapshot
4489 .settings_at(buffer_position, cx)
4490 .show_completion_documentation;
4491
4492 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4493
4494 let trigger_kind = match trigger {
4495 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4496 CompletionTriggerKind::TRIGGER_CHARACTER
4497 }
4498 _ => CompletionTriggerKind::INVOKED,
4499 };
4500 let completion_context = CompletionContext {
4501 trigger_character: trigger.and_then(|trigger| {
4502 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4503 Some(String::from(trigger))
4504 } else {
4505 None
4506 }
4507 }),
4508 trigger_kind,
4509 };
4510
4511 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4512 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4513 let word_to_exclude = buffer_snapshot
4514 .text_for_range(old_range.clone())
4515 .collect::<String>();
4516 (
4517 buffer_snapshot.anchor_before(old_range.start)
4518 ..buffer_snapshot.anchor_after(old_range.end),
4519 Some(word_to_exclude),
4520 )
4521 } else {
4522 (buffer_position..buffer_position, None)
4523 };
4524
4525 let completion_settings = language_settings(
4526 buffer_snapshot
4527 .language_at(buffer_position)
4528 .map(|language| language.name()),
4529 buffer_snapshot.file(),
4530 cx,
4531 )
4532 .completions;
4533
4534 // The document can be large, so stay in reasonable bounds when searching for words,
4535 // otherwise completion pop-up might be slow to appear.
4536 const WORD_LOOKUP_ROWS: u32 = 5_000;
4537 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4538 let min_word_search = buffer_snapshot.clip_point(
4539 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4540 Bias::Left,
4541 );
4542 let max_word_search = buffer_snapshot.clip_point(
4543 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4544 Bias::Right,
4545 );
4546 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4547 ..buffer_snapshot.point_to_offset(max_word_search);
4548
4549 let provider = self
4550 .completion_provider
4551 .as_ref()
4552 .filter(|_| !ignore_completion_provider);
4553 let skip_digits = query
4554 .as_ref()
4555 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4556
4557 let (mut words, provided_completions) = match provider {
4558 Some(provider) => {
4559 let completions = provider.completions(
4560 position.excerpt_id,
4561 &buffer,
4562 buffer_position,
4563 completion_context,
4564 window,
4565 cx,
4566 );
4567
4568 let words = match completion_settings.words {
4569 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4570 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4571 .background_spawn(async move {
4572 buffer_snapshot.words_in_range(WordsQuery {
4573 fuzzy_contents: None,
4574 range: word_search_range,
4575 skip_digits,
4576 })
4577 }),
4578 };
4579
4580 (words, completions)
4581 }
4582 None => (
4583 cx.background_spawn(async move {
4584 buffer_snapshot.words_in_range(WordsQuery {
4585 fuzzy_contents: None,
4586 range: word_search_range,
4587 skip_digits,
4588 })
4589 }),
4590 Task::ready(Ok(None)),
4591 ),
4592 };
4593
4594 let sort_completions = provider
4595 .as_ref()
4596 .map_or(false, |provider| provider.sort_completions());
4597
4598 let filter_completions = provider
4599 .as_ref()
4600 .map_or(true, |provider| provider.filter_completions());
4601
4602 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
4603
4604 let id = post_inc(&mut self.next_completion_id);
4605 let task = cx.spawn_in(window, async move |editor, cx| {
4606 async move {
4607 editor.update(cx, |this, _| {
4608 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4609 })?;
4610
4611 let mut completions = Vec::new();
4612 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
4613 completions.extend(provided_completions);
4614 if completion_settings.words == WordsCompletionMode::Fallback {
4615 words = Task::ready(BTreeMap::default());
4616 }
4617 }
4618
4619 let mut words = words.await;
4620 if let Some(word_to_exclude) = &word_to_exclude {
4621 words.remove(word_to_exclude);
4622 }
4623 for lsp_completion in &completions {
4624 words.remove(&lsp_completion.new_text);
4625 }
4626 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
4627 replace_range: old_range.clone(),
4628 new_text: word.clone(),
4629 label: CodeLabel::plain(word, None),
4630 icon_path: None,
4631 documentation: None,
4632 source: CompletionSource::BufferWord {
4633 word_range,
4634 resolved: false,
4635 },
4636 insert_text_mode: Some(InsertTextMode::AS_IS),
4637 confirm: None,
4638 }));
4639
4640 let menu = if completions.is_empty() {
4641 None
4642 } else {
4643 let mut menu = CompletionsMenu::new(
4644 id,
4645 sort_completions,
4646 show_completion_documentation,
4647 ignore_completion_provider,
4648 position,
4649 buffer.clone(),
4650 completions.into(),
4651 snippet_sort_order,
4652 );
4653
4654 menu.filter(
4655 if filter_completions {
4656 query.as_deref()
4657 } else {
4658 None
4659 },
4660 cx.background_executor().clone(),
4661 )
4662 .await;
4663
4664 menu.visible().then_some(menu)
4665 };
4666
4667 editor.update_in(cx, |editor, window, cx| {
4668 match editor.context_menu.borrow().as_ref() {
4669 None => {}
4670 Some(CodeContextMenu::Completions(prev_menu)) => {
4671 if prev_menu.id > id {
4672 return;
4673 }
4674 }
4675 _ => return,
4676 }
4677
4678 if editor.focus_handle.is_focused(window) && menu.is_some() {
4679 let mut menu = menu.unwrap();
4680 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
4681
4682 *editor.context_menu.borrow_mut() =
4683 Some(CodeContextMenu::Completions(menu));
4684
4685 if editor.show_edit_predictions_in_menu() {
4686 editor.update_visible_inline_completion(window, cx);
4687 } else {
4688 editor.discard_inline_completion(false, cx);
4689 }
4690
4691 cx.notify();
4692 } else if editor.completion_tasks.len() <= 1 {
4693 // If there are no more completion tasks and the last menu was
4694 // empty, we should hide it.
4695 let was_hidden = editor.hide_context_menu(window, cx).is_none();
4696 // If it was already hidden and we don't show inline
4697 // completions in the menu, we should also show the
4698 // inline-completion when available.
4699 if was_hidden && editor.show_edit_predictions_in_menu() {
4700 editor.update_visible_inline_completion(window, cx);
4701 }
4702 }
4703 })?;
4704
4705 anyhow::Ok(())
4706 }
4707 .log_err()
4708 .await
4709 });
4710
4711 self.completion_tasks.push((id, task));
4712 }
4713
4714 #[cfg(feature = "test-support")]
4715 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
4716 let menu = self.context_menu.borrow();
4717 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
4718 let completions = menu.completions.borrow();
4719 Some(completions.to_vec())
4720 } else {
4721 None
4722 }
4723 }
4724
4725 pub fn confirm_completion(
4726 &mut self,
4727 action: &ConfirmCompletion,
4728 window: &mut Window,
4729 cx: &mut Context<Self>,
4730 ) -> Option<Task<Result<()>>> {
4731 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4732 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
4733 }
4734
4735 pub fn confirm_completion_insert(
4736 &mut self,
4737 _: &ConfirmCompletionInsert,
4738 window: &mut Window,
4739 cx: &mut Context<Self>,
4740 ) -> Option<Task<Result<()>>> {
4741 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4742 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
4743 }
4744
4745 pub fn confirm_completion_replace(
4746 &mut self,
4747 _: &ConfirmCompletionReplace,
4748 window: &mut Window,
4749 cx: &mut Context<Self>,
4750 ) -> Option<Task<Result<()>>> {
4751 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4752 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
4753 }
4754
4755 pub fn compose_completion(
4756 &mut self,
4757 action: &ComposeCompletion,
4758 window: &mut Window,
4759 cx: &mut Context<Self>,
4760 ) -> Option<Task<Result<()>>> {
4761 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4762 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
4763 }
4764
4765 fn do_completion(
4766 &mut self,
4767 item_ix: Option<usize>,
4768 intent: CompletionIntent,
4769 window: &mut Window,
4770 cx: &mut Context<Editor>,
4771 ) -> Option<Task<Result<()>>> {
4772 use language::ToOffset as _;
4773
4774 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
4775 else {
4776 return None;
4777 };
4778
4779 let candidate_id = {
4780 let entries = completions_menu.entries.borrow();
4781 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
4782 if self.show_edit_predictions_in_menu() {
4783 self.discard_inline_completion(true, cx);
4784 }
4785 mat.candidate_id
4786 };
4787
4788 let buffer_handle = completions_menu.buffer;
4789 let completion = completions_menu
4790 .completions
4791 .borrow()
4792 .get(candidate_id)?
4793 .clone();
4794 cx.stop_propagation();
4795
4796 let snippet;
4797 let new_text;
4798 if completion.is_snippet() {
4799 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
4800 new_text = snippet.as_ref().unwrap().text.clone();
4801 } else {
4802 snippet = None;
4803 new_text = completion.new_text.clone();
4804 };
4805
4806 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
4807 let buffer = buffer_handle.read(cx);
4808 let snapshot = self.buffer.read(cx).snapshot(cx);
4809 let replace_range_multibuffer = {
4810 let excerpt = snapshot
4811 .excerpt_containing(self.selections.newest_anchor().range())
4812 .unwrap();
4813 let multibuffer_anchor = snapshot
4814 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
4815 .unwrap()
4816 ..snapshot
4817 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
4818 .unwrap();
4819 multibuffer_anchor.start.to_offset(&snapshot)
4820 ..multibuffer_anchor.end.to_offset(&snapshot)
4821 };
4822 let newest_anchor = self.selections.newest_anchor();
4823 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
4824 return None;
4825 }
4826
4827 let old_text = buffer
4828 .text_for_range(replace_range.clone())
4829 .collect::<String>();
4830 let lookbehind = newest_anchor
4831 .start
4832 .text_anchor
4833 .to_offset(buffer)
4834 .saturating_sub(replace_range.start);
4835 let lookahead = replace_range
4836 .end
4837 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
4838 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
4839 let suffix = &old_text[lookbehind.min(old_text.len())..];
4840
4841 let selections = self.selections.all::<usize>(cx);
4842 let mut ranges = Vec::new();
4843 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4844
4845 for selection in &selections {
4846 let range = if selection.id == newest_anchor.id {
4847 replace_range_multibuffer.clone()
4848 } else {
4849 let mut range = selection.range();
4850
4851 // if prefix is present, don't duplicate it
4852 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
4853 range.start = range.start.saturating_sub(lookbehind);
4854
4855 // if suffix is also present, mimic the newest cursor and replace it
4856 if selection.id != newest_anchor.id
4857 && snapshot.contains_str_at(range.end, suffix)
4858 {
4859 range.end += lookahead;
4860 }
4861 }
4862 range
4863 };
4864
4865 ranges.push(range);
4866
4867 if !self.linked_edit_ranges.is_empty() {
4868 let start_anchor = snapshot.anchor_before(selection.head());
4869 let end_anchor = snapshot.anchor_after(selection.tail());
4870 if let Some(ranges) = self
4871 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
4872 {
4873 for (buffer, edits) in ranges {
4874 linked_edits
4875 .entry(buffer.clone())
4876 .or_default()
4877 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
4878 }
4879 }
4880 }
4881 }
4882
4883 cx.emit(EditorEvent::InputHandled {
4884 utf16_range_to_replace: None,
4885 text: new_text.clone().into(),
4886 });
4887
4888 self.transact(window, cx, |this, window, cx| {
4889 if let Some(mut snippet) = snippet {
4890 snippet.text = new_text.to_string();
4891 this.insert_snippet(&ranges, snippet, window, cx).log_err();
4892 } else {
4893 this.buffer.update(cx, |buffer, cx| {
4894 let auto_indent = match completion.insert_text_mode {
4895 Some(InsertTextMode::AS_IS) => None,
4896 _ => this.autoindent_mode.clone(),
4897 };
4898 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
4899 buffer.edit(edits, auto_indent, cx);
4900 });
4901 }
4902 for (buffer, edits) in linked_edits {
4903 buffer.update(cx, |buffer, cx| {
4904 let snapshot = buffer.snapshot();
4905 let edits = edits
4906 .into_iter()
4907 .map(|(range, text)| {
4908 use text::ToPoint as TP;
4909 let end_point = TP::to_point(&range.end, &snapshot);
4910 let start_point = TP::to_point(&range.start, &snapshot);
4911 (start_point..end_point, text)
4912 })
4913 .sorted_by_key(|(range, _)| range.start);
4914 buffer.edit(edits, None, cx);
4915 })
4916 }
4917
4918 this.refresh_inline_completion(true, false, window, cx);
4919 });
4920
4921 let show_new_completions_on_confirm = completion
4922 .confirm
4923 .as_ref()
4924 .map_or(false, |confirm| confirm(intent, window, cx));
4925 if show_new_completions_on_confirm {
4926 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
4927 }
4928
4929 let provider = self.completion_provider.as_ref()?;
4930 drop(completion);
4931 let apply_edits = provider.apply_additional_edits_for_completion(
4932 buffer_handle,
4933 completions_menu.completions.clone(),
4934 candidate_id,
4935 true,
4936 cx,
4937 );
4938
4939 let editor_settings = EditorSettings::get_global(cx);
4940 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
4941 // After the code completion is finished, users often want to know what signatures are needed.
4942 // so we should automatically call signature_help
4943 self.show_signature_help(&ShowSignatureHelp, window, cx);
4944 }
4945
4946 Some(cx.foreground_executor().spawn(async move {
4947 apply_edits.await?;
4948 Ok(())
4949 }))
4950 }
4951
4952 pub fn toggle_code_actions(
4953 &mut self,
4954 action: &ToggleCodeActions,
4955 window: &mut Window,
4956 cx: &mut Context<Self>,
4957 ) {
4958 let mut context_menu = self.context_menu.borrow_mut();
4959 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
4960 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
4961 // Toggle if we're selecting the same one
4962 *context_menu = None;
4963 cx.notify();
4964 return;
4965 } else {
4966 // Otherwise, clear it and start a new one
4967 *context_menu = None;
4968 cx.notify();
4969 }
4970 }
4971 drop(context_menu);
4972 let snapshot = self.snapshot(window, cx);
4973 let deployed_from_indicator = action.deployed_from_indicator;
4974 let mut task = self.code_actions_task.take();
4975 let action = action.clone();
4976 cx.spawn_in(window, async move |editor, cx| {
4977 while let Some(prev_task) = task {
4978 prev_task.await.log_err();
4979 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
4980 }
4981
4982 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
4983 if editor.focus_handle.is_focused(window) {
4984 let multibuffer_point = action
4985 .deployed_from_indicator
4986 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
4987 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
4988 let (buffer, buffer_row) = snapshot
4989 .buffer_snapshot
4990 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
4991 .and_then(|(buffer_snapshot, range)| {
4992 editor
4993 .buffer
4994 .read(cx)
4995 .buffer(buffer_snapshot.remote_id())
4996 .map(|buffer| (buffer, range.start.row))
4997 })?;
4998 let (_, code_actions) = editor
4999 .available_code_actions
5000 .clone()
5001 .and_then(|(location, code_actions)| {
5002 let snapshot = location.buffer.read(cx).snapshot();
5003 let point_range = location.range.to_point(&snapshot);
5004 let point_range = point_range.start.row..=point_range.end.row;
5005 if point_range.contains(&buffer_row) {
5006 Some((location, code_actions))
5007 } else {
5008 None
5009 }
5010 })
5011 .unzip();
5012 let buffer_id = buffer.read(cx).remote_id();
5013 let tasks = editor
5014 .tasks
5015 .get(&(buffer_id, buffer_row))
5016 .map(|t| Arc::new(t.to_owned()));
5017 if tasks.is_none() && code_actions.is_none() {
5018 return None;
5019 }
5020
5021 editor.completion_tasks.clear();
5022 editor.discard_inline_completion(false, cx);
5023 let task_context =
5024 tasks
5025 .as_ref()
5026 .zip(editor.project.clone())
5027 .map(|(tasks, project)| {
5028 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5029 });
5030
5031 let debugger_flag = cx.has_flag::<DebuggerFeatureFlag>();
5032
5033 Some(cx.spawn_in(window, async move |editor, cx| {
5034 let task_context = match task_context {
5035 Some(task_context) => task_context.await,
5036 None => None,
5037 };
5038 let resolved_tasks =
5039 tasks
5040 .zip(task_context)
5041 .map(|(tasks, task_context)| ResolvedTasks {
5042 templates: tasks.resolve(&task_context).collect(),
5043 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5044 multibuffer_point.row,
5045 tasks.column,
5046 )),
5047 });
5048 let spawn_straight_away = resolved_tasks.as_ref().map_or(false, |tasks| {
5049 tasks
5050 .templates
5051 .iter()
5052 .filter(|task| {
5053 if matches!(task.1.task_type(), task::TaskType::Debug(_)) {
5054 debugger_flag
5055 } else {
5056 true
5057 }
5058 })
5059 .count()
5060 == 1
5061 }) && code_actions
5062 .as_ref()
5063 .map_or(true, |actions| actions.is_empty());
5064 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5065 *editor.context_menu.borrow_mut() =
5066 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5067 buffer,
5068 actions: CodeActionContents::new(
5069 resolved_tasks,
5070 code_actions,
5071 cx,
5072 ),
5073 selected_item: Default::default(),
5074 scroll_handle: UniformListScrollHandle::default(),
5075 deployed_from_indicator,
5076 }));
5077 if spawn_straight_away {
5078 if let Some(task) = editor.confirm_code_action(
5079 &ConfirmCodeAction { item_ix: Some(0) },
5080 window,
5081 cx,
5082 ) {
5083 cx.notify();
5084 return task;
5085 }
5086 }
5087 cx.notify();
5088 Task::ready(Ok(()))
5089 }) {
5090 task.await
5091 } else {
5092 Ok(())
5093 }
5094 }))
5095 } else {
5096 Some(Task::ready(Ok(())))
5097 }
5098 })?;
5099 if let Some(task) = spawned_test_task {
5100 task.await?;
5101 }
5102
5103 Ok::<_, anyhow::Error>(())
5104 })
5105 .detach_and_log_err(cx);
5106 }
5107
5108 pub fn confirm_code_action(
5109 &mut self,
5110 action: &ConfirmCodeAction,
5111 window: &mut Window,
5112 cx: &mut Context<Self>,
5113 ) -> Option<Task<Result<()>>> {
5114 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5115
5116 let actions_menu =
5117 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5118 menu
5119 } else {
5120 return None;
5121 };
5122
5123 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5124 let action = actions_menu.actions.get(action_ix)?;
5125 let title = action.label();
5126 let buffer = actions_menu.buffer;
5127 let workspace = self.workspace()?;
5128
5129 match action {
5130 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5131 match resolved_task.task_type() {
5132 task::TaskType::Script => workspace.update(cx, |workspace, cx| {
5133 workspace.schedule_resolved_task(
5134 task_source_kind,
5135 resolved_task,
5136 false,
5137 window,
5138 cx,
5139 );
5140
5141 Some(Task::ready(Ok(())))
5142 }),
5143 task::TaskType::Debug(_) => {
5144 workspace.update(cx, |workspace, cx| {
5145 workspace.schedule_debug_task(resolved_task, window, cx);
5146 });
5147 Some(Task::ready(Ok(())))
5148 }
5149 }
5150 }
5151 CodeActionsItem::CodeAction {
5152 excerpt_id,
5153 action,
5154 provider,
5155 } => {
5156 let apply_code_action =
5157 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5158 let workspace = workspace.downgrade();
5159 Some(cx.spawn_in(window, async move |editor, cx| {
5160 let project_transaction = apply_code_action.await?;
5161 Self::open_project_transaction(
5162 &editor,
5163 workspace,
5164 project_transaction,
5165 title,
5166 cx,
5167 )
5168 .await
5169 }))
5170 }
5171 }
5172 }
5173
5174 pub async fn open_project_transaction(
5175 this: &WeakEntity<Editor>,
5176 workspace: WeakEntity<Workspace>,
5177 transaction: ProjectTransaction,
5178 title: String,
5179 cx: &mut AsyncWindowContext,
5180 ) -> Result<()> {
5181 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5182 cx.update(|_, cx| {
5183 entries.sort_unstable_by_key(|(buffer, _)| {
5184 buffer.read(cx).file().map(|f| f.path().clone())
5185 });
5186 })?;
5187
5188 // If the project transaction's edits are all contained within this editor, then
5189 // avoid opening a new editor to display them.
5190
5191 if let Some((buffer, transaction)) = entries.first() {
5192 if entries.len() == 1 {
5193 let excerpt = this.update(cx, |editor, cx| {
5194 editor
5195 .buffer()
5196 .read(cx)
5197 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5198 })?;
5199 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5200 if excerpted_buffer == *buffer {
5201 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5202 let excerpt_range = excerpt_range.to_offset(buffer);
5203 buffer
5204 .edited_ranges_for_transaction::<usize>(transaction)
5205 .all(|range| {
5206 excerpt_range.start <= range.start
5207 && excerpt_range.end >= range.end
5208 })
5209 })?;
5210
5211 if all_edits_within_excerpt {
5212 return Ok(());
5213 }
5214 }
5215 }
5216 }
5217 } else {
5218 return Ok(());
5219 }
5220
5221 let mut ranges_to_highlight = Vec::new();
5222 let excerpt_buffer = cx.new(|cx| {
5223 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5224 for (buffer_handle, transaction) in &entries {
5225 let edited_ranges = buffer_handle
5226 .read(cx)
5227 .edited_ranges_for_transaction::<Point>(transaction)
5228 .collect::<Vec<_>>();
5229 let (ranges, _) = multibuffer.set_excerpts_for_path(
5230 PathKey::for_buffer(buffer_handle, cx),
5231 buffer_handle.clone(),
5232 edited_ranges,
5233 DEFAULT_MULTIBUFFER_CONTEXT,
5234 cx,
5235 );
5236
5237 ranges_to_highlight.extend(ranges);
5238 }
5239 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5240 multibuffer
5241 })?;
5242
5243 workspace.update_in(cx, |workspace, window, cx| {
5244 let project = workspace.project().clone();
5245 let editor =
5246 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5247 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5248 editor.update(cx, |editor, cx| {
5249 editor.highlight_background::<Self>(
5250 &ranges_to_highlight,
5251 |theme| theme.editor_highlighted_line_background,
5252 cx,
5253 );
5254 });
5255 })?;
5256
5257 Ok(())
5258 }
5259
5260 pub fn clear_code_action_providers(&mut self) {
5261 self.code_action_providers.clear();
5262 self.available_code_actions.take();
5263 }
5264
5265 pub fn add_code_action_provider(
5266 &mut self,
5267 provider: Rc<dyn CodeActionProvider>,
5268 window: &mut Window,
5269 cx: &mut Context<Self>,
5270 ) {
5271 if self
5272 .code_action_providers
5273 .iter()
5274 .any(|existing_provider| existing_provider.id() == provider.id())
5275 {
5276 return;
5277 }
5278
5279 self.code_action_providers.push(provider);
5280 self.refresh_code_actions(window, cx);
5281 }
5282
5283 pub fn remove_code_action_provider(
5284 &mut self,
5285 id: Arc<str>,
5286 window: &mut Window,
5287 cx: &mut Context<Self>,
5288 ) {
5289 self.code_action_providers
5290 .retain(|provider| provider.id() != id);
5291 self.refresh_code_actions(window, cx);
5292 }
5293
5294 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5295 let newest_selection = self.selections.newest_anchor().clone();
5296 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5297 let buffer = self.buffer.read(cx);
5298 if newest_selection.head().diff_base_anchor.is_some() {
5299 return None;
5300 }
5301 let (start_buffer, start) =
5302 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5303 let (end_buffer, end) =
5304 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5305 if start_buffer != end_buffer {
5306 return None;
5307 }
5308
5309 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5310 cx.background_executor()
5311 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5312 .await;
5313
5314 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5315 let providers = this.code_action_providers.clone();
5316 let tasks = this
5317 .code_action_providers
5318 .iter()
5319 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5320 .collect::<Vec<_>>();
5321 (providers, tasks)
5322 })?;
5323
5324 let mut actions = Vec::new();
5325 for (provider, provider_actions) in
5326 providers.into_iter().zip(future::join_all(tasks).await)
5327 {
5328 if let Some(provider_actions) = provider_actions.log_err() {
5329 actions.extend(provider_actions.into_iter().map(|action| {
5330 AvailableCodeAction {
5331 excerpt_id: newest_selection.start.excerpt_id,
5332 action,
5333 provider: provider.clone(),
5334 }
5335 }));
5336 }
5337 }
5338
5339 this.update(cx, |this, cx| {
5340 this.available_code_actions = if actions.is_empty() {
5341 None
5342 } else {
5343 Some((
5344 Location {
5345 buffer: start_buffer,
5346 range: start..end,
5347 },
5348 actions.into(),
5349 ))
5350 };
5351 cx.notify();
5352 })
5353 }));
5354 None
5355 }
5356
5357 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5358 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5359 self.show_git_blame_inline = false;
5360
5361 self.show_git_blame_inline_delay_task =
5362 Some(cx.spawn_in(window, async move |this, cx| {
5363 cx.background_executor().timer(delay).await;
5364
5365 this.update(cx, |this, cx| {
5366 this.show_git_blame_inline = true;
5367 cx.notify();
5368 })
5369 .log_err();
5370 }));
5371 }
5372 }
5373
5374 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5375 if self.pending_rename.is_some() {
5376 return None;
5377 }
5378
5379 let provider = self.semantics_provider.clone()?;
5380 let buffer = self.buffer.read(cx);
5381 let newest_selection = self.selections.newest_anchor().clone();
5382 let cursor_position = newest_selection.head();
5383 let (cursor_buffer, cursor_buffer_position) =
5384 buffer.text_anchor_for_position(cursor_position, cx)?;
5385 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5386 if cursor_buffer != tail_buffer {
5387 return None;
5388 }
5389 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5390 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5391 cx.background_executor()
5392 .timer(Duration::from_millis(debounce))
5393 .await;
5394
5395 let highlights = if let Some(highlights) = cx
5396 .update(|cx| {
5397 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5398 })
5399 .ok()
5400 .flatten()
5401 {
5402 highlights.await.log_err()
5403 } else {
5404 None
5405 };
5406
5407 if let Some(highlights) = highlights {
5408 this.update(cx, |this, cx| {
5409 if this.pending_rename.is_some() {
5410 return;
5411 }
5412
5413 let buffer_id = cursor_position.buffer_id;
5414 let buffer = this.buffer.read(cx);
5415 if !buffer
5416 .text_anchor_for_position(cursor_position, cx)
5417 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5418 {
5419 return;
5420 }
5421
5422 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5423 let mut write_ranges = Vec::new();
5424 let mut read_ranges = Vec::new();
5425 for highlight in highlights {
5426 for (excerpt_id, excerpt_range) in
5427 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5428 {
5429 let start = highlight
5430 .range
5431 .start
5432 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5433 let end = highlight
5434 .range
5435 .end
5436 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5437 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5438 continue;
5439 }
5440
5441 let range = Anchor {
5442 buffer_id,
5443 excerpt_id,
5444 text_anchor: start,
5445 diff_base_anchor: None,
5446 }..Anchor {
5447 buffer_id,
5448 excerpt_id,
5449 text_anchor: end,
5450 diff_base_anchor: None,
5451 };
5452 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5453 write_ranges.push(range);
5454 } else {
5455 read_ranges.push(range);
5456 }
5457 }
5458 }
5459
5460 this.highlight_background::<DocumentHighlightRead>(
5461 &read_ranges,
5462 |theme| theme.editor_document_highlight_read_background,
5463 cx,
5464 );
5465 this.highlight_background::<DocumentHighlightWrite>(
5466 &write_ranges,
5467 |theme| theme.editor_document_highlight_write_background,
5468 cx,
5469 );
5470 cx.notify();
5471 })
5472 .log_err();
5473 }
5474 }));
5475 None
5476 }
5477
5478 fn prepare_highlight_query_from_selection(
5479 &mut self,
5480 cx: &mut Context<Editor>,
5481 ) -> Option<(String, Range<Anchor>)> {
5482 if matches!(self.mode, EditorMode::SingleLine { .. }) {
5483 return None;
5484 }
5485 if !EditorSettings::get_global(cx).selection_highlight {
5486 return None;
5487 }
5488 if self.selections.count() != 1 || self.selections.line_mode {
5489 return None;
5490 }
5491 let selection = self.selections.newest::<Point>(cx);
5492 if selection.is_empty() || selection.start.row != selection.end.row {
5493 return None;
5494 }
5495 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5496 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
5497 let query = multi_buffer_snapshot
5498 .text_for_range(selection_anchor_range.clone())
5499 .collect::<String>();
5500 if query.trim().is_empty() {
5501 return None;
5502 }
5503 Some((query, selection_anchor_range))
5504 }
5505
5506 fn update_selection_occurrence_highlights(
5507 &mut self,
5508 query_text: String,
5509 query_range: Range<Anchor>,
5510 multi_buffer_range_to_query: Range<Point>,
5511 use_debounce: bool,
5512 window: &mut Window,
5513 cx: &mut Context<Editor>,
5514 ) -> Task<()> {
5515 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5516 cx.spawn_in(window, async move |editor, cx| {
5517 if use_debounce {
5518 cx.background_executor()
5519 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
5520 .await;
5521 }
5522 let match_task = cx.background_spawn(async move {
5523 let buffer_ranges = multi_buffer_snapshot
5524 .range_to_buffer_ranges(multi_buffer_range_to_query)
5525 .into_iter()
5526 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
5527 let mut match_ranges = Vec::new();
5528 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
5529 match_ranges.extend(
5530 project::search::SearchQuery::text(
5531 query_text.clone(),
5532 false,
5533 false,
5534 false,
5535 Default::default(),
5536 Default::default(),
5537 false,
5538 None,
5539 )
5540 .unwrap()
5541 .search(&buffer_snapshot, Some(search_range.clone()))
5542 .await
5543 .into_iter()
5544 .filter_map(|match_range| {
5545 let match_start = buffer_snapshot
5546 .anchor_after(search_range.start + match_range.start);
5547 let match_end =
5548 buffer_snapshot.anchor_before(search_range.start + match_range.end);
5549 let match_anchor_range = Anchor::range_in_buffer(
5550 excerpt_id,
5551 buffer_snapshot.remote_id(),
5552 match_start..match_end,
5553 );
5554 (match_anchor_range != query_range).then_some(match_anchor_range)
5555 }),
5556 );
5557 }
5558 match_ranges
5559 });
5560 let match_ranges = match_task.await;
5561 editor
5562 .update_in(cx, |editor, _, cx| {
5563 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5564 if !match_ranges.is_empty() {
5565 editor.highlight_background::<SelectedTextHighlight>(
5566 &match_ranges,
5567 |theme| theme.editor_document_highlight_bracket_background,
5568 cx,
5569 )
5570 }
5571 })
5572 .log_err();
5573 })
5574 }
5575
5576 fn refresh_selected_text_highlights(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
5577 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
5578 else {
5579 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5580 self.quick_selection_highlight_task.take();
5581 self.debounced_selection_highlight_task.take();
5582 return;
5583 };
5584 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5585 if self
5586 .quick_selection_highlight_task
5587 .as_ref()
5588 .map_or(true, |(prev_anchor_range, _)| {
5589 prev_anchor_range != &query_range
5590 })
5591 {
5592 let multi_buffer_visible_start = self
5593 .scroll_manager
5594 .anchor()
5595 .anchor
5596 .to_point(&multi_buffer_snapshot);
5597 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5598 multi_buffer_visible_start
5599 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5600 Bias::Left,
5601 );
5602 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5603 self.quick_selection_highlight_task = Some((
5604 query_range.clone(),
5605 self.update_selection_occurrence_highlights(
5606 query_text.clone(),
5607 query_range.clone(),
5608 multi_buffer_visible_range,
5609 false,
5610 window,
5611 cx,
5612 ),
5613 ));
5614 }
5615 if self
5616 .debounced_selection_highlight_task
5617 .as_ref()
5618 .map_or(true, |(prev_anchor_range, _)| {
5619 prev_anchor_range != &query_range
5620 })
5621 {
5622 let multi_buffer_start = multi_buffer_snapshot
5623 .anchor_before(0)
5624 .to_point(&multi_buffer_snapshot);
5625 let multi_buffer_end = multi_buffer_snapshot
5626 .anchor_after(multi_buffer_snapshot.len())
5627 .to_point(&multi_buffer_snapshot);
5628 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
5629 self.debounced_selection_highlight_task = Some((
5630 query_range.clone(),
5631 self.update_selection_occurrence_highlights(
5632 query_text,
5633 query_range,
5634 multi_buffer_full_range,
5635 true,
5636 window,
5637 cx,
5638 ),
5639 ));
5640 }
5641 }
5642
5643 pub fn refresh_inline_completion(
5644 &mut self,
5645 debounce: bool,
5646 user_requested: bool,
5647 window: &mut Window,
5648 cx: &mut Context<Self>,
5649 ) -> Option<()> {
5650 let provider = self.edit_prediction_provider()?;
5651 let cursor = self.selections.newest_anchor().head();
5652 let (buffer, cursor_buffer_position) =
5653 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5654
5655 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
5656 self.discard_inline_completion(false, cx);
5657 return None;
5658 }
5659
5660 if !user_requested
5661 && (!self.should_show_edit_predictions()
5662 || !self.is_focused(window)
5663 || buffer.read(cx).is_empty())
5664 {
5665 self.discard_inline_completion(false, cx);
5666 return None;
5667 }
5668
5669 self.update_visible_inline_completion(window, cx);
5670 provider.refresh(
5671 self.project.clone(),
5672 buffer,
5673 cursor_buffer_position,
5674 debounce,
5675 cx,
5676 );
5677 Some(())
5678 }
5679
5680 fn show_edit_predictions_in_menu(&self) -> bool {
5681 match self.edit_prediction_settings {
5682 EditPredictionSettings::Disabled => false,
5683 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
5684 }
5685 }
5686
5687 pub fn edit_predictions_enabled(&self) -> bool {
5688 match self.edit_prediction_settings {
5689 EditPredictionSettings::Disabled => false,
5690 EditPredictionSettings::Enabled { .. } => true,
5691 }
5692 }
5693
5694 fn edit_prediction_requires_modifier(&self) -> bool {
5695 match self.edit_prediction_settings {
5696 EditPredictionSettings::Disabled => false,
5697 EditPredictionSettings::Enabled {
5698 preview_requires_modifier,
5699 ..
5700 } => preview_requires_modifier,
5701 }
5702 }
5703
5704 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
5705 if self.edit_prediction_provider.is_none() {
5706 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5707 } else {
5708 let selection = self.selections.newest_anchor();
5709 let cursor = selection.head();
5710
5711 if let Some((buffer, cursor_buffer_position)) =
5712 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5713 {
5714 self.edit_prediction_settings =
5715 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5716 }
5717 }
5718 }
5719
5720 fn edit_prediction_settings_at_position(
5721 &self,
5722 buffer: &Entity<Buffer>,
5723 buffer_position: language::Anchor,
5724 cx: &App,
5725 ) -> EditPredictionSettings {
5726 if !self.mode.is_full()
5727 || !self.show_inline_completions_override.unwrap_or(true)
5728 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
5729 {
5730 return EditPredictionSettings::Disabled;
5731 }
5732
5733 let buffer = buffer.read(cx);
5734
5735 let file = buffer.file();
5736
5737 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
5738 return EditPredictionSettings::Disabled;
5739 };
5740
5741 let by_provider = matches!(
5742 self.menu_inline_completions_policy,
5743 MenuInlineCompletionsPolicy::ByProvider
5744 );
5745
5746 let show_in_menu = by_provider
5747 && self
5748 .edit_prediction_provider
5749 .as_ref()
5750 .map_or(false, |provider| {
5751 provider.provider.show_completions_in_menu()
5752 });
5753
5754 let preview_requires_modifier =
5755 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
5756
5757 EditPredictionSettings::Enabled {
5758 show_in_menu,
5759 preview_requires_modifier,
5760 }
5761 }
5762
5763 fn should_show_edit_predictions(&self) -> bool {
5764 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
5765 }
5766
5767 pub fn edit_prediction_preview_is_active(&self) -> bool {
5768 matches!(
5769 self.edit_prediction_preview,
5770 EditPredictionPreview::Active { .. }
5771 )
5772 }
5773
5774 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
5775 let cursor = self.selections.newest_anchor().head();
5776 if let Some((buffer, cursor_position)) =
5777 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5778 {
5779 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
5780 } else {
5781 false
5782 }
5783 }
5784
5785 fn edit_predictions_enabled_in_buffer(
5786 &self,
5787 buffer: &Entity<Buffer>,
5788 buffer_position: language::Anchor,
5789 cx: &App,
5790 ) -> bool {
5791 maybe!({
5792 if self.read_only(cx) {
5793 return Some(false);
5794 }
5795 let provider = self.edit_prediction_provider()?;
5796 if !provider.is_enabled(&buffer, buffer_position, cx) {
5797 return Some(false);
5798 }
5799 let buffer = buffer.read(cx);
5800 let Some(file) = buffer.file() else {
5801 return Some(true);
5802 };
5803 let settings = all_language_settings(Some(file), cx);
5804 Some(settings.edit_predictions_enabled_for_file(file, cx))
5805 })
5806 .unwrap_or(false)
5807 }
5808
5809 fn cycle_inline_completion(
5810 &mut self,
5811 direction: Direction,
5812 window: &mut Window,
5813 cx: &mut Context<Self>,
5814 ) -> Option<()> {
5815 let provider = self.edit_prediction_provider()?;
5816 let cursor = self.selections.newest_anchor().head();
5817 let (buffer, cursor_buffer_position) =
5818 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5819 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
5820 return None;
5821 }
5822
5823 provider.cycle(buffer, cursor_buffer_position, direction, cx);
5824 self.update_visible_inline_completion(window, cx);
5825
5826 Some(())
5827 }
5828
5829 pub fn show_inline_completion(
5830 &mut self,
5831 _: &ShowEditPrediction,
5832 window: &mut Window,
5833 cx: &mut Context<Self>,
5834 ) {
5835 if !self.has_active_inline_completion() {
5836 self.refresh_inline_completion(false, true, window, cx);
5837 return;
5838 }
5839
5840 self.update_visible_inline_completion(window, cx);
5841 }
5842
5843 pub fn display_cursor_names(
5844 &mut self,
5845 _: &DisplayCursorNames,
5846 window: &mut Window,
5847 cx: &mut Context<Self>,
5848 ) {
5849 self.show_cursor_names(window, cx);
5850 }
5851
5852 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5853 self.show_cursor_names = true;
5854 cx.notify();
5855 cx.spawn_in(window, async move |this, cx| {
5856 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
5857 this.update(cx, |this, cx| {
5858 this.show_cursor_names = false;
5859 cx.notify()
5860 })
5861 .ok()
5862 })
5863 .detach();
5864 }
5865
5866 pub fn next_edit_prediction(
5867 &mut self,
5868 _: &NextEditPrediction,
5869 window: &mut Window,
5870 cx: &mut Context<Self>,
5871 ) {
5872 if self.has_active_inline_completion() {
5873 self.cycle_inline_completion(Direction::Next, window, cx);
5874 } else {
5875 let is_copilot_disabled = self
5876 .refresh_inline_completion(false, true, window, cx)
5877 .is_none();
5878 if is_copilot_disabled {
5879 cx.propagate();
5880 }
5881 }
5882 }
5883
5884 pub fn previous_edit_prediction(
5885 &mut self,
5886 _: &PreviousEditPrediction,
5887 window: &mut Window,
5888 cx: &mut Context<Self>,
5889 ) {
5890 if self.has_active_inline_completion() {
5891 self.cycle_inline_completion(Direction::Prev, window, cx);
5892 } else {
5893 let is_copilot_disabled = self
5894 .refresh_inline_completion(false, true, window, cx)
5895 .is_none();
5896 if is_copilot_disabled {
5897 cx.propagate();
5898 }
5899 }
5900 }
5901
5902 pub fn accept_edit_prediction(
5903 &mut self,
5904 _: &AcceptEditPrediction,
5905 window: &mut Window,
5906 cx: &mut Context<Self>,
5907 ) {
5908 if self.show_edit_predictions_in_menu() {
5909 self.hide_context_menu(window, cx);
5910 }
5911
5912 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5913 return;
5914 };
5915
5916 self.report_inline_completion_event(
5917 active_inline_completion.completion_id.clone(),
5918 true,
5919 cx,
5920 );
5921
5922 match &active_inline_completion.completion {
5923 InlineCompletion::Move { target, .. } => {
5924 let target = *target;
5925
5926 if let Some(position_map) = &self.last_position_map {
5927 if position_map
5928 .visible_row_range
5929 .contains(&target.to_display_point(&position_map.snapshot).row())
5930 || !self.edit_prediction_requires_modifier()
5931 {
5932 self.unfold_ranges(&[target..target], true, false, cx);
5933 // Note that this is also done in vim's handler of the Tab action.
5934 self.change_selections(
5935 Some(Autoscroll::newest()),
5936 window,
5937 cx,
5938 |selections| {
5939 selections.select_anchor_ranges([target..target]);
5940 },
5941 );
5942 self.clear_row_highlights::<EditPredictionPreview>();
5943
5944 self.edit_prediction_preview
5945 .set_previous_scroll_position(None);
5946 } else {
5947 self.edit_prediction_preview
5948 .set_previous_scroll_position(Some(
5949 position_map.snapshot.scroll_anchor,
5950 ));
5951
5952 self.highlight_rows::<EditPredictionPreview>(
5953 target..target,
5954 cx.theme().colors().editor_highlighted_line_background,
5955 true,
5956 cx,
5957 );
5958 self.request_autoscroll(Autoscroll::fit(), cx);
5959 }
5960 }
5961 }
5962 InlineCompletion::Edit { edits, .. } => {
5963 if let Some(provider) = self.edit_prediction_provider() {
5964 provider.accept(cx);
5965 }
5966
5967 let snapshot = self.buffer.read(cx).snapshot(cx);
5968 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
5969
5970 self.buffer.update(cx, |buffer, cx| {
5971 buffer.edit(edits.iter().cloned(), None, cx)
5972 });
5973
5974 self.change_selections(None, window, cx, |s| {
5975 s.select_anchor_ranges([last_edit_end..last_edit_end])
5976 });
5977
5978 self.update_visible_inline_completion(window, cx);
5979 if self.active_inline_completion.is_none() {
5980 self.refresh_inline_completion(true, true, window, cx);
5981 }
5982
5983 cx.notify();
5984 }
5985 }
5986
5987 self.edit_prediction_requires_modifier_in_indent_conflict = false;
5988 }
5989
5990 pub fn accept_partial_inline_completion(
5991 &mut self,
5992 _: &AcceptPartialEditPrediction,
5993 window: &mut Window,
5994 cx: &mut Context<Self>,
5995 ) {
5996 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5997 return;
5998 };
5999 if self.selections.count() != 1 {
6000 return;
6001 }
6002
6003 self.report_inline_completion_event(
6004 active_inline_completion.completion_id.clone(),
6005 true,
6006 cx,
6007 );
6008
6009 match &active_inline_completion.completion {
6010 InlineCompletion::Move { target, .. } => {
6011 let target = *target;
6012 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6013 selections.select_anchor_ranges([target..target]);
6014 });
6015 }
6016 InlineCompletion::Edit { edits, .. } => {
6017 // Find an insertion that starts at the cursor position.
6018 let snapshot = self.buffer.read(cx).snapshot(cx);
6019 let cursor_offset = self.selections.newest::<usize>(cx).head();
6020 let insertion = edits.iter().find_map(|(range, text)| {
6021 let range = range.to_offset(&snapshot);
6022 if range.is_empty() && range.start == cursor_offset {
6023 Some(text)
6024 } else {
6025 None
6026 }
6027 });
6028
6029 if let Some(text) = insertion {
6030 let mut partial_completion = text
6031 .chars()
6032 .by_ref()
6033 .take_while(|c| c.is_alphabetic())
6034 .collect::<String>();
6035 if partial_completion.is_empty() {
6036 partial_completion = text
6037 .chars()
6038 .by_ref()
6039 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6040 .collect::<String>();
6041 }
6042
6043 cx.emit(EditorEvent::InputHandled {
6044 utf16_range_to_replace: None,
6045 text: partial_completion.clone().into(),
6046 });
6047
6048 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6049
6050 self.refresh_inline_completion(true, true, window, cx);
6051 cx.notify();
6052 } else {
6053 self.accept_edit_prediction(&Default::default(), window, cx);
6054 }
6055 }
6056 }
6057 }
6058
6059 fn discard_inline_completion(
6060 &mut self,
6061 should_report_inline_completion_event: bool,
6062 cx: &mut Context<Self>,
6063 ) -> bool {
6064 if should_report_inline_completion_event {
6065 let completion_id = self
6066 .active_inline_completion
6067 .as_ref()
6068 .and_then(|active_completion| active_completion.completion_id.clone());
6069
6070 self.report_inline_completion_event(completion_id, false, cx);
6071 }
6072
6073 if let Some(provider) = self.edit_prediction_provider() {
6074 provider.discard(cx);
6075 }
6076
6077 self.take_active_inline_completion(cx)
6078 }
6079
6080 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6081 let Some(provider) = self.edit_prediction_provider() else {
6082 return;
6083 };
6084
6085 let Some((_, buffer, _)) = self
6086 .buffer
6087 .read(cx)
6088 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6089 else {
6090 return;
6091 };
6092
6093 let extension = buffer
6094 .read(cx)
6095 .file()
6096 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6097
6098 let event_type = match accepted {
6099 true => "Edit Prediction Accepted",
6100 false => "Edit Prediction Discarded",
6101 };
6102 telemetry::event!(
6103 event_type,
6104 provider = provider.name(),
6105 prediction_id = id,
6106 suggestion_accepted = accepted,
6107 file_extension = extension,
6108 );
6109 }
6110
6111 pub fn has_active_inline_completion(&self) -> bool {
6112 self.active_inline_completion.is_some()
6113 }
6114
6115 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6116 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6117 return false;
6118 };
6119
6120 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6121 self.clear_highlights::<InlineCompletionHighlight>(cx);
6122 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6123 true
6124 }
6125
6126 /// Returns true when we're displaying the edit prediction popover below the cursor
6127 /// like we are not previewing and the LSP autocomplete menu is visible
6128 /// or we are in `when_holding_modifier` mode.
6129 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6130 if self.edit_prediction_preview_is_active()
6131 || !self.show_edit_predictions_in_menu()
6132 || !self.edit_predictions_enabled()
6133 {
6134 return false;
6135 }
6136
6137 if self.has_visible_completions_menu() {
6138 return true;
6139 }
6140
6141 has_completion && self.edit_prediction_requires_modifier()
6142 }
6143
6144 fn handle_modifiers_changed(
6145 &mut self,
6146 modifiers: Modifiers,
6147 position_map: &PositionMap,
6148 window: &mut Window,
6149 cx: &mut Context<Self>,
6150 ) {
6151 if self.show_edit_predictions_in_menu() {
6152 self.update_edit_prediction_preview(&modifiers, window, cx);
6153 }
6154
6155 self.update_selection_mode(&modifiers, position_map, window, cx);
6156
6157 let mouse_position = window.mouse_position();
6158 if !position_map.text_hitbox.is_hovered(window) {
6159 return;
6160 }
6161
6162 self.update_hovered_link(
6163 position_map.point_for_position(mouse_position),
6164 &position_map.snapshot,
6165 modifiers,
6166 window,
6167 cx,
6168 )
6169 }
6170
6171 fn update_selection_mode(
6172 &mut self,
6173 modifiers: &Modifiers,
6174 position_map: &PositionMap,
6175 window: &mut Window,
6176 cx: &mut Context<Self>,
6177 ) {
6178 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6179 return;
6180 }
6181
6182 let mouse_position = window.mouse_position();
6183 let point_for_position = position_map.point_for_position(mouse_position);
6184 let position = point_for_position.previous_valid;
6185
6186 self.select(
6187 SelectPhase::BeginColumnar {
6188 position,
6189 reset: false,
6190 goal_column: point_for_position.exact_unclipped.column(),
6191 },
6192 window,
6193 cx,
6194 );
6195 }
6196
6197 fn update_edit_prediction_preview(
6198 &mut self,
6199 modifiers: &Modifiers,
6200 window: &mut Window,
6201 cx: &mut Context<Self>,
6202 ) {
6203 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6204 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6205 return;
6206 };
6207
6208 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6209 if matches!(
6210 self.edit_prediction_preview,
6211 EditPredictionPreview::Inactive { .. }
6212 ) {
6213 self.edit_prediction_preview = EditPredictionPreview::Active {
6214 previous_scroll_position: None,
6215 since: Instant::now(),
6216 };
6217
6218 self.update_visible_inline_completion(window, cx);
6219 cx.notify();
6220 }
6221 } else if let EditPredictionPreview::Active {
6222 previous_scroll_position,
6223 since,
6224 } = self.edit_prediction_preview
6225 {
6226 if let (Some(previous_scroll_position), Some(position_map)) =
6227 (previous_scroll_position, self.last_position_map.as_ref())
6228 {
6229 self.set_scroll_position(
6230 previous_scroll_position
6231 .scroll_position(&position_map.snapshot.display_snapshot),
6232 window,
6233 cx,
6234 );
6235 }
6236
6237 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6238 released_too_fast: since.elapsed() < Duration::from_millis(200),
6239 };
6240 self.clear_row_highlights::<EditPredictionPreview>();
6241 self.update_visible_inline_completion(window, cx);
6242 cx.notify();
6243 }
6244 }
6245
6246 fn update_visible_inline_completion(
6247 &mut self,
6248 _window: &mut Window,
6249 cx: &mut Context<Self>,
6250 ) -> Option<()> {
6251 let selection = self.selections.newest_anchor();
6252 let cursor = selection.head();
6253 let multibuffer = self.buffer.read(cx).snapshot(cx);
6254 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6255 let excerpt_id = cursor.excerpt_id;
6256
6257 let show_in_menu = self.show_edit_predictions_in_menu();
6258 let completions_menu_has_precedence = !show_in_menu
6259 && (self.context_menu.borrow().is_some()
6260 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6261
6262 if completions_menu_has_precedence
6263 || !offset_selection.is_empty()
6264 || self
6265 .active_inline_completion
6266 .as_ref()
6267 .map_or(false, |completion| {
6268 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6269 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6270 !invalidation_range.contains(&offset_selection.head())
6271 })
6272 {
6273 self.discard_inline_completion(false, cx);
6274 return None;
6275 }
6276
6277 self.take_active_inline_completion(cx);
6278 let Some(provider) = self.edit_prediction_provider() else {
6279 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6280 return None;
6281 };
6282
6283 let (buffer, cursor_buffer_position) =
6284 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6285
6286 self.edit_prediction_settings =
6287 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6288
6289 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6290
6291 if self.edit_prediction_indent_conflict {
6292 let cursor_point = cursor.to_point(&multibuffer);
6293
6294 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6295
6296 if let Some((_, indent)) = indents.iter().next() {
6297 if indent.len == cursor_point.column {
6298 self.edit_prediction_indent_conflict = false;
6299 }
6300 }
6301 }
6302
6303 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6304 let edits = inline_completion
6305 .edits
6306 .into_iter()
6307 .flat_map(|(range, new_text)| {
6308 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6309 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6310 Some((start..end, new_text))
6311 })
6312 .collect::<Vec<_>>();
6313 if edits.is_empty() {
6314 return None;
6315 }
6316
6317 let first_edit_start = edits.first().unwrap().0.start;
6318 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6319 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6320
6321 let last_edit_end = edits.last().unwrap().0.end;
6322 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6323 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6324
6325 let cursor_row = cursor.to_point(&multibuffer).row;
6326
6327 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6328
6329 let mut inlay_ids = Vec::new();
6330 let invalidation_row_range;
6331 let move_invalidation_row_range = if cursor_row < edit_start_row {
6332 Some(cursor_row..edit_end_row)
6333 } else if cursor_row > edit_end_row {
6334 Some(edit_start_row..cursor_row)
6335 } else {
6336 None
6337 };
6338 let is_move =
6339 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6340 let completion = if is_move {
6341 invalidation_row_range =
6342 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6343 let target = first_edit_start;
6344 InlineCompletion::Move { target, snapshot }
6345 } else {
6346 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6347 && !self.inline_completions_hidden_for_vim_mode;
6348
6349 if show_completions_in_buffer {
6350 if edits
6351 .iter()
6352 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6353 {
6354 let mut inlays = Vec::new();
6355 for (range, new_text) in &edits {
6356 let inlay = Inlay::inline_completion(
6357 post_inc(&mut self.next_inlay_id),
6358 range.start,
6359 new_text.as_str(),
6360 );
6361 inlay_ids.push(inlay.id);
6362 inlays.push(inlay);
6363 }
6364
6365 self.splice_inlays(&[], inlays, cx);
6366 } else {
6367 let background_color = cx.theme().status().deleted_background;
6368 self.highlight_text::<InlineCompletionHighlight>(
6369 edits.iter().map(|(range, _)| range.clone()).collect(),
6370 HighlightStyle {
6371 background_color: Some(background_color),
6372 ..Default::default()
6373 },
6374 cx,
6375 );
6376 }
6377 }
6378
6379 invalidation_row_range = edit_start_row..edit_end_row;
6380
6381 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6382 if provider.show_tab_accept_marker() {
6383 EditDisplayMode::TabAccept
6384 } else {
6385 EditDisplayMode::Inline
6386 }
6387 } else {
6388 EditDisplayMode::DiffPopover
6389 };
6390
6391 InlineCompletion::Edit {
6392 edits,
6393 edit_preview: inline_completion.edit_preview,
6394 display_mode,
6395 snapshot,
6396 }
6397 };
6398
6399 let invalidation_range = multibuffer
6400 .anchor_before(Point::new(invalidation_row_range.start, 0))
6401 ..multibuffer.anchor_after(Point::new(
6402 invalidation_row_range.end,
6403 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6404 ));
6405
6406 self.stale_inline_completion_in_menu = None;
6407 self.active_inline_completion = Some(InlineCompletionState {
6408 inlay_ids,
6409 completion,
6410 completion_id: inline_completion.id,
6411 invalidation_range,
6412 });
6413
6414 cx.notify();
6415
6416 Some(())
6417 }
6418
6419 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6420 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6421 }
6422
6423 fn render_code_actions_indicator(
6424 &self,
6425 _style: &EditorStyle,
6426 row: DisplayRow,
6427 is_active: bool,
6428 breakpoint: Option<&(Anchor, Breakpoint)>,
6429 cx: &mut Context<Self>,
6430 ) -> Option<IconButton> {
6431 let color = Color::Muted;
6432 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6433 let show_tooltip = !self.context_menu_visible();
6434
6435 if self.available_code_actions.is_some() {
6436 Some(
6437 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
6438 .shape(ui::IconButtonShape::Square)
6439 .icon_size(IconSize::XSmall)
6440 .icon_color(color)
6441 .toggle_state(is_active)
6442 .when(show_tooltip, |this| {
6443 this.tooltip({
6444 let focus_handle = self.focus_handle.clone();
6445 move |window, cx| {
6446 Tooltip::for_action_in(
6447 "Toggle Code Actions",
6448 &ToggleCodeActions {
6449 deployed_from_indicator: None,
6450 },
6451 &focus_handle,
6452 window,
6453 cx,
6454 )
6455 }
6456 })
6457 })
6458 .on_click(cx.listener(move |editor, _e, window, cx| {
6459 window.focus(&editor.focus_handle(cx));
6460 editor.toggle_code_actions(
6461 &ToggleCodeActions {
6462 deployed_from_indicator: Some(row),
6463 },
6464 window,
6465 cx,
6466 );
6467 }))
6468 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6469 editor.set_breakpoint_context_menu(
6470 row,
6471 position,
6472 event.down.position,
6473 window,
6474 cx,
6475 );
6476 })),
6477 )
6478 } else {
6479 None
6480 }
6481 }
6482
6483 fn clear_tasks(&mut self) {
6484 self.tasks.clear()
6485 }
6486
6487 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6488 if self.tasks.insert(key, value).is_some() {
6489 // This case should hopefully be rare, but just in case...
6490 log::error!(
6491 "multiple different run targets found on a single line, only the last target will be rendered"
6492 )
6493 }
6494 }
6495
6496 /// Get all display points of breakpoints that will be rendered within editor
6497 ///
6498 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6499 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6500 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6501 fn active_breakpoints(
6502 &self,
6503 range: Range<DisplayRow>,
6504 window: &mut Window,
6505 cx: &mut Context<Self>,
6506 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
6507 let mut breakpoint_display_points = HashMap::default();
6508
6509 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
6510 return breakpoint_display_points;
6511 };
6512
6513 let snapshot = self.snapshot(window, cx);
6514
6515 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
6516 let Some(project) = self.project.as_ref() else {
6517 return breakpoint_display_points;
6518 };
6519
6520 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
6521 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
6522
6523 for (buffer_snapshot, range, excerpt_id) in
6524 multi_buffer_snapshot.range_to_buffer_ranges(range)
6525 {
6526 let Some(buffer) = project.read_with(cx, |this, cx| {
6527 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
6528 }) else {
6529 continue;
6530 };
6531 let breakpoints = breakpoint_store.read(cx).breakpoints(
6532 &buffer,
6533 Some(
6534 buffer_snapshot.anchor_before(range.start)
6535 ..buffer_snapshot.anchor_after(range.end),
6536 ),
6537 buffer_snapshot,
6538 cx,
6539 );
6540 for (anchor, breakpoint) in breakpoints {
6541 let multi_buffer_anchor =
6542 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), *anchor);
6543 let position = multi_buffer_anchor
6544 .to_point(&multi_buffer_snapshot)
6545 .to_display_point(&snapshot);
6546
6547 breakpoint_display_points
6548 .insert(position.row(), (multi_buffer_anchor, breakpoint.clone()));
6549 }
6550 }
6551
6552 breakpoint_display_points
6553 }
6554
6555 fn breakpoint_context_menu(
6556 &self,
6557 anchor: Anchor,
6558 window: &mut Window,
6559 cx: &mut Context<Self>,
6560 ) -> Entity<ui::ContextMenu> {
6561 let weak_editor = cx.weak_entity();
6562 let focus_handle = self.focus_handle(cx);
6563
6564 let row = self
6565 .buffer
6566 .read(cx)
6567 .snapshot(cx)
6568 .summary_for_anchor::<Point>(&anchor)
6569 .row;
6570
6571 let breakpoint = self
6572 .breakpoint_at_row(row, window, cx)
6573 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
6574
6575 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
6576 "Edit Log Breakpoint"
6577 } else {
6578 "Set Log Breakpoint"
6579 };
6580
6581 let condition_breakpoint_msg = if breakpoint
6582 .as_ref()
6583 .is_some_and(|bp| bp.1.condition.is_some())
6584 {
6585 "Edit Condition Breakpoint"
6586 } else {
6587 "Set Condition Breakpoint"
6588 };
6589
6590 let hit_condition_breakpoint_msg = if breakpoint
6591 .as_ref()
6592 .is_some_and(|bp| bp.1.hit_condition.is_some())
6593 {
6594 "Edit Hit Condition Breakpoint"
6595 } else {
6596 "Set Hit Condition Breakpoint"
6597 };
6598
6599 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
6600 "Unset Breakpoint"
6601 } else {
6602 "Set Breakpoint"
6603 };
6604
6605 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
6606 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
6607
6608 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
6609 BreakpointState::Enabled => Some("Disable"),
6610 BreakpointState::Disabled => Some("Enable"),
6611 });
6612
6613 let (anchor, breakpoint) =
6614 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
6615
6616 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6617 menu.on_blur_subscription(Subscription::new(|| {}))
6618 .context(focus_handle)
6619 .when(run_to_cursor, |this| {
6620 let weak_editor = weak_editor.clone();
6621 this.entry("Run to cursor", None, move |window, cx| {
6622 weak_editor
6623 .update(cx, |editor, cx| {
6624 editor.change_selections(None, window, cx, |s| {
6625 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
6626 });
6627 })
6628 .ok();
6629
6630 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
6631 })
6632 .separator()
6633 })
6634 .when_some(toggle_state_msg, |this, msg| {
6635 this.entry(msg, None, {
6636 let weak_editor = weak_editor.clone();
6637 let breakpoint = breakpoint.clone();
6638 move |_window, cx| {
6639 weak_editor
6640 .update(cx, |this, cx| {
6641 this.edit_breakpoint_at_anchor(
6642 anchor,
6643 breakpoint.as_ref().clone(),
6644 BreakpointEditAction::InvertState,
6645 cx,
6646 );
6647 })
6648 .log_err();
6649 }
6650 })
6651 })
6652 .entry(set_breakpoint_msg, None, {
6653 let weak_editor = weak_editor.clone();
6654 let breakpoint = breakpoint.clone();
6655 move |_window, cx| {
6656 weak_editor
6657 .update(cx, |this, cx| {
6658 this.edit_breakpoint_at_anchor(
6659 anchor,
6660 breakpoint.as_ref().clone(),
6661 BreakpointEditAction::Toggle,
6662 cx,
6663 );
6664 })
6665 .log_err();
6666 }
6667 })
6668 .entry(log_breakpoint_msg, None, {
6669 let breakpoint = breakpoint.clone();
6670 let weak_editor = weak_editor.clone();
6671 move |window, cx| {
6672 weak_editor
6673 .update(cx, |this, cx| {
6674 this.add_edit_breakpoint_block(
6675 anchor,
6676 breakpoint.as_ref(),
6677 BreakpointPromptEditAction::Log,
6678 window,
6679 cx,
6680 );
6681 })
6682 .log_err();
6683 }
6684 })
6685 .entry(condition_breakpoint_msg, None, {
6686 let breakpoint = breakpoint.clone();
6687 let weak_editor = weak_editor.clone();
6688 move |window, cx| {
6689 weak_editor
6690 .update(cx, |this, cx| {
6691 this.add_edit_breakpoint_block(
6692 anchor,
6693 breakpoint.as_ref(),
6694 BreakpointPromptEditAction::Condition,
6695 window,
6696 cx,
6697 );
6698 })
6699 .log_err();
6700 }
6701 })
6702 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
6703 weak_editor
6704 .update(cx, |this, cx| {
6705 this.add_edit_breakpoint_block(
6706 anchor,
6707 breakpoint.as_ref(),
6708 BreakpointPromptEditAction::HitCondition,
6709 window,
6710 cx,
6711 );
6712 })
6713 .log_err();
6714 })
6715 })
6716 }
6717
6718 fn render_breakpoint(
6719 &self,
6720 position: Anchor,
6721 row: DisplayRow,
6722 breakpoint: &Breakpoint,
6723 cx: &mut Context<Self>,
6724 ) -> IconButton {
6725 let (color, icon) = {
6726 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
6727 (false, false) => ui::IconName::DebugBreakpoint,
6728 (true, false) => ui::IconName::DebugLogBreakpoint,
6729 (false, true) => ui::IconName::DebugDisabledBreakpoint,
6730 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
6731 };
6732
6733 let color = if self
6734 .gutter_breakpoint_indicator
6735 .0
6736 .is_some_and(|(point, is_visible)| is_visible && point.row() == row)
6737 {
6738 Color::Hint
6739 } else {
6740 Color::Debugger
6741 };
6742
6743 (color, icon)
6744 };
6745
6746 let breakpoint = Arc::from(breakpoint.clone());
6747
6748 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
6749 .icon_size(IconSize::XSmall)
6750 .size(ui::ButtonSize::None)
6751 .icon_color(color)
6752 .style(ButtonStyle::Transparent)
6753 .on_click(cx.listener({
6754 let breakpoint = breakpoint.clone();
6755
6756 move |editor, event: &ClickEvent, window, cx| {
6757 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
6758 BreakpointEditAction::InvertState
6759 } else {
6760 BreakpointEditAction::Toggle
6761 };
6762
6763 window.focus(&editor.focus_handle(cx));
6764 editor.edit_breakpoint_at_anchor(
6765 position,
6766 breakpoint.as_ref().clone(),
6767 edit_action,
6768 cx,
6769 );
6770 }
6771 }))
6772 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6773 editor.set_breakpoint_context_menu(
6774 row,
6775 Some(position),
6776 event.down.position,
6777 window,
6778 cx,
6779 );
6780 }))
6781 }
6782
6783 fn build_tasks_context(
6784 project: &Entity<Project>,
6785 buffer: &Entity<Buffer>,
6786 buffer_row: u32,
6787 tasks: &Arc<RunnableTasks>,
6788 cx: &mut Context<Self>,
6789 ) -> Task<Option<task::TaskContext>> {
6790 let position = Point::new(buffer_row, tasks.column);
6791 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
6792 let location = Location {
6793 buffer: buffer.clone(),
6794 range: range_start..range_start,
6795 };
6796 // Fill in the environmental variables from the tree-sitter captures
6797 let mut captured_task_variables = TaskVariables::default();
6798 for (capture_name, value) in tasks.extra_variables.clone() {
6799 captured_task_variables.insert(
6800 task::VariableName::Custom(capture_name.into()),
6801 value.clone(),
6802 );
6803 }
6804 project.update(cx, |project, cx| {
6805 project.task_store().update(cx, |task_store, cx| {
6806 task_store.task_context_for_location(captured_task_variables, location, cx)
6807 })
6808 })
6809 }
6810
6811 pub fn spawn_nearest_task(
6812 &mut self,
6813 action: &SpawnNearestTask,
6814 window: &mut Window,
6815 cx: &mut Context<Self>,
6816 ) {
6817 let Some((workspace, _)) = self.workspace.clone() else {
6818 return;
6819 };
6820 let Some(project) = self.project.clone() else {
6821 return;
6822 };
6823
6824 // Try to find a closest, enclosing node using tree-sitter that has a
6825 // task
6826 let Some((buffer, buffer_row, tasks)) = self
6827 .find_enclosing_node_task(cx)
6828 // Or find the task that's closest in row-distance.
6829 .or_else(|| self.find_closest_task(cx))
6830 else {
6831 return;
6832 };
6833
6834 let reveal_strategy = action.reveal;
6835 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6836 cx.spawn_in(window, async move |_, cx| {
6837 let context = task_context.await?;
6838 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
6839
6840 let resolved = resolved_task.resolved.as_mut()?;
6841 resolved.reveal = reveal_strategy;
6842
6843 workspace
6844 .update_in(cx, |workspace, window, cx| {
6845 workspace.schedule_resolved_task(
6846 task_source_kind,
6847 resolved_task,
6848 false,
6849 window,
6850 cx,
6851 );
6852 })
6853 .ok()
6854 })
6855 .detach();
6856 }
6857
6858 fn find_closest_task(
6859 &mut self,
6860 cx: &mut Context<Self>,
6861 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6862 let cursor_row = self.selections.newest_adjusted(cx).head().row;
6863
6864 let ((buffer_id, row), tasks) = self
6865 .tasks
6866 .iter()
6867 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
6868
6869 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
6870 let tasks = Arc::new(tasks.to_owned());
6871 Some((buffer, *row, tasks))
6872 }
6873
6874 fn find_enclosing_node_task(
6875 &mut self,
6876 cx: &mut Context<Self>,
6877 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6878 let snapshot = self.buffer.read(cx).snapshot(cx);
6879 let offset = self.selections.newest::<usize>(cx).head();
6880 let excerpt = snapshot.excerpt_containing(offset..offset)?;
6881 let buffer_id = excerpt.buffer().remote_id();
6882
6883 let layer = excerpt.buffer().syntax_layer_at(offset)?;
6884 let mut cursor = layer.node().walk();
6885
6886 while cursor.goto_first_child_for_byte(offset).is_some() {
6887 if cursor.node().end_byte() == offset {
6888 cursor.goto_next_sibling();
6889 }
6890 }
6891
6892 // Ascend to the smallest ancestor that contains the range and has a task.
6893 loop {
6894 let node = cursor.node();
6895 let node_range = node.byte_range();
6896 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
6897
6898 // Check if this node contains our offset
6899 if node_range.start <= offset && node_range.end >= offset {
6900 // If it contains offset, check for task
6901 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
6902 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
6903 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
6904 }
6905 }
6906
6907 if !cursor.goto_parent() {
6908 break;
6909 }
6910 }
6911 None
6912 }
6913
6914 fn render_run_indicator(
6915 &self,
6916 _style: &EditorStyle,
6917 is_active: bool,
6918 row: DisplayRow,
6919 breakpoint: Option<(Anchor, Breakpoint)>,
6920 cx: &mut Context<Self>,
6921 ) -> IconButton {
6922 let color = Color::Muted;
6923 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6924
6925 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
6926 .shape(ui::IconButtonShape::Square)
6927 .icon_size(IconSize::XSmall)
6928 .icon_color(color)
6929 .toggle_state(is_active)
6930 .on_click(cx.listener(move |editor, _e, window, cx| {
6931 window.focus(&editor.focus_handle(cx));
6932 editor.toggle_code_actions(
6933 &ToggleCodeActions {
6934 deployed_from_indicator: Some(row),
6935 },
6936 window,
6937 cx,
6938 );
6939 }))
6940 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6941 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
6942 }))
6943 }
6944
6945 pub fn context_menu_visible(&self) -> bool {
6946 !self.edit_prediction_preview_is_active()
6947 && self
6948 .context_menu
6949 .borrow()
6950 .as_ref()
6951 .map_or(false, |menu| menu.visible())
6952 }
6953
6954 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
6955 self.context_menu
6956 .borrow()
6957 .as_ref()
6958 .map(|menu| menu.origin())
6959 }
6960
6961 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
6962 self.context_menu_options = Some(options);
6963 }
6964
6965 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
6966 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
6967
6968 fn render_edit_prediction_popover(
6969 &mut self,
6970 text_bounds: &Bounds<Pixels>,
6971 content_origin: gpui::Point<Pixels>,
6972 editor_snapshot: &EditorSnapshot,
6973 visible_row_range: Range<DisplayRow>,
6974 scroll_top: f32,
6975 scroll_bottom: f32,
6976 line_layouts: &[LineWithInvisibles],
6977 line_height: Pixels,
6978 scroll_pixel_position: gpui::Point<Pixels>,
6979 newest_selection_head: Option<DisplayPoint>,
6980 editor_width: Pixels,
6981 style: &EditorStyle,
6982 window: &mut Window,
6983 cx: &mut App,
6984 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6985 let active_inline_completion = self.active_inline_completion.as_ref()?;
6986
6987 if self.edit_prediction_visible_in_cursor_popover(true) {
6988 return None;
6989 }
6990
6991 match &active_inline_completion.completion {
6992 InlineCompletion::Move { target, .. } => {
6993 let target_display_point = target.to_display_point(editor_snapshot);
6994
6995 if self.edit_prediction_requires_modifier() {
6996 if !self.edit_prediction_preview_is_active() {
6997 return None;
6998 }
6999
7000 self.render_edit_prediction_modifier_jump_popover(
7001 text_bounds,
7002 content_origin,
7003 visible_row_range,
7004 line_layouts,
7005 line_height,
7006 scroll_pixel_position,
7007 newest_selection_head,
7008 target_display_point,
7009 window,
7010 cx,
7011 )
7012 } else {
7013 self.render_edit_prediction_eager_jump_popover(
7014 text_bounds,
7015 content_origin,
7016 editor_snapshot,
7017 visible_row_range,
7018 scroll_top,
7019 scroll_bottom,
7020 line_height,
7021 scroll_pixel_position,
7022 target_display_point,
7023 editor_width,
7024 window,
7025 cx,
7026 )
7027 }
7028 }
7029 InlineCompletion::Edit {
7030 display_mode: EditDisplayMode::Inline,
7031 ..
7032 } => None,
7033 InlineCompletion::Edit {
7034 display_mode: EditDisplayMode::TabAccept,
7035 edits,
7036 ..
7037 } => {
7038 let range = &edits.first()?.0;
7039 let target_display_point = range.end.to_display_point(editor_snapshot);
7040
7041 self.render_edit_prediction_end_of_line_popover(
7042 "Accept",
7043 editor_snapshot,
7044 visible_row_range,
7045 target_display_point,
7046 line_height,
7047 scroll_pixel_position,
7048 content_origin,
7049 editor_width,
7050 window,
7051 cx,
7052 )
7053 }
7054 InlineCompletion::Edit {
7055 edits,
7056 edit_preview,
7057 display_mode: EditDisplayMode::DiffPopover,
7058 snapshot,
7059 } => self.render_edit_prediction_diff_popover(
7060 text_bounds,
7061 content_origin,
7062 editor_snapshot,
7063 visible_row_range,
7064 line_layouts,
7065 line_height,
7066 scroll_pixel_position,
7067 newest_selection_head,
7068 editor_width,
7069 style,
7070 edits,
7071 edit_preview,
7072 snapshot,
7073 window,
7074 cx,
7075 ),
7076 }
7077 }
7078
7079 fn render_edit_prediction_modifier_jump_popover(
7080 &mut self,
7081 text_bounds: &Bounds<Pixels>,
7082 content_origin: gpui::Point<Pixels>,
7083 visible_row_range: Range<DisplayRow>,
7084 line_layouts: &[LineWithInvisibles],
7085 line_height: Pixels,
7086 scroll_pixel_position: gpui::Point<Pixels>,
7087 newest_selection_head: Option<DisplayPoint>,
7088 target_display_point: DisplayPoint,
7089 window: &mut Window,
7090 cx: &mut App,
7091 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7092 let scrolled_content_origin =
7093 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7094
7095 const SCROLL_PADDING_Y: Pixels = px(12.);
7096
7097 if target_display_point.row() < visible_row_range.start {
7098 return self.render_edit_prediction_scroll_popover(
7099 |_| SCROLL_PADDING_Y,
7100 IconName::ArrowUp,
7101 visible_row_range,
7102 line_layouts,
7103 newest_selection_head,
7104 scrolled_content_origin,
7105 window,
7106 cx,
7107 );
7108 } else if target_display_point.row() >= visible_row_range.end {
7109 return self.render_edit_prediction_scroll_popover(
7110 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7111 IconName::ArrowDown,
7112 visible_row_range,
7113 line_layouts,
7114 newest_selection_head,
7115 scrolled_content_origin,
7116 window,
7117 cx,
7118 );
7119 }
7120
7121 const POLE_WIDTH: Pixels = px(2.);
7122
7123 let line_layout =
7124 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7125 let target_column = target_display_point.column() as usize;
7126
7127 let target_x = line_layout.x_for_index(target_column);
7128 let target_y =
7129 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7130
7131 let flag_on_right = target_x < text_bounds.size.width / 2.;
7132
7133 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7134 border_color.l += 0.001;
7135
7136 let mut element = v_flex()
7137 .items_end()
7138 .when(flag_on_right, |el| el.items_start())
7139 .child(if flag_on_right {
7140 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7141 .rounded_bl(px(0.))
7142 .rounded_tl(px(0.))
7143 .border_l_2()
7144 .border_color(border_color)
7145 } else {
7146 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7147 .rounded_br(px(0.))
7148 .rounded_tr(px(0.))
7149 .border_r_2()
7150 .border_color(border_color)
7151 })
7152 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7153 .into_any();
7154
7155 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7156
7157 let mut origin = scrolled_content_origin + point(target_x, target_y)
7158 - point(
7159 if flag_on_right {
7160 POLE_WIDTH
7161 } else {
7162 size.width - POLE_WIDTH
7163 },
7164 size.height - line_height,
7165 );
7166
7167 origin.x = origin.x.max(content_origin.x);
7168
7169 element.prepaint_at(origin, window, cx);
7170
7171 Some((element, origin))
7172 }
7173
7174 fn render_edit_prediction_scroll_popover(
7175 &mut self,
7176 to_y: impl Fn(Size<Pixels>) -> Pixels,
7177 scroll_icon: IconName,
7178 visible_row_range: Range<DisplayRow>,
7179 line_layouts: &[LineWithInvisibles],
7180 newest_selection_head: Option<DisplayPoint>,
7181 scrolled_content_origin: gpui::Point<Pixels>,
7182 window: &mut Window,
7183 cx: &mut App,
7184 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7185 let mut element = self
7186 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7187 .into_any();
7188
7189 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7190
7191 let cursor = newest_selection_head?;
7192 let cursor_row_layout =
7193 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7194 let cursor_column = cursor.column() as usize;
7195
7196 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7197
7198 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7199
7200 element.prepaint_at(origin, window, cx);
7201 Some((element, origin))
7202 }
7203
7204 fn render_edit_prediction_eager_jump_popover(
7205 &mut self,
7206 text_bounds: &Bounds<Pixels>,
7207 content_origin: gpui::Point<Pixels>,
7208 editor_snapshot: &EditorSnapshot,
7209 visible_row_range: Range<DisplayRow>,
7210 scroll_top: f32,
7211 scroll_bottom: f32,
7212 line_height: Pixels,
7213 scroll_pixel_position: gpui::Point<Pixels>,
7214 target_display_point: DisplayPoint,
7215 editor_width: Pixels,
7216 window: &mut Window,
7217 cx: &mut App,
7218 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7219 if target_display_point.row().as_f32() < scroll_top {
7220 let mut element = self
7221 .render_edit_prediction_line_popover(
7222 "Jump to Edit",
7223 Some(IconName::ArrowUp),
7224 window,
7225 cx,
7226 )?
7227 .into_any();
7228
7229 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7230 let offset = point(
7231 (text_bounds.size.width - size.width) / 2.,
7232 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7233 );
7234
7235 let origin = text_bounds.origin + offset;
7236 element.prepaint_at(origin, window, cx);
7237 Some((element, origin))
7238 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7239 let mut element = self
7240 .render_edit_prediction_line_popover(
7241 "Jump to Edit",
7242 Some(IconName::ArrowDown),
7243 window,
7244 cx,
7245 )?
7246 .into_any();
7247
7248 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7249 let offset = point(
7250 (text_bounds.size.width - size.width) / 2.,
7251 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7252 );
7253
7254 let origin = text_bounds.origin + offset;
7255 element.prepaint_at(origin, window, cx);
7256 Some((element, origin))
7257 } else {
7258 self.render_edit_prediction_end_of_line_popover(
7259 "Jump to Edit",
7260 editor_snapshot,
7261 visible_row_range,
7262 target_display_point,
7263 line_height,
7264 scroll_pixel_position,
7265 content_origin,
7266 editor_width,
7267 window,
7268 cx,
7269 )
7270 }
7271 }
7272
7273 fn render_edit_prediction_end_of_line_popover(
7274 self: &mut Editor,
7275 label: &'static str,
7276 editor_snapshot: &EditorSnapshot,
7277 visible_row_range: Range<DisplayRow>,
7278 target_display_point: DisplayPoint,
7279 line_height: Pixels,
7280 scroll_pixel_position: gpui::Point<Pixels>,
7281 content_origin: gpui::Point<Pixels>,
7282 editor_width: Pixels,
7283 window: &mut Window,
7284 cx: &mut App,
7285 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7286 let target_line_end = DisplayPoint::new(
7287 target_display_point.row(),
7288 editor_snapshot.line_len(target_display_point.row()),
7289 );
7290
7291 let mut element = self
7292 .render_edit_prediction_line_popover(label, None, window, cx)?
7293 .into_any();
7294
7295 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7296
7297 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7298
7299 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7300 let mut origin = start_point
7301 + line_origin
7302 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7303 origin.x = origin.x.max(content_origin.x);
7304
7305 let max_x = content_origin.x + editor_width - size.width;
7306
7307 if origin.x > max_x {
7308 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7309
7310 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7311 origin.y += offset;
7312 IconName::ArrowUp
7313 } else {
7314 origin.y -= offset;
7315 IconName::ArrowDown
7316 };
7317
7318 element = self
7319 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7320 .into_any();
7321
7322 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7323
7324 origin.x = content_origin.x + editor_width - size.width - px(2.);
7325 }
7326
7327 element.prepaint_at(origin, window, cx);
7328 Some((element, origin))
7329 }
7330
7331 fn render_edit_prediction_diff_popover(
7332 self: &Editor,
7333 text_bounds: &Bounds<Pixels>,
7334 content_origin: gpui::Point<Pixels>,
7335 editor_snapshot: &EditorSnapshot,
7336 visible_row_range: Range<DisplayRow>,
7337 line_layouts: &[LineWithInvisibles],
7338 line_height: Pixels,
7339 scroll_pixel_position: gpui::Point<Pixels>,
7340 newest_selection_head: Option<DisplayPoint>,
7341 editor_width: Pixels,
7342 style: &EditorStyle,
7343 edits: &Vec<(Range<Anchor>, String)>,
7344 edit_preview: &Option<language::EditPreview>,
7345 snapshot: &language::BufferSnapshot,
7346 window: &mut Window,
7347 cx: &mut App,
7348 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7349 let edit_start = edits
7350 .first()
7351 .unwrap()
7352 .0
7353 .start
7354 .to_display_point(editor_snapshot);
7355 let edit_end = edits
7356 .last()
7357 .unwrap()
7358 .0
7359 .end
7360 .to_display_point(editor_snapshot);
7361
7362 let is_visible = visible_row_range.contains(&edit_start.row())
7363 || visible_row_range.contains(&edit_end.row());
7364 if !is_visible {
7365 return None;
7366 }
7367
7368 let highlighted_edits =
7369 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7370
7371 let styled_text = highlighted_edits.to_styled_text(&style.text);
7372 let line_count = highlighted_edits.text.lines().count();
7373
7374 const BORDER_WIDTH: Pixels = px(1.);
7375
7376 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7377 let has_keybind = keybind.is_some();
7378
7379 let mut element = h_flex()
7380 .items_start()
7381 .child(
7382 h_flex()
7383 .bg(cx.theme().colors().editor_background)
7384 .border(BORDER_WIDTH)
7385 .shadow_sm()
7386 .border_color(cx.theme().colors().border)
7387 .rounded_l_lg()
7388 .when(line_count > 1, |el| el.rounded_br_lg())
7389 .pr_1()
7390 .child(styled_text),
7391 )
7392 .child(
7393 h_flex()
7394 .h(line_height + BORDER_WIDTH * 2.)
7395 .px_1p5()
7396 .gap_1()
7397 // Workaround: For some reason, there's a gap if we don't do this
7398 .ml(-BORDER_WIDTH)
7399 .shadow(smallvec![gpui::BoxShadow {
7400 color: gpui::black().opacity(0.05),
7401 offset: point(px(1.), px(1.)),
7402 blur_radius: px(2.),
7403 spread_radius: px(0.),
7404 }])
7405 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7406 .border(BORDER_WIDTH)
7407 .border_color(cx.theme().colors().border)
7408 .rounded_r_lg()
7409 .id("edit_prediction_diff_popover_keybind")
7410 .when(!has_keybind, |el| {
7411 let status_colors = cx.theme().status();
7412
7413 el.bg(status_colors.error_background)
7414 .border_color(status_colors.error.opacity(0.6))
7415 .child(Icon::new(IconName::Info).color(Color::Error))
7416 .cursor_default()
7417 .hoverable_tooltip(move |_window, cx| {
7418 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7419 })
7420 })
7421 .children(keybind),
7422 )
7423 .into_any();
7424
7425 let longest_row =
7426 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7427 let longest_line_width = if visible_row_range.contains(&longest_row) {
7428 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7429 } else {
7430 layout_line(
7431 longest_row,
7432 editor_snapshot,
7433 style,
7434 editor_width,
7435 |_| false,
7436 window,
7437 cx,
7438 )
7439 .width
7440 };
7441
7442 let viewport_bounds =
7443 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
7444 right: -EditorElement::SCROLLBAR_WIDTH,
7445 ..Default::default()
7446 });
7447
7448 let x_after_longest =
7449 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
7450 - scroll_pixel_position.x;
7451
7452 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7453
7454 // Fully visible if it can be displayed within the window (allow overlapping other
7455 // panes). However, this is only allowed if the popover starts within text_bounds.
7456 let can_position_to_the_right = x_after_longest < text_bounds.right()
7457 && x_after_longest + element_bounds.width < viewport_bounds.right();
7458
7459 let mut origin = if can_position_to_the_right {
7460 point(
7461 x_after_longest,
7462 text_bounds.origin.y + edit_start.row().as_f32() * line_height
7463 - scroll_pixel_position.y,
7464 )
7465 } else {
7466 let cursor_row = newest_selection_head.map(|head| head.row());
7467 let above_edit = edit_start
7468 .row()
7469 .0
7470 .checked_sub(line_count as u32)
7471 .map(DisplayRow);
7472 let below_edit = Some(edit_end.row() + 1);
7473 let above_cursor =
7474 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
7475 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
7476
7477 // Place the edit popover adjacent to the edit if there is a location
7478 // available that is onscreen and does not obscure the cursor. Otherwise,
7479 // place it adjacent to the cursor.
7480 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
7481 .into_iter()
7482 .flatten()
7483 .find(|&start_row| {
7484 let end_row = start_row + line_count as u32;
7485 visible_row_range.contains(&start_row)
7486 && visible_row_range.contains(&end_row)
7487 && cursor_row.map_or(true, |cursor_row| {
7488 !((start_row..end_row).contains(&cursor_row))
7489 })
7490 })?;
7491
7492 content_origin
7493 + point(
7494 -scroll_pixel_position.x,
7495 row_target.as_f32() * line_height - scroll_pixel_position.y,
7496 )
7497 };
7498
7499 origin.x -= BORDER_WIDTH;
7500
7501 window.defer_draw(element, origin, 1);
7502
7503 // Do not return an element, since it will already be drawn due to defer_draw.
7504 None
7505 }
7506
7507 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
7508 px(30.)
7509 }
7510
7511 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
7512 if self.read_only(cx) {
7513 cx.theme().players().read_only()
7514 } else {
7515 self.style.as_ref().unwrap().local_player
7516 }
7517 }
7518
7519 fn render_edit_prediction_accept_keybind(
7520 &self,
7521 window: &mut Window,
7522 cx: &App,
7523 ) -> Option<AnyElement> {
7524 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
7525 let accept_keystroke = accept_binding.keystroke()?;
7526
7527 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7528
7529 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
7530 Color::Accent
7531 } else {
7532 Color::Muted
7533 };
7534
7535 h_flex()
7536 .px_0p5()
7537 .when(is_platform_style_mac, |parent| parent.gap_0p5())
7538 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7539 .text_size(TextSize::XSmall.rems(cx))
7540 .child(h_flex().children(ui::render_modifiers(
7541 &accept_keystroke.modifiers,
7542 PlatformStyle::platform(),
7543 Some(modifiers_color),
7544 Some(IconSize::XSmall.rems().into()),
7545 true,
7546 )))
7547 .when(is_platform_style_mac, |parent| {
7548 parent.child(accept_keystroke.key.clone())
7549 })
7550 .when(!is_platform_style_mac, |parent| {
7551 parent.child(
7552 Key::new(
7553 util::capitalize(&accept_keystroke.key),
7554 Some(Color::Default),
7555 )
7556 .size(Some(IconSize::XSmall.rems().into())),
7557 )
7558 })
7559 .into_any()
7560 .into()
7561 }
7562
7563 fn render_edit_prediction_line_popover(
7564 &self,
7565 label: impl Into<SharedString>,
7566 icon: Option<IconName>,
7567 window: &mut Window,
7568 cx: &App,
7569 ) -> Option<Stateful<Div>> {
7570 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
7571
7572 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7573 let has_keybind = keybind.is_some();
7574
7575 let result = h_flex()
7576 .id("ep-line-popover")
7577 .py_0p5()
7578 .pl_1()
7579 .pr(padding_right)
7580 .gap_1()
7581 .rounded_md()
7582 .border_1()
7583 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7584 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7585 .shadow_sm()
7586 .when(!has_keybind, |el| {
7587 let status_colors = cx.theme().status();
7588
7589 el.bg(status_colors.error_background)
7590 .border_color(status_colors.error.opacity(0.6))
7591 .pl_2()
7592 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7593 .cursor_default()
7594 .hoverable_tooltip(move |_window, cx| {
7595 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7596 })
7597 })
7598 .children(keybind)
7599 .child(
7600 Label::new(label)
7601 .size(LabelSize::Small)
7602 .when(!has_keybind, |el| {
7603 el.color(cx.theme().status().error.into()).strikethrough()
7604 }),
7605 )
7606 .when(!has_keybind, |el| {
7607 el.child(
7608 h_flex().ml_1().child(
7609 Icon::new(IconName::Info)
7610 .size(IconSize::Small)
7611 .color(cx.theme().status().error.into()),
7612 ),
7613 )
7614 })
7615 .when_some(icon, |element, icon| {
7616 element.child(
7617 div()
7618 .mt(px(1.5))
7619 .child(Icon::new(icon).size(IconSize::Small)),
7620 )
7621 });
7622
7623 Some(result)
7624 }
7625
7626 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
7627 let accent_color = cx.theme().colors().text_accent;
7628 let editor_bg_color = cx.theme().colors().editor_background;
7629 editor_bg_color.blend(accent_color.opacity(0.1))
7630 }
7631
7632 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
7633 let accent_color = cx.theme().colors().text_accent;
7634 let editor_bg_color = cx.theme().colors().editor_background;
7635 editor_bg_color.blend(accent_color.opacity(0.6))
7636 }
7637
7638 fn render_edit_prediction_cursor_popover(
7639 &self,
7640 min_width: Pixels,
7641 max_width: Pixels,
7642 cursor_point: Point,
7643 style: &EditorStyle,
7644 accept_keystroke: Option<&gpui::Keystroke>,
7645 _window: &Window,
7646 cx: &mut Context<Editor>,
7647 ) -> Option<AnyElement> {
7648 let provider = self.edit_prediction_provider.as_ref()?;
7649
7650 if provider.provider.needs_terms_acceptance(cx) {
7651 return Some(
7652 h_flex()
7653 .min_w(min_width)
7654 .flex_1()
7655 .px_2()
7656 .py_1()
7657 .gap_3()
7658 .elevation_2(cx)
7659 .hover(|style| style.bg(cx.theme().colors().element_hover))
7660 .id("accept-terms")
7661 .cursor_pointer()
7662 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
7663 .on_click(cx.listener(|this, _event, window, cx| {
7664 cx.stop_propagation();
7665 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
7666 window.dispatch_action(
7667 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
7668 cx,
7669 );
7670 }))
7671 .child(
7672 h_flex()
7673 .flex_1()
7674 .gap_2()
7675 .child(Icon::new(IconName::ZedPredict))
7676 .child(Label::new("Accept Terms of Service"))
7677 .child(div().w_full())
7678 .child(
7679 Icon::new(IconName::ArrowUpRight)
7680 .color(Color::Muted)
7681 .size(IconSize::Small),
7682 )
7683 .into_any_element(),
7684 )
7685 .into_any(),
7686 );
7687 }
7688
7689 let is_refreshing = provider.provider.is_refreshing(cx);
7690
7691 fn pending_completion_container() -> Div {
7692 h_flex()
7693 .h_full()
7694 .flex_1()
7695 .gap_2()
7696 .child(Icon::new(IconName::ZedPredict))
7697 }
7698
7699 let completion = match &self.active_inline_completion {
7700 Some(prediction) => {
7701 if !self.has_visible_completions_menu() {
7702 const RADIUS: Pixels = px(6.);
7703 const BORDER_WIDTH: Pixels = px(1.);
7704
7705 return Some(
7706 h_flex()
7707 .elevation_2(cx)
7708 .border(BORDER_WIDTH)
7709 .border_color(cx.theme().colors().border)
7710 .when(accept_keystroke.is_none(), |el| {
7711 el.border_color(cx.theme().status().error)
7712 })
7713 .rounded(RADIUS)
7714 .rounded_tl(px(0.))
7715 .overflow_hidden()
7716 .child(div().px_1p5().child(match &prediction.completion {
7717 InlineCompletion::Move { target, snapshot } => {
7718 use text::ToPoint as _;
7719 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
7720 {
7721 Icon::new(IconName::ZedPredictDown)
7722 } else {
7723 Icon::new(IconName::ZedPredictUp)
7724 }
7725 }
7726 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
7727 }))
7728 .child(
7729 h_flex()
7730 .gap_1()
7731 .py_1()
7732 .px_2()
7733 .rounded_r(RADIUS - BORDER_WIDTH)
7734 .border_l_1()
7735 .border_color(cx.theme().colors().border)
7736 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7737 .when(self.edit_prediction_preview.released_too_fast(), |el| {
7738 el.child(
7739 Label::new("Hold")
7740 .size(LabelSize::Small)
7741 .when(accept_keystroke.is_none(), |el| {
7742 el.strikethrough()
7743 })
7744 .line_height_style(LineHeightStyle::UiLabel),
7745 )
7746 })
7747 .id("edit_prediction_cursor_popover_keybind")
7748 .when(accept_keystroke.is_none(), |el| {
7749 let status_colors = cx.theme().status();
7750
7751 el.bg(status_colors.error_background)
7752 .border_color(status_colors.error.opacity(0.6))
7753 .child(Icon::new(IconName::Info).color(Color::Error))
7754 .cursor_default()
7755 .hoverable_tooltip(move |_window, cx| {
7756 cx.new(|_| MissingEditPredictionKeybindingTooltip)
7757 .into()
7758 })
7759 })
7760 .when_some(
7761 accept_keystroke.as_ref(),
7762 |el, accept_keystroke| {
7763 el.child(h_flex().children(ui::render_modifiers(
7764 &accept_keystroke.modifiers,
7765 PlatformStyle::platform(),
7766 Some(Color::Default),
7767 Some(IconSize::XSmall.rems().into()),
7768 false,
7769 )))
7770 },
7771 ),
7772 )
7773 .into_any(),
7774 );
7775 }
7776
7777 self.render_edit_prediction_cursor_popover_preview(
7778 prediction,
7779 cursor_point,
7780 style,
7781 cx,
7782 )?
7783 }
7784
7785 None if is_refreshing => match &self.stale_inline_completion_in_menu {
7786 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
7787 stale_completion,
7788 cursor_point,
7789 style,
7790 cx,
7791 )?,
7792
7793 None => {
7794 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
7795 }
7796 },
7797
7798 None => pending_completion_container().child(Label::new("No Prediction")),
7799 };
7800
7801 let completion = if is_refreshing {
7802 completion
7803 .with_animation(
7804 "loading-completion",
7805 Animation::new(Duration::from_secs(2))
7806 .repeat()
7807 .with_easing(pulsating_between(0.4, 0.8)),
7808 |label, delta| label.opacity(delta),
7809 )
7810 .into_any_element()
7811 } else {
7812 completion.into_any_element()
7813 };
7814
7815 let has_completion = self.active_inline_completion.is_some();
7816
7817 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7818 Some(
7819 h_flex()
7820 .min_w(min_width)
7821 .max_w(max_width)
7822 .flex_1()
7823 .elevation_2(cx)
7824 .border_color(cx.theme().colors().border)
7825 .child(
7826 div()
7827 .flex_1()
7828 .py_1()
7829 .px_2()
7830 .overflow_hidden()
7831 .child(completion),
7832 )
7833 .when_some(accept_keystroke, |el, accept_keystroke| {
7834 if !accept_keystroke.modifiers.modified() {
7835 return el;
7836 }
7837
7838 el.child(
7839 h_flex()
7840 .h_full()
7841 .border_l_1()
7842 .rounded_r_lg()
7843 .border_color(cx.theme().colors().border)
7844 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7845 .gap_1()
7846 .py_1()
7847 .px_2()
7848 .child(
7849 h_flex()
7850 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7851 .when(is_platform_style_mac, |parent| parent.gap_1())
7852 .child(h_flex().children(ui::render_modifiers(
7853 &accept_keystroke.modifiers,
7854 PlatformStyle::platform(),
7855 Some(if !has_completion {
7856 Color::Muted
7857 } else {
7858 Color::Default
7859 }),
7860 None,
7861 false,
7862 ))),
7863 )
7864 .child(Label::new("Preview").into_any_element())
7865 .opacity(if has_completion { 1.0 } else { 0.4 }),
7866 )
7867 })
7868 .into_any(),
7869 )
7870 }
7871
7872 fn render_edit_prediction_cursor_popover_preview(
7873 &self,
7874 completion: &InlineCompletionState,
7875 cursor_point: Point,
7876 style: &EditorStyle,
7877 cx: &mut Context<Editor>,
7878 ) -> Option<Div> {
7879 use text::ToPoint as _;
7880
7881 fn render_relative_row_jump(
7882 prefix: impl Into<String>,
7883 current_row: u32,
7884 target_row: u32,
7885 ) -> Div {
7886 let (row_diff, arrow) = if target_row < current_row {
7887 (current_row - target_row, IconName::ArrowUp)
7888 } else {
7889 (target_row - current_row, IconName::ArrowDown)
7890 };
7891
7892 h_flex()
7893 .child(
7894 Label::new(format!("{}{}", prefix.into(), row_diff))
7895 .color(Color::Muted)
7896 .size(LabelSize::Small),
7897 )
7898 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
7899 }
7900
7901 match &completion.completion {
7902 InlineCompletion::Move {
7903 target, snapshot, ..
7904 } => Some(
7905 h_flex()
7906 .px_2()
7907 .gap_2()
7908 .flex_1()
7909 .child(
7910 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
7911 Icon::new(IconName::ZedPredictDown)
7912 } else {
7913 Icon::new(IconName::ZedPredictUp)
7914 },
7915 )
7916 .child(Label::new("Jump to Edit")),
7917 ),
7918
7919 InlineCompletion::Edit {
7920 edits,
7921 edit_preview,
7922 snapshot,
7923 display_mode: _,
7924 } => {
7925 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
7926
7927 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
7928 &snapshot,
7929 &edits,
7930 edit_preview.as_ref()?,
7931 true,
7932 cx,
7933 )
7934 .first_line_preview();
7935
7936 let styled_text = gpui::StyledText::new(highlighted_edits.text)
7937 .with_default_highlights(&style.text, highlighted_edits.highlights);
7938
7939 let preview = h_flex()
7940 .gap_1()
7941 .min_w_16()
7942 .child(styled_text)
7943 .when(has_more_lines, |parent| parent.child("…"));
7944
7945 let left = if first_edit_row != cursor_point.row {
7946 render_relative_row_jump("", cursor_point.row, first_edit_row)
7947 .into_any_element()
7948 } else {
7949 Icon::new(IconName::ZedPredict).into_any_element()
7950 };
7951
7952 Some(
7953 h_flex()
7954 .h_full()
7955 .flex_1()
7956 .gap_2()
7957 .pr_1()
7958 .overflow_x_hidden()
7959 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7960 .child(left)
7961 .child(preview),
7962 )
7963 }
7964 }
7965 }
7966
7967 fn render_context_menu(
7968 &self,
7969 style: &EditorStyle,
7970 max_height_in_lines: u32,
7971 window: &mut Window,
7972 cx: &mut Context<Editor>,
7973 ) -> Option<AnyElement> {
7974 let menu = self.context_menu.borrow();
7975 let menu = menu.as_ref()?;
7976 if !menu.visible() {
7977 return None;
7978 };
7979 Some(menu.render(style, max_height_in_lines, window, cx))
7980 }
7981
7982 fn render_context_menu_aside(
7983 &mut self,
7984 max_size: Size<Pixels>,
7985 window: &mut Window,
7986 cx: &mut Context<Editor>,
7987 ) -> Option<AnyElement> {
7988 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
7989 if menu.visible() {
7990 menu.render_aside(self, max_size, window, cx)
7991 } else {
7992 None
7993 }
7994 })
7995 }
7996
7997 fn hide_context_menu(
7998 &mut self,
7999 window: &mut Window,
8000 cx: &mut Context<Self>,
8001 ) -> Option<CodeContextMenu> {
8002 cx.notify();
8003 self.completion_tasks.clear();
8004 let context_menu = self.context_menu.borrow_mut().take();
8005 self.stale_inline_completion_in_menu.take();
8006 self.update_visible_inline_completion(window, cx);
8007 context_menu
8008 }
8009
8010 fn show_snippet_choices(
8011 &mut self,
8012 choices: &Vec<String>,
8013 selection: Range<Anchor>,
8014 cx: &mut Context<Self>,
8015 ) {
8016 if selection.start.buffer_id.is_none() {
8017 return;
8018 }
8019 let buffer_id = selection.start.buffer_id.unwrap();
8020 let buffer = self.buffer().read(cx).buffer(buffer_id);
8021 let id = post_inc(&mut self.next_completion_id);
8022 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8023
8024 if let Some(buffer) = buffer {
8025 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8026 CompletionsMenu::new_snippet_choices(
8027 id,
8028 true,
8029 choices,
8030 selection,
8031 buffer,
8032 snippet_sort_order,
8033 ),
8034 ));
8035 }
8036 }
8037
8038 pub fn insert_snippet(
8039 &mut self,
8040 insertion_ranges: &[Range<usize>],
8041 snippet: Snippet,
8042 window: &mut Window,
8043 cx: &mut Context<Self>,
8044 ) -> Result<()> {
8045 struct Tabstop<T> {
8046 is_end_tabstop: bool,
8047 ranges: Vec<Range<T>>,
8048 choices: Option<Vec<String>>,
8049 }
8050
8051 let tabstops = self.buffer.update(cx, |buffer, cx| {
8052 let snippet_text: Arc<str> = snippet.text.clone().into();
8053 let edits = insertion_ranges
8054 .iter()
8055 .cloned()
8056 .map(|range| (range, snippet_text.clone()));
8057 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8058
8059 let snapshot = &*buffer.read(cx);
8060 let snippet = &snippet;
8061 snippet
8062 .tabstops
8063 .iter()
8064 .map(|tabstop| {
8065 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8066 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8067 });
8068 let mut tabstop_ranges = tabstop
8069 .ranges
8070 .iter()
8071 .flat_map(|tabstop_range| {
8072 let mut delta = 0_isize;
8073 insertion_ranges.iter().map(move |insertion_range| {
8074 let insertion_start = insertion_range.start as isize + delta;
8075 delta +=
8076 snippet.text.len() as isize - insertion_range.len() as isize;
8077
8078 let start = ((insertion_start + tabstop_range.start) as usize)
8079 .min(snapshot.len());
8080 let end = ((insertion_start + tabstop_range.end) as usize)
8081 .min(snapshot.len());
8082 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8083 })
8084 })
8085 .collect::<Vec<_>>();
8086 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8087
8088 Tabstop {
8089 is_end_tabstop,
8090 ranges: tabstop_ranges,
8091 choices: tabstop.choices.clone(),
8092 }
8093 })
8094 .collect::<Vec<_>>()
8095 });
8096 if let Some(tabstop) = tabstops.first() {
8097 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8098 s.select_ranges(tabstop.ranges.iter().cloned());
8099 });
8100
8101 if let Some(choices) = &tabstop.choices {
8102 if let Some(selection) = tabstop.ranges.first() {
8103 self.show_snippet_choices(choices, selection.clone(), cx)
8104 }
8105 }
8106
8107 // If we're already at the last tabstop and it's at the end of the snippet,
8108 // we're done, we don't need to keep the state around.
8109 if !tabstop.is_end_tabstop {
8110 let choices = tabstops
8111 .iter()
8112 .map(|tabstop| tabstop.choices.clone())
8113 .collect();
8114
8115 let ranges = tabstops
8116 .into_iter()
8117 .map(|tabstop| tabstop.ranges)
8118 .collect::<Vec<_>>();
8119
8120 self.snippet_stack.push(SnippetState {
8121 active_index: 0,
8122 ranges,
8123 choices,
8124 });
8125 }
8126
8127 // Check whether the just-entered snippet ends with an auto-closable bracket.
8128 if self.autoclose_regions.is_empty() {
8129 let snapshot = self.buffer.read(cx).snapshot(cx);
8130 for selection in &mut self.selections.all::<Point>(cx) {
8131 let selection_head = selection.head();
8132 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8133 continue;
8134 };
8135
8136 let mut bracket_pair = None;
8137 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8138 let prev_chars = snapshot
8139 .reversed_chars_at(selection_head)
8140 .collect::<String>();
8141 for (pair, enabled) in scope.brackets() {
8142 if enabled
8143 && pair.close
8144 && prev_chars.starts_with(pair.start.as_str())
8145 && next_chars.starts_with(pair.end.as_str())
8146 {
8147 bracket_pair = Some(pair.clone());
8148 break;
8149 }
8150 }
8151 if let Some(pair) = bracket_pair {
8152 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8153 let autoclose_enabled =
8154 self.use_autoclose && snapshot_settings.use_autoclose;
8155 if autoclose_enabled {
8156 let start = snapshot.anchor_after(selection_head);
8157 let end = snapshot.anchor_after(selection_head);
8158 self.autoclose_regions.push(AutocloseRegion {
8159 selection_id: selection.id,
8160 range: start..end,
8161 pair,
8162 });
8163 }
8164 }
8165 }
8166 }
8167 }
8168 Ok(())
8169 }
8170
8171 pub fn move_to_next_snippet_tabstop(
8172 &mut self,
8173 window: &mut Window,
8174 cx: &mut Context<Self>,
8175 ) -> bool {
8176 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8177 }
8178
8179 pub fn move_to_prev_snippet_tabstop(
8180 &mut self,
8181 window: &mut Window,
8182 cx: &mut Context<Self>,
8183 ) -> bool {
8184 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8185 }
8186
8187 pub fn move_to_snippet_tabstop(
8188 &mut self,
8189 bias: Bias,
8190 window: &mut Window,
8191 cx: &mut Context<Self>,
8192 ) -> bool {
8193 if let Some(mut snippet) = self.snippet_stack.pop() {
8194 match bias {
8195 Bias::Left => {
8196 if snippet.active_index > 0 {
8197 snippet.active_index -= 1;
8198 } else {
8199 self.snippet_stack.push(snippet);
8200 return false;
8201 }
8202 }
8203 Bias::Right => {
8204 if snippet.active_index + 1 < snippet.ranges.len() {
8205 snippet.active_index += 1;
8206 } else {
8207 self.snippet_stack.push(snippet);
8208 return false;
8209 }
8210 }
8211 }
8212 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8213 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8214 s.select_anchor_ranges(current_ranges.iter().cloned())
8215 });
8216
8217 if let Some(choices) = &snippet.choices[snippet.active_index] {
8218 if let Some(selection) = current_ranges.first() {
8219 self.show_snippet_choices(&choices, selection.clone(), cx);
8220 }
8221 }
8222
8223 // If snippet state is not at the last tabstop, push it back on the stack
8224 if snippet.active_index + 1 < snippet.ranges.len() {
8225 self.snippet_stack.push(snippet);
8226 }
8227 return true;
8228 }
8229 }
8230
8231 false
8232 }
8233
8234 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8235 self.transact(window, cx, |this, window, cx| {
8236 this.select_all(&SelectAll, window, cx);
8237 this.insert("", window, cx);
8238 });
8239 }
8240
8241 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8242 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8243 self.transact(window, cx, |this, window, cx| {
8244 this.select_autoclose_pair(window, cx);
8245 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8246 if !this.linked_edit_ranges.is_empty() {
8247 let selections = this.selections.all::<MultiBufferPoint>(cx);
8248 let snapshot = this.buffer.read(cx).snapshot(cx);
8249
8250 for selection in selections.iter() {
8251 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8252 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8253 if selection_start.buffer_id != selection_end.buffer_id {
8254 continue;
8255 }
8256 if let Some(ranges) =
8257 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8258 {
8259 for (buffer, entries) in ranges {
8260 linked_ranges.entry(buffer).or_default().extend(entries);
8261 }
8262 }
8263 }
8264 }
8265
8266 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8267 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8268 for selection in &mut selections {
8269 if selection.is_empty() {
8270 let old_head = selection.head();
8271 let mut new_head =
8272 movement::left(&display_map, old_head.to_display_point(&display_map))
8273 .to_point(&display_map);
8274 if let Some((buffer, line_buffer_range)) = display_map
8275 .buffer_snapshot
8276 .buffer_line_for_row(MultiBufferRow(old_head.row))
8277 {
8278 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8279 let indent_len = match indent_size.kind {
8280 IndentKind::Space => {
8281 buffer.settings_at(line_buffer_range.start, cx).tab_size
8282 }
8283 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8284 };
8285 if old_head.column <= indent_size.len && old_head.column > 0 {
8286 let indent_len = indent_len.get();
8287 new_head = cmp::min(
8288 new_head,
8289 MultiBufferPoint::new(
8290 old_head.row,
8291 ((old_head.column - 1) / indent_len) * indent_len,
8292 ),
8293 );
8294 }
8295 }
8296
8297 selection.set_head(new_head, SelectionGoal::None);
8298 }
8299 }
8300
8301 this.signature_help_state.set_backspace_pressed(true);
8302 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8303 s.select(selections)
8304 });
8305 this.insert("", window, cx);
8306 let empty_str: Arc<str> = Arc::from("");
8307 for (buffer, edits) in linked_ranges {
8308 let snapshot = buffer.read(cx).snapshot();
8309 use text::ToPoint as TP;
8310
8311 let edits = edits
8312 .into_iter()
8313 .map(|range| {
8314 let end_point = TP::to_point(&range.end, &snapshot);
8315 let mut start_point = TP::to_point(&range.start, &snapshot);
8316
8317 if end_point == start_point {
8318 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8319 .saturating_sub(1);
8320 start_point =
8321 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8322 };
8323
8324 (start_point..end_point, empty_str.clone())
8325 })
8326 .sorted_by_key(|(range, _)| range.start)
8327 .collect::<Vec<_>>();
8328 buffer.update(cx, |this, cx| {
8329 this.edit(edits, None, cx);
8330 })
8331 }
8332 this.refresh_inline_completion(true, false, window, cx);
8333 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8334 });
8335 }
8336
8337 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8338 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8339 self.transact(window, cx, |this, window, cx| {
8340 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8341 s.move_with(|map, selection| {
8342 if selection.is_empty() {
8343 let cursor = movement::right(map, selection.head());
8344 selection.end = cursor;
8345 selection.reversed = true;
8346 selection.goal = SelectionGoal::None;
8347 }
8348 })
8349 });
8350 this.insert("", window, cx);
8351 this.refresh_inline_completion(true, false, window, cx);
8352 });
8353 }
8354
8355 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8356 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8357 if self.move_to_prev_snippet_tabstop(window, cx) {
8358 return;
8359 }
8360 self.outdent(&Outdent, window, cx);
8361 }
8362
8363 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8364 if self.move_to_next_snippet_tabstop(window, cx) {
8365 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8366 return;
8367 }
8368 if self.read_only(cx) {
8369 return;
8370 }
8371 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8372 let mut selections = self.selections.all_adjusted(cx);
8373 let buffer = self.buffer.read(cx);
8374 let snapshot = buffer.snapshot(cx);
8375 let rows_iter = selections.iter().map(|s| s.head().row);
8376 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8377
8378 let mut edits = Vec::new();
8379 let mut prev_edited_row = 0;
8380 let mut row_delta = 0;
8381 for selection in &mut selections {
8382 if selection.start.row != prev_edited_row {
8383 row_delta = 0;
8384 }
8385 prev_edited_row = selection.end.row;
8386
8387 // If the selection is non-empty, then increase the indentation of the selected lines.
8388 if !selection.is_empty() {
8389 row_delta =
8390 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8391 continue;
8392 }
8393
8394 // If the selection is empty and the cursor is in the leading whitespace before the
8395 // suggested indentation, then auto-indent the line.
8396 let cursor = selection.head();
8397 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8398 if let Some(suggested_indent) =
8399 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8400 {
8401 if cursor.column < suggested_indent.len
8402 && cursor.column <= current_indent.len
8403 && current_indent.len <= suggested_indent.len
8404 {
8405 selection.start = Point::new(cursor.row, suggested_indent.len);
8406 selection.end = selection.start;
8407 if row_delta == 0 {
8408 edits.extend(Buffer::edit_for_indent_size_adjustment(
8409 cursor.row,
8410 current_indent,
8411 suggested_indent,
8412 ));
8413 row_delta = suggested_indent.len - current_indent.len;
8414 }
8415 continue;
8416 }
8417 }
8418
8419 // Otherwise, insert a hard or soft tab.
8420 let settings = buffer.language_settings_at(cursor, cx);
8421 let tab_size = if settings.hard_tabs {
8422 IndentSize::tab()
8423 } else {
8424 let tab_size = settings.tab_size.get();
8425 let indent_remainder = snapshot
8426 .text_for_range(Point::new(cursor.row, 0)..cursor)
8427 .flat_map(str::chars)
8428 .fold(row_delta % tab_size, |counter: u32, c| {
8429 if c == '\t' {
8430 0
8431 } else {
8432 (counter + 1) % tab_size
8433 }
8434 });
8435
8436 let chars_to_next_tab_stop = tab_size - indent_remainder;
8437 IndentSize::spaces(chars_to_next_tab_stop)
8438 };
8439 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
8440 selection.end = selection.start;
8441 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
8442 row_delta += tab_size.len;
8443 }
8444
8445 self.transact(window, cx, |this, window, cx| {
8446 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8447 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8448 s.select(selections)
8449 });
8450 this.refresh_inline_completion(true, false, window, cx);
8451 });
8452 }
8453
8454 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
8455 if self.read_only(cx) {
8456 return;
8457 }
8458 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8459 let mut selections = self.selections.all::<Point>(cx);
8460 let mut prev_edited_row = 0;
8461 let mut row_delta = 0;
8462 let mut edits = Vec::new();
8463 let buffer = self.buffer.read(cx);
8464 let snapshot = buffer.snapshot(cx);
8465 for selection in &mut selections {
8466 if selection.start.row != prev_edited_row {
8467 row_delta = 0;
8468 }
8469 prev_edited_row = selection.end.row;
8470
8471 row_delta =
8472 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8473 }
8474
8475 self.transact(window, cx, |this, window, cx| {
8476 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8477 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8478 s.select(selections)
8479 });
8480 });
8481 }
8482
8483 fn indent_selection(
8484 buffer: &MultiBuffer,
8485 snapshot: &MultiBufferSnapshot,
8486 selection: &mut Selection<Point>,
8487 edits: &mut Vec<(Range<Point>, String)>,
8488 delta_for_start_row: u32,
8489 cx: &App,
8490 ) -> u32 {
8491 let settings = buffer.language_settings_at(selection.start, cx);
8492 let tab_size = settings.tab_size.get();
8493 let indent_kind = if settings.hard_tabs {
8494 IndentKind::Tab
8495 } else {
8496 IndentKind::Space
8497 };
8498 let mut start_row = selection.start.row;
8499 let mut end_row = selection.end.row + 1;
8500
8501 // If a selection ends at the beginning of a line, don't indent
8502 // that last line.
8503 if selection.end.column == 0 && selection.end.row > selection.start.row {
8504 end_row -= 1;
8505 }
8506
8507 // Avoid re-indenting a row that has already been indented by a
8508 // previous selection, but still update this selection's column
8509 // to reflect that indentation.
8510 if delta_for_start_row > 0 {
8511 start_row += 1;
8512 selection.start.column += delta_for_start_row;
8513 if selection.end.row == selection.start.row {
8514 selection.end.column += delta_for_start_row;
8515 }
8516 }
8517
8518 let mut delta_for_end_row = 0;
8519 let has_multiple_rows = start_row + 1 != end_row;
8520 for row in start_row..end_row {
8521 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
8522 let indent_delta = match (current_indent.kind, indent_kind) {
8523 (IndentKind::Space, IndentKind::Space) => {
8524 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
8525 IndentSize::spaces(columns_to_next_tab_stop)
8526 }
8527 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
8528 (_, IndentKind::Tab) => IndentSize::tab(),
8529 };
8530
8531 let start = if has_multiple_rows || current_indent.len < selection.start.column {
8532 0
8533 } else {
8534 selection.start.column
8535 };
8536 let row_start = Point::new(row, start);
8537 edits.push((
8538 row_start..row_start,
8539 indent_delta.chars().collect::<String>(),
8540 ));
8541
8542 // Update this selection's endpoints to reflect the indentation.
8543 if row == selection.start.row {
8544 selection.start.column += indent_delta.len;
8545 }
8546 if row == selection.end.row {
8547 selection.end.column += indent_delta.len;
8548 delta_for_end_row = indent_delta.len;
8549 }
8550 }
8551
8552 if selection.start.row == selection.end.row {
8553 delta_for_start_row + delta_for_end_row
8554 } else {
8555 delta_for_end_row
8556 }
8557 }
8558
8559 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
8560 if self.read_only(cx) {
8561 return;
8562 }
8563 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8564 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8565 let selections = self.selections.all::<Point>(cx);
8566 let mut deletion_ranges = Vec::new();
8567 let mut last_outdent = None;
8568 {
8569 let buffer = self.buffer.read(cx);
8570 let snapshot = buffer.snapshot(cx);
8571 for selection in &selections {
8572 let settings = buffer.language_settings_at(selection.start, cx);
8573 let tab_size = settings.tab_size.get();
8574 let mut rows = selection.spanned_rows(false, &display_map);
8575
8576 // Avoid re-outdenting a row that has already been outdented by a
8577 // previous selection.
8578 if let Some(last_row) = last_outdent {
8579 if last_row == rows.start {
8580 rows.start = rows.start.next_row();
8581 }
8582 }
8583 let has_multiple_rows = rows.len() > 1;
8584 for row in rows.iter_rows() {
8585 let indent_size = snapshot.indent_size_for_line(row);
8586 if indent_size.len > 0 {
8587 let deletion_len = match indent_size.kind {
8588 IndentKind::Space => {
8589 let columns_to_prev_tab_stop = indent_size.len % tab_size;
8590 if columns_to_prev_tab_stop == 0 {
8591 tab_size
8592 } else {
8593 columns_to_prev_tab_stop
8594 }
8595 }
8596 IndentKind::Tab => 1,
8597 };
8598 let start = if has_multiple_rows
8599 || deletion_len > selection.start.column
8600 || indent_size.len < selection.start.column
8601 {
8602 0
8603 } else {
8604 selection.start.column - deletion_len
8605 };
8606 deletion_ranges.push(
8607 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
8608 );
8609 last_outdent = Some(row);
8610 }
8611 }
8612 }
8613 }
8614
8615 self.transact(window, cx, |this, window, cx| {
8616 this.buffer.update(cx, |buffer, cx| {
8617 let empty_str: Arc<str> = Arc::default();
8618 buffer.edit(
8619 deletion_ranges
8620 .into_iter()
8621 .map(|range| (range, empty_str.clone())),
8622 None,
8623 cx,
8624 );
8625 });
8626 let selections = this.selections.all::<usize>(cx);
8627 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8628 s.select(selections)
8629 });
8630 });
8631 }
8632
8633 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
8634 if self.read_only(cx) {
8635 return;
8636 }
8637 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8638 let selections = self
8639 .selections
8640 .all::<usize>(cx)
8641 .into_iter()
8642 .map(|s| s.range());
8643
8644 self.transact(window, cx, |this, window, cx| {
8645 this.buffer.update(cx, |buffer, cx| {
8646 buffer.autoindent_ranges(selections, cx);
8647 });
8648 let selections = this.selections.all::<usize>(cx);
8649 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8650 s.select(selections)
8651 });
8652 });
8653 }
8654
8655 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
8656 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8657 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8658 let selections = self.selections.all::<Point>(cx);
8659
8660 let mut new_cursors = Vec::new();
8661 let mut edit_ranges = Vec::new();
8662 let mut selections = selections.iter().peekable();
8663 while let Some(selection) = selections.next() {
8664 let mut rows = selection.spanned_rows(false, &display_map);
8665 let goal_display_column = selection.head().to_display_point(&display_map).column();
8666
8667 // Accumulate contiguous regions of rows that we want to delete.
8668 while let Some(next_selection) = selections.peek() {
8669 let next_rows = next_selection.spanned_rows(false, &display_map);
8670 if next_rows.start <= rows.end {
8671 rows.end = next_rows.end;
8672 selections.next().unwrap();
8673 } else {
8674 break;
8675 }
8676 }
8677
8678 let buffer = &display_map.buffer_snapshot;
8679 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
8680 let edit_end;
8681 let cursor_buffer_row;
8682 if buffer.max_point().row >= rows.end.0 {
8683 // If there's a line after the range, delete the \n from the end of the row range
8684 // and position the cursor on the next line.
8685 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
8686 cursor_buffer_row = rows.end;
8687 } else {
8688 // If there isn't a line after the range, delete the \n from the line before the
8689 // start of the row range and position the cursor there.
8690 edit_start = edit_start.saturating_sub(1);
8691 edit_end = buffer.len();
8692 cursor_buffer_row = rows.start.previous_row();
8693 }
8694
8695 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
8696 *cursor.column_mut() =
8697 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
8698
8699 new_cursors.push((
8700 selection.id,
8701 buffer.anchor_after(cursor.to_point(&display_map)),
8702 ));
8703 edit_ranges.push(edit_start..edit_end);
8704 }
8705
8706 self.transact(window, cx, |this, window, cx| {
8707 let buffer = this.buffer.update(cx, |buffer, cx| {
8708 let empty_str: Arc<str> = Arc::default();
8709 buffer.edit(
8710 edit_ranges
8711 .into_iter()
8712 .map(|range| (range, empty_str.clone())),
8713 None,
8714 cx,
8715 );
8716 buffer.snapshot(cx)
8717 });
8718 let new_selections = new_cursors
8719 .into_iter()
8720 .map(|(id, cursor)| {
8721 let cursor = cursor.to_point(&buffer);
8722 Selection {
8723 id,
8724 start: cursor,
8725 end: cursor,
8726 reversed: false,
8727 goal: SelectionGoal::None,
8728 }
8729 })
8730 .collect();
8731
8732 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8733 s.select(new_selections);
8734 });
8735 });
8736 }
8737
8738 pub fn join_lines_impl(
8739 &mut self,
8740 insert_whitespace: bool,
8741 window: &mut Window,
8742 cx: &mut Context<Self>,
8743 ) {
8744 if self.read_only(cx) {
8745 return;
8746 }
8747 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
8748 for selection in self.selections.all::<Point>(cx) {
8749 let start = MultiBufferRow(selection.start.row);
8750 // Treat single line selections as if they include the next line. Otherwise this action
8751 // would do nothing for single line selections individual cursors.
8752 let end = if selection.start.row == selection.end.row {
8753 MultiBufferRow(selection.start.row + 1)
8754 } else {
8755 MultiBufferRow(selection.end.row)
8756 };
8757
8758 if let Some(last_row_range) = row_ranges.last_mut() {
8759 if start <= last_row_range.end {
8760 last_row_range.end = end;
8761 continue;
8762 }
8763 }
8764 row_ranges.push(start..end);
8765 }
8766
8767 let snapshot = self.buffer.read(cx).snapshot(cx);
8768 let mut cursor_positions = Vec::new();
8769 for row_range in &row_ranges {
8770 let anchor = snapshot.anchor_before(Point::new(
8771 row_range.end.previous_row().0,
8772 snapshot.line_len(row_range.end.previous_row()),
8773 ));
8774 cursor_positions.push(anchor..anchor);
8775 }
8776
8777 self.transact(window, cx, |this, window, cx| {
8778 for row_range in row_ranges.into_iter().rev() {
8779 for row in row_range.iter_rows().rev() {
8780 let end_of_line = Point::new(row.0, snapshot.line_len(row));
8781 let next_line_row = row.next_row();
8782 let indent = snapshot.indent_size_for_line(next_line_row);
8783 let start_of_next_line = Point::new(next_line_row.0, indent.len);
8784
8785 let replace =
8786 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
8787 " "
8788 } else {
8789 ""
8790 };
8791
8792 this.buffer.update(cx, |buffer, cx| {
8793 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
8794 });
8795 }
8796 }
8797
8798 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8799 s.select_anchor_ranges(cursor_positions)
8800 });
8801 });
8802 }
8803
8804 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
8805 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8806 self.join_lines_impl(true, window, cx);
8807 }
8808
8809 pub fn sort_lines_case_sensitive(
8810 &mut self,
8811 _: &SortLinesCaseSensitive,
8812 window: &mut Window,
8813 cx: &mut Context<Self>,
8814 ) {
8815 self.manipulate_lines(window, cx, |lines| lines.sort())
8816 }
8817
8818 pub fn sort_lines_case_insensitive(
8819 &mut self,
8820 _: &SortLinesCaseInsensitive,
8821 window: &mut Window,
8822 cx: &mut Context<Self>,
8823 ) {
8824 self.manipulate_lines(window, cx, |lines| {
8825 lines.sort_by_key(|line| line.to_lowercase())
8826 })
8827 }
8828
8829 pub fn unique_lines_case_insensitive(
8830 &mut self,
8831 _: &UniqueLinesCaseInsensitive,
8832 window: &mut Window,
8833 cx: &mut Context<Self>,
8834 ) {
8835 self.manipulate_lines(window, cx, |lines| {
8836 let mut seen = HashSet::default();
8837 lines.retain(|line| seen.insert(line.to_lowercase()));
8838 })
8839 }
8840
8841 pub fn unique_lines_case_sensitive(
8842 &mut self,
8843 _: &UniqueLinesCaseSensitive,
8844 window: &mut Window,
8845 cx: &mut Context<Self>,
8846 ) {
8847 self.manipulate_lines(window, cx, |lines| {
8848 let mut seen = HashSet::default();
8849 lines.retain(|line| seen.insert(*line));
8850 })
8851 }
8852
8853 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
8854 let Some(project) = self.project.clone() else {
8855 return;
8856 };
8857 self.reload(project, window, cx)
8858 .detach_and_notify_err(window, cx);
8859 }
8860
8861 pub fn restore_file(
8862 &mut self,
8863 _: &::git::RestoreFile,
8864 window: &mut Window,
8865 cx: &mut Context<Self>,
8866 ) {
8867 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8868 let mut buffer_ids = HashSet::default();
8869 let snapshot = self.buffer().read(cx).snapshot(cx);
8870 for selection in self.selections.all::<usize>(cx) {
8871 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
8872 }
8873
8874 let buffer = self.buffer().read(cx);
8875 let ranges = buffer_ids
8876 .into_iter()
8877 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
8878 .collect::<Vec<_>>();
8879
8880 self.restore_hunks_in_ranges(ranges, window, cx);
8881 }
8882
8883 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
8884 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8885 let selections = self
8886 .selections
8887 .all(cx)
8888 .into_iter()
8889 .map(|s| s.range())
8890 .collect();
8891 self.restore_hunks_in_ranges(selections, window, cx);
8892 }
8893
8894 pub fn restore_hunks_in_ranges(
8895 &mut self,
8896 ranges: Vec<Range<Point>>,
8897 window: &mut Window,
8898 cx: &mut Context<Editor>,
8899 ) {
8900 let mut revert_changes = HashMap::default();
8901 let chunk_by = self
8902 .snapshot(window, cx)
8903 .hunks_for_ranges(ranges)
8904 .into_iter()
8905 .chunk_by(|hunk| hunk.buffer_id);
8906 for (buffer_id, hunks) in &chunk_by {
8907 let hunks = hunks.collect::<Vec<_>>();
8908 for hunk in &hunks {
8909 self.prepare_restore_change(&mut revert_changes, hunk, cx);
8910 }
8911 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
8912 }
8913 drop(chunk_by);
8914 if !revert_changes.is_empty() {
8915 self.transact(window, cx, |editor, window, cx| {
8916 editor.restore(revert_changes, window, cx);
8917 });
8918 }
8919 }
8920
8921 pub fn open_active_item_in_terminal(
8922 &mut self,
8923 _: &OpenInTerminal,
8924 window: &mut Window,
8925 cx: &mut Context<Self>,
8926 ) {
8927 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
8928 let project_path = buffer.read(cx).project_path(cx)?;
8929 let project = self.project.as_ref()?.read(cx);
8930 let entry = project.entry_for_path(&project_path, cx)?;
8931 let parent = match &entry.canonical_path {
8932 Some(canonical_path) => canonical_path.to_path_buf(),
8933 None => project.absolute_path(&project_path, cx)?,
8934 }
8935 .parent()?
8936 .to_path_buf();
8937 Some(parent)
8938 }) {
8939 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
8940 }
8941 }
8942
8943 fn set_breakpoint_context_menu(
8944 &mut self,
8945 display_row: DisplayRow,
8946 position: Option<Anchor>,
8947 clicked_point: gpui::Point<Pixels>,
8948 window: &mut Window,
8949 cx: &mut Context<Self>,
8950 ) {
8951 if !cx.has_flag::<DebuggerFeatureFlag>() {
8952 return;
8953 }
8954 let source = self
8955 .buffer
8956 .read(cx)
8957 .snapshot(cx)
8958 .anchor_before(Point::new(display_row.0, 0u32));
8959
8960 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
8961
8962 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
8963 self,
8964 source,
8965 clicked_point,
8966 context_menu,
8967 window,
8968 cx,
8969 );
8970 }
8971
8972 fn add_edit_breakpoint_block(
8973 &mut self,
8974 anchor: Anchor,
8975 breakpoint: &Breakpoint,
8976 edit_action: BreakpointPromptEditAction,
8977 window: &mut Window,
8978 cx: &mut Context<Self>,
8979 ) {
8980 let weak_editor = cx.weak_entity();
8981 let bp_prompt = cx.new(|cx| {
8982 BreakpointPromptEditor::new(
8983 weak_editor,
8984 anchor,
8985 breakpoint.clone(),
8986 edit_action,
8987 window,
8988 cx,
8989 )
8990 });
8991
8992 let height = bp_prompt.update(cx, |this, cx| {
8993 this.prompt
8994 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
8995 });
8996 let cloned_prompt = bp_prompt.clone();
8997 let blocks = vec![BlockProperties {
8998 style: BlockStyle::Sticky,
8999 placement: BlockPlacement::Above(anchor),
9000 height: Some(height),
9001 render: Arc::new(move |cx| {
9002 *cloned_prompt.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
9003 cloned_prompt.clone().into_any_element()
9004 }),
9005 priority: 0,
9006 }];
9007
9008 let focus_handle = bp_prompt.focus_handle(cx);
9009 window.focus(&focus_handle);
9010
9011 let block_ids = self.insert_blocks(blocks, None, cx);
9012 bp_prompt.update(cx, |prompt, _| {
9013 prompt.add_block_ids(block_ids);
9014 });
9015 }
9016
9017 pub(crate) fn breakpoint_at_row(
9018 &self,
9019 row: u32,
9020 window: &mut Window,
9021 cx: &mut Context<Self>,
9022 ) -> Option<(Anchor, Breakpoint)> {
9023 let snapshot = self.snapshot(window, cx);
9024 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9025
9026 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9027 }
9028
9029 pub(crate) fn breakpoint_at_anchor(
9030 &self,
9031 breakpoint_position: Anchor,
9032 snapshot: &EditorSnapshot,
9033 cx: &mut Context<Self>,
9034 ) -> Option<(Anchor, Breakpoint)> {
9035 let project = self.project.clone()?;
9036
9037 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9038 snapshot
9039 .buffer_snapshot
9040 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9041 })?;
9042
9043 let enclosing_excerpt = breakpoint_position.excerpt_id;
9044 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
9045 let buffer_snapshot = buffer.read(cx).snapshot();
9046
9047 let row = buffer_snapshot
9048 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9049 .row;
9050
9051 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9052 let anchor_end = snapshot
9053 .buffer_snapshot
9054 .anchor_after(Point::new(row, line_len));
9055
9056 let bp = self
9057 .breakpoint_store
9058 .as_ref()?
9059 .read_with(cx, |breakpoint_store, cx| {
9060 breakpoint_store
9061 .breakpoints(
9062 &buffer,
9063 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9064 &buffer_snapshot,
9065 cx,
9066 )
9067 .next()
9068 .and_then(|(anchor, bp)| {
9069 let breakpoint_row = buffer_snapshot
9070 .summary_for_anchor::<text::PointUtf16>(anchor)
9071 .row;
9072
9073 if breakpoint_row == row {
9074 snapshot
9075 .buffer_snapshot
9076 .anchor_in_excerpt(enclosing_excerpt, *anchor)
9077 .map(|anchor| (anchor, bp.clone()))
9078 } else {
9079 None
9080 }
9081 })
9082 });
9083 bp
9084 }
9085
9086 pub fn edit_log_breakpoint(
9087 &mut self,
9088 _: &EditLogBreakpoint,
9089 window: &mut Window,
9090 cx: &mut Context<Self>,
9091 ) {
9092 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9093 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9094 message: None,
9095 state: BreakpointState::Enabled,
9096 condition: None,
9097 hit_condition: None,
9098 });
9099
9100 self.add_edit_breakpoint_block(
9101 anchor,
9102 &breakpoint,
9103 BreakpointPromptEditAction::Log,
9104 window,
9105 cx,
9106 );
9107 }
9108 }
9109
9110 fn breakpoints_at_cursors(
9111 &self,
9112 window: &mut Window,
9113 cx: &mut Context<Self>,
9114 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9115 let snapshot = self.snapshot(window, cx);
9116 let cursors = self
9117 .selections
9118 .disjoint_anchors()
9119 .into_iter()
9120 .map(|selection| {
9121 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9122
9123 let breakpoint_position = self
9124 .breakpoint_at_row(cursor_position.row, window, cx)
9125 .map(|bp| bp.0)
9126 .unwrap_or_else(|| {
9127 snapshot
9128 .display_snapshot
9129 .buffer_snapshot
9130 .anchor_after(Point::new(cursor_position.row, 0))
9131 });
9132
9133 let breakpoint = self
9134 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9135 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9136
9137 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9138 })
9139 // 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.
9140 .collect::<HashMap<Anchor, _>>();
9141
9142 cursors.into_iter().collect()
9143 }
9144
9145 pub fn enable_breakpoint(
9146 &mut self,
9147 _: &crate::actions::EnableBreakpoint,
9148 window: &mut Window,
9149 cx: &mut Context<Self>,
9150 ) {
9151 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9152 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9153 continue;
9154 };
9155 self.edit_breakpoint_at_anchor(
9156 anchor,
9157 breakpoint,
9158 BreakpointEditAction::InvertState,
9159 cx,
9160 );
9161 }
9162 }
9163
9164 pub fn disable_breakpoint(
9165 &mut self,
9166 _: &crate::actions::DisableBreakpoint,
9167 window: &mut Window,
9168 cx: &mut Context<Self>,
9169 ) {
9170 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9171 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9172 continue;
9173 };
9174 self.edit_breakpoint_at_anchor(
9175 anchor,
9176 breakpoint,
9177 BreakpointEditAction::InvertState,
9178 cx,
9179 );
9180 }
9181 }
9182
9183 pub fn toggle_breakpoint(
9184 &mut self,
9185 _: &crate::actions::ToggleBreakpoint,
9186 window: &mut Window,
9187 cx: &mut Context<Self>,
9188 ) {
9189 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9190 if let Some(breakpoint) = breakpoint {
9191 self.edit_breakpoint_at_anchor(
9192 anchor,
9193 breakpoint,
9194 BreakpointEditAction::Toggle,
9195 cx,
9196 );
9197 } else {
9198 self.edit_breakpoint_at_anchor(
9199 anchor,
9200 Breakpoint::new_standard(),
9201 BreakpointEditAction::Toggle,
9202 cx,
9203 );
9204 }
9205 }
9206 }
9207
9208 pub fn edit_breakpoint_at_anchor(
9209 &mut self,
9210 breakpoint_position: Anchor,
9211 breakpoint: Breakpoint,
9212 edit_action: BreakpointEditAction,
9213 cx: &mut Context<Self>,
9214 ) {
9215 let Some(breakpoint_store) = &self.breakpoint_store else {
9216 return;
9217 };
9218
9219 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9220 if breakpoint_position == Anchor::min() {
9221 self.buffer()
9222 .read(cx)
9223 .excerpt_buffer_ids()
9224 .into_iter()
9225 .next()
9226 } else {
9227 None
9228 }
9229 }) else {
9230 return;
9231 };
9232
9233 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9234 return;
9235 };
9236
9237 breakpoint_store.update(cx, |breakpoint_store, cx| {
9238 breakpoint_store.toggle_breakpoint(
9239 buffer,
9240 (breakpoint_position.text_anchor, breakpoint),
9241 edit_action,
9242 cx,
9243 );
9244 });
9245
9246 cx.notify();
9247 }
9248
9249 #[cfg(any(test, feature = "test-support"))]
9250 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9251 self.breakpoint_store.clone()
9252 }
9253
9254 pub fn prepare_restore_change(
9255 &self,
9256 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9257 hunk: &MultiBufferDiffHunk,
9258 cx: &mut App,
9259 ) -> Option<()> {
9260 if hunk.is_created_file() {
9261 return None;
9262 }
9263 let buffer = self.buffer.read(cx);
9264 let diff = buffer.diff_for(hunk.buffer_id)?;
9265 let buffer = buffer.buffer(hunk.buffer_id)?;
9266 let buffer = buffer.read(cx);
9267 let original_text = diff
9268 .read(cx)
9269 .base_text()
9270 .as_rope()
9271 .slice(hunk.diff_base_byte_range.clone());
9272 let buffer_snapshot = buffer.snapshot();
9273 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9274 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9275 probe
9276 .0
9277 .start
9278 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9279 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9280 }) {
9281 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9282 Some(())
9283 } else {
9284 None
9285 }
9286 }
9287
9288 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9289 self.manipulate_lines(window, cx, |lines| lines.reverse())
9290 }
9291
9292 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9293 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9294 }
9295
9296 fn manipulate_lines<Fn>(
9297 &mut self,
9298 window: &mut Window,
9299 cx: &mut Context<Self>,
9300 mut callback: Fn,
9301 ) where
9302 Fn: FnMut(&mut Vec<&str>),
9303 {
9304 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9305
9306 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9307 let buffer = self.buffer.read(cx).snapshot(cx);
9308
9309 let mut edits = Vec::new();
9310
9311 let selections = self.selections.all::<Point>(cx);
9312 let mut selections = selections.iter().peekable();
9313 let mut contiguous_row_selections = Vec::new();
9314 let mut new_selections = Vec::new();
9315 let mut added_lines = 0;
9316 let mut removed_lines = 0;
9317
9318 while let Some(selection) = selections.next() {
9319 let (start_row, end_row) = consume_contiguous_rows(
9320 &mut contiguous_row_selections,
9321 selection,
9322 &display_map,
9323 &mut selections,
9324 );
9325
9326 let start_point = Point::new(start_row.0, 0);
9327 let end_point = Point::new(
9328 end_row.previous_row().0,
9329 buffer.line_len(end_row.previous_row()),
9330 );
9331 let text = buffer
9332 .text_for_range(start_point..end_point)
9333 .collect::<String>();
9334
9335 let mut lines = text.split('\n').collect_vec();
9336
9337 let lines_before = lines.len();
9338 callback(&mut lines);
9339 let lines_after = lines.len();
9340
9341 edits.push((start_point..end_point, lines.join("\n")));
9342
9343 // Selections must change based on added and removed line count
9344 let start_row =
9345 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9346 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9347 new_selections.push(Selection {
9348 id: selection.id,
9349 start: start_row,
9350 end: end_row,
9351 goal: SelectionGoal::None,
9352 reversed: selection.reversed,
9353 });
9354
9355 if lines_after > lines_before {
9356 added_lines += lines_after - lines_before;
9357 } else if lines_before > lines_after {
9358 removed_lines += lines_before - lines_after;
9359 }
9360 }
9361
9362 self.transact(window, cx, |this, window, cx| {
9363 let buffer = this.buffer.update(cx, |buffer, cx| {
9364 buffer.edit(edits, None, cx);
9365 buffer.snapshot(cx)
9366 });
9367
9368 // Recalculate offsets on newly edited buffer
9369 let new_selections = new_selections
9370 .iter()
9371 .map(|s| {
9372 let start_point = Point::new(s.start.0, 0);
9373 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9374 Selection {
9375 id: s.id,
9376 start: buffer.point_to_offset(start_point),
9377 end: buffer.point_to_offset(end_point),
9378 goal: s.goal,
9379 reversed: s.reversed,
9380 }
9381 })
9382 .collect();
9383
9384 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9385 s.select(new_selections);
9386 });
9387
9388 this.request_autoscroll(Autoscroll::fit(), cx);
9389 });
9390 }
9391
9392 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
9393 self.manipulate_text(window, cx, |text| {
9394 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
9395 if has_upper_case_characters {
9396 text.to_lowercase()
9397 } else {
9398 text.to_uppercase()
9399 }
9400 })
9401 }
9402
9403 pub fn convert_to_upper_case(
9404 &mut self,
9405 _: &ConvertToUpperCase,
9406 window: &mut Window,
9407 cx: &mut Context<Self>,
9408 ) {
9409 self.manipulate_text(window, cx, |text| text.to_uppercase())
9410 }
9411
9412 pub fn convert_to_lower_case(
9413 &mut self,
9414 _: &ConvertToLowerCase,
9415 window: &mut Window,
9416 cx: &mut Context<Self>,
9417 ) {
9418 self.manipulate_text(window, cx, |text| text.to_lowercase())
9419 }
9420
9421 pub fn convert_to_title_case(
9422 &mut self,
9423 _: &ConvertToTitleCase,
9424 window: &mut Window,
9425 cx: &mut Context<Self>,
9426 ) {
9427 self.manipulate_text(window, cx, |text| {
9428 text.split('\n')
9429 .map(|line| line.to_case(Case::Title))
9430 .join("\n")
9431 })
9432 }
9433
9434 pub fn convert_to_snake_case(
9435 &mut self,
9436 _: &ConvertToSnakeCase,
9437 window: &mut Window,
9438 cx: &mut Context<Self>,
9439 ) {
9440 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
9441 }
9442
9443 pub fn convert_to_kebab_case(
9444 &mut self,
9445 _: &ConvertToKebabCase,
9446 window: &mut Window,
9447 cx: &mut Context<Self>,
9448 ) {
9449 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
9450 }
9451
9452 pub fn convert_to_upper_camel_case(
9453 &mut self,
9454 _: &ConvertToUpperCamelCase,
9455 window: &mut Window,
9456 cx: &mut Context<Self>,
9457 ) {
9458 self.manipulate_text(window, cx, |text| {
9459 text.split('\n')
9460 .map(|line| line.to_case(Case::UpperCamel))
9461 .join("\n")
9462 })
9463 }
9464
9465 pub fn convert_to_lower_camel_case(
9466 &mut self,
9467 _: &ConvertToLowerCamelCase,
9468 window: &mut Window,
9469 cx: &mut Context<Self>,
9470 ) {
9471 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
9472 }
9473
9474 pub fn convert_to_opposite_case(
9475 &mut self,
9476 _: &ConvertToOppositeCase,
9477 window: &mut Window,
9478 cx: &mut Context<Self>,
9479 ) {
9480 self.manipulate_text(window, cx, |text| {
9481 text.chars()
9482 .fold(String::with_capacity(text.len()), |mut t, c| {
9483 if c.is_uppercase() {
9484 t.extend(c.to_lowercase());
9485 } else {
9486 t.extend(c.to_uppercase());
9487 }
9488 t
9489 })
9490 })
9491 }
9492
9493 pub fn convert_to_rot13(
9494 &mut self,
9495 _: &ConvertToRot13,
9496 window: &mut Window,
9497 cx: &mut Context<Self>,
9498 ) {
9499 self.manipulate_text(window, cx, |text| {
9500 text.chars()
9501 .map(|c| match c {
9502 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
9503 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
9504 _ => c,
9505 })
9506 .collect()
9507 })
9508 }
9509
9510 pub fn convert_to_rot47(
9511 &mut self,
9512 _: &ConvertToRot47,
9513 window: &mut Window,
9514 cx: &mut Context<Self>,
9515 ) {
9516 self.manipulate_text(window, cx, |text| {
9517 text.chars()
9518 .map(|c| {
9519 let code_point = c as u32;
9520 if code_point >= 33 && code_point <= 126 {
9521 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
9522 }
9523 c
9524 })
9525 .collect()
9526 })
9527 }
9528
9529 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
9530 where
9531 Fn: FnMut(&str) -> String,
9532 {
9533 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9534 let buffer = self.buffer.read(cx).snapshot(cx);
9535
9536 let mut new_selections = Vec::new();
9537 let mut edits = Vec::new();
9538 let mut selection_adjustment = 0i32;
9539
9540 for selection in self.selections.all::<usize>(cx) {
9541 let selection_is_empty = selection.is_empty();
9542
9543 let (start, end) = if selection_is_empty {
9544 let word_range = movement::surrounding_word(
9545 &display_map,
9546 selection.start.to_display_point(&display_map),
9547 );
9548 let start = word_range.start.to_offset(&display_map, Bias::Left);
9549 let end = word_range.end.to_offset(&display_map, Bias::Left);
9550 (start, end)
9551 } else {
9552 (selection.start, selection.end)
9553 };
9554
9555 let text = buffer.text_for_range(start..end).collect::<String>();
9556 let old_length = text.len() as i32;
9557 let text = callback(&text);
9558
9559 new_selections.push(Selection {
9560 start: (start as i32 - selection_adjustment) as usize,
9561 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
9562 goal: SelectionGoal::None,
9563 ..selection
9564 });
9565
9566 selection_adjustment += old_length - text.len() as i32;
9567
9568 edits.push((start..end, text));
9569 }
9570
9571 self.transact(window, cx, |this, window, cx| {
9572 this.buffer.update(cx, |buffer, cx| {
9573 buffer.edit(edits, None, cx);
9574 });
9575
9576 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9577 s.select(new_selections);
9578 });
9579
9580 this.request_autoscroll(Autoscroll::fit(), cx);
9581 });
9582 }
9583
9584 pub fn duplicate(
9585 &mut self,
9586 upwards: bool,
9587 whole_lines: bool,
9588 window: &mut Window,
9589 cx: &mut Context<Self>,
9590 ) {
9591 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9592
9593 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9594 let buffer = &display_map.buffer_snapshot;
9595 let selections = self.selections.all::<Point>(cx);
9596
9597 let mut edits = Vec::new();
9598 let mut selections_iter = selections.iter().peekable();
9599 while let Some(selection) = selections_iter.next() {
9600 let mut rows = selection.spanned_rows(false, &display_map);
9601 // duplicate line-wise
9602 if whole_lines || selection.start == selection.end {
9603 // Avoid duplicating the same lines twice.
9604 while let Some(next_selection) = selections_iter.peek() {
9605 let next_rows = next_selection.spanned_rows(false, &display_map);
9606 if next_rows.start < rows.end {
9607 rows.end = next_rows.end;
9608 selections_iter.next().unwrap();
9609 } else {
9610 break;
9611 }
9612 }
9613
9614 // Copy the text from the selected row region and splice it either at the start
9615 // or end of the region.
9616 let start = Point::new(rows.start.0, 0);
9617 let end = Point::new(
9618 rows.end.previous_row().0,
9619 buffer.line_len(rows.end.previous_row()),
9620 );
9621 let text = buffer
9622 .text_for_range(start..end)
9623 .chain(Some("\n"))
9624 .collect::<String>();
9625 let insert_location = if upwards {
9626 Point::new(rows.end.0, 0)
9627 } else {
9628 start
9629 };
9630 edits.push((insert_location..insert_location, text));
9631 } else {
9632 // duplicate character-wise
9633 let start = selection.start;
9634 let end = selection.end;
9635 let text = buffer.text_for_range(start..end).collect::<String>();
9636 edits.push((selection.end..selection.end, text));
9637 }
9638 }
9639
9640 self.transact(window, cx, |this, _, cx| {
9641 this.buffer.update(cx, |buffer, cx| {
9642 buffer.edit(edits, None, cx);
9643 });
9644
9645 this.request_autoscroll(Autoscroll::fit(), cx);
9646 });
9647 }
9648
9649 pub fn duplicate_line_up(
9650 &mut self,
9651 _: &DuplicateLineUp,
9652 window: &mut Window,
9653 cx: &mut Context<Self>,
9654 ) {
9655 self.duplicate(true, true, window, cx);
9656 }
9657
9658 pub fn duplicate_line_down(
9659 &mut self,
9660 _: &DuplicateLineDown,
9661 window: &mut Window,
9662 cx: &mut Context<Self>,
9663 ) {
9664 self.duplicate(false, true, window, cx);
9665 }
9666
9667 pub fn duplicate_selection(
9668 &mut self,
9669 _: &DuplicateSelection,
9670 window: &mut Window,
9671 cx: &mut Context<Self>,
9672 ) {
9673 self.duplicate(false, false, window, cx);
9674 }
9675
9676 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
9677 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9678
9679 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9680 let buffer = self.buffer.read(cx).snapshot(cx);
9681
9682 let mut edits = Vec::new();
9683 let mut unfold_ranges = Vec::new();
9684 let mut refold_creases = Vec::new();
9685
9686 let selections = self.selections.all::<Point>(cx);
9687 let mut selections = selections.iter().peekable();
9688 let mut contiguous_row_selections = Vec::new();
9689 let mut new_selections = Vec::new();
9690
9691 while let Some(selection) = selections.next() {
9692 // Find all the selections that span a contiguous row range
9693 let (start_row, end_row) = consume_contiguous_rows(
9694 &mut contiguous_row_selections,
9695 selection,
9696 &display_map,
9697 &mut selections,
9698 );
9699
9700 // Move the text spanned by the row range to be before the line preceding the row range
9701 if start_row.0 > 0 {
9702 let range_to_move = Point::new(
9703 start_row.previous_row().0,
9704 buffer.line_len(start_row.previous_row()),
9705 )
9706 ..Point::new(
9707 end_row.previous_row().0,
9708 buffer.line_len(end_row.previous_row()),
9709 );
9710 let insertion_point = display_map
9711 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
9712 .0;
9713
9714 // Don't move lines across excerpts
9715 if buffer
9716 .excerpt_containing(insertion_point..range_to_move.end)
9717 .is_some()
9718 {
9719 let text = buffer
9720 .text_for_range(range_to_move.clone())
9721 .flat_map(|s| s.chars())
9722 .skip(1)
9723 .chain(['\n'])
9724 .collect::<String>();
9725
9726 edits.push((
9727 buffer.anchor_after(range_to_move.start)
9728 ..buffer.anchor_before(range_to_move.end),
9729 String::new(),
9730 ));
9731 let insertion_anchor = buffer.anchor_after(insertion_point);
9732 edits.push((insertion_anchor..insertion_anchor, text));
9733
9734 let row_delta = range_to_move.start.row - insertion_point.row + 1;
9735
9736 // Move selections up
9737 new_selections.extend(contiguous_row_selections.drain(..).map(
9738 |mut selection| {
9739 selection.start.row -= row_delta;
9740 selection.end.row -= row_delta;
9741 selection
9742 },
9743 ));
9744
9745 // Move folds up
9746 unfold_ranges.push(range_to_move.clone());
9747 for fold in display_map.folds_in_range(
9748 buffer.anchor_before(range_to_move.start)
9749 ..buffer.anchor_after(range_to_move.end),
9750 ) {
9751 let mut start = fold.range.start.to_point(&buffer);
9752 let mut end = fold.range.end.to_point(&buffer);
9753 start.row -= row_delta;
9754 end.row -= row_delta;
9755 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9756 }
9757 }
9758 }
9759
9760 // If we didn't move line(s), preserve the existing selections
9761 new_selections.append(&mut contiguous_row_selections);
9762 }
9763
9764 self.transact(window, cx, |this, window, cx| {
9765 this.unfold_ranges(&unfold_ranges, true, true, cx);
9766 this.buffer.update(cx, |buffer, cx| {
9767 for (range, text) in edits {
9768 buffer.edit([(range, text)], None, cx);
9769 }
9770 });
9771 this.fold_creases(refold_creases, true, window, cx);
9772 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9773 s.select(new_selections);
9774 })
9775 });
9776 }
9777
9778 pub fn move_line_down(
9779 &mut self,
9780 _: &MoveLineDown,
9781 window: &mut Window,
9782 cx: &mut Context<Self>,
9783 ) {
9784 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9785
9786 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9787 let buffer = self.buffer.read(cx).snapshot(cx);
9788
9789 let mut edits = Vec::new();
9790 let mut unfold_ranges = Vec::new();
9791 let mut refold_creases = Vec::new();
9792
9793 let selections = self.selections.all::<Point>(cx);
9794 let mut selections = selections.iter().peekable();
9795 let mut contiguous_row_selections = Vec::new();
9796 let mut new_selections = Vec::new();
9797
9798 while let Some(selection) = selections.next() {
9799 // Find all the selections that span a contiguous row range
9800 let (start_row, end_row) = consume_contiguous_rows(
9801 &mut contiguous_row_selections,
9802 selection,
9803 &display_map,
9804 &mut selections,
9805 );
9806
9807 // Move the text spanned by the row range to be after the last line of the row range
9808 if end_row.0 <= buffer.max_point().row {
9809 let range_to_move =
9810 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
9811 let insertion_point = display_map
9812 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
9813 .0;
9814
9815 // Don't move lines across excerpt boundaries
9816 if buffer
9817 .excerpt_containing(range_to_move.start..insertion_point)
9818 .is_some()
9819 {
9820 let mut text = String::from("\n");
9821 text.extend(buffer.text_for_range(range_to_move.clone()));
9822 text.pop(); // Drop trailing newline
9823 edits.push((
9824 buffer.anchor_after(range_to_move.start)
9825 ..buffer.anchor_before(range_to_move.end),
9826 String::new(),
9827 ));
9828 let insertion_anchor = buffer.anchor_after(insertion_point);
9829 edits.push((insertion_anchor..insertion_anchor, text));
9830
9831 let row_delta = insertion_point.row - range_to_move.end.row + 1;
9832
9833 // Move selections down
9834 new_selections.extend(contiguous_row_selections.drain(..).map(
9835 |mut selection| {
9836 selection.start.row += row_delta;
9837 selection.end.row += row_delta;
9838 selection
9839 },
9840 ));
9841
9842 // Move folds down
9843 unfold_ranges.push(range_to_move.clone());
9844 for fold in display_map.folds_in_range(
9845 buffer.anchor_before(range_to_move.start)
9846 ..buffer.anchor_after(range_to_move.end),
9847 ) {
9848 let mut start = fold.range.start.to_point(&buffer);
9849 let mut end = fold.range.end.to_point(&buffer);
9850 start.row += row_delta;
9851 end.row += row_delta;
9852 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9853 }
9854 }
9855 }
9856
9857 // If we didn't move line(s), preserve the existing selections
9858 new_selections.append(&mut contiguous_row_selections);
9859 }
9860
9861 self.transact(window, cx, |this, window, cx| {
9862 this.unfold_ranges(&unfold_ranges, true, true, cx);
9863 this.buffer.update(cx, |buffer, cx| {
9864 for (range, text) in edits {
9865 buffer.edit([(range, text)], None, cx);
9866 }
9867 });
9868 this.fold_creases(refold_creases, true, window, cx);
9869 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9870 s.select(new_selections)
9871 });
9872 });
9873 }
9874
9875 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
9876 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9877 let text_layout_details = &self.text_layout_details(window);
9878 self.transact(window, cx, |this, window, cx| {
9879 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9880 let mut edits: Vec<(Range<usize>, String)> = Default::default();
9881 s.move_with(|display_map, selection| {
9882 if !selection.is_empty() {
9883 return;
9884 }
9885
9886 let mut head = selection.head();
9887 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
9888 if head.column() == display_map.line_len(head.row()) {
9889 transpose_offset = display_map
9890 .buffer_snapshot
9891 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9892 }
9893
9894 if transpose_offset == 0 {
9895 return;
9896 }
9897
9898 *head.column_mut() += 1;
9899 head = display_map.clip_point(head, Bias::Right);
9900 let goal = SelectionGoal::HorizontalPosition(
9901 display_map
9902 .x_for_display_point(head, text_layout_details)
9903 .into(),
9904 );
9905 selection.collapse_to(head, goal);
9906
9907 let transpose_start = display_map
9908 .buffer_snapshot
9909 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9910 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
9911 let transpose_end = display_map
9912 .buffer_snapshot
9913 .clip_offset(transpose_offset + 1, Bias::Right);
9914 if let Some(ch) =
9915 display_map.buffer_snapshot.chars_at(transpose_start).next()
9916 {
9917 edits.push((transpose_start..transpose_offset, String::new()));
9918 edits.push((transpose_end..transpose_end, ch.to_string()));
9919 }
9920 }
9921 });
9922 edits
9923 });
9924 this.buffer
9925 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9926 let selections = this.selections.all::<usize>(cx);
9927 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9928 s.select(selections);
9929 });
9930 });
9931 }
9932
9933 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
9934 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9935 self.rewrap_impl(RewrapOptions::default(), cx)
9936 }
9937
9938 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
9939 let buffer = self.buffer.read(cx).snapshot(cx);
9940 let selections = self.selections.all::<Point>(cx);
9941 let mut selections = selections.iter().peekable();
9942
9943 let mut edits = Vec::new();
9944 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
9945
9946 while let Some(selection) = selections.next() {
9947 let mut start_row = selection.start.row;
9948 let mut end_row = selection.end.row;
9949
9950 // Skip selections that overlap with a range that has already been rewrapped.
9951 let selection_range = start_row..end_row;
9952 if rewrapped_row_ranges
9953 .iter()
9954 .any(|range| range.overlaps(&selection_range))
9955 {
9956 continue;
9957 }
9958
9959 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
9960
9961 // Since not all lines in the selection may be at the same indent
9962 // level, choose the indent size that is the most common between all
9963 // of the lines.
9964 //
9965 // If there is a tie, we use the deepest indent.
9966 let (indent_size, indent_end) = {
9967 let mut indent_size_occurrences = HashMap::default();
9968 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
9969
9970 for row in start_row..=end_row {
9971 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
9972 rows_by_indent_size.entry(indent).or_default().push(row);
9973 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
9974 }
9975
9976 let indent_size = indent_size_occurrences
9977 .into_iter()
9978 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
9979 .map(|(indent, _)| indent)
9980 .unwrap_or_default();
9981 let row = rows_by_indent_size[&indent_size][0];
9982 let indent_end = Point::new(row, indent_size.len);
9983
9984 (indent_size, indent_end)
9985 };
9986
9987 let mut line_prefix = indent_size.chars().collect::<String>();
9988
9989 let mut inside_comment = false;
9990 if let Some(comment_prefix) =
9991 buffer
9992 .language_scope_at(selection.head())
9993 .and_then(|language| {
9994 language
9995 .line_comment_prefixes()
9996 .iter()
9997 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
9998 .cloned()
9999 })
10000 {
10001 line_prefix.push_str(&comment_prefix);
10002 inside_comment = true;
10003 }
10004
10005 let language_settings = buffer.language_settings_at(selection.head(), cx);
10006 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10007 RewrapBehavior::InComments => inside_comment,
10008 RewrapBehavior::InSelections => !selection.is_empty(),
10009 RewrapBehavior::Anywhere => true,
10010 };
10011
10012 let should_rewrap = options.override_language_settings
10013 || allow_rewrap_based_on_language
10014 || self.hard_wrap.is_some();
10015 if !should_rewrap {
10016 continue;
10017 }
10018
10019 if selection.is_empty() {
10020 'expand_upwards: while start_row > 0 {
10021 let prev_row = start_row - 1;
10022 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10023 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10024 {
10025 start_row = prev_row;
10026 } else {
10027 break 'expand_upwards;
10028 }
10029 }
10030
10031 'expand_downwards: while end_row < buffer.max_point().row {
10032 let next_row = end_row + 1;
10033 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10034 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10035 {
10036 end_row = next_row;
10037 } else {
10038 break 'expand_downwards;
10039 }
10040 }
10041 }
10042
10043 let start = Point::new(start_row, 0);
10044 let start_offset = start.to_offset(&buffer);
10045 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10046 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10047 let Some(lines_without_prefixes) = selection_text
10048 .lines()
10049 .map(|line| {
10050 line.strip_prefix(&line_prefix)
10051 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10052 .ok_or_else(|| {
10053 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
10054 })
10055 })
10056 .collect::<Result<Vec<_>, _>>()
10057 .log_err()
10058 else {
10059 continue;
10060 };
10061
10062 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10063 buffer
10064 .language_settings_at(Point::new(start_row, 0), cx)
10065 .preferred_line_length as usize
10066 });
10067 let wrapped_text = wrap_with_prefix(
10068 line_prefix,
10069 lines_without_prefixes.join("\n"),
10070 wrap_column,
10071 tab_size,
10072 options.preserve_existing_whitespace,
10073 );
10074
10075 // TODO: should always use char-based diff while still supporting cursor behavior that
10076 // matches vim.
10077 let mut diff_options = DiffOptions::default();
10078 if options.override_language_settings {
10079 diff_options.max_word_diff_len = 0;
10080 diff_options.max_word_diff_line_count = 0;
10081 } else {
10082 diff_options.max_word_diff_len = usize::MAX;
10083 diff_options.max_word_diff_line_count = usize::MAX;
10084 }
10085
10086 for (old_range, new_text) in
10087 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10088 {
10089 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10090 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10091 edits.push((edit_start..edit_end, new_text));
10092 }
10093
10094 rewrapped_row_ranges.push(start_row..=end_row);
10095 }
10096
10097 self.buffer
10098 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10099 }
10100
10101 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10102 let mut text = String::new();
10103 let buffer = self.buffer.read(cx).snapshot(cx);
10104 let mut selections = self.selections.all::<Point>(cx);
10105 let mut clipboard_selections = Vec::with_capacity(selections.len());
10106 {
10107 let max_point = buffer.max_point();
10108 let mut is_first = true;
10109 for selection in &mut selections {
10110 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10111 if is_entire_line {
10112 selection.start = Point::new(selection.start.row, 0);
10113 if !selection.is_empty() && selection.end.column == 0 {
10114 selection.end = cmp::min(max_point, selection.end);
10115 } else {
10116 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10117 }
10118 selection.goal = SelectionGoal::None;
10119 }
10120 if is_first {
10121 is_first = false;
10122 } else {
10123 text += "\n";
10124 }
10125 let mut len = 0;
10126 for chunk in buffer.text_for_range(selection.start..selection.end) {
10127 text.push_str(chunk);
10128 len += chunk.len();
10129 }
10130 clipboard_selections.push(ClipboardSelection {
10131 len,
10132 is_entire_line,
10133 first_line_indent: buffer
10134 .indent_size_for_line(MultiBufferRow(selection.start.row))
10135 .len,
10136 });
10137 }
10138 }
10139
10140 self.transact(window, cx, |this, window, cx| {
10141 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10142 s.select(selections);
10143 });
10144 this.insert("", window, cx);
10145 });
10146 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10147 }
10148
10149 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10150 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10151 let item = self.cut_common(window, cx);
10152 cx.write_to_clipboard(item);
10153 }
10154
10155 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10156 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10157 self.change_selections(None, window, cx, |s| {
10158 s.move_with(|snapshot, sel| {
10159 if sel.is_empty() {
10160 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10161 }
10162 });
10163 });
10164 let item = self.cut_common(window, cx);
10165 cx.set_global(KillRing(item))
10166 }
10167
10168 pub fn kill_ring_yank(
10169 &mut self,
10170 _: &KillRingYank,
10171 window: &mut Window,
10172 cx: &mut Context<Self>,
10173 ) {
10174 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10175 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10176 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10177 (kill_ring.text().to_string(), kill_ring.metadata_json())
10178 } else {
10179 return;
10180 }
10181 } else {
10182 return;
10183 };
10184 self.do_paste(&text, metadata, false, window, cx);
10185 }
10186
10187 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10188 self.do_copy(true, cx);
10189 }
10190
10191 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10192 self.do_copy(false, cx);
10193 }
10194
10195 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10196 let selections = self.selections.all::<Point>(cx);
10197 let buffer = self.buffer.read(cx).read(cx);
10198 let mut text = String::new();
10199
10200 let mut clipboard_selections = Vec::with_capacity(selections.len());
10201 {
10202 let max_point = buffer.max_point();
10203 let mut is_first = true;
10204 for selection in &selections {
10205 let mut start = selection.start;
10206 let mut end = selection.end;
10207 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10208 if is_entire_line {
10209 start = Point::new(start.row, 0);
10210 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10211 }
10212
10213 let mut trimmed_selections = Vec::new();
10214 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10215 let row = MultiBufferRow(start.row);
10216 let first_indent = buffer.indent_size_for_line(row);
10217 if first_indent.len == 0 || start.column > first_indent.len {
10218 trimmed_selections.push(start..end);
10219 } else {
10220 trimmed_selections.push(
10221 Point::new(row.0, first_indent.len)
10222 ..Point::new(row.0, buffer.line_len(row)),
10223 );
10224 for row in start.row + 1..=end.row {
10225 let mut line_len = buffer.line_len(MultiBufferRow(row));
10226 if row == end.row {
10227 line_len = end.column;
10228 }
10229 if line_len == 0 {
10230 trimmed_selections
10231 .push(Point::new(row, 0)..Point::new(row, line_len));
10232 continue;
10233 }
10234 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10235 if row_indent_size.len >= first_indent.len {
10236 trimmed_selections.push(
10237 Point::new(row, first_indent.len)..Point::new(row, line_len),
10238 );
10239 } else {
10240 trimmed_selections.clear();
10241 trimmed_selections.push(start..end);
10242 break;
10243 }
10244 }
10245 }
10246 } else {
10247 trimmed_selections.push(start..end);
10248 }
10249
10250 for trimmed_range in trimmed_selections {
10251 if is_first {
10252 is_first = false;
10253 } else {
10254 text += "\n";
10255 }
10256 let mut len = 0;
10257 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10258 text.push_str(chunk);
10259 len += chunk.len();
10260 }
10261 clipboard_selections.push(ClipboardSelection {
10262 len,
10263 is_entire_line,
10264 first_line_indent: buffer
10265 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10266 .len,
10267 });
10268 }
10269 }
10270 }
10271
10272 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10273 text,
10274 clipboard_selections,
10275 ));
10276 }
10277
10278 pub fn do_paste(
10279 &mut self,
10280 text: &String,
10281 clipboard_selections: Option<Vec<ClipboardSelection>>,
10282 handle_entire_lines: bool,
10283 window: &mut Window,
10284 cx: &mut Context<Self>,
10285 ) {
10286 if self.read_only(cx) {
10287 return;
10288 }
10289
10290 let clipboard_text = Cow::Borrowed(text);
10291
10292 self.transact(window, cx, |this, window, cx| {
10293 if let Some(mut clipboard_selections) = clipboard_selections {
10294 let old_selections = this.selections.all::<usize>(cx);
10295 let all_selections_were_entire_line =
10296 clipboard_selections.iter().all(|s| s.is_entire_line);
10297 let first_selection_indent_column =
10298 clipboard_selections.first().map(|s| s.first_line_indent);
10299 if clipboard_selections.len() != old_selections.len() {
10300 clipboard_selections.drain(..);
10301 }
10302 let cursor_offset = this.selections.last::<usize>(cx).head();
10303 let mut auto_indent_on_paste = true;
10304
10305 this.buffer.update(cx, |buffer, cx| {
10306 let snapshot = buffer.read(cx);
10307 auto_indent_on_paste = snapshot
10308 .language_settings_at(cursor_offset, cx)
10309 .auto_indent_on_paste;
10310
10311 let mut start_offset = 0;
10312 let mut edits = Vec::new();
10313 let mut original_indent_columns = Vec::new();
10314 for (ix, selection) in old_selections.iter().enumerate() {
10315 let to_insert;
10316 let entire_line;
10317 let original_indent_column;
10318 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10319 let end_offset = start_offset + clipboard_selection.len;
10320 to_insert = &clipboard_text[start_offset..end_offset];
10321 entire_line = clipboard_selection.is_entire_line;
10322 start_offset = end_offset + 1;
10323 original_indent_column = Some(clipboard_selection.first_line_indent);
10324 } else {
10325 to_insert = clipboard_text.as_str();
10326 entire_line = all_selections_were_entire_line;
10327 original_indent_column = first_selection_indent_column
10328 }
10329
10330 // If the corresponding selection was empty when this slice of the
10331 // clipboard text was written, then the entire line containing the
10332 // selection was copied. If this selection is also currently empty,
10333 // then paste the line before the current line of the buffer.
10334 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10335 let column = selection.start.to_point(&snapshot).column as usize;
10336 let line_start = selection.start - column;
10337 line_start..line_start
10338 } else {
10339 selection.range()
10340 };
10341
10342 edits.push((range, to_insert));
10343 original_indent_columns.push(original_indent_column);
10344 }
10345 drop(snapshot);
10346
10347 buffer.edit(
10348 edits,
10349 if auto_indent_on_paste {
10350 Some(AutoindentMode::Block {
10351 original_indent_columns,
10352 })
10353 } else {
10354 None
10355 },
10356 cx,
10357 );
10358 });
10359
10360 let selections = this.selections.all::<usize>(cx);
10361 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10362 s.select(selections)
10363 });
10364 } else {
10365 this.insert(&clipboard_text, window, cx);
10366 }
10367 });
10368 }
10369
10370 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10371 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10372 if let Some(item) = cx.read_from_clipboard() {
10373 let entries = item.entries();
10374
10375 match entries.first() {
10376 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10377 // of all the pasted entries.
10378 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10379 .do_paste(
10380 clipboard_string.text(),
10381 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10382 true,
10383 window,
10384 cx,
10385 ),
10386 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10387 }
10388 }
10389 }
10390
10391 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10392 if self.read_only(cx) {
10393 return;
10394 }
10395
10396 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10397
10398 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10399 if let Some((selections, _)) =
10400 self.selection_history.transaction(transaction_id).cloned()
10401 {
10402 self.change_selections(None, window, cx, |s| {
10403 s.select_anchors(selections.to_vec());
10404 });
10405 } else {
10406 log::error!(
10407 "No entry in selection_history found for undo. \
10408 This may correspond to a bug where undo does not update the selection. \
10409 If this is occurring, please add details to \
10410 https://github.com/zed-industries/zed/issues/22692"
10411 );
10412 }
10413 self.request_autoscroll(Autoscroll::fit(), cx);
10414 self.unmark_text(window, cx);
10415 self.refresh_inline_completion(true, false, window, cx);
10416 cx.emit(EditorEvent::Edited { transaction_id });
10417 cx.emit(EditorEvent::TransactionUndone { transaction_id });
10418 }
10419 }
10420
10421 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
10422 if self.read_only(cx) {
10423 return;
10424 }
10425
10426 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10427
10428 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
10429 if let Some((_, Some(selections))) =
10430 self.selection_history.transaction(transaction_id).cloned()
10431 {
10432 self.change_selections(None, window, cx, |s| {
10433 s.select_anchors(selections.to_vec());
10434 });
10435 } else {
10436 log::error!(
10437 "No entry in selection_history found for redo. \
10438 This may correspond to a bug where undo does not update the selection. \
10439 If this is occurring, please add details to \
10440 https://github.com/zed-industries/zed/issues/22692"
10441 );
10442 }
10443 self.request_autoscroll(Autoscroll::fit(), cx);
10444 self.unmark_text(window, cx);
10445 self.refresh_inline_completion(true, false, window, cx);
10446 cx.emit(EditorEvent::Edited { transaction_id });
10447 }
10448 }
10449
10450 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
10451 self.buffer
10452 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
10453 }
10454
10455 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
10456 self.buffer
10457 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
10458 }
10459
10460 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
10461 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10462 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10463 s.move_with(|map, selection| {
10464 let cursor = if selection.is_empty() {
10465 movement::left(map, selection.start)
10466 } else {
10467 selection.start
10468 };
10469 selection.collapse_to(cursor, SelectionGoal::None);
10470 });
10471 })
10472 }
10473
10474 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
10475 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10476 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10477 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
10478 })
10479 }
10480
10481 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
10482 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10483 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10484 s.move_with(|map, selection| {
10485 let cursor = if selection.is_empty() {
10486 movement::right(map, selection.end)
10487 } else {
10488 selection.end
10489 };
10490 selection.collapse_to(cursor, SelectionGoal::None)
10491 });
10492 })
10493 }
10494
10495 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
10496 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10497 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10498 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
10499 })
10500 }
10501
10502 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
10503 if self.take_rename(true, window, cx).is_some() {
10504 return;
10505 }
10506
10507 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10508 cx.propagate();
10509 return;
10510 }
10511
10512 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10513
10514 let text_layout_details = &self.text_layout_details(window);
10515 let selection_count = self.selections.count();
10516 let first_selection = self.selections.first_anchor();
10517
10518 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10519 s.move_with(|map, selection| {
10520 if !selection.is_empty() {
10521 selection.goal = SelectionGoal::None;
10522 }
10523 let (cursor, goal) = movement::up(
10524 map,
10525 selection.start,
10526 selection.goal,
10527 false,
10528 text_layout_details,
10529 );
10530 selection.collapse_to(cursor, goal);
10531 });
10532 });
10533
10534 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10535 {
10536 cx.propagate();
10537 }
10538 }
10539
10540 pub fn move_up_by_lines(
10541 &mut self,
10542 action: &MoveUpByLines,
10543 window: &mut Window,
10544 cx: &mut Context<Self>,
10545 ) {
10546 if self.take_rename(true, window, cx).is_some() {
10547 return;
10548 }
10549
10550 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10551 cx.propagate();
10552 return;
10553 }
10554
10555 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10556
10557 let text_layout_details = &self.text_layout_details(window);
10558
10559 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10560 s.move_with(|map, selection| {
10561 if !selection.is_empty() {
10562 selection.goal = SelectionGoal::None;
10563 }
10564 let (cursor, goal) = movement::up_by_rows(
10565 map,
10566 selection.start,
10567 action.lines,
10568 selection.goal,
10569 false,
10570 text_layout_details,
10571 );
10572 selection.collapse_to(cursor, goal);
10573 });
10574 })
10575 }
10576
10577 pub fn move_down_by_lines(
10578 &mut self,
10579 action: &MoveDownByLines,
10580 window: &mut Window,
10581 cx: &mut Context<Self>,
10582 ) {
10583 if self.take_rename(true, window, cx).is_some() {
10584 return;
10585 }
10586
10587 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10588 cx.propagate();
10589 return;
10590 }
10591
10592 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10593
10594 let text_layout_details = &self.text_layout_details(window);
10595
10596 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10597 s.move_with(|map, selection| {
10598 if !selection.is_empty() {
10599 selection.goal = SelectionGoal::None;
10600 }
10601 let (cursor, goal) = movement::down_by_rows(
10602 map,
10603 selection.start,
10604 action.lines,
10605 selection.goal,
10606 false,
10607 text_layout_details,
10608 );
10609 selection.collapse_to(cursor, goal);
10610 });
10611 })
10612 }
10613
10614 pub fn select_down_by_lines(
10615 &mut self,
10616 action: &SelectDownByLines,
10617 window: &mut Window,
10618 cx: &mut Context<Self>,
10619 ) {
10620 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10621 let text_layout_details = &self.text_layout_details(window);
10622 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10623 s.move_heads_with(|map, head, goal| {
10624 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
10625 })
10626 })
10627 }
10628
10629 pub fn select_up_by_lines(
10630 &mut self,
10631 action: &SelectUpByLines,
10632 window: &mut Window,
10633 cx: &mut Context<Self>,
10634 ) {
10635 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10636 let text_layout_details = &self.text_layout_details(window);
10637 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10638 s.move_heads_with(|map, head, goal| {
10639 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
10640 })
10641 })
10642 }
10643
10644 pub fn select_page_up(
10645 &mut self,
10646 _: &SelectPageUp,
10647 window: &mut Window,
10648 cx: &mut Context<Self>,
10649 ) {
10650 let Some(row_count) = self.visible_row_count() else {
10651 return;
10652 };
10653
10654 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10655
10656 let text_layout_details = &self.text_layout_details(window);
10657
10658 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10659 s.move_heads_with(|map, head, goal| {
10660 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
10661 })
10662 })
10663 }
10664
10665 pub fn move_page_up(
10666 &mut self,
10667 action: &MovePageUp,
10668 window: &mut Window,
10669 cx: &mut Context<Self>,
10670 ) {
10671 if self.take_rename(true, window, cx).is_some() {
10672 return;
10673 }
10674
10675 if self
10676 .context_menu
10677 .borrow_mut()
10678 .as_mut()
10679 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
10680 .unwrap_or(false)
10681 {
10682 return;
10683 }
10684
10685 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10686 cx.propagate();
10687 return;
10688 }
10689
10690 let Some(row_count) = self.visible_row_count() else {
10691 return;
10692 };
10693
10694 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10695
10696 let autoscroll = if action.center_cursor {
10697 Autoscroll::center()
10698 } else {
10699 Autoscroll::fit()
10700 };
10701
10702 let text_layout_details = &self.text_layout_details(window);
10703
10704 self.change_selections(Some(autoscroll), window, cx, |s| {
10705 s.move_with(|map, selection| {
10706 if !selection.is_empty() {
10707 selection.goal = SelectionGoal::None;
10708 }
10709 let (cursor, goal) = movement::up_by_rows(
10710 map,
10711 selection.end,
10712 row_count,
10713 selection.goal,
10714 false,
10715 text_layout_details,
10716 );
10717 selection.collapse_to(cursor, goal);
10718 });
10719 });
10720 }
10721
10722 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
10723 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10724 let text_layout_details = &self.text_layout_details(window);
10725 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10726 s.move_heads_with(|map, head, goal| {
10727 movement::up(map, head, goal, false, text_layout_details)
10728 })
10729 })
10730 }
10731
10732 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
10733 self.take_rename(true, window, cx);
10734
10735 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10736 cx.propagate();
10737 return;
10738 }
10739
10740 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10741
10742 let text_layout_details = &self.text_layout_details(window);
10743 let selection_count = self.selections.count();
10744 let first_selection = self.selections.first_anchor();
10745
10746 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10747 s.move_with(|map, selection| {
10748 if !selection.is_empty() {
10749 selection.goal = SelectionGoal::None;
10750 }
10751 let (cursor, goal) = movement::down(
10752 map,
10753 selection.end,
10754 selection.goal,
10755 false,
10756 text_layout_details,
10757 );
10758 selection.collapse_to(cursor, goal);
10759 });
10760 });
10761
10762 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10763 {
10764 cx.propagate();
10765 }
10766 }
10767
10768 pub fn select_page_down(
10769 &mut self,
10770 _: &SelectPageDown,
10771 window: &mut Window,
10772 cx: &mut Context<Self>,
10773 ) {
10774 let Some(row_count) = self.visible_row_count() else {
10775 return;
10776 };
10777
10778 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10779
10780 let text_layout_details = &self.text_layout_details(window);
10781
10782 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10783 s.move_heads_with(|map, head, goal| {
10784 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
10785 })
10786 })
10787 }
10788
10789 pub fn move_page_down(
10790 &mut self,
10791 action: &MovePageDown,
10792 window: &mut Window,
10793 cx: &mut Context<Self>,
10794 ) {
10795 if self.take_rename(true, window, cx).is_some() {
10796 return;
10797 }
10798
10799 if self
10800 .context_menu
10801 .borrow_mut()
10802 .as_mut()
10803 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
10804 .unwrap_or(false)
10805 {
10806 return;
10807 }
10808
10809 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10810 cx.propagate();
10811 return;
10812 }
10813
10814 let Some(row_count) = self.visible_row_count() else {
10815 return;
10816 };
10817
10818 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10819
10820 let autoscroll = if action.center_cursor {
10821 Autoscroll::center()
10822 } else {
10823 Autoscroll::fit()
10824 };
10825
10826 let text_layout_details = &self.text_layout_details(window);
10827 self.change_selections(Some(autoscroll), window, cx, |s| {
10828 s.move_with(|map, selection| {
10829 if !selection.is_empty() {
10830 selection.goal = SelectionGoal::None;
10831 }
10832 let (cursor, goal) = movement::down_by_rows(
10833 map,
10834 selection.end,
10835 row_count,
10836 selection.goal,
10837 false,
10838 text_layout_details,
10839 );
10840 selection.collapse_to(cursor, goal);
10841 });
10842 });
10843 }
10844
10845 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
10846 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10847 let text_layout_details = &self.text_layout_details(window);
10848 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10849 s.move_heads_with(|map, head, goal| {
10850 movement::down(map, head, goal, false, text_layout_details)
10851 })
10852 });
10853 }
10854
10855 pub fn context_menu_first(
10856 &mut self,
10857 _: &ContextMenuFirst,
10858 _window: &mut Window,
10859 cx: &mut Context<Self>,
10860 ) {
10861 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10862 context_menu.select_first(self.completion_provider.as_deref(), cx);
10863 }
10864 }
10865
10866 pub fn context_menu_prev(
10867 &mut self,
10868 _: &ContextMenuPrevious,
10869 _window: &mut Window,
10870 cx: &mut Context<Self>,
10871 ) {
10872 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10873 context_menu.select_prev(self.completion_provider.as_deref(), cx);
10874 }
10875 }
10876
10877 pub fn context_menu_next(
10878 &mut self,
10879 _: &ContextMenuNext,
10880 _window: &mut Window,
10881 cx: &mut Context<Self>,
10882 ) {
10883 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10884 context_menu.select_next(self.completion_provider.as_deref(), cx);
10885 }
10886 }
10887
10888 pub fn context_menu_last(
10889 &mut self,
10890 _: &ContextMenuLast,
10891 _window: &mut Window,
10892 cx: &mut Context<Self>,
10893 ) {
10894 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10895 context_menu.select_last(self.completion_provider.as_deref(), cx);
10896 }
10897 }
10898
10899 pub fn move_to_previous_word_start(
10900 &mut self,
10901 _: &MoveToPreviousWordStart,
10902 window: &mut Window,
10903 cx: &mut Context<Self>,
10904 ) {
10905 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10906 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10907 s.move_cursors_with(|map, head, _| {
10908 (
10909 movement::previous_word_start(map, head),
10910 SelectionGoal::None,
10911 )
10912 });
10913 })
10914 }
10915
10916 pub fn move_to_previous_subword_start(
10917 &mut self,
10918 _: &MoveToPreviousSubwordStart,
10919 window: &mut Window,
10920 cx: &mut Context<Self>,
10921 ) {
10922 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10923 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10924 s.move_cursors_with(|map, head, _| {
10925 (
10926 movement::previous_subword_start(map, head),
10927 SelectionGoal::None,
10928 )
10929 });
10930 })
10931 }
10932
10933 pub fn select_to_previous_word_start(
10934 &mut self,
10935 _: &SelectToPreviousWordStart,
10936 window: &mut Window,
10937 cx: &mut Context<Self>,
10938 ) {
10939 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10940 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10941 s.move_heads_with(|map, head, _| {
10942 (
10943 movement::previous_word_start(map, head),
10944 SelectionGoal::None,
10945 )
10946 });
10947 })
10948 }
10949
10950 pub fn select_to_previous_subword_start(
10951 &mut self,
10952 _: &SelectToPreviousSubwordStart,
10953 window: &mut Window,
10954 cx: &mut Context<Self>,
10955 ) {
10956 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10957 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10958 s.move_heads_with(|map, head, _| {
10959 (
10960 movement::previous_subword_start(map, head),
10961 SelectionGoal::None,
10962 )
10963 });
10964 })
10965 }
10966
10967 pub fn delete_to_previous_word_start(
10968 &mut self,
10969 action: &DeleteToPreviousWordStart,
10970 window: &mut Window,
10971 cx: &mut Context<Self>,
10972 ) {
10973 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10974 self.transact(window, cx, |this, window, cx| {
10975 this.select_autoclose_pair(window, cx);
10976 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10977 s.move_with(|map, selection| {
10978 if selection.is_empty() {
10979 let cursor = if action.ignore_newlines {
10980 movement::previous_word_start(map, selection.head())
10981 } else {
10982 movement::previous_word_start_or_newline(map, selection.head())
10983 };
10984 selection.set_head(cursor, SelectionGoal::None);
10985 }
10986 });
10987 });
10988 this.insert("", window, cx);
10989 });
10990 }
10991
10992 pub fn delete_to_previous_subword_start(
10993 &mut self,
10994 _: &DeleteToPreviousSubwordStart,
10995 window: &mut Window,
10996 cx: &mut Context<Self>,
10997 ) {
10998 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10999 self.transact(window, cx, |this, window, cx| {
11000 this.select_autoclose_pair(window, cx);
11001 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11002 s.move_with(|map, selection| {
11003 if selection.is_empty() {
11004 let cursor = movement::previous_subword_start(map, selection.head());
11005 selection.set_head(cursor, SelectionGoal::None);
11006 }
11007 });
11008 });
11009 this.insert("", window, cx);
11010 });
11011 }
11012
11013 pub fn move_to_next_word_end(
11014 &mut self,
11015 _: &MoveToNextWordEnd,
11016 window: &mut Window,
11017 cx: &mut Context<Self>,
11018 ) {
11019 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11020 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11021 s.move_cursors_with(|map, head, _| {
11022 (movement::next_word_end(map, head), SelectionGoal::None)
11023 });
11024 })
11025 }
11026
11027 pub fn move_to_next_subword_end(
11028 &mut self,
11029 _: &MoveToNextSubwordEnd,
11030 window: &mut Window,
11031 cx: &mut Context<Self>,
11032 ) {
11033 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11034 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11035 s.move_cursors_with(|map, head, _| {
11036 (movement::next_subword_end(map, head), SelectionGoal::None)
11037 });
11038 })
11039 }
11040
11041 pub fn select_to_next_word_end(
11042 &mut self,
11043 _: &SelectToNextWordEnd,
11044 window: &mut Window,
11045 cx: &mut Context<Self>,
11046 ) {
11047 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11048 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11049 s.move_heads_with(|map, head, _| {
11050 (movement::next_word_end(map, head), SelectionGoal::None)
11051 });
11052 })
11053 }
11054
11055 pub fn select_to_next_subword_end(
11056 &mut self,
11057 _: &SelectToNextSubwordEnd,
11058 window: &mut Window,
11059 cx: &mut Context<Self>,
11060 ) {
11061 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11062 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11063 s.move_heads_with(|map, head, _| {
11064 (movement::next_subword_end(map, head), SelectionGoal::None)
11065 });
11066 })
11067 }
11068
11069 pub fn delete_to_next_word_end(
11070 &mut self,
11071 action: &DeleteToNextWordEnd,
11072 window: &mut Window,
11073 cx: &mut Context<Self>,
11074 ) {
11075 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11076 self.transact(window, cx, |this, window, cx| {
11077 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11078 s.move_with(|map, selection| {
11079 if selection.is_empty() {
11080 let cursor = if action.ignore_newlines {
11081 movement::next_word_end(map, selection.head())
11082 } else {
11083 movement::next_word_end_or_newline(map, selection.head())
11084 };
11085 selection.set_head(cursor, SelectionGoal::None);
11086 }
11087 });
11088 });
11089 this.insert("", window, cx);
11090 });
11091 }
11092
11093 pub fn delete_to_next_subword_end(
11094 &mut self,
11095 _: &DeleteToNextSubwordEnd,
11096 window: &mut Window,
11097 cx: &mut Context<Self>,
11098 ) {
11099 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11100 self.transact(window, cx, |this, window, cx| {
11101 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11102 s.move_with(|map, selection| {
11103 if selection.is_empty() {
11104 let cursor = movement::next_subword_end(map, selection.head());
11105 selection.set_head(cursor, SelectionGoal::None);
11106 }
11107 });
11108 });
11109 this.insert("", window, cx);
11110 });
11111 }
11112
11113 pub fn move_to_beginning_of_line(
11114 &mut self,
11115 action: &MoveToBeginningOfLine,
11116 window: &mut Window,
11117 cx: &mut Context<Self>,
11118 ) {
11119 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11120 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11121 s.move_cursors_with(|map, head, _| {
11122 (
11123 movement::indented_line_beginning(
11124 map,
11125 head,
11126 action.stop_at_soft_wraps,
11127 action.stop_at_indent,
11128 ),
11129 SelectionGoal::None,
11130 )
11131 });
11132 })
11133 }
11134
11135 pub fn select_to_beginning_of_line(
11136 &mut self,
11137 action: &SelectToBeginningOfLine,
11138 window: &mut Window,
11139 cx: &mut Context<Self>,
11140 ) {
11141 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11142 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11143 s.move_heads_with(|map, head, _| {
11144 (
11145 movement::indented_line_beginning(
11146 map,
11147 head,
11148 action.stop_at_soft_wraps,
11149 action.stop_at_indent,
11150 ),
11151 SelectionGoal::None,
11152 )
11153 });
11154 });
11155 }
11156
11157 pub fn delete_to_beginning_of_line(
11158 &mut self,
11159 action: &DeleteToBeginningOfLine,
11160 window: &mut Window,
11161 cx: &mut Context<Self>,
11162 ) {
11163 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11164 self.transact(window, cx, |this, window, cx| {
11165 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11166 s.move_with(|_, selection| {
11167 selection.reversed = true;
11168 });
11169 });
11170
11171 this.select_to_beginning_of_line(
11172 &SelectToBeginningOfLine {
11173 stop_at_soft_wraps: false,
11174 stop_at_indent: action.stop_at_indent,
11175 },
11176 window,
11177 cx,
11178 );
11179 this.backspace(&Backspace, window, cx);
11180 });
11181 }
11182
11183 pub fn move_to_end_of_line(
11184 &mut self,
11185 action: &MoveToEndOfLine,
11186 window: &mut Window,
11187 cx: &mut Context<Self>,
11188 ) {
11189 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11190 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11191 s.move_cursors_with(|map, head, _| {
11192 (
11193 movement::line_end(map, head, action.stop_at_soft_wraps),
11194 SelectionGoal::None,
11195 )
11196 });
11197 })
11198 }
11199
11200 pub fn select_to_end_of_line(
11201 &mut self,
11202 action: &SelectToEndOfLine,
11203 window: &mut Window,
11204 cx: &mut Context<Self>,
11205 ) {
11206 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11207 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11208 s.move_heads_with(|map, head, _| {
11209 (
11210 movement::line_end(map, head, action.stop_at_soft_wraps),
11211 SelectionGoal::None,
11212 )
11213 });
11214 })
11215 }
11216
11217 pub fn delete_to_end_of_line(
11218 &mut self,
11219 _: &DeleteToEndOfLine,
11220 window: &mut Window,
11221 cx: &mut Context<Self>,
11222 ) {
11223 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11224 self.transact(window, cx, |this, window, cx| {
11225 this.select_to_end_of_line(
11226 &SelectToEndOfLine {
11227 stop_at_soft_wraps: false,
11228 },
11229 window,
11230 cx,
11231 );
11232 this.delete(&Delete, window, cx);
11233 });
11234 }
11235
11236 pub fn cut_to_end_of_line(
11237 &mut self,
11238 _: &CutToEndOfLine,
11239 window: &mut Window,
11240 cx: &mut Context<Self>,
11241 ) {
11242 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11243 self.transact(window, cx, |this, window, cx| {
11244 this.select_to_end_of_line(
11245 &SelectToEndOfLine {
11246 stop_at_soft_wraps: false,
11247 },
11248 window,
11249 cx,
11250 );
11251 this.cut(&Cut, window, cx);
11252 });
11253 }
11254
11255 pub fn move_to_start_of_paragraph(
11256 &mut self,
11257 _: &MoveToStartOfParagraph,
11258 window: &mut Window,
11259 cx: &mut Context<Self>,
11260 ) {
11261 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11262 cx.propagate();
11263 return;
11264 }
11265 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11266 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11267 s.move_with(|map, selection| {
11268 selection.collapse_to(
11269 movement::start_of_paragraph(map, selection.head(), 1),
11270 SelectionGoal::None,
11271 )
11272 });
11273 })
11274 }
11275
11276 pub fn move_to_end_of_paragraph(
11277 &mut self,
11278 _: &MoveToEndOfParagraph,
11279 window: &mut Window,
11280 cx: &mut Context<Self>,
11281 ) {
11282 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11283 cx.propagate();
11284 return;
11285 }
11286 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11287 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11288 s.move_with(|map, selection| {
11289 selection.collapse_to(
11290 movement::end_of_paragraph(map, selection.head(), 1),
11291 SelectionGoal::None,
11292 )
11293 });
11294 })
11295 }
11296
11297 pub fn select_to_start_of_paragraph(
11298 &mut self,
11299 _: &SelectToStartOfParagraph,
11300 window: &mut Window,
11301 cx: &mut Context<Self>,
11302 ) {
11303 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11304 cx.propagate();
11305 return;
11306 }
11307 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11308 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11309 s.move_heads_with(|map, head, _| {
11310 (
11311 movement::start_of_paragraph(map, head, 1),
11312 SelectionGoal::None,
11313 )
11314 });
11315 })
11316 }
11317
11318 pub fn select_to_end_of_paragraph(
11319 &mut self,
11320 _: &SelectToEndOfParagraph,
11321 window: &mut Window,
11322 cx: &mut Context<Self>,
11323 ) {
11324 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11325 cx.propagate();
11326 return;
11327 }
11328 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11329 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11330 s.move_heads_with(|map, head, _| {
11331 (
11332 movement::end_of_paragraph(map, head, 1),
11333 SelectionGoal::None,
11334 )
11335 });
11336 })
11337 }
11338
11339 pub fn move_to_start_of_excerpt(
11340 &mut self,
11341 _: &MoveToStartOfExcerpt,
11342 window: &mut Window,
11343 cx: &mut Context<Self>,
11344 ) {
11345 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11346 cx.propagate();
11347 return;
11348 }
11349 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11350 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11351 s.move_with(|map, selection| {
11352 selection.collapse_to(
11353 movement::start_of_excerpt(
11354 map,
11355 selection.head(),
11356 workspace::searchable::Direction::Prev,
11357 ),
11358 SelectionGoal::None,
11359 )
11360 });
11361 })
11362 }
11363
11364 pub fn move_to_start_of_next_excerpt(
11365 &mut self,
11366 _: &MoveToStartOfNextExcerpt,
11367 window: &mut Window,
11368 cx: &mut Context<Self>,
11369 ) {
11370 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11371 cx.propagate();
11372 return;
11373 }
11374
11375 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11376 s.move_with(|map, selection| {
11377 selection.collapse_to(
11378 movement::start_of_excerpt(
11379 map,
11380 selection.head(),
11381 workspace::searchable::Direction::Next,
11382 ),
11383 SelectionGoal::None,
11384 )
11385 });
11386 })
11387 }
11388
11389 pub fn move_to_end_of_excerpt(
11390 &mut self,
11391 _: &MoveToEndOfExcerpt,
11392 window: &mut Window,
11393 cx: &mut Context<Self>,
11394 ) {
11395 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11396 cx.propagate();
11397 return;
11398 }
11399 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11400 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11401 s.move_with(|map, selection| {
11402 selection.collapse_to(
11403 movement::end_of_excerpt(
11404 map,
11405 selection.head(),
11406 workspace::searchable::Direction::Next,
11407 ),
11408 SelectionGoal::None,
11409 )
11410 });
11411 })
11412 }
11413
11414 pub fn move_to_end_of_previous_excerpt(
11415 &mut self,
11416 _: &MoveToEndOfPreviousExcerpt,
11417 window: &mut Window,
11418 cx: &mut Context<Self>,
11419 ) {
11420 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11421 cx.propagate();
11422 return;
11423 }
11424 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11425 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11426 s.move_with(|map, selection| {
11427 selection.collapse_to(
11428 movement::end_of_excerpt(
11429 map,
11430 selection.head(),
11431 workspace::searchable::Direction::Prev,
11432 ),
11433 SelectionGoal::None,
11434 )
11435 });
11436 })
11437 }
11438
11439 pub fn select_to_start_of_excerpt(
11440 &mut self,
11441 _: &SelectToStartOfExcerpt,
11442 window: &mut Window,
11443 cx: &mut Context<Self>,
11444 ) {
11445 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11446 cx.propagate();
11447 return;
11448 }
11449 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11450 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11451 s.move_heads_with(|map, head, _| {
11452 (
11453 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11454 SelectionGoal::None,
11455 )
11456 });
11457 })
11458 }
11459
11460 pub fn select_to_start_of_next_excerpt(
11461 &mut self,
11462 _: &SelectToStartOfNextExcerpt,
11463 window: &mut Window,
11464 cx: &mut Context<Self>,
11465 ) {
11466 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11467 cx.propagate();
11468 return;
11469 }
11470 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11471 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11472 s.move_heads_with(|map, head, _| {
11473 (
11474 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
11475 SelectionGoal::None,
11476 )
11477 });
11478 })
11479 }
11480
11481 pub fn select_to_end_of_excerpt(
11482 &mut self,
11483 _: &SelectToEndOfExcerpt,
11484 window: &mut Window,
11485 cx: &mut Context<Self>,
11486 ) {
11487 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11488 cx.propagate();
11489 return;
11490 }
11491 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11492 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11493 s.move_heads_with(|map, head, _| {
11494 (
11495 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
11496 SelectionGoal::None,
11497 )
11498 });
11499 })
11500 }
11501
11502 pub fn select_to_end_of_previous_excerpt(
11503 &mut self,
11504 _: &SelectToEndOfPreviousExcerpt,
11505 window: &mut Window,
11506 cx: &mut Context<Self>,
11507 ) {
11508 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11509 cx.propagate();
11510 return;
11511 }
11512 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11513 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11514 s.move_heads_with(|map, head, _| {
11515 (
11516 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11517 SelectionGoal::None,
11518 )
11519 });
11520 })
11521 }
11522
11523 pub fn move_to_beginning(
11524 &mut self,
11525 _: &MoveToBeginning,
11526 window: &mut Window,
11527 cx: &mut Context<Self>,
11528 ) {
11529 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11530 cx.propagate();
11531 return;
11532 }
11533 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11534 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11535 s.select_ranges(vec![0..0]);
11536 });
11537 }
11538
11539 pub fn select_to_beginning(
11540 &mut self,
11541 _: &SelectToBeginning,
11542 window: &mut Window,
11543 cx: &mut Context<Self>,
11544 ) {
11545 let mut selection = self.selections.last::<Point>(cx);
11546 selection.set_head(Point::zero(), SelectionGoal::None);
11547 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11548 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11549 s.select(vec![selection]);
11550 });
11551 }
11552
11553 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
11554 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11555 cx.propagate();
11556 return;
11557 }
11558 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11559 let cursor = self.buffer.read(cx).read(cx).len();
11560 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11561 s.select_ranges(vec![cursor..cursor])
11562 });
11563 }
11564
11565 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
11566 self.nav_history = nav_history;
11567 }
11568
11569 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
11570 self.nav_history.as_ref()
11571 }
11572
11573 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
11574 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
11575 }
11576
11577 fn push_to_nav_history(
11578 &mut self,
11579 cursor_anchor: Anchor,
11580 new_position: Option<Point>,
11581 is_deactivate: bool,
11582 cx: &mut Context<Self>,
11583 ) {
11584 if let Some(nav_history) = self.nav_history.as_mut() {
11585 let buffer = self.buffer.read(cx).read(cx);
11586 let cursor_position = cursor_anchor.to_point(&buffer);
11587 let scroll_state = self.scroll_manager.anchor();
11588 let scroll_top_row = scroll_state.top_row(&buffer);
11589 drop(buffer);
11590
11591 if let Some(new_position) = new_position {
11592 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
11593 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
11594 return;
11595 }
11596 }
11597
11598 nav_history.push(
11599 Some(NavigationData {
11600 cursor_anchor,
11601 cursor_position,
11602 scroll_anchor: scroll_state,
11603 scroll_top_row,
11604 }),
11605 cx,
11606 );
11607 cx.emit(EditorEvent::PushedToNavHistory {
11608 anchor: cursor_anchor,
11609 is_deactivate,
11610 })
11611 }
11612 }
11613
11614 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
11615 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11616 let buffer = self.buffer.read(cx).snapshot(cx);
11617 let mut selection = self.selections.first::<usize>(cx);
11618 selection.set_head(buffer.len(), SelectionGoal::None);
11619 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11620 s.select(vec![selection]);
11621 });
11622 }
11623
11624 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
11625 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11626 let end = self.buffer.read(cx).read(cx).len();
11627 self.change_selections(None, window, cx, |s| {
11628 s.select_ranges(vec![0..end]);
11629 });
11630 }
11631
11632 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
11633 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11634 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11635 let mut selections = self.selections.all::<Point>(cx);
11636 let max_point = display_map.buffer_snapshot.max_point();
11637 for selection in &mut selections {
11638 let rows = selection.spanned_rows(true, &display_map);
11639 selection.start = Point::new(rows.start.0, 0);
11640 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
11641 selection.reversed = false;
11642 }
11643 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11644 s.select(selections);
11645 });
11646 }
11647
11648 pub fn split_selection_into_lines(
11649 &mut self,
11650 _: &SplitSelectionIntoLines,
11651 window: &mut Window,
11652 cx: &mut Context<Self>,
11653 ) {
11654 let selections = self
11655 .selections
11656 .all::<Point>(cx)
11657 .into_iter()
11658 .map(|selection| selection.start..selection.end)
11659 .collect::<Vec<_>>();
11660 self.unfold_ranges(&selections, true, true, cx);
11661
11662 let mut new_selection_ranges = Vec::new();
11663 {
11664 let buffer = self.buffer.read(cx).read(cx);
11665 for selection in selections {
11666 for row in selection.start.row..selection.end.row {
11667 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11668 new_selection_ranges.push(cursor..cursor);
11669 }
11670
11671 let is_multiline_selection = selection.start.row != selection.end.row;
11672 // Don't insert last one if it's a multi-line selection ending at the start of a line,
11673 // so this action feels more ergonomic when paired with other selection operations
11674 let should_skip_last = is_multiline_selection && selection.end.column == 0;
11675 if !should_skip_last {
11676 new_selection_ranges.push(selection.end..selection.end);
11677 }
11678 }
11679 }
11680 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11681 s.select_ranges(new_selection_ranges);
11682 });
11683 }
11684
11685 pub fn add_selection_above(
11686 &mut self,
11687 _: &AddSelectionAbove,
11688 window: &mut Window,
11689 cx: &mut Context<Self>,
11690 ) {
11691 self.add_selection(true, window, cx);
11692 }
11693
11694 pub fn add_selection_below(
11695 &mut self,
11696 _: &AddSelectionBelow,
11697 window: &mut Window,
11698 cx: &mut Context<Self>,
11699 ) {
11700 self.add_selection(false, window, cx);
11701 }
11702
11703 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
11704 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11705
11706 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11707 let mut selections = self.selections.all::<Point>(cx);
11708 let text_layout_details = self.text_layout_details(window);
11709 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
11710 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
11711 let range = oldest_selection.display_range(&display_map).sorted();
11712
11713 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
11714 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
11715 let positions = start_x.min(end_x)..start_x.max(end_x);
11716
11717 selections.clear();
11718 let mut stack = Vec::new();
11719 for row in range.start.row().0..=range.end.row().0 {
11720 if let Some(selection) = self.selections.build_columnar_selection(
11721 &display_map,
11722 DisplayRow(row),
11723 &positions,
11724 oldest_selection.reversed,
11725 &text_layout_details,
11726 ) {
11727 stack.push(selection.id);
11728 selections.push(selection);
11729 }
11730 }
11731
11732 if above {
11733 stack.reverse();
11734 }
11735
11736 AddSelectionsState { above, stack }
11737 });
11738
11739 let last_added_selection = *state.stack.last().unwrap();
11740 let mut new_selections = Vec::new();
11741 if above == state.above {
11742 let end_row = if above {
11743 DisplayRow(0)
11744 } else {
11745 display_map.max_point().row()
11746 };
11747
11748 'outer: for selection in selections {
11749 if selection.id == last_added_selection {
11750 let range = selection.display_range(&display_map).sorted();
11751 debug_assert_eq!(range.start.row(), range.end.row());
11752 let mut row = range.start.row();
11753 let positions =
11754 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
11755 px(start)..px(end)
11756 } else {
11757 let start_x =
11758 display_map.x_for_display_point(range.start, &text_layout_details);
11759 let end_x =
11760 display_map.x_for_display_point(range.end, &text_layout_details);
11761 start_x.min(end_x)..start_x.max(end_x)
11762 };
11763
11764 while row != end_row {
11765 if above {
11766 row.0 -= 1;
11767 } else {
11768 row.0 += 1;
11769 }
11770
11771 if let Some(new_selection) = self.selections.build_columnar_selection(
11772 &display_map,
11773 row,
11774 &positions,
11775 selection.reversed,
11776 &text_layout_details,
11777 ) {
11778 state.stack.push(new_selection.id);
11779 if above {
11780 new_selections.push(new_selection);
11781 new_selections.push(selection);
11782 } else {
11783 new_selections.push(selection);
11784 new_selections.push(new_selection);
11785 }
11786
11787 continue 'outer;
11788 }
11789 }
11790 }
11791
11792 new_selections.push(selection);
11793 }
11794 } else {
11795 new_selections = selections;
11796 new_selections.retain(|s| s.id != last_added_selection);
11797 state.stack.pop();
11798 }
11799
11800 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11801 s.select(new_selections);
11802 });
11803 if state.stack.len() > 1 {
11804 self.add_selections_state = Some(state);
11805 }
11806 }
11807
11808 pub fn select_next_match_internal(
11809 &mut self,
11810 display_map: &DisplaySnapshot,
11811 replace_newest: bool,
11812 autoscroll: Option<Autoscroll>,
11813 window: &mut Window,
11814 cx: &mut Context<Self>,
11815 ) -> Result<()> {
11816 fn select_next_match_ranges(
11817 this: &mut Editor,
11818 range: Range<usize>,
11819 replace_newest: bool,
11820 auto_scroll: Option<Autoscroll>,
11821 window: &mut Window,
11822 cx: &mut Context<Editor>,
11823 ) {
11824 this.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
11825 this.change_selections(auto_scroll, window, cx, |s| {
11826 if replace_newest {
11827 s.delete(s.newest_anchor().id);
11828 }
11829 s.insert_range(range.clone());
11830 });
11831 }
11832
11833 let buffer = &display_map.buffer_snapshot;
11834 let mut selections = self.selections.all::<usize>(cx);
11835 if let Some(mut select_next_state) = self.select_next_state.take() {
11836 let query = &select_next_state.query;
11837 if !select_next_state.done {
11838 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11839 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11840 let mut next_selected_range = None;
11841
11842 let bytes_after_last_selection =
11843 buffer.bytes_in_range(last_selection.end..buffer.len());
11844 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
11845 let query_matches = query
11846 .stream_find_iter(bytes_after_last_selection)
11847 .map(|result| (last_selection.end, result))
11848 .chain(
11849 query
11850 .stream_find_iter(bytes_before_first_selection)
11851 .map(|result| (0, result)),
11852 );
11853
11854 for (start_offset, query_match) in query_matches {
11855 let query_match = query_match.unwrap(); // can only fail due to I/O
11856 let offset_range =
11857 start_offset + query_match.start()..start_offset + query_match.end();
11858 let display_range = offset_range.start.to_display_point(display_map)
11859 ..offset_range.end.to_display_point(display_map);
11860
11861 if !select_next_state.wordwise
11862 || (!movement::is_inside_word(display_map, display_range.start)
11863 && !movement::is_inside_word(display_map, display_range.end))
11864 {
11865 // TODO: This is n^2, because we might check all the selections
11866 if !selections
11867 .iter()
11868 .any(|selection| selection.range().overlaps(&offset_range))
11869 {
11870 next_selected_range = Some(offset_range);
11871 break;
11872 }
11873 }
11874 }
11875
11876 if let Some(next_selected_range) = next_selected_range {
11877 select_next_match_ranges(
11878 self,
11879 next_selected_range,
11880 replace_newest,
11881 autoscroll,
11882 window,
11883 cx,
11884 );
11885 } else {
11886 select_next_state.done = true;
11887 }
11888 }
11889
11890 self.select_next_state = Some(select_next_state);
11891 } else {
11892 let mut only_carets = true;
11893 let mut same_text_selected = true;
11894 let mut selected_text = None;
11895
11896 let mut selections_iter = selections.iter().peekable();
11897 while let Some(selection) = selections_iter.next() {
11898 if selection.start != selection.end {
11899 only_carets = false;
11900 }
11901
11902 if same_text_selected {
11903 if selected_text.is_none() {
11904 selected_text =
11905 Some(buffer.text_for_range(selection.range()).collect::<String>());
11906 }
11907
11908 if let Some(next_selection) = selections_iter.peek() {
11909 if next_selection.range().len() == selection.range().len() {
11910 let next_selected_text = buffer
11911 .text_for_range(next_selection.range())
11912 .collect::<String>();
11913 if Some(next_selected_text) != selected_text {
11914 same_text_selected = false;
11915 selected_text = None;
11916 }
11917 } else {
11918 same_text_selected = false;
11919 selected_text = None;
11920 }
11921 }
11922 }
11923 }
11924
11925 if only_carets {
11926 for selection in &mut selections {
11927 let word_range = movement::surrounding_word(
11928 display_map,
11929 selection.start.to_display_point(display_map),
11930 );
11931 selection.start = word_range.start.to_offset(display_map, Bias::Left);
11932 selection.end = word_range.end.to_offset(display_map, Bias::Left);
11933 selection.goal = SelectionGoal::None;
11934 selection.reversed = false;
11935 select_next_match_ranges(
11936 self,
11937 selection.start..selection.end,
11938 replace_newest,
11939 autoscroll,
11940 window,
11941 cx,
11942 );
11943 }
11944
11945 if selections.len() == 1 {
11946 let selection = selections
11947 .last()
11948 .expect("ensured that there's only one selection");
11949 let query = buffer
11950 .text_for_range(selection.start..selection.end)
11951 .collect::<String>();
11952 let is_empty = query.is_empty();
11953 let select_state = SelectNextState {
11954 query: AhoCorasick::new(&[query])?,
11955 wordwise: true,
11956 done: is_empty,
11957 };
11958 self.select_next_state = Some(select_state);
11959 } else {
11960 self.select_next_state = None;
11961 }
11962 } else if let Some(selected_text) = selected_text {
11963 self.select_next_state = Some(SelectNextState {
11964 query: AhoCorasick::new(&[selected_text])?,
11965 wordwise: false,
11966 done: false,
11967 });
11968 self.select_next_match_internal(
11969 display_map,
11970 replace_newest,
11971 autoscroll,
11972 window,
11973 cx,
11974 )?;
11975 }
11976 }
11977 Ok(())
11978 }
11979
11980 pub fn select_all_matches(
11981 &mut self,
11982 _action: &SelectAllMatches,
11983 window: &mut Window,
11984 cx: &mut Context<Self>,
11985 ) -> Result<()> {
11986 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11987
11988 self.push_to_selection_history();
11989 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11990
11991 self.select_next_match_internal(&display_map, false, None, window, cx)?;
11992 let Some(select_next_state) = self.select_next_state.as_mut() else {
11993 return Ok(());
11994 };
11995 if select_next_state.done {
11996 return Ok(());
11997 }
11998
11999 let mut new_selections = Vec::new();
12000
12001 let reversed = self.selections.oldest::<usize>(cx).reversed;
12002 let buffer = &display_map.buffer_snapshot;
12003 let query_matches = select_next_state
12004 .query
12005 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12006
12007 for query_match in query_matches.into_iter() {
12008 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12009 let offset_range = if reversed {
12010 query_match.end()..query_match.start()
12011 } else {
12012 query_match.start()..query_match.end()
12013 };
12014 let display_range = offset_range.start.to_display_point(&display_map)
12015 ..offset_range.end.to_display_point(&display_map);
12016
12017 if !select_next_state.wordwise
12018 || (!movement::is_inside_word(&display_map, display_range.start)
12019 && !movement::is_inside_word(&display_map, display_range.end))
12020 {
12021 new_selections.push(offset_range.start..offset_range.end);
12022 }
12023 }
12024
12025 select_next_state.done = true;
12026 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12027 self.change_selections(None, window, cx, |selections| {
12028 selections.select_ranges(new_selections)
12029 });
12030
12031 Ok(())
12032 }
12033
12034 pub fn select_next(
12035 &mut self,
12036 action: &SelectNext,
12037 window: &mut Window,
12038 cx: &mut Context<Self>,
12039 ) -> Result<()> {
12040 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12041 self.push_to_selection_history();
12042 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12043 self.select_next_match_internal(
12044 &display_map,
12045 action.replace_newest,
12046 Some(Autoscroll::newest()),
12047 window,
12048 cx,
12049 )?;
12050 Ok(())
12051 }
12052
12053 pub fn select_previous(
12054 &mut self,
12055 action: &SelectPrevious,
12056 window: &mut Window,
12057 cx: &mut Context<Self>,
12058 ) -> Result<()> {
12059 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12060 self.push_to_selection_history();
12061 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12062 let buffer = &display_map.buffer_snapshot;
12063 let mut selections = self.selections.all::<usize>(cx);
12064 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12065 let query = &select_prev_state.query;
12066 if !select_prev_state.done {
12067 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12068 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12069 let mut next_selected_range = None;
12070 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12071 let bytes_before_last_selection =
12072 buffer.reversed_bytes_in_range(0..last_selection.start);
12073 let bytes_after_first_selection =
12074 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12075 let query_matches = query
12076 .stream_find_iter(bytes_before_last_selection)
12077 .map(|result| (last_selection.start, result))
12078 .chain(
12079 query
12080 .stream_find_iter(bytes_after_first_selection)
12081 .map(|result| (buffer.len(), result)),
12082 );
12083 for (end_offset, query_match) in query_matches {
12084 let query_match = query_match.unwrap(); // can only fail due to I/O
12085 let offset_range =
12086 end_offset - query_match.end()..end_offset - query_match.start();
12087 let display_range = offset_range.start.to_display_point(&display_map)
12088 ..offset_range.end.to_display_point(&display_map);
12089
12090 if !select_prev_state.wordwise
12091 || (!movement::is_inside_word(&display_map, display_range.start)
12092 && !movement::is_inside_word(&display_map, display_range.end))
12093 {
12094 next_selected_range = Some(offset_range);
12095 break;
12096 }
12097 }
12098
12099 if let Some(next_selected_range) = next_selected_range {
12100 self.unfold_ranges(&[next_selected_range.clone()], false, true, cx);
12101 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
12102 if action.replace_newest {
12103 s.delete(s.newest_anchor().id);
12104 }
12105 s.insert_range(next_selected_range);
12106 });
12107 } else {
12108 select_prev_state.done = true;
12109 }
12110 }
12111
12112 self.select_prev_state = Some(select_prev_state);
12113 } else {
12114 let mut only_carets = true;
12115 let mut same_text_selected = true;
12116 let mut selected_text = None;
12117
12118 let mut selections_iter = selections.iter().peekable();
12119 while let Some(selection) = selections_iter.next() {
12120 if selection.start != selection.end {
12121 only_carets = false;
12122 }
12123
12124 if same_text_selected {
12125 if selected_text.is_none() {
12126 selected_text =
12127 Some(buffer.text_for_range(selection.range()).collect::<String>());
12128 }
12129
12130 if let Some(next_selection) = selections_iter.peek() {
12131 if next_selection.range().len() == selection.range().len() {
12132 let next_selected_text = buffer
12133 .text_for_range(next_selection.range())
12134 .collect::<String>();
12135 if Some(next_selected_text) != selected_text {
12136 same_text_selected = false;
12137 selected_text = None;
12138 }
12139 } else {
12140 same_text_selected = false;
12141 selected_text = None;
12142 }
12143 }
12144 }
12145 }
12146
12147 if only_carets {
12148 for selection in &mut selections {
12149 let word_range = movement::surrounding_word(
12150 &display_map,
12151 selection.start.to_display_point(&display_map),
12152 );
12153 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12154 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12155 selection.goal = SelectionGoal::None;
12156 selection.reversed = false;
12157 }
12158 if selections.len() == 1 {
12159 let selection = selections
12160 .last()
12161 .expect("ensured that there's only one selection");
12162 let query = buffer
12163 .text_for_range(selection.start..selection.end)
12164 .collect::<String>();
12165 let is_empty = query.is_empty();
12166 let select_state = SelectNextState {
12167 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12168 wordwise: true,
12169 done: is_empty,
12170 };
12171 self.select_prev_state = Some(select_state);
12172 } else {
12173 self.select_prev_state = None;
12174 }
12175
12176 self.unfold_ranges(
12177 &selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
12178 false,
12179 true,
12180 cx,
12181 );
12182 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
12183 s.select(selections);
12184 });
12185 } else if let Some(selected_text) = selected_text {
12186 self.select_prev_state = Some(SelectNextState {
12187 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12188 wordwise: false,
12189 done: false,
12190 });
12191 self.select_previous(action, window, cx)?;
12192 }
12193 }
12194 Ok(())
12195 }
12196
12197 pub fn find_next_match(
12198 &mut self,
12199 _: &FindNextMatch,
12200 window: &mut Window,
12201 cx: &mut Context<Self>,
12202 ) -> Result<()> {
12203 let selections = self.selections.disjoint_anchors();
12204 match selections.first() {
12205 Some(first) if selections.len() >= 2 => {
12206 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12207 s.select_ranges([first.range()]);
12208 });
12209 }
12210 _ => self.select_next(
12211 &SelectNext {
12212 replace_newest: true,
12213 },
12214 window,
12215 cx,
12216 )?,
12217 }
12218 Ok(())
12219 }
12220
12221 pub fn find_previous_match(
12222 &mut self,
12223 _: &FindPreviousMatch,
12224 window: &mut Window,
12225 cx: &mut Context<Self>,
12226 ) -> Result<()> {
12227 let selections = self.selections.disjoint_anchors();
12228 match selections.last() {
12229 Some(last) if selections.len() >= 2 => {
12230 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12231 s.select_ranges([last.range()]);
12232 });
12233 }
12234 _ => self.select_previous(
12235 &SelectPrevious {
12236 replace_newest: true,
12237 },
12238 window,
12239 cx,
12240 )?,
12241 }
12242 Ok(())
12243 }
12244
12245 pub fn toggle_comments(
12246 &mut self,
12247 action: &ToggleComments,
12248 window: &mut Window,
12249 cx: &mut Context<Self>,
12250 ) {
12251 if self.read_only(cx) {
12252 return;
12253 }
12254 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12255 let text_layout_details = &self.text_layout_details(window);
12256 self.transact(window, cx, |this, window, cx| {
12257 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12258 let mut edits = Vec::new();
12259 let mut selection_edit_ranges = Vec::new();
12260 let mut last_toggled_row = None;
12261 let snapshot = this.buffer.read(cx).read(cx);
12262 let empty_str: Arc<str> = Arc::default();
12263 let mut suffixes_inserted = Vec::new();
12264 let ignore_indent = action.ignore_indent;
12265
12266 fn comment_prefix_range(
12267 snapshot: &MultiBufferSnapshot,
12268 row: MultiBufferRow,
12269 comment_prefix: &str,
12270 comment_prefix_whitespace: &str,
12271 ignore_indent: bool,
12272 ) -> Range<Point> {
12273 let indent_size = if ignore_indent {
12274 0
12275 } else {
12276 snapshot.indent_size_for_line(row).len
12277 };
12278
12279 let start = Point::new(row.0, indent_size);
12280
12281 let mut line_bytes = snapshot
12282 .bytes_in_range(start..snapshot.max_point())
12283 .flatten()
12284 .copied();
12285
12286 // If this line currently begins with the line comment prefix, then record
12287 // the range containing the prefix.
12288 if line_bytes
12289 .by_ref()
12290 .take(comment_prefix.len())
12291 .eq(comment_prefix.bytes())
12292 {
12293 // Include any whitespace that matches the comment prefix.
12294 let matching_whitespace_len = line_bytes
12295 .zip(comment_prefix_whitespace.bytes())
12296 .take_while(|(a, b)| a == b)
12297 .count() as u32;
12298 let end = Point::new(
12299 start.row,
12300 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12301 );
12302 start..end
12303 } else {
12304 start..start
12305 }
12306 }
12307
12308 fn comment_suffix_range(
12309 snapshot: &MultiBufferSnapshot,
12310 row: MultiBufferRow,
12311 comment_suffix: &str,
12312 comment_suffix_has_leading_space: bool,
12313 ) -> Range<Point> {
12314 let end = Point::new(row.0, snapshot.line_len(row));
12315 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12316
12317 let mut line_end_bytes = snapshot
12318 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12319 .flatten()
12320 .copied();
12321
12322 let leading_space_len = if suffix_start_column > 0
12323 && line_end_bytes.next() == Some(b' ')
12324 && comment_suffix_has_leading_space
12325 {
12326 1
12327 } else {
12328 0
12329 };
12330
12331 // If this line currently begins with the line comment prefix, then record
12332 // the range containing the prefix.
12333 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12334 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12335 start..end
12336 } else {
12337 end..end
12338 }
12339 }
12340
12341 // TODO: Handle selections that cross excerpts
12342 for selection in &mut selections {
12343 let start_column = snapshot
12344 .indent_size_for_line(MultiBufferRow(selection.start.row))
12345 .len;
12346 let language = if let Some(language) =
12347 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12348 {
12349 language
12350 } else {
12351 continue;
12352 };
12353
12354 selection_edit_ranges.clear();
12355
12356 // If multiple selections contain a given row, avoid processing that
12357 // row more than once.
12358 let mut start_row = MultiBufferRow(selection.start.row);
12359 if last_toggled_row == Some(start_row) {
12360 start_row = start_row.next_row();
12361 }
12362 let end_row =
12363 if selection.end.row > selection.start.row && selection.end.column == 0 {
12364 MultiBufferRow(selection.end.row - 1)
12365 } else {
12366 MultiBufferRow(selection.end.row)
12367 };
12368 last_toggled_row = Some(end_row);
12369
12370 if start_row > end_row {
12371 continue;
12372 }
12373
12374 // If the language has line comments, toggle those.
12375 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12376
12377 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12378 if ignore_indent {
12379 full_comment_prefixes = full_comment_prefixes
12380 .into_iter()
12381 .map(|s| Arc::from(s.trim_end()))
12382 .collect();
12383 }
12384
12385 if !full_comment_prefixes.is_empty() {
12386 let first_prefix = full_comment_prefixes
12387 .first()
12388 .expect("prefixes is non-empty");
12389 let prefix_trimmed_lengths = full_comment_prefixes
12390 .iter()
12391 .map(|p| p.trim_end_matches(' ').len())
12392 .collect::<SmallVec<[usize; 4]>>();
12393
12394 let mut all_selection_lines_are_comments = true;
12395
12396 for row in start_row.0..=end_row.0 {
12397 let row = MultiBufferRow(row);
12398 if start_row < end_row && snapshot.is_line_blank(row) {
12399 continue;
12400 }
12401
12402 let prefix_range = full_comment_prefixes
12403 .iter()
12404 .zip(prefix_trimmed_lengths.iter().copied())
12405 .map(|(prefix, trimmed_prefix_len)| {
12406 comment_prefix_range(
12407 snapshot.deref(),
12408 row,
12409 &prefix[..trimmed_prefix_len],
12410 &prefix[trimmed_prefix_len..],
12411 ignore_indent,
12412 )
12413 })
12414 .max_by_key(|range| range.end.column - range.start.column)
12415 .expect("prefixes is non-empty");
12416
12417 if prefix_range.is_empty() {
12418 all_selection_lines_are_comments = false;
12419 }
12420
12421 selection_edit_ranges.push(prefix_range);
12422 }
12423
12424 if all_selection_lines_are_comments {
12425 edits.extend(
12426 selection_edit_ranges
12427 .iter()
12428 .cloned()
12429 .map(|range| (range, empty_str.clone())),
12430 );
12431 } else {
12432 let min_column = selection_edit_ranges
12433 .iter()
12434 .map(|range| range.start.column)
12435 .min()
12436 .unwrap_or(0);
12437 edits.extend(selection_edit_ranges.iter().map(|range| {
12438 let position = Point::new(range.start.row, min_column);
12439 (position..position, first_prefix.clone())
12440 }));
12441 }
12442 } else if let Some((full_comment_prefix, comment_suffix)) =
12443 language.block_comment_delimiters()
12444 {
12445 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
12446 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
12447 let prefix_range = comment_prefix_range(
12448 snapshot.deref(),
12449 start_row,
12450 comment_prefix,
12451 comment_prefix_whitespace,
12452 ignore_indent,
12453 );
12454 let suffix_range = comment_suffix_range(
12455 snapshot.deref(),
12456 end_row,
12457 comment_suffix.trim_start_matches(' '),
12458 comment_suffix.starts_with(' '),
12459 );
12460
12461 if prefix_range.is_empty() || suffix_range.is_empty() {
12462 edits.push((
12463 prefix_range.start..prefix_range.start,
12464 full_comment_prefix.clone(),
12465 ));
12466 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
12467 suffixes_inserted.push((end_row, comment_suffix.len()));
12468 } else {
12469 edits.push((prefix_range, empty_str.clone()));
12470 edits.push((suffix_range, empty_str.clone()));
12471 }
12472 } else {
12473 continue;
12474 }
12475 }
12476
12477 drop(snapshot);
12478 this.buffer.update(cx, |buffer, cx| {
12479 buffer.edit(edits, None, cx);
12480 });
12481
12482 // Adjust selections so that they end before any comment suffixes that
12483 // were inserted.
12484 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
12485 let mut selections = this.selections.all::<Point>(cx);
12486 let snapshot = this.buffer.read(cx).read(cx);
12487 for selection in &mut selections {
12488 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
12489 match row.cmp(&MultiBufferRow(selection.end.row)) {
12490 Ordering::Less => {
12491 suffixes_inserted.next();
12492 continue;
12493 }
12494 Ordering::Greater => break,
12495 Ordering::Equal => {
12496 if selection.end.column == snapshot.line_len(row) {
12497 if selection.is_empty() {
12498 selection.start.column -= suffix_len as u32;
12499 }
12500 selection.end.column -= suffix_len as u32;
12501 }
12502 break;
12503 }
12504 }
12505 }
12506 }
12507
12508 drop(snapshot);
12509 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12510 s.select(selections)
12511 });
12512
12513 let selections = this.selections.all::<Point>(cx);
12514 let selections_on_single_row = selections.windows(2).all(|selections| {
12515 selections[0].start.row == selections[1].start.row
12516 && selections[0].end.row == selections[1].end.row
12517 && selections[0].start.row == selections[0].end.row
12518 });
12519 let selections_selecting = selections
12520 .iter()
12521 .any(|selection| selection.start != selection.end);
12522 let advance_downwards = action.advance_downwards
12523 && selections_on_single_row
12524 && !selections_selecting
12525 && !matches!(this.mode, EditorMode::SingleLine { .. });
12526
12527 if advance_downwards {
12528 let snapshot = this.buffer.read(cx).snapshot(cx);
12529
12530 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12531 s.move_cursors_with(|display_snapshot, display_point, _| {
12532 let mut point = display_point.to_point(display_snapshot);
12533 point.row += 1;
12534 point = snapshot.clip_point(point, Bias::Left);
12535 let display_point = point.to_display_point(display_snapshot);
12536 let goal = SelectionGoal::HorizontalPosition(
12537 display_snapshot
12538 .x_for_display_point(display_point, text_layout_details)
12539 .into(),
12540 );
12541 (display_point, goal)
12542 })
12543 });
12544 }
12545 });
12546 }
12547
12548 pub fn select_enclosing_symbol(
12549 &mut self,
12550 _: &SelectEnclosingSymbol,
12551 window: &mut Window,
12552 cx: &mut Context<Self>,
12553 ) {
12554 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12555
12556 let buffer = self.buffer.read(cx).snapshot(cx);
12557 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
12558
12559 fn update_selection(
12560 selection: &Selection<usize>,
12561 buffer_snap: &MultiBufferSnapshot,
12562 ) -> Option<Selection<usize>> {
12563 let cursor = selection.head();
12564 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
12565 for symbol in symbols.iter().rev() {
12566 let start = symbol.range.start.to_offset(buffer_snap);
12567 let end = symbol.range.end.to_offset(buffer_snap);
12568 let new_range = start..end;
12569 if start < selection.start || end > selection.end {
12570 return Some(Selection {
12571 id: selection.id,
12572 start: new_range.start,
12573 end: new_range.end,
12574 goal: SelectionGoal::None,
12575 reversed: selection.reversed,
12576 });
12577 }
12578 }
12579 None
12580 }
12581
12582 let mut selected_larger_symbol = false;
12583 let new_selections = old_selections
12584 .iter()
12585 .map(|selection| match update_selection(selection, &buffer) {
12586 Some(new_selection) => {
12587 if new_selection.range() != selection.range() {
12588 selected_larger_symbol = true;
12589 }
12590 new_selection
12591 }
12592 None => selection.clone(),
12593 })
12594 .collect::<Vec<_>>();
12595
12596 if selected_larger_symbol {
12597 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12598 s.select(new_selections);
12599 });
12600 }
12601 }
12602
12603 pub fn select_larger_syntax_node(
12604 &mut self,
12605 _: &SelectLargerSyntaxNode,
12606 window: &mut Window,
12607 cx: &mut Context<Self>,
12608 ) {
12609 let Some(visible_row_count) = self.visible_row_count() else {
12610 return;
12611 };
12612 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
12613 if old_selections.is_empty() {
12614 return;
12615 }
12616
12617 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12618
12619 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12620 let buffer = self.buffer.read(cx).snapshot(cx);
12621
12622 let mut selected_larger_node = false;
12623 let mut new_selections = old_selections
12624 .iter()
12625 .map(|selection| {
12626 let old_range = selection.start..selection.end;
12627
12628 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
12629 // manually select word at selection
12630 if ["string_content", "inline"].contains(&node.kind()) {
12631 let word_range = {
12632 let display_point = buffer
12633 .offset_to_point(old_range.start)
12634 .to_display_point(&display_map);
12635 let Range { start, end } =
12636 movement::surrounding_word(&display_map, display_point);
12637 start.to_point(&display_map).to_offset(&buffer)
12638 ..end.to_point(&display_map).to_offset(&buffer)
12639 };
12640 // ignore if word is already selected
12641 if !word_range.is_empty() && old_range != word_range {
12642 let last_word_range = {
12643 let display_point = buffer
12644 .offset_to_point(old_range.end)
12645 .to_display_point(&display_map);
12646 let Range { start, end } =
12647 movement::surrounding_word(&display_map, display_point);
12648 start.to_point(&display_map).to_offset(&buffer)
12649 ..end.to_point(&display_map).to_offset(&buffer)
12650 };
12651 // only select word if start and end point belongs to same word
12652 if word_range == last_word_range {
12653 selected_larger_node = true;
12654 return Selection {
12655 id: selection.id,
12656 start: word_range.start,
12657 end: word_range.end,
12658 goal: SelectionGoal::None,
12659 reversed: selection.reversed,
12660 };
12661 }
12662 }
12663 }
12664 }
12665
12666 let mut new_range = old_range.clone();
12667 let mut new_node = None;
12668 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
12669 {
12670 new_node = Some(node);
12671 new_range = match containing_range {
12672 MultiOrSingleBufferOffsetRange::Single(_) => break,
12673 MultiOrSingleBufferOffsetRange::Multi(range) => range,
12674 };
12675 if !display_map.intersects_fold(new_range.start)
12676 && !display_map.intersects_fold(new_range.end)
12677 {
12678 break;
12679 }
12680 }
12681
12682 if let Some(node) = new_node {
12683 // Log the ancestor, to support using this action as a way to explore TreeSitter
12684 // nodes. Parent and grandparent are also logged because this operation will not
12685 // visit nodes that have the same range as their parent.
12686 log::info!("Node: {node:?}");
12687 let parent = node.parent();
12688 log::info!("Parent: {parent:?}");
12689 let grandparent = parent.and_then(|x| x.parent());
12690 log::info!("Grandparent: {grandparent:?}");
12691 }
12692
12693 selected_larger_node |= new_range != old_range;
12694 Selection {
12695 id: selection.id,
12696 start: new_range.start,
12697 end: new_range.end,
12698 goal: SelectionGoal::None,
12699 reversed: selection.reversed,
12700 }
12701 })
12702 .collect::<Vec<_>>();
12703
12704 if !selected_larger_node {
12705 return; // don't put this call in the history
12706 }
12707
12708 // scroll based on transformation done to the last selection created by the user
12709 let (last_old, last_new) = old_selections
12710 .last()
12711 .zip(new_selections.last().cloned())
12712 .expect("old_selections isn't empty");
12713
12714 // revert selection
12715 let is_selection_reversed = {
12716 let should_newest_selection_be_reversed = last_old.start != last_new.start;
12717 new_selections.last_mut().expect("checked above").reversed =
12718 should_newest_selection_be_reversed;
12719 should_newest_selection_be_reversed
12720 };
12721
12722 if selected_larger_node {
12723 self.select_syntax_node_history.disable_clearing = true;
12724 self.change_selections(None, window, cx, |s| {
12725 s.select(new_selections.clone());
12726 });
12727 self.select_syntax_node_history.disable_clearing = false;
12728 }
12729
12730 let start_row = last_new.start.to_display_point(&display_map).row().0;
12731 let end_row = last_new.end.to_display_point(&display_map).row().0;
12732 let selection_height = end_row - start_row + 1;
12733 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
12734
12735 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
12736 let scroll_behavior = if fits_on_the_screen {
12737 self.request_autoscroll(Autoscroll::fit(), cx);
12738 SelectSyntaxNodeScrollBehavior::FitSelection
12739 } else if is_selection_reversed {
12740 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
12741 SelectSyntaxNodeScrollBehavior::CursorTop
12742 } else {
12743 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
12744 SelectSyntaxNodeScrollBehavior::CursorBottom
12745 };
12746
12747 self.select_syntax_node_history.push((
12748 old_selections,
12749 scroll_behavior,
12750 is_selection_reversed,
12751 ));
12752 }
12753
12754 pub fn select_smaller_syntax_node(
12755 &mut self,
12756 _: &SelectSmallerSyntaxNode,
12757 window: &mut Window,
12758 cx: &mut Context<Self>,
12759 ) {
12760 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12761
12762 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
12763 self.select_syntax_node_history.pop()
12764 {
12765 if let Some(selection) = selections.last_mut() {
12766 selection.reversed = is_selection_reversed;
12767 }
12768
12769 self.select_syntax_node_history.disable_clearing = true;
12770 self.change_selections(None, window, cx, |s| {
12771 s.select(selections.to_vec());
12772 });
12773 self.select_syntax_node_history.disable_clearing = false;
12774
12775 match scroll_behavior {
12776 SelectSyntaxNodeScrollBehavior::CursorTop => {
12777 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
12778 }
12779 SelectSyntaxNodeScrollBehavior::FitSelection => {
12780 self.request_autoscroll(Autoscroll::fit(), cx);
12781 }
12782 SelectSyntaxNodeScrollBehavior::CursorBottom => {
12783 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
12784 }
12785 }
12786 }
12787 }
12788
12789 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
12790 if !EditorSettings::get_global(cx).gutter.runnables {
12791 self.clear_tasks();
12792 return Task::ready(());
12793 }
12794 let project = self.project.as_ref().map(Entity::downgrade);
12795 let task_sources = self.lsp_task_sources(cx);
12796 cx.spawn_in(window, async move |editor, cx| {
12797 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
12798 let Some(project) = project.and_then(|p| p.upgrade()) else {
12799 return;
12800 };
12801 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
12802 this.display_map.update(cx, |map, cx| map.snapshot(cx))
12803 }) else {
12804 return;
12805 };
12806
12807 let hide_runnables = project
12808 .update(cx, |project, cx| {
12809 // Do not display any test indicators in non-dev server remote projects.
12810 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
12811 })
12812 .unwrap_or(true);
12813 if hide_runnables {
12814 return;
12815 }
12816 let new_rows =
12817 cx.background_spawn({
12818 let snapshot = display_snapshot.clone();
12819 async move {
12820 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
12821 }
12822 })
12823 .await;
12824 let Ok(lsp_tasks) =
12825 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
12826 else {
12827 return;
12828 };
12829 let lsp_tasks = lsp_tasks.await;
12830
12831 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
12832 lsp_tasks
12833 .into_iter()
12834 .flat_map(|(kind, tasks)| {
12835 tasks.into_iter().filter_map(move |(location, task)| {
12836 Some((kind.clone(), location?, task))
12837 })
12838 })
12839 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
12840 let buffer = location.target.buffer;
12841 let buffer_snapshot = buffer.read(cx).snapshot();
12842 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
12843 |(excerpt_id, snapshot, _)| {
12844 if snapshot.remote_id() == buffer_snapshot.remote_id() {
12845 display_snapshot
12846 .buffer_snapshot
12847 .anchor_in_excerpt(excerpt_id, location.target.range.start)
12848 } else {
12849 None
12850 }
12851 },
12852 );
12853 if let Some(offset) = offset {
12854 let task_buffer_range =
12855 location.target.range.to_point(&buffer_snapshot);
12856 let context_buffer_range =
12857 task_buffer_range.to_offset(&buffer_snapshot);
12858 let context_range = BufferOffset(context_buffer_range.start)
12859 ..BufferOffset(context_buffer_range.end);
12860
12861 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
12862 .or_insert_with(|| RunnableTasks {
12863 templates: Vec::new(),
12864 offset,
12865 column: task_buffer_range.start.column,
12866 extra_variables: HashMap::default(),
12867 context_range,
12868 })
12869 .templates
12870 .push((kind, task.original_task().clone()));
12871 }
12872
12873 acc
12874 })
12875 }) else {
12876 return;
12877 };
12878
12879 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
12880 editor
12881 .update(cx, |editor, _| {
12882 editor.clear_tasks();
12883 for (key, mut value) in rows {
12884 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
12885 value.templates.extend(lsp_tasks.templates);
12886 }
12887
12888 editor.insert_tasks(key, value);
12889 }
12890 for (key, value) in lsp_tasks_by_rows {
12891 editor.insert_tasks(key, value);
12892 }
12893 })
12894 .ok();
12895 })
12896 }
12897 fn fetch_runnable_ranges(
12898 snapshot: &DisplaySnapshot,
12899 range: Range<Anchor>,
12900 ) -> Vec<language::RunnableRange> {
12901 snapshot.buffer_snapshot.runnable_ranges(range).collect()
12902 }
12903
12904 fn runnable_rows(
12905 project: Entity<Project>,
12906 snapshot: DisplaySnapshot,
12907 runnable_ranges: Vec<RunnableRange>,
12908 mut cx: AsyncWindowContext,
12909 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
12910 runnable_ranges
12911 .into_iter()
12912 .filter_map(|mut runnable| {
12913 let tasks = cx
12914 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
12915 .ok()?;
12916 if tasks.is_empty() {
12917 return None;
12918 }
12919
12920 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
12921
12922 let row = snapshot
12923 .buffer_snapshot
12924 .buffer_line_for_row(MultiBufferRow(point.row))?
12925 .1
12926 .start
12927 .row;
12928
12929 let context_range =
12930 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
12931 Some((
12932 (runnable.buffer_id, row),
12933 RunnableTasks {
12934 templates: tasks,
12935 offset: snapshot
12936 .buffer_snapshot
12937 .anchor_before(runnable.run_range.start),
12938 context_range,
12939 column: point.column,
12940 extra_variables: runnable.extra_captures,
12941 },
12942 ))
12943 })
12944 .collect()
12945 }
12946
12947 fn templates_with_tags(
12948 project: &Entity<Project>,
12949 runnable: &mut Runnable,
12950 cx: &mut App,
12951 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
12952 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
12953 let (worktree_id, file) = project
12954 .buffer_for_id(runnable.buffer, cx)
12955 .and_then(|buffer| buffer.read(cx).file())
12956 .map(|file| (file.worktree_id(cx), file.clone()))
12957 .unzip();
12958
12959 (
12960 project.task_store().read(cx).task_inventory().cloned(),
12961 worktree_id,
12962 file,
12963 )
12964 });
12965
12966 let mut templates_with_tags = mem::take(&mut runnable.tags)
12967 .into_iter()
12968 .flat_map(|RunnableTag(tag)| {
12969 inventory
12970 .as_ref()
12971 .into_iter()
12972 .flat_map(|inventory| {
12973 inventory.read(cx).list_tasks(
12974 file.clone(),
12975 Some(runnable.language.clone()),
12976 worktree_id,
12977 cx,
12978 )
12979 })
12980 .filter(move |(_, template)| {
12981 template.tags.iter().any(|source_tag| source_tag == &tag)
12982 })
12983 })
12984 .sorted_by_key(|(kind, _)| kind.to_owned())
12985 .collect::<Vec<_>>();
12986 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
12987 // Strongest source wins; if we have worktree tag binding, prefer that to
12988 // global and language bindings;
12989 // if we have a global binding, prefer that to language binding.
12990 let first_mismatch = templates_with_tags
12991 .iter()
12992 .position(|(tag_source, _)| tag_source != leading_tag_source);
12993 if let Some(index) = first_mismatch {
12994 templates_with_tags.truncate(index);
12995 }
12996 }
12997
12998 templates_with_tags
12999 }
13000
13001 pub fn move_to_enclosing_bracket(
13002 &mut self,
13003 _: &MoveToEnclosingBracket,
13004 window: &mut Window,
13005 cx: &mut Context<Self>,
13006 ) {
13007 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13008 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13009 s.move_offsets_with(|snapshot, selection| {
13010 let Some(enclosing_bracket_ranges) =
13011 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13012 else {
13013 return;
13014 };
13015
13016 let mut best_length = usize::MAX;
13017 let mut best_inside = false;
13018 let mut best_in_bracket_range = false;
13019 let mut best_destination = None;
13020 for (open, close) in enclosing_bracket_ranges {
13021 let close = close.to_inclusive();
13022 let length = close.end() - open.start;
13023 let inside = selection.start >= open.end && selection.end <= *close.start();
13024 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13025 || close.contains(&selection.head());
13026
13027 // If best is next to a bracket and current isn't, skip
13028 if !in_bracket_range && best_in_bracket_range {
13029 continue;
13030 }
13031
13032 // Prefer smaller lengths unless best is inside and current isn't
13033 if length > best_length && (best_inside || !inside) {
13034 continue;
13035 }
13036
13037 best_length = length;
13038 best_inside = inside;
13039 best_in_bracket_range = in_bracket_range;
13040 best_destination = Some(
13041 if close.contains(&selection.start) && close.contains(&selection.end) {
13042 if inside { open.end } else { open.start }
13043 } else if inside {
13044 *close.start()
13045 } else {
13046 *close.end()
13047 },
13048 );
13049 }
13050
13051 if let Some(destination) = best_destination {
13052 selection.collapse_to(destination, SelectionGoal::None);
13053 }
13054 })
13055 });
13056 }
13057
13058 pub fn undo_selection(
13059 &mut self,
13060 _: &UndoSelection,
13061 window: &mut Window,
13062 cx: &mut Context<Self>,
13063 ) {
13064 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13065 self.end_selection(window, cx);
13066 self.selection_history.mode = SelectionHistoryMode::Undoing;
13067 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13068 self.change_selections(None, window, cx, |s| {
13069 s.select_anchors(entry.selections.to_vec())
13070 });
13071 self.select_next_state = entry.select_next_state;
13072 self.select_prev_state = entry.select_prev_state;
13073 self.add_selections_state = entry.add_selections_state;
13074 self.request_autoscroll(Autoscroll::newest(), cx);
13075 }
13076 self.selection_history.mode = SelectionHistoryMode::Normal;
13077 }
13078
13079 pub fn redo_selection(
13080 &mut self,
13081 _: &RedoSelection,
13082 window: &mut Window,
13083 cx: &mut Context<Self>,
13084 ) {
13085 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13086 self.end_selection(window, cx);
13087 self.selection_history.mode = SelectionHistoryMode::Redoing;
13088 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13089 self.change_selections(None, window, cx, |s| {
13090 s.select_anchors(entry.selections.to_vec())
13091 });
13092 self.select_next_state = entry.select_next_state;
13093 self.select_prev_state = entry.select_prev_state;
13094 self.add_selections_state = entry.add_selections_state;
13095 self.request_autoscroll(Autoscroll::newest(), cx);
13096 }
13097 self.selection_history.mode = SelectionHistoryMode::Normal;
13098 }
13099
13100 pub fn expand_excerpts(
13101 &mut self,
13102 action: &ExpandExcerpts,
13103 _: &mut Window,
13104 cx: &mut Context<Self>,
13105 ) {
13106 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13107 }
13108
13109 pub fn expand_excerpts_down(
13110 &mut self,
13111 action: &ExpandExcerptsDown,
13112 _: &mut Window,
13113 cx: &mut Context<Self>,
13114 ) {
13115 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13116 }
13117
13118 pub fn expand_excerpts_up(
13119 &mut self,
13120 action: &ExpandExcerptsUp,
13121 _: &mut Window,
13122 cx: &mut Context<Self>,
13123 ) {
13124 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13125 }
13126
13127 pub fn expand_excerpts_for_direction(
13128 &mut self,
13129 lines: u32,
13130 direction: ExpandExcerptDirection,
13131
13132 cx: &mut Context<Self>,
13133 ) {
13134 let selections = self.selections.disjoint_anchors();
13135
13136 let lines = if lines == 0 {
13137 EditorSettings::get_global(cx).expand_excerpt_lines
13138 } else {
13139 lines
13140 };
13141
13142 self.buffer.update(cx, |buffer, cx| {
13143 let snapshot = buffer.snapshot(cx);
13144 let mut excerpt_ids = selections
13145 .iter()
13146 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13147 .collect::<Vec<_>>();
13148 excerpt_ids.sort();
13149 excerpt_ids.dedup();
13150 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13151 })
13152 }
13153
13154 pub fn expand_excerpt(
13155 &mut self,
13156 excerpt: ExcerptId,
13157 direction: ExpandExcerptDirection,
13158 window: &mut Window,
13159 cx: &mut Context<Self>,
13160 ) {
13161 let current_scroll_position = self.scroll_position(cx);
13162 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13163 let mut should_scroll_up = false;
13164
13165 if direction == ExpandExcerptDirection::Down {
13166 let multi_buffer = self.buffer.read(cx);
13167 let snapshot = multi_buffer.snapshot(cx);
13168 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13169 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13170 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13171 let buffer_snapshot = buffer.read(cx).snapshot();
13172 let excerpt_end_row =
13173 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13174 let last_row = buffer_snapshot.max_point().row;
13175 let lines_below = last_row.saturating_sub(excerpt_end_row);
13176 should_scroll_up = lines_below >= lines_to_expand;
13177 }
13178 }
13179 }
13180 }
13181
13182 self.buffer.update(cx, |buffer, cx| {
13183 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13184 });
13185
13186 if should_scroll_up {
13187 let new_scroll_position =
13188 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13189 self.set_scroll_position(new_scroll_position, window, cx);
13190 }
13191 }
13192
13193 pub fn go_to_singleton_buffer_point(
13194 &mut self,
13195 point: Point,
13196 window: &mut Window,
13197 cx: &mut Context<Self>,
13198 ) {
13199 self.go_to_singleton_buffer_range(point..point, window, cx);
13200 }
13201
13202 pub fn go_to_singleton_buffer_range(
13203 &mut self,
13204 range: Range<Point>,
13205 window: &mut Window,
13206 cx: &mut Context<Self>,
13207 ) {
13208 let multibuffer = self.buffer().read(cx);
13209 let Some(buffer) = multibuffer.as_singleton() else {
13210 return;
13211 };
13212 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13213 return;
13214 };
13215 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13216 return;
13217 };
13218 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13219 s.select_anchor_ranges([start..end])
13220 });
13221 }
13222
13223 pub fn go_to_diagnostic(
13224 &mut self,
13225 _: &GoToDiagnostic,
13226 window: &mut Window,
13227 cx: &mut Context<Self>,
13228 ) {
13229 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13230 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13231 }
13232
13233 pub fn go_to_prev_diagnostic(
13234 &mut self,
13235 _: &GoToPreviousDiagnostic,
13236 window: &mut Window,
13237 cx: &mut Context<Self>,
13238 ) {
13239 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13240 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13241 }
13242
13243 pub fn go_to_diagnostic_impl(
13244 &mut self,
13245 direction: Direction,
13246 window: &mut Window,
13247 cx: &mut Context<Self>,
13248 ) {
13249 let buffer = self.buffer.read(cx).snapshot(cx);
13250 let selection = self.selections.newest::<usize>(cx);
13251
13252 let mut active_group_id = None;
13253 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13254 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13255 active_group_id = Some(active_group.group_id);
13256 }
13257 }
13258
13259 fn filtered(
13260 snapshot: EditorSnapshot,
13261 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13262 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13263 diagnostics
13264 .filter(|entry| entry.range.start != entry.range.end)
13265 .filter(|entry| !entry.diagnostic.is_unnecessary)
13266 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13267 }
13268
13269 let snapshot = self.snapshot(window, cx);
13270 let before = filtered(
13271 snapshot.clone(),
13272 buffer
13273 .diagnostics_in_range(0..selection.start)
13274 .filter(|entry| entry.range.start <= selection.start),
13275 );
13276 let after = filtered(
13277 snapshot,
13278 buffer
13279 .diagnostics_in_range(selection.start..buffer.len())
13280 .filter(|entry| entry.range.start >= selection.start),
13281 );
13282
13283 let mut found: Option<DiagnosticEntry<usize>> = None;
13284 if direction == Direction::Prev {
13285 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13286 {
13287 for diagnostic in prev_diagnostics.into_iter().rev() {
13288 if diagnostic.range.start != selection.start
13289 || active_group_id
13290 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13291 {
13292 found = Some(diagnostic);
13293 break 'outer;
13294 }
13295 }
13296 }
13297 } else {
13298 for diagnostic in after.chain(before) {
13299 if diagnostic.range.start != selection.start
13300 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13301 {
13302 found = Some(diagnostic);
13303 break;
13304 }
13305 }
13306 }
13307 let Some(next_diagnostic) = found else {
13308 return;
13309 };
13310
13311 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13312 return;
13313 };
13314 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13315 s.select_ranges(vec![
13316 next_diagnostic.range.start..next_diagnostic.range.start,
13317 ])
13318 });
13319 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13320 self.refresh_inline_completion(false, true, window, cx);
13321 }
13322
13323 fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13324 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13325 let snapshot = self.snapshot(window, cx);
13326 let selection = self.selections.newest::<Point>(cx);
13327 self.go_to_hunk_before_or_after_position(
13328 &snapshot,
13329 selection.head(),
13330 Direction::Next,
13331 window,
13332 cx,
13333 );
13334 }
13335
13336 pub fn go_to_hunk_before_or_after_position(
13337 &mut self,
13338 snapshot: &EditorSnapshot,
13339 position: Point,
13340 direction: Direction,
13341 window: &mut Window,
13342 cx: &mut Context<Editor>,
13343 ) {
13344 let row = if direction == Direction::Next {
13345 self.hunk_after_position(snapshot, position)
13346 .map(|hunk| hunk.row_range.start)
13347 } else {
13348 self.hunk_before_position(snapshot, position)
13349 };
13350
13351 if let Some(row) = row {
13352 let destination = Point::new(row.0, 0);
13353 let autoscroll = Autoscroll::center();
13354
13355 self.unfold_ranges(&[destination..destination], false, false, cx);
13356 self.change_selections(Some(autoscroll), window, cx, |s| {
13357 s.select_ranges([destination..destination]);
13358 });
13359 }
13360 }
13361
13362 fn hunk_after_position(
13363 &mut self,
13364 snapshot: &EditorSnapshot,
13365 position: Point,
13366 ) -> Option<MultiBufferDiffHunk> {
13367 snapshot
13368 .buffer_snapshot
13369 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13370 .find(|hunk| hunk.row_range.start.0 > position.row)
13371 .or_else(|| {
13372 snapshot
13373 .buffer_snapshot
13374 .diff_hunks_in_range(Point::zero()..position)
13375 .find(|hunk| hunk.row_range.end.0 < position.row)
13376 })
13377 }
13378
13379 fn go_to_prev_hunk(
13380 &mut self,
13381 _: &GoToPreviousHunk,
13382 window: &mut Window,
13383 cx: &mut Context<Self>,
13384 ) {
13385 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13386 let snapshot = self.snapshot(window, cx);
13387 let selection = self.selections.newest::<Point>(cx);
13388 self.go_to_hunk_before_or_after_position(
13389 &snapshot,
13390 selection.head(),
13391 Direction::Prev,
13392 window,
13393 cx,
13394 );
13395 }
13396
13397 fn hunk_before_position(
13398 &mut self,
13399 snapshot: &EditorSnapshot,
13400 position: Point,
13401 ) -> Option<MultiBufferRow> {
13402 snapshot
13403 .buffer_snapshot
13404 .diff_hunk_before(position)
13405 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
13406 }
13407
13408 fn go_to_next_change(
13409 &mut self,
13410 _: &GoToNextChange,
13411 window: &mut Window,
13412 cx: &mut Context<Self>,
13413 ) {
13414 if let Some(selections) = self
13415 .change_list
13416 .next_change(1, Direction::Next)
13417 .map(|s| s.to_vec())
13418 {
13419 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13420 let map = s.display_map();
13421 s.select_display_ranges(selections.iter().map(|a| {
13422 let point = a.to_display_point(&map);
13423 point..point
13424 }))
13425 })
13426 }
13427 }
13428
13429 fn go_to_previous_change(
13430 &mut self,
13431 _: &GoToPreviousChange,
13432 window: &mut Window,
13433 cx: &mut Context<Self>,
13434 ) {
13435 if let Some(selections) = self
13436 .change_list
13437 .next_change(1, Direction::Prev)
13438 .map(|s| s.to_vec())
13439 {
13440 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13441 let map = s.display_map();
13442 s.select_display_ranges(selections.iter().map(|a| {
13443 let point = a.to_display_point(&map);
13444 point..point
13445 }))
13446 })
13447 }
13448 }
13449
13450 fn go_to_line<T: 'static>(
13451 &mut self,
13452 position: Anchor,
13453 highlight_color: Option<Hsla>,
13454 window: &mut Window,
13455 cx: &mut Context<Self>,
13456 ) {
13457 let snapshot = self.snapshot(window, cx).display_snapshot;
13458 let position = position.to_point(&snapshot.buffer_snapshot);
13459 let start = snapshot
13460 .buffer_snapshot
13461 .clip_point(Point::new(position.row, 0), Bias::Left);
13462 let end = start + Point::new(1, 0);
13463 let start = snapshot.buffer_snapshot.anchor_before(start);
13464 let end = snapshot.buffer_snapshot.anchor_before(end);
13465
13466 self.highlight_rows::<T>(
13467 start..end,
13468 highlight_color
13469 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
13470 false,
13471 cx,
13472 );
13473 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
13474 }
13475
13476 pub fn go_to_definition(
13477 &mut self,
13478 _: &GoToDefinition,
13479 window: &mut Window,
13480 cx: &mut Context<Self>,
13481 ) -> Task<Result<Navigated>> {
13482 let definition =
13483 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
13484 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
13485 cx.spawn_in(window, async move |editor, cx| {
13486 if definition.await? == Navigated::Yes {
13487 return Ok(Navigated::Yes);
13488 }
13489 match fallback_strategy {
13490 GoToDefinitionFallback::None => Ok(Navigated::No),
13491 GoToDefinitionFallback::FindAllReferences => {
13492 match editor.update_in(cx, |editor, window, cx| {
13493 editor.find_all_references(&FindAllReferences, window, cx)
13494 })? {
13495 Some(references) => references.await,
13496 None => Ok(Navigated::No),
13497 }
13498 }
13499 }
13500 })
13501 }
13502
13503 pub fn go_to_declaration(
13504 &mut self,
13505 _: &GoToDeclaration,
13506 window: &mut Window,
13507 cx: &mut Context<Self>,
13508 ) -> Task<Result<Navigated>> {
13509 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
13510 }
13511
13512 pub fn go_to_declaration_split(
13513 &mut self,
13514 _: &GoToDeclaration,
13515 window: &mut Window,
13516 cx: &mut Context<Self>,
13517 ) -> Task<Result<Navigated>> {
13518 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
13519 }
13520
13521 pub fn go_to_implementation(
13522 &mut self,
13523 _: &GoToImplementation,
13524 window: &mut Window,
13525 cx: &mut Context<Self>,
13526 ) -> Task<Result<Navigated>> {
13527 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
13528 }
13529
13530 pub fn go_to_implementation_split(
13531 &mut self,
13532 _: &GoToImplementationSplit,
13533 window: &mut Window,
13534 cx: &mut Context<Self>,
13535 ) -> Task<Result<Navigated>> {
13536 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
13537 }
13538
13539 pub fn go_to_type_definition(
13540 &mut self,
13541 _: &GoToTypeDefinition,
13542 window: &mut Window,
13543 cx: &mut Context<Self>,
13544 ) -> Task<Result<Navigated>> {
13545 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
13546 }
13547
13548 pub fn go_to_definition_split(
13549 &mut self,
13550 _: &GoToDefinitionSplit,
13551 window: &mut Window,
13552 cx: &mut Context<Self>,
13553 ) -> Task<Result<Navigated>> {
13554 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
13555 }
13556
13557 pub fn go_to_type_definition_split(
13558 &mut self,
13559 _: &GoToTypeDefinitionSplit,
13560 window: &mut Window,
13561 cx: &mut Context<Self>,
13562 ) -> Task<Result<Navigated>> {
13563 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
13564 }
13565
13566 fn go_to_definition_of_kind(
13567 &mut self,
13568 kind: GotoDefinitionKind,
13569 split: bool,
13570 window: &mut Window,
13571 cx: &mut Context<Self>,
13572 ) -> Task<Result<Navigated>> {
13573 let Some(provider) = self.semantics_provider.clone() else {
13574 return Task::ready(Ok(Navigated::No));
13575 };
13576 let head = self.selections.newest::<usize>(cx).head();
13577 let buffer = self.buffer.read(cx);
13578 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
13579 text_anchor
13580 } else {
13581 return Task::ready(Ok(Navigated::No));
13582 };
13583
13584 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
13585 return Task::ready(Ok(Navigated::No));
13586 };
13587
13588 cx.spawn_in(window, async move |editor, cx| {
13589 let definitions = definitions.await?;
13590 let navigated = editor
13591 .update_in(cx, |editor, window, cx| {
13592 editor.navigate_to_hover_links(
13593 Some(kind),
13594 definitions
13595 .into_iter()
13596 .filter(|location| {
13597 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
13598 })
13599 .map(HoverLink::Text)
13600 .collect::<Vec<_>>(),
13601 split,
13602 window,
13603 cx,
13604 )
13605 })?
13606 .await?;
13607 anyhow::Ok(navigated)
13608 })
13609 }
13610
13611 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
13612 let selection = self.selections.newest_anchor();
13613 let head = selection.head();
13614 let tail = selection.tail();
13615
13616 let Some((buffer, start_position)) =
13617 self.buffer.read(cx).text_anchor_for_position(head, cx)
13618 else {
13619 return;
13620 };
13621
13622 let end_position = if head != tail {
13623 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
13624 return;
13625 };
13626 Some(pos)
13627 } else {
13628 None
13629 };
13630
13631 let url_finder = cx.spawn_in(window, async move |editor, cx| {
13632 let url = if let Some(end_pos) = end_position {
13633 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
13634 } else {
13635 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
13636 };
13637
13638 if let Some(url) = url {
13639 editor.update(cx, |_, cx| {
13640 cx.open_url(&url);
13641 })
13642 } else {
13643 Ok(())
13644 }
13645 });
13646
13647 url_finder.detach();
13648 }
13649
13650 pub fn open_selected_filename(
13651 &mut self,
13652 _: &OpenSelectedFilename,
13653 window: &mut Window,
13654 cx: &mut Context<Self>,
13655 ) {
13656 let Some(workspace) = self.workspace() else {
13657 return;
13658 };
13659
13660 let position = self.selections.newest_anchor().head();
13661
13662 let Some((buffer, buffer_position)) =
13663 self.buffer.read(cx).text_anchor_for_position(position, cx)
13664 else {
13665 return;
13666 };
13667
13668 let project = self.project.clone();
13669
13670 cx.spawn_in(window, async move |_, cx| {
13671 let result = find_file(&buffer, project, buffer_position, cx).await;
13672
13673 if let Some((_, path)) = result {
13674 workspace
13675 .update_in(cx, |workspace, window, cx| {
13676 workspace.open_resolved_path(path, window, cx)
13677 })?
13678 .await?;
13679 }
13680 anyhow::Ok(())
13681 })
13682 .detach();
13683 }
13684
13685 pub(crate) fn navigate_to_hover_links(
13686 &mut self,
13687 kind: Option<GotoDefinitionKind>,
13688 mut definitions: Vec<HoverLink>,
13689 split: bool,
13690 window: &mut Window,
13691 cx: &mut Context<Editor>,
13692 ) -> Task<Result<Navigated>> {
13693 // If there is one definition, just open it directly
13694 if definitions.len() == 1 {
13695 let definition = definitions.pop().unwrap();
13696
13697 enum TargetTaskResult {
13698 Location(Option<Location>),
13699 AlreadyNavigated,
13700 }
13701
13702 let target_task = match definition {
13703 HoverLink::Text(link) => {
13704 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
13705 }
13706 HoverLink::InlayHint(lsp_location, server_id) => {
13707 let computation =
13708 self.compute_target_location(lsp_location, server_id, window, cx);
13709 cx.background_spawn(async move {
13710 let location = computation.await?;
13711 Ok(TargetTaskResult::Location(location))
13712 })
13713 }
13714 HoverLink::Url(url) => {
13715 cx.open_url(&url);
13716 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
13717 }
13718 HoverLink::File(path) => {
13719 if let Some(workspace) = self.workspace() {
13720 cx.spawn_in(window, async move |_, cx| {
13721 workspace
13722 .update_in(cx, |workspace, window, cx| {
13723 workspace.open_resolved_path(path, window, cx)
13724 })?
13725 .await
13726 .map(|_| TargetTaskResult::AlreadyNavigated)
13727 })
13728 } else {
13729 Task::ready(Ok(TargetTaskResult::Location(None)))
13730 }
13731 }
13732 };
13733 cx.spawn_in(window, async move |editor, cx| {
13734 let target = match target_task.await.context("target resolution task")? {
13735 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
13736 TargetTaskResult::Location(None) => return Ok(Navigated::No),
13737 TargetTaskResult::Location(Some(target)) => target,
13738 };
13739
13740 editor.update_in(cx, |editor, window, cx| {
13741 let Some(workspace) = editor.workspace() else {
13742 return Navigated::No;
13743 };
13744 let pane = workspace.read(cx).active_pane().clone();
13745
13746 let range = target.range.to_point(target.buffer.read(cx));
13747 let range = editor.range_for_match(&range);
13748 let range = collapse_multiline_range(range);
13749
13750 if !split
13751 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
13752 {
13753 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
13754 } else {
13755 window.defer(cx, move |window, cx| {
13756 let target_editor: Entity<Self> =
13757 workspace.update(cx, |workspace, cx| {
13758 let pane = if split {
13759 workspace.adjacent_pane(window, cx)
13760 } else {
13761 workspace.active_pane().clone()
13762 };
13763
13764 workspace.open_project_item(
13765 pane,
13766 target.buffer.clone(),
13767 true,
13768 true,
13769 window,
13770 cx,
13771 )
13772 });
13773 target_editor.update(cx, |target_editor, cx| {
13774 // When selecting a definition in a different buffer, disable the nav history
13775 // to avoid creating a history entry at the previous cursor location.
13776 pane.update(cx, |pane, _| pane.disable_history());
13777 target_editor.go_to_singleton_buffer_range(range, window, cx);
13778 pane.update(cx, |pane, _| pane.enable_history());
13779 });
13780 });
13781 }
13782 Navigated::Yes
13783 })
13784 })
13785 } else if !definitions.is_empty() {
13786 cx.spawn_in(window, async move |editor, cx| {
13787 let (title, location_tasks, workspace) = editor
13788 .update_in(cx, |editor, window, cx| {
13789 let tab_kind = match kind {
13790 Some(GotoDefinitionKind::Implementation) => "Implementations",
13791 _ => "Definitions",
13792 };
13793 let title = definitions
13794 .iter()
13795 .find_map(|definition| match definition {
13796 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
13797 let buffer = origin.buffer.read(cx);
13798 format!(
13799 "{} for {}",
13800 tab_kind,
13801 buffer
13802 .text_for_range(origin.range.clone())
13803 .collect::<String>()
13804 )
13805 }),
13806 HoverLink::InlayHint(_, _) => None,
13807 HoverLink::Url(_) => None,
13808 HoverLink::File(_) => None,
13809 })
13810 .unwrap_or(tab_kind.to_string());
13811 let location_tasks = definitions
13812 .into_iter()
13813 .map(|definition| match definition {
13814 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
13815 HoverLink::InlayHint(lsp_location, server_id) => editor
13816 .compute_target_location(lsp_location, server_id, window, cx),
13817 HoverLink::Url(_) => Task::ready(Ok(None)),
13818 HoverLink::File(_) => Task::ready(Ok(None)),
13819 })
13820 .collect::<Vec<_>>();
13821 (title, location_tasks, editor.workspace().clone())
13822 })
13823 .context("location tasks preparation")?;
13824
13825 let locations = future::join_all(location_tasks)
13826 .await
13827 .into_iter()
13828 .filter_map(|location| location.transpose())
13829 .collect::<Result<_>>()
13830 .context("location tasks")?;
13831
13832 let Some(workspace) = workspace else {
13833 return Ok(Navigated::No);
13834 };
13835 let opened = workspace
13836 .update_in(cx, |workspace, window, cx| {
13837 Self::open_locations_in_multibuffer(
13838 workspace,
13839 locations,
13840 title,
13841 split,
13842 MultibufferSelectionMode::First,
13843 window,
13844 cx,
13845 )
13846 })
13847 .ok();
13848
13849 anyhow::Ok(Navigated::from_bool(opened.is_some()))
13850 })
13851 } else {
13852 Task::ready(Ok(Navigated::No))
13853 }
13854 }
13855
13856 fn compute_target_location(
13857 &self,
13858 lsp_location: lsp::Location,
13859 server_id: LanguageServerId,
13860 window: &mut Window,
13861 cx: &mut Context<Self>,
13862 ) -> Task<anyhow::Result<Option<Location>>> {
13863 let Some(project) = self.project.clone() else {
13864 return Task::ready(Ok(None));
13865 };
13866
13867 cx.spawn_in(window, async move |editor, cx| {
13868 let location_task = editor.update(cx, |_, cx| {
13869 project.update(cx, |project, cx| {
13870 let language_server_name = project
13871 .language_server_statuses(cx)
13872 .find(|(id, _)| server_id == *id)
13873 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
13874 language_server_name.map(|language_server_name| {
13875 project.open_local_buffer_via_lsp(
13876 lsp_location.uri.clone(),
13877 server_id,
13878 language_server_name,
13879 cx,
13880 )
13881 })
13882 })
13883 })?;
13884 let location = match location_task {
13885 Some(task) => Some({
13886 let target_buffer_handle = task.await.context("open local buffer")?;
13887 let range = target_buffer_handle.update(cx, |target_buffer, _| {
13888 let target_start = target_buffer
13889 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
13890 let target_end = target_buffer
13891 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
13892 target_buffer.anchor_after(target_start)
13893 ..target_buffer.anchor_before(target_end)
13894 })?;
13895 Location {
13896 buffer: target_buffer_handle,
13897 range,
13898 }
13899 }),
13900 None => None,
13901 };
13902 Ok(location)
13903 })
13904 }
13905
13906 pub fn find_all_references(
13907 &mut self,
13908 _: &FindAllReferences,
13909 window: &mut Window,
13910 cx: &mut Context<Self>,
13911 ) -> Option<Task<Result<Navigated>>> {
13912 let selection = self.selections.newest::<usize>(cx);
13913 let multi_buffer = self.buffer.read(cx);
13914 let head = selection.head();
13915
13916 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
13917 let head_anchor = multi_buffer_snapshot.anchor_at(
13918 head,
13919 if head < selection.tail() {
13920 Bias::Right
13921 } else {
13922 Bias::Left
13923 },
13924 );
13925
13926 match self
13927 .find_all_references_task_sources
13928 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13929 {
13930 Ok(_) => {
13931 log::info!(
13932 "Ignoring repeated FindAllReferences invocation with the position of already running task"
13933 );
13934 return None;
13935 }
13936 Err(i) => {
13937 self.find_all_references_task_sources.insert(i, head_anchor);
13938 }
13939 }
13940
13941 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
13942 let workspace = self.workspace()?;
13943 let project = workspace.read(cx).project().clone();
13944 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
13945 Some(cx.spawn_in(window, async move |editor, cx| {
13946 let _cleanup = cx.on_drop(&editor, move |editor, _| {
13947 if let Ok(i) = editor
13948 .find_all_references_task_sources
13949 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13950 {
13951 editor.find_all_references_task_sources.remove(i);
13952 }
13953 });
13954
13955 let locations = references.await?;
13956 if locations.is_empty() {
13957 return anyhow::Ok(Navigated::No);
13958 }
13959
13960 workspace.update_in(cx, |workspace, window, cx| {
13961 let title = locations
13962 .first()
13963 .as_ref()
13964 .map(|location| {
13965 let buffer = location.buffer.read(cx);
13966 format!(
13967 "References to `{}`",
13968 buffer
13969 .text_for_range(location.range.clone())
13970 .collect::<String>()
13971 )
13972 })
13973 .unwrap();
13974 Self::open_locations_in_multibuffer(
13975 workspace,
13976 locations,
13977 title,
13978 false,
13979 MultibufferSelectionMode::First,
13980 window,
13981 cx,
13982 );
13983 Navigated::Yes
13984 })
13985 }))
13986 }
13987
13988 /// Opens a multibuffer with the given project locations in it
13989 pub fn open_locations_in_multibuffer(
13990 workspace: &mut Workspace,
13991 mut locations: Vec<Location>,
13992 title: String,
13993 split: bool,
13994 multibuffer_selection_mode: MultibufferSelectionMode,
13995 window: &mut Window,
13996 cx: &mut Context<Workspace>,
13997 ) {
13998 // If there are multiple definitions, open them in a multibuffer
13999 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14000 let mut locations = locations.into_iter().peekable();
14001 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14002 let capability = workspace.project().read(cx).capability();
14003
14004 let excerpt_buffer = cx.new(|cx| {
14005 let mut multibuffer = MultiBuffer::new(capability);
14006 while let Some(location) = locations.next() {
14007 let buffer = location.buffer.read(cx);
14008 let mut ranges_for_buffer = Vec::new();
14009 let range = location.range.to_point(buffer);
14010 ranges_for_buffer.push(range.clone());
14011
14012 while let Some(next_location) = locations.peek() {
14013 if next_location.buffer == location.buffer {
14014 ranges_for_buffer.push(next_location.range.to_point(buffer));
14015 locations.next();
14016 } else {
14017 break;
14018 }
14019 }
14020
14021 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14022 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14023 PathKey::for_buffer(&location.buffer, cx),
14024 location.buffer.clone(),
14025 ranges_for_buffer,
14026 DEFAULT_MULTIBUFFER_CONTEXT,
14027 cx,
14028 );
14029 ranges.extend(new_ranges)
14030 }
14031
14032 multibuffer.with_title(title)
14033 });
14034
14035 let editor = cx.new(|cx| {
14036 Editor::for_multibuffer(
14037 excerpt_buffer,
14038 Some(workspace.project().clone()),
14039 window,
14040 cx,
14041 )
14042 });
14043 editor.update(cx, |editor, cx| {
14044 match multibuffer_selection_mode {
14045 MultibufferSelectionMode::First => {
14046 if let Some(first_range) = ranges.first() {
14047 editor.change_selections(None, window, cx, |selections| {
14048 selections.clear_disjoint();
14049 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14050 });
14051 }
14052 editor.highlight_background::<Self>(
14053 &ranges,
14054 |theme| theme.editor_highlighted_line_background,
14055 cx,
14056 );
14057 }
14058 MultibufferSelectionMode::All => {
14059 editor.change_selections(None, window, cx, |selections| {
14060 selections.clear_disjoint();
14061 selections.select_anchor_ranges(ranges);
14062 });
14063 }
14064 }
14065 editor.register_buffers_with_language_servers(cx);
14066 });
14067
14068 let item = Box::new(editor);
14069 let item_id = item.item_id();
14070
14071 if split {
14072 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14073 } else {
14074 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14075 let (preview_item_id, preview_item_idx) =
14076 workspace.active_pane().update(cx, |pane, _| {
14077 (pane.preview_item_id(), pane.preview_item_idx())
14078 });
14079
14080 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14081
14082 if let Some(preview_item_id) = preview_item_id {
14083 workspace.active_pane().update(cx, |pane, cx| {
14084 pane.remove_item(preview_item_id, false, false, window, cx);
14085 });
14086 }
14087 } else {
14088 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14089 }
14090 }
14091 workspace.active_pane().update(cx, |pane, cx| {
14092 pane.set_preview_item_id(Some(item_id), cx);
14093 });
14094 }
14095
14096 pub fn rename(
14097 &mut self,
14098 _: &Rename,
14099 window: &mut Window,
14100 cx: &mut Context<Self>,
14101 ) -> Option<Task<Result<()>>> {
14102 use language::ToOffset as _;
14103
14104 let provider = self.semantics_provider.clone()?;
14105 let selection = self.selections.newest_anchor().clone();
14106 let (cursor_buffer, cursor_buffer_position) = self
14107 .buffer
14108 .read(cx)
14109 .text_anchor_for_position(selection.head(), cx)?;
14110 let (tail_buffer, cursor_buffer_position_end) = self
14111 .buffer
14112 .read(cx)
14113 .text_anchor_for_position(selection.tail(), cx)?;
14114 if tail_buffer != cursor_buffer {
14115 return None;
14116 }
14117
14118 let snapshot = cursor_buffer.read(cx).snapshot();
14119 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14120 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14121 let prepare_rename = provider
14122 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14123 .unwrap_or_else(|| Task::ready(Ok(None)));
14124 drop(snapshot);
14125
14126 Some(cx.spawn_in(window, async move |this, cx| {
14127 let rename_range = if let Some(range) = prepare_rename.await? {
14128 Some(range)
14129 } else {
14130 this.update(cx, |this, cx| {
14131 let buffer = this.buffer.read(cx).snapshot(cx);
14132 let mut buffer_highlights = this
14133 .document_highlights_for_position(selection.head(), &buffer)
14134 .filter(|highlight| {
14135 highlight.start.excerpt_id == selection.head().excerpt_id
14136 && highlight.end.excerpt_id == selection.head().excerpt_id
14137 });
14138 buffer_highlights
14139 .next()
14140 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14141 })?
14142 };
14143 if let Some(rename_range) = rename_range {
14144 this.update_in(cx, |this, window, cx| {
14145 let snapshot = cursor_buffer.read(cx).snapshot();
14146 let rename_buffer_range = rename_range.to_offset(&snapshot);
14147 let cursor_offset_in_rename_range =
14148 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14149 let cursor_offset_in_rename_range_end =
14150 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14151
14152 this.take_rename(false, window, cx);
14153 let buffer = this.buffer.read(cx).read(cx);
14154 let cursor_offset = selection.head().to_offset(&buffer);
14155 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14156 let rename_end = rename_start + rename_buffer_range.len();
14157 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14158 let mut old_highlight_id = None;
14159 let old_name: Arc<str> = buffer
14160 .chunks(rename_start..rename_end, true)
14161 .map(|chunk| {
14162 if old_highlight_id.is_none() {
14163 old_highlight_id = chunk.syntax_highlight_id;
14164 }
14165 chunk.text
14166 })
14167 .collect::<String>()
14168 .into();
14169
14170 drop(buffer);
14171
14172 // Position the selection in the rename editor so that it matches the current selection.
14173 this.show_local_selections = false;
14174 let rename_editor = cx.new(|cx| {
14175 let mut editor = Editor::single_line(window, cx);
14176 editor.buffer.update(cx, |buffer, cx| {
14177 buffer.edit([(0..0, old_name.clone())], None, cx)
14178 });
14179 let rename_selection_range = match cursor_offset_in_rename_range
14180 .cmp(&cursor_offset_in_rename_range_end)
14181 {
14182 Ordering::Equal => {
14183 editor.select_all(&SelectAll, window, cx);
14184 return editor;
14185 }
14186 Ordering::Less => {
14187 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14188 }
14189 Ordering::Greater => {
14190 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14191 }
14192 };
14193 if rename_selection_range.end > old_name.len() {
14194 editor.select_all(&SelectAll, window, cx);
14195 } else {
14196 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14197 s.select_ranges([rename_selection_range]);
14198 });
14199 }
14200 editor
14201 });
14202 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14203 if e == &EditorEvent::Focused {
14204 cx.emit(EditorEvent::FocusedIn)
14205 }
14206 })
14207 .detach();
14208
14209 let write_highlights =
14210 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14211 let read_highlights =
14212 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14213 let ranges = write_highlights
14214 .iter()
14215 .flat_map(|(_, ranges)| ranges.iter())
14216 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14217 .cloned()
14218 .collect();
14219
14220 this.highlight_text::<Rename>(
14221 ranges,
14222 HighlightStyle {
14223 fade_out: Some(0.6),
14224 ..Default::default()
14225 },
14226 cx,
14227 );
14228 let rename_focus_handle = rename_editor.focus_handle(cx);
14229 window.focus(&rename_focus_handle);
14230 let block_id = this.insert_blocks(
14231 [BlockProperties {
14232 style: BlockStyle::Flex,
14233 placement: BlockPlacement::Below(range.start),
14234 height: Some(1),
14235 render: Arc::new({
14236 let rename_editor = rename_editor.clone();
14237 move |cx: &mut BlockContext| {
14238 let mut text_style = cx.editor_style.text.clone();
14239 if let Some(highlight_style) = old_highlight_id
14240 .and_then(|h| h.style(&cx.editor_style.syntax))
14241 {
14242 text_style = text_style.highlight(highlight_style);
14243 }
14244 div()
14245 .block_mouse_down()
14246 .pl(cx.anchor_x)
14247 .child(EditorElement::new(
14248 &rename_editor,
14249 EditorStyle {
14250 background: cx.theme().system().transparent,
14251 local_player: cx.editor_style.local_player,
14252 text: text_style,
14253 scrollbar_width: cx.editor_style.scrollbar_width,
14254 syntax: cx.editor_style.syntax.clone(),
14255 status: cx.editor_style.status.clone(),
14256 inlay_hints_style: HighlightStyle {
14257 font_weight: Some(FontWeight::BOLD),
14258 ..make_inlay_hints_style(cx.app)
14259 },
14260 inline_completion_styles: make_suggestion_styles(
14261 cx.app,
14262 ),
14263 ..EditorStyle::default()
14264 },
14265 ))
14266 .into_any_element()
14267 }
14268 }),
14269 priority: 0,
14270 }],
14271 Some(Autoscroll::fit()),
14272 cx,
14273 )[0];
14274 this.pending_rename = Some(RenameState {
14275 range,
14276 old_name,
14277 editor: rename_editor,
14278 block_id,
14279 });
14280 })?;
14281 }
14282
14283 Ok(())
14284 }))
14285 }
14286
14287 pub fn confirm_rename(
14288 &mut self,
14289 _: &ConfirmRename,
14290 window: &mut Window,
14291 cx: &mut Context<Self>,
14292 ) -> Option<Task<Result<()>>> {
14293 let rename = self.take_rename(false, window, cx)?;
14294 let workspace = self.workspace()?.downgrade();
14295 let (buffer, start) = self
14296 .buffer
14297 .read(cx)
14298 .text_anchor_for_position(rename.range.start, cx)?;
14299 let (end_buffer, _) = self
14300 .buffer
14301 .read(cx)
14302 .text_anchor_for_position(rename.range.end, cx)?;
14303 if buffer != end_buffer {
14304 return None;
14305 }
14306
14307 let old_name = rename.old_name;
14308 let new_name = rename.editor.read(cx).text(cx);
14309
14310 let rename = self.semantics_provider.as_ref()?.perform_rename(
14311 &buffer,
14312 start,
14313 new_name.clone(),
14314 cx,
14315 )?;
14316
14317 Some(cx.spawn_in(window, async move |editor, cx| {
14318 let project_transaction = rename.await?;
14319 Self::open_project_transaction(
14320 &editor,
14321 workspace,
14322 project_transaction,
14323 format!("Rename: {} → {}", old_name, new_name),
14324 cx,
14325 )
14326 .await?;
14327
14328 editor.update(cx, |editor, cx| {
14329 editor.refresh_document_highlights(cx);
14330 })?;
14331 Ok(())
14332 }))
14333 }
14334
14335 fn take_rename(
14336 &mut self,
14337 moving_cursor: bool,
14338 window: &mut Window,
14339 cx: &mut Context<Self>,
14340 ) -> Option<RenameState> {
14341 let rename = self.pending_rename.take()?;
14342 if rename.editor.focus_handle(cx).is_focused(window) {
14343 window.focus(&self.focus_handle);
14344 }
14345
14346 self.remove_blocks(
14347 [rename.block_id].into_iter().collect(),
14348 Some(Autoscroll::fit()),
14349 cx,
14350 );
14351 self.clear_highlights::<Rename>(cx);
14352 self.show_local_selections = true;
14353
14354 if moving_cursor {
14355 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14356 editor.selections.newest::<usize>(cx).head()
14357 });
14358
14359 // Update the selection to match the position of the selection inside
14360 // the rename editor.
14361 let snapshot = self.buffer.read(cx).read(cx);
14362 let rename_range = rename.range.to_offset(&snapshot);
14363 let cursor_in_editor = snapshot
14364 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14365 .min(rename_range.end);
14366 drop(snapshot);
14367
14368 self.change_selections(None, window, cx, |s| {
14369 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14370 });
14371 } else {
14372 self.refresh_document_highlights(cx);
14373 }
14374
14375 Some(rename)
14376 }
14377
14378 pub fn pending_rename(&self) -> Option<&RenameState> {
14379 self.pending_rename.as_ref()
14380 }
14381
14382 fn format(
14383 &mut self,
14384 _: &Format,
14385 window: &mut Window,
14386 cx: &mut Context<Self>,
14387 ) -> Option<Task<Result<()>>> {
14388 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14389
14390 let project = match &self.project {
14391 Some(project) => project.clone(),
14392 None => return None,
14393 };
14394
14395 Some(self.perform_format(
14396 project,
14397 FormatTrigger::Manual,
14398 FormatTarget::Buffers,
14399 window,
14400 cx,
14401 ))
14402 }
14403
14404 fn format_selections(
14405 &mut self,
14406 _: &FormatSelections,
14407 window: &mut Window,
14408 cx: &mut Context<Self>,
14409 ) -> Option<Task<Result<()>>> {
14410 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14411
14412 let project = match &self.project {
14413 Some(project) => project.clone(),
14414 None => return None,
14415 };
14416
14417 let ranges = self
14418 .selections
14419 .all_adjusted(cx)
14420 .into_iter()
14421 .map(|selection| selection.range())
14422 .collect_vec();
14423
14424 Some(self.perform_format(
14425 project,
14426 FormatTrigger::Manual,
14427 FormatTarget::Ranges(ranges),
14428 window,
14429 cx,
14430 ))
14431 }
14432
14433 fn perform_format(
14434 &mut self,
14435 project: Entity<Project>,
14436 trigger: FormatTrigger,
14437 target: FormatTarget,
14438 window: &mut Window,
14439 cx: &mut Context<Self>,
14440 ) -> Task<Result<()>> {
14441 let buffer = self.buffer.clone();
14442 let (buffers, target) = match target {
14443 FormatTarget::Buffers => {
14444 let mut buffers = buffer.read(cx).all_buffers();
14445 if trigger == FormatTrigger::Save {
14446 buffers.retain(|buffer| buffer.read(cx).is_dirty());
14447 }
14448 (buffers, LspFormatTarget::Buffers)
14449 }
14450 FormatTarget::Ranges(selection_ranges) => {
14451 let multi_buffer = buffer.read(cx);
14452 let snapshot = multi_buffer.read(cx);
14453 let mut buffers = HashSet::default();
14454 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
14455 BTreeMap::new();
14456 for selection_range in selection_ranges {
14457 for (buffer, buffer_range, _) in
14458 snapshot.range_to_buffer_ranges(selection_range)
14459 {
14460 let buffer_id = buffer.remote_id();
14461 let start = buffer.anchor_before(buffer_range.start);
14462 let end = buffer.anchor_after(buffer_range.end);
14463 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
14464 buffer_id_to_ranges
14465 .entry(buffer_id)
14466 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
14467 .or_insert_with(|| vec![start..end]);
14468 }
14469 }
14470 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
14471 }
14472 };
14473
14474 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
14475 let selections_prev = transaction_id_prev
14476 .and_then(|transaction_id_prev| {
14477 // default to selections as they were after the last edit, if we have them,
14478 // instead of how they are now.
14479 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
14480 // will take you back to where you made the last edit, instead of staying where you scrolled
14481 self.selection_history
14482 .transaction(transaction_id_prev)
14483 .map(|t| t.0.clone())
14484 })
14485 .unwrap_or_else(|| {
14486 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
14487 self.selections.disjoint_anchors()
14488 });
14489
14490 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
14491 let format = project.update(cx, |project, cx| {
14492 project.format(buffers, target, true, trigger, cx)
14493 });
14494
14495 cx.spawn_in(window, async move |editor, cx| {
14496 let transaction = futures::select_biased! {
14497 transaction = format.log_err().fuse() => transaction,
14498 () = timeout => {
14499 log::warn!("timed out waiting for formatting");
14500 None
14501 }
14502 };
14503
14504 buffer
14505 .update(cx, |buffer, cx| {
14506 if let Some(transaction) = transaction {
14507 if !buffer.is_singleton() {
14508 buffer.push_transaction(&transaction.0, cx);
14509 }
14510 }
14511 cx.notify();
14512 })
14513 .ok();
14514
14515 if let Some(transaction_id_now) =
14516 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
14517 {
14518 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
14519 if has_new_transaction {
14520 _ = editor.update(cx, |editor, _| {
14521 editor
14522 .selection_history
14523 .insert_transaction(transaction_id_now, selections_prev);
14524 });
14525 }
14526 }
14527
14528 Ok(())
14529 })
14530 }
14531
14532 fn organize_imports(
14533 &mut self,
14534 _: &OrganizeImports,
14535 window: &mut Window,
14536 cx: &mut Context<Self>,
14537 ) -> Option<Task<Result<()>>> {
14538 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14539 let project = match &self.project {
14540 Some(project) => project.clone(),
14541 None => return None,
14542 };
14543 Some(self.perform_code_action_kind(
14544 project,
14545 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
14546 window,
14547 cx,
14548 ))
14549 }
14550
14551 fn perform_code_action_kind(
14552 &mut self,
14553 project: Entity<Project>,
14554 kind: CodeActionKind,
14555 window: &mut Window,
14556 cx: &mut Context<Self>,
14557 ) -> Task<Result<()>> {
14558 let buffer = self.buffer.clone();
14559 let buffers = buffer.read(cx).all_buffers();
14560 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
14561 let apply_action = project.update(cx, |project, cx| {
14562 project.apply_code_action_kind(buffers, kind, true, cx)
14563 });
14564 cx.spawn_in(window, async move |_, cx| {
14565 let transaction = futures::select_biased! {
14566 () = timeout => {
14567 log::warn!("timed out waiting for executing code action");
14568 None
14569 }
14570 transaction = apply_action.log_err().fuse() => transaction,
14571 };
14572 buffer
14573 .update(cx, |buffer, cx| {
14574 // check if we need this
14575 if let Some(transaction) = transaction {
14576 if !buffer.is_singleton() {
14577 buffer.push_transaction(&transaction.0, cx);
14578 }
14579 }
14580 cx.notify();
14581 })
14582 .ok();
14583 Ok(())
14584 })
14585 }
14586
14587 fn restart_language_server(
14588 &mut self,
14589 _: &RestartLanguageServer,
14590 _: &mut Window,
14591 cx: &mut Context<Self>,
14592 ) {
14593 if let Some(project) = self.project.clone() {
14594 self.buffer.update(cx, |multi_buffer, cx| {
14595 project.update(cx, |project, cx| {
14596 project.restart_language_servers_for_buffers(
14597 multi_buffer.all_buffers().into_iter().collect(),
14598 cx,
14599 );
14600 });
14601 })
14602 }
14603 }
14604
14605 fn stop_language_server(
14606 &mut self,
14607 _: &StopLanguageServer,
14608 _: &mut Window,
14609 cx: &mut Context<Self>,
14610 ) {
14611 if let Some(project) = self.project.clone() {
14612 self.buffer.update(cx, |multi_buffer, cx| {
14613 project.update(cx, |project, cx| {
14614 project.stop_language_servers_for_buffers(
14615 multi_buffer.all_buffers().into_iter().collect(),
14616 cx,
14617 );
14618 cx.emit(project::Event::RefreshInlayHints);
14619 });
14620 });
14621 }
14622 }
14623
14624 fn cancel_language_server_work(
14625 workspace: &mut Workspace,
14626 _: &actions::CancelLanguageServerWork,
14627 _: &mut Window,
14628 cx: &mut Context<Workspace>,
14629 ) {
14630 let project = workspace.project();
14631 let buffers = workspace
14632 .active_item(cx)
14633 .and_then(|item| item.act_as::<Editor>(cx))
14634 .map_or(HashSet::default(), |editor| {
14635 editor.read(cx).buffer.read(cx).all_buffers()
14636 });
14637 project.update(cx, |project, cx| {
14638 project.cancel_language_server_work_for_buffers(buffers, cx);
14639 });
14640 }
14641
14642 fn show_character_palette(
14643 &mut self,
14644 _: &ShowCharacterPalette,
14645 window: &mut Window,
14646 _: &mut Context<Self>,
14647 ) {
14648 window.show_character_palette();
14649 }
14650
14651 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
14652 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
14653 let buffer = self.buffer.read(cx).snapshot(cx);
14654 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
14655 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
14656 let is_valid = buffer
14657 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
14658 .any(|entry| {
14659 entry.diagnostic.is_primary
14660 && !entry.range.is_empty()
14661 && entry.range.start == primary_range_start
14662 && entry.diagnostic.message == active_diagnostics.active_message
14663 });
14664
14665 if !is_valid {
14666 self.dismiss_diagnostics(cx);
14667 }
14668 }
14669 }
14670
14671 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
14672 match &self.active_diagnostics {
14673 ActiveDiagnostic::Group(group) => Some(group),
14674 _ => None,
14675 }
14676 }
14677
14678 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
14679 self.dismiss_diagnostics(cx);
14680 self.active_diagnostics = ActiveDiagnostic::All;
14681 }
14682
14683 fn activate_diagnostics(
14684 &mut self,
14685 buffer_id: BufferId,
14686 diagnostic: DiagnosticEntry<usize>,
14687 window: &mut Window,
14688 cx: &mut Context<Self>,
14689 ) {
14690 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
14691 return;
14692 }
14693 self.dismiss_diagnostics(cx);
14694 let snapshot = self.snapshot(window, cx);
14695 let Some(diagnostic_renderer) = cx
14696 .try_global::<GlobalDiagnosticRenderer>()
14697 .map(|g| g.0.clone())
14698 else {
14699 return;
14700 };
14701 let buffer = self.buffer.read(cx).snapshot(cx);
14702
14703 let diagnostic_group = buffer
14704 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
14705 .collect::<Vec<_>>();
14706
14707 let blocks = diagnostic_renderer.render_group(
14708 diagnostic_group,
14709 buffer_id,
14710 snapshot,
14711 cx.weak_entity(),
14712 cx,
14713 );
14714
14715 let blocks = self.display_map.update(cx, |display_map, cx| {
14716 display_map.insert_blocks(blocks, cx).into_iter().collect()
14717 });
14718 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
14719 active_range: buffer.anchor_before(diagnostic.range.start)
14720 ..buffer.anchor_after(diagnostic.range.end),
14721 active_message: diagnostic.diagnostic.message.clone(),
14722 group_id: diagnostic.diagnostic.group_id,
14723 blocks,
14724 });
14725 cx.notify();
14726 }
14727
14728 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
14729 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
14730 return;
14731 };
14732
14733 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
14734 if let ActiveDiagnostic::Group(group) = prev {
14735 self.display_map.update(cx, |display_map, cx| {
14736 display_map.remove_blocks(group.blocks, cx);
14737 });
14738 cx.notify();
14739 }
14740 }
14741
14742 /// Disable inline diagnostics rendering for this editor.
14743 pub fn disable_inline_diagnostics(&mut self) {
14744 self.inline_diagnostics_enabled = false;
14745 self.inline_diagnostics_update = Task::ready(());
14746 self.inline_diagnostics.clear();
14747 }
14748
14749 pub fn inline_diagnostics_enabled(&self) -> bool {
14750 self.inline_diagnostics_enabled
14751 }
14752
14753 pub fn show_inline_diagnostics(&self) -> bool {
14754 self.show_inline_diagnostics
14755 }
14756
14757 pub fn toggle_inline_diagnostics(
14758 &mut self,
14759 _: &ToggleInlineDiagnostics,
14760 window: &mut Window,
14761 cx: &mut Context<Editor>,
14762 ) {
14763 self.show_inline_diagnostics = !self.show_inline_diagnostics;
14764 self.refresh_inline_diagnostics(false, window, cx);
14765 }
14766
14767 fn refresh_inline_diagnostics(
14768 &mut self,
14769 debounce: bool,
14770 window: &mut Window,
14771 cx: &mut Context<Self>,
14772 ) {
14773 if !self.inline_diagnostics_enabled || !self.show_inline_diagnostics {
14774 self.inline_diagnostics_update = Task::ready(());
14775 self.inline_diagnostics.clear();
14776 return;
14777 }
14778
14779 let debounce_ms = ProjectSettings::get_global(cx)
14780 .diagnostics
14781 .inline
14782 .update_debounce_ms;
14783 let debounce = if debounce && debounce_ms > 0 {
14784 Some(Duration::from_millis(debounce_ms))
14785 } else {
14786 None
14787 };
14788 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
14789 let editor = editor.upgrade().unwrap();
14790
14791 if let Some(debounce) = debounce {
14792 cx.background_executor().timer(debounce).await;
14793 }
14794 let Some(snapshot) = editor
14795 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
14796 .ok()
14797 else {
14798 return;
14799 };
14800
14801 let new_inline_diagnostics = cx
14802 .background_spawn(async move {
14803 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
14804 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
14805 let message = diagnostic_entry
14806 .diagnostic
14807 .message
14808 .split_once('\n')
14809 .map(|(line, _)| line)
14810 .map(SharedString::new)
14811 .unwrap_or_else(|| {
14812 SharedString::from(diagnostic_entry.diagnostic.message)
14813 });
14814 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
14815 let (Ok(i) | Err(i)) = inline_diagnostics
14816 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
14817 inline_diagnostics.insert(
14818 i,
14819 (
14820 start_anchor,
14821 InlineDiagnostic {
14822 message,
14823 group_id: diagnostic_entry.diagnostic.group_id,
14824 start: diagnostic_entry.range.start.to_point(&snapshot),
14825 is_primary: diagnostic_entry.diagnostic.is_primary,
14826 severity: diagnostic_entry.diagnostic.severity,
14827 },
14828 ),
14829 );
14830 }
14831 inline_diagnostics
14832 })
14833 .await;
14834
14835 editor
14836 .update(cx, |editor, cx| {
14837 editor.inline_diagnostics = new_inline_diagnostics;
14838 cx.notify();
14839 })
14840 .ok();
14841 });
14842 }
14843
14844 pub fn set_selections_from_remote(
14845 &mut self,
14846 selections: Vec<Selection<Anchor>>,
14847 pending_selection: Option<Selection<Anchor>>,
14848 window: &mut Window,
14849 cx: &mut Context<Self>,
14850 ) {
14851 let old_cursor_position = self.selections.newest_anchor().head();
14852 self.selections.change_with(cx, |s| {
14853 s.select_anchors(selections);
14854 if let Some(pending_selection) = pending_selection {
14855 s.set_pending(pending_selection, SelectMode::Character);
14856 } else {
14857 s.clear_pending();
14858 }
14859 });
14860 self.selections_did_change(false, &old_cursor_position, true, window, cx);
14861 }
14862
14863 fn push_to_selection_history(&mut self) {
14864 self.selection_history.push(SelectionHistoryEntry {
14865 selections: self.selections.disjoint_anchors(),
14866 select_next_state: self.select_next_state.clone(),
14867 select_prev_state: self.select_prev_state.clone(),
14868 add_selections_state: self.add_selections_state.clone(),
14869 });
14870 }
14871
14872 pub fn transact(
14873 &mut self,
14874 window: &mut Window,
14875 cx: &mut Context<Self>,
14876 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
14877 ) -> Option<TransactionId> {
14878 self.start_transaction_at(Instant::now(), window, cx);
14879 update(self, window, cx);
14880 self.end_transaction_at(Instant::now(), cx)
14881 }
14882
14883 pub fn start_transaction_at(
14884 &mut self,
14885 now: Instant,
14886 window: &mut Window,
14887 cx: &mut Context<Self>,
14888 ) {
14889 self.end_selection(window, cx);
14890 if let Some(tx_id) = self
14891 .buffer
14892 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
14893 {
14894 self.selection_history
14895 .insert_transaction(tx_id, self.selections.disjoint_anchors());
14896 cx.emit(EditorEvent::TransactionBegun {
14897 transaction_id: tx_id,
14898 })
14899 }
14900 }
14901
14902 pub fn end_transaction_at(
14903 &mut self,
14904 now: Instant,
14905 cx: &mut Context<Self>,
14906 ) -> Option<TransactionId> {
14907 if let Some(transaction_id) = self
14908 .buffer
14909 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
14910 {
14911 if let Some((_, end_selections)) =
14912 self.selection_history.transaction_mut(transaction_id)
14913 {
14914 *end_selections = Some(self.selections.disjoint_anchors());
14915 } else {
14916 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
14917 }
14918
14919 cx.emit(EditorEvent::Edited { transaction_id });
14920 Some(transaction_id)
14921 } else {
14922 None
14923 }
14924 }
14925
14926 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
14927 if self.selection_mark_mode {
14928 self.change_selections(None, window, cx, |s| {
14929 s.move_with(|_, sel| {
14930 sel.collapse_to(sel.head(), SelectionGoal::None);
14931 });
14932 })
14933 }
14934 self.selection_mark_mode = true;
14935 cx.notify();
14936 }
14937
14938 pub fn swap_selection_ends(
14939 &mut self,
14940 _: &actions::SwapSelectionEnds,
14941 window: &mut Window,
14942 cx: &mut Context<Self>,
14943 ) {
14944 self.change_selections(None, window, cx, |s| {
14945 s.move_with(|_, sel| {
14946 if sel.start != sel.end {
14947 sel.reversed = !sel.reversed
14948 }
14949 });
14950 });
14951 self.request_autoscroll(Autoscroll::newest(), cx);
14952 cx.notify();
14953 }
14954
14955 pub fn toggle_fold(
14956 &mut self,
14957 _: &actions::ToggleFold,
14958 window: &mut Window,
14959 cx: &mut Context<Self>,
14960 ) {
14961 if self.is_singleton(cx) {
14962 let selection = self.selections.newest::<Point>(cx);
14963
14964 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14965 let range = if selection.is_empty() {
14966 let point = selection.head().to_display_point(&display_map);
14967 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14968 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14969 .to_point(&display_map);
14970 start..end
14971 } else {
14972 selection.range()
14973 };
14974 if display_map.folds_in_range(range).next().is_some() {
14975 self.unfold_lines(&Default::default(), window, cx)
14976 } else {
14977 self.fold(&Default::default(), window, cx)
14978 }
14979 } else {
14980 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14981 let buffer_ids: HashSet<_> = self
14982 .selections
14983 .disjoint_anchor_ranges()
14984 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14985 .collect();
14986
14987 let should_unfold = buffer_ids
14988 .iter()
14989 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
14990
14991 for buffer_id in buffer_ids {
14992 if should_unfold {
14993 self.unfold_buffer(buffer_id, cx);
14994 } else {
14995 self.fold_buffer(buffer_id, cx);
14996 }
14997 }
14998 }
14999 }
15000
15001 pub fn toggle_fold_recursive(
15002 &mut self,
15003 _: &actions::ToggleFoldRecursive,
15004 window: &mut Window,
15005 cx: &mut Context<Self>,
15006 ) {
15007 let selection = self.selections.newest::<Point>(cx);
15008
15009 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15010 let range = if selection.is_empty() {
15011 let point = selection.head().to_display_point(&display_map);
15012 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15013 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15014 .to_point(&display_map);
15015 start..end
15016 } else {
15017 selection.range()
15018 };
15019 if display_map.folds_in_range(range).next().is_some() {
15020 self.unfold_recursive(&Default::default(), window, cx)
15021 } else {
15022 self.fold_recursive(&Default::default(), window, cx)
15023 }
15024 }
15025
15026 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15027 if self.is_singleton(cx) {
15028 let mut to_fold = Vec::new();
15029 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15030 let selections = self.selections.all_adjusted(cx);
15031
15032 for selection in selections {
15033 let range = selection.range().sorted();
15034 let buffer_start_row = range.start.row;
15035
15036 if range.start.row != range.end.row {
15037 let mut found = false;
15038 let mut row = range.start.row;
15039 while row <= range.end.row {
15040 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15041 {
15042 found = true;
15043 row = crease.range().end.row + 1;
15044 to_fold.push(crease);
15045 } else {
15046 row += 1
15047 }
15048 }
15049 if found {
15050 continue;
15051 }
15052 }
15053
15054 for row in (0..=range.start.row).rev() {
15055 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15056 if crease.range().end.row >= buffer_start_row {
15057 to_fold.push(crease);
15058 if row <= range.start.row {
15059 break;
15060 }
15061 }
15062 }
15063 }
15064 }
15065
15066 self.fold_creases(to_fold, true, window, cx);
15067 } else {
15068 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15069 let buffer_ids = self
15070 .selections
15071 .disjoint_anchor_ranges()
15072 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15073 .collect::<HashSet<_>>();
15074 for buffer_id in buffer_ids {
15075 self.fold_buffer(buffer_id, cx);
15076 }
15077 }
15078 }
15079
15080 fn fold_at_level(
15081 &mut self,
15082 fold_at: &FoldAtLevel,
15083 window: &mut Window,
15084 cx: &mut Context<Self>,
15085 ) {
15086 if !self.buffer.read(cx).is_singleton() {
15087 return;
15088 }
15089
15090 let fold_at_level = fold_at.0;
15091 let snapshot = self.buffer.read(cx).snapshot(cx);
15092 let mut to_fold = Vec::new();
15093 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15094
15095 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15096 while start_row < end_row {
15097 match self
15098 .snapshot(window, cx)
15099 .crease_for_buffer_row(MultiBufferRow(start_row))
15100 {
15101 Some(crease) => {
15102 let nested_start_row = crease.range().start.row + 1;
15103 let nested_end_row = crease.range().end.row;
15104
15105 if current_level < fold_at_level {
15106 stack.push((nested_start_row, nested_end_row, current_level + 1));
15107 } else if current_level == fold_at_level {
15108 to_fold.push(crease);
15109 }
15110
15111 start_row = nested_end_row + 1;
15112 }
15113 None => start_row += 1,
15114 }
15115 }
15116 }
15117
15118 self.fold_creases(to_fold, true, window, cx);
15119 }
15120
15121 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15122 if self.buffer.read(cx).is_singleton() {
15123 let mut fold_ranges = Vec::new();
15124 let snapshot = self.buffer.read(cx).snapshot(cx);
15125
15126 for row in 0..snapshot.max_row().0 {
15127 if let Some(foldable_range) = self
15128 .snapshot(window, cx)
15129 .crease_for_buffer_row(MultiBufferRow(row))
15130 {
15131 fold_ranges.push(foldable_range);
15132 }
15133 }
15134
15135 self.fold_creases(fold_ranges, true, window, cx);
15136 } else {
15137 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15138 editor
15139 .update_in(cx, |editor, _, cx| {
15140 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15141 editor.fold_buffer(buffer_id, cx);
15142 }
15143 })
15144 .ok();
15145 });
15146 }
15147 }
15148
15149 pub fn fold_function_bodies(
15150 &mut self,
15151 _: &actions::FoldFunctionBodies,
15152 window: &mut Window,
15153 cx: &mut Context<Self>,
15154 ) {
15155 let snapshot = self.buffer.read(cx).snapshot(cx);
15156
15157 let ranges = snapshot
15158 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15159 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15160 .collect::<Vec<_>>();
15161
15162 let creases = ranges
15163 .into_iter()
15164 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15165 .collect();
15166
15167 self.fold_creases(creases, true, window, cx);
15168 }
15169
15170 pub fn fold_recursive(
15171 &mut self,
15172 _: &actions::FoldRecursive,
15173 window: &mut Window,
15174 cx: &mut Context<Self>,
15175 ) {
15176 let mut to_fold = Vec::new();
15177 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15178 let selections = self.selections.all_adjusted(cx);
15179
15180 for selection in selections {
15181 let range = selection.range().sorted();
15182 let buffer_start_row = range.start.row;
15183
15184 if range.start.row != range.end.row {
15185 let mut found = false;
15186 for row in range.start.row..=range.end.row {
15187 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15188 found = true;
15189 to_fold.push(crease);
15190 }
15191 }
15192 if found {
15193 continue;
15194 }
15195 }
15196
15197 for row in (0..=range.start.row).rev() {
15198 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15199 if crease.range().end.row >= buffer_start_row {
15200 to_fold.push(crease);
15201 } else {
15202 break;
15203 }
15204 }
15205 }
15206 }
15207
15208 self.fold_creases(to_fold, true, window, cx);
15209 }
15210
15211 pub fn fold_at(
15212 &mut self,
15213 buffer_row: MultiBufferRow,
15214 window: &mut Window,
15215 cx: &mut Context<Self>,
15216 ) {
15217 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15218
15219 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15220 let autoscroll = self
15221 .selections
15222 .all::<Point>(cx)
15223 .iter()
15224 .any(|selection| crease.range().overlaps(&selection.range()));
15225
15226 self.fold_creases(vec![crease], autoscroll, window, cx);
15227 }
15228 }
15229
15230 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15231 if self.is_singleton(cx) {
15232 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15233 let buffer = &display_map.buffer_snapshot;
15234 let selections = self.selections.all::<Point>(cx);
15235 let ranges = selections
15236 .iter()
15237 .map(|s| {
15238 let range = s.display_range(&display_map).sorted();
15239 let mut start = range.start.to_point(&display_map);
15240 let mut end = range.end.to_point(&display_map);
15241 start.column = 0;
15242 end.column = buffer.line_len(MultiBufferRow(end.row));
15243 start..end
15244 })
15245 .collect::<Vec<_>>();
15246
15247 self.unfold_ranges(&ranges, true, true, cx);
15248 } else {
15249 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15250 let buffer_ids = self
15251 .selections
15252 .disjoint_anchor_ranges()
15253 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15254 .collect::<HashSet<_>>();
15255 for buffer_id in buffer_ids {
15256 self.unfold_buffer(buffer_id, cx);
15257 }
15258 }
15259 }
15260
15261 pub fn unfold_recursive(
15262 &mut self,
15263 _: &UnfoldRecursive,
15264 _window: &mut Window,
15265 cx: &mut Context<Self>,
15266 ) {
15267 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15268 let selections = self.selections.all::<Point>(cx);
15269 let ranges = selections
15270 .iter()
15271 .map(|s| {
15272 let mut range = s.display_range(&display_map).sorted();
15273 *range.start.column_mut() = 0;
15274 *range.end.column_mut() = display_map.line_len(range.end.row());
15275 let start = range.start.to_point(&display_map);
15276 let end = range.end.to_point(&display_map);
15277 start..end
15278 })
15279 .collect::<Vec<_>>();
15280
15281 self.unfold_ranges(&ranges, true, true, cx);
15282 }
15283
15284 pub fn unfold_at(
15285 &mut self,
15286 buffer_row: MultiBufferRow,
15287 _window: &mut Window,
15288 cx: &mut Context<Self>,
15289 ) {
15290 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15291
15292 let intersection_range = Point::new(buffer_row.0, 0)
15293 ..Point::new(
15294 buffer_row.0,
15295 display_map.buffer_snapshot.line_len(buffer_row),
15296 );
15297
15298 let autoscroll = self
15299 .selections
15300 .all::<Point>(cx)
15301 .iter()
15302 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15303
15304 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15305 }
15306
15307 pub fn unfold_all(
15308 &mut self,
15309 _: &actions::UnfoldAll,
15310 _window: &mut Window,
15311 cx: &mut Context<Self>,
15312 ) {
15313 if self.buffer.read(cx).is_singleton() {
15314 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15315 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15316 } else {
15317 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15318 editor
15319 .update(cx, |editor, cx| {
15320 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15321 editor.unfold_buffer(buffer_id, cx);
15322 }
15323 })
15324 .ok();
15325 });
15326 }
15327 }
15328
15329 pub fn fold_selected_ranges(
15330 &mut self,
15331 _: &FoldSelectedRanges,
15332 window: &mut Window,
15333 cx: &mut Context<Self>,
15334 ) {
15335 let selections = self.selections.all_adjusted(cx);
15336 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15337 let ranges = selections
15338 .into_iter()
15339 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
15340 .collect::<Vec<_>>();
15341 self.fold_creases(ranges, true, window, cx);
15342 }
15343
15344 pub fn fold_ranges<T: ToOffset + Clone>(
15345 &mut self,
15346 ranges: Vec<Range<T>>,
15347 auto_scroll: bool,
15348 window: &mut Window,
15349 cx: &mut Context<Self>,
15350 ) {
15351 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15352 let ranges = ranges
15353 .into_iter()
15354 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
15355 .collect::<Vec<_>>();
15356 self.fold_creases(ranges, auto_scroll, window, cx);
15357 }
15358
15359 pub fn fold_creases<T: ToOffset + Clone>(
15360 &mut self,
15361 creases: Vec<Crease<T>>,
15362 auto_scroll: bool,
15363 _window: &mut Window,
15364 cx: &mut Context<Self>,
15365 ) {
15366 if creases.is_empty() {
15367 return;
15368 }
15369
15370 let mut buffers_affected = HashSet::default();
15371 let multi_buffer = self.buffer().read(cx);
15372 for crease in &creases {
15373 if let Some((_, buffer, _)) =
15374 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
15375 {
15376 buffers_affected.insert(buffer.read(cx).remote_id());
15377 };
15378 }
15379
15380 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
15381
15382 if auto_scroll {
15383 self.request_autoscroll(Autoscroll::fit(), cx);
15384 }
15385
15386 cx.notify();
15387
15388 self.scrollbar_marker_state.dirty = true;
15389 self.folds_did_change(cx);
15390 }
15391
15392 /// Removes any folds whose ranges intersect any of the given ranges.
15393 pub fn unfold_ranges<T: ToOffset + Clone>(
15394 &mut self,
15395 ranges: &[Range<T>],
15396 inclusive: bool,
15397 auto_scroll: bool,
15398 cx: &mut Context<Self>,
15399 ) {
15400 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15401 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
15402 });
15403 self.folds_did_change(cx);
15404 }
15405
15406 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15407 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
15408 return;
15409 }
15410 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15411 self.display_map.update(cx, |display_map, cx| {
15412 display_map.fold_buffers([buffer_id], cx)
15413 });
15414 cx.emit(EditorEvent::BufferFoldToggled {
15415 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
15416 folded: true,
15417 });
15418 cx.notify();
15419 }
15420
15421 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15422 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
15423 return;
15424 }
15425 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15426 self.display_map.update(cx, |display_map, cx| {
15427 display_map.unfold_buffers([buffer_id], cx);
15428 });
15429 cx.emit(EditorEvent::BufferFoldToggled {
15430 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
15431 folded: false,
15432 });
15433 cx.notify();
15434 }
15435
15436 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
15437 self.display_map.read(cx).is_buffer_folded(buffer)
15438 }
15439
15440 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
15441 self.display_map.read(cx).folded_buffers()
15442 }
15443
15444 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15445 self.display_map.update(cx, |display_map, cx| {
15446 display_map.disable_header_for_buffer(buffer_id, cx);
15447 });
15448 cx.notify();
15449 }
15450
15451 /// Removes any folds with the given ranges.
15452 pub fn remove_folds_with_type<T: ToOffset + Clone>(
15453 &mut self,
15454 ranges: &[Range<T>],
15455 type_id: TypeId,
15456 auto_scroll: bool,
15457 cx: &mut Context<Self>,
15458 ) {
15459 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15460 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
15461 });
15462 self.folds_did_change(cx);
15463 }
15464
15465 fn remove_folds_with<T: ToOffset + Clone>(
15466 &mut self,
15467 ranges: &[Range<T>],
15468 auto_scroll: bool,
15469 cx: &mut Context<Self>,
15470 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
15471 ) {
15472 if ranges.is_empty() {
15473 return;
15474 }
15475
15476 let mut buffers_affected = HashSet::default();
15477 let multi_buffer = self.buffer().read(cx);
15478 for range in ranges {
15479 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
15480 buffers_affected.insert(buffer.read(cx).remote_id());
15481 };
15482 }
15483
15484 self.display_map.update(cx, update);
15485
15486 if auto_scroll {
15487 self.request_autoscroll(Autoscroll::fit(), cx);
15488 }
15489
15490 cx.notify();
15491 self.scrollbar_marker_state.dirty = true;
15492 self.active_indent_guides_state.dirty = true;
15493 }
15494
15495 pub fn update_fold_widths(
15496 &mut self,
15497 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
15498 cx: &mut Context<Self>,
15499 ) -> bool {
15500 self.display_map
15501 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
15502 }
15503
15504 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
15505 self.display_map.read(cx).fold_placeholder.clone()
15506 }
15507
15508 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
15509 self.buffer.update(cx, |buffer, cx| {
15510 buffer.set_all_diff_hunks_expanded(cx);
15511 });
15512 }
15513
15514 pub fn expand_all_diff_hunks(
15515 &mut self,
15516 _: &ExpandAllDiffHunks,
15517 _window: &mut Window,
15518 cx: &mut Context<Self>,
15519 ) {
15520 self.buffer.update(cx, |buffer, cx| {
15521 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
15522 });
15523 }
15524
15525 pub fn toggle_selected_diff_hunks(
15526 &mut self,
15527 _: &ToggleSelectedDiffHunks,
15528 _window: &mut Window,
15529 cx: &mut Context<Self>,
15530 ) {
15531 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15532 self.toggle_diff_hunks_in_ranges(ranges, cx);
15533 }
15534
15535 pub fn diff_hunks_in_ranges<'a>(
15536 &'a self,
15537 ranges: &'a [Range<Anchor>],
15538 buffer: &'a MultiBufferSnapshot,
15539 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
15540 ranges.iter().flat_map(move |range| {
15541 let end_excerpt_id = range.end.excerpt_id;
15542 let range = range.to_point(buffer);
15543 let mut peek_end = range.end;
15544 if range.end.row < buffer.max_row().0 {
15545 peek_end = Point::new(range.end.row + 1, 0);
15546 }
15547 buffer
15548 .diff_hunks_in_range(range.start..peek_end)
15549 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
15550 })
15551 }
15552
15553 pub fn has_stageable_diff_hunks_in_ranges(
15554 &self,
15555 ranges: &[Range<Anchor>],
15556 snapshot: &MultiBufferSnapshot,
15557 ) -> bool {
15558 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
15559 hunks.any(|hunk| hunk.status().has_secondary_hunk())
15560 }
15561
15562 pub fn toggle_staged_selected_diff_hunks(
15563 &mut self,
15564 _: &::git::ToggleStaged,
15565 _: &mut Window,
15566 cx: &mut Context<Self>,
15567 ) {
15568 let snapshot = self.buffer.read(cx).snapshot(cx);
15569 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15570 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
15571 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15572 }
15573
15574 pub fn set_render_diff_hunk_controls(
15575 &mut self,
15576 render_diff_hunk_controls: RenderDiffHunkControlsFn,
15577 cx: &mut Context<Self>,
15578 ) {
15579 self.render_diff_hunk_controls = render_diff_hunk_controls;
15580 cx.notify();
15581 }
15582
15583 pub fn stage_and_next(
15584 &mut self,
15585 _: &::git::StageAndNext,
15586 window: &mut Window,
15587 cx: &mut Context<Self>,
15588 ) {
15589 self.do_stage_or_unstage_and_next(true, window, cx);
15590 }
15591
15592 pub fn unstage_and_next(
15593 &mut self,
15594 _: &::git::UnstageAndNext,
15595 window: &mut Window,
15596 cx: &mut Context<Self>,
15597 ) {
15598 self.do_stage_or_unstage_and_next(false, window, cx);
15599 }
15600
15601 pub fn stage_or_unstage_diff_hunks(
15602 &mut self,
15603 stage: bool,
15604 ranges: Vec<Range<Anchor>>,
15605 cx: &mut Context<Self>,
15606 ) {
15607 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
15608 cx.spawn(async move |this, cx| {
15609 task.await?;
15610 this.update(cx, |this, cx| {
15611 let snapshot = this.buffer.read(cx).snapshot(cx);
15612 let chunk_by = this
15613 .diff_hunks_in_ranges(&ranges, &snapshot)
15614 .chunk_by(|hunk| hunk.buffer_id);
15615 for (buffer_id, hunks) in &chunk_by {
15616 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
15617 }
15618 })
15619 })
15620 .detach_and_log_err(cx);
15621 }
15622
15623 fn save_buffers_for_ranges_if_needed(
15624 &mut self,
15625 ranges: &[Range<Anchor>],
15626 cx: &mut Context<Editor>,
15627 ) -> Task<Result<()>> {
15628 let multibuffer = self.buffer.read(cx);
15629 let snapshot = multibuffer.read(cx);
15630 let buffer_ids: HashSet<_> = ranges
15631 .iter()
15632 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
15633 .collect();
15634 drop(snapshot);
15635
15636 let mut buffers = HashSet::default();
15637 for buffer_id in buffer_ids {
15638 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
15639 let buffer = buffer_entity.read(cx);
15640 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
15641 {
15642 buffers.insert(buffer_entity);
15643 }
15644 }
15645 }
15646
15647 if let Some(project) = &self.project {
15648 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
15649 } else {
15650 Task::ready(Ok(()))
15651 }
15652 }
15653
15654 fn do_stage_or_unstage_and_next(
15655 &mut self,
15656 stage: bool,
15657 window: &mut Window,
15658 cx: &mut Context<Self>,
15659 ) {
15660 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
15661
15662 if ranges.iter().any(|range| range.start != range.end) {
15663 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15664 return;
15665 }
15666
15667 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15668 let snapshot = self.snapshot(window, cx);
15669 let position = self.selections.newest::<Point>(cx).head();
15670 let mut row = snapshot
15671 .buffer_snapshot
15672 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15673 .find(|hunk| hunk.row_range.start.0 > position.row)
15674 .map(|hunk| hunk.row_range.start);
15675
15676 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
15677 // Outside of the project diff editor, wrap around to the beginning.
15678 if !all_diff_hunks_expanded {
15679 row = row.or_else(|| {
15680 snapshot
15681 .buffer_snapshot
15682 .diff_hunks_in_range(Point::zero()..position)
15683 .find(|hunk| hunk.row_range.end.0 < position.row)
15684 .map(|hunk| hunk.row_range.start)
15685 });
15686 }
15687
15688 if let Some(row) = row {
15689 let destination = Point::new(row.0, 0);
15690 let autoscroll = Autoscroll::center();
15691
15692 self.unfold_ranges(&[destination..destination], false, false, cx);
15693 self.change_selections(Some(autoscroll), window, cx, |s| {
15694 s.select_ranges([destination..destination]);
15695 });
15696 }
15697 }
15698
15699 fn do_stage_or_unstage(
15700 &self,
15701 stage: bool,
15702 buffer_id: BufferId,
15703 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
15704 cx: &mut App,
15705 ) -> Option<()> {
15706 let project = self.project.as_ref()?;
15707 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
15708 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
15709 let buffer_snapshot = buffer.read(cx).snapshot();
15710 let file_exists = buffer_snapshot
15711 .file()
15712 .is_some_and(|file| file.disk_state().exists());
15713 diff.update(cx, |diff, cx| {
15714 diff.stage_or_unstage_hunks(
15715 stage,
15716 &hunks
15717 .map(|hunk| buffer_diff::DiffHunk {
15718 buffer_range: hunk.buffer_range,
15719 diff_base_byte_range: hunk.diff_base_byte_range,
15720 secondary_status: hunk.secondary_status,
15721 range: Point::zero()..Point::zero(), // unused
15722 })
15723 .collect::<Vec<_>>(),
15724 &buffer_snapshot,
15725 file_exists,
15726 cx,
15727 )
15728 });
15729 None
15730 }
15731
15732 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
15733 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15734 self.buffer
15735 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
15736 }
15737
15738 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
15739 self.buffer.update(cx, |buffer, cx| {
15740 let ranges = vec![Anchor::min()..Anchor::max()];
15741 if !buffer.all_diff_hunks_expanded()
15742 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
15743 {
15744 buffer.collapse_diff_hunks(ranges, cx);
15745 true
15746 } else {
15747 false
15748 }
15749 })
15750 }
15751
15752 fn toggle_diff_hunks_in_ranges(
15753 &mut self,
15754 ranges: Vec<Range<Anchor>>,
15755 cx: &mut Context<Editor>,
15756 ) {
15757 self.buffer.update(cx, |buffer, cx| {
15758 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
15759 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
15760 })
15761 }
15762
15763 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
15764 self.buffer.update(cx, |buffer, cx| {
15765 let snapshot = buffer.snapshot(cx);
15766 let excerpt_id = range.end.excerpt_id;
15767 let point_range = range.to_point(&snapshot);
15768 let expand = !buffer.single_hunk_is_expanded(range, cx);
15769 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
15770 })
15771 }
15772
15773 pub(crate) fn apply_all_diff_hunks(
15774 &mut self,
15775 _: &ApplyAllDiffHunks,
15776 window: &mut Window,
15777 cx: &mut Context<Self>,
15778 ) {
15779 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15780
15781 let buffers = self.buffer.read(cx).all_buffers();
15782 for branch_buffer in buffers {
15783 branch_buffer.update(cx, |branch_buffer, cx| {
15784 branch_buffer.merge_into_base(Vec::new(), cx);
15785 });
15786 }
15787
15788 if let Some(project) = self.project.clone() {
15789 self.save(true, project, window, cx).detach_and_log_err(cx);
15790 }
15791 }
15792
15793 pub(crate) fn apply_selected_diff_hunks(
15794 &mut self,
15795 _: &ApplyDiffHunk,
15796 window: &mut Window,
15797 cx: &mut Context<Self>,
15798 ) {
15799 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15800 let snapshot = self.snapshot(window, cx);
15801 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
15802 let mut ranges_by_buffer = HashMap::default();
15803 self.transact(window, cx, |editor, _window, cx| {
15804 for hunk in hunks {
15805 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
15806 ranges_by_buffer
15807 .entry(buffer.clone())
15808 .or_insert_with(Vec::new)
15809 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
15810 }
15811 }
15812
15813 for (buffer, ranges) in ranges_by_buffer {
15814 buffer.update(cx, |buffer, cx| {
15815 buffer.merge_into_base(ranges, cx);
15816 });
15817 }
15818 });
15819
15820 if let Some(project) = self.project.clone() {
15821 self.save(true, project, window, cx).detach_and_log_err(cx);
15822 }
15823 }
15824
15825 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
15826 if hovered != self.gutter_hovered {
15827 self.gutter_hovered = hovered;
15828 cx.notify();
15829 }
15830 }
15831
15832 pub fn insert_blocks(
15833 &mut self,
15834 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
15835 autoscroll: Option<Autoscroll>,
15836 cx: &mut Context<Self>,
15837 ) -> Vec<CustomBlockId> {
15838 let blocks = self
15839 .display_map
15840 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
15841 if let Some(autoscroll) = autoscroll {
15842 self.request_autoscroll(autoscroll, cx);
15843 }
15844 cx.notify();
15845 blocks
15846 }
15847
15848 pub fn resize_blocks(
15849 &mut self,
15850 heights: HashMap<CustomBlockId, u32>,
15851 autoscroll: Option<Autoscroll>,
15852 cx: &mut Context<Self>,
15853 ) {
15854 self.display_map
15855 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
15856 if let Some(autoscroll) = autoscroll {
15857 self.request_autoscroll(autoscroll, cx);
15858 }
15859 cx.notify();
15860 }
15861
15862 pub fn replace_blocks(
15863 &mut self,
15864 renderers: HashMap<CustomBlockId, RenderBlock>,
15865 autoscroll: Option<Autoscroll>,
15866 cx: &mut Context<Self>,
15867 ) {
15868 self.display_map
15869 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
15870 if let Some(autoscroll) = autoscroll {
15871 self.request_autoscroll(autoscroll, cx);
15872 }
15873 cx.notify();
15874 }
15875
15876 pub fn remove_blocks(
15877 &mut self,
15878 block_ids: HashSet<CustomBlockId>,
15879 autoscroll: Option<Autoscroll>,
15880 cx: &mut Context<Self>,
15881 ) {
15882 self.display_map.update(cx, |display_map, cx| {
15883 display_map.remove_blocks(block_ids, cx)
15884 });
15885 if let Some(autoscroll) = autoscroll {
15886 self.request_autoscroll(autoscroll, cx);
15887 }
15888 cx.notify();
15889 }
15890
15891 pub fn row_for_block(
15892 &self,
15893 block_id: CustomBlockId,
15894 cx: &mut Context<Self>,
15895 ) -> Option<DisplayRow> {
15896 self.display_map
15897 .update(cx, |map, cx| map.row_for_block(block_id, cx))
15898 }
15899
15900 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
15901 self.focused_block = Some(focused_block);
15902 }
15903
15904 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
15905 self.focused_block.take()
15906 }
15907
15908 pub fn insert_creases(
15909 &mut self,
15910 creases: impl IntoIterator<Item = Crease<Anchor>>,
15911 cx: &mut Context<Self>,
15912 ) -> Vec<CreaseId> {
15913 self.display_map
15914 .update(cx, |map, cx| map.insert_creases(creases, cx))
15915 }
15916
15917 pub fn remove_creases(
15918 &mut self,
15919 ids: impl IntoIterator<Item = CreaseId>,
15920 cx: &mut Context<Self>,
15921 ) {
15922 self.display_map
15923 .update(cx, |map, cx| map.remove_creases(ids, cx));
15924 }
15925
15926 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
15927 self.display_map
15928 .update(cx, |map, cx| map.snapshot(cx))
15929 .longest_row()
15930 }
15931
15932 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
15933 self.display_map
15934 .update(cx, |map, cx| map.snapshot(cx))
15935 .max_point()
15936 }
15937
15938 pub fn text(&self, cx: &App) -> String {
15939 self.buffer.read(cx).read(cx).text()
15940 }
15941
15942 pub fn is_empty(&self, cx: &App) -> bool {
15943 self.buffer.read(cx).read(cx).is_empty()
15944 }
15945
15946 pub fn text_option(&self, cx: &App) -> Option<String> {
15947 let text = self.text(cx);
15948 let text = text.trim();
15949
15950 if text.is_empty() {
15951 return None;
15952 }
15953
15954 Some(text.to_string())
15955 }
15956
15957 pub fn set_text(
15958 &mut self,
15959 text: impl Into<Arc<str>>,
15960 window: &mut Window,
15961 cx: &mut Context<Self>,
15962 ) {
15963 self.transact(window, cx, |this, _, cx| {
15964 this.buffer
15965 .read(cx)
15966 .as_singleton()
15967 .expect("you can only call set_text on editors for singleton buffers")
15968 .update(cx, |buffer, cx| buffer.set_text(text, cx));
15969 });
15970 }
15971
15972 pub fn display_text(&self, cx: &mut App) -> String {
15973 self.display_map
15974 .update(cx, |map, cx| map.snapshot(cx))
15975 .text()
15976 }
15977
15978 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
15979 let mut wrap_guides = smallvec::smallvec![];
15980
15981 if self.show_wrap_guides == Some(false) {
15982 return wrap_guides;
15983 }
15984
15985 let settings = self.buffer.read(cx).language_settings(cx);
15986 if settings.show_wrap_guides {
15987 match self.soft_wrap_mode(cx) {
15988 SoftWrap::Column(soft_wrap) => {
15989 wrap_guides.push((soft_wrap as usize, true));
15990 }
15991 SoftWrap::Bounded(soft_wrap) => {
15992 wrap_guides.push((soft_wrap as usize, true));
15993 }
15994 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
15995 }
15996 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
15997 }
15998
15999 wrap_guides
16000 }
16001
16002 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16003 let settings = self.buffer.read(cx).language_settings(cx);
16004 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16005 match mode {
16006 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16007 SoftWrap::None
16008 }
16009 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16010 language_settings::SoftWrap::PreferredLineLength => {
16011 SoftWrap::Column(settings.preferred_line_length)
16012 }
16013 language_settings::SoftWrap::Bounded => {
16014 SoftWrap::Bounded(settings.preferred_line_length)
16015 }
16016 }
16017 }
16018
16019 pub fn set_soft_wrap_mode(
16020 &mut self,
16021 mode: language_settings::SoftWrap,
16022
16023 cx: &mut Context<Self>,
16024 ) {
16025 self.soft_wrap_mode_override = Some(mode);
16026 cx.notify();
16027 }
16028
16029 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16030 self.hard_wrap = hard_wrap;
16031 cx.notify();
16032 }
16033
16034 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16035 self.text_style_refinement = Some(style);
16036 }
16037
16038 /// called by the Element so we know what style we were most recently rendered with.
16039 pub(crate) fn set_style(
16040 &mut self,
16041 style: EditorStyle,
16042 window: &mut Window,
16043 cx: &mut Context<Self>,
16044 ) {
16045 let rem_size = window.rem_size();
16046 self.display_map.update(cx, |map, cx| {
16047 map.set_font(
16048 style.text.font(),
16049 style.text.font_size.to_pixels(rem_size),
16050 cx,
16051 )
16052 });
16053 self.style = Some(style);
16054 }
16055
16056 pub fn style(&self) -> Option<&EditorStyle> {
16057 self.style.as_ref()
16058 }
16059
16060 // Called by the element. This method is not designed to be called outside of the editor
16061 // element's layout code because it does not notify when rewrapping is computed synchronously.
16062 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16063 self.display_map
16064 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16065 }
16066
16067 pub fn set_soft_wrap(&mut self) {
16068 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16069 }
16070
16071 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16072 if self.soft_wrap_mode_override.is_some() {
16073 self.soft_wrap_mode_override.take();
16074 } else {
16075 let soft_wrap = match self.soft_wrap_mode(cx) {
16076 SoftWrap::GitDiff => return,
16077 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16078 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16079 language_settings::SoftWrap::None
16080 }
16081 };
16082 self.soft_wrap_mode_override = Some(soft_wrap);
16083 }
16084 cx.notify();
16085 }
16086
16087 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16088 let Some(workspace) = self.workspace() else {
16089 return;
16090 };
16091 let fs = workspace.read(cx).app_state().fs.clone();
16092 let current_show = TabBarSettings::get_global(cx).show;
16093 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16094 setting.show = Some(!current_show);
16095 });
16096 }
16097
16098 pub fn toggle_indent_guides(
16099 &mut self,
16100 _: &ToggleIndentGuides,
16101 _: &mut Window,
16102 cx: &mut Context<Self>,
16103 ) {
16104 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16105 self.buffer
16106 .read(cx)
16107 .language_settings(cx)
16108 .indent_guides
16109 .enabled
16110 });
16111 self.show_indent_guides = Some(!currently_enabled);
16112 cx.notify();
16113 }
16114
16115 fn should_show_indent_guides(&self) -> Option<bool> {
16116 self.show_indent_guides
16117 }
16118
16119 pub fn toggle_line_numbers(
16120 &mut self,
16121 _: &ToggleLineNumbers,
16122 _: &mut Window,
16123 cx: &mut Context<Self>,
16124 ) {
16125 let mut editor_settings = EditorSettings::get_global(cx).clone();
16126 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16127 EditorSettings::override_global(editor_settings, cx);
16128 }
16129
16130 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16131 if let Some(show_line_numbers) = self.show_line_numbers {
16132 return show_line_numbers;
16133 }
16134 EditorSettings::get_global(cx).gutter.line_numbers
16135 }
16136
16137 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16138 self.use_relative_line_numbers
16139 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16140 }
16141
16142 pub fn toggle_relative_line_numbers(
16143 &mut self,
16144 _: &ToggleRelativeLineNumbers,
16145 _: &mut Window,
16146 cx: &mut Context<Self>,
16147 ) {
16148 let is_relative = self.should_use_relative_line_numbers(cx);
16149 self.set_relative_line_number(Some(!is_relative), cx)
16150 }
16151
16152 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16153 self.use_relative_line_numbers = is_relative;
16154 cx.notify();
16155 }
16156
16157 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16158 self.show_gutter = show_gutter;
16159 cx.notify();
16160 }
16161
16162 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
16163 self.show_scrollbars = show_scrollbars;
16164 cx.notify();
16165 }
16166
16167 pub fn disable_scrolling(&mut self, cx: &mut Context<Self>) {
16168 self.disable_scrolling = true;
16169 cx.notify();
16170 }
16171
16172 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
16173 self.show_line_numbers = Some(show_line_numbers);
16174 cx.notify();
16175 }
16176
16177 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
16178 self.disable_expand_excerpt_buttons = true;
16179 cx.notify();
16180 }
16181
16182 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
16183 self.show_git_diff_gutter = Some(show_git_diff_gutter);
16184 cx.notify();
16185 }
16186
16187 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
16188 self.show_code_actions = Some(show_code_actions);
16189 cx.notify();
16190 }
16191
16192 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
16193 self.show_runnables = Some(show_runnables);
16194 cx.notify();
16195 }
16196
16197 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
16198 self.show_breakpoints = Some(show_breakpoints);
16199 cx.notify();
16200 }
16201
16202 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
16203 if self.display_map.read(cx).masked != masked {
16204 self.display_map.update(cx, |map, _| map.masked = masked);
16205 }
16206 cx.notify()
16207 }
16208
16209 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
16210 self.show_wrap_guides = Some(show_wrap_guides);
16211 cx.notify();
16212 }
16213
16214 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
16215 self.show_indent_guides = Some(show_indent_guides);
16216 cx.notify();
16217 }
16218
16219 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
16220 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
16221 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
16222 if let Some(dir) = file.abs_path(cx).parent() {
16223 return Some(dir.to_owned());
16224 }
16225 }
16226
16227 if let Some(project_path) = buffer.read(cx).project_path(cx) {
16228 return Some(project_path.path.to_path_buf());
16229 }
16230 }
16231
16232 None
16233 }
16234
16235 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
16236 self.active_excerpt(cx)?
16237 .1
16238 .read(cx)
16239 .file()
16240 .and_then(|f| f.as_local())
16241 }
16242
16243 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16244 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16245 let buffer = buffer.read(cx);
16246 if let Some(project_path) = buffer.project_path(cx) {
16247 let project = self.project.as_ref()?.read(cx);
16248 project.absolute_path(&project_path, cx)
16249 } else {
16250 buffer
16251 .file()
16252 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
16253 }
16254 })
16255 }
16256
16257 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16258 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16259 let project_path = buffer.read(cx).project_path(cx)?;
16260 let project = self.project.as_ref()?.read(cx);
16261 let entry = project.entry_for_path(&project_path, cx)?;
16262 let path = entry.path.to_path_buf();
16263 Some(path)
16264 })
16265 }
16266
16267 pub fn reveal_in_finder(
16268 &mut self,
16269 _: &RevealInFileManager,
16270 _window: &mut Window,
16271 cx: &mut Context<Self>,
16272 ) {
16273 if let Some(target) = self.target_file(cx) {
16274 cx.reveal_path(&target.abs_path(cx));
16275 }
16276 }
16277
16278 pub fn copy_path(
16279 &mut self,
16280 _: &zed_actions::workspace::CopyPath,
16281 _window: &mut Window,
16282 cx: &mut Context<Self>,
16283 ) {
16284 if let Some(path) = self.target_file_abs_path(cx) {
16285 if let Some(path) = path.to_str() {
16286 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16287 }
16288 }
16289 }
16290
16291 pub fn copy_relative_path(
16292 &mut self,
16293 _: &zed_actions::workspace::CopyRelativePath,
16294 _window: &mut Window,
16295 cx: &mut Context<Self>,
16296 ) {
16297 if let Some(path) = self.target_file_path(cx) {
16298 if let Some(path) = path.to_str() {
16299 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16300 }
16301 }
16302 }
16303
16304 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
16305 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
16306 buffer.read(cx).project_path(cx)
16307 } else {
16308 None
16309 }
16310 }
16311
16312 // Returns true if the editor handled a go-to-line request
16313 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
16314 maybe!({
16315 let breakpoint_store = self.breakpoint_store.as_ref()?;
16316
16317 let Some((_, _, active_position)) =
16318 breakpoint_store.read(cx).active_position().cloned()
16319 else {
16320 self.clear_row_highlights::<DebugCurrentRowHighlight>();
16321 return None;
16322 };
16323
16324 let snapshot = self
16325 .project
16326 .as_ref()?
16327 .read(cx)
16328 .buffer_for_id(active_position.buffer_id?, cx)?
16329 .read(cx)
16330 .snapshot();
16331
16332 let mut handled = false;
16333 for (id, ExcerptRange { context, .. }) in self
16334 .buffer
16335 .read(cx)
16336 .excerpts_for_buffer(active_position.buffer_id?, cx)
16337 {
16338 if context.start.cmp(&active_position, &snapshot).is_ge()
16339 || context.end.cmp(&active_position, &snapshot).is_lt()
16340 {
16341 continue;
16342 }
16343 let snapshot = self.buffer.read(cx).snapshot(cx);
16344 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, active_position)?;
16345
16346 handled = true;
16347 self.clear_row_highlights::<DebugCurrentRowHighlight>();
16348 self.go_to_line::<DebugCurrentRowHighlight>(
16349 multibuffer_anchor,
16350 Some(cx.theme().colors().editor_debugger_active_line_background),
16351 window,
16352 cx,
16353 );
16354
16355 cx.notify();
16356 }
16357 handled.then_some(())
16358 })
16359 .is_some()
16360 }
16361
16362 pub fn copy_file_name_without_extension(
16363 &mut self,
16364 _: &CopyFileNameWithoutExtension,
16365 _: &mut Window,
16366 cx: &mut Context<Self>,
16367 ) {
16368 if let Some(file) = self.target_file(cx) {
16369 if let Some(file_stem) = file.path().file_stem() {
16370 if let Some(name) = file_stem.to_str() {
16371 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16372 }
16373 }
16374 }
16375 }
16376
16377 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
16378 if let Some(file) = self.target_file(cx) {
16379 if let Some(file_name) = file.path().file_name() {
16380 if let Some(name) = file_name.to_str() {
16381 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16382 }
16383 }
16384 }
16385 }
16386
16387 pub fn toggle_git_blame(
16388 &mut self,
16389 _: &::git::Blame,
16390 window: &mut Window,
16391 cx: &mut Context<Self>,
16392 ) {
16393 self.show_git_blame_gutter = !self.show_git_blame_gutter;
16394
16395 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
16396 self.start_git_blame(true, window, cx);
16397 }
16398
16399 cx.notify();
16400 }
16401
16402 pub fn toggle_git_blame_inline(
16403 &mut self,
16404 _: &ToggleGitBlameInline,
16405 window: &mut Window,
16406 cx: &mut Context<Self>,
16407 ) {
16408 self.toggle_git_blame_inline_internal(true, window, cx);
16409 cx.notify();
16410 }
16411
16412 pub fn open_git_blame_commit(
16413 &mut self,
16414 _: &OpenGitBlameCommit,
16415 window: &mut Window,
16416 cx: &mut Context<Self>,
16417 ) {
16418 self.open_git_blame_commit_internal(window, cx);
16419 }
16420
16421 fn open_git_blame_commit_internal(
16422 &mut self,
16423 window: &mut Window,
16424 cx: &mut Context<Self>,
16425 ) -> Option<()> {
16426 let blame = self.blame.as_ref()?;
16427 let snapshot = self.snapshot(window, cx);
16428 let cursor = self.selections.newest::<Point>(cx).head();
16429 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
16430 let blame_entry = blame
16431 .update(cx, |blame, cx| {
16432 blame
16433 .blame_for_rows(
16434 &[RowInfo {
16435 buffer_id: Some(buffer.remote_id()),
16436 buffer_row: Some(point.row),
16437 ..Default::default()
16438 }],
16439 cx,
16440 )
16441 .next()
16442 })
16443 .flatten()?;
16444 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
16445 let repo = blame.read(cx).repository(cx)?;
16446 let workspace = self.workspace()?.downgrade();
16447 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
16448 None
16449 }
16450
16451 pub fn git_blame_inline_enabled(&self) -> bool {
16452 self.git_blame_inline_enabled
16453 }
16454
16455 pub fn toggle_selection_menu(
16456 &mut self,
16457 _: &ToggleSelectionMenu,
16458 _: &mut Window,
16459 cx: &mut Context<Self>,
16460 ) {
16461 self.show_selection_menu = self
16462 .show_selection_menu
16463 .map(|show_selections_menu| !show_selections_menu)
16464 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
16465
16466 cx.notify();
16467 }
16468
16469 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
16470 self.show_selection_menu
16471 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
16472 }
16473
16474 fn start_git_blame(
16475 &mut self,
16476 user_triggered: bool,
16477 window: &mut Window,
16478 cx: &mut Context<Self>,
16479 ) {
16480 if let Some(project) = self.project.as_ref() {
16481 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
16482 return;
16483 };
16484
16485 if buffer.read(cx).file().is_none() {
16486 return;
16487 }
16488
16489 let focused = self.focus_handle(cx).contains_focused(window, cx);
16490
16491 let project = project.clone();
16492 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
16493 self.blame_subscription =
16494 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
16495 self.blame = Some(blame);
16496 }
16497 }
16498
16499 fn toggle_git_blame_inline_internal(
16500 &mut self,
16501 user_triggered: bool,
16502 window: &mut Window,
16503 cx: &mut Context<Self>,
16504 ) {
16505 if self.git_blame_inline_enabled {
16506 self.git_blame_inline_enabled = false;
16507 self.show_git_blame_inline = false;
16508 self.show_git_blame_inline_delay_task.take();
16509 } else {
16510 self.git_blame_inline_enabled = true;
16511 self.start_git_blame_inline(user_triggered, window, cx);
16512 }
16513
16514 cx.notify();
16515 }
16516
16517 fn start_git_blame_inline(
16518 &mut self,
16519 user_triggered: bool,
16520 window: &mut Window,
16521 cx: &mut Context<Self>,
16522 ) {
16523 self.start_git_blame(user_triggered, window, cx);
16524
16525 if ProjectSettings::get_global(cx)
16526 .git
16527 .inline_blame_delay()
16528 .is_some()
16529 {
16530 self.start_inline_blame_timer(window, cx);
16531 } else {
16532 self.show_git_blame_inline = true
16533 }
16534 }
16535
16536 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
16537 self.blame.as_ref()
16538 }
16539
16540 pub fn show_git_blame_gutter(&self) -> bool {
16541 self.show_git_blame_gutter
16542 }
16543
16544 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
16545 self.show_git_blame_gutter && self.has_blame_entries(cx)
16546 }
16547
16548 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
16549 self.show_git_blame_inline
16550 && (self.focus_handle.is_focused(window)
16551 || self
16552 .git_blame_inline_tooltip
16553 .as_ref()
16554 .and_then(|t| t.upgrade())
16555 .is_some())
16556 && !self.newest_selection_head_on_empty_line(cx)
16557 && self.has_blame_entries(cx)
16558 }
16559
16560 fn has_blame_entries(&self, cx: &App) -> bool {
16561 self.blame()
16562 .map_or(false, |blame| blame.read(cx).has_generated_entries())
16563 }
16564
16565 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
16566 let cursor_anchor = self.selections.newest_anchor().head();
16567
16568 let snapshot = self.buffer.read(cx).snapshot(cx);
16569 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
16570
16571 snapshot.line_len(buffer_row) == 0
16572 }
16573
16574 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
16575 let buffer_and_selection = maybe!({
16576 let selection = self.selections.newest::<Point>(cx);
16577 let selection_range = selection.range();
16578
16579 let multi_buffer = self.buffer().read(cx);
16580 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16581 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
16582
16583 let (buffer, range, _) = if selection.reversed {
16584 buffer_ranges.first()
16585 } else {
16586 buffer_ranges.last()
16587 }?;
16588
16589 let selection = text::ToPoint::to_point(&range.start, &buffer).row
16590 ..text::ToPoint::to_point(&range.end, &buffer).row;
16591 Some((
16592 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
16593 selection,
16594 ))
16595 });
16596
16597 let Some((buffer, selection)) = buffer_and_selection else {
16598 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
16599 };
16600
16601 let Some(project) = self.project.as_ref() else {
16602 return Task::ready(Err(anyhow!("editor does not have project")));
16603 };
16604
16605 project.update(cx, |project, cx| {
16606 project.get_permalink_to_line(&buffer, selection, cx)
16607 })
16608 }
16609
16610 pub fn copy_permalink_to_line(
16611 &mut self,
16612 _: &CopyPermalinkToLine,
16613 window: &mut Window,
16614 cx: &mut Context<Self>,
16615 ) {
16616 let permalink_task = self.get_permalink_to_line(cx);
16617 let workspace = self.workspace();
16618
16619 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16620 Ok(permalink) => {
16621 cx.update(|_, cx| {
16622 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
16623 })
16624 .ok();
16625 }
16626 Err(err) => {
16627 let message = format!("Failed to copy permalink: {err}");
16628
16629 Err::<(), anyhow::Error>(err).log_err();
16630
16631 if let Some(workspace) = workspace {
16632 workspace
16633 .update_in(cx, |workspace, _, cx| {
16634 struct CopyPermalinkToLine;
16635
16636 workspace.show_toast(
16637 Toast::new(
16638 NotificationId::unique::<CopyPermalinkToLine>(),
16639 message,
16640 ),
16641 cx,
16642 )
16643 })
16644 .ok();
16645 }
16646 }
16647 })
16648 .detach();
16649 }
16650
16651 pub fn copy_file_location(
16652 &mut self,
16653 _: &CopyFileLocation,
16654 _: &mut Window,
16655 cx: &mut Context<Self>,
16656 ) {
16657 let selection = self.selections.newest::<Point>(cx).start.row + 1;
16658 if let Some(file) = self.target_file(cx) {
16659 if let Some(path) = file.path().to_str() {
16660 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
16661 }
16662 }
16663 }
16664
16665 pub fn open_permalink_to_line(
16666 &mut self,
16667 _: &OpenPermalinkToLine,
16668 window: &mut Window,
16669 cx: &mut Context<Self>,
16670 ) {
16671 let permalink_task = self.get_permalink_to_line(cx);
16672 let workspace = self.workspace();
16673
16674 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16675 Ok(permalink) => {
16676 cx.update(|_, cx| {
16677 cx.open_url(permalink.as_ref());
16678 })
16679 .ok();
16680 }
16681 Err(err) => {
16682 let message = format!("Failed to open permalink: {err}");
16683
16684 Err::<(), anyhow::Error>(err).log_err();
16685
16686 if let Some(workspace) = workspace {
16687 workspace
16688 .update(cx, |workspace, cx| {
16689 struct OpenPermalinkToLine;
16690
16691 workspace.show_toast(
16692 Toast::new(
16693 NotificationId::unique::<OpenPermalinkToLine>(),
16694 message,
16695 ),
16696 cx,
16697 )
16698 })
16699 .ok();
16700 }
16701 }
16702 })
16703 .detach();
16704 }
16705
16706 pub fn insert_uuid_v4(
16707 &mut self,
16708 _: &InsertUuidV4,
16709 window: &mut Window,
16710 cx: &mut Context<Self>,
16711 ) {
16712 self.insert_uuid(UuidVersion::V4, window, cx);
16713 }
16714
16715 pub fn insert_uuid_v7(
16716 &mut self,
16717 _: &InsertUuidV7,
16718 window: &mut Window,
16719 cx: &mut Context<Self>,
16720 ) {
16721 self.insert_uuid(UuidVersion::V7, window, cx);
16722 }
16723
16724 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
16725 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16726 self.transact(window, cx, |this, window, cx| {
16727 let edits = this
16728 .selections
16729 .all::<Point>(cx)
16730 .into_iter()
16731 .map(|selection| {
16732 let uuid = match version {
16733 UuidVersion::V4 => uuid::Uuid::new_v4(),
16734 UuidVersion::V7 => uuid::Uuid::now_v7(),
16735 };
16736
16737 (selection.range(), uuid.to_string())
16738 });
16739 this.edit(edits, cx);
16740 this.refresh_inline_completion(true, false, window, cx);
16741 });
16742 }
16743
16744 pub fn open_selections_in_multibuffer(
16745 &mut self,
16746 _: &OpenSelectionsInMultibuffer,
16747 window: &mut Window,
16748 cx: &mut Context<Self>,
16749 ) {
16750 let multibuffer = self.buffer.read(cx);
16751
16752 let Some(buffer) = multibuffer.as_singleton() else {
16753 return;
16754 };
16755
16756 let Some(workspace) = self.workspace() else {
16757 return;
16758 };
16759
16760 let locations = self
16761 .selections
16762 .disjoint_anchors()
16763 .iter()
16764 .map(|range| Location {
16765 buffer: buffer.clone(),
16766 range: range.start.text_anchor..range.end.text_anchor,
16767 })
16768 .collect::<Vec<_>>();
16769
16770 let title = multibuffer.title(cx).to_string();
16771
16772 cx.spawn_in(window, async move |_, cx| {
16773 workspace.update_in(cx, |workspace, window, cx| {
16774 Self::open_locations_in_multibuffer(
16775 workspace,
16776 locations,
16777 format!("Selections for '{title}'"),
16778 false,
16779 MultibufferSelectionMode::All,
16780 window,
16781 cx,
16782 );
16783 })
16784 })
16785 .detach();
16786 }
16787
16788 /// Adds a row highlight for the given range. If a row has multiple highlights, the
16789 /// last highlight added will be used.
16790 ///
16791 /// If the range ends at the beginning of a line, then that line will not be highlighted.
16792 pub fn highlight_rows<T: 'static>(
16793 &mut self,
16794 range: Range<Anchor>,
16795 color: Hsla,
16796 should_autoscroll: bool,
16797 cx: &mut Context<Self>,
16798 ) {
16799 let snapshot = self.buffer().read(cx).snapshot(cx);
16800 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16801 let ix = row_highlights.binary_search_by(|highlight| {
16802 Ordering::Equal
16803 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
16804 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
16805 });
16806
16807 if let Err(mut ix) = ix {
16808 let index = post_inc(&mut self.highlight_order);
16809
16810 // If this range intersects with the preceding highlight, then merge it with
16811 // the preceding highlight. Otherwise insert a new highlight.
16812 let mut merged = false;
16813 if ix > 0 {
16814 let prev_highlight = &mut row_highlights[ix - 1];
16815 if prev_highlight
16816 .range
16817 .end
16818 .cmp(&range.start, &snapshot)
16819 .is_ge()
16820 {
16821 ix -= 1;
16822 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
16823 prev_highlight.range.end = range.end;
16824 }
16825 merged = true;
16826 prev_highlight.index = index;
16827 prev_highlight.color = color;
16828 prev_highlight.should_autoscroll = should_autoscroll;
16829 }
16830 }
16831
16832 if !merged {
16833 row_highlights.insert(
16834 ix,
16835 RowHighlight {
16836 range: range.clone(),
16837 index,
16838 color,
16839 should_autoscroll,
16840 },
16841 );
16842 }
16843
16844 // If any of the following highlights intersect with this one, merge them.
16845 while let Some(next_highlight) = row_highlights.get(ix + 1) {
16846 let highlight = &row_highlights[ix];
16847 if next_highlight
16848 .range
16849 .start
16850 .cmp(&highlight.range.end, &snapshot)
16851 .is_le()
16852 {
16853 if next_highlight
16854 .range
16855 .end
16856 .cmp(&highlight.range.end, &snapshot)
16857 .is_gt()
16858 {
16859 row_highlights[ix].range.end = next_highlight.range.end;
16860 }
16861 row_highlights.remove(ix + 1);
16862 } else {
16863 break;
16864 }
16865 }
16866 }
16867 }
16868
16869 /// Remove any highlighted row ranges of the given type that intersect the
16870 /// given ranges.
16871 pub fn remove_highlighted_rows<T: 'static>(
16872 &mut self,
16873 ranges_to_remove: Vec<Range<Anchor>>,
16874 cx: &mut Context<Self>,
16875 ) {
16876 let snapshot = self.buffer().read(cx).snapshot(cx);
16877 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16878 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
16879 row_highlights.retain(|highlight| {
16880 while let Some(range_to_remove) = ranges_to_remove.peek() {
16881 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
16882 Ordering::Less | Ordering::Equal => {
16883 ranges_to_remove.next();
16884 }
16885 Ordering::Greater => {
16886 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
16887 Ordering::Less | Ordering::Equal => {
16888 return false;
16889 }
16890 Ordering::Greater => break,
16891 }
16892 }
16893 }
16894 }
16895
16896 true
16897 })
16898 }
16899
16900 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
16901 pub fn clear_row_highlights<T: 'static>(&mut self) {
16902 self.highlighted_rows.remove(&TypeId::of::<T>());
16903 }
16904
16905 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
16906 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
16907 self.highlighted_rows
16908 .get(&TypeId::of::<T>())
16909 .map_or(&[] as &[_], |vec| vec.as_slice())
16910 .iter()
16911 .map(|highlight| (highlight.range.clone(), highlight.color))
16912 }
16913
16914 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
16915 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
16916 /// Allows to ignore certain kinds of highlights.
16917 pub fn highlighted_display_rows(
16918 &self,
16919 window: &mut Window,
16920 cx: &mut App,
16921 ) -> BTreeMap<DisplayRow, LineHighlight> {
16922 let snapshot = self.snapshot(window, cx);
16923 let mut used_highlight_orders = HashMap::default();
16924 self.highlighted_rows
16925 .iter()
16926 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
16927 .fold(
16928 BTreeMap::<DisplayRow, LineHighlight>::new(),
16929 |mut unique_rows, highlight| {
16930 let start = highlight.range.start.to_display_point(&snapshot);
16931 let end = highlight.range.end.to_display_point(&snapshot);
16932 let start_row = start.row().0;
16933 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
16934 && end.column() == 0
16935 {
16936 end.row().0.saturating_sub(1)
16937 } else {
16938 end.row().0
16939 };
16940 for row in start_row..=end_row {
16941 let used_index =
16942 used_highlight_orders.entry(row).or_insert(highlight.index);
16943 if highlight.index >= *used_index {
16944 *used_index = highlight.index;
16945 unique_rows.insert(DisplayRow(row), highlight.color.into());
16946 }
16947 }
16948 unique_rows
16949 },
16950 )
16951 }
16952
16953 pub fn highlighted_display_row_for_autoscroll(
16954 &self,
16955 snapshot: &DisplaySnapshot,
16956 ) -> Option<DisplayRow> {
16957 self.highlighted_rows
16958 .values()
16959 .flat_map(|highlighted_rows| highlighted_rows.iter())
16960 .filter_map(|highlight| {
16961 if highlight.should_autoscroll {
16962 Some(highlight.range.start.to_display_point(snapshot).row())
16963 } else {
16964 None
16965 }
16966 })
16967 .min()
16968 }
16969
16970 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
16971 self.highlight_background::<SearchWithinRange>(
16972 ranges,
16973 |colors| colors.editor_document_highlight_read_background,
16974 cx,
16975 )
16976 }
16977
16978 pub fn set_breadcrumb_header(&mut self, new_header: String) {
16979 self.breadcrumb_header = Some(new_header);
16980 }
16981
16982 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
16983 self.clear_background_highlights::<SearchWithinRange>(cx);
16984 }
16985
16986 pub fn highlight_background<T: 'static>(
16987 &mut self,
16988 ranges: &[Range<Anchor>],
16989 color_fetcher: fn(&ThemeColors) -> Hsla,
16990 cx: &mut Context<Self>,
16991 ) {
16992 self.background_highlights
16993 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16994 self.scrollbar_marker_state.dirty = true;
16995 cx.notify();
16996 }
16997
16998 pub fn clear_background_highlights<T: 'static>(
16999 &mut self,
17000 cx: &mut Context<Self>,
17001 ) -> Option<BackgroundHighlight> {
17002 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17003 if !text_highlights.1.is_empty() {
17004 self.scrollbar_marker_state.dirty = true;
17005 cx.notify();
17006 }
17007 Some(text_highlights)
17008 }
17009
17010 pub fn highlight_gutter<T: 'static>(
17011 &mut self,
17012 ranges: &[Range<Anchor>],
17013 color_fetcher: fn(&App) -> Hsla,
17014 cx: &mut Context<Self>,
17015 ) {
17016 self.gutter_highlights
17017 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17018 cx.notify();
17019 }
17020
17021 pub fn clear_gutter_highlights<T: 'static>(
17022 &mut self,
17023 cx: &mut Context<Self>,
17024 ) -> Option<GutterHighlight> {
17025 cx.notify();
17026 self.gutter_highlights.remove(&TypeId::of::<T>())
17027 }
17028
17029 #[cfg(feature = "test-support")]
17030 pub fn all_text_background_highlights(
17031 &self,
17032 window: &mut Window,
17033 cx: &mut Context<Self>,
17034 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17035 let snapshot = self.snapshot(window, cx);
17036 let buffer = &snapshot.buffer_snapshot;
17037 let start = buffer.anchor_before(0);
17038 let end = buffer.anchor_after(buffer.len());
17039 let theme = cx.theme().colors();
17040 self.background_highlights_in_range(start..end, &snapshot, theme)
17041 }
17042
17043 #[cfg(feature = "test-support")]
17044 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17045 let snapshot = self.buffer().read(cx).snapshot(cx);
17046
17047 let highlights = self
17048 .background_highlights
17049 .get(&TypeId::of::<items::BufferSearchHighlights>());
17050
17051 if let Some((_color, ranges)) = highlights {
17052 ranges
17053 .iter()
17054 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17055 .collect_vec()
17056 } else {
17057 vec![]
17058 }
17059 }
17060
17061 fn document_highlights_for_position<'a>(
17062 &'a self,
17063 position: Anchor,
17064 buffer: &'a MultiBufferSnapshot,
17065 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17066 let read_highlights = self
17067 .background_highlights
17068 .get(&TypeId::of::<DocumentHighlightRead>())
17069 .map(|h| &h.1);
17070 let write_highlights = self
17071 .background_highlights
17072 .get(&TypeId::of::<DocumentHighlightWrite>())
17073 .map(|h| &h.1);
17074 let left_position = position.bias_left(buffer);
17075 let right_position = position.bias_right(buffer);
17076 read_highlights
17077 .into_iter()
17078 .chain(write_highlights)
17079 .flat_map(move |ranges| {
17080 let start_ix = match ranges.binary_search_by(|probe| {
17081 let cmp = probe.end.cmp(&left_position, buffer);
17082 if cmp.is_ge() {
17083 Ordering::Greater
17084 } else {
17085 Ordering::Less
17086 }
17087 }) {
17088 Ok(i) | Err(i) => i,
17089 };
17090
17091 ranges[start_ix..]
17092 .iter()
17093 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17094 })
17095 }
17096
17097 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17098 self.background_highlights
17099 .get(&TypeId::of::<T>())
17100 .map_or(false, |(_, highlights)| !highlights.is_empty())
17101 }
17102
17103 pub fn background_highlights_in_range(
17104 &self,
17105 search_range: Range<Anchor>,
17106 display_snapshot: &DisplaySnapshot,
17107 theme: &ThemeColors,
17108 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17109 let mut results = Vec::new();
17110 for (color_fetcher, ranges) in self.background_highlights.values() {
17111 let color = color_fetcher(theme);
17112 let start_ix = match ranges.binary_search_by(|probe| {
17113 let cmp = probe
17114 .end
17115 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17116 if cmp.is_gt() {
17117 Ordering::Greater
17118 } else {
17119 Ordering::Less
17120 }
17121 }) {
17122 Ok(i) | Err(i) => i,
17123 };
17124 for range in &ranges[start_ix..] {
17125 if range
17126 .start
17127 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17128 .is_ge()
17129 {
17130 break;
17131 }
17132
17133 let start = range.start.to_display_point(display_snapshot);
17134 let end = range.end.to_display_point(display_snapshot);
17135 results.push((start..end, color))
17136 }
17137 }
17138 results
17139 }
17140
17141 pub fn background_highlight_row_ranges<T: 'static>(
17142 &self,
17143 search_range: Range<Anchor>,
17144 display_snapshot: &DisplaySnapshot,
17145 count: usize,
17146 ) -> Vec<RangeInclusive<DisplayPoint>> {
17147 let mut results = Vec::new();
17148 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
17149 return vec![];
17150 };
17151
17152 let start_ix = match ranges.binary_search_by(|probe| {
17153 let cmp = probe
17154 .end
17155 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17156 if cmp.is_gt() {
17157 Ordering::Greater
17158 } else {
17159 Ordering::Less
17160 }
17161 }) {
17162 Ok(i) | Err(i) => i,
17163 };
17164 let mut push_region = |start: Option<Point>, end: Option<Point>| {
17165 if let (Some(start_display), Some(end_display)) = (start, end) {
17166 results.push(
17167 start_display.to_display_point(display_snapshot)
17168 ..=end_display.to_display_point(display_snapshot),
17169 );
17170 }
17171 };
17172 let mut start_row: Option<Point> = None;
17173 let mut end_row: Option<Point> = None;
17174 if ranges.len() > count {
17175 return Vec::new();
17176 }
17177 for range in &ranges[start_ix..] {
17178 if range
17179 .start
17180 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17181 .is_ge()
17182 {
17183 break;
17184 }
17185 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
17186 if let Some(current_row) = &end_row {
17187 if end.row == current_row.row {
17188 continue;
17189 }
17190 }
17191 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
17192 if start_row.is_none() {
17193 assert_eq!(end_row, None);
17194 start_row = Some(start);
17195 end_row = Some(end);
17196 continue;
17197 }
17198 if let Some(current_end) = end_row.as_mut() {
17199 if start.row > current_end.row + 1 {
17200 push_region(start_row, end_row);
17201 start_row = Some(start);
17202 end_row = Some(end);
17203 } else {
17204 // Merge two hunks.
17205 *current_end = end;
17206 }
17207 } else {
17208 unreachable!();
17209 }
17210 }
17211 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
17212 push_region(start_row, end_row);
17213 results
17214 }
17215
17216 pub fn gutter_highlights_in_range(
17217 &self,
17218 search_range: Range<Anchor>,
17219 display_snapshot: &DisplaySnapshot,
17220 cx: &App,
17221 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17222 let mut results = Vec::new();
17223 for (color_fetcher, ranges) in self.gutter_highlights.values() {
17224 let color = color_fetcher(cx);
17225 let start_ix = match ranges.binary_search_by(|probe| {
17226 let cmp = probe
17227 .end
17228 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17229 if cmp.is_gt() {
17230 Ordering::Greater
17231 } else {
17232 Ordering::Less
17233 }
17234 }) {
17235 Ok(i) | Err(i) => i,
17236 };
17237 for range in &ranges[start_ix..] {
17238 if range
17239 .start
17240 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17241 .is_ge()
17242 {
17243 break;
17244 }
17245
17246 let start = range.start.to_display_point(display_snapshot);
17247 let end = range.end.to_display_point(display_snapshot);
17248 results.push((start..end, color))
17249 }
17250 }
17251 results
17252 }
17253
17254 /// Get the text ranges corresponding to the redaction query
17255 pub fn redacted_ranges(
17256 &self,
17257 search_range: Range<Anchor>,
17258 display_snapshot: &DisplaySnapshot,
17259 cx: &App,
17260 ) -> Vec<Range<DisplayPoint>> {
17261 display_snapshot
17262 .buffer_snapshot
17263 .redacted_ranges(search_range, |file| {
17264 if let Some(file) = file {
17265 file.is_private()
17266 && EditorSettings::get(
17267 Some(SettingsLocation {
17268 worktree_id: file.worktree_id(cx),
17269 path: file.path().as_ref(),
17270 }),
17271 cx,
17272 )
17273 .redact_private_values
17274 } else {
17275 false
17276 }
17277 })
17278 .map(|range| {
17279 range.start.to_display_point(display_snapshot)
17280 ..range.end.to_display_point(display_snapshot)
17281 })
17282 .collect()
17283 }
17284
17285 pub fn highlight_text<T: 'static>(
17286 &mut self,
17287 ranges: Vec<Range<Anchor>>,
17288 style: HighlightStyle,
17289 cx: &mut Context<Self>,
17290 ) {
17291 self.display_map.update(cx, |map, _| {
17292 map.highlight_text(TypeId::of::<T>(), ranges, style)
17293 });
17294 cx.notify();
17295 }
17296
17297 pub(crate) fn highlight_inlays<T: 'static>(
17298 &mut self,
17299 highlights: Vec<InlayHighlight>,
17300 style: HighlightStyle,
17301 cx: &mut Context<Self>,
17302 ) {
17303 self.display_map.update(cx, |map, _| {
17304 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
17305 });
17306 cx.notify();
17307 }
17308
17309 pub fn text_highlights<'a, T: 'static>(
17310 &'a self,
17311 cx: &'a App,
17312 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
17313 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
17314 }
17315
17316 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
17317 let cleared = self
17318 .display_map
17319 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
17320 if cleared {
17321 cx.notify();
17322 }
17323 }
17324
17325 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
17326 (self.read_only(cx) || self.blink_manager.read(cx).visible())
17327 && self.focus_handle.is_focused(window)
17328 }
17329
17330 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
17331 self.show_cursor_when_unfocused = is_enabled;
17332 cx.notify();
17333 }
17334
17335 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
17336 cx.notify();
17337 }
17338
17339 fn on_buffer_event(
17340 &mut self,
17341 multibuffer: &Entity<MultiBuffer>,
17342 event: &multi_buffer::Event,
17343 window: &mut Window,
17344 cx: &mut Context<Self>,
17345 ) {
17346 match event {
17347 multi_buffer::Event::Edited {
17348 singleton_buffer_edited,
17349 edited_buffer: buffer_edited,
17350 } => {
17351 self.scrollbar_marker_state.dirty = true;
17352 self.active_indent_guides_state.dirty = true;
17353 self.refresh_active_diagnostics(cx);
17354 self.refresh_code_actions(window, cx);
17355 if self.has_active_inline_completion() {
17356 self.update_visible_inline_completion(window, cx);
17357 }
17358 if let Some(buffer) = buffer_edited {
17359 let buffer_id = buffer.read(cx).remote_id();
17360 if !self.registered_buffers.contains_key(&buffer_id) {
17361 if let Some(project) = self.project.as_ref() {
17362 project.update(cx, |project, cx| {
17363 self.registered_buffers.insert(
17364 buffer_id,
17365 project.register_buffer_with_language_servers(&buffer, cx),
17366 );
17367 })
17368 }
17369 }
17370 }
17371 cx.emit(EditorEvent::BufferEdited);
17372 cx.emit(SearchEvent::MatchesInvalidated);
17373 if *singleton_buffer_edited {
17374 if let Some(project) = &self.project {
17375 #[allow(clippy::mutable_key_type)]
17376 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
17377 multibuffer
17378 .all_buffers()
17379 .into_iter()
17380 .filter_map(|buffer| {
17381 buffer.update(cx, |buffer, cx| {
17382 let language = buffer.language()?;
17383 let should_discard = project.update(cx, |project, cx| {
17384 project.is_local()
17385 && !project.has_language_servers_for(buffer, cx)
17386 });
17387 should_discard.not().then_some(language.clone())
17388 })
17389 })
17390 .collect::<HashSet<_>>()
17391 });
17392 if !languages_affected.is_empty() {
17393 self.refresh_inlay_hints(
17394 InlayHintRefreshReason::BufferEdited(languages_affected),
17395 cx,
17396 );
17397 }
17398 }
17399 }
17400
17401 let Some(project) = &self.project else { return };
17402 let (telemetry, is_via_ssh) = {
17403 let project = project.read(cx);
17404 let telemetry = project.client().telemetry().clone();
17405 let is_via_ssh = project.is_via_ssh();
17406 (telemetry, is_via_ssh)
17407 };
17408 refresh_linked_ranges(self, window, cx);
17409 telemetry.log_edit_event("editor", is_via_ssh);
17410 }
17411 multi_buffer::Event::ExcerptsAdded {
17412 buffer,
17413 predecessor,
17414 excerpts,
17415 } => {
17416 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17417 let buffer_id = buffer.read(cx).remote_id();
17418 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
17419 if let Some(project) = &self.project {
17420 get_uncommitted_diff_for_buffer(
17421 project,
17422 [buffer.clone()],
17423 self.buffer.clone(),
17424 cx,
17425 )
17426 .detach();
17427 }
17428 }
17429 cx.emit(EditorEvent::ExcerptsAdded {
17430 buffer: buffer.clone(),
17431 predecessor: *predecessor,
17432 excerpts: excerpts.clone(),
17433 });
17434 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17435 }
17436 multi_buffer::Event::ExcerptsRemoved { ids } => {
17437 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
17438 let buffer = self.buffer.read(cx);
17439 self.registered_buffers
17440 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
17441 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17442 cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
17443 }
17444 multi_buffer::Event::ExcerptsEdited {
17445 excerpt_ids,
17446 buffer_ids,
17447 } => {
17448 self.display_map.update(cx, |map, cx| {
17449 map.unfold_buffers(buffer_ids.iter().copied(), cx)
17450 });
17451 cx.emit(EditorEvent::ExcerptsEdited {
17452 ids: excerpt_ids.clone(),
17453 })
17454 }
17455 multi_buffer::Event::ExcerptsExpanded { ids } => {
17456 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17457 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
17458 }
17459 multi_buffer::Event::Reparsed(buffer_id) => {
17460 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17461 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17462
17463 cx.emit(EditorEvent::Reparsed(*buffer_id));
17464 }
17465 multi_buffer::Event::DiffHunksToggled => {
17466 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17467 }
17468 multi_buffer::Event::LanguageChanged(buffer_id) => {
17469 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
17470 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17471 cx.emit(EditorEvent::Reparsed(*buffer_id));
17472 cx.notify();
17473 }
17474 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
17475 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
17476 multi_buffer::Event::FileHandleChanged
17477 | multi_buffer::Event::Reloaded
17478 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
17479 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
17480 multi_buffer::Event::DiagnosticsUpdated => {
17481 self.refresh_active_diagnostics(cx);
17482 self.refresh_inline_diagnostics(true, window, cx);
17483 self.scrollbar_marker_state.dirty = true;
17484 cx.notify();
17485 }
17486 _ => {}
17487 };
17488 }
17489
17490 fn on_display_map_changed(
17491 &mut self,
17492 _: Entity<DisplayMap>,
17493 _: &mut Window,
17494 cx: &mut Context<Self>,
17495 ) {
17496 cx.notify();
17497 }
17498
17499 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17500 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17501 self.update_edit_prediction_settings(cx);
17502 self.refresh_inline_completion(true, false, window, cx);
17503 self.refresh_inlay_hints(
17504 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
17505 self.selections.newest_anchor().head(),
17506 &self.buffer.read(cx).snapshot(cx),
17507 cx,
17508 )),
17509 cx,
17510 );
17511
17512 let old_cursor_shape = self.cursor_shape;
17513
17514 {
17515 let editor_settings = EditorSettings::get_global(cx);
17516 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
17517 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
17518 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
17519 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
17520 }
17521
17522 if old_cursor_shape != self.cursor_shape {
17523 cx.emit(EditorEvent::CursorShapeChanged);
17524 }
17525
17526 let project_settings = ProjectSettings::get_global(cx);
17527 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
17528
17529 if self.mode.is_full() {
17530 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
17531 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
17532 if self.show_inline_diagnostics != show_inline_diagnostics {
17533 self.show_inline_diagnostics = show_inline_diagnostics;
17534 self.refresh_inline_diagnostics(false, window, cx);
17535 }
17536
17537 if self.git_blame_inline_enabled != inline_blame_enabled {
17538 self.toggle_git_blame_inline_internal(false, window, cx);
17539 }
17540 }
17541
17542 cx.notify();
17543 }
17544
17545 pub fn set_searchable(&mut self, searchable: bool) {
17546 self.searchable = searchable;
17547 }
17548
17549 pub fn searchable(&self) -> bool {
17550 self.searchable
17551 }
17552
17553 fn open_proposed_changes_editor(
17554 &mut self,
17555 _: &OpenProposedChangesEditor,
17556 window: &mut Window,
17557 cx: &mut Context<Self>,
17558 ) {
17559 let Some(workspace) = self.workspace() else {
17560 cx.propagate();
17561 return;
17562 };
17563
17564 let selections = self.selections.all::<usize>(cx);
17565 let multi_buffer = self.buffer.read(cx);
17566 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17567 let mut new_selections_by_buffer = HashMap::default();
17568 for selection in selections {
17569 for (buffer, range, _) in
17570 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
17571 {
17572 let mut range = range.to_point(buffer);
17573 range.start.column = 0;
17574 range.end.column = buffer.line_len(range.end.row);
17575 new_selections_by_buffer
17576 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
17577 .or_insert(Vec::new())
17578 .push(range)
17579 }
17580 }
17581
17582 let proposed_changes_buffers = new_selections_by_buffer
17583 .into_iter()
17584 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
17585 .collect::<Vec<_>>();
17586 let proposed_changes_editor = cx.new(|cx| {
17587 ProposedChangesEditor::new(
17588 "Proposed changes",
17589 proposed_changes_buffers,
17590 self.project.clone(),
17591 window,
17592 cx,
17593 )
17594 });
17595
17596 window.defer(cx, move |window, cx| {
17597 workspace.update(cx, |workspace, cx| {
17598 workspace.active_pane().update(cx, |pane, cx| {
17599 pane.add_item(
17600 Box::new(proposed_changes_editor),
17601 true,
17602 true,
17603 None,
17604 window,
17605 cx,
17606 );
17607 });
17608 });
17609 });
17610 }
17611
17612 pub fn open_excerpts_in_split(
17613 &mut self,
17614 _: &OpenExcerptsSplit,
17615 window: &mut Window,
17616 cx: &mut Context<Self>,
17617 ) {
17618 self.open_excerpts_common(None, true, window, cx)
17619 }
17620
17621 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
17622 self.open_excerpts_common(None, false, window, cx)
17623 }
17624
17625 fn open_excerpts_common(
17626 &mut self,
17627 jump_data: Option<JumpData>,
17628 split: bool,
17629 window: &mut Window,
17630 cx: &mut Context<Self>,
17631 ) {
17632 let Some(workspace) = self.workspace() else {
17633 cx.propagate();
17634 return;
17635 };
17636
17637 if self.buffer.read(cx).is_singleton() {
17638 cx.propagate();
17639 return;
17640 }
17641
17642 let mut new_selections_by_buffer = HashMap::default();
17643 match &jump_data {
17644 Some(JumpData::MultiBufferPoint {
17645 excerpt_id,
17646 position,
17647 anchor,
17648 line_offset_from_top,
17649 }) => {
17650 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17651 if let Some(buffer) = multi_buffer_snapshot
17652 .buffer_id_for_excerpt(*excerpt_id)
17653 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
17654 {
17655 let buffer_snapshot = buffer.read(cx).snapshot();
17656 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
17657 language::ToPoint::to_point(anchor, &buffer_snapshot)
17658 } else {
17659 buffer_snapshot.clip_point(*position, Bias::Left)
17660 };
17661 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
17662 new_selections_by_buffer.insert(
17663 buffer,
17664 (
17665 vec![jump_to_offset..jump_to_offset],
17666 Some(*line_offset_from_top),
17667 ),
17668 );
17669 }
17670 }
17671 Some(JumpData::MultiBufferRow {
17672 row,
17673 line_offset_from_top,
17674 }) => {
17675 let point = MultiBufferPoint::new(row.0, 0);
17676 if let Some((buffer, buffer_point, _)) =
17677 self.buffer.read(cx).point_to_buffer_point(point, cx)
17678 {
17679 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
17680 new_selections_by_buffer
17681 .entry(buffer)
17682 .or_insert((Vec::new(), Some(*line_offset_from_top)))
17683 .0
17684 .push(buffer_offset..buffer_offset)
17685 }
17686 }
17687 None => {
17688 let selections = self.selections.all::<usize>(cx);
17689 let multi_buffer = self.buffer.read(cx);
17690 for selection in selections {
17691 for (snapshot, range, _, anchor) in multi_buffer
17692 .snapshot(cx)
17693 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
17694 {
17695 if let Some(anchor) = anchor {
17696 // selection is in a deleted hunk
17697 let Some(buffer_id) = anchor.buffer_id else {
17698 continue;
17699 };
17700 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
17701 continue;
17702 };
17703 let offset = text::ToOffset::to_offset(
17704 &anchor.text_anchor,
17705 &buffer_handle.read(cx).snapshot(),
17706 );
17707 let range = offset..offset;
17708 new_selections_by_buffer
17709 .entry(buffer_handle)
17710 .or_insert((Vec::new(), None))
17711 .0
17712 .push(range)
17713 } else {
17714 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
17715 else {
17716 continue;
17717 };
17718 new_selections_by_buffer
17719 .entry(buffer_handle)
17720 .or_insert((Vec::new(), None))
17721 .0
17722 .push(range)
17723 }
17724 }
17725 }
17726 }
17727 }
17728
17729 new_selections_by_buffer
17730 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
17731
17732 if new_selections_by_buffer.is_empty() {
17733 return;
17734 }
17735
17736 // We defer the pane interaction because we ourselves are a workspace item
17737 // and activating a new item causes the pane to call a method on us reentrantly,
17738 // which panics if we're on the stack.
17739 window.defer(cx, move |window, cx| {
17740 workspace.update(cx, |workspace, cx| {
17741 let pane = if split {
17742 workspace.adjacent_pane(window, cx)
17743 } else {
17744 workspace.active_pane().clone()
17745 };
17746
17747 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
17748 let editor = buffer
17749 .read(cx)
17750 .file()
17751 .is_none()
17752 .then(|| {
17753 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
17754 // so `workspace.open_project_item` will never find them, always opening a new editor.
17755 // Instead, we try to activate the existing editor in the pane first.
17756 let (editor, pane_item_index) =
17757 pane.read(cx).items().enumerate().find_map(|(i, item)| {
17758 let editor = item.downcast::<Editor>()?;
17759 let singleton_buffer =
17760 editor.read(cx).buffer().read(cx).as_singleton()?;
17761 if singleton_buffer == buffer {
17762 Some((editor, i))
17763 } else {
17764 None
17765 }
17766 })?;
17767 pane.update(cx, |pane, cx| {
17768 pane.activate_item(pane_item_index, true, true, window, cx)
17769 });
17770 Some(editor)
17771 })
17772 .flatten()
17773 .unwrap_or_else(|| {
17774 workspace.open_project_item::<Self>(
17775 pane.clone(),
17776 buffer,
17777 true,
17778 true,
17779 window,
17780 cx,
17781 )
17782 });
17783
17784 editor.update(cx, |editor, cx| {
17785 let autoscroll = match scroll_offset {
17786 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
17787 None => Autoscroll::newest(),
17788 };
17789 let nav_history = editor.nav_history.take();
17790 editor.change_selections(Some(autoscroll), window, cx, |s| {
17791 s.select_ranges(ranges);
17792 });
17793 editor.nav_history = nav_history;
17794 });
17795 }
17796 })
17797 });
17798 }
17799
17800 // For now, don't allow opening excerpts in buffers that aren't backed by
17801 // regular project files.
17802 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
17803 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
17804 }
17805
17806 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
17807 let snapshot = self.buffer.read(cx).read(cx);
17808 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
17809 Some(
17810 ranges
17811 .iter()
17812 .map(move |range| {
17813 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
17814 })
17815 .collect(),
17816 )
17817 }
17818
17819 fn selection_replacement_ranges(
17820 &self,
17821 range: Range<OffsetUtf16>,
17822 cx: &mut App,
17823 ) -> Vec<Range<OffsetUtf16>> {
17824 let selections = self.selections.all::<OffsetUtf16>(cx);
17825 let newest_selection = selections
17826 .iter()
17827 .max_by_key(|selection| selection.id)
17828 .unwrap();
17829 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
17830 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
17831 let snapshot = self.buffer.read(cx).read(cx);
17832 selections
17833 .into_iter()
17834 .map(|mut selection| {
17835 selection.start.0 =
17836 (selection.start.0 as isize).saturating_add(start_delta) as usize;
17837 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
17838 snapshot.clip_offset_utf16(selection.start, Bias::Left)
17839 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
17840 })
17841 .collect()
17842 }
17843
17844 fn report_editor_event(
17845 &self,
17846 event_type: &'static str,
17847 file_extension: Option<String>,
17848 cx: &App,
17849 ) {
17850 if cfg!(any(test, feature = "test-support")) {
17851 return;
17852 }
17853
17854 let Some(project) = &self.project else { return };
17855
17856 // If None, we are in a file without an extension
17857 let file = self
17858 .buffer
17859 .read(cx)
17860 .as_singleton()
17861 .and_then(|b| b.read(cx).file());
17862 let file_extension = file_extension.or(file
17863 .as_ref()
17864 .and_then(|file| Path::new(file.file_name(cx)).extension())
17865 .and_then(|e| e.to_str())
17866 .map(|a| a.to_string()));
17867
17868 let vim_mode = vim_enabled(cx);
17869
17870 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
17871 let copilot_enabled = edit_predictions_provider
17872 == language::language_settings::EditPredictionProvider::Copilot;
17873 let copilot_enabled_for_language = self
17874 .buffer
17875 .read(cx)
17876 .language_settings(cx)
17877 .show_edit_predictions;
17878
17879 let project = project.read(cx);
17880 telemetry::event!(
17881 event_type,
17882 file_extension,
17883 vim_mode,
17884 copilot_enabled,
17885 copilot_enabled_for_language,
17886 edit_predictions_provider,
17887 is_via_ssh = project.is_via_ssh(),
17888 );
17889 }
17890
17891 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
17892 /// with each line being an array of {text, highlight} objects.
17893 fn copy_highlight_json(
17894 &mut self,
17895 _: &CopyHighlightJson,
17896 window: &mut Window,
17897 cx: &mut Context<Self>,
17898 ) {
17899 #[derive(Serialize)]
17900 struct Chunk<'a> {
17901 text: String,
17902 highlight: Option<&'a str>,
17903 }
17904
17905 let snapshot = self.buffer.read(cx).snapshot(cx);
17906 let range = self
17907 .selected_text_range(false, window, cx)
17908 .and_then(|selection| {
17909 if selection.range.is_empty() {
17910 None
17911 } else {
17912 Some(selection.range)
17913 }
17914 })
17915 .unwrap_or_else(|| 0..snapshot.len());
17916
17917 let chunks = snapshot.chunks(range, true);
17918 let mut lines = Vec::new();
17919 let mut line: VecDeque<Chunk> = VecDeque::new();
17920
17921 let Some(style) = self.style.as_ref() else {
17922 return;
17923 };
17924
17925 for chunk in chunks {
17926 let highlight = chunk
17927 .syntax_highlight_id
17928 .and_then(|id| id.name(&style.syntax));
17929 let mut chunk_lines = chunk.text.split('\n').peekable();
17930 while let Some(text) = chunk_lines.next() {
17931 let mut merged_with_last_token = false;
17932 if let Some(last_token) = line.back_mut() {
17933 if last_token.highlight == highlight {
17934 last_token.text.push_str(text);
17935 merged_with_last_token = true;
17936 }
17937 }
17938
17939 if !merged_with_last_token {
17940 line.push_back(Chunk {
17941 text: text.into(),
17942 highlight,
17943 });
17944 }
17945
17946 if chunk_lines.peek().is_some() {
17947 if line.len() > 1 && line.front().unwrap().text.is_empty() {
17948 line.pop_front();
17949 }
17950 if line.len() > 1 && line.back().unwrap().text.is_empty() {
17951 line.pop_back();
17952 }
17953
17954 lines.push(mem::take(&mut line));
17955 }
17956 }
17957 }
17958
17959 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
17960 return;
17961 };
17962 cx.write_to_clipboard(ClipboardItem::new_string(lines));
17963 }
17964
17965 pub fn open_context_menu(
17966 &mut self,
17967 _: &OpenContextMenu,
17968 window: &mut Window,
17969 cx: &mut Context<Self>,
17970 ) {
17971 self.request_autoscroll(Autoscroll::newest(), cx);
17972 let position = self.selections.newest_display(cx).start;
17973 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
17974 }
17975
17976 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
17977 &self.inlay_hint_cache
17978 }
17979
17980 pub fn replay_insert_event(
17981 &mut self,
17982 text: &str,
17983 relative_utf16_range: Option<Range<isize>>,
17984 window: &mut Window,
17985 cx: &mut Context<Self>,
17986 ) {
17987 if !self.input_enabled {
17988 cx.emit(EditorEvent::InputIgnored { text: text.into() });
17989 return;
17990 }
17991 if let Some(relative_utf16_range) = relative_utf16_range {
17992 let selections = self.selections.all::<OffsetUtf16>(cx);
17993 self.change_selections(None, window, cx, |s| {
17994 let new_ranges = selections.into_iter().map(|range| {
17995 let start = OffsetUtf16(
17996 range
17997 .head()
17998 .0
17999 .saturating_add_signed(relative_utf16_range.start),
18000 );
18001 let end = OffsetUtf16(
18002 range
18003 .head()
18004 .0
18005 .saturating_add_signed(relative_utf16_range.end),
18006 );
18007 start..end
18008 });
18009 s.select_ranges(new_ranges);
18010 });
18011 }
18012
18013 self.handle_input(text, window, cx);
18014 }
18015
18016 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
18017 let Some(provider) = self.semantics_provider.as_ref() else {
18018 return false;
18019 };
18020
18021 let mut supports = false;
18022 self.buffer().update(cx, |this, cx| {
18023 this.for_each_buffer(|buffer| {
18024 supports |= provider.supports_inlay_hints(buffer, cx);
18025 });
18026 });
18027
18028 supports
18029 }
18030
18031 pub fn is_focused(&self, window: &Window) -> bool {
18032 self.focus_handle.is_focused(window)
18033 }
18034
18035 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18036 cx.emit(EditorEvent::Focused);
18037
18038 if let Some(descendant) = self
18039 .last_focused_descendant
18040 .take()
18041 .and_then(|descendant| descendant.upgrade())
18042 {
18043 window.focus(&descendant);
18044 } else {
18045 if let Some(blame) = self.blame.as_ref() {
18046 blame.update(cx, GitBlame::focus)
18047 }
18048
18049 self.blink_manager.update(cx, BlinkManager::enable);
18050 self.show_cursor_names(window, cx);
18051 self.buffer.update(cx, |buffer, cx| {
18052 buffer.finalize_last_transaction(cx);
18053 if self.leader_peer_id.is_none() {
18054 buffer.set_active_selections(
18055 &self.selections.disjoint_anchors(),
18056 self.selections.line_mode,
18057 self.cursor_shape,
18058 cx,
18059 );
18060 }
18061 });
18062 }
18063 }
18064
18065 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18066 cx.emit(EditorEvent::FocusedIn)
18067 }
18068
18069 fn handle_focus_out(
18070 &mut self,
18071 event: FocusOutEvent,
18072 _window: &mut Window,
18073 cx: &mut Context<Self>,
18074 ) {
18075 if event.blurred != self.focus_handle {
18076 self.last_focused_descendant = Some(event.blurred);
18077 }
18078 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
18079 }
18080
18081 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18082 self.blink_manager.update(cx, BlinkManager::disable);
18083 self.buffer
18084 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
18085
18086 if let Some(blame) = self.blame.as_ref() {
18087 blame.update(cx, GitBlame::blur)
18088 }
18089 if !self.hover_state.focused(window, cx) {
18090 hide_hover(self, cx);
18091 }
18092 if !self
18093 .context_menu
18094 .borrow()
18095 .as_ref()
18096 .is_some_and(|context_menu| context_menu.focused(window, cx))
18097 {
18098 self.hide_context_menu(window, cx);
18099 }
18100 self.discard_inline_completion(false, cx);
18101 cx.emit(EditorEvent::Blurred);
18102 cx.notify();
18103 }
18104
18105 pub fn register_action<A: Action>(
18106 &mut self,
18107 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
18108 ) -> Subscription {
18109 let id = self.next_editor_action_id.post_inc();
18110 let listener = Arc::new(listener);
18111 self.editor_actions.borrow_mut().insert(
18112 id,
18113 Box::new(move |window, _| {
18114 let listener = listener.clone();
18115 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
18116 let action = action.downcast_ref().unwrap();
18117 if phase == DispatchPhase::Bubble {
18118 listener(action, window, cx)
18119 }
18120 })
18121 }),
18122 );
18123
18124 let editor_actions = self.editor_actions.clone();
18125 Subscription::new(move || {
18126 editor_actions.borrow_mut().remove(&id);
18127 })
18128 }
18129
18130 pub fn file_header_size(&self) -> u32 {
18131 FILE_HEADER_HEIGHT
18132 }
18133
18134 pub fn restore(
18135 &mut self,
18136 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
18137 window: &mut Window,
18138 cx: &mut Context<Self>,
18139 ) {
18140 let workspace = self.workspace();
18141 let project = self.project.as_ref();
18142 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
18143 let mut tasks = Vec::new();
18144 for (buffer_id, changes) in revert_changes {
18145 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
18146 buffer.update(cx, |buffer, cx| {
18147 buffer.edit(
18148 changes
18149 .into_iter()
18150 .map(|(range, text)| (range, text.to_string())),
18151 None,
18152 cx,
18153 );
18154 });
18155
18156 if let Some(project) =
18157 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
18158 {
18159 project.update(cx, |project, cx| {
18160 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
18161 })
18162 }
18163 }
18164 }
18165 tasks
18166 });
18167 cx.spawn_in(window, async move |_, cx| {
18168 for (buffer, task) in save_tasks {
18169 let result = task.await;
18170 if result.is_err() {
18171 let Some(path) = buffer
18172 .read_with(cx, |buffer, cx| buffer.project_path(cx))
18173 .ok()
18174 else {
18175 continue;
18176 };
18177 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
18178 let Some(task) = cx
18179 .update_window_entity(&workspace, |workspace, window, cx| {
18180 workspace
18181 .open_path_preview(path, None, false, false, false, window, cx)
18182 })
18183 .ok()
18184 else {
18185 continue;
18186 };
18187 task.await.log_err();
18188 }
18189 }
18190 }
18191 })
18192 .detach();
18193 self.change_selections(None, window, cx, |selections| selections.refresh());
18194 }
18195
18196 pub fn to_pixel_point(
18197 &self,
18198 source: multi_buffer::Anchor,
18199 editor_snapshot: &EditorSnapshot,
18200 window: &mut Window,
18201 ) -> Option<gpui::Point<Pixels>> {
18202 let source_point = source.to_display_point(editor_snapshot);
18203 self.display_to_pixel_point(source_point, editor_snapshot, window)
18204 }
18205
18206 pub fn display_to_pixel_point(
18207 &self,
18208 source: DisplayPoint,
18209 editor_snapshot: &EditorSnapshot,
18210 window: &mut Window,
18211 ) -> Option<gpui::Point<Pixels>> {
18212 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
18213 let text_layout_details = self.text_layout_details(window);
18214 let scroll_top = text_layout_details
18215 .scroll_anchor
18216 .scroll_position(editor_snapshot)
18217 .y;
18218
18219 if source.row().as_f32() < scroll_top.floor() {
18220 return None;
18221 }
18222 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
18223 let source_y = line_height * (source.row().as_f32() - scroll_top);
18224 Some(gpui::Point::new(source_x, source_y))
18225 }
18226
18227 pub fn has_visible_completions_menu(&self) -> bool {
18228 !self.edit_prediction_preview_is_active()
18229 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
18230 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
18231 })
18232 }
18233
18234 pub fn register_addon<T: Addon>(&mut self, instance: T) {
18235 self.addons
18236 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
18237 }
18238
18239 pub fn unregister_addon<T: Addon>(&mut self) {
18240 self.addons.remove(&std::any::TypeId::of::<T>());
18241 }
18242
18243 pub fn addon<T: Addon>(&self) -> Option<&T> {
18244 let type_id = std::any::TypeId::of::<T>();
18245 self.addons
18246 .get(&type_id)
18247 .and_then(|item| item.to_any().downcast_ref::<T>())
18248 }
18249
18250 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
18251 let text_layout_details = self.text_layout_details(window);
18252 let style = &text_layout_details.editor_style;
18253 let font_id = window.text_system().resolve_font(&style.text.font());
18254 let font_size = style.text.font_size.to_pixels(window.rem_size());
18255 let line_height = style.text.line_height_in_pixels(window.rem_size());
18256 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
18257
18258 gpui::Size::new(em_width, line_height)
18259 }
18260
18261 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
18262 self.load_diff_task.clone()
18263 }
18264
18265 fn read_metadata_from_db(
18266 &mut self,
18267 item_id: u64,
18268 workspace_id: WorkspaceId,
18269 window: &mut Window,
18270 cx: &mut Context<Editor>,
18271 ) {
18272 if self.is_singleton(cx)
18273 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
18274 {
18275 let buffer_snapshot = OnceCell::new();
18276
18277 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
18278 if !folds.is_empty() {
18279 let snapshot =
18280 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18281 self.fold_ranges(
18282 folds
18283 .into_iter()
18284 .map(|(start, end)| {
18285 snapshot.clip_offset(start, Bias::Left)
18286 ..snapshot.clip_offset(end, Bias::Right)
18287 })
18288 .collect(),
18289 false,
18290 window,
18291 cx,
18292 );
18293 }
18294 }
18295
18296 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
18297 if !selections.is_empty() {
18298 let snapshot =
18299 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18300 self.change_selections(None, window, cx, |s| {
18301 s.select_ranges(selections.into_iter().map(|(start, end)| {
18302 snapshot.clip_offset(start, Bias::Left)
18303 ..snapshot.clip_offset(end, Bias::Right)
18304 }));
18305 });
18306 }
18307 };
18308 }
18309
18310 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
18311 }
18312}
18313
18314fn vim_enabled(cx: &App) -> bool {
18315 cx.global::<SettingsStore>()
18316 .raw_user_settings()
18317 .get("vim_mode")
18318 == Some(&serde_json::Value::Bool(true))
18319}
18320
18321// Consider user intent and default settings
18322fn choose_completion_range(
18323 completion: &Completion,
18324 intent: CompletionIntent,
18325 buffer: &Entity<Buffer>,
18326 cx: &mut Context<Editor>,
18327) -> Range<usize> {
18328 fn should_replace(
18329 completion: &Completion,
18330 insert_range: &Range<text::Anchor>,
18331 intent: CompletionIntent,
18332 completion_mode_setting: LspInsertMode,
18333 buffer: &Buffer,
18334 ) -> bool {
18335 // specific actions take precedence over settings
18336 match intent {
18337 CompletionIntent::CompleteWithInsert => return false,
18338 CompletionIntent::CompleteWithReplace => return true,
18339 CompletionIntent::Complete | CompletionIntent::Compose => {}
18340 }
18341
18342 match completion_mode_setting {
18343 LspInsertMode::Insert => false,
18344 LspInsertMode::Replace => true,
18345 LspInsertMode::ReplaceSubsequence => {
18346 let mut text_to_replace = buffer.chars_for_range(
18347 buffer.anchor_before(completion.replace_range.start)
18348 ..buffer.anchor_after(completion.replace_range.end),
18349 );
18350 let mut completion_text = completion.new_text.chars();
18351
18352 // is `text_to_replace` a subsequence of `completion_text`
18353 text_to_replace
18354 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
18355 }
18356 LspInsertMode::ReplaceSuffix => {
18357 let range_after_cursor = insert_range.end..completion.replace_range.end;
18358
18359 let text_after_cursor = buffer
18360 .text_for_range(
18361 buffer.anchor_before(range_after_cursor.start)
18362 ..buffer.anchor_after(range_after_cursor.end),
18363 )
18364 .collect::<String>();
18365 completion.new_text.ends_with(&text_after_cursor)
18366 }
18367 }
18368 }
18369
18370 let buffer = buffer.read(cx);
18371
18372 if let CompletionSource::Lsp {
18373 insert_range: Some(insert_range),
18374 ..
18375 } = &completion.source
18376 {
18377 let completion_mode_setting =
18378 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
18379 .completions
18380 .lsp_insert_mode;
18381
18382 if !should_replace(
18383 completion,
18384 &insert_range,
18385 intent,
18386 completion_mode_setting,
18387 buffer,
18388 ) {
18389 return insert_range.to_offset(buffer);
18390 }
18391 }
18392
18393 completion.replace_range.to_offset(buffer)
18394}
18395
18396fn insert_extra_newline_brackets(
18397 buffer: &MultiBufferSnapshot,
18398 range: Range<usize>,
18399 language: &language::LanguageScope,
18400) -> bool {
18401 let leading_whitespace_len = buffer
18402 .reversed_chars_at(range.start)
18403 .take_while(|c| c.is_whitespace() && *c != '\n')
18404 .map(|c| c.len_utf8())
18405 .sum::<usize>();
18406 let trailing_whitespace_len = buffer
18407 .chars_at(range.end)
18408 .take_while(|c| c.is_whitespace() && *c != '\n')
18409 .map(|c| c.len_utf8())
18410 .sum::<usize>();
18411 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
18412
18413 language.brackets().any(|(pair, enabled)| {
18414 let pair_start = pair.start.trim_end();
18415 let pair_end = pair.end.trim_start();
18416
18417 enabled
18418 && pair.newline
18419 && buffer.contains_str_at(range.end, pair_end)
18420 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
18421 })
18422}
18423
18424fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
18425 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
18426 [(buffer, range, _)] => (*buffer, range.clone()),
18427 _ => return false,
18428 };
18429 let pair = {
18430 let mut result: Option<BracketMatch> = None;
18431
18432 for pair in buffer
18433 .all_bracket_ranges(range.clone())
18434 .filter(move |pair| {
18435 pair.open_range.start <= range.start && pair.close_range.end >= range.end
18436 })
18437 {
18438 let len = pair.close_range.end - pair.open_range.start;
18439
18440 if let Some(existing) = &result {
18441 let existing_len = existing.close_range.end - existing.open_range.start;
18442 if len > existing_len {
18443 continue;
18444 }
18445 }
18446
18447 result = Some(pair);
18448 }
18449
18450 result
18451 };
18452 let Some(pair) = pair else {
18453 return false;
18454 };
18455 pair.newline_only
18456 && buffer
18457 .chars_for_range(pair.open_range.end..range.start)
18458 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
18459 .all(|c| c.is_whitespace() && c != '\n')
18460}
18461
18462fn get_uncommitted_diff_for_buffer(
18463 project: &Entity<Project>,
18464 buffers: impl IntoIterator<Item = Entity<Buffer>>,
18465 buffer: Entity<MultiBuffer>,
18466 cx: &mut App,
18467) -> Task<()> {
18468 let mut tasks = Vec::new();
18469 project.update(cx, |project, cx| {
18470 for buffer in buffers {
18471 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
18472 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
18473 }
18474 }
18475 });
18476 cx.spawn(async move |cx| {
18477 let diffs = future::join_all(tasks).await;
18478 buffer
18479 .update(cx, |buffer, cx| {
18480 for diff in diffs.into_iter().flatten() {
18481 buffer.add_diff(diff, cx);
18482 }
18483 })
18484 .ok();
18485 })
18486}
18487
18488fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
18489 let tab_size = tab_size.get() as usize;
18490 let mut width = offset;
18491
18492 for ch in text.chars() {
18493 width += if ch == '\t' {
18494 tab_size - (width % tab_size)
18495 } else {
18496 1
18497 };
18498 }
18499
18500 width - offset
18501}
18502
18503#[cfg(test)]
18504mod tests {
18505 use super::*;
18506
18507 #[test]
18508 fn test_string_size_with_expanded_tabs() {
18509 let nz = |val| NonZeroU32::new(val).unwrap();
18510 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
18511 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
18512 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
18513 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
18514 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
18515 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
18516 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
18517 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
18518 }
18519}
18520
18521/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
18522struct WordBreakingTokenizer<'a> {
18523 input: &'a str,
18524}
18525
18526impl<'a> WordBreakingTokenizer<'a> {
18527 fn new(input: &'a str) -> Self {
18528 Self { input }
18529 }
18530}
18531
18532fn is_char_ideographic(ch: char) -> bool {
18533 use unicode_script::Script::*;
18534 use unicode_script::UnicodeScript;
18535 matches!(ch.script(), Han | Tangut | Yi)
18536}
18537
18538fn is_grapheme_ideographic(text: &str) -> bool {
18539 text.chars().any(is_char_ideographic)
18540}
18541
18542fn is_grapheme_whitespace(text: &str) -> bool {
18543 text.chars().any(|x| x.is_whitespace())
18544}
18545
18546fn should_stay_with_preceding_ideograph(text: &str) -> bool {
18547 text.chars().next().map_or(false, |ch| {
18548 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
18549 })
18550}
18551
18552#[derive(PartialEq, Eq, Debug, Clone, Copy)]
18553enum WordBreakToken<'a> {
18554 Word { token: &'a str, grapheme_len: usize },
18555 InlineWhitespace { token: &'a str, grapheme_len: usize },
18556 Newline,
18557}
18558
18559impl<'a> Iterator for WordBreakingTokenizer<'a> {
18560 /// Yields a span, the count of graphemes in the token, and whether it was
18561 /// whitespace. Note that it also breaks at word boundaries.
18562 type Item = WordBreakToken<'a>;
18563
18564 fn next(&mut self) -> Option<Self::Item> {
18565 use unicode_segmentation::UnicodeSegmentation;
18566 if self.input.is_empty() {
18567 return None;
18568 }
18569
18570 let mut iter = self.input.graphemes(true).peekable();
18571 let mut offset = 0;
18572 let mut grapheme_len = 0;
18573 if let Some(first_grapheme) = iter.next() {
18574 let is_newline = first_grapheme == "\n";
18575 let is_whitespace = is_grapheme_whitespace(first_grapheme);
18576 offset += first_grapheme.len();
18577 grapheme_len += 1;
18578 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
18579 if let Some(grapheme) = iter.peek().copied() {
18580 if should_stay_with_preceding_ideograph(grapheme) {
18581 offset += grapheme.len();
18582 grapheme_len += 1;
18583 }
18584 }
18585 } else {
18586 let mut words = self.input[offset..].split_word_bound_indices().peekable();
18587 let mut next_word_bound = words.peek().copied();
18588 if next_word_bound.map_or(false, |(i, _)| i == 0) {
18589 next_word_bound = words.next();
18590 }
18591 while let Some(grapheme) = iter.peek().copied() {
18592 if next_word_bound.map_or(false, |(i, _)| i == offset) {
18593 break;
18594 };
18595 if is_grapheme_whitespace(grapheme) != is_whitespace
18596 || (grapheme == "\n") != is_newline
18597 {
18598 break;
18599 };
18600 offset += grapheme.len();
18601 grapheme_len += 1;
18602 iter.next();
18603 }
18604 }
18605 let token = &self.input[..offset];
18606 self.input = &self.input[offset..];
18607 if token == "\n" {
18608 Some(WordBreakToken::Newline)
18609 } else if is_whitespace {
18610 Some(WordBreakToken::InlineWhitespace {
18611 token,
18612 grapheme_len,
18613 })
18614 } else {
18615 Some(WordBreakToken::Word {
18616 token,
18617 grapheme_len,
18618 })
18619 }
18620 } else {
18621 None
18622 }
18623 }
18624}
18625
18626#[test]
18627fn test_word_breaking_tokenizer() {
18628 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
18629 ("", &[]),
18630 (" ", &[whitespace(" ", 2)]),
18631 ("Ʒ", &[word("Ʒ", 1)]),
18632 ("Ǽ", &[word("Ǽ", 1)]),
18633 ("⋑", &[word("⋑", 1)]),
18634 ("⋑⋑", &[word("⋑⋑", 2)]),
18635 (
18636 "原理,进而",
18637 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
18638 ),
18639 (
18640 "hello world",
18641 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
18642 ),
18643 (
18644 "hello, world",
18645 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
18646 ),
18647 (
18648 " hello world",
18649 &[
18650 whitespace(" ", 2),
18651 word("hello", 5),
18652 whitespace(" ", 1),
18653 word("world", 5),
18654 ],
18655 ),
18656 (
18657 "这是什么 \n 钢笔",
18658 &[
18659 word("这", 1),
18660 word("是", 1),
18661 word("什", 1),
18662 word("么", 1),
18663 whitespace(" ", 1),
18664 newline(),
18665 whitespace(" ", 1),
18666 word("钢", 1),
18667 word("笔", 1),
18668 ],
18669 ),
18670 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
18671 ];
18672
18673 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
18674 WordBreakToken::Word {
18675 token,
18676 grapheme_len,
18677 }
18678 }
18679
18680 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
18681 WordBreakToken::InlineWhitespace {
18682 token,
18683 grapheme_len,
18684 }
18685 }
18686
18687 fn newline() -> WordBreakToken<'static> {
18688 WordBreakToken::Newline
18689 }
18690
18691 for (input, result) in tests {
18692 assert_eq!(
18693 WordBreakingTokenizer::new(input)
18694 .collect::<Vec<_>>()
18695 .as_slice(),
18696 *result,
18697 );
18698 }
18699}
18700
18701fn wrap_with_prefix(
18702 line_prefix: String,
18703 unwrapped_text: String,
18704 wrap_column: usize,
18705 tab_size: NonZeroU32,
18706 preserve_existing_whitespace: bool,
18707) -> String {
18708 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
18709 let mut wrapped_text = String::new();
18710 let mut current_line = line_prefix.clone();
18711
18712 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
18713 let mut current_line_len = line_prefix_len;
18714 let mut in_whitespace = false;
18715 for token in tokenizer {
18716 let have_preceding_whitespace = in_whitespace;
18717 match token {
18718 WordBreakToken::Word {
18719 token,
18720 grapheme_len,
18721 } => {
18722 in_whitespace = false;
18723 if current_line_len + grapheme_len > wrap_column
18724 && current_line_len != line_prefix_len
18725 {
18726 wrapped_text.push_str(current_line.trim_end());
18727 wrapped_text.push('\n');
18728 current_line.truncate(line_prefix.len());
18729 current_line_len = line_prefix_len;
18730 }
18731 current_line.push_str(token);
18732 current_line_len += grapheme_len;
18733 }
18734 WordBreakToken::InlineWhitespace {
18735 mut token,
18736 mut grapheme_len,
18737 } => {
18738 in_whitespace = true;
18739 if have_preceding_whitespace && !preserve_existing_whitespace {
18740 continue;
18741 }
18742 if !preserve_existing_whitespace {
18743 token = " ";
18744 grapheme_len = 1;
18745 }
18746 if current_line_len + grapheme_len > wrap_column {
18747 wrapped_text.push_str(current_line.trim_end());
18748 wrapped_text.push('\n');
18749 current_line.truncate(line_prefix.len());
18750 current_line_len = line_prefix_len;
18751 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
18752 current_line.push_str(token);
18753 current_line_len += grapheme_len;
18754 }
18755 }
18756 WordBreakToken::Newline => {
18757 in_whitespace = true;
18758 if preserve_existing_whitespace {
18759 wrapped_text.push_str(current_line.trim_end());
18760 wrapped_text.push('\n');
18761 current_line.truncate(line_prefix.len());
18762 current_line_len = line_prefix_len;
18763 } else if have_preceding_whitespace {
18764 continue;
18765 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
18766 {
18767 wrapped_text.push_str(current_line.trim_end());
18768 wrapped_text.push('\n');
18769 current_line.truncate(line_prefix.len());
18770 current_line_len = line_prefix_len;
18771 } else if current_line_len != line_prefix_len {
18772 current_line.push(' ');
18773 current_line_len += 1;
18774 }
18775 }
18776 }
18777 }
18778
18779 if !current_line.is_empty() {
18780 wrapped_text.push_str(¤t_line);
18781 }
18782 wrapped_text
18783}
18784
18785#[test]
18786fn test_wrap_with_prefix() {
18787 assert_eq!(
18788 wrap_with_prefix(
18789 "# ".to_string(),
18790 "abcdefg".to_string(),
18791 4,
18792 NonZeroU32::new(4).unwrap(),
18793 false,
18794 ),
18795 "# abcdefg"
18796 );
18797 assert_eq!(
18798 wrap_with_prefix(
18799 "".to_string(),
18800 "\thello world".to_string(),
18801 8,
18802 NonZeroU32::new(4).unwrap(),
18803 false,
18804 ),
18805 "hello\nworld"
18806 );
18807 assert_eq!(
18808 wrap_with_prefix(
18809 "// ".to_string(),
18810 "xx \nyy zz aa bb cc".to_string(),
18811 12,
18812 NonZeroU32::new(4).unwrap(),
18813 false,
18814 ),
18815 "// xx yy zz\n// aa bb cc"
18816 );
18817 assert_eq!(
18818 wrap_with_prefix(
18819 String::new(),
18820 "这是什么 \n 钢笔".to_string(),
18821 3,
18822 NonZeroU32::new(4).unwrap(),
18823 false,
18824 ),
18825 "这是什\n么 钢\n笔"
18826 );
18827}
18828
18829pub trait CollaborationHub {
18830 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
18831 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
18832 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
18833}
18834
18835impl CollaborationHub for Entity<Project> {
18836 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
18837 self.read(cx).collaborators()
18838 }
18839
18840 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
18841 self.read(cx).user_store().read(cx).participant_indices()
18842 }
18843
18844 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
18845 let this = self.read(cx);
18846 let user_ids = this.collaborators().values().map(|c| c.user_id);
18847 this.user_store().read_with(cx, |user_store, cx| {
18848 user_store.participant_names(user_ids, cx)
18849 })
18850 }
18851}
18852
18853pub trait SemanticsProvider {
18854 fn hover(
18855 &self,
18856 buffer: &Entity<Buffer>,
18857 position: text::Anchor,
18858 cx: &mut App,
18859 ) -> Option<Task<Vec<project::Hover>>>;
18860
18861 fn inlay_hints(
18862 &self,
18863 buffer_handle: Entity<Buffer>,
18864 range: Range<text::Anchor>,
18865 cx: &mut App,
18866 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
18867
18868 fn resolve_inlay_hint(
18869 &self,
18870 hint: InlayHint,
18871 buffer_handle: Entity<Buffer>,
18872 server_id: LanguageServerId,
18873 cx: &mut App,
18874 ) -> Option<Task<anyhow::Result<InlayHint>>>;
18875
18876 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
18877
18878 fn document_highlights(
18879 &self,
18880 buffer: &Entity<Buffer>,
18881 position: text::Anchor,
18882 cx: &mut App,
18883 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
18884
18885 fn definitions(
18886 &self,
18887 buffer: &Entity<Buffer>,
18888 position: text::Anchor,
18889 kind: GotoDefinitionKind,
18890 cx: &mut App,
18891 ) -> Option<Task<Result<Vec<LocationLink>>>>;
18892
18893 fn range_for_rename(
18894 &self,
18895 buffer: &Entity<Buffer>,
18896 position: text::Anchor,
18897 cx: &mut App,
18898 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
18899
18900 fn perform_rename(
18901 &self,
18902 buffer: &Entity<Buffer>,
18903 position: text::Anchor,
18904 new_name: String,
18905 cx: &mut App,
18906 ) -> Option<Task<Result<ProjectTransaction>>>;
18907}
18908
18909pub trait CompletionProvider {
18910 fn completions(
18911 &self,
18912 excerpt_id: ExcerptId,
18913 buffer: &Entity<Buffer>,
18914 buffer_position: text::Anchor,
18915 trigger: CompletionContext,
18916 window: &mut Window,
18917 cx: &mut Context<Editor>,
18918 ) -> Task<Result<Option<Vec<Completion>>>>;
18919
18920 fn resolve_completions(
18921 &self,
18922 buffer: Entity<Buffer>,
18923 completion_indices: Vec<usize>,
18924 completions: Rc<RefCell<Box<[Completion]>>>,
18925 cx: &mut Context<Editor>,
18926 ) -> Task<Result<bool>>;
18927
18928 fn apply_additional_edits_for_completion(
18929 &self,
18930 _buffer: Entity<Buffer>,
18931 _completions: Rc<RefCell<Box<[Completion]>>>,
18932 _completion_index: usize,
18933 _push_to_history: bool,
18934 _cx: &mut Context<Editor>,
18935 ) -> Task<Result<Option<language::Transaction>>> {
18936 Task::ready(Ok(None))
18937 }
18938
18939 fn is_completion_trigger(
18940 &self,
18941 buffer: &Entity<Buffer>,
18942 position: language::Anchor,
18943 text: &str,
18944 trigger_in_words: bool,
18945 cx: &mut Context<Editor>,
18946 ) -> bool;
18947
18948 fn sort_completions(&self) -> bool {
18949 true
18950 }
18951
18952 fn filter_completions(&self) -> bool {
18953 true
18954 }
18955}
18956
18957pub trait CodeActionProvider {
18958 fn id(&self) -> Arc<str>;
18959
18960 fn code_actions(
18961 &self,
18962 buffer: &Entity<Buffer>,
18963 range: Range<text::Anchor>,
18964 window: &mut Window,
18965 cx: &mut App,
18966 ) -> Task<Result<Vec<CodeAction>>>;
18967
18968 fn apply_code_action(
18969 &self,
18970 buffer_handle: Entity<Buffer>,
18971 action: CodeAction,
18972 excerpt_id: ExcerptId,
18973 push_to_history: bool,
18974 window: &mut Window,
18975 cx: &mut App,
18976 ) -> Task<Result<ProjectTransaction>>;
18977}
18978
18979impl CodeActionProvider for Entity<Project> {
18980 fn id(&self) -> Arc<str> {
18981 "project".into()
18982 }
18983
18984 fn code_actions(
18985 &self,
18986 buffer: &Entity<Buffer>,
18987 range: Range<text::Anchor>,
18988 _window: &mut Window,
18989 cx: &mut App,
18990 ) -> Task<Result<Vec<CodeAction>>> {
18991 self.update(cx, |project, cx| {
18992 let code_lens = project.code_lens(buffer, range.clone(), cx);
18993 let code_actions = project.code_actions(buffer, range, None, cx);
18994 cx.background_spawn(async move {
18995 let (code_lens, code_actions) = join(code_lens, code_actions).await;
18996 Ok(code_lens
18997 .context("code lens fetch")?
18998 .into_iter()
18999 .chain(code_actions.context("code action fetch")?)
19000 .collect())
19001 })
19002 })
19003 }
19004
19005 fn apply_code_action(
19006 &self,
19007 buffer_handle: Entity<Buffer>,
19008 action: CodeAction,
19009 _excerpt_id: ExcerptId,
19010 push_to_history: bool,
19011 _window: &mut Window,
19012 cx: &mut App,
19013 ) -> Task<Result<ProjectTransaction>> {
19014 self.update(cx, |project, cx| {
19015 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19016 })
19017 }
19018}
19019
19020fn snippet_completions(
19021 project: &Project,
19022 buffer: &Entity<Buffer>,
19023 buffer_position: text::Anchor,
19024 cx: &mut App,
19025) -> Task<Result<Vec<Completion>>> {
19026 let languages = buffer.read(cx).languages_at(buffer_position);
19027 let snippet_store = project.snippets().read(cx);
19028
19029 let scopes: Vec<_> = languages
19030 .iter()
19031 .filter_map(|language| {
19032 let language_name = language.lsp_id();
19033 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19034
19035 if snippets.is_empty() {
19036 None
19037 } else {
19038 Some((language.default_scope(), snippets))
19039 }
19040 })
19041 .collect();
19042
19043 if scopes.is_empty() {
19044 return Task::ready(Ok(vec![]));
19045 }
19046
19047 let snapshot = buffer.read(cx).text_snapshot();
19048 let chars: String = snapshot
19049 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19050 .collect();
19051 let executor = cx.background_executor().clone();
19052
19053 cx.background_spawn(async move {
19054 let mut all_results: Vec<Completion> = Vec::new();
19055 for (scope, snippets) in scopes.into_iter() {
19056 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19057 let mut last_word = chars
19058 .chars()
19059 .take_while(|c| classifier.is_word(*c))
19060 .collect::<String>();
19061 last_word = last_word.chars().rev().collect();
19062
19063 if last_word.is_empty() {
19064 return Ok(vec![]);
19065 }
19066
19067 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
19068 let to_lsp = |point: &text::Anchor| {
19069 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
19070 point_to_lsp(end)
19071 };
19072 let lsp_end = to_lsp(&buffer_position);
19073
19074 let candidates = snippets
19075 .iter()
19076 .enumerate()
19077 .flat_map(|(ix, snippet)| {
19078 snippet
19079 .prefix
19080 .iter()
19081 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
19082 })
19083 .collect::<Vec<StringMatchCandidate>>();
19084
19085 let mut matches = fuzzy::match_strings(
19086 &candidates,
19087 &last_word,
19088 last_word.chars().any(|c| c.is_uppercase()),
19089 100,
19090 &Default::default(),
19091 executor.clone(),
19092 )
19093 .await;
19094
19095 // Remove all candidates where the query's start does not match the start of any word in the candidate
19096 if let Some(query_start) = last_word.chars().next() {
19097 matches.retain(|string_match| {
19098 split_words(&string_match.string).any(|word| {
19099 // Check that the first codepoint of the word as lowercase matches the first
19100 // codepoint of the query as lowercase
19101 word.chars()
19102 .flat_map(|codepoint| codepoint.to_lowercase())
19103 .zip(query_start.to_lowercase())
19104 .all(|(word_cp, query_cp)| word_cp == query_cp)
19105 })
19106 });
19107 }
19108
19109 let matched_strings = matches
19110 .into_iter()
19111 .map(|m| m.string)
19112 .collect::<HashSet<_>>();
19113
19114 let mut result: Vec<Completion> = snippets
19115 .iter()
19116 .filter_map(|snippet| {
19117 let matching_prefix = snippet
19118 .prefix
19119 .iter()
19120 .find(|prefix| matched_strings.contains(*prefix))?;
19121 let start = as_offset - last_word.len();
19122 let start = snapshot.anchor_before(start);
19123 let range = start..buffer_position;
19124 let lsp_start = to_lsp(&start);
19125 let lsp_range = lsp::Range {
19126 start: lsp_start,
19127 end: lsp_end,
19128 };
19129 Some(Completion {
19130 replace_range: range,
19131 new_text: snippet.body.clone(),
19132 source: CompletionSource::Lsp {
19133 insert_range: None,
19134 server_id: LanguageServerId(usize::MAX),
19135 resolved: true,
19136 lsp_completion: Box::new(lsp::CompletionItem {
19137 label: snippet.prefix.first().unwrap().clone(),
19138 kind: Some(CompletionItemKind::SNIPPET),
19139 label_details: snippet.description.as_ref().map(|description| {
19140 lsp::CompletionItemLabelDetails {
19141 detail: Some(description.clone()),
19142 description: None,
19143 }
19144 }),
19145 insert_text_format: Some(InsertTextFormat::SNIPPET),
19146 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19147 lsp::InsertReplaceEdit {
19148 new_text: snippet.body.clone(),
19149 insert: lsp_range,
19150 replace: lsp_range,
19151 },
19152 )),
19153 filter_text: Some(snippet.body.clone()),
19154 sort_text: Some(char::MAX.to_string()),
19155 ..lsp::CompletionItem::default()
19156 }),
19157 lsp_defaults: None,
19158 },
19159 label: CodeLabel {
19160 text: matching_prefix.clone(),
19161 runs: Vec::new(),
19162 filter_range: 0..matching_prefix.len(),
19163 },
19164 icon_path: None,
19165 documentation: snippet.description.clone().map(|description| {
19166 CompletionDocumentation::SingleLine(description.into())
19167 }),
19168 insert_text_mode: None,
19169 confirm: None,
19170 })
19171 })
19172 .collect();
19173
19174 all_results.append(&mut result);
19175 }
19176
19177 Ok(all_results)
19178 })
19179}
19180
19181impl CompletionProvider for Entity<Project> {
19182 fn completions(
19183 &self,
19184 _excerpt_id: ExcerptId,
19185 buffer: &Entity<Buffer>,
19186 buffer_position: text::Anchor,
19187 options: CompletionContext,
19188 _window: &mut Window,
19189 cx: &mut Context<Editor>,
19190 ) -> Task<Result<Option<Vec<Completion>>>> {
19191 self.update(cx, |project, cx| {
19192 let snippets = snippet_completions(project, buffer, buffer_position, cx);
19193 let project_completions = project.completions(buffer, buffer_position, options, cx);
19194 cx.background_spawn(async move {
19195 let snippets_completions = snippets.await?;
19196 match project_completions.await? {
19197 Some(mut completions) => {
19198 completions.extend(snippets_completions);
19199 Ok(Some(completions))
19200 }
19201 None => {
19202 if snippets_completions.is_empty() {
19203 Ok(None)
19204 } else {
19205 Ok(Some(snippets_completions))
19206 }
19207 }
19208 }
19209 })
19210 })
19211 }
19212
19213 fn resolve_completions(
19214 &self,
19215 buffer: Entity<Buffer>,
19216 completion_indices: Vec<usize>,
19217 completions: Rc<RefCell<Box<[Completion]>>>,
19218 cx: &mut Context<Editor>,
19219 ) -> Task<Result<bool>> {
19220 self.update(cx, |project, cx| {
19221 project.lsp_store().update(cx, |lsp_store, cx| {
19222 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
19223 })
19224 })
19225 }
19226
19227 fn apply_additional_edits_for_completion(
19228 &self,
19229 buffer: Entity<Buffer>,
19230 completions: Rc<RefCell<Box<[Completion]>>>,
19231 completion_index: usize,
19232 push_to_history: bool,
19233 cx: &mut Context<Editor>,
19234 ) -> Task<Result<Option<language::Transaction>>> {
19235 self.update(cx, |project, cx| {
19236 project.lsp_store().update(cx, |lsp_store, cx| {
19237 lsp_store.apply_additional_edits_for_completion(
19238 buffer,
19239 completions,
19240 completion_index,
19241 push_to_history,
19242 cx,
19243 )
19244 })
19245 })
19246 }
19247
19248 fn is_completion_trigger(
19249 &self,
19250 buffer: &Entity<Buffer>,
19251 position: language::Anchor,
19252 text: &str,
19253 trigger_in_words: bool,
19254 cx: &mut Context<Editor>,
19255 ) -> bool {
19256 let mut chars = text.chars();
19257 let char = if let Some(char) = chars.next() {
19258 char
19259 } else {
19260 return false;
19261 };
19262 if chars.next().is_some() {
19263 return false;
19264 }
19265
19266 let buffer = buffer.read(cx);
19267 let snapshot = buffer.snapshot();
19268 if !snapshot.settings_at(position, cx).show_completions_on_input {
19269 return false;
19270 }
19271 let classifier = snapshot.char_classifier_at(position).for_completion(true);
19272 if trigger_in_words && classifier.is_word(char) {
19273 return true;
19274 }
19275
19276 buffer.completion_triggers().contains(text)
19277 }
19278}
19279
19280impl SemanticsProvider for Entity<Project> {
19281 fn hover(
19282 &self,
19283 buffer: &Entity<Buffer>,
19284 position: text::Anchor,
19285 cx: &mut App,
19286 ) -> Option<Task<Vec<project::Hover>>> {
19287 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
19288 }
19289
19290 fn document_highlights(
19291 &self,
19292 buffer: &Entity<Buffer>,
19293 position: text::Anchor,
19294 cx: &mut App,
19295 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
19296 Some(self.update(cx, |project, cx| {
19297 project.document_highlights(buffer, position, cx)
19298 }))
19299 }
19300
19301 fn definitions(
19302 &self,
19303 buffer: &Entity<Buffer>,
19304 position: text::Anchor,
19305 kind: GotoDefinitionKind,
19306 cx: &mut App,
19307 ) -> Option<Task<Result<Vec<LocationLink>>>> {
19308 Some(self.update(cx, |project, cx| match kind {
19309 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
19310 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
19311 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
19312 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
19313 }))
19314 }
19315
19316 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
19317 // TODO: make this work for remote projects
19318 self.update(cx, |this, cx| {
19319 buffer.update(cx, |buffer, cx| {
19320 this.any_language_server_supports_inlay_hints(buffer, cx)
19321 })
19322 })
19323 }
19324
19325 fn inlay_hints(
19326 &self,
19327 buffer_handle: Entity<Buffer>,
19328 range: Range<text::Anchor>,
19329 cx: &mut App,
19330 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
19331 Some(self.update(cx, |project, cx| {
19332 project.inlay_hints(buffer_handle, range, cx)
19333 }))
19334 }
19335
19336 fn resolve_inlay_hint(
19337 &self,
19338 hint: InlayHint,
19339 buffer_handle: Entity<Buffer>,
19340 server_id: LanguageServerId,
19341 cx: &mut App,
19342 ) -> Option<Task<anyhow::Result<InlayHint>>> {
19343 Some(self.update(cx, |project, cx| {
19344 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
19345 }))
19346 }
19347
19348 fn range_for_rename(
19349 &self,
19350 buffer: &Entity<Buffer>,
19351 position: text::Anchor,
19352 cx: &mut App,
19353 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
19354 Some(self.update(cx, |project, cx| {
19355 let buffer = buffer.clone();
19356 let task = project.prepare_rename(buffer.clone(), position, cx);
19357 cx.spawn(async move |_, cx| {
19358 Ok(match task.await? {
19359 PrepareRenameResponse::Success(range) => Some(range),
19360 PrepareRenameResponse::InvalidPosition => None,
19361 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
19362 // Fallback on using TreeSitter info to determine identifier range
19363 buffer.update(cx, |buffer, _| {
19364 let snapshot = buffer.snapshot();
19365 let (range, kind) = snapshot.surrounding_word(position);
19366 if kind != Some(CharKind::Word) {
19367 return None;
19368 }
19369 Some(
19370 snapshot.anchor_before(range.start)
19371 ..snapshot.anchor_after(range.end),
19372 )
19373 })?
19374 }
19375 })
19376 })
19377 }))
19378 }
19379
19380 fn perform_rename(
19381 &self,
19382 buffer: &Entity<Buffer>,
19383 position: text::Anchor,
19384 new_name: String,
19385 cx: &mut App,
19386 ) -> Option<Task<Result<ProjectTransaction>>> {
19387 Some(self.update(cx, |project, cx| {
19388 project.perform_rename(buffer.clone(), position, new_name, cx)
19389 }))
19390 }
19391}
19392
19393fn inlay_hint_settings(
19394 location: Anchor,
19395 snapshot: &MultiBufferSnapshot,
19396 cx: &mut Context<Editor>,
19397) -> InlayHintSettings {
19398 let file = snapshot.file_at(location);
19399 let language = snapshot.language_at(location).map(|l| l.name());
19400 language_settings(language, file, cx).inlay_hints
19401}
19402
19403fn consume_contiguous_rows(
19404 contiguous_row_selections: &mut Vec<Selection<Point>>,
19405 selection: &Selection<Point>,
19406 display_map: &DisplaySnapshot,
19407 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
19408) -> (MultiBufferRow, MultiBufferRow) {
19409 contiguous_row_selections.push(selection.clone());
19410 let start_row = MultiBufferRow(selection.start.row);
19411 let mut end_row = ending_row(selection, display_map);
19412
19413 while let Some(next_selection) = selections.peek() {
19414 if next_selection.start.row <= end_row.0 {
19415 end_row = ending_row(next_selection, display_map);
19416 contiguous_row_selections.push(selections.next().unwrap().clone());
19417 } else {
19418 break;
19419 }
19420 }
19421 (start_row, end_row)
19422}
19423
19424fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
19425 if next_selection.end.column > 0 || next_selection.is_empty() {
19426 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
19427 } else {
19428 MultiBufferRow(next_selection.end.row)
19429 }
19430}
19431
19432impl EditorSnapshot {
19433 pub fn remote_selections_in_range<'a>(
19434 &'a self,
19435 range: &'a Range<Anchor>,
19436 collaboration_hub: &dyn CollaborationHub,
19437 cx: &'a App,
19438 ) -> impl 'a + Iterator<Item = RemoteSelection> {
19439 let participant_names = collaboration_hub.user_names(cx);
19440 let participant_indices = collaboration_hub.user_participant_indices(cx);
19441 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
19442 let collaborators_by_replica_id = collaborators_by_peer_id
19443 .iter()
19444 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
19445 .collect::<HashMap<_, _>>();
19446 self.buffer_snapshot
19447 .selections_in_range(range, false)
19448 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
19449 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
19450 let participant_index = participant_indices.get(&collaborator.user_id).copied();
19451 let user_name = participant_names.get(&collaborator.user_id).cloned();
19452 Some(RemoteSelection {
19453 replica_id,
19454 selection,
19455 cursor_shape,
19456 line_mode,
19457 participant_index,
19458 peer_id: collaborator.peer_id,
19459 user_name,
19460 })
19461 })
19462 }
19463
19464 pub fn hunks_for_ranges(
19465 &self,
19466 ranges: impl IntoIterator<Item = Range<Point>>,
19467 ) -> Vec<MultiBufferDiffHunk> {
19468 let mut hunks = Vec::new();
19469 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
19470 HashMap::default();
19471 for query_range in ranges {
19472 let query_rows =
19473 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
19474 for hunk in self.buffer_snapshot.diff_hunks_in_range(
19475 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
19476 ) {
19477 // Include deleted hunks that are adjacent to the query range, because
19478 // otherwise they would be missed.
19479 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
19480 if hunk.status().is_deleted() {
19481 intersects_range |= hunk.row_range.start == query_rows.end;
19482 intersects_range |= hunk.row_range.end == query_rows.start;
19483 }
19484 if intersects_range {
19485 if !processed_buffer_rows
19486 .entry(hunk.buffer_id)
19487 .or_default()
19488 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
19489 {
19490 continue;
19491 }
19492 hunks.push(hunk);
19493 }
19494 }
19495 }
19496
19497 hunks
19498 }
19499
19500 fn display_diff_hunks_for_rows<'a>(
19501 &'a self,
19502 display_rows: Range<DisplayRow>,
19503 folded_buffers: &'a HashSet<BufferId>,
19504 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
19505 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
19506 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
19507
19508 self.buffer_snapshot
19509 .diff_hunks_in_range(buffer_start..buffer_end)
19510 .filter_map(|hunk| {
19511 if folded_buffers.contains(&hunk.buffer_id) {
19512 return None;
19513 }
19514
19515 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
19516 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
19517
19518 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
19519 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
19520
19521 let display_hunk = if hunk_display_start.column() != 0 {
19522 DisplayDiffHunk::Folded {
19523 display_row: hunk_display_start.row(),
19524 }
19525 } else {
19526 let mut end_row = hunk_display_end.row();
19527 if hunk_display_end.column() > 0 {
19528 end_row.0 += 1;
19529 }
19530 let is_created_file = hunk.is_created_file();
19531 DisplayDiffHunk::Unfolded {
19532 status: hunk.status(),
19533 diff_base_byte_range: hunk.diff_base_byte_range,
19534 display_row_range: hunk_display_start.row()..end_row,
19535 multi_buffer_range: Anchor::range_in_buffer(
19536 hunk.excerpt_id,
19537 hunk.buffer_id,
19538 hunk.buffer_range,
19539 ),
19540 is_created_file,
19541 }
19542 };
19543
19544 Some(display_hunk)
19545 })
19546 }
19547
19548 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
19549 self.display_snapshot.buffer_snapshot.language_at(position)
19550 }
19551
19552 pub fn is_focused(&self) -> bool {
19553 self.is_focused
19554 }
19555
19556 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
19557 self.placeholder_text.as_ref()
19558 }
19559
19560 pub fn scroll_position(&self) -> gpui::Point<f32> {
19561 self.scroll_anchor.scroll_position(&self.display_snapshot)
19562 }
19563
19564 fn gutter_dimensions(
19565 &self,
19566 font_id: FontId,
19567 font_size: Pixels,
19568 max_line_number_width: Pixels,
19569 cx: &App,
19570 ) -> Option<GutterDimensions> {
19571 if !self.show_gutter {
19572 return None;
19573 }
19574
19575 let descent = cx.text_system().descent(font_id, font_size);
19576 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
19577 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
19578
19579 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
19580 matches!(
19581 ProjectSettings::get_global(cx).git.git_gutter,
19582 Some(GitGutterSetting::TrackedFiles)
19583 )
19584 });
19585 let gutter_settings = EditorSettings::get_global(cx).gutter;
19586 let show_line_numbers = self
19587 .show_line_numbers
19588 .unwrap_or(gutter_settings.line_numbers);
19589 let line_gutter_width = if show_line_numbers {
19590 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
19591 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
19592 max_line_number_width.max(min_width_for_number_on_gutter)
19593 } else {
19594 0.0.into()
19595 };
19596
19597 let show_code_actions = self
19598 .show_code_actions
19599 .unwrap_or(gutter_settings.code_actions);
19600
19601 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
19602 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
19603
19604 let git_blame_entries_width =
19605 self.git_blame_gutter_max_author_length
19606 .map(|max_author_length| {
19607 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19608 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
19609
19610 /// The number of characters to dedicate to gaps and margins.
19611 const SPACING_WIDTH: usize = 4;
19612
19613 let max_char_count = max_author_length.min(renderer.max_author_length())
19614 + ::git::SHORT_SHA_LENGTH
19615 + MAX_RELATIVE_TIMESTAMP.len()
19616 + SPACING_WIDTH;
19617
19618 em_advance * max_char_count
19619 });
19620
19621 let is_singleton = self.buffer_snapshot.is_singleton();
19622
19623 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
19624 left_padding += if !is_singleton {
19625 em_width * 4.0
19626 } else if show_code_actions || show_runnables || show_breakpoints {
19627 em_width * 3.0
19628 } else if show_git_gutter && show_line_numbers {
19629 em_width * 2.0
19630 } else if show_git_gutter || show_line_numbers {
19631 em_width
19632 } else {
19633 px(0.)
19634 };
19635
19636 let shows_folds = is_singleton && gutter_settings.folds;
19637
19638 let right_padding = if shows_folds && show_line_numbers {
19639 em_width * 4.0
19640 } else if shows_folds || (!is_singleton && show_line_numbers) {
19641 em_width * 3.0
19642 } else if show_line_numbers {
19643 em_width
19644 } else {
19645 px(0.)
19646 };
19647
19648 Some(GutterDimensions {
19649 left_padding,
19650 right_padding,
19651 width: line_gutter_width + left_padding + right_padding,
19652 margin: -descent,
19653 git_blame_entries_width,
19654 })
19655 }
19656
19657 pub fn render_crease_toggle(
19658 &self,
19659 buffer_row: MultiBufferRow,
19660 row_contains_cursor: bool,
19661 editor: Entity<Editor>,
19662 window: &mut Window,
19663 cx: &mut App,
19664 ) -> Option<AnyElement> {
19665 let folded = self.is_line_folded(buffer_row);
19666 let mut is_foldable = false;
19667
19668 if let Some(crease) = self
19669 .crease_snapshot
19670 .query_row(buffer_row, &self.buffer_snapshot)
19671 {
19672 is_foldable = true;
19673 match crease {
19674 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
19675 if let Some(render_toggle) = render_toggle {
19676 let toggle_callback =
19677 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
19678 if folded {
19679 editor.update(cx, |editor, cx| {
19680 editor.fold_at(buffer_row, window, cx)
19681 });
19682 } else {
19683 editor.update(cx, |editor, cx| {
19684 editor.unfold_at(buffer_row, window, cx)
19685 });
19686 }
19687 });
19688 return Some((render_toggle)(
19689 buffer_row,
19690 folded,
19691 toggle_callback,
19692 window,
19693 cx,
19694 ));
19695 }
19696 }
19697 }
19698 }
19699
19700 is_foldable |= self.starts_indent(buffer_row);
19701
19702 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
19703 Some(
19704 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
19705 .toggle_state(folded)
19706 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
19707 if folded {
19708 this.unfold_at(buffer_row, window, cx);
19709 } else {
19710 this.fold_at(buffer_row, window, cx);
19711 }
19712 }))
19713 .into_any_element(),
19714 )
19715 } else {
19716 None
19717 }
19718 }
19719
19720 pub fn render_crease_trailer(
19721 &self,
19722 buffer_row: MultiBufferRow,
19723 window: &mut Window,
19724 cx: &mut App,
19725 ) -> Option<AnyElement> {
19726 let folded = self.is_line_folded(buffer_row);
19727 if let Crease::Inline { render_trailer, .. } = self
19728 .crease_snapshot
19729 .query_row(buffer_row, &self.buffer_snapshot)?
19730 {
19731 let render_trailer = render_trailer.as_ref()?;
19732 Some(render_trailer(buffer_row, folded, window, cx))
19733 } else {
19734 None
19735 }
19736 }
19737}
19738
19739impl Deref for EditorSnapshot {
19740 type Target = DisplaySnapshot;
19741
19742 fn deref(&self) -> &Self::Target {
19743 &self.display_snapshot
19744 }
19745}
19746
19747#[derive(Clone, Debug, PartialEq, Eq)]
19748pub enum EditorEvent {
19749 InputIgnored {
19750 text: Arc<str>,
19751 },
19752 InputHandled {
19753 utf16_range_to_replace: Option<Range<isize>>,
19754 text: Arc<str>,
19755 },
19756 ExcerptsAdded {
19757 buffer: Entity<Buffer>,
19758 predecessor: ExcerptId,
19759 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
19760 },
19761 ExcerptsRemoved {
19762 ids: Vec<ExcerptId>,
19763 },
19764 BufferFoldToggled {
19765 ids: Vec<ExcerptId>,
19766 folded: bool,
19767 },
19768 ExcerptsEdited {
19769 ids: Vec<ExcerptId>,
19770 },
19771 ExcerptsExpanded {
19772 ids: Vec<ExcerptId>,
19773 },
19774 BufferEdited,
19775 Edited {
19776 transaction_id: clock::Lamport,
19777 },
19778 Reparsed(BufferId),
19779 Focused,
19780 FocusedIn,
19781 Blurred,
19782 DirtyChanged,
19783 Saved,
19784 TitleChanged,
19785 DiffBaseChanged,
19786 SelectionsChanged {
19787 local: bool,
19788 },
19789 ScrollPositionChanged {
19790 local: bool,
19791 autoscroll: bool,
19792 },
19793 Closed,
19794 TransactionUndone {
19795 transaction_id: clock::Lamport,
19796 },
19797 TransactionBegun {
19798 transaction_id: clock::Lamport,
19799 },
19800 Reloaded,
19801 CursorShapeChanged,
19802 PushedToNavHistory {
19803 anchor: Anchor,
19804 is_deactivate: bool,
19805 },
19806}
19807
19808impl EventEmitter<EditorEvent> for Editor {}
19809
19810impl Focusable for Editor {
19811 fn focus_handle(&self, _cx: &App) -> FocusHandle {
19812 self.focus_handle.clone()
19813 }
19814}
19815
19816impl Render for Editor {
19817 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
19818 let settings = ThemeSettings::get_global(cx);
19819
19820 let mut text_style = match self.mode {
19821 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
19822 color: cx.theme().colors().editor_foreground,
19823 font_family: settings.ui_font.family.clone(),
19824 font_features: settings.ui_font.features.clone(),
19825 font_fallbacks: settings.ui_font.fallbacks.clone(),
19826 font_size: rems(0.875).into(),
19827 font_weight: settings.ui_font.weight,
19828 line_height: relative(settings.buffer_line_height.value()),
19829 ..Default::default()
19830 },
19831 EditorMode::Full { .. } => TextStyle {
19832 color: cx.theme().colors().editor_foreground,
19833 font_family: settings.buffer_font.family.clone(),
19834 font_features: settings.buffer_font.features.clone(),
19835 font_fallbacks: settings.buffer_font.fallbacks.clone(),
19836 font_size: settings.buffer_font_size(cx).into(),
19837 font_weight: settings.buffer_font.weight,
19838 line_height: relative(settings.buffer_line_height.value()),
19839 ..Default::default()
19840 },
19841 };
19842 if let Some(text_style_refinement) = &self.text_style_refinement {
19843 text_style.refine(text_style_refinement)
19844 }
19845
19846 let background = match self.mode {
19847 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
19848 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
19849 EditorMode::Full { .. } => cx.theme().colors().editor_background,
19850 };
19851
19852 EditorElement::new(
19853 &cx.entity(),
19854 EditorStyle {
19855 background,
19856 local_player: cx.theme().players().local(),
19857 text: text_style,
19858 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
19859 syntax: cx.theme().syntax().clone(),
19860 status: cx.theme().status().clone(),
19861 inlay_hints_style: make_inlay_hints_style(cx),
19862 inline_completion_styles: make_suggestion_styles(cx),
19863 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
19864 },
19865 )
19866 }
19867}
19868
19869impl EntityInputHandler for Editor {
19870 fn text_for_range(
19871 &mut self,
19872 range_utf16: Range<usize>,
19873 adjusted_range: &mut Option<Range<usize>>,
19874 _: &mut Window,
19875 cx: &mut Context<Self>,
19876 ) -> Option<String> {
19877 let snapshot = self.buffer.read(cx).read(cx);
19878 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
19879 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
19880 if (start.0..end.0) != range_utf16 {
19881 adjusted_range.replace(start.0..end.0);
19882 }
19883 Some(snapshot.text_for_range(start..end).collect())
19884 }
19885
19886 fn selected_text_range(
19887 &mut self,
19888 ignore_disabled_input: bool,
19889 _: &mut Window,
19890 cx: &mut Context<Self>,
19891 ) -> Option<UTF16Selection> {
19892 // Prevent the IME menu from appearing when holding down an alphabetic key
19893 // while input is disabled.
19894 if !ignore_disabled_input && !self.input_enabled {
19895 return None;
19896 }
19897
19898 let selection = self.selections.newest::<OffsetUtf16>(cx);
19899 let range = selection.range();
19900
19901 Some(UTF16Selection {
19902 range: range.start.0..range.end.0,
19903 reversed: selection.reversed,
19904 })
19905 }
19906
19907 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
19908 let snapshot = self.buffer.read(cx).read(cx);
19909 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
19910 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
19911 }
19912
19913 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19914 self.clear_highlights::<InputComposition>(cx);
19915 self.ime_transaction.take();
19916 }
19917
19918 fn replace_text_in_range(
19919 &mut self,
19920 range_utf16: Option<Range<usize>>,
19921 text: &str,
19922 window: &mut Window,
19923 cx: &mut Context<Self>,
19924 ) {
19925 if !self.input_enabled {
19926 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19927 return;
19928 }
19929
19930 self.transact(window, cx, |this, window, cx| {
19931 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
19932 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
19933 Some(this.selection_replacement_ranges(range_utf16, cx))
19934 } else {
19935 this.marked_text_ranges(cx)
19936 };
19937
19938 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
19939 let newest_selection_id = this.selections.newest_anchor().id;
19940 this.selections
19941 .all::<OffsetUtf16>(cx)
19942 .iter()
19943 .zip(ranges_to_replace.iter())
19944 .find_map(|(selection, range)| {
19945 if selection.id == newest_selection_id {
19946 Some(
19947 (range.start.0 as isize - selection.head().0 as isize)
19948 ..(range.end.0 as isize - selection.head().0 as isize),
19949 )
19950 } else {
19951 None
19952 }
19953 })
19954 });
19955
19956 cx.emit(EditorEvent::InputHandled {
19957 utf16_range_to_replace: range_to_replace,
19958 text: text.into(),
19959 });
19960
19961 if let Some(new_selected_ranges) = new_selected_ranges {
19962 this.change_selections(None, window, cx, |selections| {
19963 selections.select_ranges(new_selected_ranges)
19964 });
19965 this.backspace(&Default::default(), window, cx);
19966 }
19967
19968 this.handle_input(text, window, cx);
19969 });
19970
19971 if let Some(transaction) = self.ime_transaction {
19972 self.buffer.update(cx, |buffer, cx| {
19973 buffer.group_until_transaction(transaction, cx);
19974 });
19975 }
19976
19977 self.unmark_text(window, cx);
19978 }
19979
19980 fn replace_and_mark_text_in_range(
19981 &mut self,
19982 range_utf16: Option<Range<usize>>,
19983 text: &str,
19984 new_selected_range_utf16: Option<Range<usize>>,
19985 window: &mut Window,
19986 cx: &mut Context<Self>,
19987 ) {
19988 if !self.input_enabled {
19989 return;
19990 }
19991
19992 let transaction = self.transact(window, cx, |this, window, cx| {
19993 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
19994 let snapshot = this.buffer.read(cx).read(cx);
19995 if let Some(relative_range_utf16) = range_utf16.as_ref() {
19996 for marked_range in &mut marked_ranges {
19997 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
19998 marked_range.start.0 += relative_range_utf16.start;
19999 marked_range.start =
20000 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
20001 marked_range.end =
20002 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
20003 }
20004 }
20005 Some(marked_ranges)
20006 } else if let Some(range_utf16) = range_utf16 {
20007 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20008 Some(this.selection_replacement_ranges(range_utf16, cx))
20009 } else {
20010 None
20011 };
20012
20013 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
20014 let newest_selection_id = this.selections.newest_anchor().id;
20015 this.selections
20016 .all::<OffsetUtf16>(cx)
20017 .iter()
20018 .zip(ranges_to_replace.iter())
20019 .find_map(|(selection, range)| {
20020 if selection.id == newest_selection_id {
20021 Some(
20022 (range.start.0 as isize - selection.head().0 as isize)
20023 ..(range.end.0 as isize - selection.head().0 as isize),
20024 )
20025 } else {
20026 None
20027 }
20028 })
20029 });
20030
20031 cx.emit(EditorEvent::InputHandled {
20032 utf16_range_to_replace: range_to_replace,
20033 text: text.into(),
20034 });
20035
20036 if let Some(ranges) = ranges_to_replace {
20037 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
20038 }
20039
20040 let marked_ranges = {
20041 let snapshot = this.buffer.read(cx).read(cx);
20042 this.selections
20043 .disjoint_anchors()
20044 .iter()
20045 .map(|selection| {
20046 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
20047 })
20048 .collect::<Vec<_>>()
20049 };
20050
20051 if text.is_empty() {
20052 this.unmark_text(window, cx);
20053 } else {
20054 this.highlight_text::<InputComposition>(
20055 marked_ranges.clone(),
20056 HighlightStyle {
20057 underline: Some(UnderlineStyle {
20058 thickness: px(1.),
20059 color: None,
20060 wavy: false,
20061 }),
20062 ..Default::default()
20063 },
20064 cx,
20065 );
20066 }
20067
20068 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
20069 let use_autoclose = this.use_autoclose;
20070 let use_auto_surround = this.use_auto_surround;
20071 this.set_use_autoclose(false);
20072 this.set_use_auto_surround(false);
20073 this.handle_input(text, window, cx);
20074 this.set_use_autoclose(use_autoclose);
20075 this.set_use_auto_surround(use_auto_surround);
20076
20077 if let Some(new_selected_range) = new_selected_range_utf16 {
20078 let snapshot = this.buffer.read(cx).read(cx);
20079 let new_selected_ranges = marked_ranges
20080 .into_iter()
20081 .map(|marked_range| {
20082 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
20083 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
20084 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
20085 snapshot.clip_offset_utf16(new_start, Bias::Left)
20086 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
20087 })
20088 .collect::<Vec<_>>();
20089
20090 drop(snapshot);
20091 this.change_selections(None, window, cx, |selections| {
20092 selections.select_ranges(new_selected_ranges)
20093 });
20094 }
20095 });
20096
20097 self.ime_transaction = self.ime_transaction.or(transaction);
20098 if let Some(transaction) = self.ime_transaction {
20099 self.buffer.update(cx, |buffer, cx| {
20100 buffer.group_until_transaction(transaction, cx);
20101 });
20102 }
20103
20104 if self.text_highlights::<InputComposition>(cx).is_none() {
20105 self.ime_transaction.take();
20106 }
20107 }
20108
20109 fn bounds_for_range(
20110 &mut self,
20111 range_utf16: Range<usize>,
20112 element_bounds: gpui::Bounds<Pixels>,
20113 window: &mut Window,
20114 cx: &mut Context<Self>,
20115 ) -> Option<gpui::Bounds<Pixels>> {
20116 let text_layout_details = self.text_layout_details(window);
20117 let gpui::Size {
20118 width: em_width,
20119 height: line_height,
20120 } = self.character_size(window);
20121
20122 let snapshot = self.snapshot(window, cx);
20123 let scroll_position = snapshot.scroll_position();
20124 let scroll_left = scroll_position.x * em_width;
20125
20126 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
20127 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
20128 + self.gutter_dimensions.width
20129 + self.gutter_dimensions.margin;
20130 let y = line_height * (start.row().as_f32() - scroll_position.y);
20131
20132 Some(Bounds {
20133 origin: element_bounds.origin + point(x, y),
20134 size: size(em_width, line_height),
20135 })
20136 }
20137
20138 fn character_index_for_point(
20139 &mut self,
20140 point: gpui::Point<Pixels>,
20141 _window: &mut Window,
20142 _cx: &mut Context<Self>,
20143 ) -> Option<usize> {
20144 let position_map = self.last_position_map.as_ref()?;
20145 if !position_map.text_hitbox.contains(&point) {
20146 return None;
20147 }
20148 let display_point = position_map.point_for_position(point).previous_valid;
20149 let anchor = position_map
20150 .snapshot
20151 .display_point_to_anchor(display_point, Bias::Left);
20152 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
20153 Some(utf16_offset.0)
20154 }
20155}
20156
20157trait SelectionExt {
20158 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
20159 fn spanned_rows(
20160 &self,
20161 include_end_if_at_line_start: bool,
20162 map: &DisplaySnapshot,
20163 ) -> Range<MultiBufferRow>;
20164}
20165
20166impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
20167 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
20168 let start = self
20169 .start
20170 .to_point(&map.buffer_snapshot)
20171 .to_display_point(map);
20172 let end = self
20173 .end
20174 .to_point(&map.buffer_snapshot)
20175 .to_display_point(map);
20176 if self.reversed {
20177 end..start
20178 } else {
20179 start..end
20180 }
20181 }
20182
20183 fn spanned_rows(
20184 &self,
20185 include_end_if_at_line_start: bool,
20186 map: &DisplaySnapshot,
20187 ) -> Range<MultiBufferRow> {
20188 let start = self.start.to_point(&map.buffer_snapshot);
20189 let mut end = self.end.to_point(&map.buffer_snapshot);
20190 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
20191 end.row -= 1;
20192 }
20193
20194 let buffer_start = map.prev_line_boundary(start).0;
20195 let buffer_end = map.next_line_boundary(end).0;
20196 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
20197 }
20198}
20199
20200impl<T: InvalidationRegion> InvalidationStack<T> {
20201 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
20202 where
20203 S: Clone + ToOffset,
20204 {
20205 while let Some(region) = self.last() {
20206 let all_selections_inside_invalidation_ranges =
20207 if selections.len() == region.ranges().len() {
20208 selections
20209 .iter()
20210 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
20211 .all(|(selection, invalidation_range)| {
20212 let head = selection.head().to_offset(buffer);
20213 invalidation_range.start <= head && invalidation_range.end >= head
20214 })
20215 } else {
20216 false
20217 };
20218
20219 if all_selections_inside_invalidation_ranges {
20220 break;
20221 } else {
20222 self.pop();
20223 }
20224 }
20225 }
20226}
20227
20228impl<T> Default for InvalidationStack<T> {
20229 fn default() -> Self {
20230 Self(Default::default())
20231 }
20232}
20233
20234impl<T> Deref for InvalidationStack<T> {
20235 type Target = Vec<T>;
20236
20237 fn deref(&self) -> &Self::Target {
20238 &self.0
20239 }
20240}
20241
20242impl<T> DerefMut for InvalidationStack<T> {
20243 fn deref_mut(&mut self) -> &mut Self::Target {
20244 &mut self.0
20245 }
20246}
20247
20248impl InvalidationRegion for SnippetState {
20249 fn ranges(&self) -> &[Range<Anchor>] {
20250 &self.ranges[self.active_index]
20251 }
20252}
20253
20254fn inline_completion_edit_text(
20255 current_snapshot: &BufferSnapshot,
20256 edits: &[(Range<Anchor>, String)],
20257 edit_preview: &EditPreview,
20258 include_deletions: bool,
20259 cx: &App,
20260) -> HighlightedText {
20261 let edits = edits
20262 .iter()
20263 .map(|(anchor, text)| {
20264 (
20265 anchor.start.text_anchor..anchor.end.text_anchor,
20266 text.clone(),
20267 )
20268 })
20269 .collect::<Vec<_>>();
20270
20271 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
20272}
20273
20274pub fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
20275 match severity {
20276 DiagnosticSeverity::ERROR => colors.error,
20277 DiagnosticSeverity::WARNING => colors.warning,
20278 DiagnosticSeverity::INFORMATION => colors.info,
20279 DiagnosticSeverity::HINT => colors.info,
20280 _ => colors.ignored,
20281 }
20282}
20283
20284pub fn styled_runs_for_code_label<'a>(
20285 label: &'a CodeLabel,
20286 syntax_theme: &'a theme::SyntaxTheme,
20287) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
20288 let fade_out = HighlightStyle {
20289 fade_out: Some(0.35),
20290 ..Default::default()
20291 };
20292
20293 let mut prev_end = label.filter_range.end;
20294 label
20295 .runs
20296 .iter()
20297 .enumerate()
20298 .flat_map(move |(ix, (range, highlight_id))| {
20299 let style = if let Some(style) = highlight_id.style(syntax_theme) {
20300 style
20301 } else {
20302 return Default::default();
20303 };
20304 let mut muted_style = style;
20305 muted_style.highlight(fade_out);
20306
20307 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
20308 if range.start >= label.filter_range.end {
20309 if range.start > prev_end {
20310 runs.push((prev_end..range.start, fade_out));
20311 }
20312 runs.push((range.clone(), muted_style));
20313 } else if range.end <= label.filter_range.end {
20314 runs.push((range.clone(), style));
20315 } else {
20316 runs.push((range.start..label.filter_range.end, style));
20317 runs.push((label.filter_range.end..range.end, muted_style));
20318 }
20319 prev_end = cmp::max(prev_end, range.end);
20320
20321 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
20322 runs.push((prev_end..label.text.len(), fade_out));
20323 }
20324
20325 runs
20326 })
20327}
20328
20329pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
20330 let mut prev_index = 0;
20331 let mut prev_codepoint: Option<char> = None;
20332 text.char_indices()
20333 .chain([(text.len(), '\0')])
20334 .filter_map(move |(index, codepoint)| {
20335 let prev_codepoint = prev_codepoint.replace(codepoint)?;
20336 let is_boundary = index == text.len()
20337 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
20338 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
20339 if is_boundary {
20340 let chunk = &text[prev_index..index];
20341 prev_index = index;
20342 Some(chunk)
20343 } else {
20344 None
20345 }
20346 })
20347}
20348
20349pub trait RangeToAnchorExt: Sized {
20350 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
20351
20352 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
20353 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
20354 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
20355 }
20356}
20357
20358impl<T: ToOffset> RangeToAnchorExt for Range<T> {
20359 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
20360 let start_offset = self.start.to_offset(snapshot);
20361 let end_offset = self.end.to_offset(snapshot);
20362 if start_offset == end_offset {
20363 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
20364 } else {
20365 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
20366 }
20367 }
20368}
20369
20370pub trait RowExt {
20371 fn as_f32(&self) -> f32;
20372
20373 fn next_row(&self) -> Self;
20374
20375 fn previous_row(&self) -> Self;
20376
20377 fn minus(&self, other: Self) -> u32;
20378}
20379
20380impl RowExt for DisplayRow {
20381 fn as_f32(&self) -> f32 {
20382 self.0 as f32
20383 }
20384
20385 fn next_row(&self) -> Self {
20386 Self(self.0 + 1)
20387 }
20388
20389 fn previous_row(&self) -> Self {
20390 Self(self.0.saturating_sub(1))
20391 }
20392
20393 fn minus(&self, other: Self) -> u32 {
20394 self.0 - other.0
20395 }
20396}
20397
20398impl RowExt for MultiBufferRow {
20399 fn as_f32(&self) -> f32 {
20400 self.0 as f32
20401 }
20402
20403 fn next_row(&self) -> Self {
20404 Self(self.0 + 1)
20405 }
20406
20407 fn previous_row(&self) -> Self {
20408 Self(self.0.saturating_sub(1))
20409 }
20410
20411 fn minus(&self, other: Self) -> u32 {
20412 self.0 - other.0
20413 }
20414}
20415
20416trait RowRangeExt {
20417 type Row;
20418
20419 fn len(&self) -> usize;
20420
20421 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
20422}
20423
20424impl RowRangeExt for Range<MultiBufferRow> {
20425 type Row = MultiBufferRow;
20426
20427 fn len(&self) -> usize {
20428 (self.end.0 - self.start.0) as usize
20429 }
20430
20431 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
20432 (self.start.0..self.end.0).map(MultiBufferRow)
20433 }
20434}
20435
20436impl RowRangeExt for Range<DisplayRow> {
20437 type Row = DisplayRow;
20438
20439 fn len(&self) -> usize {
20440 (self.end.0 - self.start.0) as usize
20441 }
20442
20443 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
20444 (self.start.0..self.end.0).map(DisplayRow)
20445 }
20446}
20447
20448/// If select range has more than one line, we
20449/// just point the cursor to range.start.
20450fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
20451 if range.start.row == range.end.row {
20452 range
20453 } else {
20454 range.start..range.start
20455 }
20456}
20457pub struct KillRing(ClipboardItem);
20458impl Global for KillRing {}
20459
20460const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
20461
20462enum BreakpointPromptEditAction {
20463 Log,
20464 Condition,
20465 HitCondition,
20466}
20467
20468struct BreakpointPromptEditor {
20469 pub(crate) prompt: Entity<Editor>,
20470 editor: WeakEntity<Editor>,
20471 breakpoint_anchor: Anchor,
20472 breakpoint: Breakpoint,
20473 edit_action: BreakpointPromptEditAction,
20474 block_ids: HashSet<CustomBlockId>,
20475 gutter_dimensions: Arc<Mutex<GutterDimensions>>,
20476 _subscriptions: Vec<Subscription>,
20477}
20478
20479impl BreakpointPromptEditor {
20480 const MAX_LINES: u8 = 4;
20481
20482 fn new(
20483 editor: WeakEntity<Editor>,
20484 breakpoint_anchor: Anchor,
20485 breakpoint: Breakpoint,
20486 edit_action: BreakpointPromptEditAction,
20487 window: &mut Window,
20488 cx: &mut Context<Self>,
20489 ) -> Self {
20490 let base_text = match edit_action {
20491 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
20492 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
20493 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
20494 }
20495 .map(|msg| msg.to_string())
20496 .unwrap_or_default();
20497
20498 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
20499 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
20500
20501 let prompt = cx.new(|cx| {
20502 let mut prompt = Editor::new(
20503 EditorMode::AutoHeight {
20504 max_lines: Self::MAX_LINES as usize,
20505 },
20506 buffer,
20507 None,
20508 window,
20509 cx,
20510 );
20511 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
20512 prompt.set_show_cursor_when_unfocused(false, cx);
20513 prompt.set_placeholder_text(
20514 match edit_action {
20515 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
20516 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
20517 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
20518 },
20519 cx,
20520 );
20521
20522 prompt
20523 });
20524
20525 Self {
20526 prompt,
20527 editor,
20528 breakpoint_anchor,
20529 breakpoint,
20530 edit_action,
20531 gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
20532 block_ids: Default::default(),
20533 _subscriptions: vec![],
20534 }
20535 }
20536
20537 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
20538 self.block_ids.extend(block_ids)
20539 }
20540
20541 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
20542 if let Some(editor) = self.editor.upgrade() {
20543 let message = self
20544 .prompt
20545 .read(cx)
20546 .buffer
20547 .read(cx)
20548 .as_singleton()
20549 .expect("A multi buffer in breakpoint prompt isn't possible")
20550 .read(cx)
20551 .as_rope()
20552 .to_string();
20553
20554 editor.update(cx, |editor, cx| {
20555 editor.edit_breakpoint_at_anchor(
20556 self.breakpoint_anchor,
20557 self.breakpoint.clone(),
20558 match self.edit_action {
20559 BreakpointPromptEditAction::Log => {
20560 BreakpointEditAction::EditLogMessage(message.into())
20561 }
20562 BreakpointPromptEditAction::Condition => {
20563 BreakpointEditAction::EditCondition(message.into())
20564 }
20565 BreakpointPromptEditAction::HitCondition => {
20566 BreakpointEditAction::EditHitCondition(message.into())
20567 }
20568 },
20569 cx,
20570 );
20571
20572 editor.remove_blocks(self.block_ids.clone(), None, cx);
20573 cx.focus_self(window);
20574 });
20575 }
20576 }
20577
20578 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
20579 self.editor
20580 .update(cx, |editor, cx| {
20581 editor.remove_blocks(self.block_ids.clone(), None, cx);
20582 window.focus(&editor.focus_handle);
20583 })
20584 .log_err();
20585 }
20586
20587 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
20588 let settings = ThemeSettings::get_global(cx);
20589 let text_style = TextStyle {
20590 color: if self.prompt.read(cx).read_only(cx) {
20591 cx.theme().colors().text_disabled
20592 } else {
20593 cx.theme().colors().text
20594 },
20595 font_family: settings.buffer_font.family.clone(),
20596 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20597 font_size: settings.buffer_font_size(cx).into(),
20598 font_weight: settings.buffer_font.weight,
20599 line_height: relative(settings.buffer_line_height.value()),
20600 ..Default::default()
20601 };
20602 EditorElement::new(
20603 &self.prompt,
20604 EditorStyle {
20605 background: cx.theme().colors().editor_background,
20606 local_player: cx.theme().players().local(),
20607 text: text_style,
20608 ..Default::default()
20609 },
20610 )
20611 }
20612}
20613
20614impl Render for BreakpointPromptEditor {
20615 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20616 let gutter_dimensions = *self.gutter_dimensions.lock();
20617 h_flex()
20618 .key_context("Editor")
20619 .bg(cx.theme().colors().editor_background)
20620 .border_y_1()
20621 .border_color(cx.theme().status().info_border)
20622 .size_full()
20623 .py(window.line_height() / 2.5)
20624 .on_action(cx.listener(Self::confirm))
20625 .on_action(cx.listener(Self::cancel))
20626 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
20627 .child(div().flex_1().child(self.render_prompt_editor(cx)))
20628 }
20629}
20630
20631impl Focusable for BreakpointPromptEditor {
20632 fn focus_handle(&self, cx: &App) -> FocusHandle {
20633 self.prompt.focus_handle(cx)
20634 }
20635}
20636
20637fn all_edits_insertions_or_deletions(
20638 edits: &Vec<(Range<Anchor>, String)>,
20639 snapshot: &MultiBufferSnapshot,
20640) -> bool {
20641 let mut all_insertions = true;
20642 let mut all_deletions = true;
20643
20644 for (range, new_text) in edits.iter() {
20645 let range_is_empty = range.to_offset(&snapshot).is_empty();
20646 let text_is_empty = new_text.is_empty();
20647
20648 if range_is_empty != text_is_empty {
20649 if range_is_empty {
20650 all_deletions = false;
20651 } else {
20652 all_insertions = false;
20653 }
20654 } else {
20655 return false;
20656 }
20657
20658 if !all_insertions && !all_deletions {
20659 return false;
20660 }
20661 }
20662 all_insertions || all_deletions
20663}
20664
20665struct MissingEditPredictionKeybindingTooltip;
20666
20667impl Render for MissingEditPredictionKeybindingTooltip {
20668 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20669 ui::tooltip_container(window, cx, |container, _, cx| {
20670 container
20671 .flex_shrink_0()
20672 .max_w_80()
20673 .min_h(rems_from_px(124.))
20674 .justify_between()
20675 .child(
20676 v_flex()
20677 .flex_1()
20678 .text_ui_sm(cx)
20679 .child(Label::new("Conflict with Accept Keybinding"))
20680 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
20681 )
20682 .child(
20683 h_flex()
20684 .pb_1()
20685 .gap_1()
20686 .items_end()
20687 .w_full()
20688 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
20689 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
20690 }))
20691 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
20692 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
20693 })),
20694 )
20695 })
20696 }
20697}
20698
20699#[derive(Debug, Clone, Copy, PartialEq)]
20700pub struct LineHighlight {
20701 pub background: Background,
20702 pub border: Option<gpui::Hsla>,
20703}
20704
20705impl From<Hsla> for LineHighlight {
20706 fn from(hsla: Hsla) -> Self {
20707 Self {
20708 background: hsla.into(),
20709 border: None,
20710 }
20711 }
20712}
20713
20714impl From<Background> for LineHighlight {
20715 fn from(background: Background) -> Self {
20716 Self {
20717 background,
20718 border: None,
20719 }
20720 }
20721}
20722
20723fn render_diff_hunk_controls(
20724 row: u32,
20725 status: &DiffHunkStatus,
20726 hunk_range: Range<Anchor>,
20727 is_created_file: bool,
20728 line_height: Pixels,
20729 editor: &Entity<Editor>,
20730 _window: &mut Window,
20731 cx: &mut App,
20732) -> AnyElement {
20733 h_flex()
20734 .h(line_height)
20735 .mr_1()
20736 .gap_1()
20737 .px_0p5()
20738 .pb_1()
20739 .border_x_1()
20740 .border_b_1()
20741 .border_color(cx.theme().colors().border_variant)
20742 .rounded_b_lg()
20743 .bg(cx.theme().colors().editor_background)
20744 .gap_1()
20745 .occlude()
20746 .shadow_md()
20747 .child(if status.has_secondary_hunk() {
20748 Button::new(("stage", row as u64), "Stage")
20749 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20750 .tooltip({
20751 let focus_handle = editor.focus_handle(cx);
20752 move |window, cx| {
20753 Tooltip::for_action_in(
20754 "Stage Hunk",
20755 &::git::ToggleStaged,
20756 &focus_handle,
20757 window,
20758 cx,
20759 )
20760 }
20761 })
20762 .on_click({
20763 let editor = editor.clone();
20764 move |_event, _window, cx| {
20765 editor.update(cx, |editor, cx| {
20766 editor.stage_or_unstage_diff_hunks(
20767 true,
20768 vec![hunk_range.start..hunk_range.start],
20769 cx,
20770 );
20771 });
20772 }
20773 })
20774 } else {
20775 Button::new(("unstage", row as u64), "Unstage")
20776 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20777 .tooltip({
20778 let focus_handle = editor.focus_handle(cx);
20779 move |window, cx| {
20780 Tooltip::for_action_in(
20781 "Unstage Hunk",
20782 &::git::ToggleStaged,
20783 &focus_handle,
20784 window,
20785 cx,
20786 )
20787 }
20788 })
20789 .on_click({
20790 let editor = editor.clone();
20791 move |_event, _window, cx| {
20792 editor.update(cx, |editor, cx| {
20793 editor.stage_or_unstage_diff_hunks(
20794 false,
20795 vec![hunk_range.start..hunk_range.start],
20796 cx,
20797 );
20798 });
20799 }
20800 })
20801 })
20802 .child(
20803 Button::new(("restore", row as u64), "Restore")
20804 .tooltip({
20805 let focus_handle = editor.focus_handle(cx);
20806 move |window, cx| {
20807 Tooltip::for_action_in(
20808 "Restore Hunk",
20809 &::git::Restore,
20810 &focus_handle,
20811 window,
20812 cx,
20813 )
20814 }
20815 })
20816 .on_click({
20817 let editor = editor.clone();
20818 move |_event, window, cx| {
20819 editor.update(cx, |editor, cx| {
20820 let snapshot = editor.snapshot(window, cx);
20821 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
20822 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
20823 });
20824 }
20825 })
20826 .disabled(is_created_file),
20827 )
20828 .when(
20829 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
20830 |el| {
20831 el.child(
20832 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
20833 .shape(IconButtonShape::Square)
20834 .icon_size(IconSize::Small)
20835 // .disabled(!has_multiple_hunks)
20836 .tooltip({
20837 let focus_handle = editor.focus_handle(cx);
20838 move |window, cx| {
20839 Tooltip::for_action_in(
20840 "Next Hunk",
20841 &GoToHunk,
20842 &focus_handle,
20843 window,
20844 cx,
20845 )
20846 }
20847 })
20848 .on_click({
20849 let editor = editor.clone();
20850 move |_event, window, cx| {
20851 editor.update(cx, |editor, cx| {
20852 let snapshot = editor.snapshot(window, cx);
20853 let position =
20854 hunk_range.end.to_point(&snapshot.buffer_snapshot);
20855 editor.go_to_hunk_before_or_after_position(
20856 &snapshot,
20857 position,
20858 Direction::Next,
20859 window,
20860 cx,
20861 );
20862 editor.expand_selected_diff_hunks(cx);
20863 });
20864 }
20865 }),
20866 )
20867 .child(
20868 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
20869 .shape(IconButtonShape::Square)
20870 .icon_size(IconSize::Small)
20871 // .disabled(!has_multiple_hunks)
20872 .tooltip({
20873 let focus_handle = editor.focus_handle(cx);
20874 move |window, cx| {
20875 Tooltip::for_action_in(
20876 "Previous Hunk",
20877 &GoToPreviousHunk,
20878 &focus_handle,
20879 window,
20880 cx,
20881 )
20882 }
20883 })
20884 .on_click({
20885 let editor = editor.clone();
20886 move |_event, window, cx| {
20887 editor.update(cx, |editor, cx| {
20888 let snapshot = editor.snapshot(window, cx);
20889 let point =
20890 hunk_range.start.to_point(&snapshot.buffer_snapshot);
20891 editor.go_to_hunk_before_or_after_position(
20892 &snapshot,
20893 point,
20894 Direction::Prev,
20895 window,
20896 cx,
20897 );
20898 editor.expand_selected_diff_hunks(cx);
20899 });
20900 }
20901 }),
20902 )
20903 },
20904 )
20905 .into_any_element()
20906}